From 0a99d7888e498b4770dd66ef85774bd367405a1d Mon Sep 17 00:00:00 2001 From: Abhishek-17H Date: Mon, 20 Jan 2025 10:32:06 +0530 Subject: [PATCH 1/9] added configuration for vitest in volto --- packages/volto/package.json | 6 +++- packages/volto/test-setup-globals.js | 47 ++++++++++++++++++++++++++-- packages/volto/vitest.config.js | 44 ++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 packages/volto/vitest.config.js diff --git a/packages/volto/package.json b/packages/volto/package.json index 2d90155dfc..e002402afd 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -35,12 +35,14 @@ "main": "src/index.js", "types": "types/index.d.ts", "scripts": { + "test": "vitest --config vitest.config.js", + "test:watch": "vitest --watch", + "coverage": "vitest run --coverage", "analyze": "BUNDLE_ANALYZE=true razzle build", "start": "make build-deps && razzle start", "start:coresandbox": "make build-deps && ADDONS=coresandbox razzle start", "build": "make build-deps && razzle build --noninteractive", "build:types": "tsc --project tsconfig.declarations.json", - "test": "razzle test --maxWorkers=50%", "test:ci": "CI=true NODE_ICU_DATA=node_modules/full-icu razzle test", "test:husky": "CI=true yarn test --bail --findRelatedTests", "test:debug": "node --inspect node_modules/.bin/jest --runInBand", @@ -315,6 +317,7 @@ "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", + "@vitest/ui": "^2.1.8", "autoprefixer": "10.4.8", "axe-core": "4.4.2", "babel-loader": "9.1.0", @@ -384,6 +387,7 @@ "ts-loader": "9.4.4", "typescript": "^5.6.3", "use-trace-update": "1.3.2", + "vitest": "^2.1.3", "wait-on": "6.0.0", "webpack": "5.90.1", "webpack-bundle-analyzer": "4.10.1", diff --git a/packages/volto/test-setup-globals.js b/packages/volto/test-setup-globals.js index ac04d3293f..ae6a69fec1 100644 --- a/packages/volto/test-setup-globals.js +++ b/packages/volto/test-setup-globals.js @@ -1,12 +1,55 @@ +import '@testing-library/jest-dom'; +import { expect, describe, it, vi } from 'vitest'; + +global.describe = describe; +global.it = it; +global.expect = expect; +global.vi = vi; + global.__CLIENT__ = true; global.__DEVELOPMENT__ = false; +global.__SERVER__ = false; +global.__TEST__ = true; window.matchMedia = window.matchMedia || - function () { + function (query) { return { - matches: false, + matches: query === '(min-width: 1024px)', addListener: function () {}, removeListener: function () {}, }; }; + +vi.stubGlobal( + 'fetch', + vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve({}), + text: () => Promise.resolve(''), + }), + ), +); + +vi.stubGlobal('localStorage', { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), +}); + +vi.stubGlobal('sessionStorage', { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), +}); + +vi.stubGlobal( + 'ResizeObserver', + class { + observe() {} + unobserve() {} + disconnect() {} + }, +); diff --git a/packages/volto/vitest.config.js b/packages/volto/vitest.config.js new file mode 100644 index 0000000000..64ea35351a --- /dev/null +++ b/packages/volto/vitest.config.js @@ -0,0 +1,44 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@plone/volto': path.resolve(__dirname, 'src'), + '@plone/volto-slate': path.resolve(__dirname, '../volto-slate/src'), + '@root': path.resolve(__dirname, 'src'), + }, + }, + test: { + snapshotFormat: { printBasicPrototype: false }, + globals: true, + environment: 'jsdom', + setupFiles: [ + './test-setup-globals.js', + './test-setup-config.jsx', + './jest-setup-afterenv.js', + ], + globalSetup: './global-test-setup.js', + include: ['src/**/*.{test,spec}.{js,ts,jsx,tsx}'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: ['src/**/*.{js,ts,jsx,tsx}'], + exclude: [ + 'node_modules/**', + '**/dist/**', + '**/test/**', + '**/*.config.{js,ts}', + '**/jest-*.js', + ], + }, + css: true, + transform: { + '\\.svg$': { + transform: 'vite-plugin-svgr', + }, + }, + }, +}); From 616c0460944bf743b6f388f0ad96d7c5813d7712 Mon Sep 17 00:00:00 2001 From: Abhishek-17H Date: Tue, 21 Jan 2025 11:21:42 +0530 Subject: [PATCH 2/9] Migrated src/actions to vitest --- .../volto/src/actions/actions/actions.test.js | 6 ++--- .../volto/src/actions/addons/addons.test.js | 27 ++++++++++--------- .../volto/src/actions/aliases/aliases.test.js | 2 +- .../volto/src/actions/types/types.test.js | 3 ++- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/volto/src/actions/actions/actions.test.js b/packages/volto/src/actions/actions/actions.test.js index d9b8cc3948..9805c2a368 100644 --- a/packages/volto/src/actions/actions/actions.test.js +++ b/packages/volto/src/actions/actions/actions.test.js @@ -7,9 +7,9 @@ describe('Actions action', () => { const url = 'http://localhost'; const action = listActions(url); - expect(action.type).toEqual(LIST_ACTIONS); - expect(action.request.op).toEqual('get'); - expect(action.request.path).toEqual(`${url}/@actions`); + expect(action.type).toBe(LIST_ACTIONS); + expect(action.request.op).toBe('get'); + expect(action.request.path).toBe(`${url}/@actions`); }); }); }); diff --git a/packages/volto/src/actions/addons/addons.test.js b/packages/volto/src/actions/addons/addons.test.js index 33130df2cc..2cff5dc6ae 100644 --- a/packages/volto/src/actions/addons/addons.test.js +++ b/packages/volto/src/actions/addons/addons.test.js @@ -16,35 +16,38 @@ describe('Addons action', () => { it('should create an action to install an addon', () => { const id = 'plone.app.example'; const action = installAddon(id); - expect(action.type).toEqual(INSTALL_ADDON); - expect(action.request.op).toEqual('post'); - expect(action.request.path).toEqual(`/@addons/${id}/install`); + expect(action.type).toBe(INSTALL_ADDON); + expect(action.request.op).toBe('post'); + expect(action.request.path).toBe(`/@addons/${id}/install`); }); }); + describe('uninstallAddon', () => { it('should create an action to uninstall an addon', () => { const id = 'plone.app.example'; const action = uninstallAddon(id); - expect(action.type).toEqual(UNINSTALL_ADDON); - expect(action.request.op).toEqual('post'); - expect(action.request.path).toEqual(`/@addons/${id}/uninstall`); + expect(action.type).toBe(UNINSTALL_ADDON); + expect(action.request.op).toBe('post'); + expect(action.request.path).toBe(`/@addons/${id}/uninstall`); }); }); + describe('upgradeAddon', () => { it('should create an action to upgrade an addon', () => { const id = 'plone.app.example'; const action = upgradeAddon(id); - expect(action.type).toEqual(UPGRADE_ADDON); - expect(action.request.op).toEqual('post'); - expect(action.request.path).toEqual(`/@addons/${id}/upgrade`); + expect(action.type).toBe(UPGRADE_ADDON); + expect(action.request.op).toBe('post'); + expect(action.request.path).toBe(`/@addons/${id}/upgrade`); }); }); + describe('listAddons', () => { it('should create an action to list all addons', () => { const action = listAddons(); - expect(action.type).toEqual(LIST_ADDONS); - expect(action.request.op).toEqual('get'); - expect(action.request.path).toEqual(`/@addons`); + expect(action.type).toBe(LIST_ADDONS); + expect(action.request.op).toBe('get'); + expect(action.request.path).toBe(`/@addons`); }); }); }); diff --git a/packages/volto/src/actions/aliases/aliases.test.js b/packages/volto/src/actions/aliases/aliases.test.js index e04783cead..2077559fe2 100644 --- a/packages/volto/src/actions/aliases/aliases.test.js +++ b/packages/volto/src/actions/aliases/aliases.test.js @@ -1,5 +1,4 @@ import { getAliases, addAliases, removeAliases } from './aliases'; - import { GET_ALIASES, ADD_ALIASES, @@ -18,6 +17,7 @@ describe('Aliases action', () => { ); }); }); + describe('addAliases', () => { it('should create an action to add aliases', () => { const url = '/news'; diff --git a/packages/volto/src/actions/types/types.test.js b/packages/volto/src/actions/types/types.test.js index 885be2ec12..b252e2f718 100644 --- a/packages/volto/src/actions/types/types.test.js +++ b/packages/volto/src/actions/types/types.test.js @@ -1,5 +1,6 @@ import { getTypes } from './types'; import { GET_TYPES } from '@plone/volto/constants/ActionTypes'; +import { vi } from 'vitest'; describe('Types action', () => { describe('getTypes', () => { @@ -10,7 +11,7 @@ describe('Types action', () => { }, }); const url = '/blog'; - const dispatch = jest.fn(); + const dispatch = vi.fn(); getTypes(url)(dispatch, getState); From 480d002b0fc94e9dd1f91e8eb5409928ca3eddaa Mon Sep 17 00:00:00 2001 From: Abhishek-17H Date: Tue, 21 Jan 2025 11:27:29 +0530 Subject: [PATCH 3/9] Migrated src/helpers to vitest --- .../src/helpers/Api/Api.plone.rest.test.js | 21 +- packages/volto/src/helpers/Api/Api.test.js | 21 +- .../AsyncConnect/AsyncConnect.test.jsx | 318 ++++++++---------- .../src/helpers/AuthToken/AuthToken.test.js | 83 +++-- .../withBlockExtensions.test.jsx.snap | 6 +- packages/volto/src/helpers/Html/Html.test.jsx | 257 ++++++++++++-- .../Html/__snapshots__/Html.test.jsx.snap | 185 ---------- .../helpers/Loadable/__mocks__/Loadable.jsx | 13 +- 8 files changed, 449 insertions(+), 455 deletions(-) delete mode 100644 packages/volto/src/helpers/Html/__snapshots__/Html.test.jsx.snap diff --git a/packages/volto/src/helpers/Api/Api.plone.rest.test.js b/packages/volto/src/helpers/Api/Api.plone.rest.test.js index 71383caecd..a75041c458 100644 --- a/packages/volto/src/helpers/Api/Api.plone.rest.test.js +++ b/packages/volto/src/helpers/Api/Api.plone.rest.test.js @@ -1,15 +1,18 @@ import config from '@plone/volto/registry'; import Api from './Api'; +import { vi } from 'vitest'; -jest.mock('superagent', () => ({ - get: jest.fn((url) => ({ - url, - query: jest.fn(), - set: jest.fn(), - type: jest.fn(), - send: jest.fn(), - end: jest.fn(), - })), +vi.mock('superagent', () => ({ + default: { + get: vi.fn((url) => ({ + url, + query: vi.fn(), + set: vi.fn(), + type: vi.fn(), + send: vi.fn(), + end: vi.fn(), + })), + }, })); beforeAll(() => { diff --git a/packages/volto/src/helpers/Api/Api.test.js b/packages/volto/src/helpers/Api/Api.test.js index bb7f072230..6235ee0131 100644 --- a/packages/volto/src/helpers/Api/Api.test.js +++ b/packages/volto/src/helpers/Api/Api.test.js @@ -1,21 +1,24 @@ // import superagent from 'superagent'; import config from '@plone/volto/registry'; import Api from './Api'; +import { vi } from 'vitest'; // jest.mock('react-cookie', () => ({ // load: jest.fn(() => 'token'), // })); // -jest.mock('superagent', () => ({ - get: jest.fn((url) => ({ - url, - query: jest.fn(), - set: jest.fn(), - type: jest.fn(), - send: jest.fn(), - end: jest.fn(), - })), +vi.mock('superagent', () => ({ + default: { + get: vi.fn((url) => ({ + url, + query: vi.fn(), + set: vi.fn(), + type: vi.fn(), + send: vi.fn(), + end: vi.fn(), + })), + }, })); beforeAll(() => { diff --git a/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.jsx b/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.jsx index 1b2036e229..f3d3da87c3 100644 --- a/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.jsx +++ b/packages/volto/src/helpers/AsyncConnect/AsyncConnect.test.jsx @@ -1,3 +1,4 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { Provider, connect } from 'react-redux'; import { withRouter, StaticRouter, MemoryRouter } from 'react-router'; @@ -11,7 +12,7 @@ import { } from '@plone/volto/actions/asyncConnect/asyncConnect'; import reduxAsyncConnect from '@plone/volto/reducers/asyncConnect/asyncConnect'; -import { AsyncConnectWithContext, AsyncConnect } from './AsyncConnect'; // , AsyncConnect +import { AsyncConnectWithContext, AsyncConnect } from './AsyncConnect'; import { asyncConnect, loadOnServer } from './'; import { matchAllRoutes } from './utils'; @@ -23,6 +24,7 @@ describe('', () => { }, }, }; + const initialState = { router: { location: { @@ -44,8 +46,8 @@ describe('', () => { }; }; - const endGlobalLoadSpy = jest.fn(() => endGlobalLoad()); - const beginGlobalLoadSpy = jest.fn(() => beginGlobalLoad()); + const endGlobalLoadSpy = vi.fn(() => endGlobalLoad()); + const beginGlobalLoadSpy = vi.fn(() => beginGlobalLoad()); const ReduxAsyncConnect = withRouter( connect(null, { @@ -54,11 +56,7 @@ describe('', () => { })(AsyncConnectWithContext), ); - /* eslint-disable no-unused-vars */ - /* eslint-enable no-unused-vars */ const App = ({ - // NOTE: use this as a reference of props passed to your component from router - // these are the params that are passed from router history, location, params, @@ -69,9 +67,7 @@ describe('', () => { externalState, remappedProp, staticContext, - // our param lunch, - // react-redux dispatch prop dispatch, ...rest }) => ( @@ -85,13 +81,7 @@ describe('', () => { ); - const MultiAppA = ({ - route, - // our param - breakfast, - // react-redux dispatch prop - ...rest - }) => ( + const MultiAppA = ({ route, breakfast, ...rest }) => (
{breakfast}
{renderRoutes(route.routes)} @@ -102,6 +92,13 @@ describe('', () => {
{dinner}
); + const UnwrappedApp = ({ route }) => ( +
+ {'Hi, I do not use @asyncConnect'} + {renderRoutes(route.routes)} +
+ ); + const WrappedApp = asyncConnect( [ { @@ -164,30 +161,6 @@ describe('', () => { }), )(MultiAppB); - const UnwrappedApp = ({ route }) => ( -
- {'Hi, I do not use @asyncConnect'} - {renderRoutes(route.routes)} -
- ); - - const reducers = combineReducers({ - reduxAsyncConnect, - router: routerReducer, - }); - - /* - const routes = ( - - - - - - - - ); - */ - const routes = [ { path: '/', @@ -213,15 +186,24 @@ describe('', () => { }, ]; - // inter-test state + const reducers = combineReducers({ + reduxAsyncConnect, + router: routerReducer, + }); + let testState; - it('properly fetches data on the server', async () => { - endGlobalLoadSpy.mockClear(); - beginGlobalLoadSpy.mockClear(); + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + it('properly fetches data on the server', async () => { const store = createStore(reducers, initialEmptyState); - const eat = jest.fn(() => 'yammi'); + const eat = vi.fn(() => 'yammi'); const helpers = { eat }; const location = { pathname: '/' }; @@ -258,22 +240,17 @@ describe('', () => { expect(testState.reduxAsyncConnect.loadState.lunch.error).toBe(null); expect(eat).toHaveBeenCalledTimes(1); - - // global loader spy expect(endGlobalLoadSpy).not.toHaveBeenCalled(); expect(beginGlobalLoadSpy).not.toHaveBeenCalled(); }); - it('properly picks data up from the server', function test() { - endGlobalLoadSpy.mockClear(); - beginGlobalLoadSpy.mockClear(); - + it('properly picks data up from the server', () => { const store = createStore(reducers, testState); const proto = AsyncConnect.prototype; - const eat = jest.fn(() => 'yammi'); + const eat = vi.fn(() => 'yammi'); - const spyLoadAsyncData = jest.spyOn(proto, 'loadAsyncData'); - const spyComponentDidMount = jest.spyOn(proto, 'componentDidMount'); + const spyLoadAsyncData = vi.spyOn(proto, 'loadAsyncData'); + const spyComponentDidMount = vi.spyOn(proto, 'componentDidMount'); const { getByTestId } = render( @@ -285,29 +262,22 @@ describe('', () => { expect(spyLoadAsyncData).not.toHaveBeenCalled(); expect(spyComponentDidMount).toHaveBeenCalledTimes(1); - expect(eat).not.toHaveBeenCalled(); const app = getByTestId('App'); expect(app).toHaveTextContent('sandwich'); - // global loader spy expect(endGlobalLoadSpy).not.toHaveBeenCalled(); expect(beginGlobalLoadSpy).not.toHaveBeenCalled(); - - spyLoadAsyncData.mockClear(); - spyComponentDidMount.mockClear(); }); - it("loads data on client side when it wasn't provided by server", function test() { - endGlobalLoadSpy.mockClear(); - beginGlobalLoadSpy.mockClear(); + it("loads data on client side when it wasn't provided by server", async () => { const store = createStore(reducers); - const eat = jest.fn(() => 'yammi'); + const eat = vi.fn(() => 'yammi'); const proto = AsyncConnect.prototype; - const spyLoadAsyncData = jest.spyOn(proto, 'loadAsyncData'); - const spyComponentDidMount = jest.spyOn(proto, 'componentDidMount'); + const spyLoadAsyncData = vi.spyOn(proto, 'loadAsyncData'); + const spyComponentDidMount = vi.spyOn(proto, 'componentDidMount'); render( @@ -319,27 +289,20 @@ describe('', () => { expect(spyLoadAsyncData).toHaveBeenCalledTimes(1); expect(spyComponentDidMount).toHaveBeenCalledTimes(1); - - // global loader spy expect(beginGlobalLoadSpy).toHaveBeenCalled(); - beginGlobalLoadSpy.mockClear(); - - return spyLoadAsyncData.mock.results[0].value.then(() => { - expect(endGlobalLoadSpy).toHaveBeenCalled(); - endGlobalLoadSpy.mockClear(); - spyLoadAsyncData.mockClear(); - spyComponentDidMount.mockClear(); - }); + await spyLoadAsyncData.mock.results[0].value; + expect(endGlobalLoadSpy).toHaveBeenCalled(); }); - it('supports extended connect signature', function test() { + it('supports extended connect signature', async () => { const store = createStore(reducers, initialState); - const eat = jest.fn(() => 'yammi'); + const eat = vi.fn(() => 'yammi'); const proto = AsyncConnect.prototype; - const spyLoadAsyncData = jest.spyOn(proto, 'loadAsyncData'); - // const spyComponentDidMount = jest.spyOn(proto, 'componentDidMount'); + // Add spies for both methods + const spyLoadAsyncData = vi.spyOn(proto, 'loadAsyncData'); + const spyComponentDidMount = vi.spyOn(proto, 'componentDidMount'); const { getByTestId } = render( @@ -349,133 +312,116 @@ describe('', () => { , ); - expect(proto.loadAsyncData).toHaveBeenCalledTimes(1); - expect(proto.componentDidMount).toHaveBeenCalledTimes(1); - - // global loader spy + expect(spyLoadAsyncData).toHaveBeenCalledTimes(1); + expect(spyComponentDidMount).toHaveBeenCalledTimes(1); expect(beginGlobalLoadSpy).toHaveBeenCalled(); - beginGlobalLoadSpy.mockClear(); - return spyLoadAsyncData.mock.results[0].value.then(() => { - expect(endGlobalLoadSpy).toHaveBeenCalled(); + await spyLoadAsyncData.mock.results[0].value; + expect(endGlobalLoadSpy).toHaveBeenCalled(); - const app = getByTestId('App'); - expect(app).toHaveTextContent('sandwich'); - expect(app).toHaveAttribute('external-state', 'supported'); - expect(app).toHaveAttribute('remapped-prop', 'on'); - expect(app).toHaveTextContent('sandwich'); - - endGlobalLoadSpy.mockClear(); - spyLoadAsyncData.mockClear(); - }); + const app = getByTestId('App'); + expect(app).toHaveTextContent('sandwich'); + expect(app).toHaveAttribute('external-state', 'supported'); + expect(app).toHaveAttribute('remapped-prop', 'on'); + expect(app).toHaveTextContent('sandwich'); }); - it('renders even when no component is connected', function test() { + it('renders even when no component is connected', async () => { const store = createStore(reducers); - const eat = jest.fn(() => 'yammi'); + const eat = vi.fn(() => 'yammi'); const location = { pathname: '/notconnected' }; const helpers = { eat }; - return loadOnServer({ + await loadOnServer({ store, location, routes, helpers, - }).then(() => { - const context = {}; - - const { getByTestId } = render( - - - - - , - ); - - if (context.url) { - throw new Error('redirected'); - } - - const app = getByTestId('UnwrappedApp'); - expect(app).toHaveTextContent('Hi, I do not use @asyncConnect'); - - testState = store.getState(); - expect(testState.reduxAsyncConnect.loaded).toBe(true); - expect(testState.reduxAsyncConnect.lunch).toBe(undefined); - expect(eat).not.toHaveBeenCalled(); - - // global loader spy - expect(endGlobalLoadSpy).not.toHaveBeenCalled(); - expect(beginGlobalLoadSpy).not.toHaveBeenCalled(); - - endGlobalLoadSpy.mockClear(); - beginGlobalLoadSpy.mockClear(); }); + + const context = {}; + + const { getByTestId } = render( + + + + + , + ); + + if (context.url) { + throw new Error('redirected'); + } + + const app = getByTestId('UnwrappedApp'); + expect(app).toHaveTextContent('Hi, I do not use @asyncConnect'); + + testState = store.getState(); + expect(testState.reduxAsyncConnect.loaded).toBe(true); + expect(testState.reduxAsyncConnect.lunch).toBe(undefined); + expect(eat).not.toHaveBeenCalled(); + + expect(endGlobalLoadSpy).not.toHaveBeenCalled(); + expect(beginGlobalLoadSpy).not.toHaveBeenCalled(); }); - it('properly fetches data in the correct order given a nested routing structure', function test() { + it('properly fetches data in the correct order given a nested routing structure', async () => { const store = createStore(reducers); const promiseOrder = []; - const eat = jest.fn((meal) => { + const eat = vi.fn((meal) => { promiseOrder.push(meal); return `yammi ${meal}`; }); const location = { pathname: '/multi' }; const helpers = { eat }; - return loadOnServer({ + await loadOnServer({ store, routes, location, helpers, - }).then(() => { - const context = {}; - - const { container } = render( - - - - - , - ); - - if (context.url) { - throw new Error('redirected'); - } - - expect(container).toHaveTextContent('omelette'); - expect(container).toHaveTextContent('chicken'); - - testState = store.getState(); - expect(testState.reduxAsyncConnect.loaded).toBe(true); - expect(testState.reduxAsyncConnect.breakfast).toBe('omelette'); - expect(testState.reduxAsyncConnect.dinner).toBe('chicken'); - expect(testState.reduxAsyncConnect.action).toBe('yammi dinner'); - expect(testState.reduxAsyncConnect.loadState.dinner.loading).toBe(false); - expect(testState.reduxAsyncConnect.loadState.dinner.loaded).toBe(true); - expect(testState.reduxAsyncConnect.loadState.dinner.error).toBe(null); - expect(testState.reduxAsyncConnect.loadState.breakfast.loading).toBe( - false, - ); - expect(testState.reduxAsyncConnect.loadState.breakfast.loaded).toBe(true); - expect(testState.reduxAsyncConnect.loadState.breakfast.error).toBe(null); - expect(eat).toHaveBeenCalledTimes(2); - - expect(promiseOrder).toEqual(['breakfast', 'dinner']); - - // global loader spy - expect(endGlobalLoadSpy).not.toHaveBeenCalled(); - expect(beginGlobalLoadSpy).not.toHaveBeenCalled(); - - endGlobalLoadSpy.mockClear(); - beginGlobalLoadSpy.mockClear(); }); + + const context = {}; + + const { container } = render( + + + + + , + ); + + if (context.url) { + throw new Error('redirected'); + } + + expect(container).toHaveTextContent('omelette'); + expect(container).toHaveTextContent('chicken'); + + testState = store.getState(); + expect(testState.reduxAsyncConnect.loaded).toBe(true); + expect(testState.reduxAsyncConnect.breakfast).toBe('omelette'); + expect(testState.reduxAsyncConnect.dinner).toBe('chicken'); + expect(testState.reduxAsyncConnect.action).toBe('yammi dinner'); + expect(testState.reduxAsyncConnect.loadState.dinner.loading).toBe(false); + expect(testState.reduxAsyncConnect.loadState.dinner.loaded).toBe(true); + expect(testState.reduxAsyncConnect.loadState.dinner.error).toBe(null); + expect(testState.reduxAsyncConnect.loadState.breakfast.loading).toBe(false); + expect(testState.reduxAsyncConnect.loadState.breakfast.loaded).toBe(true); + expect(testState.reduxAsyncConnect.loadState.breakfast.error).toBe(null); + expect(eat).toHaveBeenCalledTimes(2); + + expect(promiseOrder).toEqual(['breakfast', 'dinner']); + + expect(endGlobalLoadSpy).not.toHaveBeenCalled(); + expect(beginGlobalLoadSpy).not.toHaveBeenCalled(); }); - it('Doesnt call same extender twice', function test() { + it('Doesnt call same extender twice', async () => { const store = createStore(reducers); const promiseOrder = []; - const eat = jest.fn((meal) => { + const eat = vi.fn((meal) => { promiseOrder.push(meal); return `yammi ${meal}`; }); @@ -483,25 +429,25 @@ describe('', () => { const helpers = { eat }; serial = 0; - return loadOnServer({ + await loadOnServer({ store, routes, location, helpers, - }).then(() => { - const context = {}; - - render( - - - - - , - ); - expect(serial).toBe(1); }); - }); + const context = {}; + + render( + + + + + , + ); + + expect(serial).toBe(1); + }); it('Matches multiple asyncPropExtenders', function () { const routes = [ { diff --git a/packages/volto/src/helpers/AuthToken/AuthToken.test.js b/packages/volto/src/helpers/AuthToken/AuthToken.test.js index a924beb545..e36c91fa3d 100644 --- a/packages/volto/src/helpers/AuthToken/AuthToken.test.js +++ b/packages/volto/src/helpers/AuthToken/AuthToken.test.js @@ -1,60 +1,95 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; import Cookies from 'universal-cookie'; import jwt from 'jsonwebtoken'; import jwtDecode from 'jwt-decode'; - import { getAuthToken, persistAuthToken } from './AuthToken'; -jest.mock('universal-cookie', () => { +vi.mock('universal-cookie', () => { const mCookie = { - get: jest - .fn(() => require('jsonwebtoken').sign({ exp: 1 }, 'secret')) // eslint-disable-line global-require - .mockImplementationOnce(() => null), // the first call is for anonymous, no auth_token cookie - remove: jest.fn(), - set: jest.fn(), + get: vi + .fn(() => jwt.sign({ exp: 1 }, 'secret')) + .mockImplementationOnce(() => null), + remove: vi.fn(), + set: vi.fn(), + }; + return { + default: vi.fn(() => mCookie), }; - return jest.fn(() => mCookie); }); describe('AuthToken', () => { + let cookies; + let mockStore; + + beforeEach(() => { + cookies = new Cookies(); + vi.clearAllMocks(); + + mockStore = { + subscribe: vi.fn(), + dispatch: vi.fn(), + getState: vi.fn(() => ({ + userSession: { + token: null, + }, + router: { + location: { + pathname: '/current-path', + }, + }, + })), + }; + }); + describe('anonymousAuthToken', () => { it('avoid unnecessary removing auth token', () => { - const cookies = new Cookies(); const store = { - subscribe: jest.fn(), - getState: jest.fn(() => ({ + ...mockStore, + getState: vi.fn(() => ({ userSession: { token: null, }, + router: { + location: { + pathname: '/current-path', + }, + }, })), }; + persistAuthToken(store); - expect(cookies.remove).not.toBeCalledWith('auth_token', { path: '/' }); + expect(cookies.remove).not.toHaveBeenCalledWith('auth_token', { + path: '/', + }); }); }); describe('getAuthToken', () => { it('can get the auth token', () => { - const cookies = new Cookies(); getAuthToken(); - expect(cookies.get).toBeCalledWith('auth_token'); + expect(cookies.get).toHaveBeenCalledWith('auth_token'); }); }); describe('persistAuthToken', () => { it('can set a new auth token', () => { - const cookies = new Cookies(); const store = { - subscribe: jest.fn(), - getState: jest.fn(() => ({ + ...mockStore, + getState: vi.fn(() => ({ userSession: { token: jwt.sign({ exp: 2 }, 'secret'), }, + router: { + location: { + pathname: '/current-path', + }, + }, })), }; const { token } = store.getState().userSession; persistAuthToken(store); - expect(cookies.set).toBeCalledWith('auth_token', token, { + expect(cookies.set).toHaveBeenCalledWith('auth_token', token, { path: '/', expires: new Date(jwtDecode(token).exp * 1000), secure: false, @@ -62,18 +97,22 @@ describe('AuthToken', () => { }); it('can remove an auth token', () => { - const cookies = new Cookies(); const store = { - subscribe: jest.fn(), - getState: jest.fn(() => ({ + ...mockStore, + getState: vi.fn(() => ({ userSession: { token: null, }, + router: { + location: { + pathname: '/current-path', + }, + }, })), }; persistAuthToken(store); - expect(cookies.remove).toBeCalledWith('auth_token', { path: '/' }); + expect(cookies.remove).toHaveBeenCalledWith('auth_token', { path: '/' }); }); }); }); diff --git a/packages/volto/src/helpers/Extensions/__snapshots__/withBlockExtensions.test.jsx.snap b/packages/volto/src/helpers/Extensions/__snapshots__/withBlockExtensions.test.jsx.snap index 15a1088d8f..6dfbaa7746 100644 --- a/packages/volto/src/helpers/Extensions/__snapshots__/withBlockExtensions.test.jsx.snap +++ b/packages/volto/src/helpers/Extensions/__snapshots__/withBlockExtensions.test.jsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`withBlockExtensions injects extensions as props 1`] = ` +exports[`withBlockExtensions > injects extensions as props 1`] = `
{"id":"default","title":"Default","isDefault":true} @@ -11,7 +11,7 @@ exports[`withBlockExtensions injects extensions as props 1`] = `
`; -exports[`withBlockExtensions resolve extensions according to data 1`] = ` +exports[`withBlockExtensions > resolve extensions according to data 1`] = `
{"id":"extra","title":"Extra"} diff --git a/packages/volto/src/helpers/Html/Html.test.jsx b/packages/volto/src/helpers/Html/Html.test.jsx index 981e4d0b2a..c18f814e65 100644 --- a/packages/volto/src/helpers/Html/Html.test.jsx +++ b/packages/volto/src/helpers/Html/Html.test.jsx @@ -1,46 +1,52 @@ +import { describe, it, expect, vi } from 'vitest'; import React from 'react'; -import renderer from 'react-test-renderer'; +import { create } from 'react-test-renderer'; import config from '@plone/volto/registry'; import Html from './Html'; -jest.mock('../Helmet/Helmet', () => ({ - rewind: () => ({ - base: { - toComponent: () => '', - }, - title: { - toComponent: () => '', - }, - meta: { - toComponent: () => '', - }, - link: { - toComponent: () => '', - }, - script: { - toComponent: () => '', - }, - style: { - toComponent: () => '', - }, - htmlAttributes: { - toComponent: () => ({ - lang: 'en', - }), - }, - }), +vi.mock('../Helmet/Helmet', () => ({ + default: { + rewind: () => ({ + base: { + toComponent: () => '', + }, + title: { + toComponent: () => '', + }, + meta: { + toComponent: () => '', + }, + link: { + toComponent: () => '', + }, + script: { + toComponent: () => '', + }, + style: { + toComponent: () => '', + }, + htmlAttributes: { + toComponent: () => ({ + lang: 'en', + }), + }, + }), + }, })); -jest.mock('../BodyClass/BodyClass', () => ({ - rewind: () => ['class1', 'class2'], +vi.mock('../BodyClass/BodyClass', () => ({ + default: { + rewind: () => ['class1', 'class2'], + }, })); -config.settings = {}; -config.settings.initialReducersBlacklist = ['navigation']; +config.settings = { + initialReducersBlacklist: ['navigation'], +}; describe('Html', () => { it('renders a html component', () => { - const component = renderer.create( + const component = create( [ @@ -67,10 +73,101 @@ describe('Html', () => { />, ); const json = component.toJSON(); - expect(json).toMatchSnapshot(); + expect(json).toMatchInlineSnapshot(` + + + +