CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
u-polish.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.
| dc26881 | 1 | /** |
| 2 | * Block U — Senior polish pass smoke tests. | |
| 3 | * | |
| 74a8784 | 4 | * 2026-06-10 update: `GET /` now serves the self-contained |
| 5 | * `Landing2030Page` (src/views/landing-2030.tsx) — the legacy | |
| 6 | * `LandingPage` + master Layout CSS no longer render on the home route. | |
| 7 | * U1 assertions therefore target the 2030 hero contract; U2/U4 master-CSS | |
| 8 | * assertions target /help, a public Layout-rendered page that needs no DB. | |
| 9 | * | |
| 10 | * U1: hero — exactly two CTAs in the hero actions row, trust line | |
| 11 | * beneath, product-card mock below the CTAs. | |
| dc26881 | 12 | * U2: button CSS — hover lifts every .btn by translateY(-1px) with a |
| 13 | * soft drop shadow; focus-visible uses box-shadow (not outline); | |
| 14 | * primary CTA shimmers via background-position; disabled buttons | |
| 15 | * never lift. | |
| 16 | * U4: master CSS contains the @view-transition block and the | |
| 17 | * prefers-reduced-motion guard. | |
| 18 | * | |
| 19 | * No DB stubs, no mock pollution — pure rendering checks against the | |
| 20 | * already-mounted Hono app. | |
| 21 | */ | |
| 22 | import { describe, it, expect } from "bun:test"; | |
| 23 | import app from "../app"; | |
| 24 | ||
| 25 | const HOME = "/"; | |
| 74a8784 | 26 | // Public, Layout-rendered, DB-free page that carries the master CSS. |
| 27 | const LAYOUT_PAGE = "/help"; | |
| dc26881 | 28 | |
| 29 | async function fetchHomeHtml(): Promise<string> { | |
| 30 | const res = await app.request(HOME); | |
| 31 | expect(res.status).toBe(200); | |
| 32 | return await res.text(); | |
| 33 | } | |
| 34 | ||
| 74a8784 | 35 | async function fetchLayoutHtml(): Promise<string> { |
| 36 | const res = await app.request(LAYOUT_PAGE); | |
| 37 | expect(res.status).toBe(200); | |
| 38 | return await res.text(); | |
| 39 | } | |
| 40 | ||
| 032ae5f | 41 | describe("landing hero — primary CTA contract", () => { |
| 42 | // Home `/` renders LandingProPage (src/views/landing-pro.tsx). These | |
| 43 | // assert the live hero contract (`lp-hero-*`), replacing the retired | |
| 44 | // "2030 reboot" markup (hero-actions / hero-trust / hero-card). | |
| 45 | it("renders the primary CTAs in the hero row", async () => { | |
| dc26881 | 46 | const body = await fetchHomeHtml(); |
| 032ae5f | 47 | const start = body.indexOf('class="lp-hero-ctas"'); |
| dc26881 | 48 | expect(start).toBeGreaterThan(-1); |
| 032ae5f | 49 | const tail = body.slice(start, start + 400); |
| 50 | expect(tail).toContain('href="/register"'); // Sign up free | |
| 51 | expect(tail).toContain('href="/import"'); // Migrate from GitHub | |
| 52 | const anchorMatches = tail.match(/<a[^>]*class="lp-btn[^"]*"/g) || []; | |
| 74a8784 | 53 | expect(anchorMatches.length).toBe(2); |
| dc26881 | 54 | }); |
| 55 | ||
| 032ae5f | 56 | it("renders the hero links row beneath the primary CTAs", async () => { |
| dc26881 | 57 | const body = await fetchHomeHtml(); |
| 032ae5f | 58 | const ctas = body.indexOf('class="lp-hero-ctas"'); |
| 59 | const links = body.indexOf('class="lp-hero-links"'); | |
| 74a8784 | 60 | expect(ctas).toBeGreaterThan(-1); |
| 032ae5f | 61 | expect(links).toBeGreaterThan(ctas); |
| 62 | expect(body.slice(links, links + 200)).toContain('href="/demo"'); | |
| dc26881 | 63 | }); |
| 64 | ||
| 74a8784 | 65 | it("places the product-card mock below the CTAs", async () => { |
| dc26881 | 66 | const body = await fetchHomeHtml(); |
| 032ae5f | 67 | const ctas = body.indexOf('class="lp-hero-ctas"'); |
| 68 | const card = body.indexOf('class="lp-hero-card"'); | |
| dc26881 | 69 | expect(ctas).toBeGreaterThan(-1); |
| 74a8784 | 70 | expect(card).toBeGreaterThan(ctas); |
| dc26881 | 71 | }); |
| 72 | }); | |
| 73 | ||
| 74 | describe("Block U2 — button polish", () => { | |
| 75 | it("includes the universal hover-lift transform on .btn", async () => { | |
| 74a8784 | 76 | const body = await fetchLayoutHtml(); |
| dc26881 | 77 | // The master CSS is inlined in <style> by Layout. We assert on the |
| 78 | // hover-rule shape: `.btn:hover { … transform: translateY(-1px); … }`. | |
| 79 | expect(body).toMatch(/\.btn:hover\b[\s\S]*?transform:\s*translateY\(-1px\)/); | |
| 80 | }); | |
| 81 | ||
| 82 | it("uses a soft drop shadow on .btn:hover (not just border)", async () => { | |
| 74a8784 | 83 | const body = await fetchLayoutHtml(); |
| dc26881 | 84 | expect(body).toMatch(/\.btn:hover\b[\s\S]*?box-shadow:\s*0\s+4px\s+12px/); |
| 85 | }); | |
| 86 | ||
| 87 | it("uses box-shadow (not outline) for the .btn focus ring", async () => { | |
| 74a8784 | 88 | const body = await fetchLayoutHtml(); |
| 1c60be8 | 89 | // Focus-visible rule must set box-shadow with the calm indigo accent rgba. |
| 90 | // Calm Infrastructure restyle (2026-06-13): purple #8c6dff → indigo #5b6ee8 | |
| dc26881 | 91 | expect(body).toMatch( |
| 1c60be8 | 92 | /\.btn:focus-visible\b[\s\S]*?box-shadow:\s*0\s+0\s+0\s+3px\s+rgba\(91,\s*110,\s*232,\s*0\.35\)/ |
| dc26881 | 93 | ); |
| 94 | // And must NOT fall back to outline:2px on .btn anywhere. | |
| 95 | expect(body).not.toMatch(/\.btn:focus-visible[^{]*\{\s*outline:\s*2px/); | |
| 96 | }); | |
| 97 | ||
| 98 | it("disables hover-lift on disabled buttons", async () => { | |
| 74a8784 | 99 | const body = await fetchLayoutHtml(); |
| dc26881 | 100 | // The disabled rule sets transform:none AND opacity:0.5. |
| 101 | expect(body).toMatch(/\.btn:disabled[\s\S]*?transform:\s*none/); | |
| 102 | expect(body).toMatch(/\.btn:disabled[\s\S]*?opacity:\s*0\.5/); | |
| 103 | }); | |
| 104 | ||
| 1c60be8 | 105 | it("btn-primary has a gradient background and smooth transitions", async () => { |
| 74a8784 | 106 | const body = await fetchLayoutHtml(); |
| 1c60be8 | 107 | // Calm Infrastructure restyle (2026-06-13): shimmer animation removed; |
| 108 | // replaced with a solid accent-gradient + fast transform/box-shadow transitions. | |
| 109 | expect(body).toMatch(/\.btn-primary\b[\s\S]*?background:\s*var\(--accent-gradient\)/); | |
| 110 | expect(body).toMatch(/\.btn-primary\b[\s\S]*?transition:[\s\S]*?transform\s+180ms/); | |
| dc26881 | 111 | }); |
| 112 | }); | |
| 113 | ||
| 114 | describe("Block U4 — view transitions", () => { | |
| 115 | it("includes the @view-transition opt-in", async () => { | |
| 74a8784 | 116 | const body = await fetchLayoutHtml(); |
| dc26881 | 117 | expect(body).toContain("@view-transition"); |
| 118 | expect(body).toMatch(/@view-transition\s*\{\s*navigation:\s*auto;?\s*\}/); | |
| 119 | }); | |
| 120 | ||
| 121 | it("declares both fade-out and fade-in keyframes for ::view-transition-*", async () => { | |
| 74a8784 | 122 | const body = await fetchLayoutHtml(); |
| dc26881 | 123 | expect(body).toContain("::view-transition-old(root)"); |
| 124 | expect(body).toContain("::view-transition-new(root)"); | |
| 125 | expect(body).toContain("@keyframes vt-fade-out"); | |
| 126 | expect(body).toContain("@keyframes vt-fade-in"); | |
| 127 | }); | |
| 128 | ||
| 129 | it("disables the transition under prefers-reduced-motion", async () => { | |
| 74a8784 | 130 | const body = await fetchLayoutHtml(); |
| dc26881 | 131 | // The reduced-motion block must mention the view-transition |
| 132 | // pseudos and set animation-duration: 0s. | |
| 133 | expect(body).toMatch( | |
| 134 | /@media\s*\(prefers-reduced-motion:\s*reduce\)\s*\{[\s\S]*?::view-transition-(old|new)\(root\)[\s\S]*?animation-duration:\s*0s/ | |
| 135 | ); | |
| 136 | }); | |
| 137 | ||
| 138 | it("attaches view-transition-name: root to body", async () => { | |
| 74a8784 | 139 | const body = await fetchLayoutHtml(); |
| dc26881 | 140 | expect(body).toMatch(/\bbody\b\s*\{[^}]*view-transition-name:\s*root/); |
| 141 | }); | |
| 142 | }); |