Skip to content

Commit 6105b92

Browse files
authored
Merge pull request #13 from goude/claude/backlog-tasks-CTs47
Add docs utility tests and improve CDN security with SRI hashes
2 parents 7501568 + 25695de commit 6105b92

4 files changed

Lines changed: 99 additions & 4 deletions

File tree

src/components/BaseHead.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
3838
<link
3939
rel="stylesheet"
4040
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
41+
integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g"
4142
crossorigin="anonymous"
4243
referrerpolicy="no-referrer"
4344
/>

src/pages/ai-generated/do-olls-that-will-talk/index.astro

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ import "./_score.css";
88
title="The Do-olls That Will Talk"
99
description="It's Beginning to Look a Lot Like Christmas - timing analysis"
1010
>
11-
<script src="https://cdn.jsdelivr.net/npm/wavesurfer.js@7"></script>
1211
<script
13-
src="https://cdn.jsdelivr.net/npm/wavesurfer.js@7/dist/plugins/regions.esm.js"
14-
></script>
12+
src="https://cdn.jsdelivr.net/npm/wavesurfer.js@7.12.5/dist/wavesurfer.js"
13+
integrity="sha384-CeA0Ua69ymZm/dzThkGyFBLlJ7JiSYrKc0D/M0VoqFW2U2b1XRYfdfldtJ8dcgYI"
14+
crossorigin="anonymous"></script>
15+
<script
16+
src="https://cdn.jsdelivr.net/npm/wavesurfer.js@7.12.5/dist/plugins/regions.esm.js"
17+
integrity="sha384-EegjZR2c3O5tEUtX3aAjuhXwdiK/nmUZxZSCykv5y4RaP81l5CMMW8RQxwetqqxA"
18+
crossorigin="anonymous"></script>
1519
<script
1620
src="https://unpkg.com/opensheetmusicdisplay@0.8.3/build/opensheetmusicdisplay.min.js"
17-
></script>
21+
integrity="sha384-OdHU0rQhT3M3HrT9tOJEqMc+NynwrRQofDlQReGHCKWIynxoYcyO/HDfjQqVcVi/"
22+
crossorigin="anonymous"></script>
1823

1924
<h1>The Do-olls That Will Talk</h1>
2025
<p class="subtitle">It's Beginning to Look a Lot Like Christmas</p>

src/styles/vars.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
--font-body-sans: "Outfit", sans-serif;
5656
--font-mono: "JetBrains Mono", monospace;
5757

58+
--text-xs: 0.875rem;
5859
--text-sm: 1rem;
5960
--text-base-size: 1.125rem;
6061
--text-lg: 1.25rem;

src/utils/docs.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import os from "node:os";
5+
import { getDocsFiles, getDocsDir } from "./docs.js";
6+
7+
let tmpDir: string;
8+
9+
beforeEach(() => {
10+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "docs-test-"));
11+
});
12+
13+
afterEach(() => {
14+
fs.rmSync(tmpDir, { recursive: true, force: true });
15+
});
16+
17+
describe("getDocsFiles", () => {
18+
it("returns empty array for empty directory", () => {
19+
expect(getDocsFiles(tmpDir)).toEqual([]);
20+
});
21+
22+
it("returns a DocEntry for a markdown file", () => {
23+
fs.writeFileSync(path.join(tmpDir, "guide.md"), "# Guide Title\nContent.");
24+
const entries = getDocsFiles(tmpDir);
25+
expect(entries).toHaveLength(1);
26+
expect(entries[0]).toMatchObject({
27+
slug: "guide",
28+
title: "Guide Title",
29+
});
30+
expect(entries[0]!.filePath).toBe(path.join(tmpDir, "guide.md"));
31+
});
32+
33+
it("uses filename as title when no H1 heading present", () => {
34+
fs.writeFileSync(path.join(tmpDir, "notes.md"), "no heading here");
35+
const entries = getDocsFiles(tmpDir);
36+
expect(entries[0]!.title).toBe("notes");
37+
});
38+
39+
it("ignores non-markdown files", () => {
40+
fs.writeFileSync(path.join(tmpDir, "readme.txt"), "text file");
41+
fs.writeFileSync(path.join(tmpDir, "data.json"), "{}");
42+
expect(getDocsFiles(tmpDir)).toHaveLength(0);
43+
});
44+
45+
it("recurses into subdirectories", () => {
46+
const sub = path.join(tmpDir, "sub");
47+
fs.mkdirSync(sub);
48+
fs.writeFileSync(path.join(sub, "nested.md"), "# Nested");
49+
const entries = getDocsFiles(tmpDir);
50+
expect(entries).toHaveLength(1);
51+
expect(entries[0]!.slug).toBe(path.join("sub", "nested"));
52+
expect(entries[0]!.title).toBe("Nested");
53+
});
54+
55+
it("collects files from both root and subdirectory", () => {
56+
fs.writeFileSync(path.join(tmpDir, "root.md"), "# Root");
57+
const sub = path.join(tmpDir, "sub");
58+
fs.mkdirSync(sub);
59+
fs.writeFileSync(path.join(sub, "child.md"), "# Child");
60+
const entries = getDocsFiles(tmpDir);
61+
expect(entries).toHaveLength(2);
62+
const slugs = entries.map((e) => e.slug);
63+
expect(slugs).toContain("root");
64+
expect(slugs).toContain(path.join("sub", "child"));
65+
});
66+
67+
it("picks first H1 when multiple headings exist", () => {
68+
fs.writeFileSync(
69+
path.join(tmpDir, "multi.md"),
70+
"# First Heading\n## Second\n# Third"
71+
);
72+
const entries = getDocsFiles(tmpDir);
73+
expect(entries[0]!.title).toBe("First Heading");
74+
});
75+
});
76+
77+
describe("getDocsDir", () => {
78+
it("returns an absolute path ending with 'docs'", () => {
79+
const dir = getDocsDir();
80+
expect(path.isAbsolute(dir)).toBe(true);
81+
expect(dir.endsWith("docs")).toBe(true);
82+
});
83+
84+
it("points to cwd/docs", () => {
85+
const expected = path.resolve(process.cwd(), "docs");
86+
expect(getDocsDir()).toBe(expected);
87+
});
88+
});

0 commit comments

Comments
 (0)