Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/public
/tmp
resources/
node_modules/
package-lock.json
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/google/docsy-example

go 1.12

require github.com/google/docsy/theme v0.0.0-20260530181410-94f94145fe15 // indirect
require github.com/google/docsy/theme v0.0.0-20260611213000-5c5733de6062 // indirect
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3 h1:/iluJkJiyTAdnqrw3Yi9rH2HNHhrrtCmj8VJe7I6o3w=
github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo=
github.com/google/docsy/theme v0.0.0-20260530181410-94f94145fe15 h1:ZvwTrXGvb54wT1ajJnnFcepHZnW9R3sypzhAIlGfFps=
github.com/google/docsy/theme v0.0.0-20260530181410-94f94145fe15/go.mod h1:CGCFFJjc3PAYexPDsQqTbB3/lWnncwlwKLnSQkDaaF0=
github.com/google/docsy/theme v0.0.0-20260611213000-5c5733de6062 h1:Wm1KR34lrCA0DLlTfVaVaSNl9IoCFd8Dw7vKiH+5Zqc=
github.com/google/docsy/theme v0.0.0-20260611213000-5c5733de6062/go.mod h1:CGCFFJjc3PAYexPDsQqTbB3/lWnncwlwKLnSQkDaaF0=
github.com/twbs/bootstrap v5.3.8+incompatible h1:eK1fsXP7R/FWFt+sSNmmvUH9usPocf240nWVw7Dh02o=
github.com/twbs/bootstrap v5.3.8+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
2 changes: 1 addition & 1 deletion hugo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ module:
# workspace: docsy.work
hugoVersion:
extended: true
min: 0.157.0
min: 0.158.0
imports:
- path: github.com/google/docsy/theme
disable: false
2 changes: 1 addition & 1 deletion layouts/home.redirects
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Adapted from https://gohugo.io/methods/page/aliases/#template-implementation
cSpell:ignore hugo
*/ -}}

{{ range .Sites -}}
{{ range hugo.Sites -}}
{{ range $p := .Pages -}}
{{ range .Aliases -}}
{{ if findRE `\s` . -}}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"seq": "bash -c 'for cmd in \"$@\"; do npm run $cmd || exit 1; done' - ",
"serve": "npm run _serve",
"test-and-fix": "npm run fix && npm run test-only",
"test-only": "npm run check:links",
"test-only": "npm run check:links && npm run test:site",
"test:site": "node --test 'tests/**/*.test.mjs'",
"test": "npm run test-and-fix",
"update:docsy:main": "hugo mod get -u github.com/google/docsy/theme@main && hugo mod tidy",
"update:docsy:mod": "hugo mod get -u github.com/google/docsy/theme && hugo mod tidy",
Expand Down
86 changes: 86 additions & 0 deletions tests/no-deprecations.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import {
existsSync,
mkdirSync,
rmSync,
rmdirSync,
writeFileSync,
} from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const siteDir = fileURLToPath(new URL('../', import.meta.url));

// Build to a scratch dir so this test never races with tests that read
// public/ (node --test runs test files concurrently). Kept after the run
// for inspection; cleared at start.
const outDir = join(siteDir, 'tmp', 'test-no-deprecations');

function buildSite() {
const res = spawnSync(`npm run build -- -d ${outDir}`, {
cwd: siteDir,
shell: true,
encoding: 'utf8',
});
const output = `${res.stdout ?? ''}${res.stderr ?? ''}`;
const deprecations = output
.split('\n')
.filter((line) => /deprecated/i.test(line));
return { res, output, deprecations };
}

test('site build logs no Hugo deprecation notices', (t) => {
// The `_hugo-dev` script builds with `--logLevel info`, the level at which
// Hugo first reports deprecated API usage.
rmSync(outDir, { recursive: true, force: true });
const { res, output, deprecations } = buildSite();
assert.equal(res.status, 0, `Build failed:\n${output}`);
assert.deepEqual(deprecations, [], 'Hugo build logged deprecation notice(s)');
t.diagnostic(`Scanned ${output.split('\n').length} build-log lines`);
});

// Sanity check that the test above can actually detect deprecation notices:
// seed a deprecated API call via the theme's head-end hook and ensure that
// Hugo reports it. Guards against, e.g., Hugo demoting deprecation notices
// below the info log level.
test('build with seeded deprecated API call logs a deprecation notice', (t) => {
const hookPath = join(
siteDir,
'layouts',
'_partials',
'hooks',
'head-end.html',
);
assert.ok(
!existsSync(hookPath),
`${hookPath} already exists; this test needs to create it`,
);
mkdirSync(dirname(hookPath), { recursive: true });
writeFileSync(
hookPath,
'{{/* Temporary file seeded by no-deprecations.test.mjs. */}}\n' +
'{{ .Language.LanguageName }}\n',
);
try {
const { res, output, deprecations } = buildSite();
assert.equal(res.status, 0, `Seeded build failed:\n${output}`);
assert.ok(
deprecations.length > 0,
'Seeded deprecated API call was not reported by the Hugo build',
);
t.diagnostic(`Seeded deprecation reported as: ${deprecations[0].trim()}`);
} finally {
rmSync(hookPath, { force: true });
// Prune test-created dirs only if empty (rmdirSync refuses to delete
// non-empty directories), in case other hooks/partials are added later.
for (const dir of [dirname(hookPath), dirname(dirname(hookPath))]) {
try {
rmdirSync(dir);
} catch {
break; // Directory not empty; leave it (and its parents) in place.
}
}
}
});
57 changes: 57 additions & 0 deletions tests/site.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

// Validates the navbar language selector and RTL rendering in the generated
// site (public/). Assumes the site has been built (`npm run build`); in the
// `test-only` chain, the check:links prebuild takes care of that.

const publicDir = fileURLToPath(new URL('../public', import.meta.url));

function page(relPath) {
return readFileSync(join(publicDir, relPath, 'index.html'), 'utf8');
}

// Extract the language-menu markup from a page.
function langMenu(html) {
const m = html.match(
/<div class="td-lang-menu dropdown">[\s\S]*?<\/ul>\s*<\/div>/,
);
assert.ok(m, 'page has a td-lang-menu language selector');
return m[0];
}

test('language selector marks untranslated languages as disabled', (t) => {
// /docs/tasks/ exists only in English: the menu should show an active
// entry (English) and disabled, link-less entries for no and fa.
const menu = langMenu(page('docs/tasks'));
const active = menu.match(/class="dropdown-item active"/g) ?? [];
const disabled = menu.match(/class="dropdown-item disabled"/g) ?? [];
assert.equal(active.length, 1, 'one active language entry');
assert.equal(disabled.length, 2, 'two disabled (untranslated) entries');
assert.ok(
!/<a[^>]*class="dropdown-item (active|disabled)"/.test(menu),
'active/disabled entries are not links',
);
t.diagnostic(`active: ${active.length}, disabled: ${disabled.length}`);
});

test('language selector links to existing translations', (t) => {
// /docs/overview/ has a Persian translation: the menu should link to it.
const menu = langMenu(page('docs/overview'));
assert.match(
menu,
/<a class="dropdown-item" href="\/fa\/docs\/overview\/"/,
'links to the Persian translation',
);
t.diagnostic('fa translation link present on /docs/overview/');
});

test('Persian pages render right-to-left', (t) => {
const html = page('fa');
assert.match(html, /<html[^>]*\bdir="rtl"/, 'html element has dir="rtl"');
assert.match(html, /<html[^>]*\blang="fa"/, 'html element has lang="fa"');
t.diagnostic('fa home page has dir="rtl" and lang="fa"');
});