Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions jest.setup.js

This file was deleted.

41 changes: 10 additions & 31 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
"jsnext:main": "lib-esm/index.js",
"module": "lib-esm/index.js",
"scripts": {
"test": "jest",
"test:debug": "node --inspect ./node_modules/.bin/jest --runInBand --watch",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:downstream": "npm run build && test_downstream_projects",
"start": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8000 --config ./examples/typescript/webpack.config.js --history-api-fallback",
"clean": "shx rm -rf _bundles lib lib-esm build _doc",
Expand Down Expand Up @@ -50,64 +51,42 @@
"react": ">=16.8.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^12.0.0",
"@types/classnames": "^2.3.1",
"@types/jest": "^26.0.24",
"@types/lodash": "^4.14.171",
"@types/prop-types": "15.7.4",
"@types/react": "17.0.14",
"@types/react-dom": "17.0.9",
"@uirouter/publish-scripts": "^2.6.4",
"babel-jest": "^27.0.6",
"@vitest/ui": "^4.0.16",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"cross-env": "^7.0.3",
"husky": "^4.3.6",
"jest": "27.0.6",
"jsdom": "^27.4.0",
"prettier": "^2.3.2",
"pretty-quick": "^3.1.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-test-renderer": "^17.0.2",
"ts-jest": "^27.0.4",
"ts-loader": "^9.2.3",
"ts-node": "^10.1.0",
"tsconfig-paths-webpack-plugin": "^4.2.0",
"typescript": "^4.3.5",
"vitest": "^4.0.16",
"webpack": "^5.101.3",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2"
},
"jest": {
"setupFiles": [
"../jest.setup.js"
],
"rootDir": "src",
"transform": {
".(ts|tsx)": "ts-jest"
},
"testEnvironment": "jsdom",
"moduleFileExtensions": [
"js",
"json",
"ts",
"tsx"
],
"globals": {
"ts-jest": {
"tsConfig": "./tsconfig.jest.json"
}
},
"preset": "ts-jest",
"restoreMocks": true
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"checkPeerDependencies": {
"ignore": ["@types/node", "terser", "yaml"]
},
"docgen": {
"publishDir": "_react_docs",
"navigation": {
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/core.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { servicesPlugin, memoryLocationPlugin } from '@uirouter/core';

import { UIRouterReact, StartMethodCalledMoreThanOnceError } from '../index';
Expand All @@ -12,8 +13,8 @@ describe('UIRouterReact class', () => {
});

it('starts the router with start() method', () => {
let urlRouterListenSpy = jest.spyOn(router.urlService, 'listen');
let urlRouterSyncSpy = jest.spyOn(router.urlService, 'sync');
let urlRouterListenSpy = vi.spyOn(router.urlService, 'listen');
let urlRouterSyncSpy = vi.spyOn(router.urlService, 'sync');
router.start();
expect(urlRouterListenSpy).toHaveBeenCalled();
expect(urlRouterSyncSpy).toHaveBeenCalled();
Expand Down
20 changes: 17 additions & 3 deletions src/__tests__/util.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { TransitionOptions, RawParams, StateOrName, TransitionPromise, memoryLocationPlugin } from '@uirouter/core';
import { render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
Expand Down Expand Up @@ -29,9 +30,22 @@ export const makeTestRouter = (states: ReactStateDeclaration[]) => {
return { router, routerGo, mountInRouter };
};

// silence console errors that are logged by react-dom or other actors
export function muteConsoleErrors() {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
// Silence errors from React error boundaries during tests that expect errors.
// React logs to console.error AND writes stack traces directly to stderr.
const originalStderrWrite = process.stderr.write.bind(process.stderr);
const originalConsoleError = console.error.bind(console);
export function muteConsoleErrors(messages: RegExp[] = []) {
const maybeMute =
(originalWriteFn: (...args: any[]) => boolean) =>
(...args: any[]) => {
if (messages.some((regex) => regex.test(args[0]?.toString?.() ?? ''))) {
return true;
}
return originalWriteFn(...args);
};

vi.spyOn(console, 'error').mockImplementation(maybeMute(originalConsoleError));
vi.spyOn(process.stderr, 'write').mockImplementation(maybeMute(originalStderrWrite));
}

export function defer<T = any>() {
Expand Down
5 changes: 3 additions & 2 deletions src/components/__tests__/UIRouter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render } from '@testing-library/react';
import * as React from 'react';
import * as PropTypes from 'prop-types';
Expand All @@ -17,7 +18,7 @@ class Child extends React.Component<any, any> {

describe('<UIRouter>', () => {
it('throws an error if no plugin or router instance is passed via prop', () => {
muteConsoleErrors();
muteConsoleErrors([/Router instance or plugins missing/, /The above error occurred in the <UIRouter> component:/]);
expect(() =>
render(
<UIRouter>
Expand Down Expand Up @@ -54,7 +55,7 @@ describe('<UIRouter>', () => {
it('starts the router', () => {
const router = new UIRouterReact();
router.plugin(memoryLocationPlugin);
const spy = jest.spyOn(router, 'start');
const spy = vi.spyOn(router, 'start');
render(
<UIRouter router={router}>
<UIRouterContext.Consumer>{(router) => <Child router={router} />}</UIRouterContext.Consumer>
Expand Down
49 changes: 30 additions & 19 deletions src/components/__tests__/UISref.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent } from '@testing-library/react';
import * as React from 'react';
import { UISref, UIView } from '../../components';
Expand All @@ -22,12 +23,11 @@ const states = [
];

describe('<UISref>', () => {
let mockUseEffect: any;
beforeEach(() => (mockUseEffect = jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect)));
afterEach(() => mockUseEffect.mockRestore());

let { router, routerGo, mountInRouter } = makeTestRouter([]);
beforeEach(() => ({ router, routerGo, mountInRouter } = makeTestRouter(states)));
beforeEach(() => {
muteConsoleErrors([/Not implemented: navigation to another Document/]);
return ({ router, routerGo, mountInRouter } = makeTestRouter(states));
});

it('renders its child with injected props', async () => {
const wrapper = mountInRouter(
Expand All @@ -36,23 +36,34 @@ describe('<UISref>', () => {
</UISref>
);
await routerGo('state');
const goSpy = jest.spyOn(router.stateService, 'go');
const goSpy = vi.spyOn(router.stateService, 'go');
const anchor = wrapper.getByTestId('anchor');
expect(anchor.getAttribute('href').includes('/state2')).toBe(true);
expect(anchor?.getAttribute('href')).toContain('/state2');
anchor.click();
expect(goSpy).toHaveBeenCalledTimes(1);
});

it('throws if state name is not a string', () => {
muteConsoleErrors();
expect(() => mountInRouter(<UISref to={5 as any} />)).toThrow(/must be a string/);
muteConsoleErrors([
/The state name passed to useSref must be a string./,
/Warning: Failed %s type: %s%s/,
/The above error occurred in the <UISref> component:/,
]);

expect(() =>
mountInRouter(
<UISref to={5 as any}>
<div>test</div>
</UISref>
)
).toThrow(/must be a string/);
});

it('registers and deregisters active state from parent UISrefActive when mounting/unmounting', async () => {
await routerGo('state');

const deregisterFn = jest.fn();
const parentUISrefActiveAddStateFn = jest.fn(() => deregisterFn);
const deregisterFn = vi.fn();
const parentUISrefActiveAddStateFn = vi.fn(() => deregisterFn);

mountInRouter(
<UISrefActiveContext.Provider value={parentUISrefActiveAddStateFn}>
Expand All @@ -66,7 +77,7 @@ describe('<UISref>', () => {
});

it('triggers a transition to target state', async () => {
const goSpy = jest.spyOn(router.stateService, 'go');
const goSpy = vi.spyOn(router.stateService, 'go');
const rendered = mountInRouter(
<UISref to="state2">
<a data-testid="anchor" />
Expand All @@ -81,8 +92,8 @@ describe('<UISref>', () => {

it('calls the child elements onClick function first', async () => {
const log = [];
const goSpy = jest.spyOn(router.stateService, 'go').mockImplementation(() => log.push('go') as any);
const onClickSpy = jest.fn(() => log.push('onClick'));
const goSpy = vi.spyOn(router.stateService, 'go').mockImplementation(() => log.push('go') as any);
const onClickSpy = vi.fn(() => log.push('onClick'));
const rendered = mountInRouter(
<UISref to="state2">
<a data-testid="anchor" onClick={onClickSpy}>
Expand All @@ -98,8 +109,8 @@ describe('<UISref>', () => {
});

it('calls the child elements onClick function and honors e.preventDefault()', async () => {
const goSpy = jest.spyOn(router.stateService, 'go');
const onClickSpy = jest.fn((e) => e.preventDefault());
const goSpy = vi.spyOn(router.stateService, 'go');
const onClickSpy = vi.fn((e) => e.preventDefault());
const rendered = mountInRouter(
<UISref to="state2">
<a data-testid="anchor" onClick={onClickSpy}>
Expand All @@ -114,7 +125,7 @@ describe('<UISref>', () => {
});

it("doesn't trigger a transition when middle-clicked", async () => {
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
const stateServiceGoSpy = vi.spyOn(router.stateService, 'go');
const rendered = mountInRouter(
<UISref to="state2">
<a data-testid="anchor">state2</a>
Expand All @@ -130,7 +141,7 @@ describe('<UISref>', () => {
});

it("doesn't trigger a transition when ctrl/meta/shift/alt+clicked", async () => {
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
const stateServiceGoSpy = vi.spyOn(router.stateService, 'go');
const rendered = mountInRouter(
<UISref to="state2">
<a data-testid="anchor">state2</a>
Expand All @@ -155,7 +166,7 @@ describe('<UISref>', () => {
});

it("doesn't trigger a transition when the anchor has a 'target' attribute", async () => {
const stateServiceGoSpy = jest.spyOn(router.stateService, 'go');
const stateServiceGoSpy = vi.spyOn(router.stateService, 'go');
const rendered = mountInRouter(
<UISref to="state2">
<a data-testid="anchor" target="_blank">
Expand Down
20 changes: 10 additions & 10 deletions src/components/__tests__/UISrefActive.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { render } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import * as React from 'react';
import { makeTestRouter } from '../../__tests__/util';
import { ReactStateDeclaration } from '../../index';
Expand All @@ -14,8 +15,6 @@ const states: ReactStateDeclaration[] = [
describe('<UISrefActive>', () => {
let { router, routerGo, mountInRouter } = makeTestRouter([]);
beforeEach(() => ({ router, routerGo, mountInRouter } = makeTestRouter(states)));
beforeEach(() => jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect));
afterEach(() => (React.useEffect as any).mockRestore());

function UISrefActiveTestComponent(props: {
to?: string;
Expand Down Expand Up @@ -55,14 +54,14 @@ describe('<UISrefActive>', () => {
});

it('registers onSuccess transition hook to listen for state changes', async () => {
const onSuccessSpy = jest.spyOn(router.transitionService, 'onSuccess');
const onSuccessSpy = vi.spyOn(router.transitionService, 'onSuccess');
mountInRouter(<UISrefActiveTestComponent />);
expect(onSuccessSpy).toHaveBeenCalled();
});

it('deregisters the transition hook when unmounted', async () => {
const deregisterSpy = jest.fn();
jest.spyOn(router.transitionService, 'onSuccess').mockImplementation(() => deregisterSpy);
const deregisterSpy = vi.fn();
vi.spyOn(router.transitionService, 'onSuccess').mockImplementation(() => deregisterSpy);
const Component = () => {
const [show, setShow] = React.useState(true);
return (
Expand All @@ -78,7 +77,7 @@ describe('<UISrefActive>', () => {
const rendered = mountInRouter(<Component />);
expect(deregisterSpy).not.toHaveBeenCalled();
rendered.getByTestId('btn').click();
expect(deregisterSpy).toHaveBeenCalled();
await waitFor(() => expect(deregisterSpy).toHaveBeenCalled());
});

it('works with state parameters', async () => {
Expand Down Expand Up @@ -200,15 +199,16 @@ describe('<UISrefActive>', () => {
);
};

await routerGo('parent.child1');
const rendered = render(<Comp />);

const classFor = (testid: string) =>
rendered.getByTestId(testid).getAttribute('class').split(/ +/).sort().join(' ');

await routerGo('parent.child1');
const rendered = render(<Comp />);
expect(classFor('div')).toBe('active baseclass');

rendered.getByTestId('hidebtn').click();
expect(classFor('div')).toBe('baseclass');
await waitFor(() => expect(classFor('div')).toBe('baseclass'));
});

it('checks for exact state match when exact prop is provided', async () => {
Expand Down
Loading