Skip to content

Commit eedfada

Browse files
mahataCopilot
andauthored
Restructure application layout and implement header for post pages (#27)
* feat: restructure application layout and implement header for post pages * Update src/pages/Home.ct.spec.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: remove header rendering checks from Post component tests --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e6e9a05 commit eedfada

8 files changed

Lines changed: 106 additions & 84 deletions

File tree

src/App.ct.spec.tsx

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/App.tsx

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,3 @@
1-
import { use, Suspense, useMemo } from 'react'
2-
import { useSearchParams } from 'react-router-dom'
3-
import { Header } from '@/Header'
4-
import { PostList } from '@/components/PostList'
5-
import { Pagination } from '@/components/Pagination'
6-
import { ErrorBoundary } from '@/components/ErrorBoundary'
7-
import { extractPostsMetadata } from '@/utils/posts'
8-
import { postsModules } from '@/utils/postsModules'
1+
import { Home } from '@/pages/Home'
92

10-
const POSTS_PER_PAGE = 10
11-
12-
function PostsContent() {
13-
const postsPromise = useMemo(() => extractPostsMetadata(postsModules), [])
14-
const posts = use(postsPromise)
15-
const [searchParams] = useSearchParams()
16-
17-
const pageParam = searchParams.get('page')
18-
const currentPage = Math.max(1, Number.parseInt(pageParam || '1', 10))
19-
20-
const totalPages = Math.ceil(posts.length / POSTS_PER_PAGE)
21-
const validPage = Math.min(currentPage, totalPages || 1)
22-
23-
const startIndex = (validPage - 1) * POSTS_PER_PAGE
24-
const endIndex = startIndex + POSTS_PER_PAGE
25-
const paginatedPosts = posts.slice(startIndex, endIndex)
26-
27-
return (
28-
<>
29-
<PostList posts={paginatedPosts} />
30-
<Pagination currentPage={validPage} totalPages={totalPages} />
31-
</>
32-
)
33-
}
34-
35-
function App() {
36-
return (
37-
<>
38-
<Header />
39-
<main>
40-
<ErrorBoundary>
41-
<Suspense fallback={<p>Loading posts...</p>}>
42-
<PostsContent />
43-
</Suspense>
44-
</ErrorBoundary>
45-
</main>
46-
</>
47-
)
48-
}
49-
50-
export default App
3+
export default Home

src/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styles from '@/Header.module.css';
33
export function Header() {
44
return (
55
<header className={styles.header}>
6-
<img src="logo.webp" alt="Logo" className={styles.logo} />
6+
<img src="/logo.webp" alt="Logo" className={styles.logo} />
77
</header>
88
)
99
}

src/Layout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Outlet } from 'react-router-dom'
2+
import { Header } from '@/Header'
3+
4+
export function Layout() {
5+
return (
6+
<>
7+
<Header />
8+
<main>
9+
<Outlet />
10+
</main>
11+
</>
12+
)
13+
}

src/main.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@ import "@acab/reset.css"
22
import { StrictMode } from 'react'
33
import { createRoot } from 'react-dom/client'
44
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
5-
import App from '@/App'
5+
import { Layout } from '@/Layout'
6+
import { Home } from '@/pages/Home'
67
import Health from '@/routes/Health'
78
import Post from '@/routes/Post'
89

910
const router = createBrowserRouter([
1011
{
11-
path: '/',
12-
element: <App />,
12+
element: <Layout />,
13+
children: [
14+
{
15+
path: '/',
16+
element: <Home />,
17+
},
18+
{
19+
path: '/posts/:slug',
20+
element: <Post />,
21+
},
22+
],
1323
},
1424
{
1525
path: '/health',
1626
element: <Health />,
1727
},
18-
{
19-
path: '/posts/:slug',
20-
element: <Post />,
21-
},
2228
])
2329

