Skip to content

Commit

Permalink
test: Increase coverage (#6349)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
AugustinMauroy and ovflowd authored Apr 1, 2024
1 parent cf31daf commit c3f9c12
Show file tree
Hide file tree
Showing 36 changed files with 821 additions and 49 deletions.
7 changes: 3 additions & 4 deletions COLLABORATOR_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ Unit Tests are fundamental to ensure that code changes do not disrupt the functi
- We also recommend mocking external dependencies, if unsure about how to mock a particular dependency, raise the question on your Pull Request.
- We recommend using [Jest's Mock Functions](https://jestjs.io/docs/en/mock-functions) for mocking dependencies.
- We recommend using [Jest's Mock Modules](https://jestjs.io/docs/en/manual-mocks) for mocking dependencies unavailable on the Node.js runtime.
- 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.
- 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.
- We recommend reading previous unit tests from the codebase for inspiration and code guidelines.

### General Guidelines for Storybooks
Expand Down Expand Up @@ -340,7 +340,7 @@ This custom render uses `getStaticPaths` and [Incremental Static Generation](htt
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.
`next.dynamic.mjs` is responsible for getting a full list of the source pages (`pages/en`) and identifying which pages have been translated.

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).
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).
Whereas localized pages will have localized context and content.

This custom solution is also able to decide what paths should be compiled during runtime.
Expand Down Expand Up @@ -491,12 +491,11 @@ If you're unfamiliar or curious about something, we recommend opening a Discussi
[Jest]: https://jestjs.io/
[React Testing Library]: https://testing-library.com/docs/react-testing-library/intro/
[Storybook]: https://storybook.js.org/
[`react-intl`]: https://formatjs.io/docs/react-intl/
[`next-intl`]: https://next-intl-docs.vercel.app
[Next.js]: https://nextjs.org/
[MDX]: https://mdxjs.com/
[PostCSS]: https://postcss.org/
[React]: https://react.dev/
[Shiki]: https://github.com/shikijs/shiki
[Tailwind]: https://tailwindcss.com/
[Radix UI]: https://www.radix-ui.com/
[`next-intl`]: https://www.npmjs.com/package/next-intl
2 changes: 1 addition & 1 deletion DEPENDENCY_PINNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ When adding dependencies, you should consider if that dependency should be saved

- 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.
- 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)
- 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.
- 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.
- If we're not interested in getting the latest features and bug fixes, we should consider using `~` instead.
- 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`
- TypeScript type packages of corresponding packages should follow the same `semver` of their respective packages
Expand Down
33 changes: 33 additions & 0 deletions components/Common/LinkTabs/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import LinkTabs from '@/components/Common/LinkTabs';

type Story = StoryObj<typeof LinkTabs>;
type Meta = MetaObj<typeof LinkTabs>;

export const Default: Story = {
args: {
label: 'Select Tab',
tabs: [
{
key: 'package',
label: 'Package Manager',
link: '/package-manager',
},
{
key: 'prebuilt',
label: 'Prebuilt Installer',
link: '/prebuilt-installer',
},
{
key: 'source',
label: 'Source Code',
link: '/source-code',
},
],
activeTab: 'prebuilt',
children: <p>Tab content goes here</p>,
},
};

export default { component: LinkTabs } as Meta;
10 changes: 10 additions & 0 deletions components/Common/Pagination/Ellipsis/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import Ellipsis from '@/components/Common/Pagination/Ellipsis';

type Story = StoryObj<typeof Ellipsis>;
type Meta = MetaObj<typeof Ellipsis>;

export const Default: Story = {};

export default { component: Ellipsis } as Meta;
4 changes: 3 additions & 1 deletion components/Common/Pagination/Ellipsis/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { FC } from 'react';

import styles from './index.module.css';

const Ellipsis = () => (
const Ellipsis: FC = () => (
<span aria-hidden="true" className={styles.ellipsis}>
...
</span>
Expand Down
40 changes: 40 additions & 0 deletions components/Common/Pagination/PaginationListItem/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import PaginationListItem from '@/components/Common/Pagination/PaginationListItem';

type Story = StoryObj<typeof PaginationListItem>;
type Meta = MetaObj<typeof PaginationListItem>;

export const Default: Story = {
args: {
url: '#',
pageNumber: 1,
currentPage: 2,
totalPages: 2,
},
decorators: [
Story => (
<ul className="list-none">
<Story />
</ul>
),
],
};

export const CurrentPage: Story = {
args: {
url: '#',
pageNumber: 1,
currentPage: 1,
totalPages: 1,
},
decorators: [
Story => (
<ul className="list-none">
<Story />
</ul>
),
],
};

export default { component: PaginationListItem } as Meta;
36 changes: 36 additions & 0 deletions components/Containers/Sidebar/SidebarGroup/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import SidebarGroup from '@/components/Containers/Sidebar/SidebarGroup';

type Story = StoryObj<typeof SidebarGroup>;
type Meta = MetaObj<typeof SidebarGroup>;

export const Default: Story = {
args: {
groupName: 'Example Group',
items: [
{ label: 'Item 1', link: '/item1' },
{ label: 'Item 2', link: '/item2' },
{ label: 'Item 3', link: '/item3' },
],
},
};

export const CustomGroup: Story = {
args: {
groupName: 'Custom Group',
items: [
{ label: 'Custom Item 1', link: '/custom-item1' },
{ label: 'Custom Item 2', link: '/custom-item2' },
],
},
};

export const EmptyGroup: Story = {
args: {
groupName: 'Empty Group',
items: [],
},
};

export default { component: SidebarGroup } as Meta;
15 changes: 15 additions & 0 deletions components/Containers/Sidebar/SidebarItem/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import SidebarItem from '@/components/Containers/Sidebar/SidebarItem';

type Story = StoryObj<typeof SidebarItem>;
type Meta = MetaObj<typeof SidebarItem>;

export const Default: Story = {
args: {
label: 'Example Item',
link: '/example',
},
};

export default { component: SidebarItem } as Meta;
30 changes: 30 additions & 0 deletions components/Downloads/DownloadButton/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import DownloadButton from '@/components/Downloads/DownloadButton';

type Story = StoryObj<typeof DownloadButton>;
type Meta = MetaObj<typeof DownloadButton>;

export const Default: Story = {
args: {
release: {
currentStart: '2023-04-18',
ltsStart: '2023-10-24',
maintenanceStart: '2024-10-22',
endOfLife: '2026-04-30',
status: 'Active LTS',
major: 20,
version: '20.11.0',
versionWithPrefix: 'v20.11.0',
codename: 'Iron',
isLts: true,
npm: '10.2.4',
v8: '11.3.244.8',
releaseDate: '2024-01-09',
modules: '115',
},
children: 'Download Node.js',
},
};

export default { component: DownloadButton } as Meta;
18 changes: 18 additions & 0 deletions components/MDX/Calendar/Event/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import Event from '@/components/MDX/Calendar/Event';

type Story = StoryObj<typeof Event>;
type Meta = MetaObj<typeof Event>;

export const Default: Story = {
args: {
start: { date: '2024-02-19T12:30:00.000Z' },
end: { date: '2024-02-19T16:00:00.000Z' },
summary: 'Example Event',
location: 'Event Location',
description: 'This is an example event description.',
},
};

export default { component: Event } as Meta;
11 changes: 11 additions & 0 deletions components/__design__/effects.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

export const GlowingBackdrop: StoryObj = {
render: () => <div className="glowingBackdrop" />,
};

export const H1Special: StoryObj = {
render: () => <h1 className="special">Special H1</h1>,
};

export default { title: 'Design System' } as MetaObj;
38 changes: 38 additions & 0 deletions hooks/react-client/__tests__/useBottomScrollListener.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fireEvent, renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';

import useBottomScrollListener from '@/hooks/react-client/useBottomScrollListener';

describe('useBottomScrollListener', () => {
it('should call the callback when the scroll reaches the bottom', () => {
const callback = jest.fn();
renderHook(() => useBottomScrollListener(callback));

act(() => {
fireEvent.scroll(window, {
target: { scrollY: 100, innerHeight: 200, scrollHeight: 200 },
});
});

// timout is needed because the callback is called in the next tick
setTimeout(() => {
expect(callback).toHaveBeenCalled();
}, 1);
});

it('should not call the callback when the scroll does not reach the bottom', () => {
const callback = jest.fn();
renderHook(() => useBottomScrollListener(callback));

act(() => {
fireEvent.scroll(window, {
target: { scrollY: 100, innerHeight: 200, scrollHeight: 300 },
});
});

// timout is needed because the callback is called in the next tick
setTimeout(() => {
expect(callback).not.toHaveBeenCalled();
}, 1);
});
});
45 changes: 45 additions & 0 deletions hooks/react-client/__tests__/useClickOutside.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { renderHook, act } from '@testing-library/react';

import useClickOutside from '@/hooks/react-client/useClickOutside';

describe('useClickOutside', () => {
it('should call the callback function when clicked outside the element', () => {
const fn = jest.fn();
const { rerender } = renderHook(() =>
useClickOutside({ current: null }, fn)
);

const mockEvent = new MouseEvent('click', { bubbles: true });
const mockElement = document.createElement('div');

rerender({ current: mockElement }, fn);

act(() => {
document.dispatchEvent(mockEvent);
});

setTimeout(() => {
expect(fn).toHaveBeenCalledTimes(1);
}, 1);
});

it('should not call the callback function when clicked inside the element', () => {
const fn = jest.fn();
const { rerender } = renderHook(() =>
useClickOutside({ current: null }, fn)
);

const mockEvent = new MouseEvent('click', { bubbles: true });
const mockElement = document.createElement('div');
const mockChildElement = document.createElement('button');
mockElement.appendChild(mockChildElement);

rerender({ current: mockElement }, fn);

act(() => {
mockChildElement.dispatchEvent(mockEvent);
});

expect(fn).not.toHaveBeenCalled();
});
});
26 changes: 26 additions & 0 deletions hooks/react-client/__tests__/useClientContext.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { renderHook } from '@testing-library/react';

import useClientContext from '@/hooks/react-client/useClientContext';
import { MatterContext } from '@/providers/matterProvider';

describe('useClientContext', () => {
it('should return client context values', () => {
const mockContextValue = {
pathname: '/example-path',
frontmatter: { title: 'Example Title', date: '2024-02-17' },
headings: ['Heading 1', 'Heading 2'],
readingTime: 5,
filename: 'example.md',
};

const wrapper = ({ children }) => (
<MatterContext.Provider value={mockContextValue}>
{children}
</MatterContext.Provider>
);

const { result } = renderHook(() => useClientContext(), { wrapper });

expect(result.current).toEqual(mockContextValue);
});
});
2 changes: 1 addition & 1 deletion hooks/react-client/__tests__/useCopyToClipboard.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, fireEvent, screen, act } from '@testing-library/react';

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

const mockWriteText = jest.fn();
const originalNavigator = { ...window.navigator };
Expand Down
2 changes: 1 addition & 1 deletion hooks/react-client/__tests__/useDetectOS.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook, waitFor } from '@testing-library/react';

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

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

0 comments on commit c3f9c12

Please sign in to comment.