Skip to content

Commit

Permalink
Merge pull request #163 from InseeFr/feat/add-unit-test-for-service-w…
Browse files Browse the repository at this point in the history
…orkers

Feat/add unit test for service workers
  • Loading branch information
EmmanuelDemey authored Oct 30, 2024
2 parents 6085bb7 + 57a8d0d commit e2b4378
Show file tree
Hide file tree
Showing 12 changed files with 982 additions and 573 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
},
"devDependencies": {
"@originjs/vite-plugin-federation": "^1.2.1",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.1",
"@types/react": "^18.2.46",
"@vitejs/plugin-react": "^4.2.1",
"copy-and-watch": "^0.1.4",
Expand All @@ -99,13 +101,12 @@
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.16.0",
"eslint-plugin-react-hooks": "^4.2.0",
"jest-sonar-reporter": "^2.0.0",
"jsdom": "^23.0.1",
"prettier": "^3.1.1",
"vite": "^4.2.1",
"vite-plugin-pwa": "^0.19.2",
"vite-tsconfig-paths": "^3.6.0",
"vitest": "^0.29.7"
"vitest": "^2.1.4"
},
"volta": {
"node": "20.10.0"
Expand Down
8 changes: 4 additions & 4 deletions src/ui/Header/SynchronizeButton.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Button from '@mui/material/Button';
import SyncIcon from '@mui/icons-material/Sync';
import React, { useContext } from 'react';
import { SyncContext } from '../Sync/SyncContextProvider';
import { useNetworkOnline } from '../../utils/hooks/useOnline';
import Button from '@mui/material/Button';
import { useContext } from 'react';
import D from '../../i18n/build-dictionary';
import { useNetworkOnline } from '../../utils/hooks/useOnline';
import { SyncContext } from '../Sync/SyncContextProvider';

export function SynchronizeButton() {
const isOnline = useNetworkOnline();
Expand Down
61 changes: 61 additions & 0 deletions src/ui/Header/SynchronizeButton.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { SynchronizeButton } from './SynchronizeButton';
import { SyncContext } from '../Sync/SyncContextProvider';
import { useNetworkOnline } from '../../utils/hooks/useOnline';
import D from '../../i18n/build-dictionary';

vi.mock('../../utils/hooks/useOnline', () => ({
useNetworkOnline: vi.fn(),
}));

vi.mock('@mui/material/Button', () => {
return {
default: ({ children, disabled, onClick }) => (
<button disabled={disabled} onClick={onClick}>
{children}
</button>
),
};
});

describe('SynchronizeButton', () => {
const mockSyncFunction = vi.fn();

beforeEach(() => {
mockSyncFunction.mockClear();
});

it('renders the button with correct text', () => {
useNetworkOnline.mockReturnValue(true);
render(
<SyncContext.Provider value={{ syncFunction: mockSyncFunction }}>
<SynchronizeButton />
</SyncContext.Provider>
);

screen.getByRole('button', { name: D.synchronizeButton });
});

it('disables the button when offline', () => {
useNetworkOnline.mockReturnValue(false);
render(
<SyncContext.Provider value={{ syncFunction: mockSyncFunction }}>
<SynchronizeButton />
</SyncContext.Provider>
);

screen.getByRole('button', { name: D.synchronizeButton, disabled: true });
});

it('calls syncFunction when clicked', () => {
useNetworkOnline.mockReturnValue(true);
render(
<SyncContext.Provider value={{ syncFunction: mockSyncFunction }}>
<SynchronizeButton />
</SyncContext.Provider>
);

fireEvent.click(screen.getByRole('button'));
expect(mockSyncFunction).toHaveBeenCalledTimes(1);
});
});
169 changes: 169 additions & 0 deletions src/ui/ServiceWorkerStatus.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, beforeEach, expect, vi } from 'vitest';
import { ServiceWorkerStatus } from './ServiceWorkerStatus';
import { useServiceWorker } from '../utils/hooks/useServiceWorker';
import D from '../i18n/build-dictionary';

vi.mock('../utils/hooks/useServiceWorker');

describe('ServiceWorkerStatus', () => {
beforeEach(() => {
useServiceWorker.mockReturnValue({
isUpdating: false,
isUpdateInstalled: false,
isUpdateAvailable: false,
isServiceWorkerInstalled: false,
isInstallingServiceWorker: false,
isInstallationFailed: false,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});
});

it(`Display ${D.updating}`, () => {
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: false,
isUpdateAvailable: false,
isUpdating: true,
isInstallingServiceWorker: false,
isInstallationFailed: false,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.updating);
expect(screen.queryByText(D.updateNow)).toBeNull();
});

it(`Display ${D.updateInstalled}`, () => {
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: true,
isUpdateAvailable: false,
isUpdating: false,
isInstallingServiceWorker: false,
isInstallationFailed: false,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.updateInstalled);
expect(screen.queryByText(D.updateNow)).toBeNull();
});

it(`Display ${D.updateAvailable}`, () => {
const updateApp = vi.fn();
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: false,
isUpdateAvailable: true,
isUpdating: false,
isInstallingServiceWorker: false,
isInstallationFailed: false,
clearUpdating: vi.fn(),
updateApp,
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.updateAvailable);
const button = screen.getByText(D.updateNow);
fireEvent.click(button);
expect(updateApp).toHaveBeenCalled();
});