2430
createRoot(document.getElementById('root')!).render(

src/pages/Home.ct.spec.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { test, expect } from '@playwright/experimental-ct-react'
2+
import type { ComponentFixtures } from '@playwright/experimental-ct-react'
3+
import { MemoryRouter } from 'react-router-dom'
4+
import { Header } from '@/Header'
5+
import { Home } from '@/pages/Home'
6+
7+
const mountHome = async (mount: ComponentFixtures['mount']) => {
8+
return await mount(
9+
<MemoryRouter initialEntries={['/']}>
10+
<main>
11+
<Home />
12+
</main>
13+
</MemoryRouter>,
14+
)
15+
}
16+
17+
test('should render post list and pagination', async ({ mount }) => {
18+
const component = await mountHome(mount)
19+
// Wait for posts to load - find the list by looking for the first post link
20+
await expect(component.getByRole('link').filter({ hasText: /.+/ }).first()).toBeVisible()
21+
22+
// Check pagination controls exist
23+
await expect(component.locator('nav')).toBeVisible()
24+
})

src/pages/Home.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { use, Suspense, useMemo } from 'react'
2+
import { useSearchParams } from 'react-router-dom'
3+
import { PostList } from '@/components/PostList'
4+
import { Pagination } from '@/components/Pagination'
5+
import { ErrorBoundary } from '@/components/ErrorBoundary'
6+
import { extractPostsMetadata } from '@/utils/posts'
7+
import { postsModules } from '@/utils/postsModules'
8+
9+
const POSTS_PER_PAGE = 10
10+
11+
function PostsContent() {
12+
const postsPromise = useMemo(() => extractPostsMetadata(postsModules), [])
13+
const posts = use(postsPromise)
14+
const [searchParams] = useSearchParams()
15+
16+
const pageParam = searchParams.get('page')
17+
const currentPage = Math.max(1, Number.parseInt(pageParam || '1', 10))
18+
19+
const totalPages = Math.ceil(posts.length / POSTS_PER_PAGE)
20+
const validPage = Math.min(currentPage, totalPages || 1)
21+
22+
const startIndex = (validPage - 1) * POSTS_PER_PAGE
23+
const endIndex = startIndex + POSTS_PER_PAGE
24+
const paginatedPosts = posts.slice(startIndex, endIndex)
25+
26+
return (
27+
<>
28+
<PostList posts={paginatedPosts} />
29+
<Pagination currentPage={validPage} totalPages={totalPages} />
30+
</>
31+
)
32+
}
33+
34+
export function Home() {
35+
return (
36+
<ErrorBoundary>
37+
<Suspense fallback={<p>Loading posts...</p>}>
38+
<PostsContent />
39+
</Suspense>
40+
</ErrorBoundary>
41+
)
42+
}

src/routes/Post.ct.spec.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ const mountAtPath = async (
1010
) => {
1111
return await mount(
1212
<MemoryRouter initialEntries={[path]}>
13-
<Routes>
14-
<Route path="/posts/:slug" element={<Post baseDir={baseDir} />} />
15-
</Routes>
13+
<main>
14+
<Routes>
15+
<Route path="/posts/:slug" element={<Post baseDir={baseDir} />} />
16+
</Routes>
17+
</main>
1618
</MemoryRouter>,
1719
)
1820
}
1921

20-
test('renders existing markdown post', async ({ mount }) => {
22+
test('renders header and existing markdown post', async ({ mount }) => {
2123
const component = await mountAtPath(mount, '/posts/yo', 'posts')
2224

2325
await expect(component.getByRole('heading', { name: 'Hey' })).toBeVisible()
@@ -79,10 +81,11 @@ test('handles markdown with potentially malicious HTML content', async ({ mount
7981
await expect(component.getByRole('heading', { name: 'Safe Heading' })).toBeVisible()
8082

8183
// Verify that HTML tags (script, img) are stripped by remark
82-
// The HTML elements should not be present in the rendered output
83-
// We verify by checking no script or img roles/elements exist
84-
const scriptCount = await component.locator('script').count()
85-
const imgCount = await component.locator('img').count()
84+
// The HTML elements should not be present in the rendered article content
85+
// We verify by checking no script or img elements exist in the article
86+
const article = component.locator('article')
87+
const scriptCount = await article.locator('script').count()
88+
const imgCount = await article.locator('img').count()
8689
expect(scriptCount).toBe(0)
8790
expect(imgCount).toBe(0)
8891
})

0 commit comments

Comments
 (0)