chore(deps): upgrade vitest and msw (#1852)

This commit is contained in:
Chaim Lev-Ari
2026-02-11 15:14:04 +00:00
committed by GitHub
parent 8e44c8fa06
commit 762c1ccf28
20 changed files with 1085 additions and 904 deletions

View File

@@ -139,15 +139,18 @@ overrides:
'react/jsx-props-no-spreading': off
- files:
- app/**/*.test.*
plugins:
- '@vitest'
extends:
- 'plugin:vitest/recommended'
- 'plugin:@vitest/legacy-recommended'
env:
'vitest/env': true
'@vitest/env': true
rules:
'react/jsx-no-constructed-context-values': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off
'react/jsx-props-no-spreading': off
'@vitest/no-conditional-expect': warn
- files:
- app/**/*.stories.*
rules:

View File

@@ -61,11 +61,8 @@ angular
.config(configApp);
if (require) {
const req = require.context('./', true, /^(.*\.(js$))[^.]*$/im);
req
.keys()
.filter((path) => !path.includes('.test'))
.forEach(function (key) {
const req = require.context('./', true, /^(?!.*\.test\.js$).*\.js$/im);
req.keys().forEach(function (key) {
req(key);
});
}

View File

@@ -19,9 +19,7 @@ test('renders TemplateSelector component', async () => {
});
// TODO skipped select tests because the tests take too long to run
// eslint-disable-next-line vitest/expect-expect
test.skip('selects an edge app template', async () => {
test.todo('selects an edge app template', async () => {
const onChange = vi.fn();
const selectedTemplate = {
@@ -50,8 +48,8 @@ test.skip('selects an edge app template', async () => {
});
});
// eslint-disable-next-line vitest/expect-expect
test.skip('selects an edge custom template', async () => {
// eslint-disable-next-line @vitest/expect-expect
test.todo('selects an edge custom template', async () => {
const onChange = vi.fn();
const selectedTemplate = {
@@ -87,7 +85,9 @@ test('renders with error', async () => {
expect(errorElement).toBeInTheDocument();
});
test.skip('renders TemplateSelector component with no custom templates available', async () => {
test.todo(
'renders TemplateSelector component with no custom templates available',
async () => {
renderComponent({
customTemplates: [],
});
@@ -101,7 +101,8 @@ test.skip('renders TemplateSelector component with no custom templates available
'No edge custom templates available'
);
expect(noCustomTemplatesElement).toBeInTheDocument();
});
}
);
function renderComponent({
onChange = vi.fn(),

View File

@@ -32,7 +32,7 @@ describe('getValidEditorTypes', () => {
];
tests.forEach((test) => {
// eslint-disable-next-line vitest/valid-title
// eslint-disable-next-line @vitest/valid-title
it(test.title, () => {
expect(getValidEditorTypes(test.endpointTypes)).toEqual(test.expected);
});

View File

@@ -436,7 +436,7 @@ describe('getCreateAppSummaries', () => {
},
];
tests.forEach((test) => {
// eslint-disable-next-line vitest/valid-title
// eslint-disable-next-line @vitest/valid-title
it(test.title, () => {
expect(
getAppResourceSummaries(test.newFormValues, test.oldFormValues)
@@ -522,7 +522,7 @@ describe('getUpdateAppSummaries', () => {
},
];
tests.forEach((test) => {
// eslint-disable-next-line vitest/valid-title
// eslint-disable-next-line @vitest/valid-title
it(test.title, () => {
expect(
getAppResourceSummaries(test.newFormValues, test.oldFormValues)

View File

@@ -11,16 +11,25 @@ import { KubectlShellView } from './KubectlShellView';
// Mock modules first
vi.mock('xterm', () => ({
Terminal: vi.fn(() => ({
open: vi.fn(),
setOption: vi.fn(),
focus: vi.fn(),
writeln: vi.fn(),
writeUtf8: vi.fn(),
onData: vi.fn(),
onKey: vi.fn(),
dispose: vi.fn(),
})),
Terminal: vi.fn(
class {
open = vi.fn();
setOption = vi.fn();
focus = vi.fn();
writeln = vi.fn();
writeUtf8 = vi.fn();
onData = vi.fn();
onKey = vi.fn();
dispose = vi.fn();
}
),
}));
vi.mock('xterm/lib/addons/fit/fit', () => ({
@@ -70,9 +79,9 @@ beforeEach(() => {
};
// Mock Terminal constructor to return our mock instance
vi.mocked(Terminal).mockImplementation(
() => mockTerminalInstance as Terminal
);
vi.mocked(Terminal).mockImplementation(function (this: Terminal) {
Object.assign(this, mockTerminalInstance);
});
// Create mock WebSocket instance
mockWebSocket = {
@@ -83,7 +92,17 @@ beforeEach(() => {
readyState: WebSocket.OPEN,
};
global.WebSocket = vi.fn(() => mockWebSocket) as unknown as typeof WebSocket;
global.WebSocket = vi.fn(function (this: WebSocket) {
this.send = mockWebSocket.send;
this.close = mockWebSocket.close;
this.addEventListener = mockWebSocket.addEventListener;
this.removeEventListener = mockWebSocket.removeEventListener;
Object.defineProperty(this, 'readyState', {
get: () => mockWebSocket.readyState,
configurable: true,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
// Reset window methods
Object.defineProperty(window, 'location', {

View File

@@ -201,9 +201,7 @@ function renderComponent() {
return render(<Wrapped />);
}
describe(
'HelmApplicationView',
() => {
describe('HelmApplicationView', () => {
beforeEach(() => {
// Set up default mock values
mockUseEnvironmentId.mockReturnValue(3);
@@ -303,8 +301,4 @@ describe(
// Check for the app version in the summary section
expect(await screen.findByText('1.0.0')).toBeInTheDocument();
});
},
{
timeout: 7000,
}
);
}, 7000);

View File

@@ -1,6 +1,7 @@
import { http, HttpResponse } from 'msw';
import { server } from '@/setup-tests/server';
import PortainerError from '@/portainer/error';
import { getLicenses } from './license.service';
import type { License } from './types';
@@ -37,7 +38,9 @@ describe('getLicenses', () => {
const promise = getLicenses();
await promise.then(thenFn, catchFn);
expect(catchFn).toHaveBeenCalledWith(new Error(details));
expect(catchFn).toHaveBeenCalledWith(
new PortainerError(details, new Error(message))
);
expect(thenFn).not.toHaveBeenCalled();
});
});

View File

@@ -19,6 +19,7 @@ import { kubernetesHandlers } from './setup-handlers/kubernetes';
import { endpointsHandlers } from './setup-handlers/endpoints';
import { settingsHandlers } from './setup-handlers/settings';
import { templatesHandlers } from './setup-handlers/templates';
import { edgeHandlers } from './setup-handlers/edge';
const tags: Tag[] = [
{ ID: 1, Name: 'tag1', Endpoints: {} },
@@ -54,13 +55,14 @@ export const handlers = [
...dockerHandlers,
...userHandlers,
...kubernetesHandlers,
...edgeHandlers,
http.get('/api/stacks', () => HttpResponse.json([])),
http.get('/api/licenses/info', () => HttpResponse.json(licenseInfo)),
http.get('/api/status/nodes', () => HttpResponse.json({ nodes: 3 })),
http.get('/api/backup/s3/status', () => HttpResponse.json({ Failed: false })),
http.get('/api/endpoint_groups', () => HttpResponse.json([])),
http.get('/api/endpoint_groups/:groupId', ({ params }) => {
if (params.groupId instanceof Array) {
if (!params.groupId || params.groupId instanceof Array) {
throw new Error('should be string');
}
const id = parseInt(params.groupId, 10);
@@ -82,6 +84,9 @@ export const handlers = [
http.get<never, never, Partial<StatusResponse>>('/api/status', () =>
HttpResponse.json({})
),
http.get<never, never, Partial<StatusResponse>>('/api/system/status', () =>
HttpResponse.json({})
),
http.get<never, never, Partial<VersionResponse>>('/api/system/version', () =>
HttpResponse.json({ ServerVersion: 'v2.10.0' })
),
@@ -104,4 +109,6 @@ export const handlers = [
HttpResponse.json({ success: true })
),
http.get('/api/webhooks', () => HttpResponse.json([])),
http.get('/api/policies', () => HttpResponse.json([])),
http.get('/api/roles', () => HttpResponse.json([])),
];

View File

@@ -1,4 +1,4 @@
import 'vitest-dom/extend-expect';
import '@testing-library/jest-dom/vitest';
import { mockCodeMirror } from './mock-codemirror';

View File

@@ -2,4 +2,7 @@ import { http, HttpResponse } from 'msw';
export const dockerImagesHandlers = [
http.get('/api/docker/:envId/images', () => HttpResponse.json([])),
http.get('/api/endpoints/:id/docker/images/json', () =>
HttpResponse.json([])
),
];

View File

@@ -0,0 +1,24 @@
import { http, HttpResponse } from 'msw';
export const edgeHandlers = [
http.get('/api/edge_configurations', () => HttpResponse.json([])),
http.get('/api/edge_configurations/:id', () => HttpResponse.json({})),
http.get('/api/edge_update_schedules', () => HttpResponse.json([])),
http.get('/api/edge_update_schedules/:id', () => HttpResponse.json({})),
http.get('/api/edge_groups', () => HttpResponse.json([])),
http.get('/api/edge_jobs', () => HttpResponse.json([])),
http.get('/api/edge_jobs/:id', () => HttpResponse.json({})),
http.get('/api/edge_jobs/:id/file', () =>
HttpResponse.json({ FileContent: '' })
),
http.get('/api/edge_stacks', () => HttpResponse.json([])),
http.get('/api/edge_stacks/:id', () => HttpResponse.json({})),
http.get('/api/edge_stacks/:id/stagger/status', () =>
HttpResponse.json({
status: 'idle',
})
),
http.get('/api/edge_stacks/:id/file', () =>
HttpResponse.json({ StackFileContent: '' })
),
];

View File

@@ -1,3 +1,4 @@
import { NodeList } from 'kubernetes-types/core/v1';
import { http, HttpResponse } from 'msw';
export const kubernetesHandlers = [
@@ -20,7 +21,27 @@ export const kubernetesHandlers = [
http.get('/api/kubernetes/:endpointId/customresourcedefinitions', () =>
HttpResponse.json({})
),
http.get('/api/kubernetes/:endpointId/rbac_enabled', () =>
HttpResponse.json(false)
),
http.get('/api/kubernetes/:endpointId/customresourcedefinitions/:name', () =>
HttpResponse.json({})
),
http.get(
'/api/kubernetes/:endpointId/namespaces/portainer/configmaps/:name',
() => HttpResponse.json({})
),
http.get('/api/kubernetes/:endpointId/max_resource_limits', () =>
HttpResponse.json({})
),
http.get('/api/kubernetes/:endpointId/ingresscontrollers', () =>
HttpResponse.json([])
),
http.get('/api/kubernetes/:id/namespaces/:ns/ingresscontrollers', () =>
HttpResponse.json([])
),
http.get('/api/endpoints/:endpointId/kubernetes/api/v1/nodes', () =>
HttpResponse.json({ items: [] } satisfies NodeList)
),
];

View File

@@ -5,9 +5,14 @@ import { PublicSettingsResponse } from '@/react/portainer/settings/types';
export const settingsHandlers = [
http.get('/api/ssl', () => HttpResponse.json({})),
http.get('/api/settings', () => HttpResponse.json({})),
http.get('/api/settings/edge/mtls_certificate', () => HttpResponse.json({})),
http.get('/api/settings/edge/mtls_ca_certificate', () =>
HttpResponse.json({})
),
http.get('/api/settings/additional_functionality', () =>
HttpResponse.json({})
),
http.get('/api/support/debug_log', () => HttpResponse.json({})),
http.get<never, never, Partial<PublicSettingsResponse>>(
'/api/settings/public',
() =>

View File

@@ -0,0 +1,9 @@
// MSW 2.x requires WebSocket to be writable in the global scope
// In JSDOM, WebSocket is read-only by default, so we need to make it configurable
if (typeof global.WebSocket !== 'undefined') {
Object.defineProperty(global, 'WebSocket', {
writable: true,
configurable: true,
value: global.WebSocket,
});
}

View File

@@ -1 +1 @@
import 'vitest-dom/extend-expect';
import '@testing-library/jest-dom/vitest';

View File

@@ -157,6 +157,7 @@
"@storybook/react-webpack5": "8.6.15",
"@svgr/webpack": "^8.1.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^12",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.5.2",
@@ -168,6 +169,7 @@
"@types/json-schema": "^7.0.15",
"@types/lodash": "^4.17.21",
"@types/mustache": "^4.1.2",
"@types/node": "^22.19.11",
"@types/nprogress": "^0.2.0",
"@types/qs": "^6.9.17",
"@types/react": "^17.0.37",
@@ -179,7 +181,8 @@
"@types/uuid": "^3.3.2",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"@vitest/coverage-v8": "~2.1.9",
"@vitest/coverage-v8": "^4",
"@vitest/eslint-plugin": "^1.6.7",
"auto-ngtemplate-loader": "^3.1.2",
"autoprefixer": "^10.4.16",
"babel-loader": "^9.1.3",
@@ -204,7 +207,6 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-regex": "^1.10.0",
"eslint-plugin-storybook": "^9",
"eslint-plugin-vitest": "^0.3.20",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^5.5.3",
"husky": "^8.0.0",
@@ -212,7 +214,7 @@
"lint-staged": "^14.0.1",
"lodash-webpack-plugin": "^0.11.6",
"mini-css-extract-plugin": "^2.7.6",
"msw": "^2.0.11",
"msw": "^2.12.10",
"msw-storybook-addon": "^2.0.6",
"ngtemplate-loader": "^2.1.0",
"postcss": "^8.5.6",
@@ -231,8 +233,7 @@
"typescript": "^5.5.2",
"vite-plugin-svgr": "^4.5.0",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "~2.1.9",
"vitest-dom": "^0.1.1",
"vitest": "^4",
"webpack": "^5.105.0",
"webpack-build-notifier": "^3.1.0",
"webpack-bundle-analyzer": "^5.2.0",
@@ -253,10 +254,10 @@
"@uirouter/angularjs": "1.0.11",
"moment": "^2.21.0",
"markdown-it": "^12.3.2",
"msw>wrap-ansi": "^7.0.0",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/node": "^25"
"@types/node": "^25",
"@open-amt-cloud-toolkit/ui-toolkit>ws": "^7.5.10"
},
"configDependencies": {
"@pnpm/plugin-types-fixer": "0.1.0+sha512-bLww63gRHi7siYTqFJb5qNdcXadU0jv20Et6z5AryMZ7FlLolbEJOrXLpg8+amQZNHHNW1dfFUBGVw/9ezQbFg=="

1576
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@
// https://github.com/jsonnext/codemirror-json-schema/issues/107#issuecomment-2144584296
"yaml-schema": ["../node_modules/codemirror-json-schema/dist/yaml"]
},
"types": ["vitest/globals"]
"types": ["vitest/globals", "@testing-library/jest-dom", "node"]
},
"exclude": ["api", "build", "dist", "distribution", "node_modules", "test", "webpack"],
"include": ["app", ".storybook"]

View File

@@ -3,10 +3,16 @@ import tsconfigPaths from 'vite-tsconfig-paths';
import svgr from 'vite-plugin-svgr';
export default defineConfig({
build: {
// force tests to import svg as url
// TODO consider removing when moving from webpack
assetsInlineLimit: 0,
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: [
'./app/setup-tests/setup-websocket.ts',
'./app/setup-tests/setup-rtl.ts',
'./app/setup-tests/setup-msw.ts',
'./app/setup-tests/stub-modules.ts',