mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-05 18:10:31 +01:00
Compare commits
2 Commits
postproces
...
feat/vites
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5bf3ee981 | ||
|
|
780fffa0ef |
@@ -13,8 +13,6 @@ global:
|
||||
ingestion_url: <unset>
|
||||
# the url of the SigNoz MCP server. when unset, the MCP settings page is hidden in the frontend.
|
||||
# mcp_url: <unset>
|
||||
# the url of the SigNoz AI Assistant server. when unset, the AI Assistant is hidden in the frontend.
|
||||
# ai_assistant_url: <unset>
|
||||
|
||||
##################### Version #####################
|
||||
version:
|
||||
|
||||
@@ -96,122 +96,6 @@ components:
|
||||
- createdAt
|
||||
- updatedAt
|
||||
type: object
|
||||
AlertmanagertypesPostableChannel:
|
||||
oneOf:
|
||||
- required:
|
||||
- discord_configs
|
||||
- required:
|
||||
- email_configs
|
||||
- required:
|
||||
- incidentio_configs
|
||||
- required:
|
||||
- pagerduty_configs
|
||||
- required:
|
||||
- slack_configs
|
||||
- required:
|
||||
- webhook_configs
|
||||
- required:
|
||||
- opsgenie_configs
|
||||
- required:
|
||||
- wechat_configs
|
||||
- required:
|
||||
- pushover_configs
|
||||
- required:
|
||||
- victorops_configs
|
||||
- required:
|
||||
- sns_configs
|
||||
- required:
|
||||
- telegram_configs
|
||||
- required:
|
||||
- webex_configs
|
||||
- required:
|
||||
- msteams_configs
|
||||
- required:
|
||||
- msteamsv2_configs
|
||||
- required:
|
||||
- jira_configs
|
||||
- required:
|
||||
- rocketchat_configs
|
||||
- required:
|
||||
- mattermost_configs
|
||||
properties:
|
||||
discord_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigDiscordConfig'
|
||||
type: array
|
||||
email_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigEmailConfig'
|
||||
type: array
|
||||
incidentio_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigIncidentioConfig'
|
||||
type: array
|
||||
jira_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigJiraConfig'
|
||||
type: array
|
||||
mattermost_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigMattermostConfig'
|
||||
type: array
|
||||
msteams_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigMSTeamsConfig'
|
||||
type: array
|
||||
msteamsv2_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigMSTeamsV2Config'
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
opsgenie_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigOpsGenieConfig'
|
||||
type: array
|
||||
pagerduty_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigPagerdutyConfig'
|
||||
type: array
|
||||
pushover_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigPushoverConfig'
|
||||
type: array
|
||||
rocketchat_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigRocketchatConfig'
|
||||
type: array
|
||||
slack_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigSlackConfig'
|
||||
type: array
|
||||
sns_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigSNSConfig'
|
||||
type: array
|
||||
telegram_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigTelegramConfig'
|
||||
type: array
|
||||
victorops_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigVictorOpsConfig'
|
||||
type: array
|
||||
webex_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigWebexConfig'
|
||||
type: array
|
||||
webhook_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigWebhookConfig'
|
||||
type: array
|
||||
wechat_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigWechatConfig'
|
||||
type: array
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
AlertmanagertypesPostableRoutePolicy:
|
||||
properties:
|
||||
channels:
|
||||
@@ -249,10 +133,6 @@ components:
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesAuthDomainConfig:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
- $ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
- $ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
properties:
|
||||
googleAuthConfig:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
@@ -265,15 +145,8 @@ components:
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
ssoType:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesAuthNProvider:
|
||||
enum:
|
||||
- google_auth
|
||||
- saml
|
||||
- email_password
|
||||
- oidc
|
||||
type: string
|
||||
AuthtypesAuthNProviderInfo:
|
||||
properties:
|
||||
relayStatePath:
|
||||
@@ -296,7 +169,7 @@ components:
|
||||
AuthtypesCallbackAuthNSupport:
|
||||
properties:
|
||||
provider:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type: object
|
||||
@@ -304,17 +177,27 @@ components:
|
||||
properties:
|
||||
authNProviderInfo:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProviderInfo'
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
googleAuthConfig:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
oidcConfig:
|
||||
$ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
orgId:
|
||||
type: string
|
||||
roleMapping:
|
||||
$ref: '#/components/schemas/AuthtypesRoleMapping'
|
||||
samlConfig:
|
||||
$ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
ssoEnabled:
|
||||
type: boolean
|
||||
ssoType:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
@@ -440,7 +323,7 @@ components:
|
||||
AuthtypesPasswordAuthNSupport:
|
||||
properties:
|
||||
provider:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
type: string
|
||||
type: object
|
||||
AuthtypesPatchableObjects:
|
||||
properties:
|
||||
@@ -575,7 +458,7 @@ components:
|
||||
- relation
|
||||
- object
|
||||
type: object
|
||||
AuthtypesUpdatableAuthDomain:
|
||||
AuthtypesUpdateableAuthDomain:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/AuthtypesAuthDomainConfig'
|
||||
@@ -2480,9 +2363,6 @@ components:
|
||||
type: object
|
||||
GlobaltypesConfig:
|
||||
properties:
|
||||
ai_assistant_url:
|
||||
nullable: true
|
||||
type: string
|
||||
external_url:
|
||||
type: string
|
||||
identN:
|
||||
@@ -2496,7 +2376,6 @@ components:
|
||||
- external_url
|
||||
- ingestion_url
|
||||
- mcp_url
|
||||
- ai_assistant_url
|
||||
type: object
|
||||
GlobaltypesIdentNConfig:
|
||||
properties:
|
||||
@@ -5321,9 +5200,6 @@ components:
|
||||
sub_tree_node_count:
|
||||
minimum: 0
|
||||
type: integer
|
||||
time_unix:
|
||||
minimum: 0
|
||||
type: integer
|
||||
trace_id:
|
||||
type: string
|
||||
trace_state:
|
||||
@@ -5789,7 +5665,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AlertmanagertypesPostableChannel'
|
||||
$ref: '#/components/schemas/ConfigReceiver'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
@@ -7068,20 +6944,20 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesPostableAuthDomain'
|
||||
responses:
|
||||
"201":
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesIdentifiable'
|
||||
$ref: '#/components/schemas/AuthtypesGettableAuthDomain'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
@@ -7166,63 +7042,6 @@ paths:
|
||||
summary: Delete auth domain
|
||||
tags:
|
||||
- authdomains
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns an auth domain by ID
|
||||
operationId: GetAuthDomain
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/AuthtypesGettableAuthDomain'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Get auth domain by ID
|
||||
tags:
|
||||
- authdomains
|
||||
put:
|
||||
deprecated: false
|
||||
description: This endpoint updates an auth domain
|
||||
@@ -7237,7 +7056,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthtypesUpdatableAuthDomain'
|
||||
$ref: '#/components/schemas/AuthtypesUpdateableAuthDomain'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
|
||||
@@ -1,51 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
// Mock for uplot library used in tests
|
||||
import { type Mock, vi } from 'vitest';
|
||||
|
||||
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,4 +1,6 @@
|
||||
// Mock for useSafeNavigate hook to avoid React Router version conflicts in tests
|
||||
import { type MockedFunction, vi } from 'vitest';
|
||||
|
||||
interface SafeNavigateOptions {
|
||||
replace?: boolean;
|
||||
state?: unknown;
|
||||
@@ -14,15 +16,15 @@ interface SafeNavigateTo {
|
||||
type SafeNavigateToType = string | SafeNavigateTo;
|
||||
|
||||
interface UseSafeNavigateReturn {
|
||||
safeNavigate: jest.MockedFunction<
|
||||
safeNavigate: MockedFunction<
|
||||
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||
>;
|
||||
}
|
||||
|
||||
export const useSafeNavigate = (): UseSafeNavigateReturn => ({
|
||||
safeNavigate: jest.fn(
|
||||
safeNavigate: vi.fn(
|
||||
(_to: SafeNavigateToType, _options?: SafeNavigateOptions) => {},
|
||||
) as jest.MockedFunction<
|
||||
) as MockedFunction<
|
||||
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||
>,
|
||||
});
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
"lint:generated": "oxlint ./src/api/generated --fix",
|
||||
"lint:fix": "oxlint ./src --fix",
|
||||
"lint:styles": "stylelint \"src/**/*.scss\"",
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure) && node scripts/update-registry.cjs",
|
||||
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure) && node scripts/update-registry.cjs && patch-package",
|
||||
"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": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:changedsince": "vitest run --changed HEAD~1 --coverage",
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh",
|
||||
"generate:permissions-type": "node scripts/generate-permissions-type.cjs"
|
||||
},
|
||||
@@ -171,7 +170,7 @@
|
||||
"@commitlint/config-conventional": "^20.4.2",
|
||||
"@faker-js/faker": "9.3.0",
|
||||
"@jest/globals": "30.2.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/jest-dom": "6.9.1",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/color": "^3.0.3",
|
||||
@@ -199,9 +198,11 @@
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260421.2",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"autoprefixer": "10.4.19",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"eslint-plugin-sonarjs": "4.0.2",
|
||||
"happy-dom": "20.9.0",
|
||||
"husky": "^7.0.4",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin-svgo": "^10.0.1",
|
||||
@@ -216,6 +217,7 @@
|
||||
"oxfmt": "0.47.0",
|
||||
"oxlint": "1.62.0",
|
||||
"oxlint-tsgolint": "0.22.1",
|
||||
"patch-package": "8.0.1",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-scss": "4.0.9",
|
||||
@@ -232,15 +234,15 @@
|
||||
"ts-jest": "29.4.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"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": "4.1.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"oxlint --fix",
|
||||
"oxlint --fix --quiet",
|
||||
"oxfmt --write",
|
||||
"sh -c tsgo --noEmit"
|
||||
],
|
||||
|
||||
46
frontend/patches/@mswjs+interceptors+0.17.10.patch
Normal file
46
frontend/patches/@mswjs+interceptors+0.17.10.patch
Normal file
@@ -0,0 +1,46 @@
|
||||
diff --git a/node_modules/@mswjs/interceptors/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js b/node_modules/@mswjs/interceptors/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js
|
||||
index 2480e76..67208c4 100644
|
||||
--- a/node_modules/@mswjs/interceptors/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js
|
||||
+++ b/node_modules/@mswjs/interceptors/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js
|
||||
@@ -345,7 +345,14 @@ var createXMLHttpRequestOverride = function (options) {
|
||||
});
|
||||
};
|
||||
XMLHttpRequestOverride.prototype.abort = function () {
|
||||
- this.log('abort');
|
||||
+ if (typeof this.log === 'function') {
|
||||
+ this.log('abort');
|
||||
+ }
|
||||
+ if (typeof this.setReadyState !== 'function' ||
|
||||
+ typeof this.trigger !== 'function' ||
|
||||
+ typeof this.readyState !== 'number') {
|
||||
+ return;
|
||||
+ }
|
||||
if (this.readyState > this.UNSENT && this.readyState < this.DONE) {
|
||||
this.setReadyState(this.UNSENT);
|
||||
this.trigger('abort');
|
||||
@@ -459,14 +466,17 @@ var createXMLHttpRequestOverride = function (options) {
|
||||
}
|
||||
finally { if (e_2) throw e_2.error; }
|
||||
}
|
||||
- request.onabort = this.abort;
|
||||
- request.onerror = this.onerror;
|
||||
- request.ontimeout = this.ontimeout;
|
||||
- request.onload = this.onload;
|
||||
- request.onloadstart = this.onloadstart;
|
||||
- request.onloadend = this.onloadend;
|
||||
- request.onprogress = this.onprogress;
|
||||
- request.onreadystatechange = this.onreadystatechange;
|
||||
+ request.abort = this.abort.bind(this);
|
||||
+ request.onabort = this.abort.bind(this);
|
||||
+ request.onerror = this.onerror ? this.onerror.bind(this) : null;
|
||||
+ request.ontimeout = this.ontimeout ? this.ontimeout.bind(this) : null;
|
||||
+ request.onload = this.onload ? this.onload.bind(this) : null;
|
||||
+ request.onloadstart = this.onloadstart ? this.onloadstart.bind(this) : null;
|
||||
+ request.onloadend = this.onloadend ? this.onloadend.bind(this) : null;
|
||||
+ request.onprogress = this.onprogress ? this.onprogress.bind(this) : null;
|
||||
+ request.onreadystatechange = this.onreadystatechange
|
||||
+ ? this.onreadystatechange.bind(this)
|
||||
+ : null;
|
||||
};
|
||||
/**
|
||||
* Propagates the mock XMLHttpRequest instance listeners
|
||||
@@ -1,3 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ReactElement } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter, Route, Switch, useLocation } from 'react-router-dom';
|
||||
@@ -22,13 +23,13 @@ import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import PrivateRoute from '../Private';
|
||||
|
||||
// Mock localStorage APIs
|
||||
const mockLocalStorage: Record<string, string> = {};
|
||||
jest.mock('api/browser/localstorage/get', () => ({
|
||||
const mockLocalStorage = vi.hoisted((): Record<string, 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 +37,29 @@ jest.mock('api/browser/localstorage/set', () => ({
|
||||
}));
|
||||
|
||||
// Mock useGetTenantLicense hook
|
||||
let mockIsCloudUser = true;
|
||||
jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
const mockTenantLicense = vi.hoisted(() => ({ isCloudUser: true }));
|
||||
vi.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: (): {
|
||||
isCloudUser: boolean;
|
||||
isEnterpriseSelfHostedUser: boolean;
|
||||
isCommunityUser: boolean;
|
||||
isCommunityEnterpriseUser: boolean;
|
||||
} => ({
|
||||
isCloudUser: mockIsCloudUser,
|
||||
isEnterpriseSelfHostedUser: !mockIsCloudUser,
|
||||
isCloudUser: mockTenantLicense.isCloudUser,
|
||||
isEnterpriseSelfHostedUser: !mockTenantLicense.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 },
|
||||
const mockUsers = vi.hoisted((): { data: { email: string }[] } => ({
|
||||
data: [],
|
||||
}));
|
||||
vi.mock('api/generated/services/users', async () => ({
|
||||
...(await vi.importActual('api/generated/services/users')),
|
||||
useListUsers: vi.fn(() => ({
|
||||
data: { data: mockUsers.data },
|
||||
isFetching: false,
|
||||
})),
|
||||
}));
|
||||
@@ -176,13 +179,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,
|
||||
@@ -202,7 +205,7 @@ function renderPrivateRoute(options: RenderPrivateRouteOptions = {}): void {
|
||||
isCloudUser = true,
|
||||
} = options;
|
||||
|
||||
mockIsCloudUser = isCloudUser;
|
||||
mockTenantLicense.isCloudUser = isCloudUser;
|
||||
|
||||
const contextValue = createMockAppContext(appContext);
|
||||
|
||||
@@ -245,11 +248,11 @@ function assertRendersChildren(): void {
|
||||
|
||||
describe('PrivateRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
queryClient.clear();
|
||||
Object.keys(mockLocalStorage).forEach((key) => delete mockLocalStorage[key]);
|
||||
mockIsCloudUser = true;
|
||||
mockUsersData = [];
|
||||
mockTenantLicense.isCloudUser = true;
|
||||
mockUsers.data = [];
|
||||
});
|
||||
|
||||
describe('Old Routes Handling', () => {
|
||||
@@ -1014,7 +1017,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' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1053,7 +1056,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' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1124,7 +1127,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' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.BILLING,
|
||||
@@ -1149,7 +1152,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is blocked and accessing settings', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.SETTINGS,
|
||||
@@ -1173,7 +1176,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is suspended (DEFAULTED)', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1200,7 +1203,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is access restricted (TERMINATED)', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
@@ -1227,7 +1230,7 @@ describe('PrivateRoute', () => {
|
||||
});
|
||||
|
||||
it('should not redirect to onboarding when workspace is access restricted (EXPIRED)', async () => {
|
||||
mockUsersData = [{ email: 'test@example.com' }];
|
||||
mockUsers.data = [{ email: 'test@example.com' }];
|
||||
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.HOME,
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* basePath is memoized at module init, so each test re-imports the module with
|
||||
* a fresh DOM state via vi.resetModules and dynamic import.
|
||||
*/
|
||||
|
||||
type GetModule = typeof import('../get');
|
||||
|
||||
function loadGetModule(href: string): GetModule {
|
||||
async function loadGetModule(href: string): Promise<GetModule> {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', href);
|
||||
document.head.append(base);
|
||||
|
||||
let mod!: GetModule;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../get');
|
||||
});
|
||||
vi.resetModules();
|
||||
const mod = await import('../get');
|
||||
base.remove();
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
@@ -28,19 +29,19 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('get — root path "/"', () => {
|
||||
it('reads the bare key', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
it('reads the bare key', async () => {
|
||||
const { default: get } = await loadGetModule('/');
|
||||
localStorage.setItem('AUTH_TOKEN', 'tok');
|
||||
expect(get('AUTH_TOKEN')).toBe('tok');
|
||||
});
|
||||
|
||||
it('returns null when key is absent', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
it('returns null when key is absent', async () => {
|
||||
const { default: get } = await loadGetModule('/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('does NOT promote bare keys (no-op at root)', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
it('does NOT promote bare keys (no-op at root)', async () => {
|
||||
const { default: get } = await loadGetModule('/');
|
||||
localStorage.setItem('THEME', 'light');
|
||||
get('THEME');
|
||||
// bare key must still be present — no migration at root
|
||||
@@ -49,19 +50,19 @@ describe('get — root path "/"', () => {
|
||||
});
|
||||
|
||||
describe('get — prefixed path "/signoz/"', () => {
|
||||
it('reads an already-scoped key directly', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('reads an already-scoped key directly', async () => {
|
||||
const { default: get } = await loadGetModule('/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/');
|
||||
it('returns null when neither scoped nor bare key exists', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('lazy-migrates bare key to scoped key on first read', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('lazy-migrates bare key to scoped key on first read', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
localStorage.setItem('AUTH_TOKEN', 'old-tok');
|
||||
|
||||
const result = get('AUTH_TOKEN');
|
||||
@@ -71,8 +72,8 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
expect(localStorage.getItem('AUTH_TOKEN')).toBeNull();
|
||||
});
|
||||
|
||||
it('scoped key takes precedence over bare key', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('scoped key takes precedence over bare key', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
localStorage.setItem('AUTH_TOKEN', 'bare-tok');
|
||||
localStorage.setItem('/signoz/AUTH_TOKEN', 'scoped-tok');
|
||||
|
||||
@@ -81,8 +82,8 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
expect(localStorage.getItem('AUTH_TOKEN')).toBe('bare-tok');
|
||||
});
|
||||
|
||||
it('subsequent reads after migration use scoped key (no double-write)', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('subsequent reads after migration use scoped key (no double-write)', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
localStorage.setItem('THEME', 'dark');
|
||||
|
||||
get('THEME'); // triggers migration
|
||||
@@ -94,31 +95,15 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
});
|
||||
|
||||
describe('get — two-prefix isolation', () => {
|
||||
it('/signoz/ and /testing/ do not share migrated values', () => {
|
||||
it('/signoz/ and /testing/ do not share migrated values', async () => {
|
||||
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();
|
||||
const { default: getSignoz } = await loadGetModule('/signoz/');
|
||||
|
||||
// migrate bare → /signoz/THEME
|
||||
getSignoz('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();
|
||||
const { default: getTesting } = await loadGetModule('/testing/');
|
||||
|
||||
// /testing/ prefix: bare key already gone, scoped key does not exist
|
||||
expect(getTesting('THEME')).toBeNull();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* sessionstorage/get — lazy migration tests.
|
||||
* Mirrors the localStorage get tests; same logic, different storage.
|
||||
@@ -5,17 +7,13 @@
|
||||
|
||||
type GetModule = typeof import('../get');
|
||||
|
||||
function loadGetModule(href: string): GetModule {
|
||||
async function loadGetModule(href: string): Promise<GetModule> {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', href);
|
||||
document.head.append(base);
|
||||
|
||||
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.resetModules();
|
||||
return import('../get');
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
@@ -26,19 +24,19 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('get — root path "/"', () => {
|
||||
it('reads the bare key', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
it('reads the bare key', async () => {
|
||||
const { default: get } = await loadGetModule('/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'true');
|
||||
expect(get('retry-lazy-refreshed')).toBe('true');
|
||||
});
|
||||
|
||||
it('returns null when key is absent', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
it('returns null when key is absent', async () => {
|
||||
const { default: get } = await loadGetModule('/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('does NOT promote bare keys at root', () => {
|
||||
const { default: get } = loadGetModule('/');
|
||||
it('does NOT promote bare keys at root', async () => {
|
||||
const { default: get } = await loadGetModule('/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'true');
|
||||
get('retry-lazy-refreshed');
|
||||
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBe('true');
|
||||
@@ -46,19 +44,19 @@ describe('get — root path "/"', () => {
|
||||
});
|
||||
|
||||
describe('get — prefixed path "/signoz/"', () => {
|
||||
it('reads an already-scoped key directly', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('reads an already-scoped key directly', async () => {
|
||||
const { default: get } = await loadGetModule('/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/');
|
||||
it('returns null when neither scoped nor bare key exists', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
expect(get('MISSING')).toBeNull();
|
||||
});
|
||||
|
||||
it('lazy-migrates bare key to scoped key on first read', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('lazy-migrates bare key to scoped key on first read', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'true');
|
||||
|
||||
const result = get('retry-lazy-refreshed');
|
||||
@@ -68,8 +66,8 @@ describe('get — prefixed path "/signoz/"', () => {
|
||||
expect(sessionStorage.getItem('retry-lazy-refreshed')).toBeNull();
|
||||
});
|
||||
|
||||
it('scoped key takes precedence over bare key', () => {
|
||||
const { default: get } = loadGetModule('/signoz/');
|
||||
it('scoped key takes precedence over bare key', async () => {
|
||||
const { default: get } = await loadGetModule('/signoz/');
|
||||
sessionStorage.setItem('retry-lazy-refreshed', 'bare');
|
||||
sessionStorage.setItem('/signoz/retry-lazy-refreshed', 'scoped');
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import axios from 'api';
|
||||
|
||||
import { getFieldKeys } from '../getFieldKeys';
|
||||
|
||||
// Mock the API instance
|
||||
jest.mock('api', () => ({
|
||||
get: jest.fn(),
|
||||
vi.mock('api', () => ({
|
||||
default: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('getFieldKeys API', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const mockSuccessResponse = {
|
||||
@@ -28,7 +31,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);
|
||||
vi.mocked(axios.get).mockResolvedValueOnce(mockSuccessResponse);
|
||||
|
||||
// Call function with no parameters
|
||||
await getFieldKeys();
|
||||
@@ -41,7 +44,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);
|
||||
vi.mocked(axios.get).mockResolvedValueOnce(mockSuccessResponse);
|
||||
|
||||
// Call function with signal parameter
|
||||
await getFieldKeys('traces');
|
||||
@@ -54,7 +57,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should call API with name parameter when provided', async () => {
|
||||
// Mock successful API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -76,7 +79,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({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -98,7 +101,7 @@ describe('getFieldKeys API', () => {
|
||||
|
||||
it('should return properly formatted response', async () => {
|
||||
// Mock API to return our response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
|
||||
vi.mocked(axios.get).mockResolvedValueOnce(mockSuccessResponse);
|
||||
|
||||
// Call the function
|
||||
const result = await getFieldKeys('traces');
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import axios from 'api';
|
||||
|
||||
import { getFieldValues } from '../getFieldValues';
|
||||
|
||||
// Mock the API instance
|
||||
jest.mock('api', () => ({
|
||||
get: jest.fn(),
|
||||
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({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -38,7 +41,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with signal parameter', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -62,7 +65,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with name parameter', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -86,7 +89,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with value parameter', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -110,7 +113,7 @@ describe('getFieldValues API', () => {
|
||||
|
||||
it('should call the API with time range parameters', async () => {
|
||||
// Mock API response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce({
|
||||
vi.mocked(axios.get).mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
status: 'success',
|
||||
@@ -162,7 +165,7 @@ describe('getFieldValues API', () => {
|
||||
},
|
||||
};
|
||||
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockResponse);
|
||||
vi.mocked(axios.get).mockResolvedValueOnce(mockResponse);
|
||||
|
||||
// Call the function
|
||||
const result = await getFieldValues('traces', 'mixed.values');
|
||||
@@ -193,7 +196,7 @@ describe('getFieldValues API', () => {
|
||||
};
|
||||
|
||||
// Mock API to return our response
|
||||
(axios.get as jest.Mock).mockResolvedValueOnce(mockApiResponse);
|
||||
vi.mocked(axios.get).mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
// Call the function
|
||||
const result = await getFieldValues('traces', 'service.name');
|
||||
|
||||
@@ -19,11 +19,9 @@ import type {
|
||||
|
||||
import type {
|
||||
AuthtypesPostableAuthDomainDTO,
|
||||
AuthtypesUpdatableAuthDomainDTO,
|
||||
CreateAuthDomain201,
|
||||
AuthtypesUpdateableAuthDomainDTO,
|
||||
CreateAuthDomain200,
|
||||
DeleteAuthDomainPathParameters,
|
||||
GetAuthDomain200,
|
||||
GetAuthDomainPathParameters,
|
||||
ListAuthDomains200,
|
||||
RenderErrorResponseDTO,
|
||||
UpdateAuthDomainPathParameters,
|
||||
@@ -126,7 +124,7 @@ export const createAuthDomain = (
|
||||
authtypesPostableAuthDomainDTO: BodyType<AuthtypesPostableAuthDomainDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateAuthDomain201>({
|
||||
return GeneratedAPIInstance<CreateAuthDomain200>({
|
||||
url: `/api/v1/domains`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -279,122 +277,19 @@ export const useDeleteAuthDomain = <
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint returns an auth domain by ID
|
||||
* @summary Get auth domain by ID
|
||||
*/
|
||||
export const getAuthDomain = (
|
||||
{ id }: GetAuthDomainPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetAuthDomain200>({
|
||||
url: `/api/v1/domains/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetAuthDomainQueryKey = ({
|
||||
id,
|
||||
}: GetAuthDomainPathParameters) => {
|
||||
return [`/api/v1/domains/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetAuthDomainQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getAuthDomain>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetAuthDomainPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAuthDomain>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetAuthDomainQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getAuthDomain>>> = ({
|
||||
signal,
|
||||
}) => getAuthDomain({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAuthDomain>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetAuthDomainQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getAuthDomain>>
|
||||
>;
|
||||
export type GetAuthDomainQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get auth domain by ID
|
||||
*/
|
||||
|
||||
export function useGetAuthDomain<
|
||||
TData = Awaited<ReturnType<typeof getAuthDomain>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetAuthDomainPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAuthDomain>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetAuthDomainQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get auth domain by ID
|
||||
*/
|
||||
export const invalidateGetAuthDomain = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetAuthDomainPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetAuthDomainQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint updates an auth domain
|
||||
* @summary Update auth domain
|
||||
*/
|
||||
export const updateAuthDomain = (
|
||||
{ id }: UpdateAuthDomainPathParameters,
|
||||
authtypesUpdatableAuthDomainDTO: BodyType<AuthtypesUpdatableAuthDomainDTO>,
|
||||
authtypesUpdateableAuthDomainDTO: BodyType<AuthtypesUpdateableAuthDomainDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/domains/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: authtypesUpdatableAuthDomainDTO,
|
||||
data: authtypesUpdateableAuthDomainDTO,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -407,7 +302,7 @@ export const getUpdateAuthDomainMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -416,7 +311,7 @@ export const getUpdateAuthDomainMutationOptions = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
@@ -433,7 +328,7 @@ export const getUpdateAuthDomainMutationOptions = <
|
||||
Awaited<ReturnType<typeof updateAuthDomain>>,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
@@ -448,7 +343,7 @@ export type UpdateAuthDomainMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateAuthDomain>>
|
||||
>;
|
||||
export type UpdateAuthDomainMutationBody =
|
||||
BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
export type UpdateAuthDomainMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -463,7 +358,7 @@ export const useUpdateAuthDomain = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
@@ -472,7 +367,7 @@ export const useUpdateAuthDomain = <
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateAuthDomainPathParameters;
|
||||
data: BodyType<AuthtypesUpdatableAuthDomainDTO>;
|
||||
data: BodyType<AuthtypesUpdateableAuthDomainDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
|
||||
@@ -18,7 +18,6 @@ import type {
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
AlertmanagertypesPostableChannelDTO,
|
||||
ConfigReceiverDTO,
|
||||
CreateChannel201,
|
||||
DeleteChannelByIDPathParameters,
|
||||
@@ -123,14 +122,14 @@ export const invalidateListChannels = async (
|
||||
* @summary Create notification channel
|
||||
*/
|
||||
export const createChannel = (
|
||||
alertmanagertypesPostableChannelDTO: BodyType<AlertmanagertypesPostableChannelDTO>,
|
||||
configReceiverDTO: BodyType<ConfigReceiverDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateChannel201>({
|
||||
url: `/api/v1/channels`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: alertmanagertypesPostableChannelDTO,
|
||||
data: configReceiverDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
@@ -142,13 +141,13 @@ export const getCreateChannelMutationOptions = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableChannelDTO> },
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableChannelDTO> },
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createChannel'];
|
||||
@@ -162,7 +161,7 @@ export const getCreateChannelMutationOptions = <
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
{ data: BodyType<AlertmanagertypesPostableChannelDTO> }
|
||||
{ data: BodyType<ConfigReceiverDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
@@ -175,8 +174,7 @@ export const getCreateChannelMutationOptions = <
|
||||
export type CreateChannelMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createChannel>>
|
||||
>;
|
||||
export type CreateChannelMutationBody =
|
||||
BodyType<AlertmanagertypesPostableChannelDTO>;
|
||||
export type CreateChannelMutationBody = BodyType<ConfigReceiverDTO>;
|
||||
export type CreateChannelMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
@@ -189,13 +187,13 @@ export const useCreateChannel = <
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableChannelDTO> },
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableChannelDTO> },
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateChannelMutationOptions(options);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ import { getBasePath } from 'utils/basePath';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
|
||||
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
|
||||
import { retryRequestAfterAuth } from 'api/interceptors';
|
||||
import { Logout } from './utils';
|
||||
|
||||
const RESPONSE_TIMEOUT_THRESHOLD = 5000; // 5 seconds
|
||||
@@ -129,13 +130,10 @@ export const interceptorRejected = async (
|
||||
afterLogin(response.data.accessToken, response.data.refreshToken, true);
|
||||
|
||||
try {
|
||||
const reResponse = await axios({
|
||||
...value.config,
|
||||
headers: {
|
||||
...value.config.headers,
|
||||
Authorization: `Bearer ${response.data.accessToken}`,
|
||||
},
|
||||
});
|
||||
const reResponse = await retryRequestAfterAuth(
|
||||
value.config,
|
||||
response.data.accessToken,
|
||||
);
|
||||
|
||||
return await Promise.resolve(reResponse);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,46 +1,65 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import axios, { AxiosHeaders, AxiosResponse } from 'axios';
|
||||
|
||||
import { interceptorRejected } from './index';
|
||||
|
||||
jest.mock('api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => 'mock-token'),
|
||||
}));
|
||||
|
||||
jest.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() =>
|
||||
const { retryRequestMock, postRotateMock } = vi.hoisted(() => ({
|
||||
retryRequestMock: vi.fn(),
|
||||
postRotateMock: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
data: { accessToken: 'new-token', refreshToken: 'new-refresh' },
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('AppRoutes/utils', () => ({
|
||||
vi.mock('api/interceptors', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
retryRequestAfterAuth: retryRequestMock,
|
||||
}));
|
||||
|
||||
jest.mock('axios', () => {
|
||||
const actualAxios = jest.requireActual('axios');
|
||||
const mockAxios = jest.fn().mockResolvedValue({ data: 'success' });
|
||||
vi.mock('api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: vi.fn(() => 'mock-token'),
|
||||
}));
|
||||
|
||||
vi.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: postRotateMock,
|
||||
}));
|
||||
|
||||
vi.mock('AppRoutes/utils', () => ({
|
||||
__esModule: true,
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('axios', async () => {
|
||||
const actual = await vi.importActual<typeof import('axios')>('axios');
|
||||
return {
|
||||
...actualAxios,
|
||||
default: Object.assign(mockAxios, {
|
||||
...actualAxios.default,
|
||||
isAxiosError: jest.fn().mockReturnValue(true),
|
||||
create: actualAxios.create,
|
||||
...actual,
|
||||
default: Object.assign(actual.default, {
|
||||
isAxiosError: vi.fn(() => true),
|
||||
}),
|
||||
__esModule: true,
|
||||
};
|
||||
});
|
||||
|
||||
describe('interceptorRejected', () => {
|
||||
let interceptorRejected: (value: AxiosResponse) => Promise<AxiosResponse>;
|
||||
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
const mod = await import('./index');
|
||||
interceptorRejected = mod.interceptorRejected;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(axios as unknown as jest.Mock).mockResolvedValue({ data: 'success' });
|
||||
(axios.isAxiosError as unknown as jest.Mock).mockReturnValue(true);
|
||||
vi.clearAllMocks();
|
||||
retryRequestMock.mockResolvedValue({
|
||||
data: 'success',
|
||||
} as unknown as AxiosResponse<{ data: string }>);
|
||||
(
|
||||
axios.isAxiosError as unknown as {
|
||||
mockReturnValue: (value: boolean) => void;
|
||||
}
|
||||
).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should preserve array payload structure when retrying a 401 request', async () => {
|
||||
@@ -75,11 +94,12 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
|
||||
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(arrayPayload);
|
||||
expect(retryRequestMock).toHaveBeenCalledTimes(1);
|
||||
const retryCallConfig = retryRequestMock.mock.calls[0][0];
|
||||
expect(Array.isArray(JSON.parse(retryCallConfig.data as string))).toBe(true);
|
||||
expect(JSON.parse(retryCallConfig.data as string)).toStrictEqual(
|
||||
arrayPayload,
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve object payload structure when retrying a 401 request', async () => {
|
||||
@@ -111,10 +131,11 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(objectPayload);
|
||||
expect(retryRequestMock).toHaveBeenCalledTimes(1);
|
||||
const retryCallConfig = retryRequestMock.mock.calls[0][0];
|
||||
expect(JSON.parse(retryCallConfig.data as string)).toStrictEqual(
|
||||
objectPayload,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle undefined data gracefully when retrying', async () => {
|
||||
@@ -144,9 +165,8 @@ describe('interceptorRejected', () => {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(retryRequestMock).toHaveBeenCalledTimes(1);
|
||||
const retryCallConfig = retryRequestMock.mock.calls[0][0];
|
||||
expect(retryCallConfig.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
17
frontend/src/api/interceptors.ts
Normal file
17
frontend/src/api/interceptors.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import axios, {
|
||||
AxiosHeaders,
|
||||
AxiosResponse,
|
||||
InternalAxiosRequestConfig,
|
||||
} from 'axios';
|
||||
|
||||
export async function retryRequestAfterAuth(
|
||||
valueConfig: InternalAxiosRequestConfig,
|
||||
accessToken: string,
|
||||
): Promise<AxiosResponse> {
|
||||
const headers = new AxiosHeaders(valueConfig.headers);
|
||||
headers.set('Authorization', `Bearer ${accessToken}`);
|
||||
return axios({
|
||||
...valueConfig,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
MetricRangePayloadV5,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, expect, it, vi } 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,
|
||||
@@ -20,9 +22,9 @@ import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
|
||||
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', () => {
|
||||
@@ -515,9 +517,7 @@ 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({
|
||||
vi.mocked(getStartEndRangeTime).mockReturnValueOnce({
|
||||
start: '1754623641',
|
||||
end: '1754645241',
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } 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 = vi.mocked(getLocal);
|
||||
|
||||
describe('AppLoading', () => {
|
||||
const SIGNOZ_TEXT = 'SigNoz';
|
||||
@@ -18,12 +19,12 @@ describe('AppLoading', () => {
|
||||
const CONTAINER_SELECTOR = '.app-loading-container';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render loading screen with dark theme by default', () => {
|
||||
// Mock localStorage to return dark theme (or undefined for default)
|
||||
mockGet.mockReturnValue(undefined);
|
||||
mockGet.mockReturnValue(null);
|
||||
|
||||
render(<AppLoading />);
|
||||
|
||||
@@ -40,14 +41,17 @@ describe('AppLoading', () => {
|
||||
|
||||
it('should have proper structure and content', () => {
|
||||
// Mock localStorage to return dark theme
|
||||
mockGet.mockReturnValue(undefined);
|
||||
mockGet.mockReturnValue(null);
|
||||
|
||||
render(<AppLoading />);
|
||||
|
||||
// Check for brand logo
|
||||
const logo = screen.getByAltText(SIGNOZ_TEXT);
|
||||
expect(logo).toBeInTheDocument();
|
||||
expect(logo).toHaveAttribute('src', 'test-file-stub');
|
||||
expect(logo).toHaveAttribute(
|
||||
'src',
|
||||
expect.stringContaining('data:image/svg+xml'),
|
||||
);
|
||||
|
||||
// Check for brand title
|
||||
const title = screen.getByText(SIGNOZ_TEXT);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { USER_PREFERENCES } from 'constants/userPreferences';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import {
|
||||
ChangelogSchema,
|
||||
DeploymentType,
|
||||
} from 'types/api/changelog/getChangelogByVersion';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import ChangelogModal from '../ChangelogModal';
|
||||
|
||||
@@ -37,27 +37,25 @@ 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,
|
||||
name: 'last_seen_changelog_version',
|
||||
value: 'v1.0.0',
|
||||
},
|
||||
],
|
||||
})),
|
||||
}));
|
||||
|
||||
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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
ChangelogSchema,
|
||||
DeploymentType,
|
||||
@@ -9,13 +10,11 @@ import {
|
||||
import ChangelogRenderer from '../components/ChangelogRenderer';
|
||||
|
||||
// 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>;
|
||||
},
|
||||
}));
|
||||
|
||||
const mockChangelog: ChangelogSchema = {
|
||||
id: 1,
|
||||
|
||||
@@ -1,16 +1,40 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
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,
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock('@signozhq/icons', () => ({
|
||||
Check: (): null => null,
|
||||
Copy: (): null => null,
|
||||
}));
|
||||
|
||||
vi.mock('@signozhq/ui', async () => {
|
||||
const React = await vi.importActual<typeof import('react')>('react');
|
||||
|
||||
return {
|
||||
Button: ({
|
||||
prefix,
|
||||
...props
|
||||
}: {
|
||||
prefix?: ReactNode;
|
||||
[key: string]: unknown;
|
||||
}): ReturnType<typeof React.createElement> =>
|
||||
React.createElement('button', props, prefix),
|
||||
};
|
||||
});
|
||||
|
||||
describe('CodeBlock', () => {
|
||||
beforeEach(() => {
|
||||
mockCopyToClipboard.mockReset();
|
||||
@@ -33,7 +57,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 }));
|
||||
|
||||
@@ -1,28 +1,49 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/icons', () => ({
|
||||
X: ({ size: _size }: any): JSX.Element => <span aria-hidden="true" />,
|
||||
}));
|
||||
|
||||
const mockToast = jest.mocked(toast);
|
||||
vi.mock('@signozhq/ui', () => ({
|
||||
Button: ({
|
||||
children,
|
||||
loading: _loading,
|
||||
variant: _variant,
|
||||
color: _color,
|
||||
...props
|
||||
}: any): JSX.Element => <button {...props}>{children}</button>,
|
||||
DialogFooter: ({ children, ...props }: any): JSX.Element => (
|
||||
<div {...props}>{children}</div>
|
||||
),
|
||||
DialogWrapper: ({ title, open, children }: any): JSX.Element | null =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={title}>
|
||||
{children}
|
||||
</div>
|
||||
) : null,
|
||||
Input: (props: any): JSX.Element => <input {...props} />,
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
const showErrorModal = vi.hoisted(() => 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,
|
||||
})),
|
||||
@@ -40,7 +61,7 @@ function renderModal(): ReturnType<typeof render> {
|
||||
|
||||
describe('CreateServiceAccountModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
server.use(
|
||||
rest.post(SERVICE_ACCOUNTS_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(201), ctx.json({ status: 'success', data: {} })),
|
||||
@@ -126,12 +147,16 @@ describe('CreateServiceAccountModal', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
await screen.findByRole('dialog', {
|
||||
name: /New Service Account/i,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
await waitForElementToBeRemoved(dialog);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /New Service Account/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows "Name is required" after clearing the name field', async () => {
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
import { useState } from 'react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import dayjs from 'dayjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
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 +46,30 @@ jest.mock('providers/Timezone', () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@signozhq/ui', () => ({
|
||||
Button: ({
|
||||
children,
|
||||
prefix,
|
||||
variant: _variant,
|
||||
color: _color,
|
||||
...props
|
||||
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
prefix?: React.ReactNode;
|
||||
variant?: string;
|
||||
color?: string;
|
||||
}): JSX.Element => (
|
||||
<button type="button" {...props}>
|
||||
{prefix}
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
Calendar: (): JSX.Element => <div data-testid="mock-calendar" />,
|
||||
}));
|
||||
|
||||
vi.mock('hooks/useZoomOut', () => ({
|
||||
useZoomOut: vi.fn(() => vi.fn()),
|
||||
}));
|
||||
|
||||
interface WrapperProps {
|
||||
initialValue?: string;
|
||||
showLiveLogs?: boolean;
|
||||
@@ -123,8 +148,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 +166,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 +191,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 +216,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 +238,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.spyOn(timeUtils, 'validateTimeRange');
|
||||
const onCustomDateHandler = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
validateTimeRangeSpy.mockReturnValue({
|
||||
isValid: true,
|
||||
@@ -244,9 +269,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.spyOn(timeUtils, 'validateTimeRange');
|
||||
const onValid = vi.fn();
|
||||
const onError = vi.fn();
|
||||
|
||||
validateTimeRangeSpy.mockReturnValue({
|
||||
isValid: false,
|
||||
|
||||
@@ -2,23 +2,33 @@ 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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
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 {
|
||||
MS_PER_MIN,
|
||||
NOW_MS,
|
||||
mockDispatch,
|
||||
mockSafeNavigate,
|
||||
mockUrlQueryDelete,
|
||||
mockUrlQuerySet,
|
||||
} = vi.hoisted(() => ({
|
||||
MS_PER_MIN: 60 * 1000,
|
||||
NOW_MS: 1705312800000,
|
||||
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 +40,8 @@ jest.mock('react-redux', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
@@ -43,7 +53,7 @@ interface MockUrlQuery {
|
||||
toString: () => string;
|
||||
}
|
||||
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
vi.mock('hooks/useUrlQuery', () => ({
|
||||
__esModule: true,
|
||||
default: (): MockUrlQuery => ({
|
||||
delete: mockUrlQueryDelete,
|
||||
@@ -53,26 +63,46 @@ 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' }),
|
||||
}));
|
||||
|
||||
vi.mock('@signozhq/ui', () => ({
|
||||
Button: ({
|
||||
children,
|
||||
prefix,
|
||||
variant: _variant,
|
||||
color: _color,
|
||||
...props
|
||||
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
prefix?: React.ReactNode;
|
||||
variant?: string;
|
||||
color?: string;
|
||||
}): JSX.Element => (
|
||||
<button type="button" {...props}>
|
||||
{prefix}
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
Calendar: (): JSX.Element => <div data-testid="mock-calendar" />,
|
||||
}));
|
||||
|
||||
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 +113,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,6 @@
|
||||
import type { Mock } from 'vitest';
|
||||
import { beforeEach, describe, expect, it, vi } 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 +16,28 @@ 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, mockUseQueryBuilder } = vi.hoisted(() => ({
|
||||
mockDownloadExportData: vi.fn().mockResolvedValue(undefined),
|
||||
mockUseQueryBuilder: vi.fn(),
|
||||
}));
|
||||
|
||||
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', () => ({
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: (): any => mockUseQueryBuilder(),
|
||||
}));
|
||||
|
||||
@@ -95,8 +101,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),
|
||||
});
|
||||
@@ -307,7 +313,11 @@ describe.each([
|
||||
fireEvent.click(screen.getByText('Export'));
|
||||
|
||||
expect(screen.getByTestId(testId)).toBeDisabled();
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog').closest('.ant-popover')).toHaveStyle({
|
||||
pointerEvents: 'none',
|
||||
});
|
||||
});
|
||||
|
||||
resolveDownload!();
|
||||
|
||||
@@ -323,7 +333,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 () => {
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { Table } from 'antd';
|
||||
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import DraggableTableRow from '..';
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
value: vi.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.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,16 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
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 +19,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 +28,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 +37,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,6 +1,9 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ChangeEventHandler, ReactNode } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { useListRoles } from 'api/generated/services/role';
|
||||
import {
|
||||
useCreateResetPasswordToken,
|
||||
useDeleteUser,
|
||||
@@ -15,88 +18,138 @@ import {
|
||||
listRolesSuccessResponse,
|
||||
managedRoles,
|
||||
} from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
useUpdateUser: jest.fn(),
|
||||
useUpdateMyUserV2: jest.fn(),
|
||||
useSetRoleByUserID: jest.fn(),
|
||||
useGetResetPasswordToken: jest.fn(),
|
||||
useCreateResetPasswordToken: jest.fn(),
|
||||
vi.mock('api/generated/services/role', async () => {
|
||||
const actual = await vi.importActual<
|
||||
typeof import('api/generated/services/role')
|
||||
>('api/generated/services/role');
|
||||
return {
|
||||
...actual,
|
||||
useListRoles: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: vi.fn(),
|
||||
useGetUser: vi.fn(),
|
||||
useUpdateUser: vi.fn(),
|
||||
useUpdateMyUserV2: vi.fn(),
|
||||
useSetRoleByUserID: vi.fn(),
|
||||
useGetResetPasswordToken: vi.fn(),
|
||||
useCreateResetPasswordToken: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
convertToApiError: jest.fn(),
|
||||
vi.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
convertToApiError: vi.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={title}>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): Mock => vi.fn(),
|
||||
}));
|
||||
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
const mockCopyState = { value: undefined, error: undefined };
|
||||
vi.mock('@signozhq/ui', async () => {
|
||||
const React = await vi.importActual<typeof import('react')>('react');
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
return {
|
||||
Badge: ({ children }: { children?: ReactNode }): JSX.Element =>
|
||||
React.createElement('span', null, children),
|
||||
Button: ({
|
||||
children,
|
||||
disabled,
|
||||
onClick,
|
||||
prefix,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
prefix?: ReactNode;
|
||||
}): JSX.Element =>
|
||||
React.createElement('button', { disabled, onClick }, prefix, children),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? React.createElement('div', null, children, footer) : null,
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}): JSX.Element | null =>
|
||||
open
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ role: 'dialog', 'aria-label': title },
|
||||
children,
|
||||
footer,
|
||||
)
|
||||
: null,
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element =>
|
||||
React.createElement('div', null, children),
|
||||
Input: ({
|
||||
disabled,
|
||||
id,
|
||||
onChange,
|
||||
placeholder,
|
||||
value,
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
}): JSX.Element =>
|
||||
React.createElement('input', {
|
||||
disabled,
|
||||
id,
|
||||
onChange,
|
||||
placeholder,
|
||||
value,
|
||||
}),
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { mockCopyToClipboard, mockCopyState, showErrorModal } = vi.hoisted(
|
||||
() => ({
|
||||
mockCopyToClipboard: vi.fn(),
|
||||
mockCopyState: { value: undefined, error: undefined },
|
||||
showErrorModal: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock('react-use', () => ({
|
||||
useCopyToClipboard: (): [typeof mockCopyState, typeof mockCopyToClipboard] => [
|
||||
mockCopyState,
|
||||
mockCopyToClipboard,
|
||||
],
|
||||
}));
|
||||
|
||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
||||
const mockDeleteMutate = vi.fn();
|
||||
const mockCreateTokenMutateAsync = vi.fn();
|
||||
|
||||
const mockDeleteMutate = jest.fn();
|
||||
const mockCreateTokenMutateAsync = jest.fn();
|
||||
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
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,
|
||||
})),
|
||||
@@ -155,8 +208,8 @@ function renderDrawer(
|
||||
<EditMemberDrawer
|
||||
member={activeMember}
|
||||
open
|
||||
onClose={jest.fn()}
|
||||
onComplete={jest.fn()}
|
||||
onClose={vi.fn()}
|
||||
onComplete={vi.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
@@ -164,38 +217,43 @@ function renderDrawer(
|
||||
|
||||
describe('EditMemberDrawer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockCopyState.value = undefined;
|
||||
mockCopyState.error = undefined;
|
||||
showErrorModal.mockClear();
|
||||
server.use(
|
||||
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
|
||||
),
|
||||
);
|
||||
(useGetUser as jest.Mock).mockReturnValue({
|
||||
(useListRoles as Mock).mockReturnValue({
|
||||
data: listRolesSuccessResponse,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
refetch: vi.fn(),
|
||||
isFetching: false,
|
||||
isSuccess: true,
|
||||
status: 'success',
|
||||
});
|
||||
(useGetUser as Mock).mockReturnValue({
|
||||
data: mockFetchedUser,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
(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,
|
||||
});
|
||||
(useSetRoleByUserID as jest.Mock).mockReturnValue({
|
||||
mutateAsync: jest.fn().mockResolvedValue({}),
|
||||
(useSetRoleByUserID 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',
|
||||
@@ -215,20 +273,18 @@ describe('EditMemberDrawer', () => {
|
||||
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
||||
},
|
||||
});
|
||||
(useCreateResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
(useCreateResetPasswordToken as Mock).mockReturnValue({
|
||||
mutateAsync: mockCreateTokenMutateAsync,
|
||||
isLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('renders active member details and disables Save when form is not dirty', () => {
|
||||
it('renders active member details and disables Save when form is not dirty', async () => {
|
||||
renderDrawer();
|
||||
|
||||
expect(screen.getByDisplayValue('Alice Smith')).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByDisplayValue('Alice Smith'),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText('alice@signoz.io')).toBeInTheDocument();
|
||||
expect(screen.getByText('ACTIVE')).toBeInTheDocument();
|
||||
expect(
|
||||
@@ -237,11 +293,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,
|
||||
});
|
||||
@@ -267,7 +323,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 });
|
||||
@@ -289,18 +345,18 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('selecting a different role calls setRole with the new role name', 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({});
|
||||
|
||||
(useSetRoleByUserID as jest.Mock).mockReturnValue({
|
||||
(useSetRoleByUserID as Mock).mockReturnValue({
|
||||
mutateAsync: mockSet,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
renderDrawer({ onComplete });
|
||||
|
||||
// Open the roles dropdown and select signoz-editor
|
||||
await screen.findByTitle('signoz-admin');
|
||||
await user.click(screen.getByLabelText('Roles'));
|
||||
await user.click(await screen.findByTitle('signoz-editor'));
|
||||
|
||||
@@ -318,18 +374,18 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
it('does not call removeRole when the role is changed', 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({});
|
||||
|
||||
(useSetRoleByUserID as jest.Mock).mockReturnValue({
|
||||
(useSetRoleByUserID as Mock).mockReturnValue({
|
||||
mutateAsync: mockSet,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
renderDrawer({ onComplete });
|
||||
|
||||
// Switch from signoz-admin to signoz-viewer using single-select
|
||||
await screen.findByTitle('signoz-admin');
|
||||
await user.click(screen.getByLabelText('Roles'));
|
||||
await user.click(await screen.findByTitle('signoz-viewer'));
|
||||
|
||||
@@ -347,10 +403,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?.();
|
||||
}),
|
||||
@@ -393,7 +449,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',
|
||||
@@ -413,7 +469,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,
|
||||
@@ -427,10 +483,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?.();
|
||||
}),
|
||||
@@ -457,11 +513,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,
|
||||
@@ -477,9 +533,9 @@ describe('EditMemberDrawer', () => {
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
(useUpdateUser as jest.Mock).mockReturnValue({
|
||||
(useUpdateUser as Mock).mockReturnValue({
|
||||
mutateAsync: mockMutateAsync,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -504,7 +560,7 @@ describe('EditMemberDrawer', () => {
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
const mockConvertToApiError = jest.mocked(convertToApiError);
|
||||
const mockConvertToApiError = vi.mocked(convertToApiError);
|
||||
|
||||
beforeEach(() => {
|
||||
mockConvertToApiError.mockReturnValue({
|
||||
@@ -515,8 +571,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,
|
||||
});
|
||||
|
||||
@@ -540,7 +596,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?.({});
|
||||
}),
|
||||
@@ -571,7 +627,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?.({});
|
||||
}),
|
||||
@@ -634,10 +690,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(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -717,7 +773,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,39 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import Editor from './index';
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: jest.fn(),
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@monaco-editor/react', () => ({
|
||||
default: ({ height }: { height?: string }): JSX.Element => (
|
||||
<section
|
||||
style={{
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
textAlign: 'initial',
|
||||
width: '100%',
|
||||
height,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
<div style={{ width: '100%', display: 'none' }} />
|
||||
</section>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('Editor', () => {
|
||||
@@ -34,7 +63,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 +71,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;"
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import {
|
||||
afterAll,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
|
||||
import withErrorBoundary, {
|
||||
WithErrorBoundaryOptions,
|
||||
} 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 +43,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 +73,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 +110,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 +118,7 @@ describe('withErrorBoundary', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should wrap component with ErrorBoundary and render successfully', () => {
|
||||
@@ -162,7 +167,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,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import ErrorModal from './ErrorModal';
|
||||
|
||||
// Mock the query client to return version data
|
||||
const mockVersionData = {
|
||||
const mockVersionData = vi.hoisted(() => ({
|
||||
payload: {
|
||||
ee: 'Y',
|
||||
version: '1.0.0',
|
||||
},
|
||||
};
|
||||
jest.mock('react-query', () => ({
|
||||
...jest.requireActual('react-query'),
|
||||
}));
|
||||
vi.mock('react-query', async () => ({
|
||||
...(await vi.importActual('react-query')),
|
||||
useQueryClient: (): { getQueryData: () => typeof mockVersionData } => ({
|
||||
getQueryData: jest.fn(() => mockVersionData),
|
||||
getQueryData: vi.fn(() => mockVersionData),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
const mockError: APIError = new APIError({
|
||||
@@ -31,7 +38,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 +48,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 +68,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 +89,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 +102,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 +120,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 +132,7 @@ it('should render the trigger component if provided', () => {
|
||||
<ErrorModal
|
||||
error={mockError}
|
||||
triggerComponent={mockTrigger}
|
||||
onClose={jest.fn()}
|
||||
onClose={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -139,7 +146,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 +160,14 @@ 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
|
||||
@@ -179,9 +186,7 @@ it('should close the modal when the onCancel event is triggered', async () => {
|
||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(() => {
|
||||
// check if the modal is not visible
|
||||
const modal = document.getElementsByClassName('ant-modal');
|
||||
const style = window.getComputedStyle(modal[0]);
|
||||
expect(style.display).toBe('none');
|
||||
expect(modal[0]).toHaveClass('ant-zoom-leave');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,57 +3,63 @@ import ROUTES from 'constants/routes';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
const historyReplace = jest.fn();
|
||||
const historyReplace = vi.hoisted(() => vi.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,
|
||||
}),
|
||||
}));
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
return {
|
||||
...actual,
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
useHistory: (): any => ({
|
||||
...actual.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(),
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,21 +2,22 @@ import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
vi.mock('react-router-dom', async () => ({
|
||||
...(await vi.importActual('react-router-dom')),
|
||||
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 () => ({
|
||||
...(await vi.importActual('antd')),
|
||||
useForm: vi.fn().mockReturnValue({
|
||||
onFinish: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -29,7 +30,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 +48,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}
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
vi.mock('react-router-dom', async () => ({
|
||||
...(await vi.importActual<typeof import('react-router-dom')>(
|
||||
'react-router-dom',
|
||||
)),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/services/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -20,13 +22,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 +38,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 +52,8 @@ describe('SaveViewWithName', () => {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
handlePopOverClose={vi.fn()}
|
||||
refetchAllView={vi.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useGlobalTimeStore } from 'store/globalTime/globalTimeStore';
|
||||
import { createCustomTimeRange } from 'store/globalTime/utils';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { GlobalTimeStoreAdapter } from '../GlobalTimeStoreAdapter';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { convertTimeRange, TIME_UNITS } from '../xAxisConfig';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { PrecisionOptionsEnum } from '../types';
|
||||
import { getYAxisFormattedValue } from '../yAxisConfig';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
AuthtypesGettableTransactionDTO,
|
||||
AuthtypesTransactionDTO,
|
||||
@@ -12,6 +13,12 @@ import { render, screen, waitFor } from 'tests/test-utils';
|
||||
|
||||
import { GuardAuthZ } from './GuardAuthZ';
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: () => void } => ({
|
||||
safeNavigate: (): void => {},
|
||||
}),
|
||||
}));
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL || '';
|
||||
const AUTHZ_CHECK_URL = `${BASE_URL}/api/v1/authz/check`;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import AnnouncementsModal from '../AnnouncementsModal';
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Mock dependencies before imports
|
||||
import { describe, expect, beforeEach, it, vi } from 'vitest';
|
||||
import type { Mock, Mocked, MockedFunction } from 'vitest';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
@@ -9,39 +11,38 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
vi.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
default: vi.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
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', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
vi.mock('@signozhq/ui', () => ({
|
||||
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 mockLogEvent = logEvent as jest.MockedFunction<typeof logEvent>;
|
||||
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 mockLogEvent = logEvent as MockedFunction<typeof logEvent>;
|
||||
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',
|
||||
@@ -49,7 +50,7 @@ const mockLocation = {
|
||||
|
||||
describe('FeedbackModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
isCloudUser: false,
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
// Mock dependencies before imports
|
||||
import type { ReactNode } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
||||
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 logEvent from 'api/common/logEvent';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import HeaderRightSection from '../HeaderRightSection';
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
vi.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
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('antd', async () => {
|
||||
const actual = await vi.importActual<typeof import('antd')>('antd');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
Popover: ({
|
||||
children,
|
||||
content,
|
||||
open,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
content: ReactNode;
|
||||
open?: boolean;
|
||||
}): JSX.Element => (
|
||||
<>
|
||||
{children}
|
||||
{open ? content : null}
|
||||
</>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../FeedbackModal', () => ({
|
||||
__esModule: true,
|
||||
default: ({ onClose }: { onClose: () => void }): JSX.Element => (
|
||||
<div data-testid="feedback-modal">
|
||||
@@ -28,27 +52,27 @@ 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(),
|
||||
}));
|
||||
|
||||
const mockLogEvent = logEvent as jest.Mock;
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
const mockLogEvent = logEvent as Mock;
|
||||
const mockUseLocation = useLocation as Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as Mock;
|
||||
|
||||
const defaultProps = {
|
||||
enableAnnouncements: true,
|
||||
@@ -62,7 +86,7 @@ const mockLocation = {
|
||||
|
||||
describe('HeaderRightSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
// Default to licensed user (Enterprise or Cloud)
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
@@ -177,7 +201,9 @@ describe('HeaderRightSection', () => {
|
||||
// Close feedback modal
|
||||
const closeFeedbackButton = screen.getByText('Close Feedback');
|
||||
await user.click(closeFeedbackButton);
|
||||
expect(screen.queryByTestId('feedback-modal')).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('feedback-modal')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should close other modals when opening feedback modal', async () => {
|
||||
@@ -197,7 +223,9 @@ describe('HeaderRightSection', () => {
|
||||
|
||||
await user.click(feedbackButton!);
|
||||
expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('share-modal')).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('share-modal')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show feedback button for Cloud users when feedback is enabled', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Mock dependencies before imports
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { describe, expect, it, beforeEach, vi } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
@@ -12,35 +12,39 @@ import GetMinMax from 'lib/getMinMax';
|
||||
|
||||
import ShareURLModal from '../ShareURLModal';
|
||||
|
||||
jest.mock('api/common/logEvent', () => ({
|
||||
const hoistedReduxMocks = vi.hoisted(() => ({
|
||||
useSelectorMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('api/common/logEvent', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
default: vi.fn(),
|
||||
}));
|
||||
|
||||
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<typeof import('react-redux')>('react-redux')),
|
||||
useSelector: hoistedReduxMocks.useSelectorMock,
|
||||
}));
|
||||
|
||||
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
|
||||
@@ -53,29 +57,29 @@ Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const mockLogEvent = logEvent as jest.Mock;
|
||||
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 mockLogEvent = logEvent as Mock;
|
||||
const mockUseLocation = useLocation as Mock;
|
||||
const mockUseUrlQuery = useUrlQuery as Mock;
|
||||
const mockUseSelector = hoistedReduxMocks.useSelectorMock 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,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { ChangeEventHandler, ReactNode } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import inviteUsers from 'api/v1/invite/bulk/create';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
import { StatusCodes } from 'http-status-codes';
|
||||
@@ -12,38 +15,114 @@ const makeApiError = (message: string, code = StatusCodes.CONFLICT): APIError =>
|
||||
error: { code: 'already_exists', message, url: '', errors: [] },
|
||||
});
|
||||
|
||||
jest.mock('api/v1/invite/create');
|
||||
jest.mock('api/v1/invite/bulk/create');
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
type MockButtonProps = {
|
||||
children?: ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
'aria-label'?: string;
|
||||
};
|
||||
|
||||
type MockInputProps = {
|
||||
autoComplete?: string;
|
||||
className?: string;
|
||||
name?: string;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
type MockDialogProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
const showErrorModal = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('api/v1/invite/create');
|
||||
vi.mock('api/v1/invite/bulk/create');
|
||||
vi.mock('@signozhq/ui', async () => {
|
||||
const React = await vi.importActual<typeof import('react')>('react');
|
||||
|
||||
return {
|
||||
Button: ({
|
||||
children,
|
||||
disabled,
|
||||
onClick,
|
||||
type = 'button',
|
||||
'aria-label': ariaLabel,
|
||||
}: MockButtonProps): JSX.Element =>
|
||||
React.createElement(
|
||||
'button',
|
||||
{ 'aria-label': ariaLabel, disabled, onClick, type },
|
||||
children,
|
||||
),
|
||||
Callout: ({ title }: { title?: ReactNode }): JSX.Element =>
|
||||
React.createElement('div', null, title),
|
||||
DialogFooter: ({ children, className }: MockDialogProps): JSX.Element =>
|
||||
React.createElement('div', { className }, children),
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
className,
|
||||
open,
|
||||
}: MockDialogProps): JSX.Element | null =>
|
||||
open ? React.createElement('div', { className }, children) : null,
|
||||
Input: ({
|
||||
autoComplete,
|
||||
className,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
type,
|
||||
value,
|
||||
}: MockInputProps): JSX.Element =>
|
||||
React.createElement('input', {
|
||||
autoComplete,
|
||||
className,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
type,
|
||||
value,
|
||||
}),
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
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,
|
||||
})),
|
||||
}));
|
||||
|
||||
const mockSendInvite = jest.mocked(sendInvite);
|
||||
const mockInviteUsers = jest.mocked(inviteUsers);
|
||||
const mockSendInvite = vi.mocked(sendInvite);
|
||||
const mockInviteUsers = vi.mocked(inviteUsers);
|
||||
|
||||
const defaultProps = {
|
||||
open: true,
|
||||
onClose: jest.fn(),
|
||||
onComplete: jest.fn(),
|
||||
onClose: vi.fn(),
|
||||
onComplete: vi.fn(),
|
||||
};
|
||||
|
||||
describe('InviteMembersModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
showErrorModal.mockClear();
|
||||
mockSendInvite.mockResolvedValue({
|
||||
httpStatusCode: 200,
|
||||
@@ -138,7 +217,7 @@ describe('InviteMembersModal', () => {
|
||||
|
||||
it('uses sendInvite (single) when only one row is filled', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
|
||||
render(<InviteMembersModal {...defaultProps} onComplete={onComplete} />);
|
||||
|
||||
@@ -243,7 +322,7 @@ describe('InviteMembersModal', () => {
|
||||
|
||||
it('uses inviteUsers (bulk) when multiple rows are filled', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onComplete = jest.fn();
|
||||
const onComplete = vi.fn();
|
||||
|
||||
render(<InviteMembersModal {...defaultProps} onComplete={onComplete} />);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { ComponentType, Suspense } from 'react';
|
||||
import { ComponentType, lazy as reactLazy, Suspense } from 'react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
@@ -7,6 +8,16 @@ import {
|
||||
|
||||
import Loadable from './index';
|
||||
|
||||
vi.mock('react', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('react')>();
|
||||
const lazy = vi.fn(actual.lazy);
|
||||
|
||||
return {
|
||||
...actual,
|
||||
lazy,
|
||||
};
|
||||
});
|
||||
|
||||
// Sample component to be loaded lazily
|
||||
function SampleComponent(): JSX.Element {
|
||||
return <div>Sample Component</div>;
|
||||
@@ -22,6 +33,10 @@ const loadSampleComponent = (): Promise<{
|
||||
});
|
||||
|
||||
describe('Loadable', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render the lazily loaded component', async () => {
|
||||
const LoadableSampleComponent = Loadable(loadSampleComponent);
|
||||
|
||||
@@ -38,12 +53,9 @@ describe('Loadable', () => {
|
||||
});
|
||||
|
||||
it('should call lazy with the provided import path', () => {
|
||||
const reactLazySpy = jest.spyOn(React, 'lazy');
|
||||
Loadable(loadSampleComponent);
|
||||
|
||||
expect(reactLazySpy).toHaveBeenCalledTimes(1);
|
||||
expect(reactLazySpy).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
reactLazySpy.mockRestore();
|
||||
expect(vi.mocked(reactLazy)).toHaveBeenCalledTimes(1);
|
||||
expect(vi.mocked(reactLazy)).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,6 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ILogBody } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
@@ -217,17 +216,20 @@ function LogDetailInner({
|
||||
|
||||
const logBody = useMemo(() => {
|
||||
if (!isBodyJsonQueryEnabled) {
|
||||
return (log?.body as string) ?? '';
|
||||
return log?.body || '';
|
||||
}
|
||||
// Feature enabled: body is always a map; message is always a string
|
||||
const bodyObj = log?.body as ILogBody;
|
||||
if (!bodyObj) {
|
||||
return '';
|
||||
|
||||
try {
|
||||
const json = JSON.parse(log?.body || '');
|
||||
|
||||
if (typeof json?.message === 'string' && json.message !== '') {
|
||||
return json.message;
|
||||
}
|
||||
|
||||
return log?.body || '';
|
||||
} catch (error) {
|
||||
return log?.body || '';
|
||||
}
|
||||
if (bodyObj.message) {
|
||||
return bodyObj.message;
|
||||
}
|
||||
return JSON.stringify(bodyObj);
|
||||
}, [isBodyJsonQueryEnabled, log?.body]);
|
||||
|
||||
const htmlBody = useMemo(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import LogStateIndicator from './LogStateIndicator';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
import { getLogIndicatorType, getLogIndicatorTypeForTable } from './utils';
|
||||
|
||||
@@ -9,10 +9,7 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Tooltip } from 'antd';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import {
|
||||
getBodyDisplayString,
|
||||
getSanitizedLogBody,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
// hooks
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
@@ -102,7 +99,7 @@ function RawLogView({
|
||||
// Check if body is selected
|
||||
const showBody = selectedFields.some((field) => field.name === 'body');
|
||||
if (showBody) {
|
||||
parts.push(`${attributesText} ${getBodyDisplayString(data.body)}`);
|
||||
parts.push(`${attributesText} ${data.body}`);
|
||||
} else {
|
||||
parts.push(attributesText);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@ import type { ReactElement } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import {
|
||||
getBodyDisplayString,
|
||||
getSanitizedLogBody,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
@@ -90,7 +87,7 @@ export function useLogsTableColumns({
|
||||
? {
|
||||
id: 'body',
|
||||
header: 'Body',
|
||||
accessorFn: (log): string => getBodyDisplayString(log.body),
|
||||
accessorFn: (log): string => log.body,
|
||||
canBeHidden: false,
|
||||
width: { default: '100%', min: 300 },
|
||||
cell: ({ value, isActive }): ReactElement => (
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { fireEvent, render, waitFor } from 'tests/test-utils';
|
||||
|
||||
import LogsFormatOptionsMenu from '../LogsFormatOptionsMenu';
|
||||
|
||||
const mockUpdateFormatting = jest.fn();
|
||||
const mockUpdateFormatting = vi.hoisted(() => vi.fn());
|
||||
|
||||
jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
vi.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
usePreferenceSync: (): any => ({
|
||||
preferences: {
|
||||
columns: [],
|
||||
@@ -18,11 +20,17 @@ jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
updateColumns: jest.fn(),
|
||||
updateColumns: vi.fn(),
|
||||
updateFormatting: mockUpdateFormatting,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
beforeEach(() => {
|
||||
mockUpdateFormatting.mockClear();
|
||||
@@ -31,9 +39,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: ReturnType<typeof vi.fn>;
|
||||
maxLinesOnChange: ReturnType<typeof vi.fn>;
|
||||
fontSizeOnChange: ReturnType<typeof vi.fn>;
|
||||
} {
|
||||
const items = [
|
||||
{ key: 'raw', label: 'Raw', data: { title: 'max lines per row' } },
|
||||
@@ -41,9 +49,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,11 +65,11 @@ describe('LogsFormatOptionsMenu (unit)', () => {
|
||||
isFetching: false,
|
||||
value: [],
|
||||
options: [],
|
||||
onFocus: jest.fn(),
|
||||
onBlur: jest.fn(),
|
||||
onSearch: jest.fn(),
|
||||
onSelect: jest.fn(),
|
||||
onRemove: jest.fn(),
|
||||
onFocus: vi.fn(),
|
||||
onBlur: vi.fn(),
|
||||
onSearch: vi.fn(),
|
||||
onSelect: vi.fn(),
|
||||
onRemove: vi.fn(),
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
import { MemberStatus } from 'container/MembersSettings/utils';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
|
||||
import MembersTable, { MemberRow } from '../MembersTable';
|
||||
|
||||
vi.mock('@signozhq/ui', async () => {
|
||||
const React = await vi.importActual<typeof import('react')>('react');
|
||||
|
||||
return {
|
||||
Badge: ({ children }: { children?: ReactNode }): JSX.Element =>
|
||||
React.createElement('span', null, children),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockActiveMembers: MemberRow[] = [
|
||||
{
|
||||
id: 'user-1',
|
||||
@@ -34,13 +53,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 +84,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 +104,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,3 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import MessageTip from './index';
|
||||
|
||||
@@ -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,11 +1,14 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
import type { CustomMultiSelectProps } from '../types';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
||||
|
||||
// Helper function to render with VirtuosoMockContext
|
||||
const renderWithVirtuoso = (
|
||||
@@ -17,10 +20,18 @@ const renderWithVirtuoso = (
|
||||
</VirtuosoMockContext.Provider>,
|
||||
);
|
||||
|
||||
const expectDropdownToBeClosingOrHidden = (dropdown: Element | null): void => {
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(dropdown?.className).toMatch(
|
||||
/ant-select-dropdown-hidden|ant-slide-up-leave/,
|
||||
);
|
||||
};
|
||||
|
||||
// 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 +62,18 @@ const mockGroupedOptions = [
|
||||
|
||||
describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
let mockOnChange: jest.Mock;
|
||||
let mockOnChange: MockedFunction<
|
||||
NonNullable<CustomMultiSelectProps['onChange']>
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
mockOnChange = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
mockOnChange = vi.fn();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
// ===== 1. CUSTOM VALUES SUPPORT =====
|
||||
@@ -805,7 +822,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
|
||||
@@ -832,8 +849,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
await waitFor(() => {
|
||||
// Dropdown should be hidden (not completely removed from DOM)
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expect(dropdown).toHaveStyle('pointer-events: none');
|
||||
expectDropdownToBeClosingOrHidden(dropdown);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -924,7 +940,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
// Dropdown should close and search text should be cleared
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expectDropdownToBeClosingOrHidden(dropdown);
|
||||
expect(searchInput).toHaveValue('');
|
||||
});
|
||||
});
|
||||
@@ -1157,7 +1173,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
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');
|
||||
expectDropdownToBeClosingOrHidden(dropdown);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1268,7 +1284,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);
|
||||
@@ -1491,7 +1507,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expectDropdownToBeClosingOrHidden(dropdown);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import {
|
||||
fireEvent,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
||||
|
||||
// Helper function to render with VirtuosoMockContext
|
||||
const renderWithVirtuoso = (component: React.ReactElement): RenderResult =>
|
||||
@@ -34,11 +35,11 @@ const RETRY_BUTTON_SELECTOR = '.navigation-icons .anticon-reload';
|
||||
|
||||
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 +67,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 +94,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(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import {
|
||||
fireEvent,
|
||||
@@ -11,7 +12,7 @@ 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();
|
||||
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
||||
|
||||
// Helper function to render with VirtuosoMockContext
|
||||
const renderWithVirtuoso = (component: React.ReactElement): RenderResult =>
|
||||
@@ -49,7 +50,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 +65,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 +84,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 +113,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 +157,7 @@ describe('CustomMultiSelect Component', () => {
|
||||
});
|
||||
|
||||
it('removes a tag when clicked', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const handleChange = vi.fn();
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CustomSelect from '../CustomSelect';
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
window.HTMLElement.prototype.scrollIntoView = vi.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()),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,12 +43,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 =====
|
||||
@@ -679,8 +682,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// Dropdown should close
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expect(combobox).toHaveAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -831,7 +833,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);
|
||||
@@ -1076,8 +1078,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// Dropdown should close after selection in single select
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expect(combobox).toHaveAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
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();
|
||||
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
||||
|
||||
// Mock options data
|
||||
const mockOptions = [
|
||||
@@ -31,7 +32,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 +47,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 +63,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 +115,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 +169,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 +186,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,3 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
@@ -10,9 +11,9 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import VariableItem from '../../../container/DashboardContainer/DashboardVariablesSelection/VariableItem';
|
||||
|
||||
// Mock the dashboard variables query
|
||||
jest.mock('api/dashboard/variables/dashboardVariablesQuery', () => ({
|
||||
vi.mock('api/dashboard/variables/dashboardVariablesQuery', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() =>
|
||||
default: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
payload: {
|
||||
variableValues: ['option1', 'option2', 'option3', 'option4'],
|
||||
@@ -22,7 +23,7 @@ jest.mock('api/dashboard/variables/dashboardVariablesQuery', () => ({
|
||||
}));
|
||||
|
||||
// Mock scrollIntoView which isn't available in JSDOM
|
||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
||||
|
||||
// Constants
|
||||
const TEST_VARIABLE_NAME = 'test_variable';
|
||||
@@ -76,12 +77,12 @@ function TestWrapper({ children }: { children: React.ReactNode }): JSX.Element {
|
||||
|
||||
describe('VariableItem Integration Tests', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
let mockOnValueUpdate: jest.Mock;
|
||||
let mockOnValueUpdate: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
mockOnValueUpdate = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
mockOnValueUpdate = vi.fn();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
// ===== 1. INTEGRATION WITH CUSTOMSELECT =====
|
||||
@@ -408,8 +409,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// Dropdown should close and search text should be cleared
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expect(combobox).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(searchInput).toHaveValue('');
|
||||
});
|
||||
});
|
||||
@@ -577,8 +577,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
await user.keyboard('{Escape}');
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = document.querySelector('.ant-select-dropdown');
|
||||
expect(dropdown).toHaveClass('ant-select-dropdown-hidden');
|
||||
expect(combobox).toHaveAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import store from 'store';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render } from 'tests/test-utils';
|
||||
|
||||
import NotFound from './index';
|
||||
|
||||
describe('Not Found page test', () => {
|
||||
it('should render Not Found page without errors', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<NotFound />
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
const { asFragment } = render(<NotFound />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,125 +1,31 @@
|
||||
// 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"
|
||||
src="test-file-stub"
|
||||
src="/src/assets/Images/notFound404.png"
|
||||
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"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { render, screen, userEvent, waitFor, within } from 'tests/test-utils';
|
||||
|
||||
import OverflowInputToolTip from './OverflowInputToolTip';
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const TOOLTIP_INNER_SELECTOR = '.ant-tooltip-inner';
|
||||
// Utility to mock overflow behaviour on inputs / elements.
|
||||
// Stubs HTMLElement.prototype.clientWidth, scrollWidth and offsetWidth used by component.
|
||||
@@ -41,7 +49,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 () => {
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import {
|
||||
combineInitialAndUserExpression,
|
||||
getUserExpressionFromCombined,
|
||||
} from '../utils';
|
||||
import { QuerySearchV2Context } from './context';
|
||||
import type { QuerySearchV2ContextValue } from './QuerySearchV2.store';
|
||||
import { createExpressionStore } from './QuerySearchV2.store';
|
||||
|
||||
export interface QuerySearchV2ProviderProps {
|
||||
queryParamKey: string;
|
||||
initialExpression?: string;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
persistOnUnmount?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider component that creates a scoped zustand store and exposes
|
||||
* expression state to children via context.
|
||||
*/
|
||||
export function QuerySearchV2Provider({
|
||||
initialExpression = '',
|
||||
persistOnUnmount = false,
|
||||
queryParamKey,
|
||||
children,
|
||||
}: QuerySearchV2ProviderProps): JSX.Element {
|
||||
const storeRef = useRef(createExpressionStore());
|
||||
const store = storeRef.current;
|
||||
|
||||
const [urlExpression, setUrlExpression] = useQueryState(
|
||||
queryParamKey,
|
||||
parseAsString,
|
||||
);
|
||||
|
||||
const committedExpression = useStore(store, (s) => s.committedExpression);
|
||||
const setInputExpression = useStore(store, (s) => s.setInputExpression);
|
||||
const commitExpression = useStore(store, (s) => s.commitExpression);
|
||||
const initializeFromUrl = useStore(store, (s) => s.initializeFromUrl);
|
||||
const resetExpression = useStore(store, (s) => s.resetExpression);
|
||||
|
||||
const isInitialized = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!isInitialized.current && urlExpression) {
|
||||
const cleanedExpression = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
urlExpression,
|
||||
);
|
||||
initializeFromUrl(cleanedExpression);
|
||||
isInitialized.current = true;
|
||||
}
|
||||
}, [urlExpression, initialExpression, initializeFromUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized.current || !urlExpression) {
|
||||
setUrlExpression(committedExpression || null);
|
||||
}
|
||||
}, [committedExpression, setUrlExpression, urlExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
if (!persistOnUnmount) {
|
||||
setUrlExpression(null);
|
||||
resetExpression();
|
||||
}
|
||||
};
|
||||
}, [persistOnUnmount, setUrlExpression, resetExpression]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(expression: string): void => {
|
||||
const userOnly = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
expression,
|
||||
);
|
||||
setInputExpression(userOnly);
|
||||
},
|
||||
[initialExpression, setInputExpression],
|
||||
);
|
||||
|
||||
const handleRun = useCallback(
|
||||
(expression: string): void => {
|
||||
const userOnly = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
expression,
|
||||
);
|
||||
commitExpression(userOnly);
|
||||
},
|
||||
[initialExpression, commitExpression],
|
||||
);
|
||||
|
||||
const combinedExpression = useMemo(
|
||||
() => combineInitialAndUserExpression(initialExpression, committedExpression),
|
||||
[initialExpression, committedExpression],
|
||||
);
|
||||
|
||||
const contextValue = useMemo<QuerySearchV2ContextValue>(
|
||||
() => ({
|
||||
expression: combinedExpression,
|
||||
userExpression: committedExpression,
|
||||
initialExpression,
|
||||
querySearchProps: {
|
||||
initialExpression: initialExpression.trim() ? initialExpression : undefined,
|
||||
onChange: handleChange,
|
||||
onRun: handleRun,
|
||||
},
|
||||
}),
|
||||
[
|
||||
combinedExpression,
|
||||
committedExpression,
|
||||
initialExpression,
|
||||
handleChange,
|
||||
handleRun,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<QuerySearchV2Context.Provider value={contextValue}>
|
||||
{children}
|
||||
</QuerySearchV2Context.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { createStore, StoreApi } from 'zustand';
|
||||
|
||||
export type QuerySearchV2Store = {
|
||||
/**
|
||||
* User-typed expression (local state, updates on typing)
|
||||
*/
|
||||
inputExpression: string;
|
||||
/**
|
||||
* Committed expression (synced to URL, updates on submit)
|
||||
*/
|
||||
committedExpression: string;
|
||||
setInputExpression: (expression: string) => void;
|
||||
commitExpression: (expression: string) => void;
|
||||
resetExpression: () => void;
|
||||
initializeFromUrl: (urlExpression: string) => void;
|
||||
};
|
||||
|
||||
export interface QuerySearchProps {
|
||||
initialExpression: string | undefined;
|
||||
onChange: (expression: string) => void;
|
||||
onRun: (expression: string) => void;
|
||||
}
|
||||
|
||||
export interface QuerySearchV2ContextValue {
|
||||
/**
|
||||
* Combined expression: "initialExpression AND (userExpression)"
|
||||
*/
|
||||
expression: string;
|
||||
userExpression: string;
|
||||
initialExpression: string;
|
||||
querySearchProps: QuerySearchProps;
|
||||
}
|
||||
|
||||
export function createExpressionStore(): StoreApi<QuerySearchV2Store> {
|
||||
return createStore<QuerySearchV2Store>((set) => ({
|
||||
inputExpression: '',
|
||||
committedExpression: '',
|
||||
setInputExpression: (expression: string): void => {
|
||||
set({ inputExpression: expression });
|
||||
},
|
||||
commitExpression: (expression: string): void => {
|
||||
set({
|
||||
inputExpression: expression,
|
||||
committedExpression: expression,
|
||||
});
|
||||
},
|
||||
resetExpression: (): void => {
|
||||
set({
|
||||
inputExpression: '',
|
||||
committedExpression: '',
|
||||
});
|
||||
},
|
||||
initializeFromUrl: (urlExpression: string): void => {
|
||||
set({
|
||||
inputExpression: urlExpression,
|
||||
committedExpression: urlExpression,
|
||||
});
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { useQuerySearchV2Context } from '../context';
|
||||
import {
|
||||
QuerySearchV2Provider,
|
||||
QuerySearchV2ProviderProps,
|
||||
} from '../QuerySearchV2.provider';
|
||||
|
||||
const mockSetQueryState = jest.fn();
|
||||
let mockUrlValue: string | null = null;
|
||||
|
||||
jest.mock('nuqs', () => ({
|
||||
parseAsString: {},
|
||||
useQueryState: jest.fn(() => [mockUrlValue, mockSetQueryState]),
|
||||
}));
|
||||
|
||||
function createWrapper(
|
||||
props: Partial<QuerySearchV2ProviderProps> = {},
|
||||
): ({ children }: { children: ReactNode }) => JSX.Element {
|
||||
return function Wrapper({ children }: { children: ReactNode }): JSX.Element {
|
||||
return (
|
||||
<QuerySearchV2Provider queryParamKey="testExpression" {...props}>
|
||||
{children}
|
||||
</QuerySearchV2Provider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
describe('QuerySearchExpressionProvider', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUrlValue = null;
|
||||
});
|
||||
|
||||
it('should provide initial context values', () => {
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(result.current.expression).toBe('');
|
||||
expect(result.current.userExpression).toBe('');
|
||||
expect(result.current.initialExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should combine initialExpression with userExpression', () => {
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper({ initialExpression: 'k8s.pod.name = "my-pod"' }),
|
||||
});
|
||||
|
||||
expect(result.current.expression).toBe('k8s.pod.name = "my-pod"');
|
||||
expect(result.current.initialExpression).toBe('k8s.pod.name = "my-pod"');
|
||||
|
||||
act(() => {
|
||||
result.current.querySearchProps.onChange('service = "api"');
|
||||
});
|
||||
act(() => {
|
||||
result.current.querySearchProps.onRun('service = "api"');
|
||||
});
|
||||
|
||||
expect(result.current.expression).toBe(
|
||||
'k8s.pod.name = "my-pod" AND (service = "api")',
|
||||
);
|
||||
expect(result.current.userExpression).toBe('service = "api"');
|
||||
});
|
||||
|
||||
it('should provide querySearchProps with correct callbacks', () => {
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper({ initialExpression: 'initial' }),
|
||||
});
|
||||
|
||||
expect(result.current.querySearchProps.initialExpression).toBe('initial');
|
||||
expect(typeof result.current.querySearchProps.onChange).toBe('function');
|
||||
expect(typeof result.current.querySearchProps.onRun).toBe('function');
|
||||
});
|
||||
|
||||
it('should initialize from URL value on mount', () => {
|
||||
mockUrlValue = 'status = 500';
|
||||
|
||||
const { result } = renderHook(() => useQuerySearchV2Context(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(result.current.userExpression).toBe('status = 500');
|
||||
expect(result.current.expression).toBe('status = 500');
|
||||
});
|
||||
|
||||
it('should throw error when used outside provider', () => {
|
||||
expect(() => {
|
||||
renderHook(() => useQuerySearchV2Context());
|
||||
}).toThrow(
|
||||
'useQuerySearchV2Context must be used within a QuerySearchV2Provider',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { createExpressionStore } from '../QuerySearchV2.store';
|
||||
|
||||
describe('createExpressionStore', () => {
|
||||
it('should create a store with initial state', () => {
|
||||
const store = createExpressionStore();
|
||||
const state = store.getState();
|
||||
|
||||
expect(state.inputExpression).toBe('');
|
||||
expect(state.committedExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should update inputExpression via setInputExpression', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().setInputExpression('service.name = "api"');
|
||||
|
||||
expect(store.getState().inputExpression).toBe('service.name = "api"');
|
||||
expect(store.getState().committedExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should update both expressions via commitExpression', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().setInputExpression('service.name = "api"');
|
||||
store.getState().commitExpression('service.name = "api"');
|
||||
|
||||
expect(store.getState().inputExpression).toBe('service.name = "api"');
|
||||
expect(store.getState().committedExpression).toBe('service.name = "api"');
|
||||
});
|
||||
|
||||
it('should reset all state via resetExpression', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().setInputExpression('service.name = "api"');
|
||||
store.getState().commitExpression('service.name = "api"');
|
||||
store.getState().resetExpression();
|
||||
|
||||
expect(store.getState().inputExpression).toBe('');
|
||||
expect(store.getState().committedExpression).toBe('');
|
||||
});
|
||||
|
||||
it('should initialize from URL value', () => {
|
||||
const store = createExpressionStore();
|
||||
|
||||
store.getState().initializeFromUrl('status = 500');
|
||||
|
||||
expect(store.getState().inputExpression).toBe('status = 500');
|
||||
expect(store.getState().committedExpression).toBe('status = 500');
|
||||
});
|
||||
|
||||
it('should create isolated store instances', () => {
|
||||
const store1 = createExpressionStore();
|
||||
const store2 = createExpressionStore();
|
||||
|
||||
store1.getState().setInputExpression('expr1');
|
||||
store2.getState().setInputExpression('expr2');
|
||||
|
||||
expect(store1.getState().inputExpression).toBe('expr1');
|
||||
expect(store2.getState().inputExpression).toBe('expr2');
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports -- React Context required for scoped store pattern
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import type { QuerySearchV2ContextValue } from './QuerySearchV2.store';
|
||||
|
||||
export const QuerySearchV2Context =
|
||||
createContext<QuerySearchV2ContextValue | null>(null);
|
||||
|
||||
export function useQuerySearchV2Context(): QuerySearchV2ContextValue {
|
||||
const context = useContext(QuerySearchV2Context);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useQuerySearchV2Context must be used within a QuerySearchV2Provider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export { useQuerySearchV2Context } from './context';
|
||||
export type { QuerySearchV2ProviderProps } from './QuerySearchV2.provider';
|
||||
export { QuerySearchV2Provider } from './QuerySearchV2.provider';
|
||||
export type {
|
||||
QuerySearchProps,
|
||||
QuerySearchV2ContextValue,
|
||||
QuerySearchV2Store,
|
||||
} from './QuerySearchV2.store';
|
||||
@@ -19,13 +19,6 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.query-search-initial-scope-label {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.query-where-clause-editor {
|
||||
flex: 1;
|
||||
min-width: 400px;
|
||||
@@ -60,10 +53,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.hasInitialExpression .cm-editor .cm-content {
|
||||
padding-left: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
@@ -79,6 +68,7 @@
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
padding: 0px !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border);
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useDashboardVariablesByType } from 'hooks/dashboard/useDashboardVariabl
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { debounce, isNull } from 'lodash-es';
|
||||
import { Filter, Info, TriangleAlert } from 'lucide-react';
|
||||
import { Info, TriangleAlert } from 'lucide-react';
|
||||
import {
|
||||
IDetailedError,
|
||||
IQueryContext,
|
||||
@@ -47,7 +47,6 @@ import { validateQuery } from 'utils/queryValidationUtils';
|
||||
import { unquote } from 'utils/stringUtils';
|
||||
|
||||
import { queryExamples } from './constants';
|
||||
import { combineInitialAndUserExpression } from './utils';
|
||||
|
||||
import './QuerySearch.styles.scss';
|
||||
|
||||
@@ -86,8 +85,6 @@ interface QuerySearchProps {
|
||||
hardcodedAttributeKeys?: QueryKeyDataSuggestionsProps[];
|
||||
onRun?: (query: string) => void;
|
||||
showFilterSuggestionsWithoutMetric?: boolean;
|
||||
/** When set, the editor shows only the user expression; API/filter uses `initial AND (user)`. */
|
||||
initialExpression?: string;
|
||||
}
|
||||
|
||||
function QuerySearch({
|
||||
@@ -99,7 +96,6 @@ function QuerySearch({
|
||||
signalSource,
|
||||
hardcodedAttributeKeys,
|
||||
showFilterSuggestionsWithoutMetric,
|
||||
initialExpression,
|
||||
}: QuerySearchProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
|
||||
@@ -116,26 +112,18 @@ function QuerySearch({
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const editorRef = useRef<EditorView | null>(null);
|
||||
|
||||
const isScopedFilter = initialExpression !== undefined;
|
||||
|
||||
const validateExpressionForEditor = useCallback(
|
||||
(editorDoc: string): void => {
|
||||
const toValidate = isScopedFilter
|
||||
? combineInitialAndUserExpression(initialExpression ?? '', editorDoc)
|
||||
: editorDoc;
|
||||
try {
|
||||
const validationResponse = validateQuery(toValidate);
|
||||
setValidation(validationResponse);
|
||||
} catch (error) {
|
||||
setValidation({
|
||||
isValid: false,
|
||||
message: 'Failed to process query',
|
||||
errors: [error as IDetailedError],
|
||||
});
|
||||
}
|
||||
},
|
||||
[initialExpression, isScopedFilter],
|
||||
);
|
||||
const handleQueryValidation = useCallback((newExpression: string): void => {
|
||||
try {
|
||||
const validationResponse = validateQuery(newExpression);
|
||||
setValidation(validationResponse);
|
||||
} catch (error) {
|
||||
setValidation({
|
||||
isValid: false,
|
||||
message: 'Failed to process query',
|
||||
errors: [error as IDetailedError],
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getCurrentExpression = useCallback(
|
||||
(): string => editorRef.current?.state.doc.toString() || '',
|
||||
@@ -177,8 +165,6 @@ function QuerySearch({
|
||||
setIsEditorReady(true);
|
||||
}, []);
|
||||
|
||||
const prevQueryDataExpressionRef = useRef<string | undefined>();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!isEditorReady) {
|
||||
@@ -187,22 +173,13 @@ function QuerySearch({
|
||||
|
||||
const newExpression = queryData.filter?.expression || '';
|
||||
const currentExpression = getCurrentExpression();
|
||||
const prevExpression = prevQueryDataExpressionRef.current;
|
||||
|
||||
// Only sync editor when queryData.filter?.expression actually changed from external source
|
||||
// Not when focus changed (which would reset uncommitted user input)
|
||||
const queryDataExpressionChanged = prevExpression !== newExpression;
|
||||
prevQueryDataExpressionRef.current = newExpression;
|
||||
|
||||
if (
|
||||
queryDataExpressionChanged &&
|
||||
newExpression !== currentExpression &&
|
||||
!isFocused
|
||||
) {
|
||||
// Do not update codemirror editor if the expression is the same
|
||||
if (newExpression !== currentExpression && !isFocused) {
|
||||
updateEditorValue(newExpression, { skipOnChange: true });
|
||||
}
|
||||
if (!isFocused) {
|
||||
validateExpressionForEditor(currentExpression);
|
||||
if (newExpression) {
|
||||
handleQueryValidation(newExpression);
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -307,7 +284,7 @@ function QuerySearch({
|
||||
}
|
||||
});
|
||||
}
|
||||
setKeySuggestions([...merged.values()]);
|
||||
setKeySuggestions(Array.from(merged.values()));
|
||||
|
||||
// Force reopen the completion if editor is available and focused
|
||||
if (editorRef.current) {
|
||||
@@ -360,7 +337,7 @@ function QuerySearch({
|
||||
// If value contains single quotes, escape them and wrap in single quotes
|
||||
if (value.includes("'")) {
|
||||
// Replace single quotes with escaped single quotes
|
||||
const escapedValue = value.replaceAll(/'/g, "\\'");
|
||||
const escapedValue = value.replace(/'/g, "\\'");
|
||||
return `'${escapedValue}'`;
|
||||
}
|
||||
|
||||
@@ -637,7 +614,7 @@ function QuerySearch({
|
||||
|
||||
const handleBlur = (): void => {
|
||||
const currentExpression = getCurrentExpression();
|
||||
validateExpressionForEditor(currentExpression);
|
||||
handleQueryValidation(currentExpression);
|
||||
setIsFocused(false);
|
||||
};
|
||||
|
||||
@@ -655,6 +632,7 @@ function QuerySearch({
|
||||
);
|
||||
|
||||
const handleExampleClick = (exampleQuery: string): void => {
|
||||
// If there's an existing query, append the example with AND
|
||||
const currentExpression = getCurrentExpression();
|
||||
const newExpression = currentExpression
|
||||
? `${currentExpression} AND ${exampleQuery}`
|
||||
@@ -919,12 +897,12 @@ function QuerySearch({
|
||||
|
||||
// If we have previous pairs, we can prioritize keys that haven't been used yet
|
||||
if (queryContext.queryPairs && queryContext.queryPairs.length > 0) {
|
||||
const usedKeys = new Set(queryContext.queryPairs.map((pair) => pair.key));
|
||||
const usedKeys = queryContext.queryPairs.map((pair) => pair.key);
|
||||
|
||||
// Add boost to unused keys to prioritize them
|
||||
options = options.map((option) => ({
|
||||
...option,
|
||||
boost: usedKeys.has(option.label) ? -10 : 10,
|
||||
boost: usedKeys.includes(option.label) ? -10 : 10,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1339,19 +1317,6 @@ function QuerySearch({
|
||||
)}
|
||||
|
||||
<div className="query-where-clause-editor-container">
|
||||
{isScopedFilter ? (
|
||||
<Tooltip title={initialExpression || ''} placement="left">
|
||||
<div className="query-search-initial-scope-label">
|
||||
<Filter
|
||||
size={14}
|
||||
style={{
|
||||
opacity: 0.9,
|
||||
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip
|
||||
title={<div data-log-detail-ignore="true">{getTooltipContent()}</div>}
|
||||
placement="left"
|
||||
@@ -1391,7 +1356,6 @@ function QuerySearch({
|
||||
className={cx('query-where-clause-editor', {
|
||||
isValid: validation.isValid === true,
|
||||
hasErrors: validation.errors.length > 0,
|
||||
hasInitialExpression: isScopedFilter,
|
||||
})}
|
||||
extensions={[
|
||||
autocompletion({
|
||||
@@ -1426,12 +1390,7 @@ function QuerySearch({
|
||||
// Mod-Enter is usually Ctrl-Enter or Cmd-Enter based on OS
|
||||
run: (): boolean => {
|
||||
if (onRun && typeof onRun === 'function') {
|
||||
const user = getCurrentExpression();
|
||||
onRun(
|
||||
isScopedFilter
|
||||
? combineInitialAndUserExpression(initialExpression ?? '', user)
|
||||
: user,
|
||||
);
|
||||
onRun(getCurrentExpression());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@@ -1596,7 +1555,6 @@ QuerySearch.defaultProps = {
|
||||
placeholder:
|
||||
"Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')",
|
||||
showFilterSuggestionsWithoutMetric: false,
|
||||
initialExpression: undefined,
|
||||
};
|
||||
|
||||
export default QuerySearch;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import {
|
||||
combineInitialAndUserExpression,
|
||||
getUserExpressionFromCombined,
|
||||
} from '../utils';
|
||||
|
||||
describe('entityLogsExpression', () => {
|
||||
describe('combineInitialAndUserExpression', () => {
|
||||
it('returns user when initial is empty', () => {
|
||||
expect(combineInitialAndUserExpression('', 'body contains error')).toBe(
|
||||
'body contains error',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns initial when user is empty', () => {
|
||||
expect(combineInitialAndUserExpression('k8s.pod.name = "x"', '')).toBe(
|
||||
'k8s.pod.name = "x"',
|
||||
);
|
||||
});
|
||||
|
||||
it('wraps user in parentheses with AND', () => {
|
||||
expect(
|
||||
combineInitialAndUserExpression('k8s.pod.name = "x"', 'body = "a"'),
|
||||
).toBe('k8s.pod.name = "x" AND (body = "a")');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserExpressionFromCombined', () => {
|
||||
it('returns empty when combined equals initial', () => {
|
||||
expect(
|
||||
getUserExpressionFromCombined('k8s.pod.name = "x"', 'k8s.pod.name = "x"'),
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('extracts user from wrapped form', () => {
|
||||
expect(
|
||||
getUserExpressionFromCombined(
|
||||
'k8s.pod.name = "x"',
|
||||
'k8s.pod.name = "x" AND (body = "a")',
|
||||
),
|
||||
).toBe('body = "a"');
|
||||
});
|
||||
|
||||
it('extracts user from legacy AND without parens', () => {
|
||||
expect(
|
||||
getUserExpressionFromCombined(
|
||||
'k8s.pod.name = "x"',
|
||||
'k8s.pod.name = "x" AND body = "a"',
|
||||
),
|
||||
).toBe('body = "a"');
|
||||
});
|
||||
|
||||
it('returns full combined when initial is empty', () => {
|
||||
expect(getUserExpressionFromCombined('', 'service.name = "a"')).toBe(
|
||||
'service.name = "a"',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
export function combineInitialAndUserExpression(
|
||||
initial: string,
|
||||
user: string,
|
||||
): string {
|
||||
const i = initial.trim();
|
||||
const u = user.trim();
|
||||
if (!i) {
|
||||
return u;
|
||||
}
|
||||
if (!u) {
|
||||
return i;
|
||||
}
|
||||
return `${i} AND (${u})`;
|
||||
}
|
||||
|
||||
export function getUserExpressionFromCombined(
|
||||
initial: string,
|
||||
combined: string | null | undefined,
|
||||
): string {
|
||||
const i = initial.trim();
|
||||
const c = (combined ?? '').trim();
|
||||
if (!c) {
|
||||
return '';
|
||||
}
|
||||
if (!i) {
|
||||
return c;
|
||||
}
|
||||
if (c === i) {
|
||||
return '';
|
||||
}
|
||||
const wrappedPrefix = `${i} AND (`;
|
||||
if (c.startsWith(wrappedPrefix) && c.endsWith(')')) {
|
||||
return c.slice(wrappedPrefix.length, -1);
|
||||
}
|
||||
const plainPrefix = `${i} AND `;
|
||||
if (c.startsWith(plainPrefix)) {
|
||||
return c.slice(plainPrefix.length);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Token } from 'antlr4';
|
||||
import TraceOperatorGrammarLexer from 'parser/TraceOperatorParser/TraceOperatorGrammarLexer';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
createTraceOperatorContext,
|
||||
@@ -183,11 +184,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', () => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { getInvolvedQueriesInTraceOperator } from '../utils/utils';
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { EditorView } from '@uiw/react-codemirror';
|
||||
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
||||
import * as getKeySuggestionsModule from 'api/querySuggestions/getKeySuggestions';
|
||||
import * as getValueSuggestionsModule from 'api/querySuggestions/getValueSuggestion';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { fireEvent, render, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import type { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import type { MockedFunction, MockInstance } from 'vitest';
|
||||
import {
|
||||
beforeAll,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
|
||||
import QuerySearch from '../QuerySearch/QuerySearch';
|
||||
|
||||
@@ -13,7 +22,6 @@ const CM_EDITOR_SELECTOR = '.cm-editor .cm-content';
|
||||
|
||||
// Mock DOM APIs that CodeMirror needs
|
||||
beforeAll(() => {
|
||||
// Mock getClientRects and getBoundingClientRect for Range objects
|
||||
const mockRect: DOMRect = {
|
||||
width: 100,
|
||||
height: 20,
|
||||
@@ -26,7 +34,6 @@ beforeAll(() => {
|
||||
toJSON: (): DOMRect => mockRect,
|
||||
} as DOMRect;
|
||||
|
||||
// Create a minimal Range mock with only what CodeMirror actually uses
|
||||
const createMockRange = (): Range => {
|
||||
let startContainer: Node = document.createTextNode('');
|
||||
let endContainer: Node = document.createTextNode('');
|
||||
@@ -34,7 +41,6 @@ beforeAll(() => {
|
||||
let endOffset = 0;
|
||||
|
||||
const mockRange = {
|
||||
// CodeMirror uses these for text measurement
|
||||
getClientRects: (): DOMRectList =>
|
||||
({
|
||||
length: 1,
|
||||
@@ -45,7 +51,6 @@ beforeAll(() => {
|
||||
},
|
||||
}) as unknown as DOMRectList,
|
||||
getBoundingClientRect: (): DOMRect => mockRect,
|
||||
// CodeMirror calls these to set up text ranges
|
||||
setStart: (node: Node, offset: number): void => {
|
||||
startContainer = node;
|
||||
startOffset = offset;
|
||||
@@ -54,7 +59,6 @@ beforeAll(() => {
|
||||
endContainer = node;
|
||||
endOffset = offset;
|
||||
},
|
||||
// Minimal Range properties (TypeScript requires these)
|
||||
get startContainer(): Node {
|
||||
return startContainer;
|
||||
},
|
||||
@@ -75,25 +79,28 @@ beforeAll(() => {
|
||||
return mockRange as unknown as Range;
|
||||
};
|
||||
|
||||
// Mock document.createRange to return a new Range instance each time
|
||||
document.createRange = (): Range => createMockRange();
|
||||
|
||||
// Mock getBoundingClientRect for elements
|
||||
Element.prototype.getBoundingClientRect = (): DOMRect => mockRect;
|
||||
});
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
vi.mock('hooks/useDarkMode', () => ({
|
||||
useIsDarkMode: (): boolean => false,
|
||||
}));
|
||||
|
||||
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('providers/Dashboard/store/useDashboardStore', () => ({
|
||||
useDashboardStore: (): { dashboardData: undefined } => ({
|
||||
dashboardData: undefined,
|
||||
}),
|
||||
}));
|
||||
|
||||
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 }),
|
||||
@@ -101,92 +108,85 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('api/querySuggestions/getKeySuggestions', () => ({
|
||||
getKeySuggestions: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
data: { keys: {} as Record<string, QueryKeyDataSuggestionsProps[]> },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('api/querySuggestions/getValueSuggestion', () => ({
|
||||
getValueSuggestions: jest.fn().mockResolvedValue({
|
||||
data: { data: { values: { stringValues: [], numberValues: [] } } },
|
||||
}),
|
||||
}));
|
||||
|
||||
// Note: We're NOT mocking CodeMirror here - using the real component
|
||||
// This provides integration testing with the actual CodeMirror editor
|
||||
|
||||
const SAMPLE_KEY_TYPING = 'http.';
|
||||
const SAMPLE_VALUE_TYPING_INCOMPLETE = "service.name = '";
|
||||
const SAMPLE_STATUS_QUERY = "http.status_code = '200'";
|
||||
|
||||
describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
let getKeySuggestionsSpy: MockInstance;
|
||||
let getValueSuggestionsSpy: MockInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
getKeySuggestionsSpy = vi
|
||||
.spyOn(getKeySuggestionsModule, 'getKeySuggestions')
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
data: { keys: {} as Record<string, unknown[]> },
|
||||
},
|
||||
} as Awaited<ReturnType<typeof getKeySuggestionsModule.getKeySuggestions>>);
|
||||
getValueSuggestionsSpy = vi
|
||||
.spyOn(getValueSuggestionsModule, 'getValueSuggestions')
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
data: { values: { stringValues: [], numberValues: [] } },
|
||||
},
|
||||
} as unknown as Awaited<
|
||||
ReturnType<typeof getValueSuggestionsModule.getValueSuggestions>
|
||||
>);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getKeySuggestionsSpy.mockRestore();
|
||||
getValueSuggestionsSpy.mockRestore();
|
||||
});
|
||||
|
||||
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}
|
||||
/>,
|
||||
);
|
||||
|
||||
// CodeMirror renders a contenteditable div, so we check for the container
|
||||
const editorContainer = document.querySelector('.query-where-clause-editor');
|
||||
expect(editorContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fetches key suggestions when typing a key (debounced)', async () => {
|
||||
// Use real timers for CodeMirror integration tests
|
||||
const mockedGetKeys = getKeySuggestions as jest.MockedFunction<
|
||||
typeof getKeySuggestions
|
||||
>;
|
||||
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}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for CodeMirror to initialize
|
||||
await waitFor(() => {
|
||||
const editor = document.querySelector(CM_EDITOR_SELECTOR);
|
||||
expect(editor).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find the CodeMirror editor contenteditable element
|
||||
const editor = document.querySelector(CM_EDITOR_SELECTOR) as HTMLElement;
|
||||
|
||||
// Focus and type into the editor
|
||||
await userEvent.click(editor);
|
||||
await userEvent.type(editor, SAMPLE_KEY_TYPING);
|
||||
|
||||
// Wait for debounced API call (300ms debounce + some buffer)
|
||||
await waitFor(() => expect(mockedGetKeys).toHaveBeenCalled(), {
|
||||
await waitFor(() => expect(getKeySuggestionsSpy).toHaveBeenCalled(), {
|
||||
timeout: 2000,
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches value suggestions when editing value context', async () => {
|
||||
// Use real timers for CodeMirror integration tests
|
||||
const mockedGetValues = getValueSuggestions as jest.MockedFunction<
|
||||
typeof getValueSuggestions
|
||||
>;
|
||||
mockedGetValues.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}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for CodeMirror to initialize
|
||||
await waitFor(() => {
|
||||
const editor = document.querySelector(CM_EDITOR_SELECTOR);
|
||||
expect(editor).toBeInTheDocument();
|
||||
@@ -196,51 +196,42 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
await userEvent.click(editor);
|
||||
await userEvent.type(editor, SAMPLE_VALUE_TYPING_INCOMPLETE);
|
||||
|
||||
// Wait for debounced API call (300ms debounce + some buffer)
|
||||
await waitFor(() => expect(mockedGetValues).toHaveBeenCalled(), {
|
||||
await waitFor(() => expect(getValueSuggestionsSpy).toHaveBeenCalled(), {
|
||||
timeout: 2000,
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches key suggestions on mount for LOGS', async () => {
|
||||
// Use real timers for CodeMirror integration tests
|
||||
const mockedGetKeysOnMount = getKeySuggestions as jest.MockedFunction<
|
||||
typeof getKeySuggestions
|
||||
>;
|
||||
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}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for debounced API call (300ms debounce + some buffer)
|
||||
await waitFor(() => expect(mockedGetKeysOnMount).toHaveBeenCalled(), {
|
||||
await waitFor(() => expect(getKeySuggestionsSpy).toHaveBeenCalled(), {
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
const lastArgs = mockedGetKeysOnMount.mock.calls[
|
||||
mockedGetKeysOnMount.mock.calls.length - 1
|
||||
const lastArgs = getKeySuggestionsSpy.mock.calls[
|
||||
getKeySuggestionsSpy.mock.calls.length - 1
|
||||
]?.[0] as { signal: unknown; searchText: string };
|
||||
expect(lastArgs).toMatchObject({ signal: DataSource.LOGS, searchText: '' });
|
||||
});
|
||||
|
||||
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}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for CodeMirror to initialize
|
||||
await waitFor(() => {
|
||||
const editor = document.querySelector(CM_EDITOR_SELECTOR);
|
||||
expect(editor).toBeInTheDocument();
|
||||
@@ -250,7 +241,6 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
await userEvent.click(editor);
|
||||
await userEvent.type(editor, SAMPLE_STATUS_QUERY);
|
||||
|
||||
// Use fireEvent for keyboard shortcuts as userEvent might not work well with CodeMirror
|
||||
const modKey = navigator.platform.includes('Mac') ? 'metaKey' : 'ctrlKey';
|
||||
fireEvent.keyDown(editor, {
|
||||
key: 'Enter',
|
||||
@@ -274,21 +264,18 @@ 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}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for CodeMirror to initialize and the expression to be set
|
||||
await waitFor(
|
||||
() => {
|
||||
// CodeMirror stores content in .cm-content, check the text content
|
||||
const editorContent = document.querySelector(
|
||||
CM_EDITOR_SELECTOR,
|
||||
) as HTMLElement;
|
||||
expect(editorContent).toBeInTheDocument();
|
||||
// CodeMirror may render the text in multiple ways, check if it contains our expression
|
||||
const textContent = editorContent.textContent || '';
|
||||
expect(textContent).toContain('http.status_code');
|
||||
expect(textContent).toContain('service.name');
|
||||
@@ -298,13 +285,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],
|
||||
@@ -321,7 +306,6 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
// Wait for CodeMirror to initialize with the initial expression
|
||||
await waitFor(
|
||||
() => {
|
||||
const editorContent = document.querySelector(
|
||||
@@ -334,13 +318,6 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
// Ensure the editor is explicitly blurred (not focused)
|
||||
// Blur the actual CodeMirror editor container so that QuerySearch's onBlur handler runs.
|
||||
// Note: In jsdom + CodeMirror we can't reliably assert the DOM text content changes when
|
||||
// the expression is updated programmatically, but we can assert that:
|
||||
// 1) The component continues to render, and
|
||||
// 2) No onChange is fired for programmatic updates.
|
||||
|
||||
const updatedQueryData = {
|
||||
...initialQueryData,
|
||||
filter: {
|
||||
@@ -348,7 +325,6 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
},
|
||||
};
|
||||
|
||||
// Re-render with updated queryData.filter.expression
|
||||
rerender(
|
||||
<QuerySearch
|
||||
onChange={onChange}
|
||||
@@ -357,7 +333,6 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
// updateEditorValue should have resulted in a dispatch call + onChange should not have been called
|
||||
await waitFor(() => {
|
||||
expect(dispatchSpy).toHaveBeenCalled();
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
@@ -367,11 +342,6 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
});
|
||||
|
||||
it('fetches key suggestions for metrics even without aggregateAttribute.key when showFilterSuggestionsWithoutMetric is true', async () => {
|
||||
const mockedGetKeys = getKeySuggestions as jest.MockedFunction<
|
||||
typeof getKeySuggestions
|
||||
>;
|
||||
mockedGetKeys.mockClear();
|
||||
|
||||
const queryData = {
|
||||
...initialQueriesMap.metrics.builder.queryData[0],
|
||||
aggregateAttribute: {
|
||||
@@ -383,18 +353,15 @@ describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
render(
|
||||
<QuerySearch
|
||||
onChange={jest.fn()}
|
||||
onChange={vi.fn()}
|
||||
queryData={queryData}
|
||||
dataSource={DataSource.METRICS}
|
||||
showFilterSuggestionsWithoutMetric
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(mockedGetKeys).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
await waitFor(() => expect(getKeySuggestionsSpy).toHaveBeenCalled(), {
|
||||
timeout: 2000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { jest } from '@jest/globals';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
@@ -19,47 +20,45 @@ import {
|
||||
QueryFunctionsTypes,
|
||||
} from 'types/common/queryBuilder';
|
||||
|
||||
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>;
|
||||
},
|
||||
);
|
||||
// Mock hooks
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilderOperations');
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockedUseQueryBuilder = jest.mocked(useQueryBuilder);
|
||||
const mockedUseQueryOperations = jest.mocked(
|
||||
// Local mocks for domain-specific heavy child components
|
||||
vi.mock('../QueryAggregation/QueryAggregation', () => ({
|
||||
default: function QueryAggregation() {
|
||||
return <div>QueryAggregation</div>;
|
||||
},
|
||||
}));
|
||||
vi.mock('../MerticsAggregateSection/MetricsAggregateSection', () => ({
|
||||
default: function MetricsAggregateSection() {
|
||||
return <div>MetricsAggregateSection</div>;
|
||||
},
|
||||
}));
|
||||
// Mock hooks
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilderOperations');
|
||||
|
||||
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 +102,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 +142,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', () => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
fireEvent,
|
||||
@@ -12,10 +14,16 @@ 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 = vi.fn();
|
||||
const mockHandleSetQueryData = vi.fn();
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||
useQueryOperations: (): {
|
||||
handleChangeQueryData: typeof mockHandleChangeQueryData;
|
||||
} => ({
|
||||
@@ -23,7 +31,7 @@ jest.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
vi.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: (): {
|
||||
handleSetQueryData: typeof mockHandleSetQueryData;
|
||||
} => ({
|
||||
@@ -31,7 +39,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 +50,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 +61,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 +95,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,5 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
@@ -19,45 +20,44 @@ 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('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
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 +109,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 +216,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 +266,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();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -43,7 +45,7 @@ describe('previousQuery.utils', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('getQueryKey normalizes non-meter signal to empty string', () => {
|
||||
@@ -150,11 +152,9 @@ describe('previousQuery.utils', () => {
|
||||
});
|
||||
|
||||
it('write errors (e.g., quota) are caught and do not throw', () => {
|
||||
const spy = jest
|
||||
.spyOn(window.sessionStorage.__proto__, 'setItem')
|
||||
.mockImplementation(() => {
|
||||
throw new Error('quota exceeded');
|
||||
});
|
||||
const spy = vi.spyOn(sessionStorage, 'setItem').mockImplementation(() => {
|
||||
throw new Error('quota exceeded');
|
||||
});
|
||||
|
||||
const key = getQueryKey({
|
||||
queryName: 'A',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { negateOperator, OPERATORS } from 'constants/antlrQueryConstants';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
@@ -17,7 +19,7 @@ import {
|
||||
|
||||
describe('convertFiltersToExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should handle empty, null, and undefined inputs', () => {
|
||||
@@ -983,7 +985,7 @@ describe('convertAggregationToExpression', () => {
|
||||
|
||||
describe('removeKeysFromExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Backward compatibility (removeOnlyVariableExpressions = false)', () => {
|
||||
@@ -1203,7 +1205,7 @@ describe('removeKeysFromExpression', () => {
|
||||
|
||||
describe('formatValueForExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Variable values', () => {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export type {
|
||||
QuerySearchProps,
|
||||
QuerySearchV2ContextValue,
|
||||
QuerySearchV2ProviderProps,
|
||||
} from './QueryV2/QuerySearch/Provider';
|
||||
export {
|
||||
QuerySearchV2Provider,
|
||||
useQuerySearchV2Context,
|
||||
} from './QueryV2/QuerySearch/Provider';
|
||||
export { QueryBuilderV2 } from './QueryBuilderV2';
|
||||
export {
|
||||
QueryBuilderV2Provider,
|
||||
useQueryBuilderV2Context,
|
||||
} from './QueryBuilderV2Context';
|
||||
@@ -1,3 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { FiltersType, QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
@@ -14,19 +15,25 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import CheckboxFilter from './Checkbox';
|
||||
|
||||
vi.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: ReturnType<typeof vi.fn> } => ({
|
||||
safeNavigate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// 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 +97,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 +113,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 +130,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
refetch: vi.fn(),
|
||||
} as any);
|
||||
|
||||
// Setup MSW server for API calls
|
||||
@@ -205,7 +212,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 +253,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 +311,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 +396,7 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
redirectWithQueryBuilderData: vi.fn(),
|
||||
} as any);
|
||||
|
||||
const mockFilter = createMockFilter({ defaultOpen: false });
|
||||
@@ -414,7 +421,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,3 +1,15 @@
|
||||
import {
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import type { Mock, MockedFunction } from 'vitest';
|
||||
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import {
|
||||
ApiMonitoringParams,
|
||||
@@ -19,18 +31,18 @@ import QuickFilters from '../QuickFilters';
|
||||
import { IQuickFiltersConfig, QuickFiltersSource, SignalType } from '../types';
|
||||
import { QuickFiltersConfig } from './constants';
|
||||
|
||||
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;
|
||||
@@ -121,7 +133,7 @@ beforeAll(() => {
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -129,7 +141,7 @@ afterAll(() => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
(useQueryBuilder as Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
@@ -158,10 +170,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 +225,7 @@ describe('Quick Filters', () => {
|
||||
});
|
||||
|
||||
it('should not display query dropdown in ListView', () => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
(useQueryBuilder as Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
@@ -466,9 +478,9 @@ 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();
|
||||
vi.useFakeTimers();
|
||||
const user = userEvent.setup({
|
||||
advanceTimers: (ms) => jest.advanceTimersByTime(ms),
|
||||
advanceTimers: (ms) => vi.advanceTimersByTime(ms),
|
||||
pointerEventsCheck: 0,
|
||||
});
|
||||
|
||||
@@ -492,7 +504,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
await user.type(minDuration, '10000');
|
||||
await user.clear(maxDuration);
|
||||
await user.type(maxDuration, '20000');
|
||||
jest.advanceTimersByTime(2000);
|
||||
vi.advanceTimersByTime(2000);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
|
||||
@@ -521,7 +533,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
);
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { act } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
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 +29,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<typeof import('lodash-es')>('lodash-es')),
|
||||
debounce: (fn: (...args: any[]) => any): ((...args: any[]) => any) => fn,
|
||||
}));
|
||||
|
||||
@@ -60,7 +61,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 +81,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 +113,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 +133,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 +160,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 +179,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 +203,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 +226,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
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import RolesSelect from '../RolesSelect';
|
||||
|
||||
function renderLabeledRolesSelect(node: ReactElement): void {
|
||||
render(
|
||||
<div>
|
||||
<label htmlFor="roles-select-test">Roles</label>
|
||||
{node}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
vi.mock('api/generated/services/role', async () => {
|
||||
const actual = await vi.importActual<
|
||||
typeof import('api/generated/services/role')
|
||||
>('api/generated/services/role');
|
||||
return {
|
||||
...actual,
|
||||
useListRoles: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { useListRoles } from 'api/generated/services/role';
|
||||
|
||||
function mockListRolesSuccess(): void {
|
||||
(useListRoles as Mock).mockReturnValue({
|
||||
data: listRolesSuccessResponse,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
refetch: vi.fn(),
|
||||
isFetching: false,
|
||||
isSuccess: true,
|
||||
status: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
describe('RolesSelect', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockListRolesSuccess();
|
||||
});
|
||||
|
||||
it('lists roles from the API in single mode and reports changes', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onChange = vi.fn();
|
||||
|
||||
renderLabeledRolesSelect(
|
||||
<RolesSelect
|
||||
id="roles-select-test"
|
||||
mode="single"
|
||||
value={listRolesSuccessResponse.data[0]?.id}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
const rolesControl = await screen.findByLabelText('Roles');
|
||||
await user.click(rolesControl);
|
||||
|
||||
const editorOption = await screen.findByTitle('signoz-editor');
|
||||
await user.click(editorOption);
|
||||
|
||||
const editorId = listRolesSuccessResponse.data.find(
|
||||
(r) => r.name === 'signoz-editor',
|
||||
)?.id;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
expect(onChange.mock.calls[0][0]).toBe(editorId);
|
||||
});
|
||||
});
|
||||
|
||||
it('lists roles in multiple mode and reports combined selection', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onChange = vi.fn();
|
||||
const firstId = listRolesSuccessResponse.data[0]?.id as string;
|
||||
const secondId = listRolesSuccessResponse.data[1]?.id as string;
|
||||
|
||||
renderLabeledRolesSelect(
|
||||
<RolesSelect
|
||||
id="roles-select-test"
|
||||
mode="multiple"
|
||||
value={[firstId]}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
const rolesControl = await screen.findByLabelText('Roles');
|
||||
await user.click(rolesControl);
|
||||
|
||||
const secondOption = await screen.findByTitle(
|
||||
listRolesSuccessResponse.data[1]?.name ?? '',
|
||||
);
|
||||
await user.click(secondOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
expect(onChange.mock.calls[0][0]).toStrictEqual([firstId, secondId]);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses injected roles without fetching when roles prop is set', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const injected = listRolesSuccessResponse.data.slice(0, 2);
|
||||
|
||||
renderLabeledRolesSelect(
|
||||
<RolesSelect
|
||||
id="roles-select-test"
|
||||
mode="single"
|
||||
roles={injected}
|
||||
value={injected[0]?.id}
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(useListRoles).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ enabled: false }),
|
||||
}),
|
||||
);
|
||||
|
||||
const rolesControl = await screen.findByLabelText('Roles');
|
||||
await user.click(rolesControl);
|
||||
|
||||
await expect(
|
||||
screen.findByTitle(injected[1]?.name ?? ''),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||
|
||||
import RouteTab from './index';
|
||||
@@ -75,7 +76,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}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
@@ -11,22 +12,28 @@ import {
|
||||
|
||||
import AddKeyModal from '../AddKeyModal';
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
const { mockCopyToClipboard, mockCopyState } = vi.hoisted(() => {
|
||||
const copyFn = vi.fn();
|
||||
const copyState = { value: undefined, error: undefined };
|
||||
return {
|
||||
mockCopyToClipboard: copyFn,
|
||||
mockCopyState: copyState,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@signozhq/ui', async () => ({
|
||||
...(await vi.importActual<typeof import('@signozhq/ui')>('@signozhq/ui')),
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
const 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 +60,7 @@ function renderModal(): ReturnType<typeof render> {
|
||||
|
||||
describe('AddKeyModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
mockCopyToClipboard.mockClear();
|
||||
server.use(
|
||||
rest.post(SA_KEYS_ENDPOINT, (_, res, ctx) =>
|
||||
|
||||
@@ -2,16 +2,25 @@ import { toast } from '@signozhq/ui';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
type Mock,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import EditKeyModal from '../EditKeyModal';
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui', async () => ({
|
||||
...(await vi.importActual<typeof import('@signozhq/ui')>('@signozhq/ui')),
|
||||
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';
|
||||
|
||||
@@ -29,7 +38,7 @@ function renderModal(
|
||||
account: 'sa-1',
|
||||
'edit-key': 'key-1',
|
||||
},
|
||||
onUrlUpdate?: jest.Mock,
|
||||
onUrlUpdate?: Mock,
|
||||
): ReturnType<typeof render> {
|
||||
return render(
|
||||
<NuqsTestingAdapter
|
||||
@@ -44,7 +53,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: {} })),
|
||||
@@ -99,7 +108,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');
|
||||
|
||||
@@ -2,16 +2,40 @@ import { toast } from '@signozhq/ui';
|
||||
import { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import {
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import KeysTab from '../KeysTab';
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
vi.mock('@signozhq/ui', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui')),
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const mockToast = jest.mocked(toast);
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
const TZ_ORIG = process.env.TZ;
|
||||
|
||||
beforeAll(() => {
|
||||
process.env.TZ = 'UTC';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (TZ_ORIG === undefined) {
|
||||
delete process.env.TZ;
|
||||
} else {
|
||||
process.env.TZ = TZ_ORIG;
|
||||
}
|
||||
});
|
||||
|
||||
const SA_KEY_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys/:fid';
|
||||
|
||||
@@ -53,7 +77,7 @@ function renderKeysTab(
|
||||
|
||||
describe('KeysTab', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
server.use(
|
||||
rest.delete(SA_KEY_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||
@@ -72,7 +96,7 @@ describe('KeysTab', () => {
|
||||
|
||||
it('renders empty state when no keys and clicking add sets add-key param', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onUrlUpdate = jest.fn();
|
||||
const onUrlUpdate = vi.fn();
|
||||
render(
|
||||
<NuqsTestingAdapter
|
||||
searchParams={{ account: 'sa-1' }}
|
||||
@@ -105,7 +129,7 @@ describe('KeysTab', () => {
|
||||
|
||||
it('clicking a row sets the edit-key URL param', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onUrlUpdate = jest.fn();
|
||||
const onUrlUpdate = vi.fn();
|
||||
|
||||
render(
|
||||
<NuqsTestingAdapter onUrlUpdate={onUrlUpdate}>
|
||||
@@ -129,7 +153,7 @@ describe('KeysTab', () => {
|
||||
|
||||
it('clicking revoke icon sets revoke-key URL param', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onUrlUpdate = jest.fn();
|
||||
const onUrlUpdate = vi.fn();
|
||||
|
||||
render(
|
||||
<NuqsTestingAdapter onUrlUpdate={onUrlUpdate}>
|
||||
|
||||
@@ -2,12 +2,13 @@ import type { ReactNode } from 'react';
|
||||
import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import ServiceAccountDrawer from '../ServiceAccountDrawer';
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
vi.mock('@signozhq/ui', async () => ({
|
||||
...(await vi.importActual('@signozhq/ui')),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
@@ -23,7 +24,7 @@ jest.mock('@signozhq/ui', () => ({
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
toast: { success: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
||||
@@ -54,14 +55,14 @@ function renderDrawer(
|
||||
): ReturnType<typeof render> {
|
||||
return render(
|
||||
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
|
||||
<ServiceAccountDrawer onSuccess={jest.fn()} />
|
||||
<ServiceAccountDrawer onSuccess={vi.fn()} />
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('ServiceAccountDrawer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
server.use(
|
||||
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
|
||||
@@ -112,8 +113,8 @@ describe('ServiceAccountDrawer', () => {
|
||||
});
|
||||
|
||||
it('editing name enables Save; clicking Save sends correct payload and calls onSuccess', async () => {
|
||||
const onSuccess = jest.fn();
|
||||
const updateSpy = jest.fn();
|
||||
const onSuccess = vi.fn();
|
||||
const updateSpy = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
@@ -148,8 +149,8 @@ describe('ServiceAccountDrawer', () => {
|
||||
});
|
||||
|
||||
it('changing roles enables Save; clicking Save sends role add request without delete', async () => {
|
||||
const roleSpy = jest.fn();
|
||||
const deleteSpy = jest.fn();
|
||||
const roleSpy = vi.fn();
|
||||
const deleteSpy = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
@@ -185,7 +186,7 @@ describe('ServiceAccountDrawer', () => {
|
||||
});
|
||||
|
||||
it('"Delete Service Account" opens confirm dialog; confirming sends delete request', async () => {
|
||||
const deleteSpy = jest.fn();
|
||||
const deleteSpy = vi.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
@@ -284,7 +285,7 @@ describe('ServiceAccountDrawer', () => {
|
||||
|
||||
describe('ServiceAccountDrawer – save-error UX', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
server.use(
|
||||
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ServiceAccountRow } from 'container/ServiceAccountsSettings/utils';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
|
||||
import ServiceAccountsTable from '../ServiceAccountsTable';
|
||||
@@ -24,12 +26,12 @@ const mockDisabledAccount: ServiceAccountRow = {
|
||||
|
||||
const defaultProps = {
|
||||
loading: false,
|
||||
onRowClick: jest.fn(),
|
||||
onRowClick: vi.fn(),
|
||||
};
|
||||
|
||||
describe('ServiceAccountsTable', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders name, email, role badge, and ACTIVE status badge', () => {
|
||||
@@ -49,7 +51,7 @@ describe('ServiceAccountsTable', () => {
|
||||
});
|
||||
|
||||
it('calls onRowClick with the correct account when a row is clicked', async () => {
|
||||
const onRowClick = jest.fn() as jest.MockedFunction<
|
||||
const onRowClick = vi.fn() as MockedFunction<
|
||||
(row: ServiceAccountRow) => void
|
||||
>;
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { CmdAction } from '../ShiftOverlay';
|
||||
import { ShiftOverlay } from '../ShiftOverlay';
|
||||
|
||||
jest.mock('../formatShortcut', () => ({
|
||||
vi.mock('../formatShortcut', () => ({
|
||||
formatShortcut: (shortcut: string[]): string => shortcut.join('+'),
|
||||
}));
|
||||
|
||||
@@ -15,7 +14,7 @@ const baseActions: CmdAction[] = [
|
||||
name: 'Go to Traces',
|
||||
section: 'navigation',
|
||||
shortcut: ['Shift', 'T'],
|
||||
perform: jest.fn(),
|
||||
perform: vi.fn(),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
@@ -23,20 +22,20 @@ const baseActions: CmdAction[] = [
|
||||
section: 'navigation',
|
||||
shortcut: ['Shift', 'M'],
|
||||
roles: ['ADMIN'], // ✅ now UserRole[]
|
||||
perform: jest.fn(),
|
||||
perform: vi.fn(),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Create Alert',
|
||||
section: 'actions',
|
||||
shortcut: ['A'],
|
||||
perform: jest.fn(),
|
||||
perform: vi.fn(),
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Go to Logs',
|
||||
section: 'navigation',
|
||||
perform: jest.fn(),
|
||||
perform: vi.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -58,7 +57,7 @@ describe('ShiftOverlay', () => {
|
||||
id: 'x',
|
||||
name: 'Create Alert',
|
||||
section: 'actions',
|
||||
perform: jest.fn(),
|
||||
perform: vi.fn(),
|
||||
},
|
||||
]}
|
||||
userRole="ADMIN"
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import {
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
|
||||
import { useShiftHoldOverlay } from '../useShiftHoldOverlay';
|
||||
|
||||
jest.useFakeTimers();
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
function pressShift(target: EventTarget = window): void {
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
@@ -24,7 +39,7 @@ function releaseShift(): void {
|
||||
|
||||
describe('useShiftHoldOverlay', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
vi.clearAllTimers();
|
||||
});
|
||||
|
||||
it('shows overlay after holding Shift for 600ms', () => {
|
||||
@@ -32,7 +47,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
@@ -43,9 +58,9 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(300);
|
||||
vi.advanceTimersByTime(300);
|
||||
releaseShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
@@ -56,7 +71,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
@@ -75,7 +90,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
@@ -89,7 +104,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift(input);
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
@@ -102,7 +117,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
@@ -119,7 +134,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
@@ -136,7 +151,7 @@ describe('useShiftHoldOverlay', () => {
|
||||
|
||||
act(() => {
|
||||
pressShift();
|
||||
jest.advanceTimersByTime(600);
|
||||
vi.advanceTimersByTime(600);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { TimezoneContextType } from 'providers/Timezone';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import SpanHoverCard from '../SpanHoverCard';
|
||||
|
||||
// Mock timezone provider so SpanHoverCard can use useTimezone without a real context
|
||||
jest.mock('providers/Timezone', () => ({
|
||||
vi.mock('providers/Timezone', () => ({
|
||||
__esModule: true,
|
||||
useTimezone: (): TimezoneContextType => ({
|
||||
timezone: {
|
||||
@@ -20,18 +21,18 @@ jest.mock('providers/Timezone', () => ({
|
||||
offset: 'UTC',
|
||||
searchIndex: 'UTC',
|
||||
},
|
||||
updateTimezone: jest.fn(),
|
||||
formatTimezoneAdjustedTimestamp: jest.fn(() => 'mock-date'),
|
||||
updateTimezone: vi.fn(),
|
||||
formatTimezoneAdjustedTimestamp: vi.fn(() => 'mock-date'),
|
||||
isAdaptationEnabled: true,
|
||||
setIsAdaptationEnabled: jest.fn(),
|
||||
setIsAdaptationEnabled: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock dayjs for testing, including timezone helpers used in timezoneUtils
|
||||
jest.mock('dayjs', () => {
|
||||
vi.mock('dayjs', () => {
|
||||
const mockDayjsInstance: any = {};
|
||||
|
||||
mockDayjsInstance.format = jest.fn((formatString: string) =>
|
||||
mockDayjsInstance.format = vi.fn((formatString: string) =>
|
||||
// Match the DD_MMM_YYYY_HH_MM_SS format: 'DD MMM YYYY, HH:mm:ss'
|
||||
formatString === 'DD MMM YYYY, HH:mm:ss'
|
||||
? '15 Mar 2024, 14:23:45'
|
||||
@@ -39,18 +40,21 @@ jest.mock('dayjs', () => {
|
||||
);
|
||||
|
||||
// Support chaining: dayjs().tz(timezone).format(...) and dayjs().tz(timezone).utcOffset()
|
||||
mockDayjsInstance.tz = jest.fn(() => mockDayjsInstance);
|
||||
mockDayjsInstance.utcOffset = jest.fn(() => 0);
|
||||
mockDayjsInstance.tz = vi.fn(() => mockDayjsInstance);
|
||||
mockDayjsInstance.utcOffset = vi.fn(() => 0);
|
||||
|
||||
const mockDayjs = jest.fn(() => mockDayjsInstance);
|
||||
const mockDayjs = vi.fn(() => mockDayjsInstance);
|
||||
|
||||
Object.assign(mockDayjs, {
|
||||
extend: jest.fn(),
|
||||
extend: vi.fn(),
|
||||
// Support dayjs.tz.guess()
|
||||
tz: { guess: jest.fn(() => 'UTC') },
|
||||
tz: { guess: vi.fn(() => 'UTC') },
|
||||
});
|
||||
|
||||
return mockDayjs;
|
||||
return {
|
||||
__esModule: true,
|
||||
default: mockDayjs,
|
||||
};
|
||||
});
|
||||
|
||||
const HOVER_ELEMENT_ID = 'hover-element';
|
||||
@@ -99,11 +103,11 @@ const mockTraceMetadata = {
|
||||
|
||||
describe('SpanHoverCard', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('renders child element correctly', () => {
|
||||
@@ -134,7 +138,7 @@ describe('SpanHoverCard', () => {
|
||||
|
||||
// Advance time by 0.5 seconds
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(200);
|
||||
vi.advanceTimersByTime(200);
|
||||
});
|
||||
|
||||
// Now popover should appear
|
||||
@@ -153,13 +157,13 @@ describe('SpanHoverCard', () => {
|
||||
// Quick hover and unhover (less than the 0.2s delay)
|
||||
fireEvent.mouseEnter(hoverElement);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(100); // Only 0.1 seconds
|
||||
vi.advanceTimersByTime(100); // Only 0.1 seconds
|
||||
});
|
||||
fireEvent.mouseLeave(hoverElement);
|
||||
|
||||
// Advance past the full delay
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(400);
|
||||
vi.advanceTimersByTime(400);
|
||||
});
|
||||
|
||||
// Popover should not appear
|
||||
@@ -178,7 +182,7 @@ describe('SpanHoverCard', () => {
|
||||
// Hover and wait for popover
|
||||
fireEvent.mouseEnter(hoverElement);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
// Check that popover shows span operation name in title
|
||||
@@ -208,7 +212,7 @@ describe('SpanHoverCard', () => {
|
||||
// Hover and wait for popover
|
||||
fireEvent.mouseEnter(hoverElement);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
// Verify the DD MMM YYYY, HH:mm:ss format is displayed
|
||||
@@ -232,7 +236,7 @@ describe('SpanHoverCard', () => {
|
||||
// Hover and wait for popover
|
||||
fireEvent.mouseEnter(hoverElement);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
// Check relative time display
|
||||
@@ -256,7 +260,7 @@ describe('SpanHoverCard', () => {
|
||||
// Hover and wait for popover
|
||||
fireEvent.mouseEnter(hoverElement);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
expect(screen.getByText('Events:')).toBeInTheDocument();
|
||||
@@ -284,7 +288,7 @@ describe('SpanHoverCard', () => {
|
||||
|
||||
// Should appear after delay
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(500);
|
||||
vi.advanceTimersByTime(500);
|
||||
});
|
||||
expect(screen.getByText('Duration:')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -47,16 +47,10 @@ function TanStackCustomTableRow<TData>({
|
||||
const isActive = context?.isRowActive?.(rowData) ?? false;
|
||||
const extraClass = context?.getRowClassName?.(rowData) ?? '';
|
||||
const rowStyle = context?.getRowStyle?.(rowData);
|
||||
const enableAlternatingRowColors =
|
||||
context?.enableAlternatingRowColors ?? false;
|
||||
|
||||
const rowClassName = cx(
|
||||
tableStyles.tableRow,
|
||||
isActive && tableStyles.tableRowActive,
|
||||
enableAlternatingRowColors &&
|
||||
(item.row.index % 2 === 0
|
||||
? tableStyles.tableRowEven
|
||||
: tableStyles.tableRowOdd),
|
||||
extraClass,
|
||||
);
|
||||
|
||||
@@ -111,12 +105,6 @@ function areTableRowPropsEqual<TData>(
|
||||
if (prev.context?.columnVisibilityKey !== next.context?.columnVisibilityKey) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
prev.context?.enableAlternatingRowColors !==
|
||||
next.context?.enableAlternatingRowColors
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev.context !== next.context) {
|
||||
const prevActive = prev.context?.isRowActive?.(prevData) ?? false;
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
padding: var(--tanstack-cell-padding-top, 0.3rem)
|
||||
var(--tanstack-cell-padding-right, 0.3rem)
|
||||
var(--tanstack-cell-padding-bottom, 0.3rem)
|
||||
var(--tanstack-cell-padding-left, 0.3rem);
|
||||
padding: 0.3rem;
|
||||
transform: translate3d(
|
||||
var(--tanstack-header-translate-x, 0px),
|
||||
var(--tanstack-header-translate-y, 0px),
|
||||
@@ -22,17 +19,7 @@
|
||||
}
|
||||
|
||||
border: none !important;
|
||||
background-color: var(
|
||||
--tanstack-table-header-cell-bg,
|
||||
var(--l2-background)
|
||||
) !important;
|
||||
|
||||
&:first-child {
|
||||
background-color: var(
|
||||
--tanstack-first-column-header-bg,
|
||||
var(--tanstack-table-header-cell-bg, var(--l2-background))
|
||||
) !important;
|
||||
}
|
||||
background-color: var(--l2-background) !important;
|
||||
}
|
||||
|
||||
.tanstackHeaderContent {
|
||||
@@ -74,7 +61,7 @@
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
cursor: grab;
|
||||
color: var(--tanstack-table-header-cell-color, var(--l2-foreground));
|
||||
color: var(--l2-foreground);
|
||||
opacity: 1;
|
||||
touch-action: none;
|
||||
}
|
||||
@@ -87,7 +74,7 @@
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
color: var(--tanstack-table-header-cell-color, var(--l2-foreground));
|
||||
color: var(--l2-foreground);
|
||||
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -95,9 +82,8 @@
|
||||
.tanstackColumnActionsContent {
|
||||
width: 140px;
|
||||
padding: 0;
|
||||
background: var(--tanstack-table-header-cell-bg, var(--l2-background));
|
||||
border: 1px solid
|
||||
var(--tanstack-table-header-cell-actions-border-color, var(--l2-border));
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -151,7 +137,7 @@
|
||||
}
|
||||
|
||||
.tanstackHeaderCell.isResizing .cursorColResize {
|
||||
background: var(--tanstack-table-resize-active-bg, var(--bg-robin-300));
|
||||
background: var(--bg-robin-300);
|
||||
}
|
||||
|
||||
.tanstackResizeHandleLine {
|
||||
@@ -161,7 +147,7 @@
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
transform: translateX(-50%);
|
||||
background: var(--tanstack-table-resize-handle-bg, var(--l2-background));
|
||||
background: var(--l2-background);
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
@@ -169,34 +155,13 @@
|
||||
width 120ms ease;
|
||||
}
|
||||
|
||||
.tanstackHeaderCell:first-child .tanstackResizeHandleLine {
|
||||
background: var(
|
||||
--tanstack-first-column-header-bg,
|
||||
var(--tanstack-table-resize-handle-bg, var(--l2-background))
|
||||
);
|
||||
}
|
||||
|
||||
.cursorColResize:hover .tanstackResizeHandleLine {
|
||||
background: var(--tanstack-table-resize-handle-hover-bg, var(--l2-border));
|
||||
}
|
||||
|
||||
.tanstackHeaderCell:first-child
|
||||
.cursorColResize:hover
|
||||
.tanstackResizeHandleLine {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(
|
||||
--tanstack-first-column-header-bg,
|
||||
var(--tanstack-table-resize-handle-bg, var(--l2-background))
|
||||
)
|
||||
60%,
|
||||
black
|
||||
);
|
||||
background: var(--l2-border);
|
||||
}
|
||||
|
||||
.tanstackHeaderCell.isResizing .tanstackResizeHandleLine {
|
||||
width: 2px;
|
||||
background: var(--tanstack-table-resize-handle-active-bg, var(--bg-robin-500));
|
||||
background: var(--bg-robin-500);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@@ -248,12 +213,7 @@
|
||||
flex-shrink: 0;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--l3-foreground);
|
||||
|
||||
&[data-sort-direction='asc'],
|
||||
&[data-sort-direction='desc'] {
|
||||
color: var(--primary);
|
||||
}
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.isSortable {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user