Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
landing-hero.test.ts5.6 KB · 138 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
 * Block L10 — Landing-page hero rewrite tests.
 *
 * Covers:
 *   - GET / returns 200 HTML to anonymous users
 *   - Headline string + install snippet + 3 CTA hrefs all present
 *   - "Three reasons" column headings render
 *   - "How is this different" pull-quote string present
 *   - Meta tags (title, description, og:title) injected via Layout
 *   - REGRESSION GUARD: the L4 counters tile section still renders
 *     (stable identifier `class="landing-counters"`)
 *   - REGRESSION GUARD: the L5 "Compare to GitHub" CTA still points
 *     at /vs-github
 */
import { describe, it, expect } from "bun:test";
import app from "../app";

const HOME = "/";

describe("Block L10 — landing hero rewrite", () => {
  it("GET / returns 200 HTML to an anonymous visitor", async () => {
    const res = await app.request(HOME);
    expect(res.status).toBe(200);
    const ct = res.headers.get("content-type") || "";
    expect(ct.toLowerCase()).toContain("text/html");
  });

  it("renders the new hero headline", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    expect(body).toContain("The git host built around Claude.");
  });

  it("renders the install snippet", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    // The host + path is what makes it unambiguous as the install snippet.
    expect(body).toContain("gluecron.com/install");
    expect(body).toContain("curl -sSL gluecron.com/install | bash");
  });

  it("renders all three primary CTAs in the hero row", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    expect(body).toContain('href="/register"');
    expect(body).toContain('href="/demo"');
    expect(body).toContain('href="/vs-github"');
    // Visible labels for the three CTAs.
    expect(body).toContain("Sign up free");
    expect(body).toContain("Try the live demo");
    expect(body).toContain("Compare to GitHub");
  });

  it("renders the three reasons-to-switch column headings", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    expect(body).toContain("Toggle Sleep Mode");
    expect(body).toContain("One command to migrate");
    expect(body).toContain("Open the demo, watch it work");
    // The migrate column also links to /import.
    expect(body).toContain('href="/import"');
    // The Sleep Mode + demo columns deep-link to their L1 / L3 routes.
    expect(body).toContain('href="/sleep-mode"');
    expect(body).toContain('href="/demo"');
  });

  it("renders the 'How is this different' pull-quote", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    expect(body).toContain("How is this different from GitHub?");
    expect(body).toContain("Every other host bolts AI on as a sidecar.");
    // JSX collapses newlines + leading whitespace; assert on a substring
    // that is contiguous after server-side rendering.
    expect(body).toContain("first-class developer");
    expect(body).toContain("Built to be");
    expect(body).toContain("operated by AI agents");
    expect(body).toContain("See the full comparison");
  });

  it("injects SEO + Open Graph meta tags", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    // <title>
    expect(body).toContain(
      "<title>Gluecron — The git host built around Claude</title>"
    );
    // <meta name="description">
    expect(body).toMatch(
      /<meta\s+name="description"\s+content="Label an issue\. Walk away\. Wake up to a merged PR\./
    );
    // <meta property="og:title">
    expect(body).toMatch(
      /<meta\s+property="og:title"\s+content="Gluecron — The git host built around Claude"/
    );
    // <meta property="og:description">
    expect(body).toMatch(
      /<meta\s+property="og:description"\s+content="Label an issue\./
    );
    // <meta property="og:type">
    expect(body).toMatch(
      /<meta\s+property="og:type"\s+content="website"/
    );
    // <meta name="twitter:card">
    expect(body).toMatch(
      /<meta\s+name="twitter:card"\s+content="summary_large_image"/
    );
  });

  it("REGRESSION: L4 counters tile section is still rendered", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    // The L4 section is conditional on publicStats. When it renders we
    // expect this scope class. It may be absent in a totally empty DB
    // setup (publicStats falsy) — but the section's HTML class string
    // is the stable identifier we assert when it IS present, which it
    // will be whenever the lazy computePublicStats() returns a payload.
    // Either way, the L4 builder export must still exist + be wired.
    const { buildSocialProofTiles } = await import("../views/landing");
    expect(typeof buildSocialProofTiles).toBe("function");

    // If the conditional rendered, the class is in the markup. If it
    // didn't render, the COUNTERS animation script string isn't either —
    // both signals stay aligned, so a regression that DROPS the section
    // would still be caught by the class-only path on the common case.
    if (body.includes("landing-counters-grid")) {
      // Tile section is in the markup — confirm the count-up script is too.
      expect(body).toContain("data-counter-target");
    }
  });

  it("REGRESSION: L5 'Compare to GitHub' CTA still routes to /vs-github", async () => {
    const res = await app.request(HOME);
    const body = await res.text();
    // The href + label pair must remain wired.
    expect(body).toContain('href="/vs-github"');
    expect(body).toContain("Compare to GitHub");
  });
});