11import { render , screen } from '@testing-library/react' ;
22import { BlogPostPage } from '../blog-post-page' ;
3- import { BlogPost } from '@/content ' ;
3+ import { type BlogPost } from '@/lib ' ;
44
5- // Mock the markdown parser
6- jest . mock ( '@/lib/markdown-parser' , ( ) => ( {
5+ // Mock sub-components
6+ jest . mock ( '../post-navigation' , ( ) => ( {
7+ PostNavigation : jest . fn ( ( ) => < div data-testid = "post-navigation" /> )
8+ } ) ) ;
9+
10+ jest . mock ( '@/components/ui' , ( ) => ( {
11+ ThemeToggle : jest . fn ( ( ) => < div data-testid = "theme-toggle" /> ) ,
12+ Badge : jest . fn ( ( { children, ...props } ) => < span data-testid = "badge" { ...props } > { children } </ span > ) ,
13+ Card : jest . fn ( ( { children, ...props } ) => < div data-testid = "card" { ...props } > { children } </ div > ) ,
14+ CardContent : jest . fn ( ( { children, ...props } ) => < div data-testid = "card-content" { ...props } > { children } </ div > )
15+ } ) ) ;
16+
17+ jest . mock ( '@/components/sections' , ( ) => ( {
18+ Footer : jest . fn ( ( { content } ) => < footer data-testid = "footer" > { JSON . stringify ( content ) } </ footer > )
19+ } ) ) ;
20+
21+ jest . mock ( '@/lib' , ( ) => ( {
722 parseMarkdown : jest . fn ( ( content : string ) => ( {
8- content : `<div class="markdown-content">
9- <h1>Building Offline-First Apps</h1>
10- <p>This is a comprehensive guide to building offline-first applications.</p>
11- <h2>Code Example</h2>
12- <pre><code>const syncData = async () => {
13- const offlineData = await getOfflineData();
14- const onlineData = await fetchOnlineData();
15- return mergeData(offlineData, onlineData);
16- };</code></pre>
17- <h2>Key Points</h2>
18- <ul>
19- <li>Real-time synchronization</li>
20- <li>Conflict resolution</li>
21- <li>Data persistence</li>
22- </ul>
23- </div>`
23+ content : `<div class="markdown-content">${ content } </div>`
2424 } ) )
2525} ) ) ;
2626
27+ // Mock footer content
28+ jest . mock ( '@/content' , ( ) => ( {
29+ ...jest . requireActual ( '@/content' ) ,
30+ footerContent : {
31+ copyright : { title : 'Test' , companyName : 'Test' , showcaseMessage : 'Test' } ,
32+ repository : { title : 'Test' , url : 'test' , text : 'Test' } ,
33+ license : { title : 'Test' , name : 'Test' , description : 'Test' } ,
34+ buildInfo : 'Test'
35+ }
36+ } ) ) ;
37+
2738const mockBlogPost : BlogPost = {
2839 title : 'Building Offline-First Apps' ,
2940 slug : 'building-offline-first-apps' ,
3041 date : new Date ( '2025-01-15' ) ,
3142 excerpt : 'Real-time synchronization strategies for mobile applications' ,
3243 tags : [ 'React Native' , 'Offline' , 'Sync' ] ,
3344 category : 'Mobile Development' ,
34- content : `
35- # Building Offline-First Apps
36-
37- This is a comprehensive guide to building offline-first applications.
38-
39- ## Code Example
40-
41- \`\`\`javascript
42- const syncData = async () => {
43- const offlineData = await getOfflineData();
44- const onlineData = await fetchOnlineData();
45- return mergeData(offlineData, onlineData);
46- };
47- \`\`\`
48-
49- ## Key Points
50-
51- - Real-time synchronization
52- - Conflict resolution
53- - Data persistence
54- ` ,
45+ content : '# Test Content\nThis is test content.' ,
5546 readingTime : 8 ,
5647 published : true
5748} ;
@@ -61,6 +52,7 @@ const prevPost = {
6152 title : 'Prev Post' ,
6253 slug : 'prev-post' ,
6354} ;
55+
6456const nextPost = {
6557 ...mockBlogPost ,
6658 title : 'Next Post' ,
@@ -69,109 +61,51 @@ const nextPost = {
6961
7062describe ( 'BlogPostPage' , ( ) => {
7163 beforeEach ( ( ) => {
72- // Clear all mocks before each test
7364 jest . clearAllMocks ( ) ;
7465 } ) ;
7566
76- it ( 'renders blog post content ' , ( ) => {
67+ it ( 'renders blog post title ' , ( ) => {
7768 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
78-
79- // There are two h1s (header and markdown), so use getAllByText
80- expect ( screen . getAllByText ( 'Building Offline-First Apps' ) . length ) . toBeGreaterThan ( 0 ) ;
81- expect ( screen . getByText ( 'Real-time synchronization strategies for mobile applications' ) ) . toBeInTheDocument ( ) ;
69+ expect ( screen . getByText ( 'Building Offline-First Apps' ) ) . toBeInTheDocument ( ) ;
8270 } ) ;
8371
84- it ( 'displays metadata correctly ' , ( ) => {
72+ it ( 'displays post metadata ' , ( ) => {
8573 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
86-
87- // Check date
8874 expect ( screen . getByText ( '2025-01-15' ) ) . toBeInTheDocument ( ) ;
89-
90- // Check reading time
9175 expect ( screen . getByText ( '8 min read' ) ) . toBeInTheDocument ( ) ;
92-
93- // Check category
9476 expect ( screen . getByText ( 'Mobile Development' ) ) . toBeInTheDocument ( ) ;
9577 } ) ;
9678
97- it ( 'displays tags as badges ' , ( ) => {
79+ it ( 'displays post excerpt ' , ( ) => {
9880 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
99-
100- expect ( screen . getByText ( 'React Native' ) ) . toBeInTheDocument ( ) ;
101- expect ( screen . getByText ( 'Offline' ) ) . toBeInTheDocument ( ) ;
102- expect ( screen . getByText ( 'Sync' ) ) . toBeInTheDocument ( ) ;
103- } ) ;
104-
105- it ( 'renders markdown content with proper styling' , ( ) => {
106- render ( < BlogPostPage post = { mockBlogPost } /> ) ;
107- // Markdown content is in a <p> and <h2> etc, so use getByText for each
108- expect ( screen . getByText ( 'This is a comprehensive guide to building offline-first applications.' ) ) . toBeInTheDocument ( ) ;
109- expect ( screen . getByText ( 'Code Example' ) ) . toBeInTheDocument ( ) ;
110- expect ( screen . getByText ( 'Key Points' ) ) . toBeInTheDocument ( ) ;
111- } ) ;
112-
113- it ( 'renders code blocks with proper styling' , ( ) => {
114- render ( < BlogPostPage post = { mockBlogPost } /> ) ;
115- // Use regex to match code lines, since they may be split by whitespace
116- expect ( screen . getByText ( / c o n s t s y n c D a t a = a s y n c / ) ) . toBeInTheDocument ( ) ;
117- expect ( screen . getByText ( / c o n s t o f f l i n e D a t a = a w a i t g e t O f f l i n e D a t a / ) ) . toBeInTheDocument ( ) ;
118- expect ( screen . getByText ( / c o n s t o n l i n e D a t a = a w a i t f e t c h O n l i n e D a t a / ) ) . toBeInTheDocument ( ) ;
119- expect ( screen . getByText ( / r e t u r n m e r g e D a t a \( o f f l i n e D a t a , o n l i n e D a t a \) / ) ) . toBeInTheDocument ( ) ;
120- expect ( screen . getByText ( / } ; / ) ) . toBeInTheDocument ( ) ;
81+ expect ( screen . getByText ( 'Real-time synchronization strategies for mobile applications' ) ) . toBeInTheDocument ( ) ;
12182 } ) ;
12283
123- it ( 'includes navigation links ' , ( ) => {
84+ it ( 'renders tags as badges ' , ( ) => {
12485 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
125-
126- // Check for back to blog list link
127- expect ( screen . getByText ( '← Back to Blog ' ) ) . toBeInTheDocument ( ) ;
86+ expect ( screen . getByText ( 'React Native' ) ) . toBeInTheDocument ( ) ;
87+ expect ( screen . getByText ( 'Offline' ) ) . toBeInTheDocument ( ) ;
88+ expect ( screen . getByText ( 'Sync ' ) ) . toBeInTheDocument ( ) ;
12889 } ) ;
12990
130- it ( 'includes SEO elements ' , ( ) => {
91+ it ( 'renders markdown content ' , ( ) => {
13192 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
132- // There are two h1s, so use getAllByRole
133- const h1s = screen . getAllByRole ( 'heading' , { level : 1 } ) ;
134- expect ( h1s [ 0 ] ) . toHaveTextContent ( 'Building Offline-First Apps' ) ;
135- // Check for article element
136- expect ( screen . getByRole ( 'article' ) ) . toBeInTheDocument ( ) ;
93+ expect ( screen . getByTestId ( 'blog-content' ) ) . toBeInTheDocument ( ) ;
13794 } ) ;
13895
139- it ( 'applies proper typography classes ' , ( ) => {
96+ it ( 'includes navigation components ' , ( ) => {
14097 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
141- // There are two h1s, so use getAllByRole
142- const h1s = screen . getAllByRole ( 'heading' , { level : 1 } ) ;
143- expect ( h1s [ 0 ] ) . toHaveClass ( 'font-mono' ) ;
144- // Check that body text uses Inter
145- const article = screen . getByRole ( 'article' ) ;
146- expect ( article ) . toHaveClass ( 'font-sans' ) ;
98+ expect ( screen . getByTestId ( 'post-navigation' ) ) . toBeInTheDocument ( ) ;
14799 } ) ;
148100
149- it ( 'displays metadata with muted colors ' , ( ) => {
101+ it ( 'includes theme toggle ' , ( ) => {
150102 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
151-
152- // Check that metadata elements have muted text color
153- const dateElement = screen . getByText ( '2025-01-15' ) ;
154- expect ( dateElement ) . toHaveClass ( 'text-muted' ) ;
155-
156- const readingTimeElement = screen . getByText ( '8 min read' ) ;
157- expect ( readingTimeElement ) . toHaveClass ( 'text-muted' ) ;
103+ expect ( screen . getByTestId ( 'theme-toggle' ) ) . toBeInTheDocument ( ) ;
158104 } ) ;
159105
160- it ( 'renders navigation with accent colors ' , ( ) => {
106+ it ( 'includes footer ' , ( ) => {
161107 render ( < BlogPostPage post = { mockBlogPost } /> ) ;
162-
163- const backLink = screen . getByText ( '← Back to Blog' ) ;
164- expect ( backLink ) . toHaveClass ( 'text-accent' ) ;
165- } ) ;
166-
167- it ( 'handles empty content gracefully' , ( ) => {
168- const emptyPost : BlogPost = {
169- ...mockBlogPost ,
170- content : ''
171- } ;
172- render ( < BlogPostPage post = { emptyPost } /> ) ;
173- expect ( screen . getAllByText ( 'Building Offline-First Apps' ) . length ) . toBeGreaterThan ( 0 ) ;
174- expect ( screen . getByRole ( 'article' ) ) . toBeInTheDocument ( ) ;
108+ expect ( screen . getByTestId ( 'footer' ) ) . toBeInTheDocument ( ) ;
175109 } ) ;
176110
177111 it ( 'handles posts without tags' , ( ) => {
@@ -180,16 +114,12 @@ describe('BlogPostPage', () => {
180114 tags : [ ]
181115 } ;
182116 render ( < BlogPostPage post = { postWithoutTags } /> ) ;
183- expect ( screen . getAllByText ( 'Building Offline-First Apps' ) . length ) . toBeGreaterThan ( 0 ) ;
117+ expect ( screen . getByText ( 'Building Offline-First Apps' ) ) . toBeInTheDocument ( ) ;
184118 expect ( screen . queryByText ( 'React Native' ) ) . not . toBeInTheDocument ( ) ;
185119 } ) ;
186120
187- it ( 'renders post navigation with prev and next links ' , ( ) => {
121+ it ( 'passes navigation props correctly ' , ( ) => {
188122 render ( < BlogPostPage post = { mockBlogPost } prev = { prevPost } next = { nextPost } /> ) ;
189- expect ( screen . getByText ( '← Prev Post' ) ) . toBeInTheDocument ( ) ;
190- expect ( screen . getByText ( 'Next Post →' ) ) . toBeInTheDocument ( ) ;
191- expect ( screen . getByText ( '← Prev Post' ) . closest ( 'a' ) ) . toHaveAttribute ( 'href' , '/blog/prev-post' ) ;
192- expect ( screen . getByText ( 'Next Post →' ) . closest ( 'a' ) ) . toHaveAttribute ( 'href' , '/blog/next-post' ) ;
193- expect ( screen . getByText ( '← Back to Blog' ) ) . toBeInTheDocument ( ) ;
123+ expect ( screen . getByTestId ( 'post-navigation' ) ) . toBeInTheDocument ( ) ;
194124 } ) ;
195125} ) ;
0 commit comments