Skip to content

Commit 43a180c

Browse files
authored
Use Docsy theme @ main (post Hugo-0.158.0 floor); add site tests (#462)
1 parent f9940fb commit 43a180c

8 files changed

Lines changed: 151 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/public
2+
/tmp
23
resources/
34
node_modules/
45
package-lock.json

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ module github.com/google/docsy-example
22

33
go 1.12
44

5-
require github.com/google/docsy/theme v0.0.0-20260530181410-94f94145fe15 // indirect
5+
require github.com/google/docsy/theme v0.0.0-20260611213000-5c5733de6062 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3 h1:/iluJkJiyTAdnqrw3Yi9rH2HNHhrrtCmj8VJe7I6o3w=
22
github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo=
3-
github.com/google/docsy/theme v0.0.0-20260530181410-94f94145fe15 h1:ZvwTrXGvb54wT1ajJnnFcepHZnW9R3sypzhAIlGfFps=
4-
github.com/google/docsy/theme v0.0.0-20260530181410-94f94145fe15/go.mod h1:CGCFFJjc3PAYexPDsQqTbB3/lWnncwlwKLnSQkDaaF0=
3+
github.com/google/docsy/theme v0.0.0-20260611213000-5c5733de6062 h1:Wm1KR34lrCA0DLlTfVaVaSNl9IoCFd8Dw7vKiH+5Zqc=
4+
github.com/google/docsy/theme v0.0.0-20260611213000-5c5733de6062/go.mod h1:CGCFFJjc3PAYexPDsQqTbB3/lWnncwlwKLnSQkDaaF0=
55
github.com/twbs/bootstrap v5.3.8+incompatible h1:eK1fsXP7R/FWFt+sSNmmvUH9usPocf240nWVw7Dh02o=
66
github.com/twbs/bootstrap v5.3.8+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=

hugo.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ module:
247247
# workspace: docsy.work
248248
hugoVersion:
249249
extended: true
250-
min: 0.157.0
250+
min: 0.158.0
251251
imports:
252252
- path: github.com/google/docsy/theme
253253
disable: false

layouts/home.redirects

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Adapted from https://gohugo.io/methods/page/aliases/#template-implementation
55
cSpell:ignore hugo
66
*/ -}}
77

8-
{{ range .Sites -}}
8+
{{ range hugo.Sites -}}
99
{{ range $p := .Pages -}}
1010
{{ range .Aliases -}}
1111
{{ if findRE `\s` . -}}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"seq": "bash -c 'for cmd in \"$@\"; do npm run $cmd || exit 1; done' - ",
4242
"serve": "npm run _serve",
4343
"test-and-fix": "npm run fix && npm run test-only",
44-
"test-only": "npm run check:links",
44+
"test-only": "npm run check:links && npm run test:site",
45+
"test:site": "node --test 'tests/**/*.test.mjs'",
4546
"test": "npm run test-and-fix",
4647
"update:docsy:main": "hugo mod get -u github.com/google/docsy/theme@main && hugo mod tidy",
4748
"update:docsy:mod": "hugo mod get -u github.com/google/docsy/theme && hugo mod tidy",

