156 lines
5.3 KiB
TypeScript
156 lines
5.3 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest';
|
||
|
|
import {
|
||
|
|
RESERVED_PAGE_SLUGS,
|
||
|
|
describeCollision,
|
||
|
|
menuPageSlugs,
|
||
|
|
pageSlugCollision,
|
||
|
|
resolveMenu,
|
||
|
|
type NavEntry,
|
||
|
|
type PageSummary,
|
||
|
|
} from '../pages';
|
||
|
|
import { deriveGallery, type GalleryYaml } from '../config';
|
||
|
|
import { rewritePageRefs } from '../admin/pageRefs';
|
||
|
|
|
||
|
|
const A1 = '11111111-1111-4111-8111-111111111111';
|
||
|
|
const A2 = '22222222-2222-4222-8222-222222222222';
|
||
|
|
|
||
|
|
const none = { subpages: [], albums: [], journal: [] };
|
||
|
|
|
||
|
|
describe('pageSlugCollision (#722)', () => {
|
||
|
|
it('accepts a free slug', () => {
|
||
|
|
expect(pageSlugCollision('pricing', none)).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it.each(RESERVED_PAGE_SLUGS)('refuses the built-in route /%s', (slug) => {
|
||
|
|
expect(pageSlugCollision(slug, none)).toEqual({ kind: 'reserved' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('refuses a slug taken by a subpage, an album or a journal entry', () => {
|
||
|
|
const taken = { subpages: ['travel'], albums: ['iceland'], journal: ['trip'] };
|
||
|
|
expect(pageSlugCollision('travel', taken)).toEqual({ kind: 'subpage' });
|
||
|
|
expect(pageSlugCollision('iceland', taken)).toEqual({ kind: 'album' });
|
||
|
|
expect(pageSlugCollision('trip', taken)).toEqual({ kind: 'journal' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('compares without regard to case', () => {
|
||
|
|
expect(pageSlugCollision('Travel', { ...none, subpages: ['travel'] })).toEqual({
|
||
|
|
kind: 'subpage',
|
||
|
|
});
|
||
|
|
expect(pageSlugCollision('ADMIN', none)).toEqual({ kind: 'reserved' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('refuses what is not a slug at all', () => {
|
||
|
|
expect(pageSlugCollision('a/b', none)).toEqual({ kind: 'invalid' });
|
||
|
|
expect(pageSlugCollision('', none)).toEqual({ kind: 'invalid' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('describes each collision in a sentence naming the slug', () => {
|
||
|
|
for (const kind of ['invalid', 'reserved', 'subpage', 'album', 'journal'] as const) {
|
||
|
|
expect(describeCollision('x', { kind })).toContain('x');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('deriveGallery: `- page:` references (#722)', () => {
|
||
|
|
const gallery = (subpages: NonNullable<GalleryYaml['subpages']>): GalleryYaml => ({ subpages });
|
||
|
|
|
||
|
|
it('keeps pages and subpages in one menu order', () => {
|
||
|
|
const { nav, subpages } = deriveGallery(
|
||
|
|
gallery([
|
||
|
|
{ page: 'pricing' },
|
||
|
|
{ name: 'Travel', albums: [A1] },
|
||
|
|
{ page: 'faq' },
|
||
|
|
{ name: 'Weddings', albums: [A2] },
|
||
|
|
]),
|
||
|
|
);
|
||
|
|
expect(subpages.map((s) => s.slug)).toEqual(['travel', 'weddings']);
|
||
|
|
expect(nav).toEqual([
|
||
|
|
{ type: 'page', slug: 'pricing' },
|
||
|
|
{ type: 'subpage', slug: 'travel' },
|
||
|
|
{ type: 'page', slug: 'faq' },
|
||
|
|
{ type: 'subpage', slug: 'weddings' },
|
||
|
|
]);
|
||
|
|
expect(menuPageSlugs(nav)).toEqual(['pricing', 'faq']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('allows a gallery whose menu holds only pages', () => {
|
||
|
|
expect(deriveGallery(gallery([{ page: 'pricing' }])).nav).toEqual([
|
||
|
|
{ type: 'page', slug: 'pricing' },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects an invalid page slug and a page listed twice', () => {
|
||
|
|
expect(() => deriveGallery(gallery([{ page: '../x' }]))).toThrow(/not a valid page slug/);
|
||
|
|
expect(() => deriveGallery(gallery([{ page: 'a' }, { page: 'a' }]))).toThrow(/more than once/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('builds the menu from the map form of subpages too', () => {
|
||
|
|
const { nav } = deriveGallery({ subpages: { Travel: [A1] } });
|
||
|
|
expect(nav).toEqual([{ type: 'subpage', slug: 'travel' }]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('resolveMenu (#722)', () => {
|
||
|
|
const nav: NavEntry[] = [
|
||
|
|
{ type: 'subpage', slug: 'travel' },
|
||
|
|
{ type: 'page', slug: 'pricing' },
|
||
|
|
{ type: 'subpage', slug: 'hidden' },
|
||
|
|
{ type: 'page', slug: 'draft' },
|
||
|
|
{ type: 'page', slug: 'missing' },
|
||
|
|
{ type: 'page', slug: 'locked' },
|
||
|
|
];
|
||
|
|
const pages: PageSummary[] = [
|
||
|
|
{ slug: 'pricing', frontmatter: { title: 'Pricing' } },
|
||
|
|
{ slug: 'draft', frontmatter: { title: 'Soon', draft: true } },
|
||
|
|
{ slug: 'locked', frontmatter: { password: 'scrypt:a:b' } },
|
||
|
|
];
|
||
|
|
|
||
|
|
it('interleaves visible subpages and published pages in gallery.yaml order', () => {
|
||
|
|
const menu = resolveMenu(nav, [{ slug: 'travel', name: 'Travel' }], pages);
|
||
|
|
expect(menu.map((m) => [m.href, m.label])).toEqual([
|
||
|
|
['/travel', 'Travel'],
|
||
|
|
['/pricing', 'Pricing'],
|
||
|
|
// A protected page stays listed; its title falls back to the slug.
|
||
|
|
['/locked', 'locked'],
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
// `name` is the gallery.yaml key the slug is built from ("south-korea");
|
||
|
|
// `title` is what the page itself shows ("South Korea"). The menu showed
|
||
|
|
// the key.
|
||
|
|
it('labels a subpage with its title, falling back to its name', () => {
|
||
|
|
const menu = resolveMenu(
|
||
|
|
[
|
||
|
|
{ type: 'subpage', slug: 'south-korea' },
|
||
|
|
{ type: 'subpage', slug: 'travel' },
|
||
|
|
],
|
||
|
|
[
|
||
|
|
{ slug: 'south-korea', name: 'south-korea', title: 'South Korea' },
|
||
|
|
{ slug: 'travel', name: 'Travel' },
|
||
|
|
],
|
||
|
|
[],
|
||
|
|
);
|
||
|
|
expect(menu.map((m) => m.label)).toEqual(['South Korea', 'Travel']);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('rewritePageRefs (#722)', () => {
|
||
|
|
const list = [{ name: 'Travel' }, { page: 'pricing' }, { page: 'faq' }];
|
||
|
|
|
||
|
|
it('renames a reference in place', () => {
|
||
|
|
expect(rewritePageRefs(list, 'pricing', 'rates')).toEqual([
|
||
|
|
{ name: 'Travel' },
|
||
|
|
{ page: 'rates' },
|
||
|
|
{ page: 'faq' },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('removes a reference', () => {
|
||
|
|
expect(rewritePageRefs(list, 'faq', null)).toEqual([{ name: 'Travel' }, { page: 'pricing' }]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns the same list when nothing matches', () => {
|
||
|
|
expect(rewritePageRefs(list, 'nothing', null)).toBe(list);
|
||
|
|
});
|
||
|
|
});
|