Skip to content

Commit

Permalink
test(headless): fix unit tests by changing to vitest (#4449)
Browse files Browse the repository at this point in the history
To be merged into #4442


Unit tests were failing because Jest has a hard time with ESM modules. I
tried to make it work but it ended up being easier to switch to vitest
altogether. The api are really really similar, it should not change how
we write tests.

## Headless-react unit tests are still failing it is addressed here - >
#4452

---------

Co-authored-by: Louis Bompart <[email protected]>
  • Loading branch information
alexprudhomme and louis-bompart authored Sep 24, 2024
1 parent 025a8f1 commit 7cacd90
Show file tree
Hide file tree
Showing 243 changed files with 2,191 additions and 1,636 deletions.
747 changes: 644 additions & 103 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 0 additions & 22 deletions packages/headless-react/jest.config.mjs

This file was deleted.

9 changes: 4 additions & 5 deletions packages/headless-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"scripts": {
"build": "nx build",
"clean": "rimraf dist",
"test": "jest",
"test:watch": "jest --watch --colors --no-cache",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint .; publint",
"publish:npm": "npm run-script -w=@coveo/release npm-publish",
"publish:bump": "npm run-script -w=@coveo/release bump",
Expand All @@ -46,12 +46,11 @@
"eslint-plugin-react": "7.35.0",
"eslint-plugin-testing-library": "6.2.2",
"gts": "5.3.1",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"publint": "0.2.9",
"rimraf": "5.0.9",
"ts-jest": "29.2.3",
"typescript": "5.4.5"
"typescript": "5.4.5",
"vitest": "2.1.1"
},
"peerDependencies": {
"react": "^18",
Expand Down
45 changes: 23 additions & 22 deletions packages/headless-react/src/ssr/client-utils.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {renderHook} from '@testing-library/react';
import {vi, expect, describe, test, it} from 'vitest';
import {useSyncMemoizedStore} from './client-utils';

describe('useSyncMemoizedStore', () => {
test('should return the initial snapshot', () => {
const snapshot = {count: 0};
const unsubscribe = jest.fn();
const subscribe = jest.fn(() => unsubscribe);
const getSnapshot = jest.fn(() => snapshot);
const unsubscribe = vi.fn();
const subscribe = vi.fn(() => unsubscribe);
const getSnapshot = vi.fn(() => snapshot);

const {result} = renderHook(() =>
useSyncMemoizedStore(subscribe, getSnapshot)
Expand All @@ -17,9 +18,9 @@ describe('useSyncMemoizedStore', () => {

test('should not call getSnapshot when there is a re-render with the same getSnapshot', () => {
const snapshot = {count: 0};
const unsubscribe = jest.fn();
const subscribe = jest.fn(() => unsubscribe);
const getSnapshot = jest.fn(() => snapshot);
const unsubscribe = vi.fn();
const subscribe = vi.fn(() => unsubscribe);
const getSnapshot = vi.fn(() => snapshot);

const {rerender} = renderHook(() =>
useSyncMemoizedStore(subscribe, getSnapshot)
Expand All @@ -33,26 +34,26 @@ describe('useSyncMemoizedStore', () => {
test('should update the state when getSnapshot changes', () => {
const snapshot1 = {count: 0};
const snapshot2 = {count: 1};
const subscribe = jest.fn(() => jest.fn());
let getSnapshot = jest.fn(() => snapshot1);
const subscribe = vi.fn(() => vi.fn());
let getSnapshot = vi.fn(() => snapshot1);

const {result, rerender} = renderHook(() =>
useSyncMemoizedStore(subscribe, getSnapshot)
);

expect(result.current).toEqual(snapshot1);
getSnapshot = jest.fn(() => snapshot2);
getSnapshot = vi.fn(() => snapshot2);
rerender();
expect(result.current).toEqual(snapshot2);
});

test('should unsubscribe and re-subscribe to new function when subscribe function is changed', () => {
const snapshot = {count: 0};
const unsubscribe1 = jest.fn();
const unsubscribe2 = jest.fn();
const subscribe1 = jest.fn(() => unsubscribe1);
const subscribe2 = jest.fn(() => unsubscribe2);
const getSnapshot = jest.fn(() => snapshot);
const unsubscribe1 = vi.fn();
const unsubscribe2 = vi.fn();
const subscribe1 = vi.fn(() => unsubscribe1);
const subscribe2 = vi.fn(() => unsubscribe2);
const getSnapshot = vi.fn(() => snapshot);

const {rerender} = renderHook(
({subscribe}) => useSyncMemoizedStore(subscribe, getSnapshot),
Expand All @@ -71,10 +72,10 @@ describe('useSyncMemoizedStore', () => {
test('should replace current snapshot when getSnapshot function is changed', () => {
const snapshot1 = {count: 0};
const snapshot2 = {count: 1};
const unsubscribe = jest.fn();
const subscribe = jest.fn(() => unsubscribe);
const getSnapshot1 = jest.fn(() => snapshot1);
const getSnapshot2 = jest.fn(() => snapshot2);
const unsubscribe = vi.fn();
const subscribe = vi.fn(() => unsubscribe);
const getSnapshot1 = vi.fn(() => snapshot1);
const getSnapshot2 = vi.fn(() => snapshot2);

const {result, rerender} = renderHook(
({getSnapshot}) => useSyncMemoizedStore(subscribe, getSnapshot),
Expand All @@ -88,13 +89,13 @@ describe('useSyncMemoizedStore', () => {

it('should call the subscribe listener on mount and unsubscribe on unmount', () => {
const snapshot = {count: 0};
const unsubscribe = jest.fn();
const listener = jest.fn();
const subscribe = jest.fn(() => {
const unsubscribe = vi.fn();
const listener = vi.fn();
const subscribe = vi.fn(() => {
listener();
return unsubscribe;
});
const getSnapshot = jest.fn(() => snapshot);
const getSnapshot = vi.fn(() => snapshot);

const {result, unmount} = renderHook(() => {
return useSyncMemoizedStore(subscribe, getSnapshot);
Expand Down
19 changes: 15 additions & 4 deletions packages/headless-react/src/ssr/search-engine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ import {
} from '@coveo/headless/ssr';
import {act, render, renderHook, screen} from '@testing-library/react';
import {PropsWithChildren} from 'react';
import {
vi,
expect,
describe,
test,
beforeEach,
MockInstance,
afterEach,
} from 'vitest';
import {MissingEngineProviderError} from './common.js';
import {defineSearchEngine} from './search-engine.js';

describe('Headless react SSR utils', () => {
let errorSpy: jest.SpyInstance;
let errorSpy: MockInstance;
const mockedNavigatorContextProvider: NavigatorContextProvider = () => {
return {
clientId: '123',
Expand All @@ -27,7 +36,7 @@ describe('Headless react SSR utils', () => {
};

beforeEach(() => {
errorSpy = jest.spyOn(console, 'error');
errorSpy = vi.spyOn(console, 'error');
});

afterEach(() => {
Expand Down Expand Up @@ -113,6 +122,8 @@ describe('Headless react SSR utils', () => {
const results = await screen.findAllByTestId(resultItemTestId);
expect(errorSpy).not.toHaveBeenCalled();
expect(results).toHaveLength(numResults);

results.forEach((result) => result.remove());
}

function checkRenderError(
Expand All @@ -121,7 +132,7 @@ describe('Headless react SSR utils', () => {
) {
let err: Error | undefined = undefined;
// Prevent expected error from being thrown in console when running tests
const consoleErrorStub = jest
const consoleErrorStub = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
try {
Expand Down Expand Up @@ -248,7 +259,7 @@ describe('Headless react SSR utils', () => {
wrapper: hydratedStateProviderWrapper,
});
const initialState = result.current.state;
const controllerSpy = jest.spyOn(
const controllerSpy = vi.spyOn(
hydratedState.controllers.searchBox,
'updateText'
);
Expand Down
3 changes: 2 additions & 1 deletion packages/headless-react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"moduleResolution": "NodeNext",
"module": "NodeNext",
"target": "ES2022",
"lib": ["dom", "ES2023"]
"lib": ["dom", "ES2023"],
"types": ["vitest/globals"]
},
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"],
"include": ["src/**/*.ts", "src/**/*.tsx"]
Expand Down
9 changes: 0 additions & 9 deletions packages/headless-react/tsconfig.test.json

This file was deleted.

8 changes: 8 additions & 0 deletions packages/headless-react/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {defineConfig} from 'vitest/config';

/// <reference types="vitest/config" />
export default defineConfig({
test: {
environment: 'jsdom',
},
});
32 changes: 16 additions & 16 deletions packages/headless/doc-parser/src/interface-resolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// eslint-disable-next-line n/no-unpublished-import
import {facetValueStates} from '../../src/features/facets/facet-api/value';
import {buildMockApiCallSignature} from '../mocks/mock-api-call-signature';
import {buildMockApiDocComment} from '../mocks/mock-api-doc-comment';
import {buildMockApiIndexSignature} from '../mocks/mock-api-index-signature';
import {buildMockApiInterface} from '../mocks/mock-api-interface';
import {buildMockApiMethodSignature} from '../mocks/mock-api-method-signature';
import {buildMockApiPropertySignature} from '../mocks/mock-api-property-signature';
import {buildMockApiTypeAlias} from '../mocks/mock-api-type-alias';
import {buildMockEntity} from '../mocks/mock-entity';
import {buildMockEntityWithTypeAlias} from '../mocks/mock-entity-with-type-alias';
import {buildMockEntryPoint} from '../mocks/mock-entry-point';
import {facetValueStates} from '../../src/features/facets/facet-api/value.js';
import {buildMockApiCallSignature} from '../mocks/mock-api-call-signature.js';
import {buildMockApiDocComment} from '../mocks/mock-api-doc-comment.js';
import {buildMockApiIndexSignature} from '../mocks/mock-api-index-signature.js';
import {buildMockApiInterface} from '../mocks/mock-api-interface.js';
import {buildMockApiMethodSignature} from '../mocks/mock-api-method-signature.js';
import {buildMockApiPropertySignature} from '../mocks/mock-api-property-signature.js';
import {buildMockApiTypeAlias} from '../mocks/mock-api-type-alias.js';
import {buildMockEntityWithTypeAlias} from '../mocks/mock-entity-with-type-alias.js';
import {buildMockEntity} from '../mocks/mock-entity.js';
import {buildMockEntryPoint} from '../mocks/mock-entry-point.js';
import {
buildContentExcerptToken,
buildReferenceExcerptToken,
} from '../mocks/mock-excerpt-token';
import {buildMockFuncEntity} from '../mocks/mock-func-entity';
import {buildMockObjEntity} from '../mocks/mock-obj-entity';
import {AnyEntity} from './entity';
import {resolveInterfaceMembers} from './interface-resolver';
} from '../mocks/mock-excerpt-token.js';
import {buildMockFuncEntity} from '../mocks/mock-func-entity.js';
import {buildMockObjEntity} from '../mocks/mock-obj-entity.js';
import {AnyEntity} from './entity.js';
import {resolveInterfaceMembers} from './interface-resolver.js';

describe('#resolveInterfaceMembers', () => {
it('resolves a property with a primitive type', () => {
Expand Down
9 changes: 0 additions & 9 deletions packages/headless/jest.config.cjs

This file was deleted.

11 changes: 4 additions & 7 deletions packages/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,8 @@
"build:bundles": "node esbuild.mjs",
"build:definitions": "tsc -p src/tsconfig.build.json -d --emitDeclarationOnly --declarationDir dist/definitions",
"clean": "rimraf -rf dist/*",
"test": "jest",
"test:watch": "jest --watch --colors --no-cache --silent=false",
"test:unit": "jest --testPathIgnorePatterns=src/integration-tests",
"test:integration": "jest --testPathPattern=src/integration-tests",
"test": "vitest run",
"test:watch": "vitest",
"publish:npm": "npm run-script -w=@coveo/release npm-publish",
"publish:bump": "npm run-script -w=@coveo/release bump",
"promote:npm:latest": "node ../../scripts/deploy/update-npm-tag.mjs latest",
Expand Down Expand Up @@ -169,9 +167,8 @@
"eslint-plugin-canonical": "4.18.0",
"execa": "8.0.1",
"install": "0.13.0",
"jest": "29.7.0",
"ts-jest": "29.2.3",
"ts-node": "10.9.2"
"ts-node": "10.9.2",
"vitest": "2.1.1"
},
"engines": {
"node": "^20.9.0"
Expand Down
20 changes: 10 additions & 10 deletions packages/headless/src/api/analytics/analytics-relay-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ import {getAnalyticsNextApiBaseUrl} from '../platform-client.js';
import {getRelayInstanceFromState} from './analytics-relay-client.js';
import {getAnalyticsSource} from './analytics-selectors.js';

jest.mock('@coveo/relay');
jest.mock('./analytics-selectors');
vi.mock('@coveo/relay');
vi.mock('./analytics-selectors');

describe('#getRelayInstanceFromState', () => {
const mockedCreateRelay = jest.mocked(createRelay).mockImplementation(() => ({
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
getMeta: jest.fn(),
updateConfig: jest.fn(),
const mockedCreateRelay = vi.mocked(createRelay).mockImplementation(() => ({
emit: vi.fn(),
on: vi.fn(),
off: vi.fn(),
getMeta: vi.fn(),
updateConfig: vi.fn(),
version: 'test',
}));

beforeEach(() => {
jest.mocked(getAnalyticsSource).mockReturnValue(['baguette']);
vi.mocked(getAnalyticsSource).mockReturnValue(['baguette']);
});

afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});

it('creates a Relay client properly and returns it', () => {
Expand Down
10 changes: 5 additions & 5 deletions packages/headless/src/api/analytics/search-analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ import {
StateNeededBySearchAnalyticsProvider,
} from './search-analytics.js';

jest.mock('@coveo/relay');
vi.mock('@coveo/relay');

const mockGetHistory = jest.fn();
const mockGetHistory = vi.fn();

jest.mock('coveo.analytics', () => {
const originalModule = jest.requireActual('coveo.analytics');
vi.mock('coveo.analytics', async () => {
const originalModule = await vi.importActual('coveo.analytics');
return {
...originalModule,
history: {
HistoryStore: jest.fn().mockImplementation(() => {
HistoryStore: vi.fn().mockImplementation(() => {
return {
getHistory: () => mockGetHistory(),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {Mock} from 'vitest';
import {SortBy} from '../../features/sort/sort.js';
import {buildMockCommerceAPIClient} from '../../test/mock-commerce-api-client.js';
import {VERSION} from '../../utils/version.js';
Expand All @@ -17,18 +18,18 @@ describe('commerce api client', () => {
const trackingId = 'some-tracking-id';

let client: CommerceAPIClient;
let platformCallMock: jest.Mock;
let platformCallMock: Mock;

beforeEach(() => {
client = buildMockCommerceAPIClient();
});

afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});

const mockPlatformCall = (fakeResponse: unknown) => {
platformCallMock = jest.fn();
platformCallMock = vi.fn();

platformCallMock.mockReturnValue(fakeResponse);
PlatformClient.call = platformCallMock;
Expand Down
Loading

0 comments on commit 7cacd90

Please sign in to comment.