it(`Display ${D.appReadyOffline}`, () => {
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: true,
isUpdateInstalled: false,
isUpdateAvailable: false,
isUpdating: false,
isInstallingServiceWorker: false,
isInstallationFailed: false,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.appReadyOffline);
expect(screen.queryByText(D.updateNow)).toBeNull();
});

it(`Display ${D.appInstalling}`, () => {
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: false,
isUpdateAvailable: false,
isUpdating: false,
isInstallingServiceWorker: true,
isInstallationFailed: false,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.appInstalling);
expect(screen.queryByText(D.updateNow)).toBeNull();
});

it(`Display ${D.installError}`, () => {
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: false,
isUpdateAvailable: false,
isUpdating: false,
isInstallingServiceWorker: false,
isInstallationFailed: true,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.installError);
expect(screen.queryByText(D.updateNow)).toBeNull();
});

it('should close the Snackbar', () => {
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: false,
isUpdateAvailable: false,
isUpdating: false,
isInstallingServiceWorker: false,
isInstallationFailed: true,
clearUpdating: vi.fn(),
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.installError);

const closeButton = screen.getByRole('button', { name: /close/i });
fireEvent.click(closeButton);
expect(screen.queryByText(D.installError)).toBeNull();
});

it('should close the Snackbar and call clearUpdating', () => {
const clearUpdating = vi.fn();
useServiceWorker.mockReturnValueOnce({
isServiceWorkerInstalled: false,
isUpdateInstalled: true,
isUpdateAvailable: false,
isUpdating: false,
isInstallingServiceWorker: false,
isInstallationFailed: false,
clearUpdating,
updateApp: vi.fn(),
});

render(<ServiceWorkerStatus authenticated={true} />);
screen.getByText(D.updateInstalled);

const closeButton = screen.getByRole('button', { name: /close/i });
fireEvent.click(closeButton);
expect(screen.queryByText(D.updateInstalled)).toBeNull();
expect(clearUpdating).toHaveBeenCalled();
});
});
13 changes: 0 additions & 13 deletions src/utils/functions/dom.js

This file was deleted.

37 changes: 37 additions & 0 deletions src/utils/functions/dom.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, it, expect, vi } from 'vitest';
import { addListener } from './dom';

describe('addListener', () => {
it('should add an event listener to the target', () => {
const target = document.createElement('div');
const listener = vi.fn();

const cleanup = addListener(target, 'click', listener);

target.dispatchEvent(new Event('click'));

expect(listener).toHaveBeenCalledTimes(1);

cleanup();

target.dispatchEvent(new Event('click'));

expect(listener).toHaveBeenCalledTimes(1);
});

it('should work with window as target', () => {
const listener = vi.fn();

const cleanup = addListener(window, 'resize', listener);

window.dispatchEvent(new Event('resize'));

expect(listener).toHaveBeenCalledTimes(1);

cleanup();

window.dispatchEvent(new Event('resize'));

expect(listener).toHaveBeenCalledTimes(1); // L'écouteur ne doit pas être appelé à nouveau
});
});
9 changes: 9 additions & 0 deletions src/utils/functions/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Add a listener to the target while returning a cleaning function
*/
export function addListener(target: HTMLElement | Window, event: string, listener: () => void) {
target.addEventListener(event, listener);
return () => {
target.removeEventListener(event, listener);
};
}
21 changes: 0 additions & 21 deletions src/utils/hooks/useOnline.js

This file was deleted.

Loading

0 comments on commit e2b4378

Please sign in to comment.