Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
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.

u-polish.test.tsBlame142 lines · 3 contributors
dc26881CC LABS App1/**
2 * Block U — Senior polish pass smoke tests.
3 *
74a8784Claude4 * 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.
dc26881CC LABS App12 * 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 */
22import { describe, it, expect } from "bun:test";
23import app from "../app";
24
25const HOME = "/";
74a8784Claude26// Public, Layout-rendered, DB-free page that carries the master CSS.
27const LAYOUT_PAGE = "/help";
dc26881CC LABS App28
29async function fetchHomeHtml(): Promise<string> {
30 const res = await app.request(HOME);
31 expect(res.status).toBe(200);
32 return await res.text();
33}
34
74a8784Claude35async 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
032ae5fccanty labs41describe("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 () => {
dc26881CC LABS App46 const body = await fetchHomeHtml();
032ae5fccanty labs47 const start = body.indexOf('class="lp-hero-ctas"');
dc26881CC LABS App48 expect(start).toBeGreaterThan(-1);
032ae5fccanty labs49 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) || [];
74a8784Claude53 expect(anchorMatches.length).toBe(2);
dc26881CC LABS App54 });
55
032ae5fccanty labs56 it("renders the hero links row beneath the primary CTAs", async () => {
dc26881CC LABS App57 const body = await fetchHomeHtml();
032ae5fccanty labs58 const ctas = body.indexOf('class="lp-hero-ctas"');
59 const links = body.indexOf('class="lp-hero-links"');
74a8784Claude60 expect(ctas).toBeGreaterThan(-1);
032ae5fccanty labs61 expect(links).toBeGreaterThan(ctas);
62 expect(body.slice(links, links + 200)).toContain('href="/demo"');
dc26881CC LABS App63 });
64
74a8784Claude65 it("places the product-card mock below the CTAs", async () => {
dc26881CC LABS App66 const body = await fetchHomeHtml();
032ae5fccanty labs67 const ctas = body.indexOf('class="lp-hero-ctas"');
68 const card = body.indexOf('class="lp-hero-card"');
dc26881CC LABS App69 expect(ctas).toBeGreaterThan(-1);
74a8784Claude70 expect(card).toBeGreaterThan(ctas);
dc26881CC LABS App71 });
72});
73
74describe("Block U2 — button polish", () => {
75 it("includes the universal hover-lift transform on .btn", async () => {
74a8784Claude76 const body = await fetchLayoutHtml();
dc26881CC LABS App77 // 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 () => {
74a8784Claude83 const body = await fetchLayoutHtml();
dc26881CC LABS App84 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 () => {
74a8784Claude88 const body = await fetchLayoutHtml();
1c60be8Claude89 // Focus-visible rule must set box-shadow with the calm indigo accent rgba.
90 // Calm Infrastructure restyle (2026-06-13): purple #8c6dff → indigo #5b6ee8
dc26881CC LABS App91 expect(body).toMatch(
1c60be8Claude92 /\.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\)/
dc26881CC LABS App93 );
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 () => {
74a8784Claude99 const body = await fetchLayoutHtml();
dc26881CC LABS App100 // 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
1c60be8Claude105 it("btn-primary has a gradient background and smooth transitions", async () => {
74a8784Claude106 const body = await fetchLayoutHtml();
1c60be8Claude107 // 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/);
dc26881CC LABS App111 });
112});
113
114describe("Block U4 — view transitions", () => {
115 it("includes the @view-transition opt-in", async () => {
74a8784Claude116 const body = await fetchLayoutHtml();
dc26881CC LABS App117 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 () => {
74a8784Claude122 const body = await fetchLayoutHtml();
dc26881CC LABS App123 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 () => {
74a8784Claude130 const body = await fetchLayoutHtml();
dc26881CC LABS App131 // 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 () => {
74a8784Claude139 const body = await fetchLayoutHtml();
dc26881CC LABS App140 expect(body).toMatch(/\bbody\b\s*\{[^}]*view-transition-name:\s*root/);
141 });
142});