Commit032ae5f
test: fix 12 false-failing tests — line endings, Windows paths, stale landing markup
test: fix 12 false-failing tests — line endings, Windows paths, stale landing markup Suite was 22 red on a Windows checkout; none were product bugs. Fixed the real, permanent causes: - .gitattributes: force LF on .claude/skills/**/*.md (+ AGENTS.md, llms.txt). CRLF broke strict frontmatter parsers (Claude harness + our tests). Cleared 6 skill-frontmatter failures. Normalized the 3 committed SKILL.md files. - Windows path assertions: normalize separators in git-repository (bare-repo path), claude-web-session (sessionWorkdir), and self-host (fake fsExists key) tests. They asserted POSIX paths against platform-native join() output. - Landing hero tests (u-polish): rewrote against the LIVE view. Home `/` renders LandingProPage (landing-pro.tsx, `lp-hero-*`); the tests still asserted a retired "2030 reboot" hero (hero-actions/hero-trust/hero-card) that no longer exists. - Added a `typecheck` script (tsc --noEmit) as a target. Note: the codebase has pre-existing type errors (Bun transpiles without checking); cleaning them is tracked separately — not yet a blocking gate. Suite now 2934 pass / 4 fail. The remaining 4 pass in isolation and fail only from cross-file Bun mock.module bleed (~14 files mock ../db at module scope without restore) — a pre-existing test-infra issue, not a product defect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6 files changed+35−18032ae5fb354789ced6fa6aa9e9c97992a43320c8
6 changed files+35−18
Modified.gitattributes+8−0View fileUnifiedSplit
@@ -1,3 +1,11 @@
11# Shell scripts must stay LF — a CRLF shebang line fails on Linux with
22# "bad interpreter: /usr/bin/env bash^M". These run on the VPS.
33*.sh text eol=lf
4
5# Claude skill/agent files are parsed by strict LF-expecting frontmatter
6# parsers (both the Claude Code harness and our own tests). A CRLF checkout
7# on Windows breaks `name:`/`description:` extraction. Force LF everywhere.
8.claude/skills/**/*.md text eol=lf
9*.SKILL.md text eol=lf
10AGENTS.md text eol=lf
11llms.txt text eol=lf
Modifiedpackage.json+1−0View fileUnifiedSplit
@@ -18,6 +18,7 @@
1818 "db:migrate": "bun run src/db/migrate.ts",
1919 "db:studio": "drizzle-kit studio",
2020 "test": "bun test",
21 "typecheck": "tsc --noEmit",
2122 "preflight": "bun scripts/preflight.ts",
2223 "e2e": "playwright test --config=e2e/playwright.config.ts"
2324 },
Modifiedsrc/__tests__/claude-web-session.test.ts+3−1View fileUnifiedSplit
@@ -59,7 +59,9 @@ describe("claudeWebRoot + sessionWorkdir + claudeBinary", () => {
5959 it("reads from env at call time", () => {
6060 expect(claudeWebRoot()).toBe("/tmp/cw-test");
6161 expect(claudeBinary()).toBe("claude-test");
62 expect(sessionWorkdir("abc")).toBe("/tmp/cw-test/abc");
62 // sessionWorkdir joins with the platform separator; on the Linux server
63 // it is POSIX. Normalize so the assertion is stable on a Windows checkout.
64 expect(sessionWorkdir("abc").replace(/\\/g, "/")).toBe("/tmp/cw-test/abc");
6365 });
6466
6567 it("defaults sensibly when env unset", () => {
Modifiedsrc/__tests__/git-repository.test.ts+3−1View fileUnifiedSplit
@@ -35,7 +35,9 @@ describe("git repository management", () => {
3535
3636 it("should initialize a bare repository", async () => {
3737 const path = await initBareRepo(owner, repo);
38 expect(path).toContain(`${owner}/${repo}.git`);
38 // Normalize separators — initBareRepo returns platform-native paths
39 // (backslashes on Windows), so compare on a POSIX-normalized form.
40 expect(path.replace(/\\/g, "/")).toContain(`${owner}/${repo}.git`);
3941 expect(await repoExists(owner, repo)).toBe(true);
4042 });
4143
Modifiedsrc/__tests__/self-host.test.ts+3−1View fileUnifiedSplit
@@ -399,7 +399,9 @@ function makeFakeDepsForBootstrap(opts: {
399399 // pair is treated as a no-op; tests assert on `calls` instead.
400400 return { ok: true, stdout: "", stderr: "", exitCode: 0 };
401401 },
402 fsExists: (p: string) => fsExistsMap[p] === true,
402 // Normalize separators: the orchestrator builds paths with the platform
403 // join (backslashes on a Windows checkout), but the map keys are POSIX.
404 fsExists: (p: string) => fsExistsMap[p.replace(/\\/g, "/")] === true,
403405 fsMkdir: async (_p: string, _opts?: any) => undefined,
404406 fsWrite: async (p: string, body: string) => {
405407 writes.push({ path: p, body });
Modifiedsrc/__tests__/u-polish.test.ts+17−15View fileUnifiedSplit
@@ -38,32 +38,34 @@ async function fetchLayoutHtml(): Promise<string> {
3838 return await res.text();
3939}
4040
41describe("Block U1 — landing hero (2030 reboot)", () => {
42 it("renders exactly two CTAs in the hero actions row", async () => {
41describe("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 () => {
4346 const body = await fetchHomeHtml();
44 const start = body.indexOf('class="hero-actions');
47 const start = body.indexOf('class="lp-hero-ctas"');
4548 expect(start).toBeGreaterThan(-1);
46 // The actions row is a tight window of <a … class="btn …"> anchors.
47 const tail = body.slice(start, start + 600);
48 const anchorMatches = tail.match(/<a[^>]*class="btn[^"]*"/g) || [];
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) || [];
4953 expect(anchorMatches.length).toBe(2);
50 expect(tail).toContain('href="/register"');
51 expect(tail).toContain('href="#loop"');
5254 });
5355
54 it("renders the trust line beneath the CTAs", async () => {
56 it("renders the hero links row beneath the primary CTAs", async () => {
5557 const body = await fetchHomeHtml();
56 const ctas = body.indexOf('class="hero-actions');
57 const trust = body.indexOf('class="hero-trust');
58 const ctas = body.indexOf('class="lp-hero-ctas"');
59 const links = body.indexOf('class="lp-hero-links"');
5860 expect(ctas).toBeGreaterThan(-1);
59 expect(trust).toBeGreaterThan(ctas);
60 expect(body).toContain("Self-hosted · Git-native · Claude-first");
61 expect(links).toBeGreaterThan(ctas);
62 expect(body.slice(links, links + 200)).toContain('href="/demo"');
6163 });
6264
6365 it("places the product-card mock below the CTAs", async () => {
6466 const body = await fetchHomeHtml();
65 const ctas = body.indexOf('class="hero-actions');
66 const card = body.indexOf('class="hero-card');
67 const ctas = body.indexOf('class="lp-hero-ctas"');
68 const card = body.indexOf('class="lp-hero-card"');
6769 expect(ctas).toBeGreaterThan(-1);
6870 expect(card).toBeGreaterThan(ctas);
6971 });
7072