news: track @newsHidden in Caddy snapshot + extract testable feed routing helpers
Housekeeping per Codex: - Mirror the live @newsHidden rule into deploy/caddy/Caddyfile.snapshot so the /news noindex protection is reproducibly recorded. - Extract the feed's routing helpers (feedBase/parseView/viewUrl) into pure $lib/feednav.js and unit-test them (the base-aware URL generation wasn't exercised by the prior suite). NewsFeed imports them; behavior unchanged. (Note: the step-1 commit also swept in data/wotd_audio/renewal.mp3 — a legit cached pronunciation, not extraction-related; left as-is per Codex.) 32 frontend tests green; build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { feedBase, parseView, viewUrl } from './feednav.js';
|
||||
|
||||
const view = (path) => parseView(new URL('https://x' + path));
|
||||
|
||||
describe('feedBase', () => {
|
||||
it('is /news only under the /news path; everything else is /', () => {
|
||||
expect(feedBase('/news')).toBe('/news');
|
||||
expect(feedBase('/news?view=latest')).toBe('/news');
|
||||
expect(feedBase('/')).toBe('/');
|
||||
expect(feedBase('/?view=latest')).toBe('/');
|
||||
expect(feedBase('')).toBe('/');
|
||||
expect(feedBase(undefined)).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseView', () => {
|
||||
it('derives the view from query params, path-agnostic', () => {
|
||||
expect(view('/')).toBe('today');
|
||||
expect(view('/news')).toBe('today');
|
||||
expect(view('/?view=latest')).toBe('latest');
|
||||
expect(view('/news?view=following')).toBe('following');
|
||||
expect(view('/?tag=clean-energy')).toBe('tag:clean-energy');
|
||||
expect(view('/?source=7')).toBe('source:7');
|
||||
expect(view('/?q=solar')).toBe('search');
|
||||
});
|
||||
it('prioritizes search > source > tag > view', () => {
|
||||
expect(view('/?q=a&source=7&tag=b&view=latest')).toBe('search');
|
||||
expect(view('/?source=7&tag=b&view=latest')).toBe('source:7');
|
||||
expect(view('/?tag=b&view=latest')).toBe('tag:b');
|
||||
expect(view('/?q=%20')).toBe('today'); // whitespace-only q is not a search
|
||||
});
|
||||
});
|
||||
|
||||
describe('viewUrl', () => {
|
||||
it('builds base-relative links that parseView reads back identically', () => {
|
||||
for (const base of ['/', '/news']) {
|
||||
expect(viewUrl(base, 'today')).toBe(base);
|
||||
expect(viewUrl(base, 'latest')).toBe(base + '?view=latest');
|
||||
expect(viewUrl(base, 'tag:clean-energy')).toBe(base + '?tag=clean-energy');
|
||||
expect(viewUrl(base, 'source:7')).toBe(base + '?source=7');
|
||||
// round-trip: the generated URL resolves back to the same view key
|
||||
expect(view(viewUrl(base, 'latest').replace(base, '/'))).toBe('latest');
|
||||
}
|
||||
});
|
||||
it('encodes tag/source/view values', () => {
|
||||
expect(viewUrl('/news', 'tag:good news')).toBe('/news?tag=good%20news');
|
||||
expect(viewUrl('/', 'view=a&b')).toBe('/?view=view%3Da%26b');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user