tests/no-deprecations.test.mjs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { spawnSync } from 'node:child_process';
4+
import {
5+
existsSync,
6+
mkdirSync,
7+
rmSync,
8+
rmdirSync,
9+
writeFileSync,
10+
} from 'node:fs';
11+
import { dirname, join } from 'node:path';
12+
import { fileURLToPath } from 'node:url';
13+
14+
const siteDir = fileURLToPath(new URL('../', import.meta.url));
15+
16+
// Build to a scratch dir so this test never races with tests that read
17+
// public/ (node --test runs test files concurrently). Kept after the run
18+
// for inspection; cleared at start.
19+
const outDir = join(siteDir, 'tmp', 'test-no-deprecations');
20+
21+
function buildSite() {
22+
const res = spawnSync(`npm run build -- -d ${outDir}`, {
23+
cwd: siteDir,
24+
shell: true,
25+
encoding: 'utf8',
26+
});
27+
const output = `${res.stdout ?? ''}${res.stderr ?? ''}`;
28+
const deprecations = output
29+
.split('\n')
30+
.filter((line) => /deprecated/i.test(line));
31+
return { res, output, deprecations };
32+
}
33+
34+
test('site build logs no Hugo deprecation notices', (t) => {
35+
// The `_hugo-dev` script builds with `--logLevel info`, the level at which
36+
// Hugo first reports deprecated API usage.
37+
rmSync(outDir, { recursive: true, force: true });
38+
const { res, output, deprecations } = buildSite();
39+
assert.equal(res.status, 0, `Build failed:\n${output}`);
40+
assert.deepEqual(deprecations, [], 'Hugo build logged deprecation notice(s)');
41+
t.diagnostic(`Scanned ${output.split('\n').length} build-log lines`);
42+
});
43+
44+
// Sanity check that the test above can actually detect deprecation notices:
45+
// seed a deprecated API call via the theme's head-end hook and ensure that
46+
// Hugo reports it. Guards against, e.g., Hugo demoting deprecation notices
47+
// below the info log level.
48+
test('build with seeded deprecated API call logs a deprecation notice', (t) => {
49+
const hookPath = join(
50+
siteDir,
51+
'layouts',
52+
'_partials',
53+
'hooks',
54+
'head-end.html',
55+
);
56+
assert.ok(
57+
!existsSync(hookPath),
58+
`${hookPath} already exists; this test needs to create it`,
59+
);
60+
mkdirSync(dirname(hookPath), { recursive: true });
61+
writeFileSync(
62+
hookPath,
63+
'{{/* Temporary file seeded by no-deprecations.test.mjs. */}}\n' +
64+
'{{ .Language.LanguageName }}\n',
65+
);
66+
try {
67+
const { res, output, deprecations } = buildSite();
68+
assert.equal(res.status, 0, `Seeded build failed:\n${output}`);
69+
assert.ok(
70+
deprecations.length > 0,
71+
'Seeded deprecated API call was not reported by the Hugo build',
72+
);
73+
t.diagnostic(`Seeded deprecation reported as: ${deprecations[0].trim()}`);
74+
} finally {
75+
rmSync(hookPath, { force: true });
76+
// Prune test-created dirs only if empty (rmdirSync refuses to delete
77+
// non-empty directories), in case other hooks/partials are added later.
78+
for (const dir of [dirname(hookPath), dirname(dirname(hookPath))]) {
79+
try {
80+
rmdirSync(dir);
81+
} catch {
82+
break; // Directory not empty; leave it (and its parents) in place.
83+
}
84+
}
85+
}
86+
});

tests/site.test.mjs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { readFileSync } from 'node:fs';
4+
import { join } from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
7+
// Validates the navbar language selector and RTL rendering in the generated
8+
// site (public/). Assumes the site has been built (`npm run build`); in the
9+
// `test-only` chain, the check:links prebuild takes care of that.
10+
11+
const publicDir = fileURLToPath(new URL('../public', import.meta.url));
12+
13+
function page(relPath) {
14+
return readFileSync(join(publicDir, relPath, 'index.html'), 'utf8');
15+
}
16+
17+
// Extract the language-menu markup from a page.
18+
function langMenu(html) {
19+
const m = html.match(
20+
/<div class="td-lang-menu dropdown">[\s\S]*?<\/ul>\s*<\/div>/,
21+
);
22+
assert.ok(m, 'page has a td-lang-menu language selector');
23+
return m[0];
24+
}
25+
26+
test('language selector marks untranslated languages as disabled', (t) => {
27+
// /docs/tasks/ exists only in English: the menu should show an active
28+
// entry (English) and disabled, link-less entries for no and fa.
29+
const menu = langMenu(page('docs/tasks'));
30+
const active = menu.match(/class="dropdown-item active"/g) ?? [];
31+
const disabled = menu.match(/class="dropdown-item disabled"/g) ?? [];
32+
assert.equal(active.length, 1, 'one active language entry');
33+
assert.equal(disabled.length, 2, 'two disabled (untranslated) entries');
34+
assert.ok(
35+
!/<a[^>]*class="dropdown-item (active|disabled)"/.test(menu),
36+
'active/disabled entries are not links',
37+
);
38+
t.diagnostic(`active: ${active.length}, disabled: ${disabled.length}`);
39+
});
40+
41+
test('language selector links to existing translations', (t) => {
42+
// /docs/overview/ has a Persian translation: the menu should link to it.
43+
const menu = langMenu(page('docs/overview'));
44+
assert.match(
45+
menu,
46+
/<a class="dropdown-item" href="\/fa\/docs\/overview\/"/,
47+
'links to the Persian translation',
48+
);
49+
t.diagnostic('fa translation link present on /docs/overview/');
50+
});
51+
52+
test('Persian pages render right-to-left', (t) => {
53+
const html = page('fa');
54+
assert.match(html, /<html[^>]*\bdir="rtl"/, 'html element has dir="rtl"');
55+
assert.match(html, /<html[^>]*\blang="fa"/, 'html element has lang="fa"');
56+
t.diagnostic('fa home page has dir="rtl" and lang="fa"');
57+
});

0 commit comments

Comments
 (0)