feat: redesign homepage from flat card list to book-style pitch page
This commit is contained in:
@@ -35,39 +35,45 @@ describe('Clean build output', () => {
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 2. Index page structure
|
||||
// 2. Index page structure (book-style pitch page)
|
||||
// ============================================================
|
||||
describe('Index page', () => {
|
||||
const html = readDist('index.html');
|
||||
|
||||
test('contains two article cards', () => {
|
||||
const cardMatches = html.match(/<article[^>]*class="card"/g);
|
||||
assert.ok(cardMatches, 'No article cards found');
|
||||
assert.equal(cardMatches.length, 2, `Expected 2 article cards, got ${cardMatches.length}`);
|
||||
test('has hero section with site title', () => {
|
||||
assert.ok(html.includes('class="hero"') || html.includes('class=\"hero\"'),
|
||||
'Should have hero section');
|
||||
assert.ok(html.includes('Crash Test Dev'), 'Should have site title');
|
||||
});
|
||||
|
||||
test('card surface uses warm off-white (--surface-card)', () => {
|
||||
assert.ok(html.includes('var(--surface-card)'), 'Card should use --surface-card token');
|
||||
test('has featured excerpt section', () => {
|
||||
assert.ok(html.includes('class="featured"') || html.includes('class=\"featured\"'),
|
||||
'Should have featured section');
|
||||
assert.ok(html.includes('class="excerpt"') || html.includes('class=\"excerpt\"'),
|
||||
'Should have excerpt blockquote');
|
||||
});
|
||||
|
||||
test('card has subtle shadow (--shadow-sm)', () => {
|
||||
assert.ok(html.includes('var(--shadow-sm)'), 'Card should use --shadow-sm token');
|
||||
test('has series section with Rage', () => {
|
||||
assert.ok(html.includes('class="series"') || html.includes('class=\"series\"'),
|
||||
'Should have series section');
|
||||
assert.ok(html.includes('Rage'), 'Should mention Rage series');
|
||||
});
|
||||
|
||||
test('card hover shows deeper shadow (--shadow-md)', () => {
|
||||
assert.ok(html.includes('var(--shadow-md)'), 'Card hover should use --shadow-md token');
|
||||
test('has all-posts section with post entries', () => {
|
||||
assert.ok(html.includes('class="all-posts"') || html.includes('class=\"all-posts\"'),
|
||||
'Should have all-posts section');
|
||||
const entryMatches = html.match(/class="post-entry"/g);
|
||||
assert.ok(entryMatches, 'No post entries found');
|
||||
assert.ok(entryMatches.length >= 2, `Expected at least 2 post entries, got ${entryMatches.length}`);
|
||||
});
|
||||
|
||||
test('card hover shows terracotta title (--accent-primary)', () => {
|
||||
assert.ok(
|
||||
html.includes(':hover .title') || html.includes(':hover .title{'),
|
||||
'Card hover should style the title'
|
||||
);
|
||||
assert.ok(html.includes('var(--accent-primary)'), 'Title hover should use terracotta accent');
|
||||
test('uses terracotta accent (--accent-primary)', () => {
|
||||
// Astro scoped styles compile to external CSS, check source instead
|
||||
const source = readFileSync('src/pages/index.astro', 'utf-8');
|
||||
assert.ok(source.includes('var(--accent-primary)'), 'Should use terracotta accent in source');
|
||||
});
|
||||
|
||||
test('card links have correct URL format with slash separator', () => {
|
||||
// The base is /crashtestdev and posts must be at /crashtestdev/posts/...
|
||||
test('post links have correct URL format with slash separator', () => {
|
||||
const linkRegex = /href="([^"]*posts[^"]*)"/g;
|
||||
let match;
|
||||
let found = false;
|
||||
@@ -83,7 +89,7 @@ describe('Index page', () => {
|
||||
`URL "${url}" should not concatenate base and posts without slash`
|
||||
);
|
||||
}
|
||||
assert.ok(found, 'Should find article card links');
|
||||
assert.ok(found, 'Should find post links');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+88
-70
@@ -30,11 +30,6 @@ describe('Index page', () => {
|
||||
assert.match(content, /import\s+NavBar\s+from\s+['"].*NavBar\.astro['"]/, 'should import NavBar');
|
||||
});
|
||||
|
||||
it('should import ArticleCard', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /import\s+ArticleCard\s+from\s+['"].*ArticleCard\.astro['"]/, 'should import ArticleCard');
|
||||
});
|
||||
|
||||
it('should import getReadingTime', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /import\s+.*getReadingTime.*from\s+['"].*reading-time['"]/, 'should import getReadingTime');
|
||||
@@ -46,15 +41,89 @@ describe('Index page', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /getCollection\s*\(\s*['"]posts['"]\s*\)/, 'should call getCollection("posts")');
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort posts descending by date', () => {
|
||||
describe('Hero section', () => {
|
||||
it('should have a hero section', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /sort\s*\(/, 'should call sort');
|
||||
assert.match(content, /b\.data\.date\.getTime\(\)\s*-\s*a\.data\.date\.getTime\(\)/, 'should sort descending by date');
|
||||
assert.match(content, /class="hero"/, 'should have a section with class hero');
|
||||
});
|
||||
|
||||
it('should have the site title Crash Test Dev as a heading', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /<h1[^>]*>Crash Test Dev<\/h1>/, 'should have h1 with site title');
|
||||
});
|
||||
|
||||
it('should have a tagline in prose font', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="tagline"/, 'should have a tagline element');
|
||||
});
|
||||
|
||||
it('should have an author byline', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="byline"/, 'should have a byline element');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Template', () => {
|
||||
describe('Featured excerpt section', () => {
|
||||
it('should have a featured excerpt section', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="featured"/, 'should have a section with class featured');
|
||||
});
|
||||
|
||||
it('should contain an excerpt blockquote', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /<blockquote[^>]*class="excerpt"/, 'should have a blockquote with class excerpt');
|
||||
});
|
||||
|
||||
it('should have a link to the full post', () => {
|
||||
const content = readPage();
|
||||
// The featured section should contain a link to read the full post
|
||||
assert.match(content, /class="featured-link"/, 'should have a featured-link to the full post');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Series section', () => {
|
||||
it('should have a series section', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="series"/, 'should have a section with class series');
|
||||
});
|
||||
|
||||
it('should mention the Rage series', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /Rage/, 'should mention Rage in the series section');
|
||||
});
|
||||
|
||||
it('should list planned posts', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="series-item"/, 'should have series items');
|
||||
});
|
||||
|
||||
it('should indicate coming soon items', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /coming soon/i, 'should indicate coming soon posts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('All posts section', () => {
|
||||
it('should have a posts listing section below the pitch', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="all-posts"/, 'should have a section with class all-posts');
|
||||
});
|
||||
|
||||
it('should map posts to compact entries', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /sortedPosts\.map/, 'should map sortedPosts');
|
||||
});
|
||||
|
||||
it('should show post titles as links', () => {
|
||||
const content = readPage();
|
||||
// Each post entry should link to its page
|
||||
assert.match(content, /posts\/\$\{post\.id\}/, 'should link to post pages');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Layout', () => {
|
||||
it('should use BaseLayout with title Crash Test Dev', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /<BaseLayout\s+title\s*=\s*['"]Crash Test Dev['"]/, 'should have BaseLayout with title');
|
||||
@@ -64,79 +133,28 @@ describe('Index page', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /<NavBar\s+slot\s*=\s*["']nav["']/, 'should have NavBar with slot="nav"');
|
||||
});
|
||||
|
||||
it('should have a section with class post-list and aria-label', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /<section\s+class\s*=\s*["']post-list["']/, 'should have section.post-list');
|
||||
assert.match(content, /aria-label\s*=\s*["']All posts["']/, 'should have aria-label="All posts"');
|
||||
});
|
||||
|
||||
it('should map sortedPosts to ArticleCard components', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /sortedPosts\.map/, 'should map sortedPosts');
|
||||
assert.match(content, /<ArticleCard/, 'should render ArticleCard');
|
||||
});
|
||||
|
||||
it('should pass title from post.data.title', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /title\s*=\s*\{post\.data\.title\}/, 'should pass title');
|
||||
});
|
||||
|
||||
it('should pass date from post.data.date', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /date\s*=\s*\{post\.data\.date\}/, 'should pass date');
|
||||
});
|
||||
|
||||
it('should pass type from post.data.type', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /type\s*=\s*\{post\.data\.type\}/, 'should pass type');
|
||||
});
|
||||
|
||||
it('should pass summary from post.data.summary', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /summary\s*=\s*\{post\.data\.summary\}/, 'should pass summary');
|
||||
});
|
||||
|
||||
it('should pass slug as post.id', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /slug\s*=\s*\{post\.id\}/, 'should pass slug as post.id');
|
||||
});
|
||||
|
||||
it('should compute and pass readingTime from post.body', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /getReadingTime\s*\(\s*post\.body/, 'should call getReadingTime(post.body...)');
|
||||
assert.match(content, /readingTime\s*=\s*\{/, 'should pass readingTime prop');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Style', () => {
|
||||
it('should have .post-list with prose max-width', () => {
|
||||
it('should use --width-prose for content width', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.post-list\s*\{/, 'should have .post-list selector');
|
||||
assert.match(content, /max-width\s*:\s*var\(--width-prose\)/, 'should have prose max-width');
|
||||
assert.match(content, /max-width\s*:\s*var\(--width-prose\)/, 'should constrain content to prose width');
|
||||
});
|
||||
|
||||
it('should have space-6 top and space-12 bottom margins', () => {
|
||||
it('should use --font-structure for hero heading', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /margin-top\s*:\s*var\(--space-6\)/, 'should have space-6 top margin');
|
||||
assert.match(content, /margin-bottom\s*:\s*var\(--space-12\)/, 'should have space-12 bottom margin');
|
||||
assert.match(content, /\.hero[\s\S]*?font-family\s*:\s*var\(--font-structure\)/, 'should use structure font in hero');
|
||||
});
|
||||
|
||||
it('should have auto left and right margins', () => {
|
||||
it('should use --font-prose for excerpt', () => {
|
||||
const content = readPage();
|
||||
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, /\.excerpt[\s\S]*?font-family\s*:\s*var\(--font-prose\)/, 'should use prose font for excerpt');
|
||||
});
|
||||
|
||||
it('should have flex column display', () => {
|
||||
it('should not have the old .post-list as the main layout', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /display\s*:\s*flex/, 'should have flex display');
|
||||
assert.match(content, /flex-direction\s*:\s*column/, 'should have column direction');
|
||||
});
|
||||
|
||||
it('should have space-2 gap', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /gap\s*:\s*var\(--space-2\)/, 'should have space-2 gap');
|
||||
// The old flat list structure should be gone
|
||||
assert.doesNotMatch(content, /<ArticleCard/, 'should not use ArticleCard component');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user