Files
Ken eaef3f8e84 feat: implement three-width layout system (reading, wide, full-bleed)
Replace the narrow 720px column layout with a three-width breakout system:

- Reading width (--width-prose, 720px): prose paragraphs, lists, blockquotes
- Wide width (--width-code, 840px): code blocks, inline SVGs, figures, .wide/.breakout
- Full width (--width-container, 1100px): hero diagrams, .full elements

The outer container is wide; individual content blocks self-constrain via
max-width + margin: auto. This creates the narrow-wide-narrow rhythm.

Changes:
- prose.css: Add .prose > * breakout pattern for three widths, SVG styling
- BaseLayout.astro: Remove max-width from main (children handle their own widths)
- [...slug].astro: Remove --width-prose constraint from .prose-wrapper
- prose.css: Remove old left:50%/translateX(-50%) code block hack
2026-05-27 04:02:12 +00:00

137 lines
5.9 KiB
JavaScript

import { describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';
const pagePath = resolve('src/pages/posts/[...slug].astro');
function readPage() {
return readFileSync(pagePath, 'utf-8');
}
describe('Post page template', () => {
it('should exist at src/pages/posts/[...slug].astro', () => {
assert.ok(existsSync(pagePath), 'Post page file should exist');
});
describe('Imports', () => {
it('should import getCollection and render from astro:content', () => {
const content = readPage();
assert.match(content, /import\s+.*getCollection.*from\s+['"]astro:content['"]/, 'should import getCollection');
assert.match(content, /import\s+.*render.*from\s+['"]astro:content['"]/, 'should import render');
});
it('should import CollectionEntry type from astro:content', () => {
const content = readPage();
assert.match(content, /import\s+type\s+.*CollectionEntry.*from\s+['"]astro:content['"]/, 'should import CollectionEntry type');
});
it('should import BaseLayout', () => {
const content = readPage();
assert.match(content, /import\s+BaseLayout\s+from\s+['"].*BaseLayout\.astro['"]/, 'should import BaseLayout');
});
it('should import NavBar', () => {
const content = readPage();
assert.match(content, /import\s+NavBar\s+from\s+['"].*NavBar\.astro['"]/, 'should import NavBar');
});
it('should import PostHeader', () => {
const content = readPage();
assert.match(content, /import\s+PostHeader\s+from\s+['"].*PostHeader\.astro['"]/, 'should import PostHeader');
});
it('should import getReadingTime', () => {
const content = readPage();
assert.match(content, /import\s+.*getReadingTime.*from\s+['"].*reading-time['"]/, 'should import getReadingTime');
});
it('should import prose.css', () => {
const content = readPage();
assert.match(content, /import\s+['"].*prose\.css['"]/, 'should import prose.css');
});
});
describe('getStaticPaths', () => {
it('should export getStaticPaths function', () => {
const content = readPage();
assert.match(content, /export\s+(async\s+)?function\s+getStaticPaths/, 'should export getStaticPaths');
});
it('should call getCollection for posts', () => {
const content = readPage();
assert.match(content, /getCollection\s*\(\s*['"]posts['"]\s*\)/, 'should call getCollection("posts")');
});
it('should map posts to params with slug from post.id and props with post', () => {
const content = readPage();
assert.match(content, /params\s*:\s*\{\s*slug\s*:\s*post\.id\s*\}/, 'should map slug to post.id');
assert.match(content, /props\s*:\s*\{\s*post\s*\}/, 'should pass post as props');
});
});
describe('Props interface', () => {
it('should define Props interface with post: CollectionEntry<"posts">', () => {
const content = readPage();
assert.match(content, /interface\s+Props/, 'should define Props interface');
assert.match(content, /post\s*:\s*CollectionEntry\s*<\s*['"]posts['"]\s*>/, 'should have post: CollectionEntry<"posts">');
});
});
describe('Component logic', () => {
it('should destructure post from Astro.props', () => {
const content = readPage();
assert.match(content, /const\s+\{\s*post\s*\}\s*=\s*Astro\.props/, 'should destructure post from Astro.props');
});
it('should await render(post) for Content', () => {
const content = readPage();
assert.match(content, /await\s+render\s*\(\s*post\s*\)/, 'should await render(post)');
assert.match(content, /Content/, 'should destructure Content');
});
it('should compute readingTime from post.body', () => {
const content = readPage();
assert.match(content, /getReadingTime\s*\(\s*post\.body/, 'should call getReadingTime(post.body...)');
});
});
describe('Template', () => {
it('should use BaseLayout with title and description from post.data', () => {
const content = readPage();
assert.match(content, /<BaseLayout/, 'should use BaseLayout');
assert.match(content, /title\s*=\s*\{post\.data\.title\}/, 'should pass title from post.data');
});
it('should include NavBar in nav slot', () => {
const content = readPage();
assert.match(content, /<NavBar\s+slot\s*=\s*["']nav["']/, 'should have NavBar with slot="nav"');
});
it('should include PostHeader with required props', () => {
const content = readPage();
assert.match(content, /<PostHeader/, 'should use PostHeader');
assert.match(content, /title\s*=\s*\{post\.data\.title\}/, 'should pass title');
assert.match(content, /date\s*=\s*\{post\.data\.date\}/, 'should pass date');
assert.match(content, /type\s*=\s*\{post\.data\.type\}/, 'should pass type');
assert.match(content, /readingTime\s*=\s*\{readingTime\}/, 'should pass readingTime');
});
it('should have article.prose-wrapper > div.prose > Content', () => {
const content = readPage();
assert.match(content, /<article\s+class\s*=\s*["']prose-wrapper["']/, 'should have article.prose-wrapper');
assert.match(content, /<div\s+class\s*=\s*["']prose["']/, 'should have div.prose');
assert.match(content, /<Content\s*\/>/, 'should render Content');
});
});
describe('Style', () => {
it('should have .prose-wrapper with auto margins and space-12 bottom padding (no max-width — children self-constrain)', () => {
const content = readPage();
assert.match(content, /\.prose-wrapper/, 'should style .prose-wrapper');
assert.match(content, /margin-left\s*:\s*auto/, 'should have auto left margin');
assert.match(content, /margin-right\s*:\s*auto/, 'should have auto right margin');
assert.match(content, /padding-bottom\s*:\s*var\(--space-12\)/, 'should have space-12 bottom padding');
});
});
});