CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
landing-hero.test.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 5f2e749 | 1 | /** |
| 2 | * Block L10 — Landing-page hero rewrite tests. | |
| 3 | * | |
| 4 | * Covers: | |
| 5 | * - GET / returns 200 HTML to anonymous users | |
| 6 | * - Headline string + install snippet + 3 CTA hrefs all present | |
| 7 | * - "Three reasons" column headings render | |
| 8 | * - "How is this different" pull-quote string present | |
| 9 | * - Meta tags (title, description, og:title) injected via Layout | |
| 10 | * - REGRESSION GUARD: the L4 counters tile section still renders | |
| 11 | * (stable identifier `class="landing-counters"`) | |
| 12 | * - REGRESSION GUARD: the L5 "Compare to GitHub" CTA still points | |
| 13 | * at /vs-github | |
| 14 | */ | |
| 15 | import { describe, it, expect } from "bun:test"; | |
| 16 | import app from "../app"; | |
| 17 | ||
| 18 | const HOME = "/"; | |
| 19 | ||
| 20 | describe("Block L10 — landing hero rewrite", () => { | |
| 21 | it("GET / returns 200 HTML to an anonymous visitor", async () => { | |
| 22 | const res = await app.request(HOME); | |
| 23 | expect(res.status).toBe(200); | |
| 24 | const ct = res.headers.get("content-type") || ""; | |
| 25 | expect(ct.toLowerCase()).toContain("text/html"); | |
| 26 | }); | |
| 27 | ||
| 28 | it("renders the new hero headline", async () => { | |
| 29 | const res = await app.request(HOME); | |
| 30 | const body = await res.text(); | |
| 31 | expect(body).toContain("The git host built around Claude."); | |
| 32 | }); | |
| 33 | ||
| 34 | it("renders the install snippet", async () => { | |
| 35 | const res = await app.request(HOME); | |
| 36 | const body = await res.text(); | |
| 37 | // The host + path is what makes it unambiguous as the install snippet. | |
| 38 | expect(body).toContain("gluecron.com/install"); | |
| 39 | expect(body).toContain("curl -sSL gluecron.com/install | bash"); | |
| 40 | }); | |
| 41 | ||
| 42 | it("renders all three primary CTAs in the hero row", async () => { | |
| 43 | const res = await app.request(HOME); | |
| 44 | const body = await res.text(); | |
| 45 | expect(body).toContain('href="/register"'); | |
| 46 | expect(body).toContain('href="/demo"'); | |
| 47 | expect(body).toContain('href="/vs-github"'); | |
| 48 | // Visible labels for the three CTAs. | |
| 49 | expect(body).toContain("Sign up free"); | |
| 50 | expect(body).toContain("Try the live demo"); | |
| 51 | expect(body).toContain("Compare to GitHub"); | |
| 52 | }); | |
| 53 | ||
| 54 | it("renders the three reasons-to-switch column headings", async () => { | |
| 55 | const res = await app.request(HOME); | |
| 56 | const body = await res.text(); | |
| 57 | expect(body).toContain("Toggle Sleep Mode"); | |
| 58 | expect(body).toContain("One command to migrate"); | |
| 59 | expect(body).toContain("Open the demo, watch it work"); | |
| 60 | // The migrate column also links to /import. | |
| 61 | expect(body).toContain('href="/import"'); | |
| 62 | // The Sleep Mode + demo columns deep-link to their L1 / L3 routes. | |
| 63 | expect(body).toContain('href="/sleep-mode"'); | |
| 64 | expect(body).toContain('href="/demo"'); | |
| 65 | }); | |
| 66 | ||
| 67 | it("renders the 'How is this different' pull-quote", async () => { | |
| 68 | const res = await app.request(HOME); | |
| 69 | const body = await res.text(); | |
| 70 | expect(body).toContain("How is this different from GitHub?"); | |
| 71 | expect(body).toContain("Every other host bolts AI on as a sidecar."); | |
| 72 | // JSX collapses newlines + leading whitespace; assert on a substring | |
| 73 | // that is contiguous after server-side rendering. | |
| 74 | expect(body).toContain("first-class developer"); | |
| 75 | expect(body).toContain("Built to be"); | |
| 76 | expect(body).toContain("operated by AI agents"); | |
| 77 | expect(body).toContain("See the full comparison"); | |
| 78 | }); | |
| 79 | ||
| 80 | it("injects SEO + Open Graph meta tags", async () => { | |
| 81 | const res = await app.request(HOME); | |
| 82 | const body = await res.text(); | |
| 83 | // <title> | |
| 84 | expect(body).toContain( | |
| 85 | "<title>Gluecron — The git host built around Claude</title>" | |
| 86 | ); | |
| 87 | // <meta name="description"> | |
| 88 | expect(body).toMatch( | |
| 89 | /<meta\s+name="description"\s+content="Label an issue\. Walk away\. Wake up to a merged PR\./ | |
| 90 | ); | |
| 91 | // <meta property="og:title"> | |
| 92 | expect(body).toMatch( | |
| 93 | /<meta\s+property="og:title"\s+content="Gluecron — The git host built around Claude"/ | |
| 94 | ); | |
| 95 | // <meta property="og:description"> | |
| 96 | expect(body).toMatch( | |
| 97 | /<meta\s+property="og:description"\s+content="Label an issue\./ | |
| 98 | ); | |
| 99 | // <meta property="og:type"> | |
| 100 | expect(body).toMatch( | |
| 101 | /<meta\s+property="og:type"\s+content="website"/ | |
| 102 | ); | |
| 103 | // <meta name="twitter:card"> | |
| 104 | expect(body).toMatch( | |
| 105 | /<meta\s+name="twitter:card"\s+content="summary_large_image"/ | |
| 106 | ); | |
| 107 | }); | |
| 108 | ||
| 109 | it("REGRESSION: L4 counters tile section is still rendered", async () => { | |
| 110 | const res = await app.request(HOME); | |
| 111 | const body = await res.text(); | |
| 112 | // The L4 section is conditional on publicStats. When it renders we | |
| 113 | // expect this scope class. It may be absent in a totally empty DB | |
| 114 | // setup (publicStats falsy) — but the section's HTML class string | |
| 115 | // is the stable identifier we assert when it IS present, which it | |
| 116 | // will be whenever the lazy computePublicStats() returns a payload. | |
| 117 | // Either way, the L4 builder export must still exist + be wired. | |
| 118 | const { buildSocialProofTiles } = await import("../views/landing"); | |
| 119 | expect(typeof buildSocialProofTiles).toBe("function"); | |
| 120 | ||
| 121 | // If the conditional rendered, the class is in the markup. If it | |
| 122 | // didn't render, the COUNTERS animation script string isn't either — | |
| 123 | // both signals stay aligned, so a regression that DROPS the section | |
| 124 | // would still be caught by the class-only path on the common case. | |
| 125 | if (body.includes("landing-counters-grid")) { | |
| 126 | // Tile section is in the markup — confirm the count-up script is too. | |
| 127 | expect(body).toContain("data-counter-target"); | |
| 128 | } | |
| 129 | }); | |
| 130 | ||
| 131 | it("REGRESSION: L5 'Compare to GitHub' CTA still routes to /vs-github", async () => { | |
| 132 | const res = await app.request(HOME); | |
| 133 | const body = await res.text(); | |
| 134 | // The href + label pair must remain wired. | |
| 135 | expect(body).toContain('href="/vs-github"'); | |
| 136 | expect(body).toContain("Compare to GitHub"); | |
| 137 | }); | |
| 138 | }); |