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
2 changes: 1 addition & 1 deletion website/lib/ui/site-footer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function siteFooter() {
</nav>
<div class="pt-8 border-t border-border flex flex-col md:flex-row justify-between items-center gap-4 text-fg-subtle text-xs">
<div><a class="no-underline hover:text-accent transition-colors" href=${GH_URL + '/blob/main/LICENSE'} target="_blank" rel="noopener noreferrer">MIT License${NEW_TAB}</a></div>
<div class="flex items-center gap-1">Built with webjs <svg class="heart" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg></div>
<div class="flex items-center gap-1">Built with WebJs <svg class="heart" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg></div>
</div>
</div>
</footer>
Expand Down
20 changes: 20 additions & 0 deletions website/test/ssr/nav-and-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,23 @@ test('no internal link still points at the old /why path', async () => {
assert.ok(!/href="\/why"/.test(out), 'no chrome link targets the pre-rename path');
}
});

test('the footer writes the brand name with proper casing', async () => {
// Invariant 11: WebJs is capitalized wherever it NAMES the project in prose,
// and the lowercase `webjs` form is reserved for literal code tokens (a CLI
// command, the domain, an `@webjsdev` package, a config key, an env var).
// The footer renders on EVERY page, so a slip here is the most-viewed
// instance of the brand name on the site, and it shipped that way once
// already (#1190).
//
// The edit-time hook that enforces casing only scans NEW content, so a string
// already in the tree is never re-checked and CI has no equivalent. This test
// is that missing check, scoped to the one surface that matters most.
const out = await renderToString(siteFooter());
assert.ok(out.includes('Built with WebJs'), 'footer credits the brand with proper casing');
assert.ok(!/Built with webjs/.test(out), 'and never the lowercase code-token form');

// Route paths are NOT prose and must stay lowercase, so guard against an
// over-eager fix that "corrects" the hrefs and breaks every comparison link.
assert.ok(out.includes('href="/compare/webjs-vs-nextjs"'), 'url paths stay lowercase');
});