mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-21 19:00:42 +01:00
Compare commits
2 Commits
fix/ui-tes
...
refactor/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c9c2f17ab | ||
|
|
bda281ce1f |
@@ -68,7 +68,7 @@ process on top of it.
|
||||
`src/storybook/`, and builders typed from `src/api/generated` where the endpoint
|
||||
has types, so a contract change is a compile error instead of a mock that lies.
|
||||
- **Reuse fixtures** from `src/mocks-server/` and `src/tests/fixtures/` where they
|
||||
exist. An endpoint jest needs too belongs in `src/mocks-server/handlers.ts`.
|
||||
exist. An endpoint the tests need too belongs in `src/mocks-server/handlers.ts`.
|
||||
- **Shared response builders live in `src/storybook/msw/__story_mockdata__/`**: typed
|
||||
helpers like `queryRangeV5ScalarResponse` that multiple pages need. Before
|
||||
writing a response shape inline, check if a builder exists; if not and the
|
||||
|
||||
27
.github/workflows/jsci.yaml
vendored
27
.github/workflows/jsci.yaml
vendored
@@ -28,12 +28,27 @@ jobs:
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
uses: signoz/primus.workflows/.github/workflows/js-test.yaml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-noble
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: node-install
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||
- name: install-frontend
|
||||
run: cd frontend && pnpm install
|
||||
- name: test
|
||||
run: cd frontend && pnpm test
|
||||
fmt:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -29,6 +29,9 @@ e2e/test-plan/service-map/
|
||||
e2e/test-plan/services/
|
||||
e2e/test-plan/traces/
|
||||
e2e/test-plan/user-preferences/
|
||||
**/__screenshots__
|
||||
**/.vitest-attachments
|
||||
**/.vitest
|
||||
|
||||
# Storybook
|
||||
/storybook-static/
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"typescript",
|
||||
"jsx-a11y",
|
||||
"import",
|
||||
"jest",
|
||||
"vitest",
|
||||
"promise",
|
||||
"jsdoc"
|
||||
],
|
||||
@@ -23,9 +23,14 @@
|
||||
"builtin": true,
|
||||
"es2021": true,
|
||||
"browser": true,
|
||||
// The globals set named for jest: describe/it/expect/beforeEach, which
|
||||
// vitest provides under `globals: true`. There is no vitest-named set.
|
||||
"jest": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"vi": "readonly"
|
||||
},
|
||||
"options": {
|
||||
"typeAware": true,
|
||||
"typeCheck": false
|
||||
@@ -250,12 +255,15 @@
|
||||
"import/no-duplicates": "warn",
|
||||
// TODO: Changed to warn during oxlint migration, should be changed to error
|
||||
"arrow-body-style": "off",
|
||||
"jest/no-disabled-tests": "warn",
|
||||
// Would ask for an explicit signature on every `vi.fn()` in the suite;
|
||||
// 2393 of them, and the jest plugin this replaced had no equivalent.
|
||||
"vitest/require-mock-type-parameters": "off",
|
||||
"vitest/no-disabled-tests": "warn",
|
||||
// Jest test rules
|
||||
"jest/no-focused-tests": "error",
|
||||
"jest/no-identical-title": "warn",
|
||||
"jest/prefer-to-have-length": "warn",
|
||||
"jest/valid-expect": "warn",
|
||||
"vitest/no-focused-tests": "error",
|
||||
"vitest/no-identical-title": "warn",
|
||||
"vitest/prefer-to-have-length": "warn",
|
||||
"vitest/valid-expect": "warn",
|
||||
// TODO: Change to error after migration to oxlint
|
||||
// TODO: Change to error after migration to oxlint
|
||||
"react-hooks/rules-of-hooks": "warn",
|
||||
@@ -358,37 +366,33 @@
|
||||
"oxc/number-arg-out-of-range": "error",
|
||||
"oxc/only-used-in-recursion": "warn",
|
||||
"oxc/uninvoked-array-callback": "error",
|
||||
"jest/consistent-test-it": [
|
||||
"vitest/consistent-test-it": [
|
||||
"warn",
|
||||
{
|
||||
"fn": "it"
|
||||
}
|
||||
],
|
||||
"jest/expect-expect": "warn",
|
||||
"jest/no-alias-methods": "warn",
|
||||
"jest/no-commented-out-tests": "warn",
|
||||
"jest/no-conditional-expect": "warn",
|
||||
"jest/no-deprecated-functions": "warn",
|
||||
"jest/no-done-callback": "warn",
|
||||
"jest/no-duplicate-hooks": "warn",
|
||||
"jest/no-export": "warn",
|
||||
"jest/no-jasmine-globals": "warn",
|
||||
"jest/no-mocks-import": "warn",
|
||||
"jest/no-standalone-expect": "warn",
|
||||
"jest/no-test-prefixes": "warn",
|
||||
"jest/no-test-return-statement": "warn",
|
||||
"jest/prefer-called-with": "off", // The auto-fix for this can break the tests when the function has args
|
||||
"jest/prefer-comparison-matcher": "warn",
|
||||
"jest/prefer-equality-matcher": "warn",
|
||||
"jest/prefer-expect-resolves": "warn",
|
||||
"jest/prefer-hooks-on-top": "warn",
|
||||
"jest/prefer-spy-on": "warn",
|
||||
"jest/prefer-strict-equal": "warn",
|
||||
"jest/prefer-to-be": "warn",
|
||||
"jest/prefer-to-contain": "warn",
|
||||
"jest/prefer-todo": "warn",
|
||||
"jest/valid-describe-callback": "warn",
|
||||
"jest/valid-title": "warn",
|
||||
"vitest/expect-expect": "warn",
|
||||
"vitest/no-alias-methods": "warn",
|
||||
"vitest/no-commented-out-tests": "warn",
|
||||
"vitest/no-conditional-expect": "warn",
|
||||
"vitest/no-duplicate-hooks": "warn",
|
||||
"vitest/no-mocks-import": "warn",
|
||||
"vitest/no-standalone-expect": "warn",
|
||||
"vitest/no-test-prefixes": "warn",
|
||||
"vitest/no-test-return-statement": "warn",
|
||||
"vitest/prefer-called-with": "off", // The auto-fix for this can break the tests when the function has args
|
||||
"vitest/prefer-comparison-matcher": "warn",
|
||||
"vitest/prefer-equality-matcher": "warn",
|
||||
"vitest/prefer-expect-resolves": "warn",
|
||||
"vitest/prefer-hooks-on-top": "warn",
|
||||
"vitest/prefer-spy-on": "warn",
|
||||
"vitest/prefer-strict-equal": "warn",
|
||||
"vitest/prefer-to-be": "warn",
|
||||
"vitest/prefer-to-contain": "warn",
|
||||
"vitest/prefer-todo": "warn",
|
||||
"vitest/valid-describe-callback": "warn",
|
||||
"vitest/valid-title": "warn",
|
||||
"promise/catch-or-return": "warn",
|
||||
"promise/no-return-wrap": "error",
|
||||
"promise/param-names": "warn",
|
||||
|
||||
@@ -6,30 +6,30 @@ import type { Plugin, PluginOption } from 'vite';
|
||||
const srcPath = resolve(dirname(fileURLToPath(import.meta.url)), '../src');
|
||||
|
||||
/**
|
||||
* Modules replaced for every story. Same idea as `moduleNameMapper` in
|
||||
* `jest.config.ts`: the app keeps importing its own paths, Storybook resolves
|
||||
* Modules replaced for every story. Same idea as `resolve.alias` in
|
||||
* `vitest.config.ts`: the app keeps importing its own paths, Storybook resolves
|
||||
* them to a mock. Regexes so only exact specifiers match: `lib/history` must
|
||||
* not catch `lib/historyUtils`.
|
||||
*
|
||||
* Each replacement is typed as the module it stands in for, so drift is a
|
||||
* compile error rather than a story that fails at render. The `jest` note on
|
||||
* each entry is where the same import lands under the other runner. The two
|
||||
* compile error rather than a story that fails at render. The `vitest` note on
|
||||
* each entry is where the same import lands under the test runner. The two
|
||||
* only diverge where the runner needs them to.
|
||||
*/
|
||||
const mockAliases = [
|
||||
{
|
||||
// jest: not replaced, jsdom drives a real browser history.
|
||||
// vitest: not replaced, browser mode drives a real browser history.
|
||||
find: /^(?:src\/)?lib\/history$/,
|
||||
replacement: `${srcPath}/storybook/navigation/history.alias.ts`,
|
||||
},
|
||||
{
|
||||
// jest: src/__tests__/logEventMock.ts
|
||||
// vitest: src/__tests__/logEventMock.ts
|
||||
find: /^(?:src\/)?api\/common\/logEvent$/,
|
||||
replacement: `${srcPath}/storybook/mocks/logEvent.mock.ts`,
|
||||
},
|
||||
{
|
||||
// jest: __mocks__/env.ts, which leaves `baseURL` empty because jsdom already
|
||||
// resolves a relative `/api/...` against `http://localhost`.
|
||||
// vitest: __mocks__/env.ts, which leaves `baseURL` empty because the test
|
||||
// page already resolves a relative `/api/...` against its own origin.
|
||||
find: /^(?:src\/)?constants\/env$/,
|
||||
replacement: `${srcPath}/storybook/mocks/env.mock.ts`,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
// Handlers in src/mocks-server are pinned to absolute `http://localhost/...`
|
||||
// URLs. Under jsdom the page origin already is that, but vitest browser mode
|
||||
// serves the page from a random port, so relative request URLs would resolve
|
||||
// somewhere msw is not listening. Make the base URL absolute instead, which
|
||||
// resolves to the same request URL under both environments.
|
||||
export const ENVIRONMENT = {
|
||||
baseURL: process.env.VITE_FRONTEND_API_ENDPOINT || '',
|
||||
wsURL: process.env.VITE_WEBSOCKET_API_ENDPOINT || '',
|
||||
baseURL: 'http://localhost',
|
||||
wsURL: '',
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Test stub for the Vite-only icon glob module: `import.meta.glob` can't be
|
||||
// parsed by jest, so every test that transitively imports it is redirected here
|
||||
// (see moduleNameMapper in jest.config.ts). Provides a minimal name → URL map so
|
||||
// parsed under test, so every test that transitively imports it is redirected
|
||||
// here (see the alias in vitest.config.ts). Provides a minimal name → URL map so
|
||||
// the resolver stays deterministic under test.
|
||||
export const ICON_URLS: Record<string, string> = {
|
||||
'eight-ball': 'mock-eight-ball-url',
|
||||
|
||||
302
frontend/__mocks__/mockServiceWorker.js
Normal file
302
frontend/__mocks__/mockServiceWorker.js
Normal file
@@ -0,0 +1,302 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
|
||||
/**
|
||||
* Mock Service Worker (1.3.2).
|
||||
* @see https://github.com/mswjs/msw
|
||||
* - Please do NOT modify this file.
|
||||
* - Please do NOT serve this file on production.
|
||||
*/
|
||||
|
||||
const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70';
|
||||
const activeClientIds = new Set();
|
||||
|
||||
self.addEventListener('install', function () {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', function (event) {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('message', async function (event) {
|
||||
const clientId = event.source.id;
|
||||
|
||||
if (!clientId || !self.clients) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await self.clients.get(clientId);
|
||||
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
});
|
||||
|
||||
switch (event.data) {
|
||||
case 'KEEPALIVE_REQUEST': {
|
||||
sendToClient(client, {
|
||||
type: 'KEEPALIVE_RESPONSE',
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'INTEGRITY_CHECK_REQUEST': {
|
||||
sendToClient(client, {
|
||||
type: 'INTEGRITY_CHECK_RESPONSE',
|
||||
payload: INTEGRITY_CHECKSUM,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MOCK_ACTIVATE': {
|
||||
activeClientIds.add(clientId);
|
||||
|
||||
sendToClient(client, {
|
||||
type: 'MOCKING_ENABLED',
|
||||
payload: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MOCK_DEACTIVATE': {
|
||||
activeClientIds.delete(clientId);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'CLIENT_CLOSED': {
|
||||
activeClientIds.delete(clientId);
|
||||
|
||||
const remainingClients = allClients.filter((client) => {
|
||||
return client.id !== clientId;
|
||||
});
|
||||
|
||||
// Unregister itself when there are no more clients
|
||||
if (remainingClients.length === 0) {
|
||||
self.registration.unregister();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function (event) {
|
||||
const { request } = event;
|
||||
const accept = request.headers.get('accept') || '';
|
||||
|
||||
// Bypass server-sent events.
|
||||
if (accept.includes('text/event-stream')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bypass navigation requests.
|
||||
if (request.mode === 'navigate') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Opening the DevTools triggers the "only-if-cached" request
|
||||
// that cannot be handled by the worker. Bypass such requests.
|
||||
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bypass all requests when there are no active clients.
|
||||
// Prevents the self-unregistered worked from handling requests
|
||||
// after it's been deleted (still remains active until the next reload).
|
||||
if (activeClientIds.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate unique request ID.
|
||||
const requestId = Math.random().toString(16).slice(2);
|
||||
|
||||
event.respondWith(
|
||||
handleRequest(event, requestId).catch((error) => {
|
||||
if (error.name === 'NetworkError') {
|
||||
console.warn(
|
||||
'[MSW] Successfully emulated a network error for the "%s %s" request.',
|
||||
request.method,
|
||||
request.url,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, any exception indicates an issue with the original request/response.
|
||||
console.error(
|
||||
`\
|
||||
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
|
||||
request.method,
|
||||
request.url,
|
||||
`${error.name}: ${error.message}`,
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
async function handleRequest(event, requestId) {
|
||||
const client = await resolveMainClient(event);
|
||||
const response = await getResponse(event, client, requestId);
|
||||
|
||||
// Send back the response clone for the "response:*" life-cycle events.
|
||||
// Ensure MSW is active and ready to handle the message, otherwise
|
||||
// this message will pend indefinitely.
|
||||
if (client && activeClientIds.has(client.id)) {
|
||||
(async function () {
|
||||
const clonedResponse = response.clone();
|
||||
sendToClient(client, {
|
||||
type: 'RESPONSE',
|
||||
payload: {
|
||||
requestId,
|
||||
type: clonedResponse.type,
|
||||
ok: clonedResponse.ok,
|
||||
status: clonedResponse.status,
|
||||
statusText: clonedResponse.statusText,
|
||||
body: clonedResponse.body === null ? null : await clonedResponse.text(),
|
||||
headers: Object.fromEntries(clonedResponse.headers.entries()),
|
||||
redirected: clonedResponse.redirected,
|
||||
},
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Resolve the main client for the given event.
|
||||
// Client that issues a request doesn't necessarily equal the client
|
||||
// that registered the worker. It's with the latter the worker should
|
||||
// communicate with during the response resolving phase.
|
||||
async function resolveMainClient(event) {
|
||||
const client = await self.clients.get(event.clientId);
|
||||
|
||||
if (client?.frameType === 'top-level') {
|
||||
return client;
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
});
|
||||
|
||||
return allClients
|
||||
.filter((client) => {
|
||||
// Get only those clients that are currently visible.
|
||||
return client.visibilityState === 'visible';
|
||||
})
|
||||
.find((client) => {
|
||||
// Find the client ID that's recorded in the
|
||||
// set of clients that have registered the worker.
|
||||
return activeClientIds.has(client.id);
|
||||
});
|
||||
}
|
||||
|
||||
async function getResponse(event, client, requestId) {
|
||||
const { request } = event;
|
||||
const clonedRequest = request.clone();
|
||||
|
||||
function passthrough() {
|
||||
// Clone the request because it might've been already used
|
||||
// (i.e. its body has been read and sent to the client).
|
||||
const headers = Object.fromEntries(clonedRequest.headers.entries());
|
||||
|
||||
// Remove MSW-specific request headers so the bypassed requests
|
||||
// comply with the server's CORS preflight check.
|
||||
// Operate with the headers as an object because request "Headers"
|
||||
// are immutable.
|
||||
delete headers['x-msw-bypass'];
|
||||
|
||||
return fetch(clonedRequest, { headers });
|
||||
}
|
||||
|
||||
// Bypass mocking when the client is not active.
|
||||
if (!client) {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Bypass initial page load requests (i.e. static assets).
|
||||
// The absence of the immediate/parent client in the map of the active clients
|
||||
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||
// and is not ready to handle requests.
|
||||
if (!activeClientIds.has(client.id)) {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Bypass requests with the explicit bypass header.
|
||||
// Such requests can be issued by "ctx.fetch()".
|
||||
if (request.headers.get('x-msw-bypass') === 'true') {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Notify the client that a request has been intercepted.
|
||||
const clientMessage = await sendToClient(client, {
|
||||
type: 'REQUEST',
|
||||
payload: {
|
||||
id: requestId,
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
cache: request.cache,
|
||||
mode: request.mode,
|
||||
credentials: request.credentials,
|
||||
destination: request.destination,
|
||||
integrity: request.integrity,
|
||||
redirect: request.redirect,
|
||||
referrer: request.referrer,
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
body: await request.text(),
|
||||
bodyUsed: request.bodyUsed,
|
||||
keepalive: request.keepalive,
|
||||
},
|
||||
});
|
||||
|
||||
switch (clientMessage.type) {
|
||||
case 'MOCK_RESPONSE': {
|
||||
return respondWithMock(clientMessage.data);
|
||||
}
|
||||
|
||||
case 'MOCK_NOT_FOUND': {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
case 'NETWORK_ERROR': {
|
||||
const { name, message } = clientMessage.data;
|
||||
const networkError = new Error(message);
|
||||
networkError.name = name;
|
||||
|
||||
// Rejecting a "respondWith" promise emulates a network error.
|
||||
throw networkError;
|
||||
}
|
||||
}
|
||||
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
function sendToClient(client, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
if (event.data && event.data.error) {
|
||||
return reject(event.data.error);
|
||||
}
|
||||
|
||||
resolve(event.data);
|
||||
};
|
||||
|
||||
client.postMessage(message, [channel.port2]);
|
||||
});
|
||||
}
|
||||
|
||||
function sleep(timeMs) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, timeMs);
|
||||
});
|
||||
}
|
||||
|
||||
async function respondWithMock(response) {
|
||||
await sleep(response.delay);
|
||||
return new Response(response.body, response);
|
||||
}
|
||||
22
frontend/__mocks__/reduxMockStore.ts
Normal file
22
frontend/__mocks__/reduxMockStore.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error redux-mock-store ships no types for its deep entry, which is
|
||||
// the one that has to be imported here: aliasing the bare specifier to this file
|
||||
// would otherwise make it import itself.
|
||||
import * as mockStoreModule from 'redux-mock-store/lib/index.js';
|
||||
|
||||
type ConfigureStore = typeof import('redux-mock-store').default;
|
||||
|
||||
// rolldown-vite's dependency pre-bundler emits `export default require_lib()`
|
||||
// for a CommonJS module that sets `__esModule`, so the default import lands on
|
||||
// the module record instead of on its `default` export. Unwrap whichever shape
|
||||
// arrives, so the same helper works under jsdom and browser mode.
|
||||
const candidate = (
|
||||
mockStoreModule as unknown as {
|
||||
default: ConfigureStore | { default: ConfigureStore };
|
||||
}
|
||||
).default;
|
||||
|
||||
const configureStore: ConfigureStore =
|
||||
typeof candidate === 'function' ? candidate : candidate.default;
|
||||
|
||||
export default configureStore;
|
||||
114
frontend/__mocks__/storeSnapshot.ts
Normal file
114
frontend/__mocks__/storeSnapshot.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// `tests/test-utils` needs the redux store only for `getState()`, to seed
|
||||
// `redux-mock-store`, which never runs a reducer. Importing the real store pulls
|
||||
// its whole reducer graph into every test file, so this snapshot of the initial
|
||||
// state stands in for it.
|
||||
//
|
||||
// Regenerate by printing `JSON.stringify(store.getState())` from a test.
|
||||
const INITIAL_STATE = {
|
||||
traces: {
|
||||
filter: {},
|
||||
filterToFetchData: ['duration', 'status', 'serviceName'],
|
||||
filterLoading: true,
|
||||
filterResponseSelected: {},
|
||||
selectedFilter: {},
|
||||
selectedTags: [],
|
||||
isTagModalOpen: false,
|
||||
isTagModalError: false,
|
||||
isFilterExclude: {},
|
||||
userSelectedFilter: {},
|
||||
spansAggregate: {
|
||||
currentPage: 1,
|
||||
loading: false,
|
||||
data: [],
|
||||
error: false,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
order: '',
|
||||
orderParam: '',
|
||||
},
|
||||
selectedGroupBy: '',
|
||||
selectedFunction: 'count',
|
||||
yAxisUnit: '',
|
||||
spansGraph: {
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
payload: {
|
||||
items: {},
|
||||
},
|
||||
},
|
||||
filterDisplayValue: {},
|
||||
},
|
||||
usageDate: [
|
||||
{
|
||||
timestamp: 0,
|
||||
count: 0,
|
||||
},
|
||||
],
|
||||
globalTime: {
|
||||
maxTime: 1789616738446000000,
|
||||
minTime: 1789615838446000000,
|
||||
loading: true,
|
||||
selectedTime: '30m',
|
||||
isAutoRefreshDisabled: false,
|
||||
selectedAutoRefreshInterval: '',
|
||||
},
|
||||
serviceMap: {
|
||||
items: [],
|
||||
loading: true,
|
||||
},
|
||||
app: {
|
||||
currentVersion: '',
|
||||
latestVersion: '',
|
||||
isCurrentVersionError: false,
|
||||
isLatestVersionError: false,
|
||||
configs: {},
|
||||
ee: 'Y',
|
||||
setupCompleted: true,
|
||||
},
|
||||
metrics: {
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
metricsApplicationLoading: true,
|
||||
services: [],
|
||||
dbOverView: [],
|
||||
externalService: [],
|
||||
topOperations: [],
|
||||
externalAverageDuration: [],
|
||||
externalError: [],
|
||||
serviceOverview: [],
|
||||
topLevelOperations: [],
|
||||
},
|
||||
logs: {
|
||||
fields: {
|
||||
interesting: [],
|
||||
selected: [],
|
||||
},
|
||||
searchFilter: {
|
||||
queryString: '',
|
||||
parsedQuery: [],
|
||||
},
|
||||
logs: [],
|
||||
logLinesPerPage: 200,
|
||||
linesPerRow: 2,
|
||||
viewMode: 'raw',
|
||||
idEnd: '',
|
||||
idStart: '',
|
||||
isLoading: false,
|
||||
isLoadingAggregate: false,
|
||||
logsAggregate: [],
|
||||
liveTail: 'STOPPED',
|
||||
liveTailStartRange: 15,
|
||||
selectedLogId: null,
|
||||
detailedLog: null,
|
||||
order: 'desc',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
getState: (): typeof INITIAL_STATE => INITIAL_STATE,
|
||||
dispatch: (): void => {},
|
||||
subscribe: (): (() => void) => (): void => {},
|
||||
replaceReducer: (): void => {},
|
||||
};
|
||||
@@ -1,51 +1,53 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
// Mock for uplot library used in tests
|
||||
export interface MockUPlotInstance {
|
||||
setData: jest.Mock;
|
||||
setSize: jest.Mock;
|
||||
destroy: jest.Mock;
|
||||
redraw: jest.Mock;
|
||||
setSeries: jest.Mock;
|
||||
setData: Mock;
|
||||
setSize: Mock;
|
||||
destroy: Mock;
|
||||
redraw: Mock;
|
||||
setSeries: Mock;
|
||||
}
|
||||
|
||||
export interface MockUPlotPaths {
|
||||
spline: jest.Mock;
|
||||
bars: jest.Mock;
|
||||
linear: jest.Mock;
|
||||
stepped: jest.Mock;
|
||||
spline: Mock;
|
||||
bars: Mock;
|
||||
linear: Mock;
|
||||
stepped: Mock;
|
||||
}
|
||||
|
||||
// Create mock instance methods
|
||||
const createMockUPlotInstance = (): MockUPlotInstance => ({
|
||||
setData: jest.fn(),
|
||||
setSize: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
redraw: jest.fn(),
|
||||
setSeries: jest.fn(),
|
||||
setData: vi.fn(),
|
||||
setSize: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
redraw: vi.fn(),
|
||||
setSeries: vi.fn(),
|
||||
});
|
||||
|
||||
// Path builder: (self, seriesIdx, idx0, idx1) => paths or null
|
||||
const createMockPathBuilder = (name: string): jest.Mock =>
|
||||
jest.fn(() => ({
|
||||
const createMockPathBuilder = (name: string): Mock =>
|
||||
vi.fn(() => ({
|
||||
name, // To test if the correct pathBuilder is used
|
||||
stroke: jest.fn(),
|
||||
fill: jest.fn(),
|
||||
clip: jest.fn(),
|
||||
stroke: vi.fn(),
|
||||
fill: vi.fn(),
|
||||
clip: vi.fn(),
|
||||
}));
|
||||
|
||||
// Create mock paths - linear, spline, stepped needed by UPlotSeriesBuilder.getPathBuilder
|
||||
const mockPaths = {
|
||||
spline: jest.fn(() => createMockPathBuilder('spline')),
|
||||
bars: jest.fn(() => createMockPathBuilder('bars')),
|
||||
linear: jest.fn(() => createMockPathBuilder('linear')),
|
||||
stepped: jest.fn((opts?: { align?: number }) =>
|
||||
spline: vi.fn(() => createMockPathBuilder('spline')),
|
||||
bars: vi.fn(() => createMockPathBuilder('bars')),
|
||||
linear: vi.fn(() => createMockPathBuilder('linear')),
|
||||
stepped: vi.fn((opts?: { align?: number }) =>
|
||||
createMockPathBuilder(`stepped-(${opts?.align ?? 0})`),
|
||||
),
|
||||
};
|
||||
|
||||
// Mock static methods
|
||||
const mockTzDate = jest.fn(
|
||||
const mockTzDate = vi.fn(
|
||||
(date: Date, _timezone: string) => new Date(date.getTime()),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { modules: 'auto' }],
|
||||
['@babel/preset-react', { runtime: 'automatic' }],
|
||||
['@babel/preset-typescript'],
|
||||
],
|
||||
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{ modules: 'commonjs', targets: { node: 'current' } },
|
||||
],
|
||||
['@babel/preset-react', { runtime: 'automatic' }],
|
||||
['@babel/preset-typescript'],
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { Config } from '@jest/types';
|
||||
|
||||
const USE_SAFE_NAVIGATE_MOCK_PATH =
|
||||
'<rootDir>/src/__tests__/safeNavigateMock.ts';
|
||||
const LOG_EVENT_MOCK_PATH = '<rootDir>/src/__tests__/logEventMock.ts';
|
||||
|
||||
const config: Config.InitialOptions = {
|
||||
silent: true,
|
||||
clearMocks: true,
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'cobertura', 'html', 'json-summary'],
|
||||
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
modulePathIgnorePatterns: ['dist'],
|
||||
moduleNameMapper: {
|
||||
'\\.(png|jpg|jpeg|gif|svg|webp|avif|ico|bmp|tiff)$':
|
||||
'<rootDir>/__mocks__/fileMock.ts',
|
||||
// The icon glob module uses `import.meta.glob` (Vite-only); jest can't parse
|
||||
// it, so redirect any import of it to a stub.
|
||||
'(^|/)iconAssets$': '<rootDir>/__mocks__/iconAssetsMock.ts',
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'\\.module\\.mjs$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
|
||||
'^motion/react$': '<rootDir>/__mocks__/motionMock.tsx',
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^api/common/logEvent$': LOG_EVENT_MOCK_PATH,
|
||||
'^src/api/common/logEvent$': LOG_EVENT_MOCK_PATH,
|
||||
'^constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^@signozhq/icons$': '<rootDir>/__mocks__/signozhqIconsMock.tsx',
|
||||
'^lib/env$': '<rootDir>/__mocks__/lib/env.ts',
|
||||
'^test-mocks/(.*)$': '<rootDir>/__mocks__/$1',
|
||||
'^react-syntax-highlighter/dist/esm/(.*)$':
|
||||
'<rootDir>/node_modules/react-syntax-highlighter/dist/cjs/$1',
|
||||
'^@signozhq/(?!ui(?:/|$))([^/]+)$':
|
||||
'<rootDir>/node_modules/@signozhq/$1/dist/$1.js',
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
testMatch: ['<rootDir>/src/**/*?(*.)(test).(ts|js)?(x)'],
|
||||
preset: 'ts-jest/presets/js-with-ts-esm',
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
tsconfig: '<rootDir>/tsconfig.jest.json',
|
||||
},
|
||||
],
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
// TODO: https://github.com/SigNoz/engineering-pod/issues/5334
|
||||
transformIgnorePatterns: [
|
||||
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
|
||||
// Pattern 1: allow .pnpm virtual store through (handled by pattern 2), plus root-level ESM packages.
|
||||
'node_modules/(?!(\\.pnpm|react-json-tree|react-base16-styling|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard|react-markdown|vfile|vfile-message|unist-util-stringify-position|unified|bail|is-plain-obj|trough|remark-parse|remark-gfm|mdast-util-gfm|mdast-util-gfm-autolink-literal|mdast-util-gfm-footnote|mdast-util-gfm-strikethrough|mdast-util-gfm-table|mdast-util-gfm-task-list-item|mdast-util-find-and-replace|mdast-util-phrasing|mdast-util-to-markdown|markdown-table|longest-streak|ccount|escape-string-regexp|mdast-util-from-markdown|mdast-util-to-string|micromark|micromark-core-commonmark|micromark-extension-gfm|micromark-extension-gfm-autolink-literal|micromark-extension-gfm-footnote|micromark-extension-gfm-strikethrough|micromark-extension-gfm-table|micromark-extension-gfm-tagfilter|micromark-extension-gfm-task-list-item|micromark-factory-destination|micromark-factory-label|micromark-factory-space|micromark-factory-title|micromark-factory-whitespace|micromark-util-character|micromark-util-chunked|micromark-util-classify-character|micromark-util-combine-extensions|micromark-util-decode-numeric-character-reference|micromark-util-decode-string|micromark-util-encode|micromark-util-html-tag-name|micromark-util-normalize-identifier|micromark-util-resolve-all|micromark-util-sanitize-uri|micromark-util-subtokenize|micromark-util-symbol|micromark-util-types|decode-named-character-reference|remark-rehype|mdast-util-to-hast|unist-util-position|trim-lines|unist-util-visit|unist-util-visit-parents|unist-util-is|unist-util-generated|mdast-util-definitions|property-information|hast-util-whitespace|space-separated-tokens|comma-separated-tokens|rehype-raw|hast-util-raw|hast-util-from-parse5|devlop|hastscript|hast-util-parse-selector|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)/)',
|
||||
// Pattern 2: pnpm virtual store — ignore everything except ESM-only packages.
|
||||
// pnpm encodes scoped packages as @scope+name@version, so match on scope prefix.
|
||||
'node_modules/\\.pnpm/(?!(react-json-tree|react-base16-styling|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard|react-markdown|vfile|vfile-message|unist-util-stringify-position|unified|bail|is-plain-obj|trough|remark-parse|remark-gfm|mdast-util-gfm|mdast-util-gfm-autolink-literal|mdast-util-gfm-footnote|mdast-util-gfm-strikethrough|mdast-util-gfm-table|mdast-util-gfm-task-list-item|mdast-util-find-and-replace|mdast-util-phrasing|mdast-util-to-markdown|markdown-table|longest-streak|ccount|escape-string-regexp|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|remark-rehype|mdast-util-to-hast|unist-util-position|trim-lines|unist-util-visit|unist-util-visit-parents|unist-util-is|unist-util-generated|mdast-util-definitions|property-information|hast-util-whitespace|space-separated-tokens|comma-separated-tokens|rehype-raw|hast-util-raw|hast-util-from-parse5|devlop|hastscript|hast-util-parse-selector|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)[^/]*/node_modules)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 80,
|
||||
branches: 65,
|
||||
functions: 80,
|
||||
lines: 80,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,127 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable object-shorthand */
|
||||
/* eslint-disable func-names */
|
||||
|
||||
/**
|
||||
* Adds custom matchers from the react testing library to all tests
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import 'jest-styled-components';
|
||||
|
||||
import { server } from './src/mocks-server/server';
|
||||
|
||||
import './src/styles.scss';
|
||||
// Establish API mocking before all tests.
|
||||
|
||||
// Mock window.matchMedia
|
||||
window.matchMedia =
|
||||
window.matchMedia ||
|
||||
function (): any {
|
||||
return {
|
||||
matches: false,
|
||||
addListener: function () {},
|
||||
removeListener: function () {},
|
||||
};
|
||||
};
|
||||
|
||||
if (!HTMLElement.prototype.scrollIntoView) {
|
||||
HTMLElement.prototype.scrollIntoView = function (): void {};
|
||||
}
|
||||
|
||||
// jsdom doesn't implement the Pointer Capture API, which Radix UI primitives
|
||||
// (e.g. @signozhq/ui Select) call when opening. Stub them so those components
|
||||
// can be exercised in tests.
|
||||
if (!HTMLElement.prototype.hasPointerCapture) {
|
||||
HTMLElement.prototype.hasPointerCapture = function (): boolean {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
if (!HTMLElement.prototype.releasePointerCapture) {
|
||||
HTMLElement.prototype.releasePointerCapture = function (): void {};
|
||||
}
|
||||
|
||||
if (typeof window.IntersectionObserver === 'undefined') {
|
||||
class IntersectionObserverMock {
|
||||
observe(): void {}
|
||||
unobserve(): void {}
|
||||
disconnect(): void {}
|
||||
takeRecords(): IntersectionObserverEntry[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
(window as any).IntersectionObserver = IntersectionObserverMock;
|
||||
}
|
||||
|
||||
if (typeof window.ResizeObserver === 'undefined') {
|
||||
class ResizeObserverMock {
|
||||
observe(): void {}
|
||||
unobserve(): void {}
|
||||
disconnect(): void {}
|
||||
}
|
||||
(window as any).ResizeObserver = ResizeObserverMock;
|
||||
}
|
||||
|
||||
if (typeof globalThis.DOMRect === 'undefined') {
|
||||
(globalThis as any).DOMRect = class DOMRect {
|
||||
x = 0;
|
||||
y = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
top = 0;
|
||||
right = 0;
|
||||
bottom = 0;
|
||||
left = 0;
|
||||
constructor(x = 0, y = 0, width = 0, height = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.top = y;
|
||||
this.right = x + width;
|
||||
this.bottom = y + height;
|
||||
this.left = x;
|
||||
}
|
||||
toJSON(): any {
|
||||
return { x: this.x, y: this.y, width: this.width, height: this.height };
|
||||
}
|
||||
static fromRect(rect?: {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}): DOMRect {
|
||||
return new DOMRect(rect?.x, rect?.y, rect?.width, rect?.height);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
|
||||
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
|
||||
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),
|
||||
// causing SyntaxErrors during getComputedStyle / getByRole calls.
|
||||
const _origGetComputedStyle = window.getComputedStyle;
|
||||
window.getComputedStyle = function (
|
||||
elt: Element,
|
||||
pseudoElt?: string | null,
|
||||
): CSSStyleDeclaration {
|
||||
try {
|
||||
return _origGetComputedStyle.call(window, elt, pseudoElt);
|
||||
} catch {
|
||||
// Return a minimal CSSStyleDeclaration so callers (testing-library, Radix UI)
|
||||
// see the element as visible and without animations.
|
||||
return {
|
||||
display: '',
|
||||
visibility: '',
|
||||
opacity: '1',
|
||||
animationName: 'none',
|
||||
getPropertyValue: () => '',
|
||||
} as unknown as CSSStyleDeclaration;
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
afterAll(() => server.close());
|
||||
@@ -20,14 +20,14 @@
|
||||
"lint:fix": "oxlint ./src --fix",
|
||||
"lint:styles": "stylelint \"src/**/*.scss\"",
|
||||
"test:plugins": "node --test \"plugins/__tests__/*.test.mjs\"",
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "pnpm i18n:generate-hash && (is-ci || pnpm husky:configure) && node scripts/update-registry.cjs",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
||||
"commitlint": "commitlint --edit $1",
|
||||
"test": "jest",
|
||||
"test:changedsince": "jest --changedSince=main --coverage --silent",
|
||||
"test:browsers": "playwright install chromium",
|
||||
"test": "pnpm test:browsers && vitest run",
|
||||
"test:watch": "pnpm test:browsers && vitest",
|
||||
"test:coverage": "pnpm test:browsers && vitest run --coverage",
|
||||
"test:changedsince": "pnpm test:browsers && vitest run --changed main --coverage --silent",
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh",
|
||||
"generate:config:web-settings": "json2ts ./src/schemas/generated/webSettings.schema.json -o src/types/generated/webSettings.ts --style.useTabs --style.tabWidth=1 --style.singleQuote --bannerComment '/* AUTO GENERATED FILE - DO NOT EDIT - GENERATED FROM frontend/src/schemas/generated/webSettings.schema.json */'"
|
||||
},
|
||||
@@ -69,7 +69,6 @@
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"antlr4": "4.13.2",
|
||||
"axios": "1.18.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"chart.js": "3.9.1",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-plugin-annotation": "^1.4.0",
|
||||
@@ -88,7 +87,6 @@
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^4.0.0",
|
||||
"immer": "11.1.3",
|
||||
"jest": "30.2.0",
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "0.55.1",
|
||||
@@ -152,15 +150,8 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/preset-env": "^7.22.14",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/cli": "20.4.4",
|
||||
"@commitlint/config-conventional": "20.4.4",
|
||||
"@jest/globals": "30.4.1",
|
||||
"@jest/types": "30.2.0",
|
||||
"@storybook/addon-a11y": "10.5.9",
|
||||
"@storybook/react-vite": "10.5.9",
|
||||
"@storybook/test-runner": "0.24.5",
|
||||
@@ -173,7 +164,6 @@
|
||||
"@types/d3-hierarchy": "1.1.11",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/papaparse": "5.3.7",
|
||||
@@ -191,12 +181,13 @@
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260430.1",
|
||||
"@vitest/browser": "5.0.0",
|
||||
"@vitest/browser-playwright": "5.0.0",
|
||||
"@vitest/coverage-v8": "5.0.0",
|
||||
"eslint-plugin-sonarjs": "4.0.2",
|
||||
"glob": "^13.0.6",
|
||||
"husky": "^7.0.4",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-styled-components": "^7.2.0",
|
||||
"json-schema-to-typescript": "^15.0.4",
|
||||
"lint-staged": "^17.0.4",
|
||||
"msw": "1.3.2",
|
||||
@@ -204,6 +195,7 @@
|
||||
"oxfmt": "0.54.0",
|
||||
"oxlint": "1.69.0",
|
||||
"oxlint-tsgolint": "0.23.0",
|
||||
"playwright": "1.57.0",
|
||||
"postcss": "8.5.26",
|
||||
"postcss-scss": "4.0.9",
|
||||
"react-resizable": "3.0.4",
|
||||
@@ -213,13 +205,13 @@
|
||||
"storybook": "10.5.9",
|
||||
"stylelint": "17.15.0",
|
||||
"svgo": "4.1.0",
|
||||
"ts-jest": "29.4.9",
|
||||
"typescript-plugin-css-modules": "5.2.0",
|
||||
"use-sync-external-store": "1.6.0",
|
||||
"vite-plugin-checker": "0.12.0",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vite-plugin-image-optimizer": "2.0.3",
|
||||
"vite-tsconfig-paths": "6.1.1"
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "5.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
|
||||
@@ -6,9 +6,9 @@ Tests for the custom oxlint rules in `plugins/rules/`.
|
||||
pnpm test:plugins
|
||||
```
|
||||
|
||||
Runs on `node --test` rather than jest. The jest config is built for application
|
||||
code — jsdom, ts-jest ESM transforms, a large `transformIgnorePatterns` wall —
|
||||
and none of it applies to a suite whose only job is to shell out to the linter.
|
||||
Runs on `node --test` rather than the app's test runner. `vitest.config.ts` is
|
||||
built for application code (a browser page, app aliases, msw) and none of it
|
||||
applies to a suite whose only job is to shell out to the linter.
|
||||
|
||||
## Why it drives the real binary
|
||||
|
||||
|
||||
2419
frontend/pnpm-lock.yaml
generated
2419
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -21,14 +21,20 @@ import { routeWithInitialAuthZSupport } from 'utils/permission';
|
||||
|
||||
import PrivateRoute from '../Private';
|
||||
|
||||
// Mock localStorage APIs
|
||||
const mockLocalStorage: Record<string, string> = {};
|
||||
jest.mock('api/browser/localstorage/get', () => ({
|
||||
// Mock localStorage APIs (hoisted: vi.mock factories are hoisted above imports)
|
||||
const { mockLocalStorage, mockState } = vi.hoisted(() => ({
|
||||
mockLocalStorage: {} as Record<string, string>,
|
||||
mockState: {
|
||||
isCloudUser: true,
|
||||
usersData: [] as { email: string }[],
|
||||
},
|
||||
}));
|
||||
vi.mock('api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: (key: string): string | null => mockLocalStorage[key] || null,
|
||||
}));
|
||||
|
||||
jest.mock('api/browser/localstorage/set', () => ({
|
||||
vi.mock('api/browser/localstorage/set', () => ({
|
||||
__esModule: true,
|
||||
default: (key: string, value: string): void => {
|
||||
mockLocalStorage[key] = value;
|
||||
@@ -36,27 +42,25 @@ jest.mock('api/browser/localstorage/set', () => ({
|
||||
}));
|
||||
|
||||
// Mock useGetTenantLicense hook
|
||||
let mockIsCloudUser = true;
|
||||
jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
vi.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: (): {
|
||||
isCloudUser: boolean;
|
||||
isEnterpriseSelfHostedUser: boolean;
|
||||
isCommunityUser: boolean;
|
||||
isCommunityEnterpriseUser: boolean;
|
||||
} => ({
|
||||
isCloudUser: mockIsCloudUser,
|
||||
isEnterpriseSelfHostedUser: !mockIsCloudUser,
|
||||
isCloudUser: mockState.isCloudUser,
|
||||
isEnterpriseSelfHostedUser: !mockState.isCloudUser,
|
||||
isCommunityUser: false,
|
||||
isCommunityEnterpriseUser: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock react-query for users fetch
|
||||
let mockUsersData: { email: string }[] = [];
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
...jest.requireActual('api/generated/services/users'),
|
||||
useListUsers: jest.fn(() => ({
|
||||
data: { data: mockUsersData },
|
||||
vi.mock('api/generated/services/users', async () => ({
|
||||
...(await vi.importActual('api/generated/services/users')),
|
||||
useListUsers: vi.fn(() => ({
|
||||
data: { data: mockState.usersData },
|
||||
isFetching: false,
|
||||
})),
|
||||
}));
|
||||
@@ -187,13 +191,13 @@ function createMockAppContext(
|
||||
orgPreferencesFetchError: null,
|
||||
changelog: null,
|
||||
showChangelogModal: false,
|
||||
activeLicenseRefetch: jest.fn(),
|
||||
updateUser: jest.fn(),
|
||||
updateOrgPreferences: jest.fn(),
|
||||
updateUserPreferenceInContext: jest.fn(),
|
||||
updateOrg: jest.fn(),
|
||||
updateChangelog: jest.fn(),
|
||||
toggleChangelogModal: jest.fn(),
|
||||
activeLicenseRefetch: vi.fn(),
|
||||
updateUser: vi.fn(),
|
||||
updateOrgPreferences: vi.fn(),
|
||||
updateUserPreferenceInContext: vi.fn(),
|
||||
updateOrg: vi.fn(),
|
||||
updateChangelog: vi.fn(),
|
||||
toggleChangelogModal: vi.fn(),
|
||||
versionData: { version: '1.0.0', ee: 'Y', setupCompleted: true },
|
||||
hasEditPermission: true,
|
||||
...overrides,
|
||||
@@ -251,7 +255,7 @@ function buildPrivateRouteTree(
|
||||
}
|
||||
|
||||
function renderPrivateRoute(options: RenderPrivateRouteOptions = {}): void {
|
||||
mockIsCloudUser = options.isCloudUser ?? true;
|
||||
mockState.isCloudUser = options.isCloudUser ?? true;
|
||||
render(buildPrivateRouteTree(options));
|
||||
}
|
||||
|
||||
@@ -276,11 +280,11 @@ function assertRendersChildren(): void {
|
||||
|
||||
describe('PrivateRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
queryClient.clear();
|
||||
Object.keys(mockLocalStorage).forEach((key) => delete mockLocalStorage[key]);
|
||||
mockIsCloudUser = true;
|
||||
mockUsersData = [];
|
||||
mockState.isCloudUser = true;
|
||||
mockState.usersData = [];
|
||||
});
|
||||
|
||||
describe('Old Routes Handling', () => {
|
||||
@@ -1079,7 +1083,7 @@ describe('PrivateRoute', () => {
|
||||
describe('Onboarding Flow (Cloud Users)', () => {
|
||||
it('should redirect to onboarding when first user has not completed onboarding', async () => {
|
||||
// Set up exactly one user (not admin@signoz.cloud) to trigger first user check
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1118,7 +1122,7 @@ describe('PrivateRoute', () => {
|
||||
it('should not redirect to onboarding when onboarding is already complete', async () => {
|
||||
// Set up first user condition - this ensures the ONLY reason we don't redirect
|
||||
// is because isOnboardingComplete is true
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1189,7 +1193,7 @@ describe('PrivateRoute', () => {
|
||||
it('should not redirect to onboarding when workspace is blocked and accessing billing', async () => {
|
||||
// This tests the scenario where admin tries to access billing to fix payment
|
||||
// while workspace is blocked and onboarding is not complete
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.BILLING,
|
||||
@@ -1214,7 +1218,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is blocked and accessing settings', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.SETTINGS,
|
||||
@@ -1238,7 +1242,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is suspended (DEFAULTED)', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1265,7 +1269,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is access restricted (TERMINATED)', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1292,7 +1296,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is access restricted (EXPIRED)', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockState.usersData = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Shared mock for `api/common/logEvent`.
|
||||
// Wired into jest.config.ts moduleNameMapper, so any import of
|
||||
// Wired into the runner's module aliases, so any import of
|
||||
// `api/common/logEvent` in test code resolves to this file.
|
||||
// Tests can import `logEventMock` to assert analytics calls — Jest's
|
||||
// `clearMocks: true` resets call history between tests.
|
||||
|
||||
export const logEventMock: jest.MockedFunction<
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
export const logEventMock: Mock<
|
||||
(eventName: string, attributes?: Record<string, unknown>) => void
|
||||
> = jest.fn();
|
||||
> = vi.fn();
|
||||
|
||||
export default logEventMock;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Shared mock for `hooks/useSafeNavigate`.
|
||||
// Wired into jest.config.ts moduleNameMapper, so any import of
|
||||
// Wired into the runner's module aliases, so any import of
|
||||
// `hooks/useSafeNavigate` in test code resolves to this file.
|
||||
// Tests can import `safeNavigateMock` to assert navigation calls — Jest's
|
||||
// `clearMocks: true` resets call history between tests.
|
||||
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
interface SafeNavigateOptions {
|
||||
replace?: boolean;
|
||||
state?: unknown;
|
||||
@@ -18,9 +20,9 @@ interface SafeNavigateTo {
|
||||
|
||||
type SafeNavigateToType = string | SafeNavigateTo;
|
||||
|
||||
export const safeNavigateMock: jest.MockedFunction<
|
||||
export const safeNavigateMock: Mock<
|
||||
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||
> = jest.fn();
|
||||
> = vi.fn();
|
||||
|
||||
export const useSafeNavigate = (): {
|
||||
safeNavigate: typeof safeNavigateMock;
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import axios from 'axios';
|
||||
import post from 'api/v2/sessions/rotate/post';
|
||||
import { getIsNoAuthMode } from 'utils/noAuthMode';
|
||||
|
||||
import { Logout } from '../utils';
|
||||
import { interceptorRejected } from '../index';
|
||||
|
||||
jest.mock('utils/noAuthMode', () => ({
|
||||
getIsNoAuthMode: jest.fn(),
|
||||
vi.mock('utils/noAuthMode', () => ({
|
||||
getIsNoAuthMode: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/v2/sessions/rotate/post', () => ({
|
||||
vi.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('AppRoutes/utils', () => ({
|
||||
vi.mock('AppRoutes/utils', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../utils', () => ({
|
||||
Logout: jest.fn(),
|
||||
vi.mock('../utils', () => ({
|
||||
Logout: vi.fn(),
|
||||
}));
|
||||
|
||||
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
|
||||
const post = require('api/v2/sessions/rotate/post').default;
|
||||
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
|
||||
const { Logout } = require('../utils');
|
||||
|
||||
describe('interceptorRejected — no-auth mode', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(axios, 'isAxiosError').mockReturnValue(true);
|
||||
vi.clearAllMocks();
|
||||
vi.spyOn(axios, 'isAxiosError').mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('does NOT call rotate or Logout when no-auth mode is enabled on 401', async () => {
|
||||
(getIsNoAuthMode as jest.Mock).mockReturnValue(true);
|
||||
vi.mocked(getIsNoAuthMode).mockReturnValue(true);
|
||||
|
||||
const error = {
|
||||
isAxiosError: true,
|
||||
@@ -51,8 +50,8 @@ describe('interceptorRejected — no-auth mode', () => {
|
||||
});
|
||||
|
||||
it('DOES attempt rotate when no-auth mode is disabled on 401', async () => {
|
||||
(getIsNoAuthMode as jest.Mock).mockReturnValue(false);
|
||||
(post as jest.Mock).mockResolvedValue({
|
||||
vi.mocked(getIsNoAuthMode).mockReturnValue(false);
|
||||
(post as unknown as Mock).mockResolvedValue({
|
||||
data: { accessToken: 'a', refreshToken: 'b' },
|
||||
});
|
||||
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
/**
|
||||
* localstorage/get — lazy migration tests.
|
||||
*
|
||||
* basePath is memoized at module init, so each describe block re-imports the
|
||||
* module with a fresh DOM state via jest.isolateModules.
|
||||
* getBasePath() is memoized at module init, and vi.resetModules() does not
|
||||
* re-evaluate modules in browser mode, so per-path state is driven through a
|
||||
* utils/basePath mock instead of re-importing with a fresh DOM state.
|
||||
*/
|
||||
|
||||
type GetModule = typeof import('../get');
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
|
||||
function loadGetModule(href: string): GetModule {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', href);
|
||||
document.head.append(base);
|
||||
import get from '../get';
|
||||
|
||||
let mod!: GetModule;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../get');
|
||||
});
|
||||
return mod;
|
||||
vi.mock('utils/basePath', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('utils/basePath')>('utils/basePath');
|
||||
return { ...actual, getBasePath: vi.fn(() => '/') };
|
||||
});
|
||||
|
||||
function setBasePath(href: string): void {
|
||||
vi.mocked(getBasePath).mockReturnValue(href.endsWith('/') ? href : `${href}/`);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const el of document.head.querySelectorAll('base')) {
|
||||
el.remove();
|
||||
}
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('get — root path "/"', () => {
|
||||
it('reads the bare key', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
setBasePath('/');
|
||||
localStorage.setItem('AUTH_TOKEN', 'tok');
|
||||
expect(get('AUTH_TOKEN')).toBe('tok');
|
||||
});
|
||||
|
||||
it('returns null when key is absent', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
setBasePath('/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('does NOT promote bare keys (no-op at root)', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
setBasePath('/');
|
||||
localStorage.setItem('THEME', 'light');
|
||||
get('THEME');
|
||||
// bare key must still be present — no migration at root
|
||||
@@ -50,18 +47,18 @@ describe('get — root path "/"', () => {
|
||||
|
||||
describe('get — prefixed path "/signoz/"', () => {
|
||||
it('reads an already-scoped key directly', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
|
||||
expect(get('AUTH_TOKEN')).toBe('scoped-tok');
|
||||
});
|
||||
|
||||
it('returns null when neither scoped nor bare key exists', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('lazy-migrates bare key to scoped key on first read', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
localStorage.setItem('AUTH_TOKEN', 'old-tok');
|
||||
|
||||
const result = get('AUTH_TOKEN');
|
||||
@@ -72,7 +69,7 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
});
|
||||
|
||||
it('scoped key takes precedence over bare key', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
localStorage.setItem('AUTH_TOKEN', 'bare-tok');
|
||||
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
|
||||
|
||||
@@ -82,7 +79,7 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
});
|
||||
|
||||
it('subsequent reads after migration use scoped key (no double-write)', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
localStorage.setItem('THEME', 'dark');
|
||||
|
||||
get('THEME'); // triggers migration
|
||||
@@ -95,33 +92,16 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
|
||||
describe('get — two-prefix isolation', () => {
|
||||
it('/signoz/ and /testing/ do not share migrated values', () => {
|
||||
setBasePath('/signoz/');
|
||||
localStorage.setItem('THEME', 'light');
|
||||
|
||||
const base1 = document.createElement('base');
|
||||
base1.setAttribute('href', '/signoz/');
|
||||
document.head.append(base1);
|
||||
let getSignoz!: GetModule['default'];
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
getSignoz = require('../get').default;
|
||||
});
|
||||
base1.remove();
|
||||
|
||||
// migrate bare → /signoz/THEME
|
||||
getSignoz('THEME');
|
||||
get('THEME');
|
||||
|
||||
const base2 = document.createElement('base');
|
||||
base2.setAttribute('href', '/testing/');
|
||||
document.head.append(base2);
|
||||
let getTesting!: GetModule['default'];
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
getTesting = require('../get').default;
|
||||
});
|
||||
base2.remove();
|
||||
setBasePath('/testing/');
|
||||
|
||||
// /testing/ prefix: bare key already gone, scoped key does not exist
|
||||
expect(getTesting('THEME')).toBeNull();
|
||||
expect(get('THEME')).toBeNull();
|
||||
expect(localStorage.getItem('/signoz/THEME')).toBe('light');
|
||||
expect(localStorage.getItem('/testing/THEME')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/**
|
||||
* sessionstorage/get — lazy migration tests.
|
||||
* Mirrors the localStorage get tests; same logic, different storage.
|
||||
*
|
||||
* getBasePath() is memoized at module init, and vi.resetModules() does not
|
||||
* re-evaluate modules in browser mode, so per-path state is driven through a
|
||||
* utils/basePath mock instead of re-importing with a fresh DOM state.
|
||||
*/
|
||||
|
||||
type GetModule = typeof import('../get');
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
|
||||
function loadGetModule(href: string): GetModule {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', href);
|
||||
document.head.append(base);
|
||||
import get from '../get';
|
||||
|
||||
let mod!: GetModule;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../get');
|
||||
});
|
||||
return mod;
|
||||
vi.mock('utils/basePath', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('utils/basePath')>('utils/basePath');
|
||||
return { ...actual, getBasePath: vi.fn(() => '/') };
|
||||
});
|
||||
|
||||
function setBasePath(href: string): void {
|
||||
vi.mocked(getBasePath).mockReturnValue(href.endsWith('/') ? href : `${href}/`);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const el of document.head.querySelectorAll('base')) {
|
||||
el.remove();
|
||||
}
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
describe('get — root path "/"', () => {
|
||||
it('reads the bare key', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
setBasePath('/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'true');
|
||||
expect(get('retry-lazy-refreshed')).toBe('true');
|
||||
});
|
||||
|
||||
it('returns null when key is absent', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
setBasePath('/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('does NOT promote bare keys at root', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
setBasePath('/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'true');
|
||||
get('retry-lazy-refreshed');
|
||||
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBe('true');
|
||||
@@ -47,18 +47,18 @@ describe('get — root path "/"', () => {
|
||||
|
||||
describe('get — prefixed path "/signoz/"', () => {
|
||||
it('reads an already-scoped key directly', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'true');
|
||||
expect(get('retry-lazy-refreshed')).toBe('true');
|
||||
});
|
||||
|
||||
it('returns null when neither scoped nor bare key exists', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('lazy-migrates bare key to scoped key on first read', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'true');
|
||||
|
||||
const result = get('retry-lazy-refreshed');
|
||||
@@ -69,7 +69,7 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
});
|
||||
|
||||
it('scoped key takes precedence over bare key', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
setBasePath('/signoz/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'bare');
|
||||
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'scoped');
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import axios from 'api';
|
||||
|
||||
import { getFieldKeys } from '../getFieldKeys';
|
||||
|
||||
// Mock the API instance
|
||||
jest.mock('api', () => ({
|
||||
get: jest.fn(),
|
||||
// Mock the API instance (default export is the axios instance)
|
||||
vi.mock('api', () => ({
|
||||
default: { get: vi.fn() },
|
||||
}));
|
||||
|
||||
describe('getFieldKeys API', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const mockSuccessResponse = {
|
||||
@@ -28,7 +30,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should call API with correct parameters when no args provided', async () => {
|
||||
// Mock successful API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
(axios.get as Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
|
||||
// Call function with no parameters
|
||||
await getFieldKeys();
|
||||
@@ -41,7 +43,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should call API with signal parameter when provided', async () => {
|
||||
// Mock successful API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
(axios.get as Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
|
||||
// Call function with signal parameter
|
||||
await getFieldKeys('traces');
|
||||
@@ -54,7 +56,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should call API with name parameter when provided', async () => {
|
||||
// Mock successful API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -76,7 +78,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should call API with both signal and name when provided', async () => {
|
||||
// Mock successful API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -98,7 +100,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should return properly formatted response', async () => {
|
||||
// Mock API to return our response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
(axios.get as Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
|
||||
// Call the function
|
||||
const result = await getFieldKeys('traces');
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import axios from 'api';
|
||||
|
||||
import { getFieldValues } from '../getFieldValues';
|
||||
|
||||
// Mock the API instance
|
||||
jest.mock('api', () => ({
|
||||
get: jest.fn(),
|
||||
// Mock the API instance (default export is the axios instance)
|
||||
vi.mock('api', () => ({
|
||||
default: { get: vi.fn() },
|
||||
}));
|
||||
|
||||
describe('getFieldValues API', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call the API with correct parameters (no options)', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -38,7 +40,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with signal parameter', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -62,7 +64,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with name parameter', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -86,7 +88,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with value parameter', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -110,7 +112,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with time range parameters', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
(axios.get as Mock).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -162,7 +164,7 @@ describe('getFieldValues API', () => {
|
||||
},
|
||||
};
|
||||
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockResponse);
|
||||
(axios.get as Mock).mockResolvedValueOnce(mockResponse);
|
||||
|
||||
// Call the function
|
||||
const result = await getFieldValues('traces', 'mixed.values');
|
||||
@@ -193,7 +195,7 @@ describe('getFieldValues API', () => {
|
||||
};
|
||||
|
||||
// Mock API to return our response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockApiResponse);
|
||||
(axios.get as Mock).mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
// Call the function
|
||||
const result = await getFieldValues('traces', 'service.name');
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import axios, { AxiosHeaders, AxiosResponse } from 'axios';
|
||||
|
||||
import { interceptorRejected } from './index';
|
||||
|
||||
jest.mock('api/browser/localstorage/get', () => ({
|
||||
vi.mock('api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => 'mock-token'),
|
||||
default: vi.fn(() => 'mock-token'),
|
||||
}));
|
||||
|
||||
jest.mock('api/v2/sessions/rotate/post', () => ({
|
||||
vi.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() =>
|
||||
default: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
data: { accessToken: 'new-token', refreshToken: 'new-refresh' },
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('AppRoutes/utils', () => ({
|
||||
vi.mock('AppRoutes/utils', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('axios', () => {
|
||||
const actualAxios = jest.requireActual('axios');
|
||||
const mockAxios = jest.fn().mockResolvedValue({ data: 'success' });
|
||||
vi.mock('axios', async () => {
|
||||
const actualAxios = await vi.importActual('axios');
|
||||
const mockAxios = vi.fn().mockResolvedValue({ data: 'success' });
|
||||
|
||||
return {
|
||||
...actualAxios,
|
||||
...(actualAxios as object),
|
||||
default: Object.assign(mockAxios, {
|
||||
...actualAxios.default,
|
||||
isAxiosError: jest.fn().mockReturnValue(true),
|
||||
create: actualAxios.create,
|
||||
...(actualAxios as { default: object }).default,
|
||||
isAxiosError: vi.fn().mockReturnValue(true),
|
||||
create: (actualAxios as { create: unknown }).create,
|
||||
}),
|
||||
__esModule: true,
|
||||
};
|
||||
@@ -38,9 +40,9 @@ jest.mock('axios', () => {
|
||||
|
||||
describe('interceptorRejected', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(axios as unknown as jest.Mock).mockResolvedValue({ data: 'success' });
|
||||
(axios.isAxiosError as unknown as jest.Mock).mockReturnValue(true);
|
||||
vi.clearAllMocks();
|
||||
(axios as unknown as Mock).mockResolvedValue({ data: 'success' });
|
||||
(axios.isAxiosError as unknown as Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should preserve array payload structure when retrying a 401 request', async () => {
|
||||
@@ -75,7 +77,7 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
const mockAxiosFn = axios as unknown as Mock;
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
|
||||
@@ -111,7 +113,7 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
const mockAxiosFn = axios as unknown as Mock;
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(objectPayload);
|
||||
@@ -144,7 +146,7 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
const mockAxiosFn = axios as unknown as Mock;
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(retryCallConfig.data).toBeUndefined();
|
||||
|
||||
@@ -4,21 +4,20 @@ import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { getFieldKeySuggestions } from '../getFieldKeySuggestions';
|
||||
import { FieldKeysResponse } from '../types';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
jest.mock('api/generated/services/ai-observability', () => ({
|
||||
getAIObservabilityFieldsKeys: jest.fn(),
|
||||
vi.mock('api/generated/services/ai-observability', () => ({
|
||||
getAIObservabilityFieldsKeys: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/generated/services/fields', () => ({
|
||||
getFieldsKeys: jest.fn(),
|
||||
vi.mock('api/generated/services/fields', () => ({
|
||||
getFieldsKeys: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockedAIKeys = getAIObservabilityFieldsKeys as jest.MockedFunction<
|
||||
const mockedAIKeys = getAIObservabilityFieldsKeys as MockedFunction<
|
||||
typeof getAIObservabilityFieldsKeys
|
||||
>;
|
||||
const mockedGenericKeys = getFieldsKeys as jest.MockedFunction<
|
||||
typeof getFieldsKeys
|
||||
>;
|
||||
const mockedGenericKeys = getFieldsKeys as MockedFunction<typeof getFieldsKeys>;
|
||||
|
||||
const keysResponse = (): FieldKeysResponse => ({
|
||||
status: 'success',
|
||||
@@ -30,7 +29,7 @@ const keysResponse = (): FieldKeysResponse => ({
|
||||
|
||||
describe('getFieldKeySuggestions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('reads the ai_observability endpoint for a builder_ai_query', async () => {
|
||||
|
||||
@@ -4,19 +4,20 @@ import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { getFieldValueSuggestions } from '../getFieldValueSuggestions';
|
||||
import { FieldValuesResponse } from '../types';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
jest.mock('api/generated/services/ai-observability', () => ({
|
||||
getAIObservabilityFieldsValues: jest.fn(),
|
||||
vi.mock('api/generated/services/ai-observability', () => ({
|
||||
getAIObservabilityFieldsValues: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/generated/services/fields', () => ({
|
||||
getFieldsValues: jest.fn(),
|
||||
vi.mock('api/generated/services/fields', () => ({
|
||||
getFieldsValues: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockedAIValues = getAIObservabilityFieldsValues as jest.MockedFunction<
|
||||
const mockedAIValues = getAIObservabilityFieldsValues as MockedFunction<
|
||||
typeof getAIObservabilityFieldsValues
|
||||
>;
|
||||
const mockedGenericValues = getFieldsValues as jest.MockedFunction<
|
||||
const mockedGenericValues = getFieldsValues as MockedFunction<
|
||||
typeof getFieldsValues
|
||||
>;
|
||||
|
||||
@@ -27,7 +28,7 @@ const valuesResponse = (): FieldValuesResponse => ({
|
||||
|
||||
describe('getFieldValueSuggestions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('reads the ai_observability endpoint for a builder_ai_query, forwarding the key as name', async () => {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
IBuilderFormula,
|
||||
@@ -24,9 +27,9 @@ import {
|
||||
prepareQueryRangePayloadV5,
|
||||
} from './prepareQueryRangePayloadV5';
|
||||
|
||||
jest.mock('lib/getStartEndRangeTime', () => ({
|
||||
vi.mock('lib/getStartEndRangeTime', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => ({ start: '100', end: '200' })),
|
||||
default: vi.fn(() => ({ start: '100', end: '200' })),
|
||||
}));
|
||||
|
||||
describe('prepareQueryRangePayloadV5', () => {
|
||||
@@ -519,9 +522,8 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
});
|
||||
|
||||
it('maps groupBy, order, having, aggregations and filter for logs builder query', () => {
|
||||
const getStartEndRangeTime = jest.requireMock('lib/getStartEndRangeTime')
|
||||
.default as jest.Mock;
|
||||
getStartEndRangeTime.mockReturnValueOnce({
|
||||
const mockedGetStartEndRangeTime = getStartEndRangeTime as unknown as Mock;
|
||||
mockedGetStartEndRangeTime.mockReturnValueOnce({
|
||||
start: '1754623641',
|
||||
end: '1754645241',
|
||||
});
|
||||
|
||||
@@ -10,9 +10,9 @@ class MockResizeObserver {
|
||||
resizeCallback = callback;
|
||||
}
|
||||
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
observe = vi.fn();
|
||||
unobserve = vi.fn();
|
||||
disconnect = vi.fn();
|
||||
}
|
||||
|
||||
function triggerResize(width: number): void {
|
||||
@@ -27,7 +27,8 @@ function triggerResize(width: number): void {
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
global.ResizeObserver = MockResizeObserver as unknown as typeof ResizeObserver;
|
||||
globalThis.ResizeObserver =
|
||||
MockResizeObserver as unknown as typeof ResizeObserver;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('NoResultsEmptyState', () => {
|
||||
});
|
||||
|
||||
it('should render clear button when onClear is provided', () => {
|
||||
const onClear = jest.fn();
|
||||
const onClear = vi.fn();
|
||||
|
||||
render(<NoResultsEmptyState onClear={onClear} />);
|
||||
|
||||
@@ -49,9 +49,7 @@ describe('NoResultsEmptyState', () => {
|
||||
});
|
||||
|
||||
it('should render custom clear button text', () => {
|
||||
render(
|
||||
<NoResultsEmptyState onClear={jest.fn()} clearButtonText="Reset All" />,
|
||||
);
|
||||
render(<NoResultsEmptyState onClear={vi.fn()} clearButtonText="Reset All" />);
|
||||
|
||||
expect(screen.getByTestId('no-results-clear-button')).toHaveTextContent(
|
||||
'Reset All',
|
||||
@@ -60,7 +58,7 @@ describe('NoResultsEmptyState', () => {
|
||||
|
||||
it('should call onClear when clear button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClear = jest.fn();
|
||||
const onClear = vi.fn();
|
||||
|
||||
render(<NoResultsEmptyState onClear={onClear} />);
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import getLocal from '../../../api/browser/localstorage/get';
|
||||
import AppLoading from '../AppLoading';
|
||||
|
||||
jest.mock('../../../api/browser/localstorage/get', () => ({
|
||||
vi.mock('../../../api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
// Access the mocked function
|
||||
const mockGet = getLocal as unknown as jest.Mock;
|
||||
const mockGet = getLocal as unknown as Mock;
|
||||
|
||||
describe('AppLoading', () => {
|
||||
const SIGNOZ_TEXT = 'SigNoz';
|
||||
@@ -18,7 +20,7 @@ describe('AppLoading', () => {
|
||||
const CONTAINER_SELECTOR = '.app-loading-container';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render loading screen with dark theme by default', () => {
|
||||
|
||||
@@ -37,17 +37,15 @@ const mockChangelog: ChangelogSchema = {
|
||||
};
|
||||
|
||||
// Mock react-markdown to just render children as plain text
|
||||
jest.mock(
|
||||
'react-markdown',
|
||||
() =>
|
||||
function ReactMarkdown({ children }: any) {
|
||||
return <div>{children}</div>;
|
||||
},
|
||||
);
|
||||
vi.mock('react-markdown', () => ({
|
||||
default: function ReactMarkdown({ children }: any) {
|
||||
return <div>{children}</div>;
|
||||
},
|
||||
}));
|
||||
// mock useAppContext
|
||||
jest.mock('providers/App/App', () => ({
|
||||
useAppContext: jest.fn(() => ({
|
||||
updateUserPreferenceInContext: jest.fn(),
|
||||
vi.mock('providers/App/App', () => ({
|
||||
useAppContext: vi.fn(() => ({
|
||||
updateUserPreferenceInContext: vi.fn(),
|
||||
userPreferences: [
|
||||
{
|
||||
name: USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
|
||||
@@ -57,7 +55,7 @@ jest.mock('providers/App/App', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
function renderChangelog(onClose: () => void = jest.fn()): void {
|
||||
function renderChangelog(onClose: () => void = vi.fn()): void {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<ChangelogModal changelog={mockChangelog} onClose={onClose} />
|
||||
@@ -78,14 +76,14 @@ describe('ChangelogModal', () => {
|
||||
});
|
||||
|
||||
it('calls onClose when Skip for now is clicked', () => {
|
||||
const onClose = jest.fn();
|
||||
const onClose = vi.fn();
|
||||
renderChangelog(onClose);
|
||||
fireEvent.click(screen.getByText('Skip for now'));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('opens migration docs when Update my workspace is clicked', () => {
|
||||
window.open = jest.fn();
|
||||
window.open = vi.fn();
|
||||
renderChangelog();
|
||||
fireEvent.click(screen.getByText('Update my workspace'));
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
@@ -100,7 +98,7 @@ describe('ChangelogModal', () => {
|
||||
const scrollBtn = screen.getByTestId('scroll-more-btn');
|
||||
const contentDiv = screen.getByTestId('changelog-content');
|
||||
if (contentDiv) {
|
||||
contentDiv.scrollTo = jest.fn();
|
||||
contentDiv.scrollTo = vi.fn();
|
||||
}
|
||||
fireEvent.click(scrollBtn);
|
||||
if (contentDiv) {
|
||||
|
||||
@@ -10,19 +10,17 @@ import ChangelogRenderer from '../components/ChangelogRenderer';
|
||||
|
||||
// Mock react-markdown to render children as plain text and a sample
|
||||
// anchor through the `components.a` override
|
||||
jest.mock(
|
||||
'react-markdown',
|
||||
() =>
|
||||
function ReactMarkdown({ children, components }: any) {
|
||||
const Anchor = components?.a;
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
{Anchor && <Anchor href="https://signoz.io/docs">docs</Anchor>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
vi.mock('react-markdown', () => ({
|
||||
default: function ReactMarkdown({ children, components }: any) {
|
||||
const Anchor = components?.a;
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
{Anchor && <Anchor href="https://signoz.io/docs">docs</Anchor>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
const mockChangelog: ChangelogSchema = {
|
||||
id: 1,
|
||||
|
||||
@@ -2,9 +2,11 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import CodeBlock from './CodeBlock';
|
||||
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
const { mockCopyToClipboard } = vi.hoisted(() => ({
|
||||
mockCopyToClipboard: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
vi.mock('react-use', () => ({
|
||||
useCopyToClipboard: (): [unknown, (text: string) => void] => [
|
||||
undefined,
|
||||
mockCopyToClipboard,
|
||||
@@ -33,7 +35,7 @@ describe('CodeBlock', () => {
|
||||
});
|
||||
|
||||
it('copies code and triggers callback', async () => {
|
||||
const onCopy = jest.fn();
|
||||
const onCopy = vi.fn();
|
||||
render(<CodeBlock code="SELECT * FROM logs;" onCopy={onCopy} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /copy code/i }));
|
||||
|
||||
@@ -9,18 +9,18 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui/sonner', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui/sonner')),
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const mockToast = jest.mocked(toast);
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
const { showErrorModal } = vi.hoisted(() => ({ showErrorModal: vi.fn() }));
|
||||
vi.mock('providers/ErrorModalProvider', async () => ({
|
||||
__esModule: true,
|
||||
...jest.requireActual('providers/ErrorModalProvider'),
|
||||
useErrorModal: jest.fn(() => ({
|
||||
...(await vi.importActual('providers/ErrorModalProvider')),
|
||||
useErrorModal: vi.fn(() => ({
|
||||
showErrorModal,
|
||||
isErrorModalVisible: false,
|
||||
})),
|
||||
@@ -38,7 +38,7 @@ function renderModal(): ReturnType<typeof render> {
|
||||
|
||||
describe('CreateServiceAccountModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
server.use(
|
||||
setupAuthzAdmin(),
|
||||
rest.post(SERVICE_ACCOUNTS_ENDPOINT, (_, res, ctx) =>
|
||||
|
||||
@@ -5,32 +5,32 @@ import * as timeUtils from 'utils/timeUtils';
|
||||
|
||||
import CustomTimePicker from './CustomTimePicker';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual('react-router-dom');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
useLocation: vi.fn().mockReturnValue({
|
||||
pathname: '/test-path',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
useDispatch: jest.fn(() => jest.fn()),
|
||||
useSelector: jest.fn(() => ({
|
||||
vi.mock('react-redux', async () => ({
|
||||
...(await vi.importActual('react-redux')),
|
||||
useDispatch: vi.fn(() => vi.fn()),
|
||||
useSelector: vi.fn(() => ({
|
||||
minTime: 0,
|
||||
maxTime: Date.now(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('providers/Timezone', () => {
|
||||
const actual = jest.requireActual('providers/Timezone');
|
||||
vi.mock('providers/Timezone', async () => {
|
||||
const actual = await vi.importActual('providers/Timezone');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useTimezone: jest.fn().mockReturnValue({
|
||||
useTimezone: vi.fn().mockReturnValue({
|
||||
timezone: {
|
||||
value: 'UTC',
|
||||
offset: '+00:00',
|
||||
@@ -45,6 +45,8 @@ jest.mock('providers/Timezone', () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('utils/timeUtils', { spy: true });
|
||||
|
||||
interface WrapperProps {
|
||||
initialValue?: string;
|
||||
showLiveLogs?: boolean;
|
||||
@@ -123,8 +125,8 @@ describe('CustomTimePicker', () => {
|
||||
});
|
||||
|
||||
it('applies valid shorthand on Enter', () => {
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const onValid = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
render(<Wrapper onValidCustomDateChange={onValid} onError={onError} />);
|
||||
|
||||
@@ -141,9 +143,9 @@ describe('CustomTimePicker', () => {
|
||||
});
|
||||
|
||||
it('sets error and updates custom time status for invalid shorthand exceeding max allowed window', () => {
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const onCustomTimeStatusUpdate = jest.fn();
|
||||
const onValid = vi.fn();
|
||||
const onError = vi.fn();
|
||||
const onCustomTimeStatusUpdate = vi.fn();
|
||||
|
||||
render(
|
||||
<Wrapper
|
||||
@@ -166,8 +168,8 @@ describe('CustomTimePicker', () => {
|
||||
});
|
||||
|
||||
it('treats close after change like pressing Enter (blur + chevron)', () => {
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const onValid = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
render(<Wrapper onValidCustomDateChange={onValid} onError={onError} />);
|
||||
|
||||
@@ -191,8 +193,8 @@ describe('CustomTimePicker', () => {
|
||||
});
|
||||
|
||||
it('applies epoch start/end range on Enter via onCustomDateHandler', () => {
|
||||
const onCustomDateHandler = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const onCustomDateHandler = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
render(
|
||||
<Wrapper onCustomDateHandler={onCustomDateHandler} onError={onError} />,
|
||||
@@ -213,9 +215,9 @@ describe('CustomTimePicker', () => {
|
||||
});
|
||||
|
||||
it('uses validateTimeRange result for generic formatted ranges (valid case)', () => {
|
||||
const validateTimeRangeSpy = jest.spyOn(timeUtils, 'validateTimeRange');
|
||||
const onCustomDateHandler = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const validateTimeRangeSpy = vi.mocked(timeUtils.validateTimeRange);
|
||||
const onCustomDateHandler = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
validateTimeRangeSpy.mockReturnValue({
|
||||
isValid: true,
|
||||
@@ -244,9 +246,9 @@ describe('CustomTimePicker', () => {
|
||||
});
|
||||
|
||||
it('uses validateTimeRange result for generic formatted ranges (invalid case)', () => {
|
||||
const validateTimeRangeSpy = jest.spyOn(timeUtils, 'validateTimeRange');
|
||||
const onValid = jest.fn();
|
||||
const onError = jest.fn();
|
||||
const validateTimeRangeSpy = vi.mocked(timeUtils.validateTimeRange);
|
||||
const onValid = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
validateTimeRangeSpy.mockReturnValue({
|
||||
isValid: false,
|
||||
|
||||
@@ -2,23 +2,27 @@ import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import CustomTimePicker from '../CustomTimePicker';
|
||||
|
||||
const MS_PER_MIN = 60 * 1000;
|
||||
const NOW_MS = 1705312800000;
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
const mockSafeNavigate = jest.fn();
|
||||
const mockUrlQueryDelete = jest.fn();
|
||||
const mockUrlQuerySet = jest.fn();
|
||||
const { mockDispatch, mockSafeNavigate, mockUrlQueryDelete, mockUrlQuerySet } =
|
||||
vi.hoisted(() => ({
|
||||
mockDispatch: vi.fn(),
|
||||
mockSafeNavigate: vi.fn(),
|
||||
mockUrlQueryDelete: vi.fn(),
|
||||
mockUrlQuerySet: vi.fn(),
|
||||
}));
|
||||
|
||||
interface MockAppState {
|
||||
globalTime: Pick<GlobalReducer, 'minTime' | 'maxTime'>;
|
||||
}
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
useDispatch: (): jest.Mock => mockDispatch,
|
||||
vi.mock('react-redux', () => ({
|
||||
useDispatch: (): Mock => mockDispatch,
|
||||
useSelector: (selector: (state: MockAppState) => unknown): unknown => {
|
||||
const mockState: MockAppState = {
|
||||
globalTime: {
|
||||
@@ -30,8 +34,8 @@ jest.mock('react-redux', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
@@ -43,7 +47,7 @@ interface MockUrlQuery {
|
||||
toString: () => string;
|
||||
}
|
||||
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
vi.mock('hooks/useUrlQuery', () => ({
|
||||
__esModule: true,
|
||||
default: (): MockUrlQuery => ({
|
||||
delete: mockUrlQueryDelete,
|
||||
@@ -53,26 +57,26 @@ jest.mock('hooks/useUrlQuery', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('providers/Timezone', () => ({
|
||||
vi.mock('providers/Timezone', () => ({
|
||||
useTimezone: (): { timezone: { value: string; offset: string } } => ({
|
||||
timezone: { value: 'UTC', offset: 'UTC' },
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useLocation: (): { pathname: string } => ({ pathname: '/logs-explorer' }),
|
||||
}));
|
||||
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
const now = Date.now();
|
||||
const defaultProps = {
|
||||
onSelect: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
onSelect: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
selectedValue: '15m',
|
||||
selectedTime: '15m',
|
||||
onValidCustomDateChange: jest.fn(),
|
||||
onValidCustomDateChange: vi.fn(),
|
||||
open: false,
|
||||
setOpen: jest.fn(),
|
||||
setOpen: vi.fn(),
|
||||
items: [
|
||||
{ value: '15m', label: 'Last 15 minutes' },
|
||||
{ value: '1h', label: 'Last 1 hour' },
|
||||
@@ -83,12 +87,12 @@ const defaultProps = {
|
||||
|
||||
describe('CustomTimePicker - zoom out button', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(Date, 'now').mockReturnValue(NOW_MS);
|
||||
vi.clearAllMocks();
|
||||
vi.spyOn(Date, 'now').mockReturnValue(NOW_MS);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should render zoom out button when showLiveLogs is false', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Mock } from 'vitest';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
@@ -13,25 +14,29 @@ import '@testing-library/jest-dom';
|
||||
import { DownloadFormats, DownloadRowCounts } from './constants';
|
||||
import DownloadOptionsMenu from './DownloadOptionsMenu';
|
||||
|
||||
const mockDownloadExportData = jest.fn().mockResolvedValue(undefined);
|
||||
jest.mock('api/v1/download/downloadExportData', () => ({
|
||||
const { mockDownloadExportData } = vi.hoisted(() => ({
|
||||
mockDownloadExportData: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
vi.mock('api/v1/download/downloadExportData', () => ({
|
||||
downloadExportData: (...args: any[]): any => mockDownloadExportData(...args),
|
||||
default: (...args: any[]): any => mockDownloadExportData(...args),
|
||||
}));
|
||||
|
||||
jest.mock('antd', () => {
|
||||
const actual = jest.requireActual('antd');
|
||||
vi.mock('antd', async () => {
|
||||
const actual = await vi.importActual<typeof import('antd')>('antd');
|
||||
return {
|
||||
...actual,
|
||||
message: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const mockUseQueryBuilder = jest.fn();
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
const { mockUseQueryBuilder } = vi.hoisted(() => ({
|
||||
mockUseQueryBuilder: vi.fn(),
|
||||
}));
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: (): any => mockUseQueryBuilder(),
|
||||
}));
|
||||
|
||||
@@ -95,8 +100,8 @@ describe.each([
|
||||
|
||||
beforeEach(() => {
|
||||
mockDownloadExportData.mockReset().mockResolvedValue(undefined);
|
||||
(message.success as jest.Mock).mockReset();
|
||||
(message.error as jest.Mock).mockReset();
|
||||
(message.success as Mock).mockReset();
|
||||
(message.error as Mock).mockReset();
|
||||
mockUseQueryBuilder.mockReturnValue({
|
||||
stagedQuery: createMockStagedQuery(dataSource),
|
||||
});
|
||||
@@ -309,7 +314,11 @@ describe.each([
|
||||
fireEvent.click(screen.getByText('Export'));
|
||||
|
||||
expect(screen.getByTestId(testId)).toBeDisabled();
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
// The popover close animation keeps the dialog node mounted briefly in
|
||||
// a real browser; jsdom removed it synchronously.
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
resolveDownload!();
|
||||
|
||||
@@ -325,7 +334,7 @@ describe('DownloadOptionsMenu for traces with queryTraceOperator', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockDownloadExportData.mockReset().mockResolvedValue(undefined);
|
||||
(message.success as jest.Mock).mockReset();
|
||||
(message.success as Mock).mockReset();
|
||||
});
|
||||
|
||||
it('applies limit and clears groupBy on queryTraceOperator entries', async () => {
|
||||
|
||||
@@ -3,25 +3,9 @@ import { Table } from 'antd';
|
||||
|
||||
import DraggableTableRow from '..';
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('react-dnd', () => ({
|
||||
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
vi.mock('react-dnd', () => ({
|
||||
useDrop: vi.fn().mockImplementation(() => [vi.fn(), vi.fn(), vi.fn()]),
|
||||
useDrag: vi.fn().mockImplementation(() => [vi.fn(), vi.fn(), vi.fn()]),
|
||||
}));
|
||||
|
||||
describe('DraggableTableRow Snapshot test', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`DraggableTableRow Snapshot test should render DraggableTableRow 1`] = `
|
||||
exports[`DraggableTableRow Snapshot test > should render DraggableTableRow 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="ant-table-wrapper css-dev-only-do-not-override-2i2tap"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { dragHandler, dropHandler } from '../utils';
|
||||
|
||||
jest.mock('react-dnd', () => ({
|
||||
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
vi.mock('react-dnd', () => ({
|
||||
useDrop: vi.fn().mockImplementation(() => [vi.fn(), vi.fn(), vi.fn()]),
|
||||
useDrag: vi.fn().mockImplementation(() => [vi.fn(), vi.fn(), vi.fn()]),
|
||||
}));
|
||||
|
||||
describe('Utils testing of DraggableTableRow component', () => {
|
||||
it('Should dropHandler return true', () => {
|
||||
const monitor = {
|
||||
isOver: jest.fn().mockReturnValueOnce(true),
|
||||
isOver: vi.fn().mockReturnValueOnce(true),
|
||||
} as never;
|
||||
const dropDataTruthy = dropHandler(monitor);
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Utils testing of DraggableTableRow component', () => {
|
||||
|
||||
it('Should dropHandler return false', () => {
|
||||
const monitor = {
|
||||
isOver: jest.fn().mockReturnValueOnce(false),
|
||||
isOver: vi.fn().mockReturnValueOnce(false),
|
||||
} as never;
|
||||
const dropDataFalsy = dropHandler(monitor);
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('Utils testing of DraggableTableRow component', () => {
|
||||
|
||||
it('Should dragHandler return true', () => {
|
||||
const monitor = {
|
||||
isDragging: jest.fn().mockReturnValueOnce(true),
|
||||
isDragging: vi.fn().mockReturnValueOnce(true),
|
||||
} as never;
|
||||
const dragDataTruthy = dragHandler(monitor);
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('Utils testing of DraggableTableRow component', () => {
|
||||
|
||||
it('Should dragHandler return false', () => {
|
||||
const monitor = {
|
||||
isDragging: jest.fn().mockReturnValueOnce(false),
|
||||
isDragging: vi.fn().mockReturnValueOnce(false),
|
||||
} as never;
|
||||
const dragDataFalsy = dragHandler(monitor);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Mock } from 'vitest';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
@@ -17,30 +18,38 @@ import {
|
||||
managedRoles,
|
||||
} from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils-full';
|
||||
|
||||
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
useDeleteUserRole: jest.fn(),
|
||||
useUpdateUser: jest.fn(),
|
||||
useUpdateMyUserV2: jest.fn(),
|
||||
useCreateUserRole: jest.fn(),
|
||||
useGetResetPasswordToken: jest.fn(),
|
||||
useCreateResetPasswordToken: jest.fn(),
|
||||
vi.mock('api/generated/services/users', async () => ({
|
||||
...(await vi.importActual<typeof import('api/generated/services/users')>(
|
||||
'api/generated/services/users',
|
||||
)),
|
||||
useDeleteUser: vi.fn(),
|
||||
useGetUser: vi.fn(),
|
||||
useDeleteUserRole: vi.fn(),
|
||||
useUpdateUser: vi.fn(),
|
||||
useUpdateMyUserV2: vi.fn(),
|
||||
useCreateUserRole: vi.fn(),
|
||||
useGetResetPasswordToken: vi.fn(),
|
||||
useCreateResetPasswordToken: vi.fn(),
|
||||
getGetUserQueryKey: ({ id }: { id: string }): string[] => [
|
||||
`/api/v2/users/${id}`,
|
||||
],
|
||||
}));
|
||||
|
||||
jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
convertToApiError: jest.fn(),
|
||||
vi.mock('api/ErrorResponseHandlerForGeneratedAPIs', async () => ({
|
||||
...(await vi.importActual<
|
||||
typeof import('api/ErrorResponseHandlerForGeneratedAPIs')
|
||||
>('api/ErrorResponseHandlerForGeneratedAPIs')),
|
||||
convertToApiError: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/drawer', () => ({
|
||||
...jest.requireActual('@signozhq/ui/drawer'),
|
||||
vi.mock('@signozhq/ui/drawer', async () => ({
|
||||
...(await vi.importActual<typeof import('@signozhq/ui/drawer')>(
|
||||
'@signozhq/ui/drawer',
|
||||
)),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
@@ -58,8 +67,10 @@ jest.mock('@signozhq/ui/drawer', () => ({
|
||||
) : null,
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/dialog', () => ({
|
||||
...jest.requireActual('@signozhq/ui/dialog'),
|
||||
vi.mock('@signozhq/ui/dialog', async () => ({
|
||||
...(await vi.importActual<typeof import('@signozhq/ui/dialog')>(
|
||||
'@signozhq/ui/dialog',
|
||||
)),
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
@@ -82,18 +93,25 @@ jest.mock('@signozhq/ui/dialog', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
vi.mock('@signozhq/ui/sonner', async () => ({
|
||||
...(await vi.importActual<typeof import('@signozhq/ui/sonner')>(
|
||||
'@signozhq/ui/sonner',
|
||||
)),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
const mockCopyState = { value: undefined, error: undefined };
|
||||
const { mockCopyToClipboard, mockCopyState } = vi.hoisted(() => ({
|
||||
mockCopyToClipboard: vi.fn(),
|
||||
mockCopyState: { value: undefined, error: undefined } as {
|
||||
value: unknown;
|
||||
error: unknown;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
vi.mock('react-use', () => ({
|
||||
useCopyToClipboard: (): [typeof mockCopyState, typeof mockCopyToClipboard] => [
|
||||
mockCopyState,
|
||||
mockCopyToClipboard,
|
||||
@@ -102,15 +120,19 @@ jest.mock('react-use', () => ({
|
||||
|
||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
||||
|
||||
const mockDeleteMutate = jest.fn();
|
||||
const mockRemoveMutateAsync = jest.fn();
|
||||
const mockCreateTokenMutateAsync = jest.fn();
|
||||
const mockDeleteMutate = vi.fn();
|
||||
const mockRemoveMutateAsync = vi.fn();
|
||||
const mockCreateTokenMutateAsync = vi.fn();
|
||||
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
const { showErrorModal } = vi.hoisted(() => ({
|
||||
showErrorModal: vi.fn(),
|
||||
}));
|
||||
vi.mock('providers/ErrorModalProvider', async () => ({
|
||||
__esModule: true,
|
||||
...jest.requireActual('providers/ErrorModalProvider'),
|
||||
useErrorModal: jest.fn(() => ({
|
||||
...(await vi.importActual<typeof import('providers/ErrorModalProvider')>(
|
||||
'providers/ErrorModalProvider',
|
||||
)),
|
||||
useErrorModal: vi.fn(() => ({
|
||||
showErrorModal,
|
||||
isErrorModalVisible: false,
|
||||
})),
|
||||
@@ -169,8 +191,8 @@ function renderDrawer(
|
||||
<EditMemberDrawer
|
||||
member={activeMember}
|
||||
open
|
||||
onClose={jest.fn()}
|
||||
onComplete={jest.fn()}
|
||||
onClose={vi.fn()}
|
||||
onComplete={vi.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
@@ -178,7 +200,7 @@ function renderDrawer(
|
||||
|
||||
describe('EditMemberDrawer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockCopyState.value = undefined;
|
||||
mockCopyState.error = undefined;
|
||||
showErrorModal.mockClear();
|
||||
@@ -187,33 +209,33 @@ describe('EditMemberDrawer', () => {
|
||||
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
|
||||
),
|
||||
);
|
||||
(useGetUser as jest.Mock).mockReturnValue({
|
||||
(useGetUser as Mock).mockReturnValue({
|
||||
data: mockFetchedUser,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
(useDeleteUserRole as jest.Mock).mockReturnValue({
|
||||
(useDeleteUserRole as Mock).mockReturnValue({
|
||||
mutateAsync: mockRemoveMutateAsync.mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
(useUpdateUser as jest.Mock).mockReturnValue({
|
||||
mutateAsync: jest.fn().mockResolvedValue({}),
|
||||
(useUpdateUser as Mock).mockReturnValue({
|
||||
mutateAsync: vi.fn().mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
(useUpdateMyUserV2 as jest.Mock).mockReturnValue({
|
||||
mutateAsync: jest.fn().mockResolvedValue({}),
|
||||
(useUpdateMyUserV2 as Mock).mockReturnValue({
|
||||
mutateAsync: vi.fn().mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
(useCreateUserRole as jest.Mock).mockReturnValue({
|
||||
mutateAsync: jest.fn().mockResolvedValue({}),
|
||||
(useCreateUserRole as Mock).mockReturnValue({
|
||||
mutateAsync: vi.fn().mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
});
|
||||
(useDeleteUser as jest.Mock).mockReturnValue({
|
||||
(useDeleteUser as Mock).mockReturnValue({
|
||||
mutate: mockDeleteMutate,
|
||||
isLoading: false,
|
||||
});
|
||||
// Token query: valid token for invited members
|
||||
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
(useGetResetPasswordToken as Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
token: 'invite-tok-valid',
|
||||
@@ -233,7 +255,7 @@ describe('EditMemberDrawer', () => {
|
||||
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
||||
},
|
||||
});
|
||||
(useCreateResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
(useCreateResetPasswordToken as Mock).mockReturnValue({
|
||||
mutateAsync: mockCreateTokenMutateAsync,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -255,11 +277,11 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('enables Save after editing name and calls updateUser on confirm', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockMutateAsync = jest.fn().mockResolvedValue({});
|
||||
const mockMutateAsync = vi.fn().mockResolvedValue({});
|
||||
|
||||
(useUpdateUser as jest.Mock).mockReturnValue({
|
||||
(useUpdateUser as Mock).mockReturnValue({
|
||||
mutateAsync: mockMutateAsync,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -285,7 +307,7 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('does not close the drawer after a successful save', async () => {
|
||||
const onClose = jest.fn();
|
||||
const onClose = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
renderDrawer({ onClose });
|
||||
@@ -307,11 +329,11 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('adding a new role creates a user role without removing existing ones', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockSet = jest.fn().mockResolvedValue({});
|
||||
const mockSet = vi.fn().mockResolvedValue({});
|
||||
|
||||
(useCreateUserRole as jest.Mock).mockReturnValue({
|
||||
(useCreateUserRole as Mock).mockReturnValue({
|
||||
mutateAsync: mockSet,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -336,7 +358,7 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('deselecting a role deletes the user role by its assignment id', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
renderDrawer({ onComplete });
|
||||
@@ -361,10 +383,10 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('shows delete confirm dialog and calls deleteUser for active members', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
|
||||
(useDeleteUser as Mock).mockImplementation((options) => ({
|
||||
mutate: mockDeleteMutate.mockImplementation(() => {
|
||||
options?.mutation?.onSuccess?.();
|
||||
}),
|
||||
@@ -407,7 +429,7 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('shows "Regenerate Invite Link" when token is expired', () => {
|
||||
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
(useGetResetPasswordToken as Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
token: 'old-tok',
|
||||
@@ -427,7 +449,7 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('shows "Generate Invite Link" when no token exists', () => {
|
||||
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
(useGetResetPasswordToken as Mock).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
@@ -441,10 +463,10 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('calls deleteUser after confirming revoke invite for invited members', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
|
||||
(useDeleteUser as Mock).mockImplementation((options) => ({
|
||||
mutate: mockDeleteMutate.mockImplementation(() => {
|
||||
options?.mutation?.onSuccess?.();
|
||||
}),
|
||||
@@ -471,11 +493,11 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('calls updateUser when saving name change for an invited member', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockMutateAsync = jest.fn().mockResolvedValue({});
|
||||
const mockMutateAsync = vi.fn().mockResolvedValue({});
|
||||
|
||||
(useGetUser as jest.Mock).mockReturnValue({
|
||||
(useGetUser as Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
...mockFetchedUser.data,
|
||||
@@ -491,9 +513,9 @@ describe('EditMemberDrawer', () => {
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
(useUpdateUser as jest.Mock).mockReturnValue({
|
||||
(useUpdateUser as Mock).mockReturnValue({
|
||||
mutateAsync: mockMutateAsync,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -518,7 +540,7 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
const mockConvertToApiError = jest.mocked(convertToApiError);
|
||||
const mockConvertToApiError = vi.mocked(convertToApiError);
|
||||
|
||||
beforeEach(() => {
|
||||
mockConvertToApiError.mockReturnValue({
|
||||
@@ -529,8 +551,8 @@ describe('EditMemberDrawer', () => {
|
||||
it('shows SaveErrorItem when updateUser fails for name change', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
(useUpdateUser as jest.Mock).mockReturnValue({
|
||||
mutateAsync: jest.fn().mockRejectedValue(new Error('server error')),
|
||||
(useUpdateUser as Mock).mockReturnValue({
|
||||
mutateAsync: vi.fn().mockRejectedValue(new Error('server error')),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
@@ -554,7 +576,7 @@ describe('EditMemberDrawer', () => {
|
||||
it('shows API error message when deleteUser fails for active member', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
|
||||
(useDeleteUser as Mock).mockImplementation((options) => ({
|
||||
mutate: mockDeleteMutate.mockImplementation(() => {
|
||||
options?.mutation?.onError?.({});
|
||||
}),
|
||||
@@ -585,7 +607,7 @@ describe('EditMemberDrawer', () => {
|
||||
it('shows API error message when deleteUser fails for invited member', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
|
||||
(useDeleteUser as Mock).mockImplementation((options) => ({
|
||||
mutate: mockDeleteMutate.mockImplementation(() => {
|
||||
options?.mutation?.onError?.({});
|
||||
}),
|
||||
@@ -648,10 +670,10 @@ describe('EditMemberDrawer', () => {
|
||||
|
||||
describe('root user', () => {
|
||||
beforeEach(() => {
|
||||
(useGetUser as jest.Mock).mockReturnValue({
|
||||
(useGetUser as Mock).mockReturnValue({
|
||||
data: rootMockFetchedUser,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -731,7 +753,7 @@ describe('EditMemberDrawer', () => {
|
||||
|
||||
it('copies the link to clipboard and shows "Copied!" on the button', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockToast = jest.mocked(toast);
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
renderDrawer();
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import Editor from './index';
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: jest.fn(),
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('Editor', () => {
|
||||
@@ -34,7 +35,7 @@ describe('Editor', () => {
|
||||
});
|
||||
|
||||
it('renders with dark mode theme', () => {
|
||||
(useIsDarkMode as jest.Mock).mockImplementation(() => true);
|
||||
(useIsDarkMode as Mock).mockImplementation(() => true);
|
||||
|
||||
const { container } = render(<Editor value="dark mode text" />);
|
||||
|
||||
@@ -42,7 +43,7 @@ describe('Editor', () => {
|
||||
});
|
||||
|
||||
it('renders with light mode theme', () => {
|
||||
(useIsDarkMode as jest.Mock).mockImplementation(() => false);
|
||||
(useIsDarkMode as Mock).mockImplementation(() => false);
|
||||
|
||||
const { container } = render(<Editor value="light mode text" />);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Editor renders correctly with custom props 1`] = `
|
||||
exports[`Editor > renders correctly with custom props 1`] = `
|
||||
<div>
|
||||
<section
|
||||
style="display: flex; position: relative; text-align: initial; width: 100%; height: 50vh;"
|
||||
@@ -17,7 +17,7 @@ exports[`Editor renders correctly with custom props 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Editor renders correctly with default props 1`] = `
|
||||
exports[`Editor > renders correctly with default props 1`] = `
|
||||
<div>
|
||||
<section
|
||||
style="display: flex; position: relative; text-align: initial; width: 100%; height: 40vh;"
|
||||
@@ -34,7 +34,7 @@ exports[`Editor renders correctly with default props 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Editor renders with dark mode theme 1`] = `
|
||||
exports[`Editor > renders with dark mode theme 1`] = `
|
||||
<div>
|
||||
<section
|
||||
style="display: flex; position: relative; text-align: initial; width: 100%; height: 40vh;"
|
||||
@@ -51,7 +51,7 @@ exports[`Editor renders with dark mode theme 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Editor renders with light mode theme 1`] = `
|
||||
exports[`Editor > renders with light mode theme 1`] = `
|
||||
<div>
|
||||
<section
|
||||
style="display: flex; position: relative; text-align: initial; width: 100%; height: 40vh;"
|
||||
|
||||
@@ -6,8 +6,8 @@ import withErrorBoundary, {
|
||||
} from '../withErrorBoundary';
|
||||
|
||||
// Mock dependencies before imports
|
||||
jest.mock('@sentry/react', () => {
|
||||
const ReactMock = jest.requireActual('react');
|
||||
vi.mock('@sentry/react', async () => {
|
||||
const ReactMock = await vi.importActual<typeof import('react')>('react');
|
||||
|
||||
class MockErrorBoundary extends ReactMock.Component<
|
||||
{
|
||||
@@ -34,8 +34,8 @@ jest.mock('@sentry/react', () => {
|
||||
const { beforeCapture, onError } = this.props;
|
||||
if (beforeCapture) {
|
||||
const mockScope = {
|
||||
setTag: jest.fn(),
|
||||
setLevel: jest.fn(),
|
||||
setTag: vi.fn(),
|
||||
setLevel: vi.fn(),
|
||||
};
|
||||
beforeCapture(mockScope);
|
||||
}
|
||||
@@ -64,15 +64,11 @@ jest.mock('@sentry/react', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'../../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback',
|
||||
() =>
|
||||
function MockErrorBoundaryFallback(): JSX.Element {
|
||||
return (
|
||||
<div data-testid="default-error-fallback">Default Error Fallback</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
vi.mock('../../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback', () => ({
|
||||
default: function MockErrorBoundaryFallback(): JSX.Element {
|
||||
return <div data-testid="default-error-fallback">Default Error Fallback</div>;
|
||||
},
|
||||
}));
|
||||
|
||||
// Test component that can throw errors
|
||||
interface TestComponentProps {
|
||||
@@ -105,7 +101,7 @@ describe('withErrorBoundary', () => {
|
||||
// Suppress console errors for cleaner test output
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = jest.fn();
|
||||
console.error = vi.fn();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -113,7 +109,7 @@ describe('withErrorBoundary', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should wrap component with ErrorBoundary and render successfully', () => {
|
||||
@@ -162,7 +158,7 @@ describe('withErrorBoundary', () => {
|
||||
|
||||
it('should call custom error handler when error occurs', () => {
|
||||
// Arrange
|
||||
const mockErrorHandler = jest.fn();
|
||||
const mockErrorHandler = vi.fn();
|
||||
const options: WithErrorBoundaryOptions = {
|
||||
onError: mockErrorHandler,
|
||||
};
|
||||
|
||||
@@ -4,16 +4,18 @@ import APIError from 'types/api/error';
|
||||
import ErrorModal from './ErrorModal';
|
||||
|
||||
// Mock the query client to return version data
|
||||
const mockVersionData = {
|
||||
payload: {
|
||||
ee: 'Y',
|
||||
version: '1.0.0',
|
||||
const { mockVersionData } = vi.hoisted(() => ({
|
||||
mockVersionData: {
|
||||
payload: {
|
||||
ee: 'Y',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
};
|
||||
jest.mock('react-query', () => ({
|
||||
...jest.requireActual('react-query'),
|
||||
}));
|
||||
vi.mock('react-query', async () => ({
|
||||
...(await vi.importActual<typeof import('react-query')>('react-query')),
|
||||
useQueryClient: (): { getQueryData: () => typeof mockVersionData } => ({
|
||||
getQueryData: jest.fn(() => mockVersionData),
|
||||
getQueryData: vi.fn(() => mockVersionData),
|
||||
}),
|
||||
}));
|
||||
const mockError: APIError = new APIError({
|
||||
@@ -31,7 +33,7 @@ const mockError: APIError = new APIError({
|
||||
});
|
||||
describe('ErrorModal Component', () => {
|
||||
it('should render the modal when open is true', () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} open onClose={vi.fn()} />);
|
||||
|
||||
// Check if the error message is displayed
|
||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||
@@ -41,14 +43,14 @@ describe('ErrorModal Component', () => {
|
||||
});
|
||||
|
||||
it('should not render the modal when open is false', () => {
|
||||
render(<ErrorModal error={mockError} open={false} onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} open={false} onClose={vi.fn()} />);
|
||||
|
||||
// Check that the modal content is not in the document
|
||||
expect(screen.queryByText('An error occurred')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClose when the close button is clicked', async () => {
|
||||
const onCloseMock = jest.fn();
|
||||
const onCloseMock = vi.fn();
|
||||
render(<ErrorModal error={mockError} open onClose={onCloseMock} />);
|
||||
|
||||
// Click the close button
|
||||
@@ -61,14 +63,14 @@ describe('ErrorModal Component', () => {
|
||||
});
|
||||
|
||||
it('should display version data if available', async () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} open onClose={vi.fn()} />);
|
||||
|
||||
// Check if the version data is displayed
|
||||
expect(screen.getByText('ENTERPRISE')).toBeInTheDocument();
|
||||
expect(screen.getByText('1.0.0')).toBeInTheDocument();
|
||||
});
|
||||
it('should render the messages count badge when there are multiple errors', () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} open onClose={vi.fn()} />);
|
||||
|
||||
// Check if the messages count badge is displayed
|
||||
expect(screen.getByText('MESSAGES')).toBeInTheDocument();
|
||||
@@ -82,7 +84,7 @@ describe('ErrorModal Component', () => {
|
||||
});
|
||||
|
||||
it('should render the open docs button when URL is provided', async () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} open onClose={vi.fn()} />);
|
||||
|
||||
// Check if the open docs button is displayed
|
||||
const openDocsButton = screen.getByTestId('error-docs-button');
|
||||
@@ -95,7 +97,7 @@ describe('ErrorModal Component', () => {
|
||||
});
|
||||
|
||||
it('should not display scroll for more if there are less than 10 messages', () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} open onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.queryByText('Scroll for more')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -113,7 +115,7 @@ describe('ErrorModal Component', () => {
|
||||
},
|
||||
});
|
||||
|
||||
render(<ErrorModal error={longError} open onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={longError} open onClose={vi.fn()} />);
|
||||
|
||||
// Check if the scroll hint is displayed
|
||||
expect(screen.getByText('Scroll for more')).toBeInTheDocument();
|
||||
@@ -125,7 +127,7 @@ it('should render the trigger component if provided', () => {
|
||||
<ErrorModal
|
||||
error={mockError}
|
||||
triggerComponent={mockTrigger}
|
||||
onClose={jest.fn()}
|
||||
onClose={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -139,7 +141,7 @@ it('should open the modal when the trigger component is clicked', async () => {
|
||||
<ErrorModal
|
||||
error={mockError}
|
||||
triggerComponent={mockTrigger}
|
||||
onClose={jest.fn()}
|
||||
onClose={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -153,14 +155,36 @@ it('should open the modal when the trigger component is clicked', async () => {
|
||||
});
|
||||
|
||||
it('should render the default trigger tag if no trigger component is provided', () => {
|
||||
render(<ErrorModal error={mockError} onClose={jest.fn()} />);
|
||||
render(<ErrorModal error={mockError} onClose={vi.fn()} />);
|
||||
|
||||
// Check if the default trigger tag is rendered
|
||||
expect(screen.getByText('error')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should close the modal when the onCancel event is triggered', async () => {
|
||||
const onCloseMock = jest.fn();
|
||||
const onCloseMock = vi.fn();
|
||||
render(<ErrorModal error={mockError} onClose={onCloseMock} />);
|
||||
|
||||
// Click the trigger component
|
||||
const triggerButton = screen.getByText('error');
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
await user.click(triggerButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Trigger the onCancel event
|
||||
await user.click(screen.getByTestId('close-button'));
|
||||
|
||||
// Check if the modal is closed
|
||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// jsdom never fires CSS transition events and antd sets no motionDeadline, so
|
||||
// the leave motion that sets display:none never completes there (passes in browser).
|
||||
it.skip('should hide the modal element after close', async () => {
|
||||
const onCloseMock = vi.fn();
|
||||
render(<ErrorModal error={mockError} onClose={onCloseMock} />);
|
||||
|
||||
// Click the trigger component
|
||||
|
||||
@@ -7,53 +7,59 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
const historyReplace = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
useHistory: (): any => ({
|
||||
...jest.requireActual('react-router-dom').useHistory(),
|
||||
replace: historyReplace,
|
||||
}),
|
||||
const { historyReplace } = vi.hoisted(() => ({
|
||||
historyReplace: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
useHistory: (): any => ({
|
||||
...(actual as any).useHistory(),
|
||||
replace: historyReplace,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): any => ({
|
||||
safeNavigate: jest.fn(),
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
vi.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: vi.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useGetAllViews', () => ({
|
||||
useGetAllViews: jest.fn(() => ({
|
||||
vi.mock('hooks/saveViews/useGetAllViews', () => ({
|
||||
useGetAllViews: vi.fn(() => ({
|
||||
data: { data: { data: viewMockData } },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
isRefetching: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useUpdateView', () => ({
|
||||
useUpdateView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
vi.mock('hooks/saveViews/useUpdateView', () => ({
|
||||
useUpdateView: vi.fn(() => ({
|
||||
mutateAsync: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useDeleteView', () => ({
|
||||
useDeleteView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
vi.mock('hooks/saveViews/useDeleteView', () => ({
|
||||
useDeleteView: vi.fn(() => ({
|
||||
mutateAsync: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock usePreferenceSync
|
||||
jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
vi.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
usePreferenceSync: (): any => ({
|
||||
preferences: {
|
||||
columns: [],
|
||||
@@ -66,8 +72,8 @@ jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
updateColumns: jest.fn(),
|
||||
updateFormatting: jest.fn(),
|
||||
updateColumns: vi.fn(),
|
||||
updateFormatting: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -6,19 +6,26 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('antd', () => ({
|
||||
...jest.requireActual('antd'),
|
||||
useForm: jest.fn().mockReturnValue({
|
||||
onFinish: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
vi.mock('antd', async () => {
|
||||
const actual = await vi.importActual<typeof import('antd')>('antd');
|
||||
return {
|
||||
...actual,
|
||||
useForm: vi.fn().mockReturnValue({
|
||||
onFinish: vi.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('MenuItemGenerator', () => {
|
||||
it('should render MenuItemGenerator component', () => {
|
||||
@@ -29,7 +36,7 @@ describe('MenuItemGenerator', () => {
|
||||
viewKey={viewMockData[0].id}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].id}
|
||||
refetchAllView={jest.fn()}
|
||||
refetchAllView={vi.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
@@ -47,7 +54,7 @@ describe('MenuItemGenerator', () => {
|
||||
viewKey={viewMockData[0].id}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].id}
|
||||
refetchAllView={jest.fn()}
|
||||
refetchAllView={vi.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
|
||||
@@ -5,12 +5,16 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -20,13 +24,13 @@ const queryClient = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
vi.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: vi.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useSaveView', () => ({
|
||||
useSaveView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
vi.mock('hooks/saveViews/useSaveView', () => ({
|
||||
useSaveView: vi.fn(() => ({
|
||||
mutateAsync: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -36,8 +40,8 @@ describe('SaveViewWithName', () => {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
handlePopOverClose={vi.fn()}
|
||||
refetchAllView={vi.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
@@ -50,8 +54,8 @@ describe('SaveViewWithName', () => {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
handlePopOverClose={vi.fn()}
|
||||
refetchAllView={vi.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
@@ -4,12 +4,14 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import ExportMenu from '../ExportMenu';
|
||||
|
||||
const mockHandleExport = jest.fn();
|
||||
let mockIsExporting = false;
|
||||
const { mockHandleExport, mockExportState } = vi.hoisted(() => ({
|
||||
mockHandleExport: vi.fn(),
|
||||
mockExportState: { isExporting: false },
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useExportData/useClientExport', () => ({
|
||||
vi.mock('hooks/useExportData/useClientExport', () => ({
|
||||
useClientExport: (): unknown => ({
|
||||
isExporting: mockIsExporting,
|
||||
isExporting: mockExportState.isExporting,
|
||||
handleExport: mockHandleExport,
|
||||
}),
|
||||
}));
|
||||
@@ -36,7 +38,7 @@ function renderMenu(): void {
|
||||
describe('ExportMenu', () => {
|
||||
beforeEach(() => {
|
||||
mockHandleExport.mockReset();
|
||||
mockIsExporting = false;
|
||||
mockExportState.isExporting = false;
|
||||
});
|
||||
|
||||
it('renders the download trigger button', () => {
|
||||
@@ -77,7 +79,7 @@ describe('ExportMenu', () => {
|
||||
});
|
||||
|
||||
it('disables the trigger while an export is in progress', () => {
|
||||
mockIsExporting = true;
|
||||
mockExportState.isExporting = true;
|
||||
renderMenu();
|
||||
|
||||
expect(screen.getByTestId(TEST_ID)).toBeDisabled();
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('AddedFields — requiredFields', () => {
|
||||
const fields = [makeField('a'), makeField('b'), makeField('c')];
|
||||
|
||||
render(
|
||||
<AddedFields inputValue="" fields={fields} onFieldsChange={jest.fn()} />,
|
||||
<AddedFields inputValue="" fields={fields} onFieldsChange={vi.fn()} />,
|
||||
);
|
||||
|
||||
expect(screen.getAllByRole('button', { name: /remove/i })).toHaveLength(3);
|
||||
@@ -32,7 +32,7 @@ describe('AddedFields — requiredFields', () => {
|
||||
<AddedFields
|
||||
inputValue=""
|
||||
fields={fields}
|
||||
onFieldsChange={jest.fn()}
|
||||
onFieldsChange={vi.fn()}
|
||||
requiredFields={['log:a', 'log:c']}
|
||||
/>,
|
||||
);
|
||||
@@ -49,7 +49,7 @@ describe('AddedFields — requiredFields', () => {
|
||||
<AddedFields
|
||||
inputValue=""
|
||||
fields={fields}
|
||||
onFieldsChange={jest.fn()}
|
||||
onFieldsChange={vi.fn()}
|
||||
requiredFields={['log:a']}
|
||||
/>,
|
||||
);
|
||||
@@ -67,7 +67,7 @@ describe('AddedFields — requiredFields', () => {
|
||||
<AddedFields
|
||||
inputValue=""
|
||||
fields={fields}
|
||||
onFieldsChange={jest.fn()}
|
||||
onFieldsChange={vi.fn()}
|
||||
requiredFields={['log:body']}
|
||||
/>,
|
||||
);
|
||||
@@ -84,7 +84,7 @@ describe('AddedFields — requiredFields', () => {
|
||||
<AddedFields
|
||||
inputValue=""
|
||||
fields={fields}
|
||||
onFieldsChange={jest.fn()}
|
||||
onFieldsChange={vi.fn()}
|
||||
requiredFields={['body']}
|
||||
/>,
|
||||
);
|
||||
@@ -100,7 +100,7 @@ describe('AddedFields — requiredFields', () => {
|
||||
<AddedFields
|
||||
inputValue=""
|
||||
fields={fields}
|
||||
onFieldsChange={jest.fn()}
|
||||
onFieldsChange={vi.fn()}
|
||||
requiredFields={['log:body']}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -4,30 +4,31 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import FieldsSelector from '../FieldsSelector';
|
||||
import { useFieldKeysSuggestion } from 'hooks/querySuggestions/useFieldKeysSuggestion';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
jest.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
|
||||
useFieldKeysSuggestion: jest.fn(() => ({
|
||||
vi.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
|
||||
useFieldKeysSuggestion: vi.fn(() => ({
|
||||
data: undefined,
|
||||
isFetching: false,
|
||||
isFetched: true,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui/sonner', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui/sonner')),
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
// FloatingPanel is a react-rnd/portal shell — presentation only. Render its
|
||||
// children directly so the test exercises the column-editing behavior.
|
||||
jest.mock('periscope/components/FloatingPanel', () => ({
|
||||
vi.mock('periscope/components/FloatingPanel', () => ({
|
||||
FloatingPanel: ({ children }: { children: React.ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockSuggestions = (names: string[]): void => {
|
||||
(useFieldKeysSuggestion as jest.Mock).mockReturnValue({
|
||||
(useFieldKeysSuggestion as Mock).mockReturnValue({
|
||||
data: names.map((name) => ({
|
||||
name,
|
||||
signal: 'logs',
|
||||
@@ -48,15 +49,15 @@ const field = (name: string, fieldContext = 'log'): TelemetryFieldKey => ({
|
||||
|
||||
const renderPanel = (
|
||||
props: Partial<React.ComponentProps<typeof FieldsSelector>> = {},
|
||||
): { onFieldsChange: jest.Mock } => {
|
||||
const onFieldsChange = jest.fn();
|
||||
): { onFieldsChange: Mock } => {
|
||||
const onFieldsChange = vi.fn();
|
||||
render(
|
||||
<FieldsSelector
|
||||
isOpen
|
||||
title="Edit columns"
|
||||
fields={props.fields ?? []}
|
||||
onFieldsChange={onFieldsChange}
|
||||
onClose={jest.fn()}
|
||||
onClose={vi.fn()}
|
||||
signal={DataSource.LOGS}
|
||||
allowCustomFields
|
||||
{...props}
|
||||
@@ -73,19 +74,19 @@ const typeSearch = (value: string): void => {
|
||||
fireEvent.change(input, { target: { value } });
|
||||
});
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(400);
|
||||
vi.advanceTimersByTime(400);
|
||||
});
|
||||
};
|
||||
|
||||
describe('FieldsSelector — edit columns (integration)', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
vi.useFakeTimers();
|
||||
mockSuggestions([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
jest.useRealTimers();
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('adds a free-typed field end to end and saves the synthesized key', () => {
|
||||
|
||||
@@ -6,9 +6,10 @@ import { BuilderQueryType, TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
import { DATA_SOURCE_TO_SIGNAL, DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import OtherFields from '../OtherFields';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
jest.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
|
||||
useFieldKeysSuggestion: jest.fn(() => ({
|
||||
vi.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
|
||||
useFieldKeysSuggestion: vi.fn(() => ({
|
||||
data: undefined,
|
||||
isFetching: false,
|
||||
isFetched: true,
|
||||
@@ -16,7 +17,7 @@ jest.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
|
||||
}));
|
||||
|
||||
const mockSuggestions = (names: string[]): void => {
|
||||
(useFieldKeysSuggestion as jest.Mock).mockReturnValue({
|
||||
(useFieldKeysSuggestion as Mock).mockReturnValue({
|
||||
data: names.map((name) => ({
|
||||
name,
|
||||
signal: 'logs',
|
||||
@@ -30,8 +31,8 @@ const mockSuggestions = (names: string[]): void => {
|
||||
|
||||
const renderOtherFields = (
|
||||
props: Partial<React.ComponentProps<typeof OtherFields>> = {},
|
||||
): { onAdd: jest.Mock } => {
|
||||
const onAdd = jest.fn();
|
||||
): { onAdd: Mock } => {
|
||||
const onAdd = vi.fn();
|
||||
render(
|
||||
<OtherFields
|
||||
signal={DataSource.LOGS}
|
||||
@@ -135,7 +136,7 @@ describe('OtherFields — field keys config', () => {
|
||||
const builderQueryType: BuilderQueryType = 'builder_ai_query';
|
||||
|
||||
const mockPool = (fields: TelemetryFieldKey[]): void => {
|
||||
(useFieldKeysSuggestion as jest.Mock).mockReturnValue({
|
||||
(useFieldKeysSuggestion as Mock).mockReturnValue({
|
||||
data: fields,
|
||||
isFetching: false,
|
||||
isFetched: true,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Mock, Mocked } from 'vitest';
|
||||
|
||||
// Mock dependencies before imports
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
@@ -9,33 +11,33 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
vi.mock('react-router-dom', async () => ({
|
||||
...(await vi.importActual('react-router-dom')),
|
||||
useLocation: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
vi.mock('@signozhq/ui/sonner', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui/sonner')),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: jest.fn(),
|
||||
vi.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('container/Integrations/utils', () => ({
|
||||
handleContactSupport: jest.fn(),
|
||||
vi.mock('container/Integrations/utils', () => ({
|
||||
handleContactSupport: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
const mockHandleContactSupport = handleContactSupport as jest.Mock;
|
||||
const mockToast = toast as jest.Mocked<typeof toast>;
|
||||
const mockUseLocation = useLocation as Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as Mock;
|
||||
const mockHandleContactSupport = handleContactSupport as Mock;
|
||||
const mockToast = toast as Mocked<typeof toast>;
|
||||
|
||||
const mockOnClose = jest.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
|
||||
const mockLocation = {
|
||||
pathname: '/test-path',
|
||||
@@ -43,7 +45,7 @@ const mockLocation = {
|
||||
|
||||
describe('FeedbackModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
logEventMock.mockReturnValue(Promise.resolve() as never);
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
// Mock dependencies before imports
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { logEventMock } from '__tests__/logEventMock';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import HeaderRightSection from '../HeaderRightSection';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
vi.mock('react-router-dom', async () => ({
|
||||
...(await vi.importActual('react-router-dom')),
|
||||
useLocation: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../FeedbackModal', () => ({
|
||||
vi.mock('../FeedbackModal', () => ({
|
||||
__esModule: true,
|
||||
default: ({ onClose }: { onClose: () => void }): JSX.Element => (
|
||||
<div data-testid="feedback-modal">
|
||||
@@ -23,30 +25,30 @@ jest.mock('../FeedbackModal', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../ShareURLModal', () => ({
|
||||
vi.mock('../ShareURLModal', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => (
|
||||
<div data-testid="share-modal">Share URL Modal</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../AnnouncementsModal', () => ({
|
||||
vi.mock('../AnnouncementsModal', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => (
|
||||
<div data-testid="announcements-modal">Announcements Modal</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: jest.fn(),
|
||||
vi.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useIsAIAssistantEnabled', () => ({
|
||||
vi.mock('hooks/useIsAIAssistantEnabled', () => ({
|
||||
useIsAIAssistantEnabled: (): boolean => false,
|
||||
}));
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
const mockUseLocation = useLocation as Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as Mock;
|
||||
|
||||
const defaultProps = {
|
||||
enableAnnouncements: true,
|
||||
@@ -60,7 +62,7 @@ const mockLocation = {
|
||||
|
||||
describe('HeaderRightSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
// Default to licensed user (Enterprise or Cloud)
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
@@ -163,7 +165,10 @@ describe('HeaderRightSection', () => {
|
||||
// Close feedback modal
|
||||
const closeFeedbackButton = screen.getByText('Close Feedback');
|
||||
await user.click(closeFeedbackButton);
|
||||
expect(screen.queryByTestId('feedback-modal')).not.toBeInTheDocument();
|
||||
// Popover exit animation outlives the state flip in a real browser
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('feedback-modal')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should close other modals when opening feedback modal', async () => {
|
||||
@@ -181,7 +186,10 @@ describe('HeaderRightSection', () => {
|
||||
|
||||
await user.click(feedbackButton!);
|
||||
expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('share-modal')).not.toBeInTheDocument();
|
||||
// Popover exit animation outlives the state flip in a real browser
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('share-modal')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show feedback button for Cloud users when feedback is enabled', () => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
// Mock dependencies before imports
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -12,64 +14,54 @@ import GetMinMax from 'lib/getMinMax';
|
||||
|
||||
import ShareURLModal from '../ShareURLModal';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
matchPath: jest.fn(),
|
||||
vi.mock('react-router-dom', async () => ({
|
||||
...(await vi.importActual('react-router-dom')),
|
||||
useLocation: vi.fn(),
|
||||
matchPath: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
vi.mock('hooks/useUrlQuery', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
useSelector: jest.fn(),
|
||||
vi.mock('react-redux', async () => ({
|
||||
...(await vi.importActual('react-redux')),
|
||||
useSelector: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('lib/getMinMax', () => ({
|
||||
vi.mock('lib/getMinMax', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
...jest.requireActual('react-use'),
|
||||
useCopyToClipboard: jest.fn(),
|
||||
vi.mock('react-use', async () => ({
|
||||
...(await vi.importActual('react-use')),
|
||||
useCopyToClipboard: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock window.location
|
||||
const mockLocation = {
|
||||
href: 'https://example.com/test-path?param=value',
|
||||
origin: 'https://example.com',
|
||||
};
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: mockLocation,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseUrlQuery = useUrlQuery as jest.Mock;
|
||||
const mockUseSelector = useSelector as jest.Mock;
|
||||
const mockGetMinMax = GetMinMax as jest.Mock;
|
||||
const mockUseCopyToClipboard = useCopyToClipboard as jest.Mock;
|
||||
const mockMatchPath = matchPath as jest.Mock;
|
||||
const mockUseLocation = useLocation as Mock;
|
||||
const mockUseUrlQuery = useUrlQuery as Mock;
|
||||
const mockUseSelector = useSelector as Mock;
|
||||
const mockGetMinMax = GetMinMax as Mock;
|
||||
const mockUseCopyToClipboard = useCopyToClipboard as Mock;
|
||||
const mockMatchPath = matchPath as Mock;
|
||||
|
||||
const mockUrlQuery = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
toString: jest.fn(() => 'param=value'),
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
toString: vi.fn(() => 'param=value'),
|
||||
};
|
||||
|
||||
const mockHandleCopyToClipboard = jest.fn();
|
||||
const mockHandleCopyToClipboard = vi.fn();
|
||||
|
||||
const TEST_PATH = '/test-path';
|
||||
const ENABLE_ABSOLUTE_TIME_TEXT = 'Enable absolute time';
|
||||
|
||||
describe('ShareURLModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockUseLocation.mockReturnValue({
|
||||
pathname: TEST_PATH,
|
||||
|
||||
@@ -154,7 +154,7 @@ describe('InviteMembers - Edge Cases', () => {
|
||||
describe('empty submission', () => {
|
||||
it('does not submit when no rows are touched', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onSuccess = jest.fn();
|
||||
const onSuccess = vi.fn();
|
||||
|
||||
render(
|
||||
<InviteMembers
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('InviteMembers - Submission', () => {
|
||||
describe('callbacks', () => {
|
||||
it('calls onSuccess when all invites succeed', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onSuccess = jest.fn();
|
||||
const onSuccess = vi.fn();
|
||||
|
||||
render(
|
||||
<InviteMembers
|
||||
@@ -137,7 +137,7 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
it('calls onAllFailed when all invites fail', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onAllFailed = jest.fn();
|
||||
const onAllFailed = vi.fn();
|
||||
|
||||
server.use(createErrorHandler('already_exists', 'User already exists'));
|
||||
|
||||
@@ -174,9 +174,9 @@ describe('InviteMembers - Submission', () => {
|
||||
|
||||
it('calls onPartialSuccess when some invites succeed and some fail', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onPartialSuccess = jest.fn();
|
||||
const onSuccess = jest.fn();
|
||||
const onAllFailed = jest.fn();
|
||||
const onPartialSuccess = vi.fn();
|
||||
const onSuccess = vi.fn();
|
||||
const onAllFailed = vi.fn();
|
||||
const apiCalls: string[] = [];
|
||||
let callCount = 0;
|
||||
|
||||
@@ -233,19 +233,21 @@ describe('InviteMembers - Submission', () => {
|
||||
});
|
||||
|
||||
expect(apiCalls).toStrictEqual(['alice@signoz.io', 'bob@signoz.io']);
|
||||
await waitFor(() => {
|
||||
expect(onPartialSuccess).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: 'alice@signoz.io', success: true }),
|
||||
expect.objectContaining({
|
||||
email: 'bob@signoz.io',
|
||||
success: false,
|
||||
error: 'User already exists',
|
||||
}),
|
||||
]),
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
expect(onSuccess).not.toHaveBeenCalled();
|
||||
expect(onAllFailed).not.toHaveBeenCalled();
|
||||
expect(onPartialSuccess).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ email: 'alice@signoz.io', success: true }),
|
||||
expect.objectContaining({
|
||||
email: 'bob@signoz.io',
|
||||
success: false,
|
||||
error: 'User already exists',
|
||||
}),
|
||||
]),
|
||||
expect.any(Array),
|
||||
);
|
||||
|
||||
await expect(
|
||||
screen.findByTestId('invite-api-error'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ComponentType, Suspense } from 'react';
|
||||
import { ComponentType, lazy, Suspense } from 'react';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
|
||||
import Loadable from './index';
|
||||
|
||||
// ESM namespace is frozen in browser mode, so spy through the module mock
|
||||
vi.mock('react', { spy: true });
|
||||
|
||||
// Sample component to be loaded lazily
|
||||
function SampleComponent(): JSX.Element {
|
||||
return <div>Sample Component</div>;
|
||||
@@ -38,7 +41,7 @@ describe('Loadable', () => {
|
||||
});
|
||||
|
||||
it('should call lazy with the provided import path', () => {
|
||||
const reactLazySpy = jest.spyOn(React, 'lazy');
|
||||
const reactLazySpy = vi.mocked(lazy);
|
||||
Loadable(loadSampleComponent);
|
||||
|
||||
expect(reactLazySpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
import { render, screen, userEvent } from 'tests/test-utils-full';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import LogDetail from '..';
|
||||
import { VIEW_TYPES } from '../constants';
|
||||
import { LogDetailProps } from '../LogDetail.interfaces';
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui/sonner', () => ({
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
// DataViewer pulls in react-json-tree (ESM) + Monaco; mock it (as trace's tests
|
||||
// do). These drawer tests assert the header/highlights, not the Overview body.
|
||||
jest.mock('periscope/components/DataViewer', () => ({
|
||||
vi.mock('periscope/components/DataViewer', () => ({
|
||||
__esModule: true,
|
||||
DataViewer: (): JSX.Element => <div data-testid="overview-data-viewer" />,
|
||||
}));
|
||||
|
||||
// Force v2 for these tests regardless of route.
|
||||
jest.mock('../useIsLogDetailsV2', () => ({
|
||||
vi.mock('../useIsLogDetailsV2', () => ({
|
||||
useIsLogDetailsV2: (): boolean => true,
|
||||
}));
|
||||
|
||||
@@ -50,9 +50,9 @@ function renderDrawer(props: Partial<LogDetailProps> = {}): void {
|
||||
<LogDetail
|
||||
log={mockLog}
|
||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||
onAddToQuery={jest.fn()}
|
||||
onClickActionItem={jest.fn()}
|
||||
onClose={jest.fn()}
|
||||
onAddToQuery={vi.fn()}
|
||||
onClickActionItem={vi.fn()}
|
||||
onClose={vi.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
@@ -60,7 +60,7 @@ function renderDrawer(props: Partial<LogDetailProps> = {}): void {
|
||||
|
||||
describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
});
|
||||
|
||||
it('shows "Open in Explorer" when a handleOpenInExplorer handler is provided', () => {
|
||||
renderDrawer({ handleOpenInExplorer: jest.fn() });
|
||||
renderDrawer({ handleOpenInExplorer: vi.fn() });
|
||||
|
||||
expect(screen.getByText('Open in Explorer')).toBeInTheDocument();
|
||||
});
|
||||
@@ -187,8 +187,8 @@ describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
it('navigates to the next / previous log with the Down / Up arrow keys', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1'), makeLog('log-2')];
|
||||
const onNavigateLog = jest.fn();
|
||||
const onScrollToLog = jest.fn();
|
||||
const onNavigateLog = vi.fn();
|
||||
const onScrollToLog = vi.fn();
|
||||
|
||||
// Active log is the middle one so both directions are available.
|
||||
renderDrawer({ log: logs[1], logs, onNavigateLog, onScrollToLog });
|
||||
@@ -205,7 +205,7 @@ describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
it('does not navigate past the first log on ArrowUp', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1')];
|
||||
const onNavigateLog = jest.fn();
|
||||
const onNavigateLog = vi.fn();
|
||||
|
||||
renderDrawer({ log: logs[0], logs, onNavigateLog });
|
||||
|
||||
@@ -216,7 +216,7 @@ describe('LogDetail drawer — header (isLogDetailsV2)', () => {
|
||||
it('navigates via the header up / down buttons and disables them at boundaries', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const logs = [makeLog('log-0'), makeLog('log-1')];
|
||||
const onNavigateLog = jest.fn();
|
||||
const onNavigateLog = vi.fn();
|
||||
|
||||
// Active log is the first one.
|
||||
renderDrawer({ log: logs[0], logs, onNavigateLog });
|
||||
|
||||
@@ -2,15 +2,17 @@ import { renderHook } from '@testing-library/react';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import { useLogsTableColumns } from '../useLogsTableColumns';
|
||||
|
||||
jest.mock('providers/Timezone', () => ({
|
||||
useTimezone: (): { formatTimezoneAdjustedTimestamp: jest.Mock } => ({
|
||||
formatTimezoneAdjustedTimestamp: jest.fn(() => 'TS'),
|
||||
vi.mock('providers/Timezone', () => ({
|
||||
useTimezone: (): { formatTimezoneAdjustedTimestamp: Mock } => ({
|
||||
formatTimezoneAdjustedTimestamp: vi.fn(() => 'TS'),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('providers/App/App', () => ({
|
||||
vi.mock('providers/App/App', () => ({
|
||||
useAppContext: (): { featureFlags: [] } => ({ featureFlags: [] }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { fireEvent, render, waitFor } from 'tests/test-utils';
|
||||
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import LogsFormatOptionsMenu from '../LogsFormatOptionsMenu';
|
||||
|
||||
const mockUpdateFormatting = jest.fn();
|
||||
const { mockUpdateFormatting } = vi.hoisted(() => ({
|
||||
mockUpdateFormatting: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
vi.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
usePreferenceSync: (): any => ({
|
||||
preferences: {
|
||||
columns: [],
|
||||
@@ -18,7 +22,7 @@ jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
updateColumns: jest.fn(),
|
||||
updateColumns: vi.fn(),
|
||||
updateFormatting: mockUpdateFormatting,
|
||||
}),
|
||||
}));
|
||||
@@ -31,9 +35,9 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
function setup(): {
|
||||
getByTestId: ReturnType<typeof render>['getByTestId'];
|
||||
findItemByLabel: (label: string) => Element | undefined;
|
||||
formatOnChange: jest.Mock<any, any>;
|
||||
maxLinesOnChange: jest.Mock<any, any>;
|
||||
fontSizeOnChange: jest.Mock<any, any>;
|
||||
formatOnChange: Mock;
|
||||
maxLinesOnChange: Mock;
|
||||
fontSizeOnChange: Mock;
|
||||
} {
|
||||
const items = [
|
||||
{ key: 'raw', label: 'Raw', data: { title: 'max lines per row' } },
|
||||
@@ -41,9 +45,9 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
{ key: 'table', label: 'Column', data: { title: 'columns' } },
|
||||
];
|
||||
|
||||
const formatOnChange = jest.fn();
|
||||
const maxLinesOnChange = jest.fn();
|
||||
const fontSizeOnChange = jest.fn();
|
||||
const formatOnChange = vi.fn();
|
||||
const maxLinesOnChange = vi.fn();
|
||||
const fontSizeOnChange = vi.fn();
|
||||
|
||||
const { getByTestId } = render(
|
||||
<LogsFormatOptionsMenu
|
||||
@@ -57,12 +61,12 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
isFetching: false,
|
||||
value: [],
|
||||
options: [],
|
||||
onFocus: jest.fn(),
|
||||
onBlur: jest.fn(),
|
||||
onSearch: jest.fn(),
|
||||
onSelect: jest.fn(),
|
||||
onRemove: jest.fn(),
|
||||
onReorder: jest.fn(),
|
||||
onFocus: vi.fn(),
|
||||
onBlur: vi.fn(),
|
||||
onSearch: vi.fn(),
|
||||
onSelect: vi.fn(),
|
||||
onRemove: vi.fn(),
|
||||
onReorder: vi.fn(),
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
@@ -157,7 +161,7 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
});
|
||||
|
||||
function renderWithOnOpen(
|
||||
onOpenColumns?: jest.Mock,
|
||||
onOpenColumns?: Mock,
|
||||
selectedOptionFormat: 'table' | 'raw' | 'list' = 'table',
|
||||
): { getByTestId: ReturnType<typeof render>['getByTestId'] } {
|
||||
const items = [
|
||||
@@ -171,9 +175,9 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
items={items}
|
||||
selectedOptionFormat={selectedOptionFormat}
|
||||
config={{
|
||||
format: { value: selectedOptionFormat, onChange: jest.fn() },
|
||||
maxLines: { value: 1, onChange: jest.fn() },
|
||||
fontSize: { value: FontSize.SMALL, onChange: jest.fn() },
|
||||
format: { value: selectedOptionFormat, onChange: vi.fn() },
|
||||
maxLines: { value: 1, onChange: vi.fn() },
|
||||
fontSize: { value: FontSize.SMALL, onChange: vi.fn() },
|
||||
}}
|
||||
onOpenColumns={onOpenColumns}
|
||||
/>,
|
||||
@@ -183,7 +187,7 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
}
|
||||
|
||||
it('renders "Edit columns" row when format=table and onOpenColumns provided', () => {
|
||||
const onOpenColumns = jest.fn();
|
||||
const onOpenColumns = vi.fn();
|
||||
const { getByTestId } = renderWithOnOpen(onOpenColumns, 'table');
|
||||
|
||||
expect(getByTestId('periscope-btn-edit-columns')).toBeInTheDocument();
|
||||
@@ -198,7 +202,7 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
});
|
||||
|
||||
it('does not render "Edit columns" row when format is not table', () => {
|
||||
renderWithOnOpen(jest.fn(), 'raw');
|
||||
renderWithOnOpen(vi.fn(), 'raw');
|
||||
|
||||
expect(
|
||||
document.querySelector('[data-testid="periscope-btn-edit-columns"]'),
|
||||
@@ -206,7 +210,7 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
});
|
||||
|
||||
it('fires onOpenColumns and closes the popover when "Edit columns" is clicked', async () => {
|
||||
const onOpenColumns = jest.fn();
|
||||
const onOpenColumns = vi.fn();
|
||||
const { getByTestId } = renderWithOnOpen(onOpenColumns, 'table');
|
||||
|
||||
fireEvent.click(getByTestId('periscope-btn-edit-columns'));
|
||||
|
||||
@@ -17,7 +17,7 @@ beforeAll(() => {
|
||||
mockCodeMirrorDomApis();
|
||||
});
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: (): boolean => true,
|
||||
}));
|
||||
|
||||
@@ -187,7 +187,7 @@ describe('MarkdownEditor', () => {
|
||||
});
|
||||
|
||||
it('reports every keystroke to the caller', () => {
|
||||
const onChange = jest.fn();
|
||||
const onChange = vi.fn();
|
||||
render(<MarkdownEditor value="ab" onChange={onChange} />);
|
||||
|
||||
type(2, 'c');
|
||||
@@ -196,7 +196,7 @@ describe('MarkdownEditor', () => {
|
||||
});
|
||||
|
||||
it('does not report the seed back as a change', () => {
|
||||
const onChange = jest.fn();
|
||||
const onChange = vi.fn();
|
||||
render(<MarkdownEditor value="seeded" onChange={onChange} />);
|
||||
|
||||
expect(documentText()).toBe('seeded');
|
||||
@@ -223,7 +223,7 @@ describe('MarkdownEditor', () => {
|
||||
});
|
||||
|
||||
it('counts characters from the document, not from the lagging value', async () => {
|
||||
render(<MarkdownEditor value="ab" onChange={jest.fn()} />);
|
||||
render(<MarkdownEditor value="ab" onChange={vi.fn()} />);
|
||||
|
||||
type(2, 'cde');
|
||||
|
||||
@@ -250,7 +250,7 @@ describe('MarkdownEditor', () => {
|
||||
render(
|
||||
<MarkdownEditor
|
||||
value="body"
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
variables={VARIABLES}
|
||||
readOnly
|
||||
/>,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { MemberStatus } from 'container/MembersSettings/utils';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
import { render, screen, userEvent } from 'tests/test-utils-full';
|
||||
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
import MembersTable, { MemberRow } from '../MembersTable';
|
||||
|
||||
@@ -34,13 +36,13 @@ const defaultProps = {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
searchQuery: '',
|
||||
onPageChange: jest.fn(),
|
||||
onRowClick: jest.fn(),
|
||||
onPageChange: vi.fn(),
|
||||
onRowClick: vi.fn(),
|
||||
};
|
||||
|
||||
describe('MembersTable', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders member rows with name, email, and ACTIVE status', () => {
|
||||
@@ -65,9 +67,7 @@ describe('MembersTable', () => {
|
||||
});
|
||||
|
||||
it('calls onRowClick with the member data when a row is clicked', async () => {
|
||||
const onRowClick = jest.fn() as jest.MockedFunction<
|
||||
(member: MemberRow) => void
|
||||
>;
|
||||
const onRowClick = vi.fn() as MockedFunction<(member: MemberRow) => void>;
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
@@ -87,7 +87,7 @@ describe('MembersTable', () => {
|
||||
});
|
||||
|
||||
it('renders DELETED badge and calls onRowClick when a deleted member row is clicked', async () => {
|
||||
const onRowClick = jest.fn();
|
||||
const onRowClick = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const deletedMember: MemberRow = {
|
||||
id: 'user-del',
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`MessageTip custom action 1`] = `
|
||||
.c0 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`MessageTip > custom action 1`] = `
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-with-description c0 css-dev-only-do-not-override-2i2tap"
|
||||
class="ant-alert ant-alert-info ant-alert-with-description sc-aXZVg bzzGSj css-dev-only-do-not-override-2i2tap"
|
||||
data-show="true"
|
||||
role="alert"
|
||||
>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
// Helper function to render with VirtuosoMockContext
|
||||
const renderWithVirtuoso = (
|
||||
component: React.ReactElement,
|
||||
@@ -17,10 +15,29 @@ const renderWithVirtuoso = (
|
||||
</VirtuosoMockContext.Provider>,
|
||||
);
|
||||
|
||||
/**
|
||||
* Wait for the antd dropdown to reach its hidden state. Closing starts an
|
||||
* rc-motion leave animation that only finishes on an animation event; jsdom
|
||||
* runs no CSS so that event never fires there (in this jsdom the motion
|
||||
* listens for the prefixed `webkitAnimationEnd`). Firing it manually ends the
|
||||
* motion exactly as the browser's own event would; in a real browser the
|
||||
* motion is already over and the synthetic event is a no-op.
|
||||
*/
|
||||
async function waitForDropdownHidden(): Promise<void> {
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
for (const name of ['animationend', 'webkitAnimationEnd']) {
|
||||
dropdown?.dispatchEvent(new Event(name, { bubbles: false }));
|
||||
}
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Mock clipboard API
|
||||
Object.assign(navigator, {
|
||||
clipboard: {
|
||||
writeText: jest.fn(() => Promise.resolve()),
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: {
|
||||
writeText: vi.fn(() => Promise.resolve()),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,12 +68,12 @@ const mockGroupedOptions = [
|
||||
|
||||
describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
let mockOnChange: jest.Mock;
|
||||
let mockOnChange: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
mockOnChange = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
mockOnChange = vi.fn();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
// ===== 1. CUSTOM VALUES SUPPORT =====
|
||||
@@ -827,7 +844,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
// ===== 7. SAVE AND SELECTION TRIGGERS =====
|
||||
describe('Save and Selection Triggers (ST)', () => {
|
||||
it('ST-01: ESC triggers save action', async () => {
|
||||
const mockDropdownChange = jest.fn();
|
||||
const mockDropdownChange = vi.fn();
|
||||
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
@@ -851,6 +868,8 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
// Verify dropdown is closed after Escape
|
||||
expect(mockDropdownChange).toHaveBeenCalledWith(false);
|
||||
|
||||
await waitForDropdownHidden();
|
||||
|
||||
await waitFor(() => {
|
||||
// Dropdown should be hidden (not completely removed from DOM)
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
@@ -944,6 +963,8 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
await user.keyboard('{Escape}');
|
||||
|
||||
// Dropdown should close and search text should be cleared
|
||||
await waitForDropdownHidden();
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
@@ -1176,11 +1197,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
await user.click(document.body);
|
||||
|
||||
// Dropdown should close - check for hidden state
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
// The dropdown should be hidden with the hidden class
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
});
|
||||
await waitForDropdownHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1290,7 +1307,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
// ===== 11. ADVANCED CLEAR ACTIONS =====
|
||||
describe('Advanced Clear Actions (ACA)', () => {
|
||||
it('ACA-01: Clear action waiting behavior', async () => {
|
||||
const mockOnChangeWithDelay = jest.fn().mockImplementation(
|
||||
const mockOnChangeWithDelay = vi.fn().mockImplementation(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 100);
|
||||
@@ -1511,10 +1528,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
// Only ESC should close the dropdown
|
||||
await user.keyboard('{Escape}');
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
});
|
||||
await waitForDropdownHidden();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,9 +9,6 @@ import {
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
// Helper function to render with VirtuosoMockContext
|
||||
const renderWithVirtuoso = (component: React.ReactElement): RenderResult =>
|
||||
render(
|
||||
@@ -34,11 +31,11 @@ const RETRY_BUTTON_SELECTOR = '[data-testid="retry-button"]';
|
||||
|
||||
describe('CustomMultiSelect - Retry Functionality', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should show retry button when 5xx error occurs and error message is displayed', async () => {
|
||||
const mockOnRetry = jest.fn();
|
||||
const mockOnRetry = vi.fn();
|
||||
const errorMessage = 'Internal Server Error (500)';
|
||||
|
||||
renderWithVirtuoso(
|
||||
@@ -66,7 +63,7 @@ describe('CustomMultiSelect - Retry Functionality', () => {
|
||||
});
|
||||
|
||||
it('should show retry button when 4xx error occurs and error message is displayed (current behavior)', async () => {
|
||||
const mockOnRetry = jest.fn();
|
||||
const mockOnRetry = vi.fn();
|
||||
const errorMessage = 'Bad Request (400)';
|
||||
|
||||
renderWithVirtuoso(
|
||||
@@ -93,7 +90,7 @@ describe('CustomMultiSelect - Retry Functionality', () => {
|
||||
});
|
||||
|
||||
it('should call onRetry function when retry button is clicked', async () => {
|
||||
const mockOnRetry = jest.fn();
|
||||
const mockOnRetry = vi.fn();
|
||||
const errorMessage = 'Internal Server Error (500)';
|
||||
|
||||
renderWithVirtuoso(
|
||||
|
||||
@@ -28,20 +28,20 @@ function renderSelect(): void {
|
||||
|
||||
/** Hovers an element and lets the tooltip's open delay elapse. */
|
||||
async function hover(element: HTMLElement): Promise<void> {
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
||||
await user.hover(element);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
}
|
||||
|
||||
describe('CustomMultiSelect tag tooltip', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("reveals a tag's untruncated value on hover", async () => {
|
||||
|
||||
@@ -10,9 +10,6 @@ import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
// Helper function to render with VirtuosoMockContext
|
||||
const renderWithVirtuoso = (component: React.ReactElement): RenderResult =>
|
||||
render(
|
||||
@@ -49,7 +46,7 @@ const mockGroupedOptions = [
|
||||
|
||||
describe('CustomMultiSelect Component', () => {
|
||||
it('renders with placeholder', () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
placeholder="Select multiple options"
|
||||
@@ -64,7 +61,7 @@ describe('CustomMultiSelect Component', () => {
|
||||
});
|
||||
|
||||
it('opens dropdown when clicked', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={handleChange} />,
|
||||
);
|
||||
@@ -83,7 +80,7 @@ describe('CustomMultiSelect Component', () => {
|
||||
});
|
||||
|
||||
it('selects multiple options', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
|
||||
// Start with option1 already selected
|
||||
renderWithVirtuoso(
|
||||
@@ -112,7 +109,7 @@ describe('CustomMultiSelect Component', () => {
|
||||
});
|
||||
|
||||
it('selects ALL options when ALL is clicked', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -156,7 +153,7 @@ describe('CustomMultiSelect Component', () => {
|
||||
});
|
||||
|
||||
it('removes a tag when clicked', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import CustomSelect from '../CustomSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
// Mock clipboard API
|
||||
Object.assign(navigator, {
|
||||
clipboard: {
|
||||
writeText: jest.fn(() => Promise.resolve()),
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: {
|
||||
writeText: vi.fn(() => Promise.resolve()),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for the antd dropdown to reach its hidden state. Closing starts an
|
||||
* rc-motion leave animation that only finishes on an animation event; jsdom
|
||||
* runs no CSS so that event never fires there (in this jsdom the motion
|
||||
* listens for the prefixed `webkitAnimationEnd`). Firing it manually ends the
|
||||
* motion exactly as the browser's own event would; in a real browser the
|
||||
* motion is already over and the synthetic event is a no-op.
|
||||
*/
|
||||
async function waitForDropdownHidden(): Promise<void> {
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
for (const name of ['animationend', 'webkitAnimationEnd']) {
|
||||
dropdown?.dispatchEvent(new Event(name, { bubbles: false }));
|
||||
}
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Test data
|
||||
const mockOptions = [
|
||||
{ label: 'Frontend', value: 'frontend' },
|
||||
@@ -40,12 +58,12 @@ const mockGroupedOptions = [
|
||||
|
||||
describe('CustomSelect - Comprehensive Tests', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
let mockOnChange: jest.Mock;
|
||||
let mockOnChange: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
mockOnChange = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
mockOnChange = vi.fn();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
// ===== 1. CUSTOM VALUES SUPPORT =====
|
||||
@@ -678,10 +696,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
await user.click(document.body);
|
||||
|
||||
// Dropdown should close
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
});
|
||||
await waitForDropdownHidden();
|
||||
});
|
||||
|
||||
it('AKN-02: TAB navigation from input to dropdown', async () => {
|
||||
@@ -831,7 +846,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
// ===== 13. ADVANCED CLEAR ACTIONS =====
|
||||
describe('Advanced Clear Actions (ACA)', () => {
|
||||
it('ACA-01: Clear action waiting behavior', async () => {
|
||||
const mockOnChangeWithDelay = jest.fn().mockImplementation(
|
||||
const mockOnChangeWithDelay = vi.fn().mockImplementation(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
@@ -1075,10 +1090,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
await user.click(backendOption);
|
||||
|
||||
// Dropdown should close after selection in single select
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
});
|
||||
await waitForDropdownHidden();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import CustomSelect from '../CustomSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
// Mock options data
|
||||
const mockOptions = [
|
||||
{ label: 'Option 1', value: 'option1' },
|
||||
@@ -31,7 +28,7 @@ const mockGroupedOptions = [
|
||||
|
||||
describe('CustomSelect Component', () => {
|
||||
it('renders with placeholder and options', () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
render(
|
||||
<CustomSelect
|
||||
placeholder="Test placeholder"
|
||||
@@ -46,7 +43,7 @@ describe('CustomSelect Component', () => {
|
||||
});
|
||||
|
||||
it('opens dropdown when clicked', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
render(<CustomSelect options={mockOptions} onChange={handleChange} />);
|
||||
|
||||
// Click to open the dropdown
|
||||
@@ -62,7 +59,7 @@ describe('CustomSelect Component', () => {
|
||||
});
|
||||
|
||||
it('calls onChange when option is selected', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
render(<CustomSelect options={mockOptions} onChange={handleChange} />);
|
||||
|
||||
// Open dropdown
|
||||
@@ -114,7 +111,7 @@ describe('CustomSelect Component', () => {
|
||||
});
|
||||
|
||||
it('renders grouped options correctly', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
render(<CustomSelect options={mockGroupedOptions} onChange={handleChange} />);
|
||||
|
||||
// Open dropdown
|
||||
@@ -168,7 +165,7 @@ describe('CustomSelect Component', () => {
|
||||
});
|
||||
|
||||
it('supports keyboard navigation', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
render(<CustomSelect options={mockOptions} onChange={handleChange} />);
|
||||
|
||||
// Open dropdown using keyboard
|
||||
@@ -185,7 +182,7 @@ describe('CustomSelect Component', () => {
|
||||
});
|
||||
|
||||
it('handles selection via keyboard', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
render(<CustomSelect options={mockOptions} onChange={handleChange} />);
|
||||
|
||||
// Open dropdown
|
||||
|
||||
@@ -1,103 +1,9 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
exports[`Not Found page test > should render Not Found page without errors 1`] = `
|
||||
<DocumentFragment>
|
||||
.c3 {
|
||||
border: 2px solid #2f80ed;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
width: 400px;
|
||||
background: inherit;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
line-height: 20px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
padding-top: 14px;
|
||||
padding-bottom: 14px;
|
||||
color: #2f80ed;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
min-height: 80vh;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: #828282;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
min-height: 50px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
margin-bottom: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
<div
|
||||
class="sc-gEvEer jnIQEo"
|
||||
>
|
||||
<img
|
||||
alt="not-found"
|
||||
@@ -105,21 +11,21 @@ exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
style="max-height: 480px; max-width: 480px;"
|
||||
/>
|
||||
<div
|
||||
class="c1"
|
||||
class="sc-fqkvVR dmgRTJ"
|
||||
>
|
||||
<p
|
||||
class="c2"
|
||||
class="sc-eqUAAy keriGu"
|
||||
>
|
||||
Ah, seems like we reached a dead end!
|
||||
</p>
|
||||
<p
|
||||
class="c2"
|
||||
class="sc-eqUAAy keriGu"
|
||||
>
|
||||
Page Not Found
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
class="c3"
|
||||
class="sc-aXZVg hSWmhs"
|
||||
href="/home"
|
||||
tabindex="0"
|
||||
>
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('ListViewOrderBy', () => {
|
||||
render(
|
||||
<ListViewOrderBy
|
||||
value="last_activity_time:desc"
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
dataSource={DataSource.TRACES}
|
||||
fieldKeysConfig={TRACE_VIEW_FIELD_KEYS}
|
||||
builderQueryType={TRACE_VIEW_BUILDER_QUERY_TYPE}
|
||||
@@ -99,7 +99,7 @@ describe('ListViewOrderBy', () => {
|
||||
render(
|
||||
<ListViewOrderBy
|
||||
value="last_activity_time:desc"
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
dataSource={DataSource.TRACES}
|
||||
fieldKeysConfig={TRACE_VIEW_FIELD_KEYS}
|
||||
builderQueryType={TRACE_VIEW_BUILDER_QUERY_TYPE}
|
||||
@@ -121,7 +121,7 @@ describe('ListViewOrderBy', () => {
|
||||
render(
|
||||
<ListViewOrderBy
|
||||
value="last_activity_time:desc"
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
dataSource={DataSource.TRACES}
|
||||
fieldKeysConfig={TRACE_VIEW_FIELD_KEYS}
|
||||
builderQueryType={TRACE_VIEW_BUILDER_QUERY_TYPE}
|
||||
@@ -149,7 +149,7 @@ describe('ListViewOrderBy', () => {
|
||||
render(
|
||||
<ListViewOrderBy
|
||||
value="timestamp:desc"
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
dataSource={DataSource.TRACES}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ function queryTooltipInner(): HTMLElement | null {
|
||||
|
||||
describe('OverflowInputToolTip', () => {
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('shows tooltip when content overflows and input is clamped at maxAutoWidth', async () => {
|
||||
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
QuerySearchV2ProviderProps,
|
||||
} from '../QuerySearchV2.provider';
|
||||
|
||||
const mockSetQueryState = jest.fn();
|
||||
let mockUrlValue: string | null = null;
|
||||
const { mockSetQueryState, mockUrlState } = vi.hoisted(() => ({
|
||||
mockSetQueryState: vi.fn(),
|
||||
mockUrlState: { value: null as string | null },
|
||||
}));
|
||||
|
||||
jest.mock('nuqs', () => ({
|
||||
vi.mock('nuqs', () => ({
|
||||
parseAsString: {},
|
||||
useQueryState: jest.fn(() => [mockUrlValue, mockSetQueryState]),
|
||||
useQueryState: vi.fn(() => [mockUrlState.value, mockSetQueryState]),
|
||||
}));
|
||||
|
||||
function createWrapper(
|
||||
@@ -54,8 +56,8 @@ function useTestHooks(): {
|
||||
|
||||
describe('QuerySearchExpressionProvider', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUrlValue = null;
|
||||
vi.clearAllMocks();
|
||||
mockUrlState.value = null;
|
||||
});
|
||||
|
||||
it('should provide initial context values', () => {
|
||||
@@ -100,7 +102,7 @@ describe('QuerySearchExpressionProvider', () => {
|
||||
});
|
||||
|
||||
it('should initialize from URL value on mount', () => {
|
||||
mockUrlValue = 'status = 500';
|
||||
mockUrlState.value = 'status = 500';
|
||||
|
||||
const { result } = renderHook(() => useTestHooks(), {
|
||||
wrapper: createWrapper(),
|
||||
|
||||
@@ -183,11 +183,11 @@ describe('traceOperatorContextUtils', () => {
|
||||
describe('getTraceOperatorContextAtCursor', () => {
|
||||
beforeEach(() => {
|
||||
// Reset console.error mock
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should return default context for empty query', () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
import QuerySearch from '../QuerySearch/QuerySearch';
|
||||
import { mockCodeMirrorDomApis } from './codemirrorDomMocks';
|
||||
@@ -13,19 +14,19 @@ beforeAll(() => {
|
||||
mockCodeMirrorDomApis();
|
||||
});
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: (): boolean => false,
|
||||
}));
|
||||
|
||||
// Shrink the suggestion-fetch debounce (300ms in prod) so these integration
|
||||
// tests aren't paced by it; coalescing semantics stay intact.
|
||||
jest.mock('../QuerySearch/constants', () => ({
|
||||
...jest.requireActual('../QuerySearch/constants'),
|
||||
vi.mock('../QuerySearch/constants', async () => ({
|
||||
...(await vi.importActual('../QuerySearch/constants')),
|
||||
SUGGESTION_FETCH_DEBOUNCE_MS: 30,
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||
const handleRunQuery = jest.fn();
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||
const handleRunQuery = vi.fn();
|
||||
return {
|
||||
__esModule: true,
|
||||
useQueryBuilder: (): { handleRunQuery: () => void } => ({ handleRunQuery }),
|
||||
@@ -77,6 +78,10 @@ const KEYS_FIXTURE = {
|
||||
const fetchedSearchTexts: string[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
// test-utils freezes Date via vi.setSystemTime while leaving real timers in
|
||||
// place, which stalls lodash debounce indefinitely; restore real timers so
|
||||
// the suggestion fetches fire. (Shared-file fix reported to the coordinator.)
|
||||
vi.useRealTimers();
|
||||
fetchedSearchTexts.length = 0;
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v1/fields/keys', (req, res, ctx) => {
|
||||
@@ -104,7 +109,7 @@ beforeEach(() => {
|
||||
async function renderAndType(text: string): Promise<HTMLElement> {
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { completionStatus, startCompletion } from '@codemirror/autocomplete';
|
||||
import { EditorView } from '@uiw/react-codemirror';
|
||||
import { getFieldKeySuggestions } from 'api/querySuggestions/getFieldKeySuggestions';
|
||||
import { getFieldValueSuggestions } from 'api/querySuggestions/getFieldValueSuggestions';
|
||||
@@ -14,6 +15,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import QuerySearch from '../QuerySearch/QuerySearch';
|
||||
import { mockCodeMirrorDomApis } from './codemirrorDomMocks';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
const CM_EDITOR_SELECTOR = '.cm-editor .cm-content';
|
||||
|
||||
@@ -22,12 +24,19 @@ beforeAll(() => {
|
||||
mockCodeMirrorDomApis();
|
||||
});
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
// test-utils freezes Date via vi.setSystemTime while leaving real timers in
|
||||
// place, which stalls lodash debounce indefinitely; restore real timers so the
|
||||
// suggestion fetches fire.
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: (): boolean => false,
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||
const handleRunQuery = jest.fn();
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||
const handleRunQuery = vi.fn();
|
||||
return {
|
||||
__esModule: true,
|
||||
useQueryBuilder: (): { handleRunQuery: () => void } => ({ handleRunQuery }),
|
||||
@@ -35,15 +44,15 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('api/querySuggestions/getFieldKeySuggestions', () => ({
|
||||
getFieldKeySuggestions: jest.fn().mockResolvedValue({
|
||||
vi.mock('api/querySuggestions/getFieldKeySuggestions', () => ({
|
||||
getFieldKeySuggestions: vi.fn().mockResolvedValue({
|
||||
status: 'success',
|
||||
data: { complete: true, keys: {} },
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('api/querySuggestions/getFieldValueSuggestions', () => ({
|
||||
getFieldValueSuggestions: jest.fn().mockResolvedValue({
|
||||
vi.mock('api/querySuggestions/getFieldValueSuggestions', () => ({
|
||||
getFieldValueSuggestions: vi.fn().mockResolvedValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
complete: true,
|
||||
@@ -64,11 +73,35 @@ const SAMPLE_KEY_TYPING = 'http.';
|
||||
const SAMPLE_VALUE_TYPING_INCOMPLETE = "service.name = '";
|
||||
const SAMPLE_STATUS_QUERY = "http.status_code = '200'";
|
||||
|
||||
// The dropdown is opened by CodeMirror, which needs a layout roundtrip to settle
|
||||
// in a real browser, and the async fetches can close it again. Re-request it
|
||||
// while waiting, but at most every 750ms: restarting on every poll aborts the
|
||||
// in-flight open and pins it in 'pending'.
|
||||
let lastCompletionRequest = 0;
|
||||
|
||||
function waitForCompletionText(text: string): Promise<HTMLElement> {
|
||||
return waitFor(
|
||||
() => {
|
||||
const root = document.querySelector<HTMLElement>('.cm-editor');
|
||||
const view = root ? EditorView.findFromDOM(root) : null;
|
||||
if (view && completionStatus(view.state) !== 'active') {
|
||||
const now = Date.now();
|
||||
if (now - lastCompletionRequest >= 750) {
|
||||
lastCompletionRequest = now;
|
||||
startCompletion(view);
|
||||
}
|
||||
}
|
||||
return screen.getByText(text);
|
||||
},
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
}
|
||||
|
||||
describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
it('renders with placeholder', () => {
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
@@ -81,14 +114,14 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
it('fetches key suggestions when typing a key (debounced)', async () => {
|
||||
// Use real timers for CodeMirror integration tests
|
||||
const mockedGetKeys = getFieldKeySuggestions as jest.MockedFunction<
|
||||
const mockedGetKeys = getFieldKeySuggestions as MockedFunction<
|
||||
typeof getFieldKeySuggestions
|
||||
>;
|
||||
mockedGetKeys.mockClear();
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
@@ -115,11 +148,13 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
it('fetches value suggestions when editing value context', async () => {
|
||||
// Use real timers for CodeMirror integration tests
|
||||
const mockedGetValues = getFieldValueSuggestions as jest.MockedFunction<
|
||||
const mockedGetValues = getFieldValueSuggestions as MockedFunction<
|
||||
typeof getFieldValueSuggestions
|
||||
>;
|
||||
mockedGetValues.mockClear();
|
||||
mockedGetValues.mockResolvedValueOnce({
|
||||
// Not `Once`: typing debounces into more than one fetch here, and the first
|
||||
// of them would otherwise consume the only response carrying the values.
|
||||
mockedGetValues.mockResolvedValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
complete: true,
|
||||
@@ -134,7 +169,7 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
@@ -156,22 +191,23 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
});
|
||||
|
||||
// the string and number values off the response both reach the dropdown
|
||||
lastCompletionRequest = 0;
|
||||
await expect(
|
||||
screen.findByText('payment-service'),
|
||||
waitForCompletionText('payment-service'),
|
||||
).resolves.toBeInTheDocument();
|
||||
await expect(screen.findByText('200')).resolves.toBeInTheDocument();
|
||||
await expect(waitForCompletionText('200')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fetches key suggestions on mount for LOGS', async () => {
|
||||
// Use real timers for CodeMirror integration tests
|
||||
const mockedGetKeysOnMount = getFieldKeySuggestions as jest.MockedFunction<
|
||||
const mockedGetKeysOnMount = getFieldKeySuggestions as MockedFunction<
|
||||
typeof getFieldKeySuggestions
|
||||
>;
|
||||
mockedGetKeysOnMount.mockClear();
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
@@ -191,11 +227,11 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
});
|
||||
|
||||
it('calls provided onRun on Mod-Enter', async () => {
|
||||
const onRun = jest.fn() as jest.MockedFunction<(q: string) => void>;
|
||||
const onRun = vi.fn() as MockedFunction<(q: string) => void>;
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||
dataSource={DataSource.LOGS}
|
||||
onRun={onRun}
|
||||
@@ -236,7 +272,7 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||
onChange={vi.fn() as MockedFunction<(v: string) => void>}
|
||||
queryData={queryDataWithExpression}
|
||||
dataSource={DataSource.LOGS}
|
||||
/>,
|
||||
@@ -262,11 +298,11 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
it('handles queryData.filter.expression changes without triggering onChange', async () => {
|
||||
// Spy on CodeMirror's EditorView.dispatch, which is invoked when updateEditorValue
|
||||
// applies a programmatic change to the editor.
|
||||
const dispatchSpy = jest.spyOn(EditorView.prototype, 'dispatch');
|
||||
const dispatchSpy = vi.spyOn(EditorView.prototype, 'dispatch');
|
||||
const initialExpression = "service.name = 'frontend'";
|
||||
const updatedExpression = "service.name = 'backend'";
|
||||
|
||||
const onChange = jest.fn() as jest.MockedFunction<(v: string) => void>;
|
||||
const onChange = vi.fn() as MockedFunction<(v: string) => void>;
|
||||
|
||||
const initialQueryData = {
|
||||
...initialQueriesMap.logs.builder.queryData[0],
|
||||
@@ -329,8 +365,8 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
});
|
||||
|
||||
it('does not crash when the expression contains CRLF line breaks (issue #5869)', async () => {
|
||||
const dispatchSpy = jest.spyOn(EditorView.prototype, 'dispatch');
|
||||
const onChange = jest.fn() as jest.MockedFunction<(v: string) => void>;
|
||||
const dispatchSpy = vi.spyOn(EditorView.prototype, 'dispatch');
|
||||
const onChange = vi.fn() as MockedFunction<(v: string) => void>;
|
||||
const initialExpression = "service.name = 'frontend'";
|
||||
// Filtering on a multi-line log value (CRLF) used to throw
|
||||
// "RangeError: Selection points outside of document".
|
||||
@@ -389,7 +425,7 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
});
|
||||
|
||||
it('fetches key suggestions for metrics even without aggregateAttribute.key when showFilterSuggestionsWithoutMetric is true', async () => {
|
||||
const mockedGetKeys = getFieldKeySuggestions as jest.MockedFunction<
|
||||
const mockedGetKeys = getFieldKeySuggestions as MockedFunction<
|
||||
typeof getFieldKeySuggestions
|
||||
>;
|
||||
mockedGetKeys.mockClear();
|
||||
@@ -405,7 +441,7 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
queryData={queryData}
|
||||
dataSource={DataSource.METRICS}
|
||||
showFilterSuggestionsWithoutMetric
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { jest } from '@jest/globals';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
@@ -18,48 +17,43 @@ import {
|
||||
QueryBuilderContextType,
|
||||
QueryFunctionsTypes,
|
||||
} from 'types/common/queryBuilder';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { QueryBuilderV2 } from '../../QueryBuilderV2';
|
||||
|
||||
// Local mocks for domain-specific heavy child components
|
||||
jest.mock(
|
||||
'../QueryAggregation/QueryAggregation',
|
||||
() =>
|
||||
function QueryAggregation() {
|
||||
return <div>QueryAggregation</div>;
|
||||
},
|
||||
);
|
||||
jest.mock(
|
||||
'../MerticsAggregateSection/MetricsAggregateSection',
|
||||
() =>
|
||||
function MetricsAggregateSection() {
|
||||
return <div>MetricsAggregateSection</div>;
|
||||
},
|
||||
);
|
||||
vi.mock('../QueryAggregation/QueryAggregation', () => ({
|
||||
default: function QueryAggregation(): JSX.Element {
|
||||
return <div>QueryAggregation</div>;
|
||||
},
|
||||
}));
|
||||
vi.mock('../MerticsAggregateSection/MetricsAggregateSection', () => ({
|
||||
default: function MetricsAggregateSection(): JSX.Element {
|
||||
return <div>MetricsAggregateSection</div>;
|
||||
},
|
||||
}));
|
||||
// Mock hooks
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilderOperations');
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilderOperations');
|
||||
|
||||
const mockedUseQueryBuilder = jest.mocked(useQueryBuilder);
|
||||
const mockedUseQueryOperations = jest.mocked(
|
||||
const mockedUseQueryBuilder = vi.mocked(useQueryBuilder);
|
||||
const mockedUseQueryOperations = vi.mocked(
|
||||
useQueryOperations,
|
||||
) as jest.MockedFunction<UseQueryOperations>;
|
||||
) as MockedFunction<UseQueryOperations>;
|
||||
|
||||
describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
let handleRunQueryMock: jest.MockedFunction<() => void>;
|
||||
let handleQueryFunctionsUpdatesMock: jest.MockedFunction<() => void>;
|
||||
let handleRunQueryMock: MockedFunction<() => void>;
|
||||
let handleQueryFunctionsUpdatesMock: MockedFunction<() => void>;
|
||||
let baseQBContext: QueryBuilderContextType;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockCloneQuery = jest.fn() as jest.MockedFunction<
|
||||
const mockCloneQuery = vi.fn() as MockedFunction<
|
||||
(type: string, q: IBuilderQuery) => void
|
||||
>;
|
||||
handleRunQueryMock = jest.fn() as jest.MockedFunction<() => void>;
|
||||
handleQueryFunctionsUpdatesMock = jest.fn() as jest.MockedFunction<
|
||||
() => void
|
||||
>;
|
||||
handleRunQueryMock = vi.fn() as MockedFunction<() => void>;
|
||||
handleQueryFunctionsUpdatesMock = vi.fn() as MockedFunction<() => void>;
|
||||
const baseQuery: IBuilderQuery = {
|
||||
queryName: 'A',
|
||||
dataSource: DataSource.LOGS,
|
||||
@@ -103,35 +97,35 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
currentQuery: currentQueryObj,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
setLastUsedQuery: jest.fn(),
|
||||
setLastUsedQuery: vi.fn(),
|
||||
supersetQuery: currentQueryObj,
|
||||
setSupersetQuery: jest.fn(),
|
||||
setSupersetQuery: vi.fn(),
|
||||
initialDataSource: null,
|
||||
panelType: PANEL_TYPES.TABLE,
|
||||
isEnabledQuery: true,
|
||||
handleSetQueryData: jest.fn(),
|
||||
handleSetTraceOperatorData: jest.fn(),
|
||||
handleSetFormulaData: jest.fn(),
|
||||
handleSetQueryItemData: jest.fn(),
|
||||
handleSetConfig: jest.fn(),
|
||||
removeQueryBuilderEntityByIndex: jest.fn(),
|
||||
removeAllQueryBuilderEntities: jest.fn(),
|
||||
removeQueryTypeItemByIndex: jest.fn(),
|
||||
addNewBuilderQuery: jest.fn(),
|
||||
addNewFormula: jest.fn(),
|
||||
removeTraceOperator: jest.fn(),
|
||||
addTraceOperator: jest.fn(),
|
||||
handleSetQueryData: vi.fn(),
|
||||
handleSetTraceOperatorData: vi.fn(),
|
||||
handleSetFormulaData: vi.fn(),
|
||||
handleSetQueryItemData: vi.fn(),
|
||||
handleSetConfig: vi.fn(),
|
||||
removeQueryBuilderEntityByIndex: vi.fn(),
|
||||
removeAllQueryBuilderEntities: vi.fn(),
|
||||
removeQueryTypeItemByIndex: vi.fn(),
|
||||
addNewBuilderQuery: vi.fn(),
|
||||
addNewFormula: vi.fn(),
|
||||
removeTraceOperator: vi.fn(),
|
||||
addTraceOperator: vi.fn(),
|
||||
cloneQuery: mockCloneQuery,
|
||||
addNewQueryItem: jest.fn(),
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
addNewQueryItem: vi.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
handleRunQuery: handleRunQueryMock,
|
||||
resetQuery: jest.fn(),
|
||||
handleOnUnitsChange: jest.fn(),
|
||||
resetQuery: vi.fn(),
|
||||
handleOnUnitsChange: vi.fn(),
|
||||
updateAllQueriesOperators,
|
||||
updateQueriesData,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
initQueryBuilderData: vi.fn(),
|
||||
isStagedQueryUpdated: vi.fn(() => false),
|
||||
isDefaultQuery: vi.fn(() => false),
|
||||
} as unknown as QueryBuilderContextType;
|
||||
|
||||
baseQBContext = baseContext;
|
||||
@@ -143,21 +137,21 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
operators: [],
|
||||
spaceAggregationOptions: [],
|
||||
listOfAdditionalFilters: [],
|
||||
handleChangeOperator: jest.fn(),
|
||||
handleSpaceAggregationChange: jest.fn(),
|
||||
handleChangeAggregatorAttribute: jest.fn(),
|
||||
handleChangeDataSource: jest.fn(),
|
||||
handleDeleteQuery: jest.fn(),
|
||||
handleChangeOperator: vi.fn(),
|
||||
handleSpaceAggregationChange: vi.fn(),
|
||||
handleChangeAggregatorAttribute: vi.fn(),
|
||||
handleChangeDataSource: vi.fn(),
|
||||
handleDeleteQuery: vi.fn(),
|
||||
handleChangeQueryData:
|
||||
jest.fn() as unknown as ReturnType<UseQueryOperations>['handleChangeQueryData'],
|
||||
handleChangeFormulaData: jest.fn(),
|
||||
vi.fn() as unknown as ReturnType<UseQueryOperations>['handleChangeQueryData'],
|
||||
handleChangeFormulaData: vi.fn(),
|
||||
handleQueryFunctionsUpdates: handleQueryFunctionsUpdatesMock,
|
||||
listOfAdditionalFormulaFilters: [],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders limit input when dataSource is logs', () => {
|
||||
@@ -238,7 +232,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fx button is disabled when functions already exist', () => {
|
||||
it('fx button is disabled when functions already exist', async () => {
|
||||
const currentQueryBase = baseQBContext.currentQuery as Query;
|
||||
const supersetQueryBase = baseQBContext.supersetQuery as Query;
|
||||
|
||||
@@ -284,8 +278,8 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
'.query-function-delete-btn',
|
||||
) as HTMLButtonElement;
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
userEvent.click(deleteButton);
|
||||
waitFor(() => {
|
||||
await userEvent.click(deleteButton);
|
||||
await waitFor(() => {
|
||||
expect(fxButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,19 +27,19 @@ beforeAll(() => {
|
||||
mockCodeMirrorDomApis();
|
||||
});
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: (): boolean => false,
|
||||
}));
|
||||
|
||||
jest.mock('api/querySuggestions/getFieldKeySuggestions', () => ({
|
||||
getFieldKeySuggestions: jest.fn().mockResolvedValue({
|
||||
vi.mock('api/querySuggestions/getFieldKeySuggestions', () => ({
|
||||
getFieldKeySuggestions: vi.fn().mockResolvedValue({
|
||||
status: 'success',
|
||||
data: { complete: true, keys: {} },
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('api/querySuggestions/getFieldValueSuggestions', () => ({
|
||||
getFieldValueSuggestions: jest.fn().mockResolvedValue({
|
||||
vi.mock('api/querySuggestions/getFieldValueSuggestions', () => ({
|
||||
getFieldValueSuggestions: vi.fn().mockResolvedValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
complete: true,
|
||||
@@ -53,7 +53,7 @@ jest.mock('api/querySuggestions/getFieldValueSuggestions', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function renderLogsSearch(onChange: (value: string) => void = jest.fn()): void {
|
||||
function renderLogsSearch(onChange: (value: string) => void = vi.fn()): void {
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={onChange}
|
||||
@@ -94,7 +94,7 @@ function getRecentLabels(): string[] {
|
||||
}
|
||||
|
||||
async function renderAndFocus(
|
||||
onChange: (value: string) => void = jest.fn(),
|
||||
onChange: (value: string) => void = vi.fn(),
|
||||
): Promise<HTMLElement> {
|
||||
renderLogsSearch(onChange);
|
||||
|
||||
@@ -111,6 +111,20 @@ async function renderAndFocus(
|
||||
return editor;
|
||||
}
|
||||
|
||||
// Restart at most every 750ms: in a real browser each startCompletion needs a
|
||||
// layout roundtrip to settle, and re-requesting on every waitFor poll (interval
|
||||
// + MutationObserver) aborts the in-flight open, pinning it in 'pending'.
|
||||
let lastCompletionRequest = 0;
|
||||
|
||||
function requestCompletion(view: EditorView): void {
|
||||
const now = Date.now();
|
||||
if (now - lastCompletionRequest < 750) {
|
||||
return;
|
||||
}
|
||||
lastCompletionRequest = now;
|
||||
startCompletion(view);
|
||||
}
|
||||
|
||||
// Re-requests completions while waiting: typing and the async fetches can close the popup.
|
||||
function waitForRecents(
|
||||
assertLabels: (labels: string[]) => void,
|
||||
@@ -119,7 +133,7 @@ function waitForRecents(
|
||||
() => {
|
||||
const view = getEditorView();
|
||||
if (view && !isCompletionOpen()) {
|
||||
startCompletion(view);
|
||||
requestCompletion(view);
|
||||
}
|
||||
assertLabels(getRecentLabels());
|
||||
},
|
||||
@@ -138,7 +152,7 @@ function waitForPopupElement(
|
||||
() => {
|
||||
const view = getEditorView();
|
||||
if (view && !isCompletionOpen()) {
|
||||
startCompletion(view);
|
||||
requestCompletion(view);
|
||||
}
|
||||
const node = find();
|
||||
expect(node).toBeTruthy();
|
||||
@@ -150,6 +164,10 @@ function waitForPopupElement(
|
||||
|
||||
describe('QuerySearch recent searches', () => {
|
||||
beforeEach(() => {
|
||||
// test-utils freezes Date via vi.setSystemTime while leaving real timers in
|
||||
// place; restore real timers for store timestamps and dayjs labels.
|
||||
vi.useRealTimers();
|
||||
lastCompletionRequest = 0;
|
||||
recentQueriesStore.useRecentQueriesStore.setState({ buckets: {} });
|
||||
localStorage.clear();
|
||||
});
|
||||
@@ -230,7 +248,7 @@ describe('QuerySearch recent searches', () => {
|
||||
it('applies the full expression to the editor when a recent is clicked', async () => {
|
||||
saveLogsRecent(FRONTEND_FILTER);
|
||||
|
||||
const onChange = jest.fn();
|
||||
const onChange = vi.fn();
|
||||
await renderAndFocus(onChange);
|
||||
await openRecents();
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
// Mocks the DOM measurement APIs CodeMirror needs to render in jsdom
|
||||
// (Range client rects + element bounding rects). Call from a beforeAll in
|
||||
// specs that render the real CodeMirror editor.
|
||||
//
|
||||
// jsdom-only: a real browser already provides working Range rects, and
|
||||
// overriding Element.prototype.getBoundingClientRect there with a plain object
|
||||
// breaks Chromium's EditContext integration (updateControlBounds requires a
|
||||
// genuine DOMRect), aborting CodeMirror updates. Feature-detect and leave the
|
||||
// native implementations alone when they exist.
|
||||
export function mockCodeMirrorDomApis(): void {
|
||||
if (typeof document.createRange().getClientRects === 'function') {
|
||||
return;
|
||||
}
|
||||
const mockRect: DOMRect = {
|
||||
width: 100,
|
||||
height: 20,
|
||||
|
||||
@@ -12,10 +12,14 @@ import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import QueryAddOns from '../QueryV2/QueryAddOns/QueryAddOns';
|
||||
|
||||
// Mocks: only what is required for this component to render and for us to assert handler calls
|
||||
const mockHandleChangeQueryData = jest.fn();
|
||||
const mockHandleSetQueryData = jest.fn();
|
||||
const { mockHandleChangeQueryData, mockHandleSetQueryData } = vi.hoisted(
|
||||
() => ({
|
||||
mockHandleChangeQueryData: vi.fn(),
|
||||
mockHandleSetQueryData: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||
useQueryOperations: (): {
|
||||
handleChangeQueryData: typeof mockHandleChangeQueryData;
|
||||
} => ({
|
||||
@@ -23,7 +27,7 @@ jest.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: (): {
|
||||
handleSetQueryData: typeof mockHandleSetQueryData;
|
||||
} => ({
|
||||
@@ -31,7 +35,7 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/QueryBuilder/filters/GroupByFilter/GroupByFilter', () => ({
|
||||
vi.mock('container/QueryBuilder/filters/GroupByFilter/GroupByFilter', () => ({
|
||||
GroupByFilter: ({ onChange }: any): JSX.Element => (
|
||||
<button
|
||||
data-testid="groupby"
|
||||
@@ -42,7 +46,7 @@ jest.mock('container/QueryBuilder/filters/GroupByFilter/GroupByFilter', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('container/QueryBuilder/filters/OrderByFilter/OrderByFilter', () => ({
|
||||
vi.mock('container/QueryBuilder/filters/OrderByFilter/OrderByFilter', () => ({
|
||||
OrderByFilter: ({ onChange }: any): JSX.Element => (
|
||||
<button
|
||||
data-testid="orderby"
|
||||
@@ -53,7 +57,7 @@ jest.mock('container/QueryBuilder/filters/OrderByFilter/OrderByFilter', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../QueryV2/QueryAddOns/HavingFilter/HavingFilter', () => ({
|
||||
vi.mock('../QueryV2/QueryAddOns/HavingFilter/HavingFilter', () => ({
|
||||
__esModule: true,
|
||||
default: ({ onChange, onClose }: any): JSX.Element => (
|
||||
<div>
|
||||
@@ -87,7 +91,7 @@ function baseQuery(overrides: Partial<any> = {}): any {
|
||||
|
||||
describe('QueryAddOns', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('VALUE panel: no sections auto-open when query has no active add-ons', () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
@@ -11,6 +10,7 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
import { QueryBuilderV2 } from '../QueryBuilderV2';
|
||||
import {
|
||||
@@ -19,45 +19,39 @@ import {
|
||||
} from '../QueryV2/previousQuery.utils';
|
||||
|
||||
// Local mocks for domain-specific heavy child components
|
||||
jest.mock(
|
||||
'../QueryV2/QueryAggregation/QueryAggregation',
|
||||
() =>
|
||||
function QueryAggregation(): JSX.Element {
|
||||
return <div>QueryAggregation</div>;
|
||||
},
|
||||
);
|
||||
jest.mock(
|
||||
'../QueryV2/MerticsAggregateSection/MetricsAggregateSection',
|
||||
() =>
|
||||
function MetricsAggregateSection(): JSX.Element {
|
||||
return <div>MetricsAggregateSection</div>;
|
||||
},
|
||||
);
|
||||
vi.mock('../QueryV2/QueryAggregation/QueryAggregation', () => ({
|
||||
default: function QueryAggregation(): JSX.Element {
|
||||
return <div>QueryAggregation</div>;
|
||||
},
|
||||
}));
|
||||
vi.mock('../QueryV2/MerticsAggregateSection/MetricsAggregateSection', () => ({
|
||||
default: function MetricsAggregateSection(): JSX.Element {
|
||||
return <div>MetricsAggregateSection</div>;
|
||||
},
|
||||
}));
|
||||
// Mock networked children to avoid axios during unit tests
|
||||
jest.mock(
|
||||
'../QueryV2/QuerySearch/QuerySearch',
|
||||
() =>
|
||||
function QuerySearch(): JSX.Element {
|
||||
return <div>QuerySearch</div>;
|
||||
},
|
||||
);
|
||||
jest.mock('container/QueryBuilder/filters', () => ({
|
||||
vi.mock('../QueryV2/QuerySearch/QuerySearch', () => ({
|
||||
default: function QuerySearch(): JSX.Element {
|
||||
return <div>QuerySearch</div>;
|
||||
},
|
||||
}));
|
||||
vi.mock('container/QueryBuilder/filters', () => ({
|
||||
AggregatorFilter: (): JSX.Element => <div />,
|
||||
MetricNameSelector: (): JSX.Element => <div />,
|
||||
}));
|
||||
// Mock hooks
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
|
||||
const mockedUseQueryBuilder = jest.mocked(useQueryBuilder);
|
||||
const mockedUseQueryBuilder = vi.mocked(useQueryBuilder);
|
||||
|
||||
describe('MetricsSelect - signal source switching (standalone)', () => {
|
||||
let handleSetQueryDataMock: jest.MockedFunction<
|
||||
let handleSetQueryDataMock: MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
clearPreviousQuery();
|
||||
handleSetQueryDataMock = jest.fn() as unknown as jest.MockedFunction<
|
||||
handleSetQueryDataMock = vi.fn() as unknown as MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
@@ -109,40 +103,40 @@ describe('MetricsSelect - signal source switching (standalone)', () => {
|
||||
currentQuery: currentQueryObj,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
setLastUsedQuery: jest.fn(),
|
||||
setLastUsedQuery: vi.fn(),
|
||||
supersetQuery: currentQueryObj,
|
||||
setSupersetQuery: jest.fn(),
|
||||
setSupersetQuery: vi.fn(),
|
||||
initialDataSource: null,
|
||||
panelType: PANEL_TYPES.TABLE,
|
||||
isEnabledQuery: true,
|
||||
handleSetQueryData: handleSetQueryDataMock,
|
||||
handleSetTraceOperatorData: jest.fn(),
|
||||
handleSetFormulaData: jest.fn(),
|
||||
handleSetQueryItemData: jest.fn(),
|
||||
handleSetConfig: jest.fn(),
|
||||
removeQueryBuilderEntityByIndex: jest.fn(),
|
||||
removeAllQueryBuilderEntities: jest.fn(),
|
||||
removeQueryTypeItemByIndex: jest.fn(),
|
||||
addNewBuilderQuery: jest.fn(),
|
||||
addNewFormula: jest.fn(),
|
||||
removeTraceOperator: jest.fn(),
|
||||
addTraceOperator: jest.fn(),
|
||||
cloneQuery: jest.fn(),
|
||||
addNewQueryItem: jest.fn(),
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
handleRunQuery: jest.fn(),
|
||||
resetQuery: jest.fn(),
|
||||
handleOnUnitsChange: jest.fn(),
|
||||
handleSetTraceOperatorData: vi.fn(),
|
||||
handleSetFormulaData: vi.fn(),
|
||||
handleSetQueryItemData: vi.fn(),
|
||||
handleSetConfig: vi.fn(),
|
||||
removeQueryBuilderEntityByIndex: vi.fn(),
|
||||
removeAllQueryBuilderEntities: vi.fn(),
|
||||
removeQueryTypeItemByIndex: vi.fn(),
|
||||
addNewBuilderQuery: vi.fn(),
|
||||
addNewFormula: vi.fn(),
|
||||
removeTraceOperator: vi.fn(),
|
||||
addTraceOperator: vi.fn(),
|
||||
cloneQuery: vi.fn(),
|
||||
addNewQueryItem: vi.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
handleRunQuery: vi.fn(),
|
||||
resetQuery: vi.fn(),
|
||||
handleOnUnitsChange: vi.fn(),
|
||||
updateAllQueriesOperators: ((q: any) => q) as any,
|
||||
updateQueriesData: ((q: any) => q) as any,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
initQueryBuilderData: vi.fn(),
|
||||
isStagedQueryUpdated: vi.fn(() => false),
|
||||
isDefaultQuery: vi.fn(() => false),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
clearPreviousQuery();
|
||||
});
|
||||
|
||||
@@ -216,13 +210,13 @@ describe('MetricsSelect - signal source switching (standalone)', () => {
|
||||
});
|
||||
|
||||
describe('DataSource change - Logs to Traces', () => {
|
||||
let handleSetQueryDataMock: jest.MockedFunction<
|
||||
let handleSetQueryDataMock: MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
clearPreviousQuery();
|
||||
handleSetQueryDataMock = jest.fn() as unknown as jest.MockedFunction<
|
||||
handleSetQueryDataMock = vi.fn() as unknown as MockedFunction<
|
||||
(i: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
@@ -266,40 +260,40 @@ describe('DataSource change - Logs to Traces', () => {
|
||||
currentQuery: logsCurrentQuery,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
setLastUsedQuery: jest.fn(),
|
||||
setLastUsedQuery: vi.fn(),
|
||||
supersetQuery: logsCurrentQuery,
|
||||
setSupersetQuery: jest.fn(),
|
||||
setSupersetQuery: vi.fn(),
|
||||
initialDataSource: null,
|
||||
panelType: PANEL_TYPES.TABLE,
|
||||
isEnabledQuery: true,
|
||||
handleSetQueryData: handleSetQueryDataMock,
|
||||
handleSetTraceOperatorData: jest.fn(),
|
||||
handleSetFormulaData: jest.fn(),
|
||||
handleSetQueryItemData: jest.fn(),
|
||||
handleSetConfig: jest.fn(),
|
||||
removeQueryBuilderEntityByIndex: jest.fn(),
|
||||
removeAllQueryBuilderEntities: jest.fn(),
|
||||
removeQueryTypeItemByIndex: jest.fn(),
|
||||
addNewBuilderQuery: jest.fn(),
|
||||
addNewFormula: jest.fn(),
|
||||
removeTraceOperator: jest.fn(),
|
||||
addTraceOperator: jest.fn(),
|
||||
cloneQuery: jest.fn(),
|
||||
addNewQueryItem: jest.fn(),
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
handleRunQuery: jest.fn(),
|
||||
resetQuery: jest.fn(),
|
||||
handleOnUnitsChange: jest.fn(),
|
||||
handleSetTraceOperatorData: vi.fn(),
|
||||
handleSetFormulaData: vi.fn(),
|
||||
handleSetQueryItemData: vi.fn(),
|
||||
handleSetConfig: vi.fn(),
|
||||
removeQueryBuilderEntityByIndex: vi.fn(),
|
||||
removeAllQueryBuilderEntities: vi.fn(),
|
||||
removeQueryTypeItemByIndex: vi.fn(),
|
||||
addNewBuilderQuery: vi.fn(),
|
||||
addNewFormula: vi.fn(),
|
||||
removeTraceOperator: vi.fn(),
|
||||
addTraceOperator: vi.fn(),
|
||||
cloneQuery: vi.fn(),
|
||||
addNewQueryItem: vi.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
handleRunQuery: vi.fn(),
|
||||
resetQuery: vi.fn(),
|
||||
handleOnUnitsChange: vi.fn(),
|
||||
updateAllQueriesOperators: ((q: any) => q) as any,
|
||||
updateQueriesData: ((q: any) => q) as any,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
initQueryBuilderData: vi.fn(),
|
||||
isStagedQueryUpdated: vi.fn(() => false),
|
||||
isDefaultQuery: vi.fn(() => false),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
clearPreviousQuery();
|
||||
});
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('previousQuery.utils', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('getQueryKey normalizes non-meter signal to empty string', () => {
|
||||
@@ -150,7 +150,7 @@ describe('previousQuery.utils', () => {
|
||||
});
|
||||
|
||||
it('write errors (e.g., quota) are caught and do not throw', () => {
|
||||
const spy = jest
|
||||
const spy = vi
|
||||
.spyOn(window.sessionStorage.__proto__, 'setItem')
|
||||
.mockImplementation(() => {
|
||||
throw new Error('quota exceeded');
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
|
||||
describe('convertFiltersToExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should handle empty, null, and undefined inputs', () => {
|
||||
@@ -984,7 +984,7 @@ describe('convertAggregationToExpression', () => {
|
||||
|
||||
describe('removeKeysFromExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Backward compatibility (removeOnlyVariableExpressions = false)', () => {
|
||||
@@ -1386,7 +1386,7 @@ describe('removeKeysFromExpression', () => {
|
||||
|
||||
describe('formatValueForExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Variable values', () => {
|
||||
|
||||
@@ -15,18 +15,18 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import CheckboxFilter from './Checkbox';
|
||||
|
||||
// Mock the query builder hook
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
const mockUseQueryBuilder = jest.mocked(useQueryBuilder);
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
const mockUseQueryBuilder = vi.mocked(useQueryBuilder);
|
||||
|
||||
// Mock the aggregate values hook
|
||||
jest.mock('hooks/queryBuilder/useGetAggregateValues');
|
||||
vi.mock('hooks/queryBuilder/useGetAggregateValues');
|
||||
|
||||
const mockUseGetAggregateValues = jest.mocked(useGetAggregateValues);
|
||||
const mockUseGetAggregateValues = vi.mocked(useGetAggregateValues);
|
||||
|
||||
// Mock the key value suggestions hook
|
||||
jest.mock('hooks/querySuggestions/useGetQueryKeyValueSuggestions');
|
||||
vi.mock('hooks/querySuggestions/useGetQueryKeyValueSuggestions');
|
||||
|
||||
const mockUseGetQueryKeyValueSuggestions = jest.mocked(
|
||||
const mockUseGetQueryKeyValueSuggestions = vi.mocked(
|
||||
useGetQueryKeyValueSuggestions,
|
||||
);
|
||||
|
||||
@@ -90,13 +90,13 @@ const createMockQueryBuilderData = (hasActiveFilters = false): any => ({
|
||||
],
|
||||
},
|
||||
},
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
});
|
||||
|
||||
describe('CheckboxFilter - User Flows', () => {
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Default mock implementations for useGetAggregateValues
|
||||
mockUseGetAggregateValues.mockReturnValue({
|
||||
@@ -106,7 +106,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
} as unknown as UseQueryResult<SuccessResponse<IAttributeValuesResponse>>);
|
||||
|
||||
// Default mock implementations for useGetQueryKeyValueSuggestions
|
||||
@@ -123,7 +123,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
} as any);
|
||||
|
||||
// Setup MSW server for API calls
|
||||
@@ -205,7 +205,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
});
|
||||
|
||||
it('should update query filters when a checkbox is clicked', async () => {
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const redirectWithQueryBuilderData = vi.fn();
|
||||
|
||||
// Start with no active filters so clicking a checkbox creates one
|
||||
mockUseQueryBuilder.mockReturnValue({
|
||||
@@ -246,7 +246,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
});
|
||||
|
||||
it('should set an IN filter with only the clicked value when using Only', async () => {
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const redirectWithQueryBuilderData = vi.fn();
|
||||
|
||||
// Existing filter: service.name IN ['mq-kafka', 'otel-demo']
|
||||
mockUseQueryBuilder.mockReturnValue({
|
||||
@@ -304,7 +304,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
});
|
||||
|
||||
it('should clear filters for the attribute when using All', async () => {
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const redirectWithQueryBuilderData = vi.fn();
|
||||
|
||||
// Existing filter: service.name IN ['mq-kafka']
|
||||
mockUseQueryBuilder.mockReturnValue({
|
||||
@@ -389,7 +389,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
} as any);
|
||||
|
||||
const mockFilter = createMockFilter({ defaultOpen: false });
|
||||
@@ -414,7 +414,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
});
|
||||
|
||||
it('should extend an existing IN filter when checking an additional value', async () => {
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const redirectWithQueryBuilderData = vi.fn();
|
||||
|
||||
// Existing filter: service.name IN 'mq-kafka'
|
||||
mockUseQueryBuilder.mockReturnValue({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, RenderResult } from 'tests/test-utils';
|
||||
import { render, RenderResult } from 'tests/test-utils-full';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
QuickFiltersSource,
|
||||
} from '../../../types';
|
||||
import CheckboxFilterV2 from './CheckboxFilterV2';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
export const DEFAULT_FILTER: IQuickFiltersConfig = {
|
||||
type: FiltersType.CHECKBOX,
|
||||
@@ -64,12 +65,6 @@ export function mockFieldsValuesAPILoading(): void {
|
||||
);
|
||||
}
|
||||
|
||||
export function setupServer(): void {
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }));
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
}
|
||||
|
||||
// Components read currentQuery for the checkbox state and stagedQuery for the
|
||||
// values fetch; in the app both are set by the same URL sync, so tests pass one
|
||||
// query as both.
|
||||
@@ -83,7 +78,7 @@ export interface FilterItemConfig {
|
||||
}
|
||||
|
||||
export function renderWithFilter(
|
||||
onFilterChange: jest.Mock,
|
||||
onFilterChange: Mock,
|
||||
filterItem?: FilterItemConfig,
|
||||
): RenderResult {
|
||||
const items: TagFilterItem[] = filterItem
|
||||
@@ -123,7 +118,7 @@ export function renderWithFilter(
|
||||
}
|
||||
|
||||
export function getFilterFromCall(
|
||||
onFilterChange: jest.Mock,
|
||||
onFilterChange: Mock,
|
||||
callIndex = 0,
|
||||
): TagFilterItem | undefined {
|
||||
const query = onFilterChange.mock.calls[callIndex]?.[0] as Query | undefined;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { render } from 'tests/test-utils-full';
|
||||
|
||||
import { QuickFiltersSource } from '../../../../types';
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
buildQueryBuilderOverrides,
|
||||
DEFAULT_FILTER,
|
||||
DEFAULT_USE_FIELD_APIS,
|
||||
setupServer,
|
||||
} from '../CheckboxFilterV2.testUtils';
|
||||
|
||||
const USE_FIELD_APIS_AUTO_DERIVE = {
|
||||
@@ -17,8 +16,6 @@ const USE_FIELD_APIS_AUTO_DERIVE = {
|
||||
existingQuery: undefined,
|
||||
};
|
||||
|
||||
setupServer();
|
||||
|
||||
describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
const captureExistingQuery = (): Promise<string | null> =>
|
||||
new Promise((resolve) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { render } from 'tests/test-utils-full';
|
||||
|
||||
import { QuickFiltersSource } from '../../../../types';
|
||||
|
||||
@@ -13,10 +13,19 @@ import {
|
||||
getFilterFromCall,
|
||||
mockFieldsValuesAPI,
|
||||
renderWithFilter,
|
||||
setupServer,
|
||||
} from '../CheckboxFilterV2.testUtils';
|
||||
|
||||
setupServer();
|
||||
// tests/test-utils freezes Date via vi.setSystemTime, which stalls lodash
|
||||
// debounce (it derives remaining wait from Date.now). Search tests use real
|
||||
// timers, so restore the real clock.
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
// CheckboxFilterV2.testUtils still types its helpers with Mock, so bridge
|
||||
// the vitest mock to that type until the helper is ported. Type-only cast.
|
||||
const mockFilterChange = (): Parameters<typeof renderWithFilter>[0] =>
|
||||
vi.fn() as unknown as Parameters<typeof renderWithFilter>[0];
|
||||
|
||||
describe('CheckboxFilterV2 - interactions', () => {
|
||||
describe('search functionality', () => {
|
||||
@@ -542,7 +551,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('does not dispatch on clear when the key has no filter', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = vi.fn();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
stringValues: ['production'],
|
||||
@@ -578,7 +587,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('calls onFilterChange when clear clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
stringValues: ['production'],
|
||||
@@ -625,7 +634,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
describe('value row interactions', () => {
|
||||
it('calls onFilterChange when checkbox value clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
stringValues: ['production', 'staging'],
|
||||
@@ -651,7 +660,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('creates NOT IN filter when unchecking related item with no existing filter', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
relatedValues: ['valueA'],
|
||||
@@ -675,7 +684,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('adds to NOT IN when unchecking a non-excluded (other) item', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
relatedValues: ['valueA'],
|
||||
@@ -699,7 +708,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('adds to NOT IN when unchecking a non-excluded item without related values', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = vi.fn();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
stringValues: ['valueA', 'valueB'],
|
||||
@@ -750,7 +759,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('accumulates both values in IN when toggling checked (related) then unchecked (other)', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
relatedValues: ['valueA'],
|
||||
@@ -777,7 +786,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('adds to NOT IN when toggling checked (related) with existing NOT IN filter', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
relatedValues: ['valueA'],
|
||||
@@ -802,7 +811,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
|
||||
it('creates NOT IN for single value when toggling related item with existing IN filter', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = jest.fn();
|
||||
const onFilterChange = mockFilterChange();
|
||||
|
||||
mockFieldsValuesAPI({
|
||||
relatedValues: ['relatedValue'],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { screen, within } from '@testing-library/react';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { render } from 'tests/test-utils-full';
|
||||
|
||||
import { QuickFiltersSource } from '../../../../types';
|
||||
|
||||
@@ -9,11 +9,8 @@ import {
|
||||
DEFAULT_FILTER,
|
||||
DEFAULT_USE_FIELD_APIS,
|
||||
mockFieldsValuesAPI,
|
||||
setupServer,
|
||||
} from '../CheckboxFilterV2.testUtils';
|
||||
|
||||
setupServer();
|
||||
|
||||
describe('CheckboxFilterV2 - item rules', () => {
|
||||
describe('related values unsupported (existingQuery: null)', () => {
|
||||
it('renders a single flat section even when the api returns related values', async () => {
|
||||
|
||||
@@ -11,10 +11,14 @@ import {
|
||||
DEFAULT_USE_FIELD_APIS,
|
||||
mockFieldsValuesAPI,
|
||||
mockFieldsValuesAPILoading,
|
||||
setupServer,
|
||||
} from '../CheckboxFilterV2.testUtils';
|
||||
|
||||
setupServer();
|
||||
// tests/test-utils freezes Date via vi.setSystemTime, which stalls lodash
|
||||
// debounce (it derives remaining wait from Date.now). Search tests use real
|
||||
// timers, so restore the real clock.
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe('CheckboxFilterV2 - states', () => {
|
||||
describe('loading states', () => {
|
||||
@@ -77,11 +81,13 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
it('shows search spinner when fetching after initial load', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
let requestCount = 0;
|
||||
// Succeed every unsearched (initial) request; delay only the search
|
||||
// request. Branching on searchText instead of request count because
|
||||
// the component can fire the initial fetch twice in the same tick
|
||||
// and a count-based branch hangs the wrong one in browser mode.
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v1/fields/values', (req, res, ctx) => {
|
||||
requestCount += 1;
|
||||
if (requestCount === 1) {
|
||||
if (!req.url.searchParams.get('searchText')) {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
|
||||
@@ -8,13 +8,13 @@ describe('CheckboxFilterV2Header', () => {
|
||||
const defaultProps = {
|
||||
title: 'Environment',
|
||||
isOpen: false,
|
||||
onToggleOpen: jest.fn(),
|
||||
onToggleSearch: jest.fn(),
|
||||
onClear: jest.fn(),
|
||||
onToggleOpen: vi.fn(),
|
||||
onToggleSearch: vi.fn(),
|
||||
onClear: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('collapsed state', () => {
|
||||
@@ -64,7 +64,7 @@ describe('CheckboxFilterV2Header', () => {
|
||||
describe('interactions', () => {
|
||||
it('calls onToggleOpen on header click', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onToggleOpen = jest.fn();
|
||||
const onToggleOpen = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2Header {...defaultProps} onToggleOpen={onToggleOpen} />,
|
||||
);
|
||||
@@ -76,7 +76,7 @@ describe('CheckboxFilterV2Header', () => {
|
||||
|
||||
it('calls onToggleOpen on Enter key', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onToggleOpen = jest.fn();
|
||||
const onToggleOpen = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2Header {...defaultProps} onToggleOpen={onToggleOpen} />,
|
||||
);
|
||||
@@ -89,7 +89,7 @@ describe('CheckboxFilterV2Header', () => {
|
||||
|
||||
it('calls onToggleOpen on Space key', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onToggleOpen = jest.fn();
|
||||
const onToggleOpen = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2Header {...defaultProps} onToggleOpen={onToggleOpen} />,
|
||||
);
|
||||
@@ -102,8 +102,8 @@ describe('CheckboxFilterV2Header', () => {
|
||||
|
||||
it('calls onToggleSearch on search click without toggling open', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onToggleSearch = jest.fn();
|
||||
const onToggleOpen = jest.fn();
|
||||
const onToggleSearch = vi.fn();
|
||||
const onToggleOpen = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2Header
|
||||
{...defaultProps}
|
||||
@@ -121,8 +121,8 @@ describe('CheckboxFilterV2Header', () => {
|
||||
|
||||
it('calls onClear on reset click without toggling open', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClear = jest.fn();
|
||||
const onToggleOpen = jest.fn();
|
||||
const onClear = vi.fn();
|
||||
const onToggleOpen = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2Header
|
||||
{...defaultProps}
|
||||
@@ -143,16 +143,16 @@ describe('CheckboxFilterV2Header', () => {
|
||||
// jsdom has no layout, so truncation is simulated at the prototype level
|
||||
// before mount (the component measures in a layout effect).
|
||||
function mockTitleWidths(scrollWidth: number, clientWidth: number): void {
|
||||
jest
|
||||
vi
|
||||
.spyOn(HTMLElement.prototype, 'scrollWidth', 'get')
|
||||
.mockReturnValue(scrollWidth);
|
||||
jest
|
||||
vi
|
||||
.spyOn(HTMLElement.prototype, 'clientWidth', 'get')
|
||||
.mockReturnValue(clientWidth);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('shows the full name on hover when the title is truncated', async () => {
|
||||
|
||||
@@ -12,13 +12,13 @@ describe('CheckboxFilterV2ValueRow', () => {
|
||||
disabled: false,
|
||||
title: 'Environment',
|
||||
onlyButtonLabel: 'Only',
|
||||
onCheckboxChange: jest.fn(),
|
||||
onOnlyOrAllClick: jest.fn(),
|
||||
onCheckboxChange: vi.fn(),
|
||||
onOnlyOrAllClick: vi.fn(),
|
||||
badge: null as BadgeConfig | null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('checked states', () => {
|
||||
@@ -111,7 +111,7 @@ describe('CheckboxFilterV2ValueRow', () => {
|
||||
|
||||
it('does not call onOnlyOrAllClick when disabled + clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOnlyOrAllClick = jest.fn();
|
||||
const onOnlyOrAllClick = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2ValueRow
|
||||
{...defaultProps}
|
||||
@@ -127,7 +127,7 @@ describe('CheckboxFilterV2ValueRow', () => {
|
||||
|
||||
it('does not call onOnlyOrAllClick on keydown when disabled', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOnlyOrAllClick = jest.fn();
|
||||
const onOnlyOrAllClick = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2ValueRow
|
||||
{...defaultProps}
|
||||
@@ -181,7 +181,7 @@ describe('CheckboxFilterV2ValueRow', () => {
|
||||
|
||||
it('calls onOnlyOrAllClick on value text click', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOnlyOrAllClick = jest.fn();
|
||||
const onOnlyOrAllClick = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2ValueRow
|
||||
{...defaultProps}
|
||||
@@ -196,7 +196,7 @@ describe('CheckboxFilterV2ValueRow', () => {
|
||||
|
||||
it('calls onOnlyOrAllClick on Enter key', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOnlyOrAllClick = jest.fn();
|
||||
const onOnlyOrAllClick = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2ValueRow
|
||||
{...defaultProps}
|
||||
@@ -218,7 +218,7 @@ describe('CheckboxFilterV2ValueRow', () => {
|
||||
|
||||
it('calls onOnlyOrAllClick on Space key', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOnlyOrAllClick = jest.fn();
|
||||
const onOnlyOrAllClick = vi.fn();
|
||||
render(
|
||||
<CheckboxFilterV2ValueRow
|
||||
{...defaultProps}
|
||||
|
||||
@@ -26,13 +26,14 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import QuickFilters from '../QuickFilters';
|
||||
import { QuickFiltersSource, SignalType } from '../types';
|
||||
import { QuickFiltersConfig } from './constants';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: jest.fn(),
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: vi.fn(),
|
||||
}));
|
||||
jest.mock('container/ApiMonitoring/queryParams');
|
||||
vi.mock('container/ApiMonitoring/queryParams');
|
||||
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
const mockUseApiMonitoringParams = vi.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
@@ -46,7 +47,7 @@ const FILTER_SERVICE_NAME = 'Service Name';
|
||||
const SETTINGS_CONTAINER_TEST_ID = 'settings-icon-container';
|
||||
|
||||
beforeEach(() => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
(useQueryBuilder as Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
@@ -58,11 +59,11 @@ beforeEach(() => {
|
||||
},
|
||||
},
|
||||
lastUsedQuery: 0,
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
});
|
||||
mockUseApiMonitoringParams.mockReturnValue([
|
||||
{ showIP: true } as ApiMonitoringParams,
|
||||
jest.fn(),
|
||||
vi.fn(),
|
||||
]);
|
||||
server.use(
|
||||
rest.get(quickFiltersListURL, (_req, res, ctx) =>
|
||||
@@ -82,7 +83,7 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
function renderWithSignal(): void {
|
||||
@@ -90,7 +91,7 @@ function renderWithSignal(): void {
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.LOGS_EXPLORER}
|
||||
signal={SIGNAL}
|
||||
handleFilterVisibilityChange={jest.fn()}
|
||||
handleFilterVisibilityChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
@@ -100,7 +101,7 @@ function renderStaticConfig(): void {
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.EXCEPTIONS}
|
||||
config={QuickFiltersConfig}
|
||||
handleFilterVisibilityChange={jest.fn()}
|
||||
handleFilterVisibilityChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,19 +18,20 @@ import '@testing-library/jest-dom';
|
||||
import QuickFilters from '../QuickFilters';
|
||||
import { IQuickFiltersConfig, QuickFiltersSource, SignalType } from '../types';
|
||||
import { QuickFiltersConfig } from './constants';
|
||||
import type { Mock, MockedFunction } from 'vitest';
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: jest.fn(),
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: vi.fn(),
|
||||
}));
|
||||
jest.mock('container/ApiMonitoring/queryParams');
|
||||
vi.mock('container/ApiMonitoring/queryParams');
|
||||
|
||||
const handleFilterVisibilityChange = jest.fn();
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const putHandler = jest.fn();
|
||||
const mockSetApiMonitoringParams = jest.fn() as jest.MockedFunction<
|
||||
const handleFilterVisibilityChange = vi.fn();
|
||||
const redirectWithQueryBuilderData = vi.fn();
|
||||
const putHandler = vi.fn();
|
||||
const mockSetApiMonitoringParams = vi.fn() as MockedFunction<
|
||||
(newParams: Partial<ApiMonitoringParams>, replace?: boolean) => void
|
||||
>;
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
const mockUseApiMonitoringParams = vi.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
@@ -115,21 +116,16 @@ TestQuickFiltersApiMonitoring.defaultProps = {
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
vi.clearAllMocks();
|
||||
// One test below swaps in fake timers. Restoring them here rather than at the
|
||||
// end of that test keeps a failure there from hanging every test after it.
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
(useQueryBuilder as Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
@@ -158,10 +154,10 @@ describe('Quick Filters', () => {
|
||||
});
|
||||
|
||||
it('should display and allow selection from query dropdown when multiple queries exist', async () => {
|
||||
const setLastUsedQuery = jest.fn();
|
||||
const setLastUsedQuery = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
(useQueryBuilder as Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
@@ -213,7 +209,7 @@ describe('Quick Filters', () => {
|
||||
});
|
||||
|
||||
it('should not display query dropdown in ListView', () => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
(useQueryBuilder as Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
@@ -316,8 +312,9 @@ describe('Quick Filters with custom filters', () => {
|
||||
expect(screen.getByText(QUERY_NAME)).toBeInTheDocument();
|
||||
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
const allByText = await screen.findAllByText('otel-demo');
|
||||
expect(allByText).toHaveLength(2);
|
||||
// findAllBy* resolves on the first match, and the second occurrence only
|
||||
// arrives with the values fetch.
|
||||
await waitFor(() => expect(screen.getAllByText('otel-demo')).toHaveLength(2));
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
const settingsButton = icon.closest('button') ?? icon;
|
||||
@@ -520,10 +517,16 @@ describe('Quick Filters with custom filters', () => {
|
||||
});
|
||||
|
||||
it('should render duration slider for duration_nono filter', async () => {
|
||||
// Use fake timers only in this test (for debounce), and wire them to userEvent
|
||||
jest.useFakeTimers();
|
||||
// Fake timers cover the debounce only. `findBy*` does not advance vitest's
|
||||
// fake clock, so the initial fetch has to settle on real timers first.
|
||||
const user = userEvent.setup({
|
||||
advanceTimers: (ms) => jest.advanceTimersByTime(ms),
|
||||
// userEvent calls this for its own inter-event delay too, which happens
|
||||
// before the clock is faked below.
|
||||
advanceTimers: (ms) => {
|
||||
if (vi.isFakeTimers()) {
|
||||
vi.advanceTimersByTime(ms);
|
||||
}
|
||||
},
|
||||
pointerEventsCheck: 0,
|
||||
});
|
||||
|
||||
@@ -543,11 +546,13 @@ describe('Quick Filters with custom filters', () => {
|
||||
expect(maxDuration).toHaveProperty('placeholder', '100000000');
|
||||
|
||||
// Type values and advance debounce
|
||||
vi.useFakeTimers();
|
||||
await user.clear(minDuration);
|
||||
await user.type(minDuration, '10000');
|
||||
await user.clear(maxDuration);
|
||||
await user.type(maxDuration, '20000');
|
||||
jest.advanceTimersByTime(2000);
|
||||
vi.advanceTimersByTime(2000);
|
||||
vi.useRealTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
|
||||
@@ -575,8 +580,6 @@ describe('Quick Filters with custom filters', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { render, screen, userEvent } from 'tests/test-utils';
|
||||
|
||||
import ResizeTable from '../ResizeTable';
|
||||
|
||||
jest.mock('react-resizable', () => ({
|
||||
vi.mock('react-resizable', () => ({
|
||||
Resizable: ({
|
||||
children,
|
||||
onResize,
|
||||
@@ -28,8 +28,8 @@ jest.mock('react-resizable', () => ({
|
||||
}));
|
||||
|
||||
// Make debounce synchronous so onColumnWidthsChange fires immediately
|
||||
jest.mock('lodash-es', () => ({
|
||||
...jest.requireActual('lodash-es'),
|
||||
vi.mock('lodash-es', async () => ({
|
||||
...(await vi.importActual('lodash-es')),
|
||||
debounce: (fn: (...args: any[]) => any): ((...args: any[]) => any) => fn,
|
||||
}));
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('ResizeTable', () => {
|
||||
});
|
||||
|
||||
it('overrides column widths from columnWidths prop and reports them via onColumnWidthsChange', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
act(() => {
|
||||
render(
|
||||
@@ -80,7 +80,7 @@ describe('ResizeTable', () => {
|
||||
});
|
||||
|
||||
it('reports original column widths via onColumnWidthsChange when columnWidths prop is not provided', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
act(() => {
|
||||
render(
|
||||
@@ -112,7 +112,7 @@ describe('ResizeTable', () => {
|
||||
});
|
||||
|
||||
it('only overrides the column that has a stored width, leaving others at their original width', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
act(() => {
|
||||
render(
|
||||
@@ -132,7 +132,7 @@ describe('ResizeTable', () => {
|
||||
});
|
||||
|
||||
it('does not call onColumnWidthsChange on re-render when widths have not changed', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<ResizeTable
|
||||
@@ -159,7 +159,7 @@ describe('ResizeTable', () => {
|
||||
});
|
||||
|
||||
it('does not call onColumnWidthsChange when no column has a defined width', () => {
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
@@ -178,7 +178,7 @@ describe('ResizeTable', () => {
|
||||
|
||||
it('calls onColumnWidthsChange with the new width after a column is resized', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
@@ -202,7 +202,7 @@ describe('ResizeTable', () => {
|
||||
|
||||
it('does not affect other columns when only one column is resized', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onColumnWidthsChange = jest.fn();
|
||||
const onColumnWidthsChange = vi.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
@@ -225,7 +225,7 @@ describe('ResizeTable', () => {
|
||||
});
|
||||
|
||||
it('wraps column titles in drag handler spans when onDragColumn is provided', () => {
|
||||
const onDragColumn = jest.fn();
|
||||
const onDragColumn = vi.fn();
|
||||
|
||||
render(
|
||||
<ResizeTable
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('RouteTab component', () => {
|
||||
});
|
||||
|
||||
it('calls onChangeHandler on tab change', () => {
|
||||
const onChangeHandler = jest.fn();
|
||||
const onChangeHandler = vi.fn();
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
|
||||
@@ -7,26 +7,28 @@ import {
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils-full';
|
||||
|
||||
import AddKeyModal from '../AddKeyModal';
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui/sonner', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui/sonner')),
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
const mockCopyState = { value: undefined, error: undefined };
|
||||
const { mockCopyToClipboard, mockCopyState } = vi.hoisted(() => ({
|
||||
mockCopyToClipboard: vi.fn(),
|
||||
mockCopyState: { value: undefined, error: undefined },
|
||||
}));
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
vi.mock('react-use', () => ({
|
||||
useCopyToClipboard: (): [typeof mockCopyState, typeof mockCopyToClipboard] => [
|
||||
mockCopyState,
|
||||
mockCopyToClipboard,
|
||||
],
|
||||
}));
|
||||
|
||||
const mockToast = jest.mocked(toast);
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys';
|
||||
|
||||
@@ -53,7 +55,7 @@ function renderModal(): ReturnType<typeof render> {
|
||||
|
||||
describe('AddKeyModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockCopyToClipboard.mockClear();
|
||||
server.use(
|
||||
rest.post(SA_KEYS_ENDPOINT, (_, res, ctx) =>
|
||||
|
||||
@@ -3,11 +3,12 @@ import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/s
|
||||
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils-full';
|
||||
|
||||
import EditKeyModal from '../EditKeyModal';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
vi.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
children,
|
||||
@@ -16,12 +17,12 @@ jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
}): React.ReactElement => children,
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui/sonner', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui/sonner')),
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const mockToast = jest.mocked(toast);
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
const SA_KEY_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys/key-1';
|
||||
|
||||
@@ -39,7 +40,7 @@ function renderModal(
|
||||
account: 'sa-1',
|
||||
'edit-key': 'key-1',
|
||||
},
|
||||
onUrlUpdate?: jest.Mock,
|
||||
onUrlUpdate?: Mock,
|
||||
): ReturnType<typeof render> {
|
||||
return render(
|
||||
<NuqsTestingAdapter
|
||||
@@ -54,7 +55,7 @@ function renderModal(
|
||||
|
||||
describe('EditKeyModal (URL-controlled)', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
server.use(
|
||||
rest.put(SA_KEY_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||
@@ -106,7 +107,7 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
|
||||
it('cancel clears edit-key param and closes modal', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onUrlUpdate = jest.fn();
|
||||
const onUrlUpdate = vi.fn();
|
||||
renderModal(mockKey, undefined, onUrlUpdate);
|
||||
|
||||
await screen.findByDisplayValue('Original Key Name');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user