mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
chore: Vite 8 upgrade (#27680)
This commit is contained in:
parent
34894af3fa
commit
efc474cc01
60 changed files with 2640 additions and 1649 deletions
|
|
@ -21,6 +21,7 @@ describe('resolveConnection', () => {
|
|||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
delete process.env.N8N_URL;
|
||||
delete process.env.N8N_API_KEY;
|
||||
setNoConfigFile();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { CompletionResult, CompletionSource } from '@codemirror/autocomplete';
|
||||
import { CompletionContext } from '@codemirror/autocomplete';
|
||||
import { ensureSyntaxTree } from '@codemirror/language';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
|
||||
import type { SQLConfig } from '../src/sql';
|
||||
|
|
@ -20,6 +21,7 @@ function get(doc: string, conf: SQLConfig & { explicit?: boolean } = {}) {
|
|||
}),
|
||||
],
|
||||
});
|
||||
ensureSyntaxTree(state, state.doc.length, 1e9);
|
||||
const result = state.languageDataAt<CompletionSource>('autocomplete', cur)[0](
|
||||
new CompletionContext(state, cur, !!conf.explicit),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"@n8n/vitest-config": "workspace:*",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript-eslint/eslint-plugin": "^8.35.0",
|
||||
"@typescript-eslint/rule-tester": "^8.35.0",
|
||||
"@typescript-eslint/utils": "^8.35.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["vitest/globals"],
|
||||
"types": ["vitest/globals", "node"],
|
||||
"esModuleInterop": true,
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "nodenext",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"devDependencies": {
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
"@n8n/vitest-config": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript-eslint/rule-tester": "^8.35.0",
|
||||
"eslint-doc-generator": "^2.2.2",
|
||||
"eslint-plugin-eslint-plugin": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["vitest/globals"]
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ interface TmpDirFixture {
|
|||
}
|
||||
|
||||
export const tmpdirTest = test.extend<TmpDirFixture>({
|
||||
tmpdir: async ({ expect: _expect }, use) => {
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
tmpdir: async ({}, use) => {
|
||||
const directory = await createTempDir();
|
||||
const originalCwd = process.cwd();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const createVitestConfig = (options: InlineConfig = {}) => {
|
|||
outputFile: { junit: './junit.xml' },
|
||||
coverage: {
|
||||
enabled: false,
|
||||
all: false,
|
||||
include: ['src/**'],
|
||||
provider: 'v8',
|
||||
reporter: ['text-summary', 'lcov', 'html-spa'],
|
||||
},
|
||||
|
|
@ -29,7 +29,7 @@ export const createVitestConfig = (options: InlineConfig = {}) => {
|
|||
const { coverage } = vitestConfig.test;
|
||||
coverage.enabled = true;
|
||||
if (process.env.CI === 'true' && coverage.provider === 'v8') {
|
||||
coverage.all = true;
|
||||
coverage.include = ['src/**'];
|
||||
coverage.reporter = ['cobertura'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export const createBaseInlineConfig = (options: InlineConfig = {}): InlineConfig
|
|||
enabled: true,
|
||||
provider: 'v8',
|
||||
reporter: process.env.CI === 'true' ? 'cobertura' : 'text-summary',
|
||||
all: true,
|
||||
include: ['src/**/*'],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ const MOCK_FEATURE_FLAG = 'feat:sharing';
|
|||
const MOCK_MAIN_PLAN_ID = '1b765dc4-d39d-4ffe-9885-c56dd67c4b26';
|
||||
|
||||
function makeDateWithHourOffset(offsetInHours: number): Date {
|
||||
const date = new Date();
|
||||
date.setHours(date.getHours() + offsetInHours);
|
||||
return date;
|
||||
return new Date(Date.now() + offsetInHours * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
const licenseConfig: GlobalConfig['license'] = {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export class NodeTestHarness {
|
|||
this.assertOutput(testData, result, nodeExecutionOrder);
|
||||
|
||||
if (options.customAssertions) options.customAssertions();
|
||||
});
|
||||
}, 20_000);
|
||||
}
|
||||
|
||||
@Memoized
|
||||
|
|
|
|||
|
|
@ -3,10 +3,19 @@ import { configure } from '@testing-library/vue';
|
|||
|
||||
configure({ testIdAttribute: 'data-test-id' });
|
||||
|
||||
window.ResizeObserver =
|
||||
window.ResizeObserver ||
|
||||
vi.fn().mockImplementation(() => ({
|
||||
disconnect: vi.fn(),
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
}));
|
||||
class ResizeObserverMock extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
observe = vi.fn();
|
||||
|
||||
disconnect = vi.fn();
|
||||
|
||||
unobserve = vi.fn();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
||||
});
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { defineConfig, mergeConfig, PluginOption } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
import { renameSync, writeFileSync, readFileSync } from 'fs';
|
||||
import { renameSync, writeFileSync, readFileSync, existsSync } from 'fs';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import icons from 'unplugin-icons/vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
|
|
@ -32,7 +32,9 @@ export default mergeConfig(
|
|||
const cssPath = resolve(__dirname, 'dist', 'chat.css');
|
||||
const newCssPath = resolve(__dirname, 'dist', 'style.css');
|
||||
try {
|
||||
renameSync(cssPath, newCssPath);
|
||||
if (existsSync(cssPath)) {
|
||||
renameSync(cssPath, newCssPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to rename chat.css file:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
|
|
|
|||
|
|
@ -9,14 +9,6 @@ configure({ testIdAttribute: 'data-test-id' });
|
|||
|
||||
config.global.plugins = [N8nPlugin];
|
||||
|
||||
window.ResizeObserver =
|
||||
window.ResizeObserver ||
|
||||
vi.fn().mockImplementation(() => ({
|
||||
disconnect: vi.fn(),
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
}));
|
||||
|
||||
// Globally mock is-emoji-supported
|
||||
vi.mock('is-emoji-supported', () => ({
|
||||
isEmojiSupported: () => true,
|
||||
|
|
@ -75,8 +67,21 @@ class PatchedPointerEvent extends OriginalPointerEvent {
|
|||
}
|
||||
}
|
||||
|
||||
class ResizeObserverMock extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
observe = vi.fn();
|
||||
|
||||
disconnect = vi.fn();
|
||||
|
||||
unobserve = vi.fn();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('MouseEvent', PatchedMouseEvent);
|
||||
vi.stubGlobal('PointerEvent', PatchedPointerEvent);
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
||||
});
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
|
|
|||
8
packages/frontend/@n8n/i18n/src/utils.test.ts
Normal file
8
packages/frontend/@n8n/i18n/src/utils.test.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// this test file is a placeholder since vitest requires a minimum of 2 test files to
|
||||
// allow sharding to 2 shards.
|
||||
|
||||
describe('utils', () => {
|
||||
it('succeeds', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
|
|
|
|||
8
packages/frontend/@n8n/rest-api-client/src/index.test.ts
Normal file
8
packages/frontend/@n8n/rest-api-client/src/index.test.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// this test file is a placeholder since vitest requires a minimum of 2 test files to
|
||||
// allow sharding to 2 shards.
|
||||
|
||||
describe('index', () => {
|
||||
it('succeeds', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,13 +2,12 @@
|
|||
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "dist",
|
||||
"noEmit": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
"isolatedModules": true,
|
||||
"paths": {
|
||||
"@n8n/utils/*": ["../../../@n8n/utils/src/*"]
|
||||
}
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "vite.config.ts", "tsdown.config.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
|
|
|
|||
|
|
@ -131,17 +131,18 @@
|
|||
"@types/json-schema": "^7.0.15",
|
||||
"@types/lodash": "catalog:",
|
||||
"@types/uuid": "catalog:",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
"@vitejs/plugin-legacy": "^8.0.0",
|
||||
"@vitejs/plugin-vue": "catalog:frontend",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"miragejs": "^0.1.48",
|
||||
"sass-embedded": "catalog:",
|
||||
"unplugin-icons": "catalog:frontend",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-istanbul": "^7.2.0",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vite-plugin-static-copy": "2.2.0",
|
||||
"vite-plugin-istanbul": "^8.0.0",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vite-plugin-static-copy": "4.0.0",
|
||||
"vite-svg-loader": "catalog:frontend",
|
||||
"vitest": "catalog:",
|
||||
"vitest-mock-extended": "catalog:",
|
||||
|
|
|
|||
|
|
@ -196,12 +196,12 @@ export function createTestWorkflowObject({
|
|||
return new Workflow({
|
||||
id,
|
||||
name,
|
||||
nodes,
|
||||
connections,
|
||||
nodes: Array.isArray(nodes) ? nodes : [],
|
||||
connections: typeof connections === 'object' && connections !== null ? connections : {},
|
||||
active,
|
||||
staticData,
|
||||
settings,
|
||||
pinData,
|
||||
staticData: typeof staticData === 'object' && staticData !== null ? staticData : {},
|
||||
settings: typeof settings === 'object' && settings !== null ? settings : {},
|
||||
pinData: typeof pinData === 'object' && pinData !== null ? pinData : {},
|
||||
nodeTypes: rest.nodeTypes ?? nodeTypes,
|
||||
});
|
||||
}
|
||||
|
|
@ -254,6 +254,7 @@ export function createTestNode(node: Partial<INode> = {}): INode {
|
|||
export function createTestNodeProperties(data: Partial<INodeProperties> = {}): INodeProperties {
|
||||
return {
|
||||
displayName: 'Name',
|
||||
displayOptions: undefined,
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { createCanvasGraphNode } from '@/features/workflows/canvas/__tests__/utils';
|
||||
import { createTestNode, createTestWorkflow, mockNodeTypeDescription } from '@/__tests__/mocks';
|
||||
import {
|
||||
createTestNode,
|
||||
createTestNodeProperties,
|
||||
createTestWorkflow,
|
||||
mockNodeTypeDescription,
|
||||
} from '@/__tests__/mocks';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
import { SET_NODE_TYPE } from '@/app/constants';
|
||||
|
|
@ -44,22 +49,22 @@ describe('FocusSidebar', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const parameter0: INodeProperties = {
|
||||
const parameter0: INodeProperties = createTestNodeProperties({
|
||||
displayName: 'P0',
|
||||
name: 'p0',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: '',
|
||||
validateType: 'string',
|
||||
};
|
||||
const parameter1: INodeProperties = {
|
||||
});
|
||||
const parameter1: INodeProperties = createTestNodeProperties({
|
||||
displayName: 'P1',
|
||||
name: 'p1',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: '',
|
||||
validateType: 'string',
|
||||
};
|
||||
});
|
||||
|
||||
let experimentalNdvStore: ReturnType<typeof mockedStore<typeof useExperimentalNdvStore>>;
|
||||
let focusPanelStore: ReturnType<typeof useFocusPanelStore>;
|
||||
|
|
|
|||
|
|
@ -6,53 +6,53 @@ exports[`useFlattenSchema > flattenMultipleSchemas > should flatten node schemas
|
|||
"collapsable": true,
|
||||
"id": "Test Node",
|
||||
"info": undefined,
|
||||
"itemCount": [MockFunction spy],
|
||||
"itemCount": [MockFunction],
|
||||
"lastSuccessfulPreview": false,
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"preview": false,
|
||||
"title": "Test Node",
|
||||
"type": "header",
|
||||
},
|
||||
{
|
||||
"binaryData": [MockFunction spy],
|
||||
"binaryData": [MockFunction],
|
||||
"collapsable": true,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json }}",
|
||||
"icon": "box",
|
||||
"id": "Test Node-{{ $('Test Node').item.json }}",
|
||||
"level": 0,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": "",
|
||||
"preview": false,
|
||||
"title": [MockFunction spy],
|
||||
"title": [MockFunction],
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"binaryData": [MockFunction spy],
|
||||
"binaryData": [MockFunction],
|
||||
"collapsable": true,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json.obj }}",
|
||||
"icon": "box",
|
||||
"id": "Test Node-{{ $('Test Node').item.json.obj }}",
|
||||
"level": 1,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": ".obj",
|
||||
"preview": false,
|
||||
"title": "obj",
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"binaryData": [MockFunction spy],
|
||||
"binaryData": [MockFunction],
|
||||
"collapsable": true,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json.obj.foo }}",
|
||||
"icon": "box",
|
||||
"id": "Test Node-{{ $('Test Node').item.json.obj.foo }}",
|
||||
"level": 2,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": ".obj.foo",
|
||||
"preview": false,
|
||||
"title": "foo",
|
||||
|
|
@ -60,13 +60,13 @@ exports[`useFlattenSchema > flattenMultipleSchemas > should flatten node schemas
|
|||
},
|
||||
{
|
||||
"collapsable": false,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json.obj.foo.nested }}",
|
||||
"icon": "type",
|
||||
"id": "Test Node-{{ $('Test Node').item.json.obj.foo.nested }}",
|
||||
"level": 3,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": ".obj.foo.nested",
|
||||
"preview": false,
|
||||
"title": "nested",
|
||||
|
|
@ -77,53 +77,53 @@ exports[`useFlattenSchema > flattenMultipleSchemas > should flatten node schemas
|
|||
"collapsable": true,
|
||||
"id": "Test Node",
|
||||
"info": undefined,
|
||||
"itemCount": [MockFunction spy],
|
||||
"itemCount": [MockFunction],
|
||||
"lastSuccessfulPreview": false,
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"preview": false,
|
||||
"title": "Test Node",
|
||||
"type": "header",
|
||||
},
|
||||
{
|
||||
"binaryData": [MockFunction spy],
|
||||
"binaryData": [MockFunction],
|
||||
"collapsable": true,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json }}",
|
||||
"icon": "box",
|
||||
"id": "Test Node-{{ $('Test Node').item.json }}",
|
||||
"level": 0,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": "",
|
||||
"preview": false,
|
||||
"title": [MockFunction spy],
|
||||
"title": [MockFunction],
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"binaryData": [MockFunction spy],
|
||||
"binaryData": [MockFunction],
|
||||
"collapsable": true,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json.obj }}",
|
||||
"icon": "box",
|
||||
"id": "Test Node-{{ $('Test Node').item.json.obj }}",
|
||||
"level": 1,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": ".obj",
|
||||
"preview": false,
|
||||
"title": "obj",
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"binaryData": [MockFunction spy],
|
||||
"binaryData": [MockFunction],
|
||||
"collapsable": true,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json.obj.foo }}",
|
||||
"icon": "box",
|
||||
"id": "Test Node-{{ $('Test Node').item.json.obj.foo }}",
|
||||
"level": 2,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": ".obj.foo",
|
||||
"preview": false,
|
||||
"title": "foo",
|
||||
|
|
@ -131,13 +131,13 @@ exports[`useFlattenSchema > flattenMultipleSchemas > should flatten node schemas
|
|||
},
|
||||
{
|
||||
"collapsable": false,
|
||||
"depth": [MockFunction spy],
|
||||
"depth": [MockFunction],
|
||||
"expression": "{{ $('Test Node').item.json.obj.foo.nested }}",
|
||||
"icon": "type",
|
||||
"id": "Test Node-{{ $('Test Node').item.json.obj.foo.nested }}",
|
||||
"level": 3,
|
||||
"nodeName": "Test Node",
|
||||
"nodeType": [MockFunction spy],
|
||||
"nodeType": [MockFunction],
|
||||
"path": ".obj.foo.nested",
|
||||
"preview": false,
|
||||
"title": "nested",
|
||||
|
|
|
|||
|
|
@ -9,36 +9,33 @@ const mockRequestPermission = vi.fn();
|
|||
// Store original Notification
|
||||
const originalNotification = global.Notification;
|
||||
|
||||
const MockNotificationConstructor = vi.fn();
|
||||
|
||||
function setupNotificationMock(permission: NotificationPermission = 'default') {
|
||||
const MockNotification = vi
|
||||
.fn()
|
||||
.mockImplementation((title: string, options?: NotificationOptions) => ({
|
||||
title,
|
||||
...options,
|
||||
}));
|
||||
class MockNotification extends EventTarget {
|
||||
constructor(
|
||||
public title: string,
|
||||
public options?: NotificationOptions,
|
||||
) {
|
||||
super();
|
||||
|
||||
Object.defineProperty(MockNotification, 'permission', {
|
||||
value: permission,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
MockNotificationConstructor(title, options);
|
||||
}
|
||||
|
||||
Object.defineProperty(MockNotification, 'requestPermission', {
|
||||
value: mockRequestPermission,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
static init = vi.fn();
|
||||
|
||||
Object.defineProperty(global, 'Notification', {
|
||||
value: MockNotification,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
static permission = permission;
|
||||
|
||||
static requestPermission = mockRequestPermission;
|
||||
}
|
||||
|
||||
vi.stubGlobal('Notification', MockNotification);
|
||||
}
|
||||
|
||||
describe('useBrowserNotifications', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
localStorage.clear();
|
||||
|
||||
// Reset Notification mock with default permission
|
||||
|
|
@ -287,7 +284,7 @@ describe('useBrowserNotifications', () => {
|
|||
const notification = showNotification('Test Title', { body: 'Test Body' });
|
||||
|
||||
expect(notification).not.toBeNull();
|
||||
expect(global.Notification).toHaveBeenCalledWith('Test Title', { body: 'Test Body' });
|
||||
expect(MockNotificationConstructor).toHaveBeenCalledWith('Test Title', { body: 'Test Body' });
|
||||
});
|
||||
|
||||
it('should return null when notifications are not enabled', () => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { useHistoryStore } from '@/app/stores/history.store';
|
|||
import { useNDVStore } from '@/features/ndv/shared/ndv.store';
|
||||
import {
|
||||
createTestNode,
|
||||
createTestNodeProperties,
|
||||
createTestWorkflow,
|
||||
createTestWorkflowObject,
|
||||
mockNode,
|
||||
|
|
@ -3821,12 +3822,12 @@ describe('useCanvasOperations', () => {
|
|||
name: type,
|
||||
version,
|
||||
properties: [
|
||||
{
|
||||
createTestNodeProperties({
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { vi, describe, it, expect, beforeEach, afterEach, type Mock } from 'vitest';
|
||||
import { useIntersectionObserver } from './useIntersectionObserver';
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface MockIntersectionObserverConstructor {
|
||||
__callback?: IntersectionObserverCallback;
|
||||
new (callback: IntersectionObserverCallback): IntersectionObserver;
|
||||
}
|
||||
|
||||
function createMockEntry(element: Element, isIntersecting: boolean): IntersectionObserverEntry {
|
||||
return {
|
||||
isIntersecting,
|
||||
|
|
@ -19,35 +14,43 @@ function createMockEntry(element: Element, isIntersecting: boolean): Intersectio
|
|||
};
|
||||
}
|
||||
|
||||
class MockIntersectionObserver extends IntersectionObserver {
|
||||
constructor(handler: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
||||
super(handler, options);
|
||||
|
||||
this.__callback = handler;
|
||||
MockIntersectionObserver._instance = this;
|
||||
MockIntersectionObserver.init(handler, options);
|
||||
}
|
||||
|
||||
static _instance: MockIntersectionObserver;
|
||||
|
||||
static getInstance() {
|
||||
return MockIntersectionObserver._instance;
|
||||
}
|
||||
|
||||
__callback: IntersectionObserverCallback;
|
||||
|
||||
static init = vi.fn();
|
||||
|
||||
observe = vi.fn();
|
||||
|
||||
disconnect = vi.fn();
|
||||
|
||||
unobserve = vi.fn();
|
||||
}
|
||||
|
||||
describe('useIntersectionObserver()', () => {
|
||||
let mockIntersectionObserver: {
|
||||
observe: ReturnType<typeof vi.fn>;
|
||||
disconnect: ReturnType<typeof vi.fn>;
|
||||
unobserve: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let mockCallback: ReturnType<typeof vi.fn>;
|
||||
let mockCallback: Mock;
|
||||
let mockRoot: Element;
|
||||
let mockConstructor: MockIntersectionObserverConstructor;
|
||||
let originalIntersectionObserver: typeof IntersectionObserver;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Cache original IntersectionObserver
|
||||
originalIntersectionObserver = global.IntersectionObserver;
|
||||
|
||||
// Mock IntersectionObserver
|
||||
mockIntersectionObserver = {
|
||||
observe: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
};
|
||||
|
||||
mockConstructor = vi.fn((callback) => {
|
||||
// Store callback for manual triggering
|
||||
mockConstructor.__callback = callback;
|
||||
return mockIntersectionObserver;
|
||||
}) as unknown as MockIntersectionObserverConstructor;
|
||||
|
||||
global.IntersectionObserver = mockConstructor as unknown as typeof IntersectionObserver;
|
||||
vi.stubGlobal('IntersectionObserver', MockIntersectionObserver);
|
||||
|
||||
mockCallback = vi.fn();
|
||||
mockRoot = document.createElement('div');
|
||||
|
|
@ -69,14 +72,14 @@ describe('useIntersectionObserver()', () => {
|
|||
const element = document.createElement('div');
|
||||
observe(element);
|
||||
|
||||
expect(global.IntersectionObserver).toHaveBeenCalledWith(
|
||||
expect(MockIntersectionObserver.init).toHaveBeenCalledWith(
|
||||
expect.any(Function),
|
||||
expect.objectContaining({
|
||||
root: mockRoot,
|
||||
threshold: 0.01,
|
||||
}),
|
||||
);
|
||||
expect(mockIntersectionObserver.observe).toHaveBeenCalledWith(element);
|
||||
expect(MockIntersectionObserver.getInstance().observe).toHaveBeenCalledWith(element);
|
||||
});
|
||||
|
||||
it('executes callback when element intersects', () => {
|
||||
|
|
@ -90,7 +93,7 @@ describe('useIntersectionObserver()', () => {
|
|||
observe(element);
|
||||
|
||||
// Simulate intersection
|
||||
const callback = mockConstructor.__callback;
|
||||
const callback = MockIntersectionObserver.getInstance().__callback;
|
||||
if (callback) {
|
||||
callback([createMockEntry(element, true)], {} as IntersectionObserver);
|
||||
}
|
||||
|
|
@ -109,7 +112,7 @@ describe('useIntersectionObserver()', () => {
|
|||
observe(element);
|
||||
|
||||
// Simulate no intersection
|
||||
const callback = mockConstructor.__callback;
|
||||
const callback = MockIntersectionObserver.getInstance().__callback;
|
||||
if (callback) {
|
||||
callback([createMockEntry(element, false)], {} as IntersectionObserver);
|
||||
}
|
||||
|
|
@ -128,12 +131,12 @@ describe('useIntersectionObserver()', () => {
|
|||
observe(element);
|
||||
|
||||
// Simulate intersection
|
||||
const callback = mockConstructor.__callback;
|
||||
const callback = MockIntersectionObserver.getInstance().__callback;
|
||||
if (callback) {
|
||||
callback([createMockEntry(element, true)], {} as IntersectionObserver);
|
||||
}
|
||||
|
||||
expect(mockIntersectionObserver.disconnect).toHaveBeenCalledTimes(1);
|
||||
expect(MockIntersectionObserver.getInstance().disconnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('continues observing when once is false', () => {
|
||||
|
|
@ -148,12 +151,12 @@ describe('useIntersectionObserver()', () => {
|
|||
observe(element);
|
||||
|
||||
// Simulate intersection
|
||||
const callback = mockConstructor.__callback;
|
||||
const callback = MockIntersectionObserver.getInstance().__callback;
|
||||
if (callback) {
|
||||
callback([createMockEntry(element, true)], {} as IntersectionObserver);
|
||||
}
|
||||
|
||||
expect(mockIntersectionObserver.disconnect).not.toHaveBeenCalled();
|
||||
expect(MockIntersectionObserver.getInstance().disconnect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses custom threshold when provided', () => {
|
||||
|
|
@ -168,7 +171,7 @@ describe('useIntersectionObserver()', () => {
|
|||
const element = document.createElement('div');
|
||||
observe(element);
|
||||
|
||||
expect(global.IntersectionObserver).toHaveBeenCalledWith(
|
||||
expect(MockIntersectionObserver.init).toHaveBeenCalledWith(
|
||||
expect.any(Function),
|
||||
expect.objectContaining({
|
||||
threshold: customThreshold,
|
||||
|
|
@ -187,7 +190,7 @@ describe('useIntersectionObserver()', () => {
|
|||
const element2 = document.createElement('div');
|
||||
|
||||
observe(element1);
|
||||
const firstObserver = mockIntersectionObserver.disconnect;
|
||||
const firstObserver = MockIntersectionObserver.getInstance().disconnect;
|
||||
|
||||
observe(element2);
|
||||
|
||||
|
|
@ -203,7 +206,7 @@ describe('useIntersectionObserver()', () => {
|
|||
|
||||
observe(null);
|
||||
|
||||
expect(global.IntersectionObserver).not.toHaveBeenCalled();
|
||||
expect(MockIntersectionObserver.init).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does nothing when observing undefined element', () => {
|
||||
|
|
@ -215,7 +218,7 @@ describe('useIntersectionObserver()', () => {
|
|||
|
||||
observe(undefined);
|
||||
|
||||
expect(global.IntersectionObserver).not.toHaveBeenCalled();
|
||||
expect(MockIntersectionObserver.init).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('exposes disconnect method for manual cleanup', () => {
|
||||
|
|
@ -230,7 +233,7 @@ describe('useIntersectionObserver()', () => {
|
|||
|
||||
disconnect();
|
||||
|
||||
expect(mockIntersectionObserver.disconnect).toHaveBeenCalledTimes(1);
|
||||
expect(MockIntersectionObserver.getInstance().disconnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('safely handles multiple disconnect calls', () => {
|
||||
|
|
@ -246,6 +249,6 @@ describe('useIntersectionObserver()', () => {
|
|||
disconnect();
|
||||
disconnect(); // Second call should not error
|
||||
|
||||
expect(mockIntersectionObserver.disconnect).toHaveBeenCalledTimes(1);
|
||||
expect(MockIntersectionObserver.getInstance().disconnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
/** Mocked EventSource class to help testing */
|
||||
export class MockEventSource extends EventTarget {
|
||||
constructor(public url: string) {
|
||||
constructor(
|
||||
public url: string = 'http://test.com',
|
||||
...args: unknown[]
|
||||
) {
|
||||
super();
|
||||
|
||||
MockEventSource._instance = this;
|
||||
MockEventSource.init(url, ...args);
|
||||
}
|
||||
|
||||
static init = vi.fn();
|
||||
|
||||
static _instance: MockEventSource;
|
||||
|
||||
static getInstance() {
|
||||
return MockEventSource._instance;
|
||||
}
|
||||
|
||||
simulateConnectionOpen() {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,24 @@
|
|||
import { WebSocketState } from '@/app/push-connection/useWebSocketClient';
|
||||
import { WebSocketState, type WebSocketStateType } from '@/app/push-connection/useWebSocketClient';
|
||||
|
||||
/** Mocked WebSocket class to help testing */
|
||||
export class MockWebSocket extends EventTarget {
|
||||
readyState: number = WebSocketState.CONNECTING;
|
||||
export class MockWebSocket extends WebSocket {
|
||||
readyState: WebSocketStateType = WebSocketState.CONNECTING;
|
||||
|
||||
constructor(public url: string) {
|
||||
super();
|
||||
constructor(url: string) {
|
||||
super(url);
|
||||
|
||||
MockWebSocket._instance = this;
|
||||
MockWebSocket.init(url);
|
||||
}
|
||||
|
||||
static _instance: MockWebSocket;
|
||||
|
||||
static getInstance() {
|
||||
return MockWebSocket._instance;
|
||||
}
|
||||
|
||||
static init = vi.fn();
|
||||
|
||||
simulateConnectionOpen() {
|
||||
this.dispatchEvent(new Event('open'));
|
||||
this.readyState = WebSocketState.OPEN;
|
||||
|
|
|
|||
|
|
@ -3,14 +3,8 @@ import { useEventSourceClient } from '../useEventSourceClient';
|
|||
import { MockEventSource } from './mockEventSource';
|
||||
|
||||
describe('useEventSourceClient', () => {
|
||||
let mockEventSource: MockEventSource;
|
||||
|
||||
beforeEach(() => {
|
||||
mockEventSource = new MockEventSource('http://test.com');
|
||||
|
||||
// @ts-expect-error - mock EventSource
|
||||
global.EventSource = vi.fn(() => mockEventSource);
|
||||
|
||||
vi.stubGlobal('EventSource', MockEventSource);
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
|
|
@ -26,7 +20,7 @@ describe('useEventSourceClient', () => {
|
|||
const { connect } = useEventSourceClient({ url, onMessage });
|
||||
connect();
|
||||
|
||||
expect(EventSource).toHaveBeenCalledWith(url, { withCredentials: true });
|
||||
expect(MockEventSource.init).toHaveBeenCalledWith(url, { withCredentials: true });
|
||||
});
|
||||
|
||||
test('should update connection status on successful connection', () => {
|
||||
|
|
@ -36,7 +30,7 @@ describe('useEventSourceClient', () => {
|
|||
});
|
||||
connect();
|
||||
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
|
||||
expect(isConnected.value).toBe(true);
|
||||
});
|
||||
|
|
@ -46,7 +40,7 @@ describe('useEventSourceClient', () => {
|
|||
const { connect } = useEventSourceClient({ url: 'http://test.com', onMessage });
|
||||
connect();
|
||||
|
||||
mockEventSource.simulateMessageEvent('test data');
|
||||
MockEventSource.getInstance().simulateMessageEvent('test data');
|
||||
|
||||
expect(onMessage).toHaveBeenCalledWith('test data');
|
||||
});
|
||||
|
|
@ -59,13 +53,13 @@ describe('useEventSourceClient', () => {
|
|||
connect();
|
||||
|
||||
// Simulate successful connection
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
expect(isConnected.value).toBe(true);
|
||||
|
||||
disconnect();
|
||||
|
||||
expect(isConnected.value).toBe(false);
|
||||
expect(mockEventSource.close).toHaveBeenCalled();
|
||||
expect(MockEventSource.getInstance().close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle connection loss', () => {
|
||||
|
|
@ -74,19 +68,19 @@ describe('useEventSourceClient', () => {
|
|||
onMessage: vi.fn(),
|
||||
});
|
||||
connect();
|
||||
expect(EventSource).toHaveBeenCalledTimes(1);
|
||||
expect(MockEventSource.init).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Simulate successful connection
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
expect(isConnected.value).toBe(true);
|
||||
|
||||
// Simulate connection loss
|
||||
mockEventSource.simulateConnectionClose();
|
||||
MockEventSource.getInstance().simulateConnectionClose();
|
||||
expect(isConnected.value).toBe(false);
|
||||
|
||||
// Advance timer to trigger reconnect
|
||||
vi.advanceTimersByTime(1_000);
|
||||
expect(EventSource).toHaveBeenCalledTimes(2);
|
||||
expect(MockEventSource.init).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('sendMessage should be a noop function', () => {
|
||||
|
|
@ -97,7 +91,7 @@ describe('useEventSourceClient', () => {
|
|||
connect();
|
||||
|
||||
// Simulate successful connection
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
|
||||
const message = 'test message';
|
||||
// Should not throw error and should do nothing
|
||||
|
|
@ -111,18 +105,18 @@ describe('useEventSourceClient', () => {
|
|||
});
|
||||
connect();
|
||||
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
mockEventSource.simulateConnectionClose();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionClose();
|
||||
|
||||
// First reconnection attempt after 1 second
|
||||
vi.advanceTimersByTime(1_000);
|
||||
expect(EventSource).toHaveBeenCalledTimes(2);
|
||||
expect(MockEventSource.init).toHaveBeenCalledTimes(2);
|
||||
|
||||
mockEventSource.simulateConnectionClose();
|
||||
MockEventSource.getInstance().simulateConnectionClose();
|
||||
|
||||
// Second reconnection attempt after 2 seconds
|
||||
vi.advanceTimersByTime(2_000);
|
||||
expect(EventSource).toHaveBeenCalledTimes(3);
|
||||
expect(MockEventSource.init).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('should reset connection attempts on successful connection', () => {
|
||||
|
|
@ -133,21 +127,21 @@ describe('useEventSourceClient', () => {
|
|||
connect();
|
||||
|
||||
// First connection attempt
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
mockEventSource.simulateConnectionClose();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionClose();
|
||||
|
||||
// First reconnection attempt
|
||||
vi.advanceTimersByTime(1_000);
|
||||
expect(EventSource).toHaveBeenCalledTimes(2);
|
||||
expect(MockEventSource.init).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Successful connection
|
||||
mockEventSource.simulateConnectionOpen();
|
||||
MockEventSource.getInstance().simulateConnectionOpen();
|
||||
|
||||
// Connection lost again
|
||||
mockEventSource.simulateConnectionClose();
|
||||
MockEventSource.getInstance().simulateConnectionClose();
|
||||
|
||||
// Should start with initial delay again
|
||||
vi.advanceTimersByTime(1_000);
|
||||
expect(EventSource).toHaveBeenCalledTimes(3);
|
||||
expect(MockEventSource.init).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,13 +3,8 @@ import { useWebSocketClient } from '../useWebSocketClient';
|
|||
import { MockWebSocket } from './mockWebSocketClient';
|
||||
|
||||
describe('useWebSocketClient', () => {
|
||||
let mockWebSocket: MockWebSocket;
|
||||
|
||||
beforeEach(() => {
|
||||
mockWebSocket = new MockWebSocket('ws://test.com');
|
||||
|
||||
// @ts-expect-error - mock WebSocket
|
||||
global.WebSocket = vi.fn(() => mockWebSocket);
|
||||
vi.stubGlobal('WebSocket', MockWebSocket);
|
||||
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
|
@ -26,7 +21,7 @@ describe('useWebSocketClient', () => {
|
|||
const { connect } = useWebSocketClient({ url, onMessage });
|
||||
connect();
|
||||
|
||||
expect(WebSocket).toHaveBeenCalledWith(url);
|
||||
expect(MockWebSocket.init).toHaveBeenCalledWith(url);
|
||||
});
|
||||
|
||||
test('should update connection status and start heartbeat on successful connection', () => {
|
||||
|
|
@ -36,13 +31,15 @@ describe('useWebSocketClient', () => {
|
|||
});
|
||||
connect();
|
||||
|
||||
mockWebSocket.simulateConnectionOpen();
|
||||
MockWebSocket.getInstance().simulateConnectionOpen();
|
||||
|
||||
expect(isConnected.value).toBe(true);
|
||||
|
||||
// Advance timer to trigger heartbeat
|
||||
vi.advanceTimersByTime(30_000);
|
||||
expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify({ type: 'heartbeat' }));
|
||||
expect(MockWebSocket.getInstance().send).toHaveBeenCalledWith(
|
||||
JSON.stringify({ type: 'heartbeat' }),
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle incoming messages', () => {
|
||||
|
|
@ -50,7 +47,7 @@ describe('useWebSocketClient', () => {
|
|||
const { connect } = useWebSocketClient({ url: 'ws://test.com', onMessage });
|
||||
connect();
|
||||
|
||||
mockWebSocket.simulateMessageEvent('test data');
|
||||
MockWebSocket.getInstance().simulateMessageEvent('test data');
|
||||
|
||||
expect(onMessage).toHaveBeenCalledWith('test data');
|
||||
});
|
||||
|
|
@ -63,14 +60,14 @@ describe('useWebSocketClient', () => {
|
|||
connect();
|
||||
|
||||
// Simulate successful connection
|
||||
mockWebSocket.simulateConnectionOpen();
|
||||
MockWebSocket.getInstance().simulateConnectionOpen();
|
||||
|
||||
expect(isConnected.value).toBe(true);
|
||||
|
||||
disconnect();
|
||||
|
||||
expect(isConnected.value).toBe(false);
|
||||
expect(mockWebSocket.close).toHaveBeenCalledWith(1000);
|
||||
expect(MockWebSocket.getInstance().close).toHaveBeenCalledWith(1000);
|
||||
});
|
||||
|
||||
test('should handle connection loss', () => {
|
||||
|
|
@ -79,20 +76,20 @@ describe('useWebSocketClient', () => {
|
|||
onMessage: vi.fn(),
|
||||
});
|
||||
connect();
|
||||
expect(WebSocket).toHaveBeenCalledTimes(1);
|
||||
expect(MockWebSocket.init).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Simulate successful connection
|
||||
mockWebSocket.simulateConnectionOpen();
|
||||
MockWebSocket.getInstance().simulateConnectionOpen();
|
||||
|
||||
expect(isConnected.value).toBe(true);
|
||||
|
||||
// Simulate connection loss
|
||||
mockWebSocket.simulateConnectionClose(1006);
|
||||
MockWebSocket.getInstance().simulateConnectionClose(1006);
|
||||
|
||||
expect(isConnected.value).toBe(false);
|
||||
// Advance timer to reconnect
|
||||
vi.advanceTimersByTime(1_000);
|
||||
expect(WebSocket).toHaveBeenCalledTimes(2);
|
||||
expect(MockWebSocket.init).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('should throw error when trying to send message while disconnected', () => {
|
||||
|
|
@ -103,23 +100,23 @@ describe('useWebSocketClient', () => {
|
|||
|
||||
test('should attempt reconnection with increasing delays', () => {
|
||||
const { connect } = useWebSocketClient({
|
||||
url: 'http://test.com',
|
||||
url: 'ws://test.com',
|
||||
onMessage: vi.fn(),
|
||||
});
|
||||
connect();
|
||||
|
||||
mockWebSocket.simulateConnectionOpen();
|
||||
mockWebSocket.simulateConnectionClose(1006);
|
||||
MockWebSocket.getInstance().simulateConnectionOpen();
|
||||
MockWebSocket.getInstance().simulateConnectionClose(1006);
|
||||
|
||||
// First reconnection attempt after 1 second
|
||||
vi.advanceTimersByTime(1_000);
|
||||
expect(WebSocket).toHaveBeenCalledTimes(2);
|
||||
expect(MockWebSocket.init).toHaveBeenCalledTimes(2);
|
||||
|
||||
mockWebSocket.simulateConnectionClose(1006);
|
||||
MockWebSocket.getInstance().simulateConnectionClose(1006);
|
||||
|
||||
// Second reconnection attempt after 2 seconds
|
||||
vi.advanceTimersByTime(2_000);
|
||||
expect(WebSocket).toHaveBeenCalledTimes(3);
|
||||
expect(MockWebSocket.init).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('should send message when connected', () => {
|
||||
|
|
@ -130,11 +127,11 @@ describe('useWebSocketClient', () => {
|
|||
connect();
|
||||
|
||||
// Simulate successful connection
|
||||
mockWebSocket.simulateConnectionOpen();
|
||||
MockWebSocket.getInstance().simulateConnectionOpen();
|
||||
|
||||
const message = 'test message';
|
||||
sendMessage(message);
|
||||
|
||||
expect(mockWebSocket.send).toHaveBeenCalledWith(message);
|
||||
expect(MockWebSocket.getInstance().send).toHaveBeenCalledWith(message);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ export const WebSocketState = {
|
|||
OPEN: 1,
|
||||
CLOSING: 2,
|
||||
CLOSED: 3,
|
||||
};
|
||||
} as const;
|
||||
|
||||
export type WebSocketStateType = 0 | 1 | 2 | 3;
|
||||
|
||||
/**
|
||||
* Creates a WebSocket connection to the server. Uses reconnection logic
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ describe('useNpsSurvey', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
setActivePinia(createPinia());
|
||||
useSettingsStore().settings.telemetry = { enabled: true };
|
||||
npsSurveyStore = useNpsSurveyStore();
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const nodeTypeHttpRequest = mock<INodeTypeDescription>({
|
|||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
displayOptions: undefined,
|
||||
options: [
|
||||
{ name: 'Basic Auth', value: 'basicAuth' },
|
||||
{ name: 'Digest Auth', value: 'digestAuth' },
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ import * as apiUtils from '@n8n/rest-api-client';
|
|||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type { ChatRequest } from '@/features/ai/assistant/assistant.types';
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest';
|
||||
import type { MockInstance } from 'vitest';
|
||||
import type { Mock, MockInstance } from 'vitest';
|
||||
|
||||
vi.mock('@n8n/rest-api-client');
|
||||
|
||||
describe('API: ai', () => {
|
||||
describe('chatWithBuilder', () => {
|
||||
let mockContext: IRestApiContext;
|
||||
let mockOnMessageUpdated: ReturnType<typeof vi.fn>;
|
||||
let mockOnDone: ReturnType<typeof vi.fn>;
|
||||
let mockOnError: ReturnType<typeof vi.fn>;
|
||||
let mockOnMessageUpdated: Mock;
|
||||
let mockOnDone: Mock;
|
||||
let mockOnError: Mock;
|
||||
let streamRequestSpy: MockInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -306,9 +306,9 @@ describe('API: ai', () => {
|
|||
|
||||
describe('chatWithAssistant', () => {
|
||||
let mockContext: IRestApiContext;
|
||||
let mockOnMessageUpdated: ReturnType<typeof vi.fn>;
|
||||
let mockOnDone: ReturnType<typeof vi.fn>;
|
||||
let mockOnError: ReturnType<typeof vi.fn>;
|
||||
let mockOnMessageUpdated: Mock;
|
||||
let mockOnDone: Mock;
|
||||
let mockOnError: Mock;
|
||||
let streamRequestSpy: MockInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, vi, type Mock, type MockInstance } from 'vitest';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { useBuilderStore } from './builder.store';
|
||||
|
|
@ -121,9 +121,9 @@ let nodeTypesStore: ReturnType<typeof mockedStore<typeof useNodeTypesStore>>;
|
|||
let credentialsStore: ReturnType<typeof mockedStore<typeof useCredentialsStore>>;
|
||||
let pinia: ReturnType<typeof createTestingPinia>;
|
||||
|
||||
let setWorkflowNameSpy: ReturnType<typeof vi.fn>;
|
||||
let getNodeTypeSpy: ReturnType<typeof vi.fn>;
|
||||
let getCredentialsByTypeSpy: ReturnType<typeof vi.fn>;
|
||||
let setWorkflowNameSpy: Mock;
|
||||
let getNodeTypeSpy: Mock;
|
||||
let getCredentialsByTypeSpy: Mock;
|
||||
|
||||
const apiSpy = vi.spyOn(chatAPI, 'chatWithBuilder');
|
||||
|
||||
|
|
@ -152,7 +152,6 @@ vi.mock('vue-router', () => ({
|
|||
let workflowState: WorkflowState;
|
||||
describe('AI Builder store', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDocumentState = undefined;
|
||||
pinia = createTestingPinia({ stubActions: false });
|
||||
setActivePinia(pinia);
|
||||
|
|
@ -2088,10 +2087,13 @@ describe('AI Builder store', () => {
|
|||
});
|
||||
|
||||
describe('Version management and revert functionality', () => {
|
||||
const mockGetAiSessions = vi.spyOn(chatAPI, 'getAiSessions');
|
||||
const mockTruncateBuilderMessages = vi.spyOn(chatAPI, 'truncateBuilderMessages');
|
||||
let mockGetAiSessions: MockInstance<typeof chatAPI.getAiSessions>;
|
||||
let mockTruncateBuilderMessages: MockInstance<typeof chatAPI.truncateBuilderMessages>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetAiSessions = vi.spyOn(chatAPI, 'getAiSessions');
|
||||
mockTruncateBuilderMessages = vi.spyOn(chatAPI, 'truncateBuilderMessages');
|
||||
|
||||
mockGetAiSessions.mockReset();
|
||||
mockTruncateBuilderMessages.mockReset();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
|
||||
import { render, fireEvent, waitFor } from '@testing-library/vue';
|
||||
import ChatButtons from './ChatButtons.vue';
|
||||
import type { ChatHubMessageButton } from '@n8n/api-types';
|
||||
|
|
@ -9,7 +9,7 @@ describe('ChatButtons', () => {
|
|||
{ text: 'Reject', link: 'https://example.com/reject', type: 'secondary' },
|
||||
];
|
||||
|
||||
let fetchMock: ReturnType<typeof vi.fn>;
|
||||
let fetchMock: Mock;
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { setActivePinia } from 'pinia';
|
||||
|
|
@ -30,8 +30,8 @@ describe('useChatInputFocus', () => {
|
|||
let mockInputRef: ReturnType<
|
||||
typeof ref<{ focus: () => void; appendText: (text: string) => void } | null>
|
||||
>;
|
||||
let focusSpy: ReturnType<typeof vi.fn>;
|
||||
let appendTextSpy: ReturnType<typeof vi.fn>;
|
||||
let focusSpy: Mock;
|
||||
let appendTextSpy: Mock;
|
||||
|
||||
function createKeyboardEvent(key: string, options: Partial<KeyboardEvent> = {}): KeyboardEvent {
|
||||
return new KeyboardEvent('keydown', {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe('evaluation.store.ee', () => {
|
|||
let rootStoreMock: ReturnType<typeof useRootStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
setActivePinia(createPinia());
|
||||
store = useEvaluationStore();
|
||||
rootStoreMock = useRootStore();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, test, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import { ref, computed, nextTick, type Ref } from 'vue';
|
||||
import type { PushMessage } from '@n8n/api-types';
|
||||
import { useEventRelay } from '../useEventRelay';
|
||||
|
|
@ -49,7 +49,7 @@ function createExecState(
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('useEventRelay', () => {
|
||||
let relay: ReturnType<typeof vi.fn>;
|
||||
let relay: Mock;
|
||||
let workflowExecutions: Ref<Map<string, WorkflowExecutionState>>;
|
||||
let activeWorkflowId: Ref<string | null>;
|
||||
let bufferedEventsStore: Map<string, PushMessage[]>;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { MCP_CONNECT_WORKFLOWS_MODAL_KEY } from '@/features/ai/mcpAccess/mcp.con
|
|||
import { useMCPStore } from '@/features/ai/mcpAccess/mcp.store';
|
||||
import { createWorkflow } from '@/features/ai/mcpAccess/mcp.test.utils';
|
||||
import { useTelemetry } from '@/app/composables/useTelemetry';
|
||||
import { type Mock } from 'vitest';
|
||||
|
||||
vi.mock('@/app/composables/useTelemetry', () => {
|
||||
const track = vi.fn();
|
||||
|
|
@ -65,7 +66,7 @@ const telemetry = useTelemetry();
|
|||
|
||||
let pinia: ReturnType<typeof createTestingPinia>;
|
||||
let mcpStore: MockedStore<typeof useMCPStore>;
|
||||
let mockOnEnableMcpAccess: ReturnType<typeof vi.fn>;
|
||||
let mockOnEnableMcpAccess: Mock;
|
||||
|
||||
describe('MCPConnectWorkflowsModal', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ describe('ConfirmPasswordModal', () => {
|
|||
let pinia: ReturnType<typeof createPinia>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
pinia = createTestingPinia({ initialState });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ describe('folders.store', () => {
|
|||
let rootStore: ReturnType<typeof useRootStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
setActivePinia(createPinia());
|
||||
rootStore = useRootStore();
|
||||
foldersStore = useFoldersStore();
|
||||
|
|
|
|||
|
|
@ -306,28 +306,53 @@ describe('useCredentialOAuth', () => {
|
|||
};
|
||||
|
||||
let mockPopup: { closed: boolean; close: ReturnType<typeof vi.fn> };
|
||||
let mockBroadcastChannel: {
|
||||
close: ReturnType<typeof vi.fn>;
|
||||
addEventListener: ReturnType<typeof vi.fn>;
|
||||
postMessage: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
class MockBroadcastChannel {
|
||||
static failOauth = false;
|
||||
|
||||
static noopEventListener = false;
|
||||
|
||||
static closeCalled = false;
|
||||
|
||||
static __reset() {
|
||||
MockBroadcastChannel.closeCalled = false;
|
||||
MockBroadcastChannel.noopEventListener = false;
|
||||
MockBroadcastChannel.failOauth = false;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
MockBroadcastChannel.closeCalled = true;
|
||||
};
|
||||
|
||||
addEventListener = (event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (MockBroadcastChannel.noopEventListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (MockBroadcastChannel.failOauth) {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'error' } as MessageEvent), 0);
|
||||
}
|
||||
} else {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'success' } as MessageEvent), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
removeEventListener = vi.fn();
|
||||
|
||||
postMessage = vi.fn();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockPopup = { closed: false, close: vi.fn() };
|
||||
mockBroadcastChannel = {
|
||||
close: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
postMessage: vi.fn(),
|
||||
};
|
||||
|
||||
vi.stubGlobal(
|
||||
'BroadcastChannel',
|
||||
vi.fn().mockImplementation(() => mockBroadcastChannel),
|
||||
);
|
||||
vi.stubGlobal('BroadcastChannel', MockBroadcastChannel);
|
||||
vi.stubGlobal('open', vi.fn().mockReturnValue(mockPopup));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
MockBroadcastChannel.__reset();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
|
|
@ -335,15 +360,6 @@ describe('useCredentialOAuth', () => {
|
|||
const credentialsStore = mockedStore(useCredentialsStore);
|
||||
credentialsStore.oAuth2Authorize.mockResolvedValue('https://oauth.example.com/auth');
|
||||
|
||||
// Make the BroadcastChannel fire success immediately
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(
|
||||
(event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'success' } as MessageEvent), 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const { authorize } = useCredentialOAuth();
|
||||
const result = await authorize(mockCredential);
|
||||
|
||||
|
|
@ -359,14 +375,6 @@ describe('useCredentialOAuth', () => {
|
|||
};
|
||||
credentialsStore.oAuth1Authorize.mockResolvedValue('https://oauth1.example.com/auth');
|
||||
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(
|
||||
(event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'success' } as MessageEvent), 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const { authorize } = useCredentialOAuth();
|
||||
const result = await authorize(oauth1Credential);
|
||||
|
||||
|
|
@ -411,13 +419,7 @@ describe('useCredentialOAuth', () => {
|
|||
const credentialsStore = mockedStore(useCredentialsStore);
|
||||
credentialsStore.oAuth2Authorize.mockResolvedValue('https://oauth.example.com/auth');
|
||||
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(
|
||||
(event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'error' } as MessageEvent), 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
MockBroadcastChannel.failOauth = true;
|
||||
|
||||
const { authorize } = useCredentialOAuth();
|
||||
const result = await authorize(mockCredential);
|
||||
|
|
@ -430,18 +432,10 @@ describe('useCredentialOAuth', () => {
|
|||
const credentialsStore = mockedStore(useCredentialsStore);
|
||||
credentialsStore.oAuth2Authorize.mockResolvedValue('https://oauth.example.com/auth');
|
||||
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(
|
||||
(event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'success' } as MessageEvent), 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const { authorize } = useCredentialOAuth();
|
||||
await authorize(mockCredential);
|
||||
|
||||
expect(mockBroadcastChannel.close).toHaveBeenCalled();
|
||||
expect(MockBroadcastChannel.closeCalled).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when aborted via signal', async () => {
|
||||
|
|
@ -449,9 +443,7 @@ describe('useCredentialOAuth', () => {
|
|||
credentialsStore.oAuth2Authorize.mockResolvedValue('https://oauth.example.com/auth');
|
||||
|
||||
// Don't fire any BroadcastChannel message - instead simulate abort
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(() => {
|
||||
// no-op: message handler never fires
|
||||
});
|
||||
MockBroadcastChannel.noopEventListener = true;
|
||||
|
||||
const originalAddEventListener = AbortSignal.prototype.addEventListener;
|
||||
vi.spyOn(AbortSignal.prototype, 'addEventListener').mockImplementation(function (
|
||||
|
|
@ -487,27 +479,33 @@ describe('useCredentialOAuth', () => {
|
|||
};
|
||||
|
||||
let mockPopup: { closed: boolean; close: ReturnType<typeof vi.fn> };
|
||||
let mockBroadcastChannel: {
|
||||
close: ReturnType<typeof vi.fn>;
|
||||
addEventListener: ReturnType<typeof vi.fn>;
|
||||
removeEventListener: ReturnType<typeof vi.fn>;
|
||||
postMessage: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
class MockBroadcastChannel {
|
||||
static failOauth = false;
|
||||
|
||||
close = vi.fn();
|
||||
|
||||
addEventListener = (event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (MockBroadcastChannel.failOauth) {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'error' } as MessageEvent), 0);
|
||||
}
|
||||
} else {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'success' } as MessageEvent), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
removeEventListener = vi.fn();
|
||||
|
||||
postMessage = vi.fn();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockTrack.mockClear();
|
||||
mockPopup = { closed: false, close: vi.fn() };
|
||||
mockBroadcastChannel = {
|
||||
close: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
postMessage: vi.fn(),
|
||||
};
|
||||
|
||||
vi.stubGlobal(
|
||||
'BroadcastChannel',
|
||||
vi.fn().mockImplementation(() => mockBroadcastChannel),
|
||||
);
|
||||
vi.stubGlobal('BroadcastChannel', MockBroadcastChannel);
|
||||
vi.stubGlobal('open', vi.fn().mockReturnValue(mockPopup));
|
||||
});
|
||||
|
||||
|
|
@ -520,14 +518,7 @@ describe('useCredentialOAuth', () => {
|
|||
credentialsStore.createNewCredential.mockResolvedValue(createdCredential);
|
||||
credentialsStore.oAuth2Authorize.mockResolvedValue('https://oauth.example.com/auth');
|
||||
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(
|
||||
(event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'success' } as MessageEvent), 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
MockBroadcastChannel.failOauth = false;
|
||||
return credentialsStore;
|
||||
}
|
||||
|
||||
|
|
@ -536,13 +527,7 @@ describe('useCredentialOAuth', () => {
|
|||
credentialsStore.createNewCredential.mockResolvedValue(createdCredential);
|
||||
credentialsStore.oAuth2Authorize.mockResolvedValue('https://oauth.example.com/auth');
|
||||
|
||||
mockBroadcastChannel.addEventListener.mockImplementation(
|
||||
(event: string, handler: (e: MessageEvent) => void) => {
|
||||
if (event === 'message') {
|
||||
setTimeout(() => handler({ data: 'error' } as MessageEvent), 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
MockBroadcastChannel.failOauth = true;
|
||||
|
||||
return credentialsStore;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,66 +138,62 @@ describe('GlobalExecutionsList', () => {
|
|||
expect(getByTestId('execution-list-empty')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it(
|
||||
'should handle selection flow when loading more items',
|
||||
async () => {
|
||||
const { getByTestId, getAllByTestId, queryByTestId, rerender } = renderComponent({
|
||||
props: {
|
||||
executions: executionsData[0].results as ExecutionSummaryWithScopes[],
|
||||
total: executionsData[0].count,
|
||||
filters: {} as ExecutionFilterType,
|
||||
estimated: false,
|
||||
},
|
||||
});
|
||||
await waitAllPromises();
|
||||
test('should handle selection flow when loading more items', async () => {
|
||||
const { getByTestId, getAllByTestId, queryByTestId, rerender } = renderComponent({
|
||||
props: {
|
||||
executions: executionsData[0].results as ExecutionSummaryWithScopes[],
|
||||
total: executionsData[0].count,
|
||||
filters: {} as ExecutionFilterType,
|
||||
estimated: false,
|
||||
},
|
||||
});
|
||||
await waitAllPromises();
|
||||
|
||||
await userEvent.click(getByTestId('select-visible-executions-checkbox'));
|
||||
await userEvent.click(getByTestId('select-visible-executions-checkbox'));
|
||||
|
||||
await retry(() => {
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(10);
|
||||
});
|
||||
expect(getByTestId('select-all-executions-checkbox')).toBeInTheDocument();
|
||||
expect(getByTestId('selected-items-info').textContent).toContain(10);
|
||||
|
||||
await userEvent.click(getByTestId('load-more-button'));
|
||||
await rerender({
|
||||
executions: executionsData[0].results.concat(executionsData[1].results),
|
||||
filteredExecutions: executionsData[0].results.concat(executionsData[1].results),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(getAllByTestId('select-execution-checkbox').length).toBe(20));
|
||||
await retry(() => {
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(10);
|
||||
});
|
||||
expect(getByTestId('select-all-executions-checkbox')).toBeInTheDocument();
|
||||
expect(getByTestId('selected-items-info').textContent).toContain(10);
|
||||
|
||||
await userEvent.click(getByTestId('select-all-executions-checkbox'));
|
||||
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(20);
|
||||
expect(getByTestId('selected-items-info').textContent).toContain(20);
|
||||
await userEvent.click(getByTestId('load-more-button'));
|
||||
await rerender({
|
||||
executions: executionsData[0].results.concat(executionsData[1].results),
|
||||
filteredExecutions: executionsData[0].results.concat(executionsData[1].results),
|
||||
});
|
||||
|
||||
await userEvent.click(getAllByTestId('select-execution-checkbox')[2]);
|
||||
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(19);
|
||||
expect(getByTestId('selected-items-info').textContent).toContain(19);
|
||||
expect(getByTestId('select-visible-executions-checkbox')).toBeInTheDocument();
|
||||
expect(queryByTestId('select-all-executions-checkbox')).not.toBeInTheDocument();
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
await waitFor(() => expect(getAllByTestId('select-execution-checkbox').length).toBe(20));
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(10);
|
||||
|
||||
await userEvent.click(getByTestId('select-all-executions-checkbox'));
|
||||
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(20);
|
||||
expect(getByTestId('selected-items-info').textContent).toContain(20);
|
||||
|
||||
await userEvent.click(getAllByTestId('select-execution-checkbox')[2]);
|
||||
expect(getAllByTestId('select-execution-checkbox').length).toBe(20);
|
||||
expect(
|
||||
getAllByTestId('select-execution-checkbox').filter(
|
||||
(el) => el.getAttribute('data-state') === 'checked',
|
||||
).length,
|
||||
).toBe(19);
|
||||
expect(getByTestId('selected-items-info').textContent).toContain(19);
|
||||
expect(getByTestId('select-visible-executions-checkbox')).toBeInTheDocument();
|
||||
expect(queryByTestId('select-all-executions-checkbox')).not.toBeInTheDocument();
|
||||
}, 10000);
|
||||
|
||||
it('should show "retry" data when appropriate', async () => {
|
||||
const retryOf = executionsData[0].results.filter((execution) => execution.retryOf);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import { nextTick } from 'vue';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
|
|
@ -62,7 +62,7 @@ describe('useChatState', () => {
|
|||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||
let logsStore: ReturnType<typeof useLogsStore>;
|
||||
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
||||
let mockRunWorkflow: ReturnType<typeof vi.fn>;
|
||||
let mockRunWorkflow: Mock;
|
||||
|
||||
// Mock node type that mirrors the real ChatTrigger structure:
|
||||
// - Multiple 'options' collections with displayOptions at the collection level
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type { INodeUi } from '@/Interface';
|
|||
import type { INodeTypeDescription, WorkflowParameters } from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, Workflow } from 'n8n-workflow';
|
||||
import { nextTick } from 'vue';
|
||||
import { type Mock } from 'vitest';
|
||||
|
||||
const nodeType: INodeTypeDescription = {
|
||||
displayName: 'OpenAI',
|
||||
|
|
@ -59,7 +60,7 @@ const workflow: WorkflowParameters = {
|
|||
|
||||
const getNodeType = vi.fn();
|
||||
let mockWorkflowData = workflow;
|
||||
let mockGetNodeByName = vi.fn(() => node);
|
||||
let mockGetNodeByName: Mock<(name: string) => INodeUi | null> = vi.fn(() => node);
|
||||
|
||||
vi.mock('@/app/stores/nodeTypes.store', () => ({
|
||||
useNodeTypesStore: vi.fn(() => ({
|
||||
|
|
|
|||
|
|
@ -44,8 +44,9 @@ vi.mock('@n8n/i18n', async (importOriginal) => ({
|
|||
baseText: (key: string) => key,
|
||||
}),
|
||||
}));
|
||||
const { saveAsMock } = vi.hoisted(() => ({ saveAsMock: vi.fn() }));
|
||||
vi.mock('file-saver', () => ({
|
||||
saveAs: vi.fn(),
|
||||
saveAs: saveAsMock,
|
||||
}));
|
||||
const mockTelemetryTrack = vi.fn();
|
||||
vi.mock('@/app/composables/useTelemetry', () => ({
|
||||
|
|
@ -79,6 +80,7 @@ describe('useWorkflowCommands', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }));
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockWorkflow = ref(
|
||||
createTestWorkflow({
|
||||
|
|
@ -357,8 +359,6 @@ describe('useWorkflowCommands', () => {
|
|||
|
||||
describe('export commands', () => {
|
||||
it('should handle download workflow', async () => {
|
||||
const { saveAs } = await import('file-saver');
|
||||
|
||||
const { commands } = useWorkflowCommands();
|
||||
const downloadCommand = commands.value.find((cmd) => cmd.id === 'download-workflow');
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ describe('useWorkflowCommands', () => {
|
|||
expect(mockTelemetryTrack).toHaveBeenCalledWith('User exported workflow', {
|
||||
workflow_id: 'workflow-123',
|
||||
});
|
||||
expect(saveAs).toHaveBeenCalled();
|
||||
expect(saveAsMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import uniqBy from 'lodash/uniqBy';
|
|||
beforeEach(async () => {
|
||||
setActivePinia(createTestingPinia());
|
||||
|
||||
vi.restoreAllMocks();
|
||||
vi.spyOn(utils, 'receivesNoBinaryData').mockResolvedValue(true); // hide $binary
|
||||
vi.spyOn(utils, 'isSplitInBatchesAbsent').mockReturnValue(false); // show context
|
||||
vi.spyOn(utils, 'hasActiveNode').mockReturnValue(true);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,40 @@ let externalSecretsStore: ReturnType<typeof useExternalSecretsStore>;
|
|||
let uiStore: ReturnType<typeof useUIStore>;
|
||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||
|
||||
export async function completions(docWithCursor: string, explicit = false) {
|
||||
const cursorPosition = docWithCursor.indexOf('|');
|
||||
|
||||
const doc = docWithCursor.slice(0, cursorPosition) + docWithCursor.slice(cursorPosition + 1);
|
||||
|
||||
const state = EditorState.create({
|
||||
doc,
|
||||
selection: { anchor: cursorPosition },
|
||||
extensions: [n8nLang()],
|
||||
});
|
||||
|
||||
const context = new CompletionContext(state, cursorPosition, explicit);
|
||||
|
||||
for (const completionSource of state.languageDataAt<CompletionSource>(
|
||||
'autocomplete',
|
||||
cursorPosition,
|
||||
)) {
|
||||
const result = await completionSource(context);
|
||||
|
||||
if (isCompletionResult(result)) return result.options;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isCompletionResult(candidate: unknown): candidate is CompletionResult {
|
||||
return (
|
||||
candidate !== null &&
|
||||
typeof candidate === 'object' &&
|
||||
'from' in candidate &&
|
||||
'options' in candidate
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
setActivePinia(createTestingPinia());
|
||||
|
||||
|
|
@ -44,6 +78,7 @@ beforeEach(async () => {
|
|||
uiStore = useUIStore();
|
||||
settingsStore = useSettingsStore();
|
||||
|
||||
vi.restoreAllMocks();
|
||||
vi.spyOn(utils, 'receivesNoBinaryData').mockResolvedValue(true); // hide $binary
|
||||
vi.spyOn(utils, 'isSplitInBatchesAbsent').mockReturnValue(false); // show context
|
||||
vi.spyOn(utils, 'hasActiveNode').mockReturnValue(true);
|
||||
|
|
@ -255,9 +290,7 @@ describe('Resolution-based completions', () => {
|
|||
});
|
||||
|
||||
test('should return completions when node reference is used as a function parameter', async () => {
|
||||
const initialState = { workflows: { workflow: { nodes: mockNodes } } };
|
||||
|
||||
setActivePinia(createTestingPinia({ initialState }));
|
||||
vi.spyOn(utils, 'autocompletableNodeNames').mockReturnValue(mockNodes.map((n) => n.name));
|
||||
|
||||
expect(await completions('{{ new Date($(|) }}')).toHaveLength(mockNodes.length);
|
||||
});
|
||||
|
|
@ -848,37 +881,3 @@ describe('Resolution-based completions', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
export async function completions(docWithCursor: string, explicit = false) {
|
||||
const cursorPosition = docWithCursor.indexOf('|');
|
||||
|
||||
const doc = docWithCursor.slice(0, cursorPosition) + docWithCursor.slice(cursorPosition + 1);
|
||||
|
||||
const state = EditorState.create({
|
||||
doc,
|
||||
selection: { anchor: cursorPosition },
|
||||
extensions: [n8nLang()],
|
||||
});
|
||||
|
||||
const context = new CompletionContext(state, cursorPosition, explicit);
|
||||
|
||||
for (const completionSource of state.languageDataAt<CompletionSource>(
|
||||
'autocomplete',
|
||||
cursorPosition,
|
||||
)) {
|
||||
const result = await completionSource(context);
|
||||
|
||||
if (isCompletionResult(result)) return result.options;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isCompletionResult(candidate: unknown): candidate is CompletionResult {
|
||||
return (
|
||||
candidate !== null &&
|
||||
typeof candidate === 'object' &&
|
||||
'from' in candidate &&
|
||||
'options' in candidate
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import type { WorkerInitOptions } from '../types';
|
|||
import { worker } from './typescript.worker';
|
||||
|
||||
vi.mock('@typescript/vfs');
|
||||
vi.mock('typescript');
|
||||
vi.mock('typescript', async (importOriginal) => {
|
||||
return await importOriginal();
|
||||
});
|
||||
|
||||
async function createWorker({
|
||||
doc,
|
||||
|
|
|
|||
|
|
@ -10,11 +10,21 @@ describe(useViewportAutoAdjust, () => {
|
|||
it('should set viewport when canvas is resized', async () => {
|
||||
let resizeHandler: ResizeObserverCallback = () => {};
|
||||
|
||||
vi.spyOn(window, 'ResizeObserver').mockImplementation((handler) => {
|
||||
resizeHandler = handler;
|
||||
class ResizeObserverMock extends ResizeObserver {
|
||||
constructor(handler: ResizeObserverCallback) {
|
||||
super(handler);
|
||||
resizeHandler = handler;
|
||||
}
|
||||
|
||||
observe = vi.fn();
|
||||
|
||||
disconnect = vi.fn();
|
||||
|
||||
unobserve = vi.fn();
|
||||
}
|
||||
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
||||
|
||||
return { observe() {}, disconnect() {}, unobserve() {} } as ResizeObserver;
|
||||
});
|
||||
const container = document.createElement('div');
|
||||
|
||||
Object.defineProperty(container, 'offsetWidth', {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@ const unnamedWorkflowHistoryDataFactory = (): WorkflowHistory => ({
|
|||
|
||||
vi.stubGlobal(
|
||||
'IntersectionObserver',
|
||||
vi.fn(() => ({
|
||||
disconnect: vi.fn(),
|
||||
observe: vi.fn(),
|
||||
takeRecords: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
})),
|
||||
class {
|
||||
disconnect = vi.fn();
|
||||
|
||||
observe = vi.fn();
|
||||
|
||||
takeRecords = vi.fn();
|
||||
|
||||
unobserve = vi.fn();
|
||||
},
|
||||
);
|
||||
|
||||
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import vue from '@vitejs/plugin-vue';
|
||||
import { posix as pathPosix, resolve, sep as pathSep } from 'path';
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig, mergeConfig, type UserConfig } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
|
|
@ -92,7 +92,7 @@ const plugins: UserConfig['plugins'] = [
|
|||
nodePopularityPlugin(),
|
||||
icons({
|
||||
compiler: 'vue3',
|
||||
autoInstall: true,
|
||||
autoInstall: NODE_ENV === 'development',
|
||||
}),
|
||||
// Add istanbul coverage plugin for E2E tests
|
||||
...(process.env.BUILD_WITH_COVERAGE === 'true'
|
||||
|
|
@ -109,20 +109,20 @@ const plugins: UserConfig['plugins'] = [
|
|||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: pathPosix.resolve('node_modules/web-tree-sitter/tree-sitter.wasm'),
|
||||
dest: resolve(__dirname, 'dist'),
|
||||
src: 'node_modules/web-tree-sitter/tree-sitter.wasm',
|
||||
dest: 'dist',
|
||||
},
|
||||
{
|
||||
src: pathPosix.resolve('node_modules/curlconverter/dist/tree-sitter-bash.wasm'),
|
||||
dest: resolve(__dirname, 'dist'),
|
||||
src: 'node_modules/curlconverter/dist/tree-sitter-bash.wasm',
|
||||
dest: 'dist',
|
||||
},
|
||||
// wa-sqlite WASM files for OPFS database support (no cross-origin isolation needed)
|
||||
{
|
||||
src: pathPosix.resolve('node_modules/wa-sqlite/dist/wa-sqlite.wasm'),
|
||||
src: 'node_modules/wa-sqlite/dist/wa-sqlite.wasm',
|
||||
dest: 'assets',
|
||||
},
|
||||
{
|
||||
src: pathPosix.resolve('node_modules/wa-sqlite/dist/wa-sqlite-async.wasm'),
|
||||
src: 'node_modules/wa-sqlite/dist/wa-sqlite-async.wasm',
|
||||
dest: 'assets',
|
||||
},
|
||||
],
|
||||
|
|
@ -231,6 +231,7 @@ export default mergeConfig(
|
|||
base: publicPath,
|
||||
envPrefix: ['VUE', 'N8N_ENV_FEAT'],
|
||||
css: {
|
||||
preprocessorMaxWorkers: true,
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: [
|
||||
|
|
@ -248,9 +249,7 @@ export default mergeConfig(
|
|||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['wa-sqlite'],
|
||||
esbuildOptions: {
|
||||
target,
|
||||
},
|
||||
rolldownOptions: {},
|
||||
},
|
||||
worker: {
|
||||
format: 'es',
|
||||
|
|
|
|||
|
|
@ -78,9 +78,6 @@ my operation 20,000 0.04 0.20 0.05 0.10 ±0.5% 10000
|
|||
|------|------------------|----------------|
|
||||
| Expression Engine | `={{ }}` evaluation speed | Runs for every node parameter |
|
||||
|
||||
## Notes
|
||||
|
||||
This package pins `vitest@^3.2.0` independently from the monorepo catalog (`^3.1.3`) because CodSpeed requires vitest 3.2+.
|
||||
|
||||
## Tips
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
"bench:compare": "vitest bench --run --outputJson ./profiles/benchmark-results.json && node scripts/check-regression.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codspeed/vitest-plugin": "^4.0.0",
|
||||
"@codspeed/vitest-plugin": "^5.2.0",
|
||||
"@n8n/expression-runtime": "workspace:*",
|
||||
"vitest": "^3.2.0",
|
||||
"vitest": "catalog:",
|
||||
"n8n-workflow": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ import {
|
|||
import { randomInt } from '../src/utils';
|
||||
import { DEFAULT_EVALUATION_METRIC } from '../src/evaluation-helpers';
|
||||
|
||||
vi.mock('../src/node-helpers', async () => {
|
||||
const actual = await vi.importActual<typeof import('../src/node-helpers')>('../src/node-helpers');
|
||||
return {
|
||||
...actual,
|
||||
getNodeParameters: vi.fn().mockImplementation(actual.getNodeParameters),
|
||||
};
|
||||
});
|
||||
|
||||
describe('getDomainBase should return protocol plus domain', () => {
|
||||
test('in valid URLs', () => {
|
||||
for (const url of validUrls(numericId)) {
|
||||
|
|
@ -1345,9 +1353,9 @@ describe('generateNodesGraph', () => {
|
|||
});
|
||||
|
||||
test('should not fail on error to resolve a node parameter for sticky node type', () => {
|
||||
const workflow = mock<IWorkflowBase>({ nodes: [{ type: STICKY_NODE_TYPE }] });
|
||||
const workflow = mock<IWorkflowBase>({ nodes: [{ type: STICKY_NODE_TYPE }], connections: {} });
|
||||
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockImplementationOnce(() => {
|
||||
vi.mocked(nodeHelpers.getNodeParameters).mockImplementationOnce(() => {
|
||||
throw new ApplicationError('Could not find property option');
|
||||
});
|
||||
|
||||
|
|
@ -3306,7 +3314,7 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
|||
},
|
||||
});
|
||||
const runData = mockRunData('Agent', new Error('Some error'));
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
vi.mocked(nodeHelpers.getNodeParameters).mockReturnValueOnce(
|
||||
mock<INodeParameters>({ model: { value: 'gpt-4-turbo' } }),
|
||||
);
|
||||
|
||||
|
|
@ -3360,7 +3368,7 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
|||
],
|
||||
});
|
||||
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
vi.mocked(nodeHelpers.getNodeParameters).mockReturnValueOnce(
|
||||
mock<INodeParameters>({ model: { value: 'gpt-4.1-mini' } }),
|
||||
);
|
||||
|
||||
|
|
@ -3388,7 +3396,7 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
|||
|
||||
const runData = mockRunData('Agent', new Error('Some error'));
|
||||
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
vi.mocked(nodeHelpers.getNodeParameters).mockReturnValueOnce(
|
||||
mock<INodeParameters>({ model: 'gpt-4' }),
|
||||
);
|
||||
|
||||
|
|
@ -3478,7 +3486,7 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
|||
});
|
||||
const runData = mockRunData('Agent', new Error('Some error'));
|
||||
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
vi.mocked(nodeHelpers.getNodeParameters).mockReturnValueOnce(
|
||||
mock<INodeParameters>({ modelName: 'gemini-1.5-pro' }),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default defineConfig({
|
|||
test: {
|
||||
reporters,
|
||||
outputFile,
|
||||
workspace: [
|
||||
projects: [
|
||||
{
|
||||
test: {
|
||||
...sharedTestConfig,
|
||||
|
|
|
|||
3342
pnpm-lock.yaml
3342
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -5,8 +5,6 @@ packages:
|
|||
- packages/extensions/**
|
||||
- packages/testing/**
|
||||
|
||||
catalogMode: strict
|
||||
|
||||
catalog:
|
||||
'@azure/identity': 4.13.0
|
||||
'@codemirror/autocomplete': 6.20.0
|
||||
|
|
@ -51,7 +49,7 @@ catalog:
|
|||
'@types/mime-types': 3.0.1
|
||||
'@types/uuid': ^10.0.0
|
||||
'@types/xml2js': ^0.4.14
|
||||
'@vitest/coverage-v8': 3.2.4
|
||||
'@vitest/coverage-v8': 4.1.1
|
||||
axios: 1.13.5
|
||||
basic-auth: 2.0.1
|
||||
callsites: 3.1.0
|
||||
|
|
@ -80,6 +78,7 @@ catalog:
|
|||
picocolors: 1.0.1
|
||||
reflect-metadata: 0.2.2
|
||||
rimraf: 6.0.1
|
||||
sass-embedded: ^1.98.0
|
||||
simple-git: 3.32.3
|
||||
stream-json: 1.9.1
|
||||
testcontainers: ^11.13.0
|
||||
|
|
@ -93,9 +92,9 @@ catalog:
|
|||
tsx: ^4.19.3
|
||||
typescript: 6.0.2
|
||||
uuid: 10.0.0
|
||||
vite: npm:rolldown-vite@latest
|
||||
vite: ^8.0.2
|
||||
vite-plugin-dts: ^4.5.4
|
||||
vitest: ^3.1.3
|
||||
vitest: ^4.1.1
|
||||
vitest-mock-extended: ^3.1.0
|
||||
vm2: ^3.10.5
|
||||
xml2js: 0.6.2
|
||||
|
|
@ -104,6 +103,8 @@ catalog:
|
|||
zod-to-json-schema: 3.23.3
|
||||
playwright-core: 1.58.0
|
||||
|
||||
catalogMode: strict
|
||||
|
||||
catalogs:
|
||||
e2e:
|
||||
'@currents/playwright': ^1.15.3
|
||||
|
|
@ -124,7 +125,7 @@ catalogs:
|
|||
element-plus: 2.4.3
|
||||
highlight.js: 11.8.0
|
||||
pinia: ^2.2.4
|
||||
unplugin-icons: ^0.19.0
|
||||
unplugin-icons: ^23.0.1
|
||||
vite-svg-loader: 5.1.0
|
||||
vue: ^3.5.13
|
||||
vue-i18n: ^11.1.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue