Skip to content

Commit c3f9c12

Browse files
test: Increase coverage (#6349)
* test(utils): increase test coverage * stories: increase coverage * test(util): increase * test: providers * tests: hooks * chore(docs): small change about test * stories: add "effects" * stories: increase component tested * stories: add `LinkTabs` * test: next-data * chore(COLLABORATOR_GUIDE): apply review * Fix: typo "Github" -> "GitHub" Co-authored-by: Claudio W <[email protected]> Signed-off-by: Augustin Mauroy <[email protected]> * clean: story of `Event` * remove useless comment * test: use mock on `getBitness` * test: fix * test(util): increase * test(hooks): increase * fix: typo * refracto: `useBottomScrollListener.test.mjs` * re-add this test * fix ci ? * please ci * fix import * ci will work ? * fix ? * fix(docs) * fix: rebase --------- Signed-off-by: Augustin Mauroy <[email protected]> Signed-off-by: Claudio W <[email protected]> Co-authored-by: Claudio W <[email protected]>
1 parent cf31daf commit c3f9c12

36 files changed

+821
-49
lines changed

COLLABORATOR_GUIDE.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ Unit Tests are fundamental to ensure that code changes do not disrupt the functi
277277
- We also recommend mocking external dependencies, if unsure about how to mock a particular dependency, raise the question on your Pull Request.
278278
- We recommend using [Jest's Mock Functions](https://jestjs.io/docs/en/mock-functions) for mocking dependencies.
279279
- We recommend using [Jest's Mock Modules](https://jestjs.io/docs/en/manual-mocks) for mocking dependencies unavailable on the Node.js runtime.
280-
- Common Providers and Contexts from the lifecycle of our App, such as [`react-intl`][] should not be mocked but given an empty or fake context whenever possible.
280+
- Common Providers and Contexts from the lifecycle of our App, such as [`next-intl`][] should not be mocked but given an empty or fake context whenever possible.
281281
- We recommend reading previous unit tests from the codebase for inspiration and code guidelines.
282282

283283
### General Guidelines for Storybooks
@@ -340,7 +340,7 @@ This custom render uses `getStaticPaths` and [Incremental Static Generation](htt
340340
For example, this allows us to generate Localized Pages for every page that is not translated, by telling Next.js to create a localised path.
341341
`next.dynamic.mjs` is responsible for getting a full list of the source pages (`pages/en`) and identifying which pages have been translated.
342342

343-
Non-translated pages will have their Localized contexts and translated React message-bags (`react-intl`) but the content will be the same as the source page (English).
343+
Non-translated pages will have their Localized contexts and translated React message-bags (`next-intl`) but the content will be the same as the source page (English).
344344
Whereas localized pages will have localized context and content.
345345

346346
This custom solution is also able to decide what paths should be compiled during runtime.
@@ -491,12 +491,11 @@ If you're unfamiliar or curious about something, we recommend opening a Discussi
491491
[Jest]: https://jestjs.io/
492492
[React Testing Library]: https://testing-library.com/docs/react-testing-library/intro/
493493
[Storybook]: https://storybook.js.org/
494-
[`react-intl`]: https://formatjs.io/docs/react-intl/
494+
[`next-intl`]: https://next-intl-docs.vercel.app
495495
[Next.js]: https://nextjs.org/
496496
[MDX]: https://mdxjs.com/
497497
[PostCSS]: https://postcss.org/
498498
[React]: https://react.dev/
499499
[Shiki]: https://github.com/shikijs/shiki
500500
[Tailwind]: https://tailwindcss.com/
501501
[Radix UI]: https://www.radix-ui.com/
502-
[`next-intl`]: https://www.npmjs.com/package/next-intl

DEPENDENCY_PINNING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ When adding dependencies, you should consider if that dependency should be saved
2121

2222
- A dependency, in general, should be pinned to its exact dependency if it's either a tooling or a CLI dependency. Examples include `husky`, `prettier`, `jest` and others.
2323
- A dependency should generally use `~` if we're interested in patch updates (such as hot-fixes and bug-fixes) and the package is part of the Development or Testing Environment. (Such as `storybook`, for example)
24-
- A dependency should generally use `^` if they're part of the Website Application itself, such as `react`, `react-intl` etc. This is done because we intentionally want to get these dependencies' latest features and bug-fixes.
24+
- A dependency should generally use `^` if they're part of the Website Application itself, such as `react`, `next-intl` etc. This is done because we intentionally want to get these dependencies' latest features and bug-fixes.
2525
- If we're not interested in getting the latest features and bug fixes, we should consider using `~` instead.
2626
- Node. js-only dependencies used in scripts or during the build process of the Website (not used within actual Application code) should use `~` instead. Examples include `glob`, `@nodevu/core`
2727
- TypeScript type packages of corresponding packages should follow the same `semver` of their respective packages
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import LinkTabs from '@/components/Common/LinkTabs';
4+
5+
type Story = StoryObj<typeof LinkTabs>;
6+
type Meta = MetaObj<typeof LinkTabs>;
7+
8+
export const Default: Story = {
9+
args: {
10+
label: 'Select Tab',
11+
tabs: [
12+
{
13+
key: 'package',
14+
label: 'Package Manager',
15+
link: '/package-manager',
16+
},
17+
{
18+
key: 'prebuilt',
19+
label: 'Prebuilt Installer',
20+
link: '/prebuilt-installer',
21+
},
22+
{
23+
key: 'source',
24+
label: 'Source Code',
25+
link: '/source-code',
26+
},
27+
],
28+
activeTab: 'prebuilt',
29+
children: <p>Tab content goes here</p>,
30+
},
31+
};
32+
33+
export default { component: LinkTabs } as Meta;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import Ellipsis from '@/components/Common/Pagination/Ellipsis';
4+
5+
type Story = StoryObj<typeof Ellipsis>;
6+
type Meta = MetaObj<typeof Ellipsis>;
7+
8+
export const Default: Story = {};
9+
10+
export default { component: Ellipsis } as Meta;

components/Common/Pagination/Ellipsis/index.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import type { FC } from 'react';
2+
13
import styles from './index.module.css';
24

3-
const Ellipsis = () => (
5+
const Ellipsis: FC = () => (
46
<span aria-hidden="true" className={styles.ellipsis}>
57
...
68
</span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import PaginationListItem from '@/components/Common/Pagination/PaginationListItem';
4+
5+
type Story = StoryObj<typeof PaginationListItem>;
6+
type Meta = MetaObj<typeof PaginationListItem>;
7+
8+
export const Default: Story = {
9+
args: {
10+
url: '#',
11+
pageNumber: 1,
12+
currentPage: 2,
13+
totalPages: 2,
14+
},
15+
decorators: [
16+
Story => (
17+
<ul className="list-none">
18+
<Story />
19+
</ul>
20+
),
21+
],
22+
};
23+
24+
export const CurrentPage: Story = {
25+
args: {
26+
url: '#',
27+
pageNumber: 1,
28+
currentPage: 1,
29+
totalPages: 1,
30+
},
31+
decorators: [
32+
Story => (
33+
<ul className="list-none">
34+
<Story />
35+
</ul>
36+
),
37+
],
38+
};
39+
40+
export default { component: PaginationListItem } as Meta;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import SidebarGroup from '@/components/Containers/Sidebar/SidebarGroup';
4+
5+
type Story = StoryObj<typeof SidebarGroup>;
6+
type Meta = MetaObj<typeof SidebarGroup>;
7+
8+
export const Default: Story = {
9+
args: {
10+
groupName: 'Example Group',
11+
items: [
12+
{ label: 'Item 1', link: '/item1' },
13+
{ label: 'Item 2', link: '/item2' },
14+
{ label: 'Item 3', link: '/item3' },
15+
],
16+
},
17+
};
18+
19+
export const CustomGroup: Story = {
20+
args: {
21+
groupName: 'Custom Group',
22+
items: [
23+
{ label: 'Custom Item 1', link: '/custom-item1' },
24+
{ label: 'Custom Item 2', link: '/custom-item2' },
25+
],
26+
},
27+
};
28+
29+
export const EmptyGroup: Story = {
30+
args: {
31+
groupName: 'Empty Group',
32+
items: [],
33+
},
34+
};
35+
36+
export default { component: SidebarGroup } as Meta;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import SidebarItem from '@/components/Containers/Sidebar/SidebarItem';
4+
5+
type Story = StoryObj<typeof SidebarItem>;
6+
type Meta = MetaObj<typeof SidebarItem>;
7+
8+
export const Default: Story = {
9+
args: {
10+
label: 'Example Item',
11+
link: '/example',
12+
},
13+
};
14+
15+
export default { component: SidebarItem } as Meta;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import DownloadButton from '@/components/Downloads/DownloadButton';
4+
5+
type Story = StoryObj<typeof DownloadButton>;
6+
type Meta = MetaObj<typeof DownloadButton>;
7+
8+
export const Default: Story = {
9+
args: {
10+
release: {
11+
currentStart: '2023-04-18',
12+
ltsStart: '2023-10-24',
13+
maintenanceStart: '2024-10-22',
14+
endOfLife: '2026-04-30',
15+
status: 'Active LTS',
16+
major: 20,
17+
version: '20.11.0',
18+
versionWithPrefix: 'v20.11.0',
19+
codename: 'Iron',
20+
isLts: true,
21+
npm: '10.2.4',
22+
v8: '11.3.244.8',
23+
releaseDate: '2024-01-09',
24+
modules: '115',
25+
},
26+
children: 'Download Node.js',
27+
},
28+
};
29+
30+
export default { component: DownloadButton } as Meta;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import Event from '@/components/MDX/Calendar/Event';
4+
5+
type Story = StoryObj<typeof Event>;
6+
type Meta = MetaObj<typeof Event>;
7+
8+
export const Default: Story = {
9+
args: {
10+
start: { date: '2024-02-19T12:30:00.000Z' },
11+
end: { date: '2024-02-19T16:00:00.000Z' },
12+
summary: 'Example Event',
13+
location: 'Event Location',
14+
description: 'This is an example event description.',
15+
},
16+
};
17+
18+
export default { component: Event } as Meta;
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
export const GlowingBackdrop: StoryObj = {
4+
render: () => <div className="glowingBackdrop" />,
5+
};
6+
7+
export const H1Special: StoryObj = {
8+
render: () => <h1 className="special">Special H1</h1>,
9+
};
10+
11+
export default { title: 'Design System' } as MetaObj;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { fireEvent, renderHook } from '@testing-library/react';
2+
import { act } from 'react-dom/test-utils';
3+
4+
import useBottomScrollListener from '@/hooks/react-client/useBottomScrollListener';
5+
6+
describe('useBottomScrollListener', () => {
7+
it('should call the callback when the scroll reaches the bottom', () => {
8+
const callback = jest.fn();
9+
renderHook(() => useBottomScrollListener(callback));
10+
11+
act(() => {
12+
fireEvent.scroll(window, {
13+
target: { scrollY: 100, innerHeight: 200, scrollHeight: 200 },
14+
});
15+
});
16+
17+
// timout is needed because the callback is called in the next tick
18+
setTimeout(() => {
19+
expect(callback).toHaveBeenCalled();
20+
}, 1);
21+
});
22+
23+
it('should not call the callback when the scroll does not reach the bottom', () => {
24+
const callback = jest.fn();
25+
renderHook(() => useBottomScrollListener(callback));
26+
27+
act(() => {
28+
fireEvent.scroll(window, {
29+
target: { scrollY: 100, innerHeight: 200, scrollHeight: 300 },
30+
});
31+
});
32+
33+
// timout is needed because the callback is called in the next tick
34+
setTimeout(() => {
35+
expect(callback).not.toHaveBeenCalled();
36+
}, 1);
37+
});
38+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { renderHook, act } from '@testing-library/react';
2+
3+
import useClickOutside from '@/hooks/react-client/useClickOutside';
4+
5+
describe('useClickOutside', () => {
6+
it('should call the callback function when clicked outside the element', () => {
7+
const fn = jest.fn();
8+
const { rerender } = renderHook(() =>
9+
useClickOutside({ current: null }, fn)
10+
);
11+
12+
const mockEvent = new MouseEvent('click', { bubbles: true });
13+
const mockElement = document.createElement('div');
14+
15+
rerender({ current: mockElement }, fn);
16+
17+
act(() => {
18+
document.dispatchEvent(mockEvent);
19+
});
20+
21+
setTimeout(() => {
22+
expect(fn).toHaveBeenCalledTimes(1);
23+
}, 1);
24+
});
25+
26+
it('should not call the callback function when clicked inside the element', () => {
27+
const fn = jest.fn();
28+
const { rerender } = renderHook(() =>
29+
useClickOutside({ current: null }, fn)
30+
);
31+
32+
const mockEvent = new MouseEvent('click', { bubbles: true });
33+
const mockElement = document.createElement('div');
34+
const mockChildElement = document.createElement('button');
35+
mockElement.appendChild(mockChildElement);
36+
37+
rerender({ current: mockElement }, fn);
38+
39+
act(() => {
40+
mockChildElement.dispatchEvent(mockEvent);
41+
});
42+
43+
expect(fn).not.toHaveBeenCalled();
44+
});
45+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { renderHook } from '@testing-library/react';
2+
3+
import useClientContext from '@/hooks/react-client/useClientContext';
4+
import { MatterContext } from '@/providers/matterProvider';
5+
6+
describe('useClientContext', () => {
7+
it('should return client context values', () => {
8+
const mockContextValue = {
9+
pathname: '/example-path',
10+
frontmatter: { title: 'Example Title', date: '2024-02-17' },
11+
headings: ['Heading 1', 'Heading 2'],
12+
readingTime: 5,
13+
filename: 'example.md',
14+
};
15+
16+
const wrapper = ({ children }) => (
17+
<MatterContext.Provider value={mockContextValue}>
18+
{children}
19+
</MatterContext.Provider>
20+
);
21+
22+
const { result } = renderHook(() => useClientContext(), { wrapper });
23+
24+
expect(result.current).toEqual(mockContextValue);
25+
});
26+
});

hooks/react-client/__tests__/useCopyToClipboard.test.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { render, fireEvent, screen, act } from '@testing-library/react';
22

3-
import { useCopyToClipboard } from '..';
3+
import useCopyToClipboard from '@/hooks/react-client/useCopyToClipboard';
44

55
const mockWriteText = jest.fn();
66
const originalNavigator = { ...window.navigator };

hooks/react-client/__tests__/useDetectOS.test.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { renderHook, waitFor } from '@testing-library/react';
22

3-
import { useDetectOS } from '..';
3+
import useDetectOS from '@/hooks/react-client/useDetectOS';
44

55
const windowsUserAgent =
66
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36';

0 commit comments

Comments
 (0)