Skip to content

Commit 7c7154e

Browse files
authored
test: add behavior tests for uncovered branches (#503)
1 parent bfa27e6 commit 7c7154e

6 files changed

Lines changed: 129 additions & 2 deletions

File tree

__tests__/api/reactions-slug.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,24 @@ describe('API /api/reactions/[slug]', () => {
113113
reactions: { heart: 6, beer: 1, trophy: 0 },
114114
});
115115
});
116+
117+
it('zero-fills types missing from the DB (first reaction on a post)', async () => {
118+
mock$transaction.mockResolvedValue([
119+
{ slug: 'my-post', type: 'heart', count: 1n },
120+
[{ type: 'heart', count: 1n }],
121+
]);
122+
const { req, res, status, json } = createMockReqRes({
123+
method: 'POST',
124+
body: { type: 'heart' },
125+
});
126+
127+
await handler(req, res);
128+
129+
expect(status).toHaveBeenCalledWith(200);
130+
expect(json).toHaveBeenCalledWith({
131+
reactions: { heart: 1, beer: 0, trophy: 0 },
132+
});
133+
});
116134
});
117135

118136
describe('unsupported method', () => {

__tests__/blog.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ describe('Blog Page', () => {
5858
expect(blogPostsLinks).toHaveLength(POSTS.length + 1);
5959
});
6060

61+
test('uses singular "article" in the counter for exactly one post', () => {
62+
render(<BlogPage posts={[POSTS[0]]} categories={CATEGORIES} />);
63+
const heading = screen.getByRole('heading', { name: 'Blog 1 article' });
64+
65+
expect(heading).toBeInTheDocument();
66+
});
67+
6168
test('on search correct filtered exist articles', () => {
6269
render(<BlogPage posts={POSTS} categories={CATEGORIES} />);
6370
const inputElement = screen.getByLabelText('Search articles');
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { render, screen } from '@testing-library/react';
2+
3+
import { NoResults } from '@/components/no-results';
4+
5+
describe('NoResults', () => {
6+
it('echoes the searched term so the user can correct it', () => {
7+
render(<NoResults searchValue="reactt" />);
8+
9+
expect(
10+
screen.getByText(/We couldn't find any articles matching/),
11+
).toBeInTheDocument();
12+
expect(screen.getByText('"reactt"')).toBeInTheDocument();
13+
});
14+
15+
it('falls back to a generic message when the search term is empty', () => {
16+
render(<NoResults searchValue="" />);
17+
18+
expect(
19+
screen.getByText('No articles available at the moment.'),
20+
).toBeInTheDocument();
21+
});
22+
});

components/tag/tag.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { render, screen } from '@testing-library/react';
2+
3+
import { Tag } from '@/components/tag';
4+
5+
describe('Tag', () => {
6+
it('renders an accessible link to a route (category navigation)', () => {
7+
render(<Tag label="All" href="/blog" />);
8+
9+
expect(screen.getByRole('link', { name: 'All' })).toHaveAttribute(
10+
'href',
11+
'/blog',
12+
);
13+
});
14+
15+
it('keeps the link contract for the inline variant (post category tags)', () => {
16+
render(<Tag variant="inline" label="#react" href="/blog/category/react" />);
17+
18+
expect(screen.getByRole('link', { name: '#react' })).toHaveAttribute(
19+
'href',
20+
'/blog/category/react',
21+
);
22+
});
23+
24+
it('renders an accessible in-page link for hash hrefs (section navigation)', () => {
25+
render(<Tag label="Podcasts" href="#podcasts" />);
26+
27+
expect(screen.getByRole('link', { name: 'Podcasts' })).toHaveAttribute(
28+
'href',
29+
'#podcasts',
30+
);
31+
});
32+
});
Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { render, screen } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
3+
import { renderToString } from 'react-dom/server';
34

45
const mockSetTheme = jest.fn();
6+
let mockResolvedTheme: 'light' | 'dark' = 'dark';
57

68
jest.mock('next-themes', () => ({
79
useTheme: () => ({
8-
resolvedTheme: 'dark',
10+
resolvedTheme: mockResolvedTheme,
911
setTheme: mockSetTheme,
1012
}),
1113
}));
@@ -14,11 +16,41 @@ import { ThemeChanger } from '@/components/theme-changer';
1416

1517
describe('ThemeChanger', () => {
1618
it('toggles from dark to light on click', async () => {
19+
mockResolvedTheme = 'dark';
1720
const user = userEvent.setup();
1821
render(<ThemeChanger />);
1922

20-
await user.click(screen.getByRole('button'));
23+
const button = screen.getByRole('button', {
24+
name: 'Switch to light mode',
25+
});
26+
expect(button).toHaveAttribute('aria-pressed', 'true');
27+
28+
await user.click(button);
2129

2230
expect(mockSetTheme).toHaveBeenCalledWith('light');
2331
});
32+
33+
it('toggles from light to dark on click', async () => {
34+
mockResolvedTheme = 'light';
35+
const user = userEvent.setup();
36+
render(<ThemeChanger />);
37+
38+
const button = screen.getByRole('button', {
39+
name: 'Switch to dark mode',
40+
});
41+
expect(button).toHaveAttribute('aria-pressed', 'false');
42+
43+
await user.click(button);
44+
45+
expect(mockSetTheme).toHaveBeenCalledWith('dark');
46+
});
47+
48+
it('renders an icon-free placeholder on the server to avoid hydration mismatch', () => {
49+
mockResolvedTheme = 'dark';
50+
51+
const html = renderToString(<ThemeChanger />);
52+
53+
expect(html).toContain('<button');
54+
expect(html).not.toContain('svg');
55+
});
2456
});

lib/schema.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ describe('generateTechArticleSchema', () => {
8686
expect(schema.proficiencyLevel).toBe('Expert');
8787
});
8888

89+
it('should include dateModified when updateDate provided', () => {
90+
const snippet = {
91+
...validSnippet,
92+
updateDate: new Date('2024-05-10').getTime(),
93+
};
94+
const schema = generateTechArticleSchema(snippet);
95+
96+
expect(schema.dateModified).toBe('2024-05-10T00:00:00.000Z');
97+
});
98+
99+
it('should not include dateModified when updateDate is null', () => {
100+
const schema = generateTechArticleSchema(validSnippet);
101+
102+
expect(schema).not.toHaveProperty('dateModified');
103+
});
104+
89105
it('should use provided programmingLanguage', () => {
90106
const snippet = { ...validSnippet, programmingLanguage: 'TypeScript' };
91107
const schema = generateTechArticleSchema(snippet);

0 commit comments

Comments
 (0)