From 10804705bc8b2aeb5a71105b09d1ee2269750257 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 26 May 2026 23:21:07 +0000 Subject: [PATCH] feat: build index page with sorted post listing and ArticleCard components This replaces the smoke test index with a real page that: - Imports getCollection, BaseLayout, NavBar, ArticleCard, getReadingTime - Fetches all posts and sorts descending by date - Maps posts to ArticleCard components with title, date, type, summary, slug, readingTime - Styles .post-list with prose max-width, space-6/space-12 margins, flex column, space-2 gap Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- src/pages/index.astro | 53 ++++++++------ tests/index-page.test.mjs | 142 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 21 deletions(-) create mode 100644 tests/index-page.test.mjs diff --git a/src/pages/index.astro b/src/pages/index.astro index aafbb95..3ca95df 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,28 +1,39 @@ --- +import { getCollection } from 'astro:content'; import BaseLayout from '../layouts/BaseLayout.astro'; import NavBar from '../components/NavBar.astro'; -import PostHeader from '../components/PostHeader.astro'; +import ArticleCard from '../components/ArticleCard.astro'; +import { getReadingTime } from '../utils/reading-time'; + +const posts = await getCollection('posts'); +const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); --- - + - -
-

- This is the prose voice — Source Serif 4 at 18px. It carries the reader through long-form - content with a warm, readable serif that feels like ink on parchment. -

-

- This is the structure voice — Inter at 14px in secondary color. It handles navigation, - labels, metadata, and UI chrome with a clean sans-serif that stays out of the way. -

- - const buildTime = 7 * previousBuildTime; // evidence voice — JetBrains Mono - -
+
+ {sortedPosts.map((post) => ( + + ))} +
+ + diff --git a/tests/index-page.test.mjs b/tests/index-page.test.mjs new file mode 100644 index 0000000..af0ad9f --- /dev/null +++ b/tests/index-page.test.mjs @@ -0,0 +1,142 @@ +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/index.astro'); + +function readPage() { + return readFileSync(pagePath, 'utf-8'); +} + +describe('Index page', () => { + it('should exist at src/pages/index.astro', () => { + assert.ok(existsSync(pagePath), 'Index page file should exist'); + }); + + describe('Imports', () => { + it('should import getCollection from astro:content', () => { + const content = readPage(); + assert.match(content, /import\s+.*getCollection.*from\s+['"]astro:content['"]/, 'should import getCollection'); + }); + + 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 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'); + }); + }); + + describe('Data fetching', () => { + it('should fetch all posts with getCollection', () => { + const content = readPage(); + assert.match(content, /getCollection\s*\(\s*['"]posts['"]\s*\)/, 'should call getCollection("posts")'); + }); + + it('should sort posts descending by date', () => { + 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'); + }); + }); + + describe('Template', () => { + it('should use BaseLayout with title Crash Test Dev', () => { + const content = readPage(); + assert.match(content, / { + const content = readPage(); + assert.match(content, / { + const content = readPage(); + assert.match(content, / { + const content = readPage(); + assert.match(content, /sortedPosts\.map/, 'should map sortedPosts'); + assert.match(content, / { + 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', () => { + 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'); + }); + + it('should have space-6 top and space-12 bottom margins', () => { + 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'); + }); + + it('should have auto left and right margins', () => { + 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'); + }); + + it('should have flex column display', () => { + 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'); + }); + }); +}); \ No newline at end of file