mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
feat: frontend unit test suite setup (#9027)
* feat: update context link modal form init * feat: add double way sync on urls and param * feat: minor refactor * feat: minor refactor * feat: change contextlinks data structure * feat: context menu changes init * feat: context menu hook refactor * feat: context links processors * feat: context variables hook added * feat: add support for field variables * feat: minor refactor * feat: minor refactor * feat: minor refactor * feat: handle on save * feat: minor refactor * feat: snapshot update * feat: revert qbv5 * feat: aggregation header val * feat: fix header color * feat: minor refactor * feat: minor refactor * feat: fix breaking changes from qb v5 * feat: change api for breakout opitons * feat: minor refactor * feat: minor refactor * fix: added fix for extractquerypararms when value is string in multivalue operator * feat: minor refactor * feat: add back in breakout * feat: minor refactor * feat: add substitute var api call to decode vars * feat: minor fix * feat: optimize query value comparison in QueryBuilderV2 * feat: minor fix * feat: minor fix * feat: test fix * feat: added dynamic variables creation flow (#7541) * feat: added dynamic variables creation flow * feat: added keys and value apis and hooks * feat: added api and select component changes * feat: added keys fetching and preview values * feat: added dynamic variable to variable items * feat: handled value persistence and tab switches * feat: added default value and formed a schema for dyn-variables * feat: added client and server side searches * feat: corrected the initial load getfieldKey api * feat: removed fetch on mount restriction * feat: added dynamic variable to the dashboard details (#7755) * feat: added dynamic variable to the dashboard details * feat: added new component to existing variables * feat: added enhancement to multiselect and select for dyn-variables * feat: added refetch method between all dynamic-variables * feat: correct error handling * feat: correct error handling * feat: enforced non-empty selectedvalues and default value * feat: added client and server side searches * feat: retry on error * feat: correct error handling * feat: handle defautl value in existing variables * feat: lowercase the source for payload * feat: fixed the incorrect assignment of active indices * feat: improved handling of all option * feat: improved the ALL option visuals * feat: handled default value enforcement in existing variables * feat: added unix time to values call * feat: added incomplete data message and info to search * feat: changed dashboard panel call handling with existing variables * feat: adjusted the response type and data with the new API schema for values * feat: code refactor * feat: made dyn-variable option as the default * feat: added test cases for dyn variable creation and completion * feat: updated test cases * feat: added variable in url and made dashboard sync around that and sharable (#7944) * feat: added dynamic variable to the dashboard details * feat: added new component to existing variables * feat: added enhancement to multiselect and select for dyn-variables * feat: added refetch method between all dynamic-variables * feat: correct error handling * feat: correct error handling * feat: enforced non-empty selectedvalues and default value * feat: added client and server side searches * feat: retry on error * feat: correct error handling * feat: handle defautl value in existing variables * feat: lowercase the source for payload * feat: fixed the incorrect assignment of active indices * feat: improved handling of all option * feat: improved the ALL option visuals * feat: handled default value enforcement in existing variables * feat: added unix time to values call * feat: added incomplete data message and info to search * feat: changed dashboard panel call handling with existing variables * feat: adjusted the response type and data with the new API schema for values * feat: code refactor * feat: made dyn-variable option as the default * feat: added test cases for dyn variable creation and completion * feat: updated test cases * feat: added variable in url and made dashboard sync around that and sharable * feat: added test cases * feat: added safety check * feat: enabled url setting on first load itself * feat: code refactor * feat: cleared options query param when on dashboard list page * feat: resolved conflicts * feat: added dynamic variable suggestion in where clause * feat: added test cases for hooks and api call functions * feat: added test case for querybuildersearchv2 suggestion changes * feat: code refactor * feat: updated test case * feat: corrected the regex matcher for resolved titles * feat: added ability to add/remove variable filter to one or more existing panels * feat: added widgetselector on variable creation * feat: show labels in widget selector * feat: added apply to all and variable removal logical * feat: refectch only related and affected panels in case of dynamic variables * feat: added button loader for apply-all * feat: light-mode styles * feat: minor refactor * feat: added test cases * feat: refactor * feat: remove consoles * feat: pass panel types to substitutevars * feat: cross filtering init * fix: added fix for query builder filters * feat: cross filtering add set/unset/create functionality * feat: test update * fix: added migration to filter expression for crud operations of variable * feat: format legend name according to existing format * feat: breakout test init * feat: breakout test match query * feat: context links tests * feat: minor refactor * feat: show edit only if user has access * feat: added dynamic variables creation flow (#7541) * feat: added dynamic variables creation flow * feat: added keys and value apis and hooks * feat: added api and select component changes * feat: added keys fetching and preview values * feat: added dynamic variable to variable items * feat: handled value persistence and tab switches * feat: added default value and formed a schema for dyn-variables * feat: added client and server side searches * feat: corrected the initial load getfieldKey api * feat: removed fetch on mount restriction * feat: added dynamic variable to the dashboard details (#7755) * feat: added dynamic variable to the dashboard details * feat: added new component to existing variables * feat: added enhancement to multiselect and select for dyn-variables * feat: added refetch method between all dynamic-variables * feat: correct error handling * feat: correct error handling * feat: enforced non-empty selectedvalues and default value * feat: added client and server side searches * feat: retry on error * feat: correct error handling * feat: handle defautl value in existing variables * feat: lowercase the source for payload * feat: fixed the incorrect assignment of active indices * feat: improved handling of all option * feat: improved the ALL option visuals * feat: handled default value enforcement in existing variables * feat: added unix time to values call * feat: added incomplete data message and info to search * feat: changed dashboard panel call handling with existing variables * feat: adjusted the response type and data with the new API schema for values * feat: code refactor * feat: made dyn-variable option as the default * feat: added test cases for dyn variable creation and completion * feat: updated test cases * feat: added dynamic variable suggestion in where clause * feat: added test cases for hooks and api call functions * feat: added test case for querybuildersearchv2 suggestion changes * feat: code refactor * feat: updated test case * feat: corrected the regex matcher for resolved titles * feat: added ability to add/remove variable filter to one or more existing panels * feat: added widgetselector on variable creation * feat: show labels in widget selector * feat: added apply to all and variable removal logical * feat: refectch only related and affected panels in case of dynamic variables * feat: added button loader for apply-all * feat: light-mode styles * fix: added migration to filter expression for crud operations of variable * feat: reverted dynamic variable url config changes (#8877) * Revert "feat: changed query param name" This reverts commit 62bee5f003bf74b0da1c5951f1b5d0f2c250905d. * Revert "feat: added user-friendly format to dashboard variable url" This reverts commit 6de8b1c2e8c6a838941014ea4929e9f5c908d975. * feat: reverted url var changes * feat: reverted url changed from usedashboardvarupdate hook * feat: send empty array for widgetId * feat: added type in the variables in query_range payload for dynamic * feat: minor fixes * fix: added fix for multivalue operator without brackets * feat: minor fix * feat: fix failing test * feat: change revert * test: added tests for querycontextUtils + querybuilderv2 utils * fix: added fix for replacing filter with the new value * fix: added fix for replacing filters + datetimepicker composite query * test: fixed querybuilderv2 utils test * feat: handle number dataType in filters * feat: correct the variable addition to panel format for new qb expression * feat: remove other queries in breakout * feat: add metric to traces mapping * feat: pass proper time range * feat: update time range logic * feat: value panel drilldown init * feat: value panel drilldown init * feat: enable context links in value panel * feat: minor fix * feat: update snapshot * feat: hide breakout in value panel * feat: add panel type to view mode * feat: add support to change panel in breakouts * feat: panel change for breakout logic added * chore: fix style * chore: show variables suggestion while creating context links * chore: add timestamp to graphs * chore: add timestamp to table panel * chore: fix failing tests * chore: fix infinite re-rendering due to queryRange * chore: send appropriate time range when signal is metrics * chore: show variables suggestion while creating context links * chore: minor refactor * chore: show trace details link if filter has trace_id * chore: fix infinite render of table component * chore: added tests for v2 * fix: context links set from dropdown * chore: minor refactor * chore: minor refactor * chore: fix test * chore: fix timerange for apm metrics * fix: get correct timestamp for clicked data * chore: comment out change to histogram on breakout by number * chore: change panel type on panel type change in url * chore: remove consoles * feat: added dynamic variables creation flow (#7541) * feat: added dynamic variables creation flow * feat: added keys and value apis and hooks * feat: added api and select component changes * feat: added keys fetching and preview values * feat: added dynamic variable to variable items * feat: handled value persistence and tab switches * feat: added default value and formed a schema for dyn-variables * feat: added client and server side searches * feat: corrected the initial load getfieldKey api * feat: removed fetch on mount restriction * feat: added dynamic variable to the dashboard details (#7755) * feat: added dynamic variable to the dashboard details * feat: added new component to existing variables * feat: added enhancement to multiselect and select for dyn-variables * feat: added refetch method between all dynamic-variables * feat: correct error handling * feat: correct error handling * feat: enforced non-empty selectedvalues and default value * feat: added client and server side searches * feat: retry on error * feat: correct error handling * feat: handle defautl value in existing variables * feat: lowercase the source for payload * feat: fixed the incorrect assignment of active indices * feat: improved handling of all option * feat: improved the ALL option visuals * feat: handled default value enforcement in existing variables * feat: added unix time to values call * feat: added incomplete data message and info to search * feat: changed dashboard panel call handling with existing variables * feat: adjusted the response type and data with the new API schema for values * feat: code refactor * feat: made dyn-variable option as the default * feat: added test cases for dyn variable creation and completion * feat: updated test cases * feat: fix lint and test cases * feat: fix typo * feat: fixed test case * feat: added dynamic variable suggestion in where clause * feat: added test cases for hooks and api call functions * feat: added test case for querybuildersearchv2 suggestion changes * feat: code refactor * feat: corrected the regex matcher for resolved titles * feat: fixed test cases * feat: added ability to add/remove variable filter to one or more existing panels * feat: added widgetselector on variable creation * feat: show labels in widget selector * feat: added apply to all and variable removal logical * feat: refectch only related and affected panels in case of dynamic variables * feat: added button loader for apply-all * feat: light-mode styles * fix: added migration to filter expression for crud operations of variable * feat: added type in the variables in query_range payload for dynamic * feat: correct the variable addition to panel format for new qb expression * feat: added test cases for dynamic variable and add/remove panel feat * feat: implemented where clause suggestion in new qb v5 * feat: added retries for dyn variable and fixed on-enter selection issue * feat: added relatedValues and existing query in param related changes * feat: sanitized data storage and removed duplicates * fix: fixed typechecks * feat: updated panel wait and refetch logic and ALL option selection * feat: fixed variable tabel reordering issue * feat: added empty name validation in variable creation * feat: change value to searchtext in values API * feat: added option for regex in the component, disabled for now * feat: added beta and not rec. tag in variable tabs * feat: added check to prevent api and updates calls with same payload * feat: optimized localstorage for all selection in dynamic variable and updated __all__ case * feat: resolved variable tables infinite loop update error * feat: aded variable name auto-update based on attribute name entered for dynamic variables * feat: modified only/all click behaviour and set all selection always true for dynamic variable * feat: fix dropdown closing doesn't reset us back to our all available values when we have a search * feat: handled all state distinction and carry forward in existing variables * feat: trucate + n more tooltip content to 10 * feat: fixed infinite loop because of dependency of frequently changing object ref in var table * feat: fixed inconsist search implementations * feat: reverted only - all updated area implementation * feat: added more space for search in multiselect component * feat: checked for variable id instead of variable key for refetch * feat: improved performance around multiselect component and added confirm modal for apply to all * feat: rewrite functionality around add and remove panels * feat: changed color for apply to all modal * feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func * feat: added validation in variable edit panel * chore: fix dynamic variable update in context menu to latest logic * chore: minor fix * chore: type fix * fix: remove unwanted code * fix: remove unwanted code * fix: resolved pr comments * fix: minor fix * fix: fix tests * fix: style fix * fix: hide drilldown options in view mode for non-builder panels * chore: add global uplot mock * chore: query builder context update to all provider * chore: add cursor rules init * chore: useSafeNavigate mock added * chore: more cleanups * chore: remove react-router-v5 mock from setup * chore: update cursorrules * chore: add tests readme init * chore: minor refactor --------- Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local> Co-authored-by: Abhi Kumar <ahrefabhi@gmail.com> Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Co-authored-by: SagarRajput-7 <sagar@signoz.io>
This commit is contained in:
parent
0c25de9560
commit
bced4774bb
484
frontend/.cursorrules
Normal file
484
frontend/.cursorrules
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
# Persona
|
||||||
|
You are an expert developer with deep knowledge of Jest, React Testing Library, MSW, and TypeScript, tasked with creating unit tests for this repository.
|
||||||
|
|
||||||
|
# Auto-detect TypeScript Usage
|
||||||
|
Check for TypeScript in the project through tsconfig.json or package.json dependencies.
|
||||||
|
Adjust syntax based on this detection.
|
||||||
|
|
||||||
|
# TypeScript Type Safety for Jest Tests
|
||||||
|
**CRITICAL**: All Jest tests MUST be fully type-safe with proper TypeScript types.
|
||||||
|
|
||||||
|
**Type Safety Requirements:**
|
||||||
|
- Use proper TypeScript interfaces for all mock data
|
||||||
|
- Type all Jest mock functions with `jest.MockedFunction<T>`
|
||||||
|
- Use generic types for React components and hooks
|
||||||
|
- Define proper return types for mock functions
|
||||||
|
- Use `as const` for literal types when needed
|
||||||
|
- Avoid `any` type – use proper typing instead
|
||||||
|
|
||||||
|
# Unit Testing Focus
|
||||||
|
Focus on critical functionality (business logic, utility functions, component behavior)
|
||||||
|
Mock dependencies (API calls, external modules) before imports
|
||||||
|
Test multiple data scenarios (valid inputs, invalid inputs, edge cases)
|
||||||
|
Write maintainable tests with descriptive names grouped in describe blocks
|
||||||
|
|
||||||
|
# Global vs Local Mocks
|
||||||
|
**Use Global Mocks for:**
|
||||||
|
- High-frequency dependencies (20+ test files)
|
||||||
|
- Core infrastructure (react-router-dom, react-query, antd)
|
||||||
|
- Standard implementations across the app
|
||||||
|
- Browser APIs (ResizeObserver, matchMedia, localStorage)
|
||||||
|
- Utility libraries (date-fns, lodash)
|
||||||
|
|
||||||
|
**Use Local Mocks for:**
|
||||||
|
- Business logic dependencies (5-15 test files)
|
||||||
|
- Test-specific behavior (different data per test)
|
||||||
|
- API endpoints with specific responses
|
||||||
|
- Domain-specific components
|
||||||
|
- Error scenarios and edge cases
|
||||||
|
|
||||||
|
**Global Mock Files Available (from jest.config.ts):**
|
||||||
|
- `uplot` → `__mocks__/uplotMock.ts`
|
||||||
|
|
||||||
|
# Repo-specific Testing Conventions
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
Always import from our harness:
|
||||||
|
```ts
|
||||||
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
|
```
|
||||||
|
For API mocks:
|
||||||
|
```ts
|
||||||
|
import { server, rest } from 'mocks-server/server';
|
||||||
|
```
|
||||||
|
Do not import directly from `@testing-library/react`.
|
||||||
|
|
||||||
|
## Router
|
||||||
|
Use the router built into render:
|
||||||
|
```ts
|
||||||
|
render(<Page />, undefined, { initialRoute: '/traces-explorer' });
|
||||||
|
```
|
||||||
|
Only mock `useLocation` / `useParams` if the test depends on them.
|
||||||
|
|
||||||
|
## Hook Mocks
|
||||||
|
Pattern:
|
||||||
|
```ts
|
||||||
|
import useFoo from 'hooks/useFoo';
|
||||||
|
jest.mock('hooks/useFoo');
|
||||||
|
const mockUseFoo = jest.mocked(useFoo);
|
||||||
|
mockUseFoo.mockReturnValue(/* minimal shape */ as any);
|
||||||
|
```
|
||||||
|
Prefer helpers (`rqSuccess`, `rqLoading`, `rqError`) for React Query results.
|
||||||
|
|
||||||
|
## MSW
|
||||||
|
Global MSW server runs automatically.
|
||||||
|
Override per-test:
|
||||||
|
```ts
|
||||||
|
server.use(
|
||||||
|
rest.get('*/api/v1/foo', (_req, res, ctx) => res(ctx.status(200), ctx.json({ ok: true })))
|
||||||
|
);
|
||||||
|
```
|
||||||
|
Keep large responses in `mocks-server/__mockdata_`.
|
||||||
|
|
||||||
|
## Interactions
|
||||||
|
- Prefer `userEvent` for real user interactions (click, type, select, tab).
|
||||||
|
- Use `fireEvent` only for low-level/programmatic events not covered by `userEvent` (e.g., scroll, resize, setting `element.scrollTop` for virtualization). Wrap in `act(...)` if needed.
|
||||||
|
- Always await interactions:
|
||||||
|
```ts
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Example: virtualized list scroll (no userEvent helper)
|
||||||
|
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
|
||||||
|
scroller.scrollTop = targetScrollTop;
|
||||||
|
act(() => { fireEvent.scroll(scroller); });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timers
|
||||||
|
❌ No global fake timers.
|
||||||
|
✅ Per-test only, for debounce/throttle:
|
||||||
|
```ts
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const user = userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) });
|
||||||
|
await user.type(screen.getByRole('textbox'), 'query');
|
||||||
|
jest.advanceTimersByTime(400);
|
||||||
|
jest.useRealTimers();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Queries
|
||||||
|
Prefer accessible queries (`getByRole`, `findByRole`, `getByLabelText`).
|
||||||
|
Fallback: visible text.
|
||||||
|
Last resort: `data-testid`.
|
||||||
|
|
||||||
|
# Example Test (using only configured global mocks)
|
||||||
|
```ts
|
||||||
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
|
import { server, rest } from 'mocks-server/server';
|
||||||
|
import MyComponent from '../MyComponent';
|
||||||
|
|
||||||
|
describe('MyComponent', () => {
|
||||||
|
it('renders and interacts', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<MyComponent />, undefined, { initialRoute: '/foo' });
|
||||||
|
|
||||||
|
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
|
||||||
|
await user.click(screen.getByRole('button', { name: /refresh/i }));
|
||||||
|
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
# Anti-patterns
|
||||||
|
❌ Importing RTL directly
|
||||||
|
❌ Using global fake timers
|
||||||
|
❌ Wrapping render in `act(...)`
|
||||||
|
❌ Mocking infra dependencies locally (router, react-query)
|
||||||
|
✅ Use our harness (`tests/test-utils`)
|
||||||
|
✅ Use MSW for API overrides
|
||||||
|
✅ Use userEvent + await
|
||||||
|
✅ Pin time only in tests that assert relative dates
|
||||||
|
|
||||||
|
# Best Practices
|
||||||
|
- **Critical Functionality**: Prioritize testing business logic and utilities
|
||||||
|
- **Dependency Mocking**: Global mocks for infra, local mocks for business logic
|
||||||
|
- **Data Scenarios**: Always test valid, invalid, and edge cases
|
||||||
|
- **Descriptive Names**: Make test intent clear
|
||||||
|
- **Organization**: Group related tests in describe
|
||||||
|
- **Consistency**: Match repo conventions
|
||||||
|
- **Edge Cases**: Test null, undefined, unexpected values
|
||||||
|
- **Limit Scope**: 3–5 focused tests per file
|
||||||
|
- **Use Helpers**: `rqSuccess`, `makeUser`, etc.
|
||||||
|
- **No Any**: Enforce type safety
|
||||||
|
|
||||||
|
# Example Test
|
||||||
|
```ts
|
||||||
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
|
import { server, rest } from 'mocks-server/server';
|
||||||
|
import MyComponent from '../MyComponent';
|
||||||
|
|
||||||
|
describe('MyComponent', () => {
|
||||||
|
it('renders and interacts', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<MyComponent />, undefined, { initialRoute: '/foo' });
|
||||||
|
|
||||||
|
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
|
||||||
|
await user.click(screen.getByRole('button', { name: /refresh/i }));
|
||||||
|
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
# Anti-patterns
|
||||||
|
❌ Importing RTL directly
|
||||||
|
❌ Using global fake timers
|
||||||
|
❌ Wrapping render in `act(...)`
|
||||||
|
❌ Mocking infra dependencies locally (router, react-query)
|
||||||
|
✅ Use our harness (`tests/test-utils`)
|
||||||
|
✅ Use MSW for API overrides
|
||||||
|
✅ Use userEvent + await
|
||||||
|
✅ Pin time only in tests that assert relative dates
|
||||||
|
|
||||||
|
# TypeScript Type Safety Examples
|
||||||
|
|
||||||
|
## Proper Mock Typing
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Properly typed mocks
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiResponse<T> {
|
||||||
|
data: T;
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type the mock functions
|
||||||
|
const mockFetchUser = jest.fn() as jest.MockedFunction<(id: number) => Promise<ApiResponse<User>>>;
|
||||||
|
const mockUpdateUser = jest.fn() as jest.MockedFunction<(user: User) => Promise<ApiResponse<User>>>;
|
||||||
|
|
||||||
|
// Mock implementation with proper typing
|
||||||
|
mockFetchUser.mockResolvedValue({
|
||||||
|
data: { id: 1, name: 'John Doe', email: 'john@example.com' },
|
||||||
|
status: 200,
|
||||||
|
message: 'Success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// ❌ BAD - Using any type
|
||||||
|
const mockFetchUser = jest.fn() as any; // Don't do this
|
||||||
|
```
|
||||||
|
|
||||||
|
## React Component Testing with Types
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Properly typed component testing
|
||||||
|
interface ComponentProps {
|
||||||
|
title: string;
|
||||||
|
data: User[];
|
||||||
|
onUserSelect: (user: User) => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestComponent: React.FC<ComponentProps> = ({ title, data, onUserSelect, isLoading = false }) => {
|
||||||
|
// Component implementation
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('TestComponent', () => {
|
||||||
|
it('should render with proper props', () => {
|
||||||
|
// Arrange - Type the props properly
|
||||||
|
const mockProps: ComponentProps = {
|
||||||
|
title: 'Test Title',
|
||||||
|
data: [{ id: 1, name: 'John', email: 'john@example.com' }],
|
||||||
|
onUserSelect: jest.fn() as jest.MockedFunction<(user: User) => void>,
|
||||||
|
isLoading: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
render(<TestComponent {...mockProps} />);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(screen.getByText('Test Title')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hook Testing with Types
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Properly typed hook testing
|
||||||
|
interface UseUserDataReturn {
|
||||||
|
user: User | null;
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
refetch: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useUserData = (id: number): UseUserDataReturn => {
|
||||||
|
// Hook implementation
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useUserData', () => {
|
||||||
|
it('should return user data with proper typing', () => {
|
||||||
|
// Arrange
|
||||||
|
const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
|
||||||
|
mockFetchUser.mockResolvedValue({
|
||||||
|
data: mockUser,
|
||||||
|
status: 200,
|
||||||
|
message: 'Success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const { result } = renderHook(() => useUserData(1));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result.current.user).toEqual(mockUser);
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
expect(result.current.error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global Mock Type Safety
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Type-safe global mocks
|
||||||
|
// In __mocks__/routerMock.ts
|
||||||
|
export const mockUseLocation = (overrides: Partial<Location> = {}): Location => ({
|
||||||
|
pathname: '/traces',
|
||||||
|
search: '',
|
||||||
|
hash: '',
|
||||||
|
state: null,
|
||||||
|
key: 'test-key',
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
// In test files
|
||||||
|
const location = useLocation(); // Properly typed from global mock
|
||||||
|
expect(location.pathname).toBe('/traces');
|
||||||
|
```
|
||||||
|
|
||||||
|
# TypeScript Configuration for Jest
|
||||||
|
|
||||||
|
## Required Jest Configuration
|
||||||
|
```json
|
||||||
|
// jest.config.ts
|
||||||
|
{
|
||||||
|
"preset": "ts-jest/presets/js-with-ts-esm",
|
||||||
|
"globals": {
|
||||||
|
"ts-jest": {
|
||||||
|
"useESM": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"tsconfig": "<rootDir>/tsconfig.jest.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extensionsToTreatAsEsm": [".ts", ".tsx"],
|
||||||
|
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Jest Configuration
|
||||||
|
```json
|
||||||
|
// tsconfig.jest.json
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["jest", "@testing-library/jest-dom"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"__mocks__/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Type Safety Patterns
|
||||||
|
|
||||||
|
### Mock Function Typing
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Proper mock function typing
|
||||||
|
const mockApiCall = jest.fn() as jest.MockedFunction<typeof apiCall>;
|
||||||
|
const mockEventHandler = jest.fn() as jest.MockedFunction<(event: Event) => void>;
|
||||||
|
|
||||||
|
// ❌ BAD - Using any
|
||||||
|
const mockApiCall = jest.fn() as any;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generic Mock Typing
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Generic mock typing
|
||||||
|
interface MockApiResponse<T> {
|
||||||
|
data: T;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockFetchData = jest.fn() as jest.MockedFunction<
|
||||||
|
<T>(endpoint: string) => Promise<MockApiResponse<T>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
mockFetchData<User>('/users').mockResolvedValue({
|
||||||
|
data: { id: 1, name: 'John' },
|
||||||
|
status: 200
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### React Testing Library with Types
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Typed testing utilities
|
||||||
|
import { render, screen, RenderResult } from '@testing-library/react';
|
||||||
|
import { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
type TestComponentProps = ComponentProps<typeof TestComponent>;
|
||||||
|
|
||||||
|
const renderTestComponent = (props: Partial<TestComponentProps> = {}): RenderResult => {
|
||||||
|
const defaultProps: TestComponentProps = {
|
||||||
|
title: 'Test',
|
||||||
|
data: [],
|
||||||
|
onSelect: jest.fn(),
|
||||||
|
...props
|
||||||
|
};
|
||||||
|
|
||||||
|
return render(<TestComponent {...defaultProps} />);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling with Types
|
||||||
|
```ts
|
||||||
|
// ✅ GOOD - Typed error handling
|
||||||
|
interface ApiError {
|
||||||
|
message: string;
|
||||||
|
code: number;
|
||||||
|
details?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockApiError: ApiError = {
|
||||||
|
message: 'API Error',
|
||||||
|
code: 500,
|
||||||
|
details: { endpoint: '/users' }
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetchUser.mockRejectedValue(new Error(JSON.stringify(mockApiError)));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Safety Checklist
|
||||||
|
- [ ] All mock functions use `jest.MockedFunction<T>`
|
||||||
|
- [ ] All mock data has proper interfaces
|
||||||
|
- [ ] No `any` types in test files
|
||||||
|
- [ ] Generic types are used where appropriate
|
||||||
|
- [ ] Error types are properly defined
|
||||||
|
- [ ] Component props are typed
|
||||||
|
- [ ] Hook return types are defined
|
||||||
|
- [ ] API response types are defined
|
||||||
|
- [ ] Global mocks are type-safe
|
||||||
|
- [ ] Test utilities are properly typed
|
||||||
|
|
||||||
|
# Mock Decision Tree
|
||||||
|
```
|
||||||
|
Is it used in 20+ test files?
|
||||||
|
├─ YES → Use Global Mock
|
||||||
|
│ ├─ react-router-dom
|
||||||
|
│ ├─ react-query
|
||||||
|
│ ├─ antd components
|
||||||
|
│ └─ browser APIs
|
||||||
|
│
|
||||||
|
└─ NO → Is it business logic?
|
||||||
|
├─ YES → Use Local Mock
|
||||||
|
│ ├─ API endpoints
|
||||||
|
│ ├─ Custom hooks
|
||||||
|
│ └─ Domain components
|
||||||
|
│
|
||||||
|
└─ NO → Is it test-specific?
|
||||||
|
├─ YES → Use Local Mock
|
||||||
|
│ ├─ Error scenarios
|
||||||
|
│ ├─ Loading states
|
||||||
|
│ └─ Specific data
|
||||||
|
│
|
||||||
|
└─ NO → Consider Global Mock
|
||||||
|
└─ If it becomes frequently used
|
||||||
|
```
|
||||||
|
|
||||||
|
# Common Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
❌ **Don't mock global dependencies locally:**
|
||||||
|
```js
|
||||||
|
// BAD - This is already globally mocked
|
||||||
|
jest.mock('react-router-dom', () => ({ ... }));
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Don't create global mocks for test-specific data:**
|
||||||
|
```js
|
||||||
|
// BAD - This should be local
|
||||||
|
jest.mock('../api/tracesService', () => ({
|
||||||
|
getTraces: jest.fn(() => specificTestData)
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Do use global mocks for infrastructure:**
|
||||||
|
```js
|
||||||
|
// GOOD - Use global mock
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Do create local mocks for business logic:**
|
||||||
|
```js
|
||||||
|
// GOOD - Local mock for specific test needs
|
||||||
|
jest.mock('../api/tracesService', () => ({
|
||||||
|
getTraces: jest.fn(() => mockTracesData)
|
||||||
|
}));
|
||||||
|
```
|
||||||
51
frontend/__mocks__/uplotMock.ts
Normal file
51
frontend/__mocks__/uplotMock.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
|
||||||
|
// Mock for uplot library used in tests
|
||||||
|
export interface MockUPlotInstance {
|
||||||
|
setData: jest.Mock;
|
||||||
|
setSize: jest.Mock;
|
||||||
|
destroy: jest.Mock;
|
||||||
|
redraw: jest.Mock;
|
||||||
|
setSeries: jest.Mock;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MockUPlotPaths {
|
||||||
|
spline: jest.Mock;
|
||||||
|
bars: jest.Mock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mock instance methods
|
||||||
|
const createMockUPlotInstance = (): MockUPlotInstance => ({
|
||||||
|
setData: jest.fn(),
|
||||||
|
setSize: jest.fn(),
|
||||||
|
destroy: jest.fn(),
|
||||||
|
redraw: jest.fn(),
|
||||||
|
setSeries: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create mock paths
|
||||||
|
const mockPaths: MockUPlotPaths = {
|
||||||
|
spline: jest.fn(),
|
||||||
|
bars: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock static methods
|
||||||
|
const mockTzDate = jest.fn(
|
||||||
|
(date: Date, _timezone: string) => new Date(date.getTime()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock uPlot constructor - this needs to be a proper constructor function
|
||||||
|
function MockUPlot(
|
||||||
|
_options: unknown,
|
||||||
|
_data: unknown,
|
||||||
|
_target: HTMLElement,
|
||||||
|
): MockUPlotInstance {
|
||||||
|
return createMockUPlotInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add static methods to the constructor
|
||||||
|
MockUPlot.tzDate = mockTzDate;
|
||||||
|
MockUPlot.paths = mockPaths;
|
||||||
|
|
||||||
|
// Export the constructor as default
|
||||||
|
export default MockUPlot;
|
||||||
29
frontend/__mocks__/useSafeNavigate.ts
Normal file
29
frontend/__mocks__/useSafeNavigate.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Mock for useSafeNavigate hook to avoid React Router version conflicts in tests
|
||||||
|
interface SafeNavigateOptions {
|
||||||
|
replace?: boolean;
|
||||||
|
state?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SafeNavigateTo {
|
||||||
|
pathname?: string;
|
||||||
|
search?: string;
|
||||||
|
hash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SafeNavigateToType = string | SafeNavigateTo;
|
||||||
|
|
||||||
|
interface UseSafeNavigateReturn {
|
||||||
|
safeNavigate: jest.MockedFunction<
|
||||||
|
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSafeNavigate = (): UseSafeNavigateReturn => ({
|
||||||
|
safeNavigate: jest.fn(
|
||||||
|
(to: SafeNavigateToType, options?: SafeNavigateOptions) => {
|
||||||
|
console.log(`Mock safeNavigate called with:`, to, options);
|
||||||
|
},
|
||||||
|
) as jest.MockedFunction<
|
||||||
|
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||||
|
>,
|
||||||
|
});
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import type { Config } from '@jest/types';
|
import type { Config } from '@jest/types';
|
||||||
|
|
||||||
|
const USE_SAFE_NAVIGATE_MOCK_PATH = '<rootDir>/__mocks__/useSafeNavigate.ts';
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
coverageDirectory: 'coverage',
|
coverageDirectory: 'coverage',
|
||||||
@ -10,6 +12,10 @@ const config: Config.InitialOptions = {
|
|||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||||
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
|
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
|
||||||
|
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
|
||||||
|
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||||
|
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||||
|
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
extensionsToTreatAsEsm: ['.ts'],
|
extensionsToTreatAsEsm: ['.ts'],
|
||||||
|
|||||||
@ -19,20 +19,6 @@ beforeAll(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('react-dnd', () => ({
|
jest.mock('react-dnd', () => ({
|
||||||
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||||
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
import APIError from 'types/api/error';
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
import ErrorModal from './ErrorModal';
|
import ErrorModal from './ErrorModal';
|
||||||
@ -56,9 +56,8 @@ describe('ErrorModal Component', () => {
|
|||||||
|
|
||||||
// Click the close button
|
// Click the close button
|
||||||
const closeButton = screen.getByTestId('close-button');
|
const closeButton = screen.getByTestId('close-button');
|
||||||
act(() => {
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
fireEvent.click(closeButton);
|
await user.click(closeButton);
|
||||||
});
|
|
||||||
|
|
||||||
// Check if onClose was called
|
// Check if onClose was called
|
||||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||||
@ -149,9 +148,8 @@ it('should open the modal when the trigger component is clicked', async () => {
|
|||||||
|
|
||||||
// Click the trigger component
|
// Click the trigger component
|
||||||
const triggerButton = screen.getByText('Open Error Modal');
|
const triggerButton = screen.getByText('Open Error Modal');
|
||||||
act(() => {
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
fireEvent.click(triggerButton);
|
await user.click(triggerButton);
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the modal is displayed
|
// Check if the modal is displayed
|
||||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||||
@ -170,18 +168,15 @@ it('should close the modal when the onCancel event is triggered', async () => {
|
|||||||
|
|
||||||
// Click the trigger component
|
// Click the trigger component
|
||||||
const triggerButton = screen.getByText('error');
|
const triggerButton = screen.getByText('error');
|
||||||
act(() => {
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
fireEvent.click(triggerButton);
|
await user.click(triggerButton);
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger the onCancel event
|
// Trigger the onCancel event
|
||||||
act(() => {
|
await user.click(screen.getByTestId('close-button'));
|
||||||
fireEvent.click(screen.getByTestId('close-button'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the modal is closed
|
// Check if the modal is closed
|
||||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@ -1,15 +1,6 @@
|
|||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
import {
|
|
||||||
act,
|
|
||||||
cleanup,
|
|
||||||
fireEvent,
|
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
waitFor,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import { ENVIRONMENT } from 'constants/env';
|
import { ENVIRONMENT } from 'constants/env';
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import {
|
import {
|
||||||
otherFiltersResponse,
|
otherFiltersResponse,
|
||||||
@ -18,8 +9,7 @@ import {
|
|||||||
} from 'mocks-server/__mockdata__/customQuickFilters';
|
} from 'mocks-server/__mockdata__/customQuickFilters';
|
||||||
import { server } from 'mocks-server/server';
|
import { server } from 'mocks-server/server';
|
||||||
import { rest } from 'msw';
|
import { rest } from 'msw';
|
||||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
import { USER_ROLES } from 'types/roles';
|
|
||||||
|
|
||||||
import QuickFilters from '../QuickFilters';
|
import QuickFilters from '../QuickFilters';
|
||||||
import { IQuickFiltersConfig, QuickFiltersSource, SignalType } from '../types';
|
import { IQuickFiltersConfig, QuickFiltersSource, SignalType } from '../types';
|
||||||
@ -29,21 +19,6 @@ jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
|||||||
useQueryBuilder: jest.fn(),
|
useQueryBuilder: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
|
||||||
jest.mock('react-router-dom', () => ({
|
|
||||||
...jest.requireActual('react-router-dom'),
|
|
||||||
useLocation: (): { pathname: string } => ({
|
|
||||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const userRole = USER_ROLES.ADMIN;
|
|
||||||
|
|
||||||
// mock useAppContext
|
|
||||||
jest.mock('providers/App/App', () => ({
|
|
||||||
useAppContext: jest.fn(() => ({ user: { role: userRole } })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const handleFilterVisibilityChange = jest.fn();
|
const handleFilterVisibilityChange = jest.fn();
|
||||||
const redirectWithQueryBuilderData = jest.fn();
|
const redirectWithQueryBuilderData = jest.fn();
|
||||||
const putHandler = jest.fn();
|
const putHandler = jest.fn();
|
||||||
@ -78,11 +53,10 @@ const setupServer = (): void => {
|
|||||||
putHandler(await req.json());
|
putHandler(await req.json());
|
||||||
return res(ctx.status(200), ctx.json({}));
|
return res(ctx.status(200), ctx.json({}));
|
||||||
}),
|
}),
|
||||||
|
rest.get(quickFiltersAttributeValuesURL, (_req, res, ctx) =>
|
||||||
rest.get(quickFiltersAttributeValuesURL, (req, res, ctx) =>
|
|
||||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||||
),
|
),
|
||||||
rest.get(fieldsValuesURL, (req, res, ctx) =>
|
rest.get(fieldsValuesURL, (_req, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -96,14 +70,12 @@ function TestQuickFilters({
|
|||||||
config?: IQuickFiltersConfig[];
|
config?: IQuickFiltersConfig[];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<MockQueryClientProvider>
|
|
||||||
<QuickFilters
|
<QuickFilters
|
||||||
source={QuickFiltersSource.EXCEPTIONS}
|
source={QuickFiltersSource.EXCEPTIONS}
|
||||||
config={config}
|
config={config}
|
||||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
signal={signal}
|
signal={signal}
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,11 +90,11 @@ beforeAll(() => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
server.resetHandlers();
|
server.resetHandlers();
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
server.close();
|
server.close();
|
||||||
cleanup();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -151,9 +123,13 @@ describe('Quick Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add filter data to query when checkbox is clicked', async () => {
|
it('should add filter data to query when checkbox is clicked', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
render(<TestQuickFilters />);
|
render(<TestQuickFilters />);
|
||||||
const checkbox = screen.getByText('mq-kafka');
|
|
||||||
fireEvent.click(checkbox);
|
// Prefer role if possible; if label text isn’t wired to input, clicking the label text is OK
|
||||||
|
const target = await screen.findByText('mq-kafka');
|
||||||
|
await user.click(target);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
|
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
|
||||||
@ -182,16 +158,20 @@ describe('Quick Filters', () => {
|
|||||||
|
|
||||||
describe('Quick Filters with custom filters', () => {
|
describe('Quick Filters with custom filters', () => {
|
||||||
it('loads the custom filters correctly', async () => {
|
it('loads the custom filters correctly', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
render(<TestQuickFilters signal={SIGNAL} />);
|
render(<TestQuickFilters signal={SIGNAL} />);
|
||||||
|
|
||||||
expect(screen.getByText('Filters for')).toBeInTheDocument();
|
expect(screen.getByText('Filters for')).toBeInTheDocument();
|
||||||
expect(screen.getByText(QUERY_NAME)).toBeInTheDocument();
|
expect(screen.getByText(QUERY_NAME)).toBeInTheDocument();
|
||||||
|
|
||||||
await screen.findByText(FILTER_SERVICE_NAME);
|
await screen.findByText(FILTER_SERVICE_NAME);
|
||||||
const allByText = await screen.findAllByText('otel-demo');
|
const allByText = await screen.findAllByText('otel-demo');
|
||||||
// since 2 filter collapse are open, there are 2 filter items visible
|
|
||||||
expect(allByText).toHaveLength(2);
|
expect(allByText).toHaveLength(2);
|
||||||
|
|
||||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||||
fireEvent.click(icon);
|
const settingsButton = icon.closest('button') ?? icon;
|
||||||
|
await user.click(settingsButton);
|
||||||
|
|
||||||
expect(await screen.findByText('Edit quick filters')).toBeInTheDocument();
|
expect(await screen.findByText('Edit quick filters')).toBeInTheDocument();
|
||||||
|
|
||||||
@ -207,16 +187,19 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('adds a filter from OTHER FILTERS to ADDED FILTERS when clicked', async () => {
|
it('adds a filter from OTHER FILTERS to ADDED FILTERS when clicked', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
render(<TestQuickFilters signal={SIGNAL} />);
|
render(<TestQuickFilters signal={SIGNAL} />);
|
||||||
await screen.findByText(FILTER_SERVICE_NAME);
|
await screen.findByText(FILTER_SERVICE_NAME);
|
||||||
|
|
||||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||||
fireEvent.click(icon);
|
const settingsButton = icon.closest('button') ?? icon;
|
||||||
|
await user.click(settingsButton);
|
||||||
|
|
||||||
const otherFilterItem = await screen.findByText(FILTER_K8S_DEPLOYMENT_NAME);
|
const otherFilterItem = await screen.findByText(FILTER_K8S_DEPLOYMENT_NAME);
|
||||||
const addButton = otherFilterItem.parentElement?.querySelector('button');
|
const addButton = otherFilterItem.parentElement?.querySelector('button');
|
||||||
expect(addButton).not.toBeNull();
|
expect(addButton).not.toBeNull();
|
||||||
fireEvent.click(addButton as HTMLButtonElement);
|
await user.click(addButton as HTMLButtonElement);
|
||||||
|
|
||||||
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -225,17 +208,21 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('removes a filter from ADDED FILTERS and moves it to OTHER FILTERS', async () => {
|
it('removes a filter from ADDED FILTERS and moves it to OTHER FILTERS', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
render(<TestQuickFilters signal={SIGNAL} />);
|
render(<TestQuickFilters signal={SIGNAL} />);
|
||||||
await screen.findByText(FILTER_SERVICE_NAME);
|
await screen.findByText(FILTER_SERVICE_NAME);
|
||||||
|
|
||||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||||
fireEvent.click(icon);
|
const settingsButton = icon.closest('button') ?? icon;
|
||||||
|
await user.click(settingsButton);
|
||||||
|
|
||||||
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
||||||
const target = await screen.findByText(FILTER_OS_DESCRIPTION);
|
const target = await screen.findByText(FILTER_OS_DESCRIPTION);
|
||||||
const removeBtn = target.parentElement?.querySelector('button');
|
const removeBtn = target.parentElement?.querySelector('button');
|
||||||
expect(removeBtn).not.toBeNull();
|
expect(removeBtn).not.toBeNull();
|
||||||
fireEvent.click(removeBtn as HTMLButtonElement);
|
|
||||||
|
await user.click(removeBtn as HTMLButtonElement);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(addedSection).not.toContainElement(
|
expect(addedSection).not.toContainElement(
|
||||||
@ -250,17 +237,20 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('restores original filter state on Discard', async () => {
|
it('restores original filter state on Discard', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
render(<TestQuickFilters signal={SIGNAL} />);
|
render(<TestQuickFilters signal={SIGNAL} />);
|
||||||
await screen.findByText(FILTER_SERVICE_NAME);
|
await screen.findByText(FILTER_SERVICE_NAME);
|
||||||
|
|
||||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||||
fireEvent.click(icon);
|
const settingsButton = icon.closest('button') ?? icon;
|
||||||
|
await user.click(settingsButton);
|
||||||
|
|
||||||
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
||||||
const target = await screen.findByText(FILTER_OS_DESCRIPTION);
|
const target = await screen.findByText(FILTER_OS_DESCRIPTION);
|
||||||
const removeBtn = target.parentElement?.querySelector('button');
|
const removeBtn = target.parentElement?.querySelector('button');
|
||||||
expect(removeBtn).not.toBeNull();
|
expect(removeBtn).not.toBeNull();
|
||||||
fireEvent.click(removeBtn as HTMLButtonElement);
|
await user.click(removeBtn as HTMLButtonElement);
|
||||||
|
|
||||||
const otherSection = screen.getByText(OTHER_FILTERS_LABEL).parentElement!;
|
const otherSection = screen.getByText(OTHER_FILTERS_LABEL).parentElement!;
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@ -272,7 +262,7 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByText(DISCARD_TEXT));
|
await user.click(screen.getByRole('button', { name: DISCARD_TEXT }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(addedSection).toContainElement(
|
expect(addedSection).toContainElement(
|
||||||
@ -285,18 +275,21 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('saves the updated filters by calling PUT with correct payload', async () => {
|
it('saves the updated filters by calling PUT with correct payload', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
render(<TestQuickFilters signal={SIGNAL} />);
|
render(<TestQuickFilters signal={SIGNAL} />);
|
||||||
await screen.findByText(FILTER_SERVICE_NAME);
|
await screen.findByText(FILTER_SERVICE_NAME);
|
||||||
|
|
||||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||||
fireEvent.click(icon);
|
const settingsButton = icon.closest('button') ?? icon;
|
||||||
|
await user.click(settingsButton);
|
||||||
|
|
||||||
const target = await screen.findByText(FILTER_OS_DESCRIPTION);
|
const target = await screen.findByText(FILTER_OS_DESCRIPTION);
|
||||||
const removeBtn = target.parentElement?.querySelector('button');
|
const removeBtn = target.parentElement?.querySelector('button');
|
||||||
expect(removeBtn).not.toBeNull();
|
expect(removeBtn).not.toBeNull();
|
||||||
fireEvent.click(removeBtn as HTMLButtonElement);
|
await user.click(removeBtn as HTMLButtonElement);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText(SAVE_CHANGES_TEXT));
|
await user.click(screen.getByRole('button', { name: SAVE_CHANGES_TEXT }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(putHandler).toHaveBeenCalled();
|
expect(putHandler).toHaveBeenCalled();
|
||||||
@ -311,31 +304,36 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
expect(requestBody.signal).toBe(SIGNAL);
|
expect(requestBody.signal).toBe(SIGNAL);
|
||||||
});
|
});
|
||||||
|
|
||||||
// render duration filter
|
|
||||||
it('should render duration slider for duration_nono filter', async () => {
|
it('should render duration slider for duration_nono filter', async () => {
|
||||||
// Set up fake timers **before rendering**
|
// Use fake timers only in this test (for debounce), and wire them to userEvent
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
const user = userEvent.setup({
|
||||||
|
advanceTimers: (ms) => jest.advanceTimersByTime(ms),
|
||||||
|
pointerEventsCheck: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const { getByTestId } = render(<TestQuickFilters signal={SIGNAL} />);
|
const { getByTestId } = render(<TestQuickFilters signal={SIGNAL} />);
|
||||||
await screen.findByText(FILTER_SERVICE_NAME);
|
await screen.findByText(FILTER_SERVICE_NAME);
|
||||||
expect(screen.getByText('Duration')).toBeInTheDocument();
|
expect(screen.getByText('Duration')).toBeInTheDocument();
|
||||||
|
|
||||||
// click to open the duration filter
|
// Open the duration section (use role if it’s a button/collapse)
|
||||||
fireEvent.click(screen.getByText('Duration'));
|
await user.click(screen.getByText('Duration'));
|
||||||
|
|
||||||
const minDuration = getByTestId('min-input') as HTMLInputElement;
|
const minDuration = getByTestId('min-input') as HTMLInputElement;
|
||||||
const maxDuration = getByTestId('max-input') as HTMLInputElement;
|
const maxDuration = getByTestId('max-input') as HTMLInputElement;
|
||||||
|
|
||||||
expect(minDuration).toHaveValue(null);
|
expect(minDuration).toHaveValue(null);
|
||||||
expect(minDuration).toHaveProperty('placeholder', '0');
|
expect(minDuration).toHaveProperty('placeholder', '0');
|
||||||
expect(maxDuration).toHaveValue(null);
|
expect(maxDuration).toHaveValue(null);
|
||||||
expect(maxDuration).toHaveProperty('placeholder', '100000000');
|
expect(maxDuration).toHaveProperty('placeholder', '100000000');
|
||||||
|
|
||||||
await act(async () => {
|
// Type values and advance debounce
|
||||||
// set values
|
await user.clear(minDuration);
|
||||||
fireEvent.change(minDuration, { target: { value: '10000' } });
|
await user.type(minDuration, '10000');
|
||||||
fireEvent.change(maxDuration, { target: { value: '20000' } });
|
await user.clear(maxDuration);
|
||||||
|
await user.type(maxDuration, '20000');
|
||||||
jest.advanceTimersByTime(2000);
|
jest.advanceTimersByTime(2000);
|
||||||
});
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
|
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -363,6 +361,6 @@ describe('Quick Filters with custom filters', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.useRealTimers(); // Clean up
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,6 +22,8 @@ jest.mock('react-router-dom', () => ({
|
|||||||
|
|
||||||
describe('Alert Channels Settings List page', () => {
|
describe('Alert Channels Settings List page', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(new Date('2023-10-20'));
|
||||||
render(<AlertChannels />);
|
render(<AlertChannels />);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
|
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
|
||||||
@ -29,6 +31,7 @@ describe('Alert Channels Settings List page', () => {
|
|||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
describe('Should display the Alert Channels page properly', () => {
|
describe('Should display the Alert Channels page properly', () => {
|
||||||
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
|
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
|
||||||
|
|||||||
@ -28,6 +28,7 @@ jest.mock('react-router-dom', () => ({
|
|||||||
|
|
||||||
describe('Alert Channels Settings List page (Normal User)', () => {
|
describe('Alert Channels Settings List page (Normal User)', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
render(<AlertChannels />);
|
render(<AlertChannels />);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
|
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
|
||||||
@ -35,6 +36,7 @@ describe('Alert Channels Settings List page (Normal User)', () => {
|
|||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
describe('Should display the Alert Channels page properly', () => {
|
describe('Should display the Alert Channels page properly', () => {
|
||||||
it('Should check if "The alerts will be sent to all the configured channels." is visible ', async () => {
|
it('Should check if "The alerts will be sent to all the configured channels." is visible ', async () => {
|
||||||
|
|||||||
@ -9,22 +9,6 @@ import { getFormattedDate } from 'utils/timeUtils';
|
|||||||
|
|
||||||
import BillingContainer from './BillingContainer';
|
import BillingContainer from './BillingContainer';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
window.ResizeObserver =
|
window.ResizeObserver =
|
||||||
window.ResizeObserver ||
|
window.ResizeObserver ||
|
||||||
jest.fn().mockImplementation(() => ({
|
jest.fn().mockImplementation(() => ({
|
||||||
@ -67,45 +51,63 @@ describe('BillingContainer', () => {
|
|||||||
expect(currentBill).toBeInTheDocument();
|
expect(currentBill).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Trial scenarios', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(new Date('2023-10-20'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
test('OnTrail', async () => {
|
test('OnTrail', async () => {
|
||||||
await act(async () => {
|
// Pin "now" so trial end (20 Oct 2023) is tomorrow => "1 days_remaining"
|
||||||
render(<BillingContainer />, undefined, undefined, {
|
|
||||||
trialInfo: licensesSuccessResponse.data,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const freeTrailText = await screen.findByText('Free Trial');
|
render(
|
||||||
expect(freeTrailText).toBeInTheDocument();
|
<BillingContainer />,
|
||||||
|
{},
|
||||||
const currentBill = await screen.findByText('billing');
|
{ appContextOverrides: { trialInfo: licensesSuccessResponse.data } },
|
||||||
expect(currentBill).toBeInTheDocument();
|
|
||||||
|
|
||||||
const dollar0 = await screen.findByText(/\$0/i);
|
|
||||||
expect(dollar0).toBeInTheDocument();
|
|
||||||
const onTrail = await screen.findByText(
|
|
||||||
/You are in free trial period. Your free trial will end on 20 Oct 2023/i,
|
|
||||||
);
|
);
|
||||||
expect(onTrail).toBeInTheDocument();
|
|
||||||
|
|
||||||
const numberOfDayRemaining = await screen.findByText(/1 days_remaining/i);
|
// If the component schedules any setTimeout on mount, flush them:
|
||||||
expect(numberOfDayRemaining).toBeInTheDocument();
|
jest.runOnlyPendingTimers();
|
||||||
const upgradeButton = await screen.findAllByRole('button', {
|
|
||||||
|
expect(await screen.findByText('Free Trial')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('billing')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText(/\$0/i)).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText(
|
||||||
|
/You are in free trial period. Your free trial will end on 20 Oct 2023/i,
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(await screen.findByText(/1 days_remaining/i)).toBeInTheDocument();
|
||||||
|
|
||||||
|
const upgradeButtons = await screen.findAllByRole('button', {
|
||||||
name: /upgrade_plan/i,
|
name: /upgrade_plan/i,
|
||||||
});
|
});
|
||||||
expect(upgradeButton[1]).toBeInTheDocument();
|
expect(upgradeButtons).toHaveLength(2);
|
||||||
expect(upgradeButton.length).toBe(2);
|
expect(upgradeButtons[1]).toBeInTheDocument();
|
||||||
const checkPaidPlan = await screen.findByText(/checkout_plans/i);
|
|
||||||
expect(checkPaidPlan).toBeInTheDocument();
|
|
||||||
|
|
||||||
const link = await screen.findByRole('link', { name: /here/i });
|
expect(await screen.findByText(/checkout_plans/i)).toBeInTheDocument();
|
||||||
expect(link).toBeInTheDocument();
|
expect(
|
||||||
|
await screen.findByRole('link', { name: /here/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('OnTrail but trialConvertedToSubscription', async () => {
|
test('OnTrail but trialConvertedToSubscription', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(<BillingContainer />, undefined, undefined, {
|
render(
|
||||||
|
<BillingContainer />,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
appContextOverrides: {
|
||||||
trialInfo: trialConvertedToSubscriptionResponse.data,
|
trialInfo: trialConvertedToSubscriptionResponse.data,
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentBill = await screen.findByText('billing');
|
const currentBill = await screen.findByText('billing');
|
||||||
@ -134,11 +136,18 @@ describe('BillingContainer', () => {
|
|||||||
);
|
);
|
||||||
expect(dayRemainingInBillingPeriod).toBeInTheDocument();
|
expect(dayRemainingInBillingPeriod).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('Not on ontrail', async () => {
|
test('Not on ontrail', async () => {
|
||||||
const { findByText } = render(<BillingContainer />, undefined, undefined, {
|
const { findByText } = render(
|
||||||
|
<BillingContainer />,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
appContextOverrides: {
|
||||||
trialInfo: notOfTrailResponse.data,
|
trialInfo: notOfTrailResponse.data,
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const billingPeriodText = `Your current billing period is from ${getFormattedDate(
|
const billingPeriodText = `Your current billing period is from ${getFormattedDate(
|
||||||
billingSuccessResponse.data.billingPeriodStart,
|
billingSuccessResponse.data.billingPeriodStart,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import * as usePrefillAlertConditions from 'container/FormAlertRules/usePrefillAlertConditions';
|
import * as usePrefillAlertConditions from 'container/FormAlertRules/usePrefillAlertConditions';
|
||||||
import CreateAlertPage from 'pages/CreateAlert';
|
import CreateAlertPage from 'pages/CreateAlert';
|
||||||
import { MemoryRouter, Route } from 'react-router-dom';
|
|
||||||
import { act, fireEvent, render } from 'tests/test-utils';
|
import { act, fireEvent, render } from 'tests/test-utils';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
|
|
||||||
@ -14,20 +13,6 @@ jest.mock('react-router-dom', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('hooks/useSafeNavigate', () => ({
|
jest.mock('hooks/useSafeNavigate', () => ({
|
||||||
useSafeNavigate: (): any => ({
|
useSafeNavigate: (): any => ({
|
||||||
safeNavigate: jest.fn(),
|
safeNavigate: jest.fn(),
|
||||||
@ -84,11 +69,11 @@ describe('Alert rule documentation redirection', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
act(() => {
|
act(() => {
|
||||||
renderResult = render(
|
renderResult = render(
|
||||||
<MemoryRouter initialEntries={['/alerts/new']}>
|
<CreateAlertPage />,
|
||||||
<Route path={ROUTES.ALERTS_NEW}>
|
{},
|
||||||
<CreateAlertPage />
|
{
|
||||||
</Route>
|
initialRoute: ROUTES.ALERTS_NEW,
|
||||||
</MemoryRouter>,
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,20 +15,6 @@ jest.mock('react-router-dom', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
window.ResizeObserver =
|
window.ResizeObserver =
|
||||||
window.ResizeObserver ||
|
window.ResizeObserver ||
|
||||||
jest.fn().mockImplementation(() => ({
|
jest.fn().mockImplementation(() => ({
|
||||||
|
|||||||
@ -30,20 +30,6 @@ jest.mock('hooks/useSafeNavigate', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mock data
|
// Mock data
|
||||||
const mockProps: WidgetGraphComponentProps = {
|
const mockProps: WidgetGraphComponentProps = {
|
||||||
widget: {
|
widget: {
|
||||||
|
|||||||
@ -33,19 +33,6 @@ jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
|||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
jest.mock('react-redux', () => ({
|
jest.mock('react-redux', () => ({
|
||||||
...jest.requireActual('react-redux'),
|
...jest.requireActual('react-redux'),
|
||||||
useSelector: (): any => ({
|
useSelector: (): any => ({
|
||||||
|
|||||||
@ -3,20 +3,6 @@ import { render, screen } from '@testing-library/react';
|
|||||||
|
|
||||||
import HostsListTable from '../HostsListTable';
|
import HostsListTable from '../HostsListTable';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const EMPTY_STATE_CONTAINER_CLASS = '.hosts-empty-state-container';
|
const EMPTY_STATE_CONTAINER_CLASS = '.hosts-empty-state-container';
|
||||||
|
|
||||||
describe('HostsListTable', () => {
|
describe('HostsListTable', () => {
|
||||||
|
|||||||
@ -4,20 +4,6 @@ import { formatDataForTable, GetHostsQuickFiltersConfig } from '../utils';
|
|||||||
|
|
||||||
const PROGRESS_BAR_CLASS = '.progress-bar';
|
const PROGRESS_BAR_CLASS = '.progress-bar';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('InfraMonitoringHosts utils', () => {
|
describe('InfraMonitoringHosts utils', () => {
|
||||||
describe('formatDataForTable', () => {
|
describe('formatDataForTable', () => {
|
||||||
it('should format host data correctly', () => {
|
it('should format host data correctly', () => {
|
||||||
|
|||||||
@ -44,20 +44,6 @@ const verifyEntityLogsPayload = ({
|
|||||||
return queryData;
|
return queryData;
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'components/OverlayScrollbar/OverlayScrollbar',
|
'components/OverlayScrollbar/OverlayScrollbar',
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@ -4,14 +4,8 @@ import setupCommonMocks from '../../commonMocks';
|
|||||||
|
|
||||||
setupCommonMocks();
|
setupCommonMocks();
|
||||||
|
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
|
||||||
import JobDetails from 'container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails';
|
import JobDetails from 'container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
|
||||||
|
|
||||||
describe('JobDetails', () => {
|
describe('JobDetails', () => {
|
||||||
const mockJob = {
|
const mockJob = {
|
||||||
@ -24,13 +18,7 @@ describe('JobDetails', () => {
|
|||||||
|
|
||||||
it('should render modal with relevant metadata', () => {
|
it('should render modal with relevant metadata', () => {
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||||
<Provider store={store}>
|
|
||||||
<MemoryRouter>
|
|
||||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
|
||||||
</MemoryRouter>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const jobNameElements = screen.getAllByText('test-job');
|
const jobNameElements = screen.getAllByText('test-job');
|
||||||
@ -44,13 +32,7 @@ describe('JobDetails', () => {
|
|||||||
|
|
||||||
it('should render modal with 4 tabs', () => {
|
it('should render modal with 4 tabs', () => {
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||||
<Provider store={store}>
|
|
||||||
<MemoryRouter>
|
|
||||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
|
||||||
</MemoryRouter>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const metricsTab = screen.getByText('Metrics');
|
const metricsTab = screen.getByText('Metrics');
|
||||||
@ -68,13 +50,7 @@ describe('JobDetails', () => {
|
|||||||
|
|
||||||
it('default tab should be metrics', () => {
|
it('default tab should be metrics', () => {
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||||
<Provider store={store}>
|
|
||||||
<MemoryRouter>
|
|
||||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
|
||||||
</MemoryRouter>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||||
@ -83,13 +59,7 @@ describe('JobDetails', () => {
|
|||||||
|
|
||||||
it('should switch to events tab when events tab is clicked', () => {
|
it('should switch to events tab when events tab is clicked', () => {
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||||
<Provider store={store}>
|
|
||||||
<MemoryRouter>
|
|
||||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
|
||||||
</MemoryRouter>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||||
@ -100,13 +70,7 @@ describe('JobDetails', () => {
|
|||||||
|
|
||||||
it('should close modal when close button is clicked', () => {
|
it('should close modal when close button is clicked', () => {
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||||
<Provider store={store}>
|
|
||||||
<MemoryRouter>
|
|
||||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
|
||||||
</MemoryRouter>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||||
|
|||||||
@ -56,19 +56,6 @@ const setupCommonMocks = (): void => {
|
|||||||
useNavigationType: (): any => 'PUSH',
|
useNavigationType: (): any => 'PUSH',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('hooks/useUrlQuery', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: jest.fn(() => ({
|
|
||||||
set: jest.fn(),
|
|
||||||
delete: jest.fn(),
|
|
||||||
get: jest.fn(),
|
|
||||||
has: jest.fn(),
|
|
||||||
entries: jest.fn(() => []),
|
|
||||||
append: jest.fn(),
|
|
||||||
toString: jest.fn(() => ''),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('lib/getMinMax', () => ({
|
jest.mock('lib/getMinMax', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: jest.fn().mockImplementation(() => ({
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
|||||||
@ -32,20 +32,6 @@ import {
|
|||||||
// Mock the useContextLogData hook
|
// Mock the useContextLogData hook
|
||||||
const mockHandleRunQuery = jest.fn();
|
const mockHandleRunQuery = jest.fn();
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('container/OptionsMenu', () => ({
|
jest.mock('container/OptionsMenu', () => ({
|
||||||
useOptionsMenu: (): any => ({
|
useOptionsMenu: (): any => ({
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@ -73,20 +73,6 @@ jest.mock('hooks/useSafeNavigate', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('hooks/queryBuilder/useGetExplorerQueryRange', () => ({
|
jest.mock('hooks/queryBuilder/useGetExplorerQueryRange', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
useGetExplorerQueryRange: jest.fn(),
|
useGetExplorerQueryRange: jest.fn(),
|
||||||
|
|||||||
@ -7,18 +7,16 @@ import { logsresponse } from 'mocks-server/__mockdata__/query_range';
|
|||||||
import { server } from 'mocks-server/server';
|
import { server } from 'mocks-server/server';
|
||||||
import { rest } from 'msw';
|
import { rest } from 'msw';
|
||||||
import LogsExplorer from 'pages/LogsExplorer';
|
import LogsExplorer from 'pages/LogsExplorer';
|
||||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
|
||||||
import { MemoryRouter } from 'react-router-dom-v5-compat';
|
|
||||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||||
import i18n from 'ReactI18';
|
|
||||||
import {
|
import {
|
||||||
act,
|
act,
|
||||||
|
AllTheProviders,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
RenderResult,
|
RenderResult,
|
||||||
screen,
|
screen,
|
||||||
|
userEvent,
|
||||||
waitFor,
|
waitFor,
|
||||||
} from 'tests/test-utils';
|
} from 'tests/test-utils';
|
||||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -91,20 +89,6 @@ getStateSpy.mockImplementation(() => {
|
|||||||
return originalState;
|
return originalState;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...jest.requireActual('react-router-dom'),
|
||||||
useLocation: (): { search: string; pathname: string } => ({
|
useLocation: (): { search: string; pathname: string } => ({
|
||||||
@ -277,9 +261,7 @@ describe.skip('LogsExplorerViews Pagination', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
renderResult = render(
|
renderResult = render(
|
||||||
<VirtuosoMockContext.Provider value={{ viewportHeight, itemHeight }}>
|
<VirtuosoMockContext.Provider value={{ viewportHeight, itemHeight }}>
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<LogsExplorer />
|
<LogsExplorer />
|
||||||
</I18nextProvider>
|
|
||||||
</VirtuosoMockContext.Provider>,
|
</VirtuosoMockContext.Provider>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -453,13 +435,14 @@ function LogsExplorerWithMockContext({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MemoryRouter>
|
<AllTheProviders
|
||||||
<QueryBuilderContext.Provider value={contextValue as any}>
|
queryBuilderOverrides={contextValue as any}
|
||||||
|
initialRoute="/logs"
|
||||||
|
>
|
||||||
<VirtuosoMockContext.Provider value={virtuosoContextValue}>
|
<VirtuosoMockContext.Provider value={virtuosoContextValue}>
|
||||||
<LogsExplorer />
|
<LogsExplorer />
|
||||||
</VirtuosoMockContext.Provider>
|
</VirtuosoMockContext.Provider>
|
||||||
</QueryBuilderContext.Provider>
|
</AllTheProviders>
|
||||||
</MemoryRouter>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,13 +519,12 @@ describe('Logs Explorer -> stage and run query', () => {
|
|||||||
const initialEnd = initialPayload.end;
|
const initialEnd = initialPayload.end;
|
||||||
|
|
||||||
// Click the Stage & Run Query button
|
// Click the Stage & Run Query button
|
||||||
await act(async () => {
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
fireEvent.click(
|
await user.click(
|
||||||
screen.getByRole('button', {
|
screen.getByRole('button', {
|
||||||
name: /stage & run query/i,
|
name: /stage & run query/i,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for additional API calls to be made after clicking Stage & Run Query
|
// Wait for additional API calls to be made after clicking Stage & Run Query
|
||||||
await waitFor(
|
await waitFor(
|
||||||
|
|||||||
@ -33,20 +33,6 @@ const lodsQueryServerRequest = (): void =>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// mocking the graph components in this test as this should be handled separately
|
// mocking the graph components in this test as this should be handled separately
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'container/TimeSeriesView/TimeSeriesView',
|
'container/TimeSeriesView/TimeSeriesView',
|
||||||
|
|||||||
@ -18,10 +18,6 @@ const MOCK_SEARCH_PARAMS =
|
|||||||
'?graphType=list&widgetId=36a7b342-c642-4b92-abe4-cb833a244786&compositeQuery=%7B%22id%22%3A%22b325ac88-5e75-4117-a38c-1a2a7caf8115%22%2C%22builder%22%3A%7B%22queryData%22%3A%5B%7B%22dataSource%22%3A%22logs%22%2C%22queryName%22%3A%22A%22%2C%22aggregateOperator%22%3A%22noop%22%2C%22aggregateAttribute%22%3A%7B%22id%22%3A%22------%22%2C%22dataType%22%3A%22%22%2C%22key%22%3A%22%22%2C%22isColumn%22%3Afalse%2C%22type%22%3A%22%22%2C%22isJSON%22%3Afalse%7D%2C%22timeAggregation%22%3A%22rate%22%2C%22spaceAggregation%22%3A%22sum%22%2C%22functions%22%3A%5B%5D%2C%22filters%22%3A%7B%22items%22%3A%5B%5D%2C%22op%22%3A%22AND%22%7D%2C%22expression%22%3A%22A%22%2C%22disabled%22%3Afalse%2C%22stepInterval%22%3A60%2C%22having%22%3A%5B%5D%2C%22limit%22%3Anull%2C%22orderBy%22%3A%5B%7B%22columnName%22%3A%22timestamp%22%2C%22order%22%3A%22desc%22%7D%5D%2C%22groupBy%22%3A%5B%5D%2C%22legend%22%3A%22%22%2C%22reduceTo%22%3A%22avg%22%2C%22offset%22%3A0%2C%22pageSize%22%3A100%7D%5D%2C%22queryFormulas%22%3A%5B%5D%7D%2C%22clickhouse_sql%22%3A%5B%7B%22name%22%3A%22A%22%2C%22legend%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22query%22%3A%22%22%7D%5D%2C%22promql%22%3A%5B%7B%22name%22%3A%22A%22%2C%22query%22%3A%22%22%2C%22legend%22%3A%22%22%2C%22disabled%22%3Afalse%7D%5D%2C%22queryType%22%3A%22builder%22%7D&relativeTime=30m&options=%7B%22selectColumns%22%3A%5B%5D%2C%22maxLines%22%3A2%2C%22format%22%3A%22list%22%2C%22fontSize%22%3A%22small%22%7D';
|
'?graphType=list&widgetId=36a7b342-c642-4b92-abe4-cb833a244786&compositeQuery=%7B%22id%22%3A%22b325ac88-5e75-4117-a38c-1a2a7caf8115%22%2C%22builder%22%3A%7B%22queryData%22%3A%5B%7B%22dataSource%22%3A%22logs%22%2C%22queryName%22%3A%22A%22%2C%22aggregateOperator%22%3A%22noop%22%2C%22aggregateAttribute%22%3A%7B%22id%22%3A%22------%22%2C%22dataType%22%3A%22%22%2C%22key%22%3A%22%22%2C%22isColumn%22%3Afalse%2C%22type%22%3A%22%22%2C%22isJSON%22%3Afalse%7D%2C%22timeAggregation%22%3A%22rate%22%2C%22spaceAggregation%22%3A%22sum%22%2C%22functions%22%3A%5B%5D%2C%22filters%22%3A%7B%22items%22%3A%5B%5D%2C%22op%22%3A%22AND%22%7D%2C%22expression%22%3A%22A%22%2C%22disabled%22%3Afalse%2C%22stepInterval%22%3A60%2C%22having%22%3A%5B%5D%2C%22limit%22%3Anull%2C%22orderBy%22%3A%5B%7B%22columnName%22%3A%22timestamp%22%2C%22order%22%3A%22desc%22%7D%5D%2C%22groupBy%22%3A%5B%5D%2C%22legend%22%3A%22%22%2C%22reduceTo%22%3A%22avg%22%2C%22offset%22%3A0%2C%22pageSize%22%3A100%7D%5D%2C%22queryFormulas%22%3A%5B%5D%7D%2C%22clickhouse_sql%22%3A%5B%7B%22name%22%3A%22A%22%2C%22legend%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22query%22%3A%22%22%7D%5D%2C%22promql%22%3A%5B%7B%22name%22%3A%22A%22%2C%22query%22%3A%22%22%2C%22legend%22%3A%22%22%2C%22disabled%22%3Afalse%7D%5D%2C%22queryType%22%3A%22builder%22%7D&relativeTime=30m&options=%7B%22selectColumns%22%3A%5B%5D%2C%22maxLines%22%3A2%2C%22format%22%3A%22list%22%2C%22fontSize%22%3A%22small%22%7D';
|
||||||
|
|
||||||
// Mocks
|
// Mocks
|
||||||
jest.mock('uplot', () => ({
|
|
||||||
paths: { spline: jest.fn(), bars: jest.fn() },
|
|
||||||
default: jest.fn(() => ({ paths: { spline: jest.fn(), bars: jest.fn() } })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('components/OverlayScrollbar/OverlayScrollbar', () => ({
|
jest.mock('components/OverlayScrollbar/OverlayScrollbar', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
|||||||
@ -68,19 +68,6 @@ jest.mock('hooks/useNotifications', () => ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
jest.mock('react-redux', () => ({
|
jest.mock('react-redux', () => ({
|
||||||
...jest.requireActual('react-redux'),
|
...jest.requireActual('react-redux'),
|
||||||
useSelector: (): any => ({
|
useSelector: (): any => ({
|
||||||
|
|||||||
@ -15,12 +15,6 @@ import {
|
|||||||
TimeAggregationOptions,
|
TimeAggregationOptions,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
jest.mock('uplot', () =>
|
|
||||||
jest.fn().mockImplementation(() => ({
|
|
||||||
destroy: jest.fn(),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockResizeObserver = jest.fn();
|
const mockResizeObserver = jest.fn();
|
||||||
mockResizeObserver.mockImplementation(() => ({
|
mockResizeObserver.mockImplementation(() => ({
|
||||||
observe: (): void => undefined,
|
observe: (): void => undefined,
|
||||||
|
|||||||
@ -76,12 +76,6 @@ jest
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
jest.mock('uplot', () =>
|
|
||||||
jest.fn().mockImplementation(() => ({
|
|
||||||
destroy: jest.fn(),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...jest.requireActual('react-router-dom'),
|
||||||
useLocation: (): { pathname: string } => ({
|
useLocation: (): { pathname: string } => ({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import * as useSafeNavigate from 'hooks/useSafeNavigate';
|
|
||||||
|
|
||||||
import DashboardsAndAlertsPopover from '../DashboardsAndAlertsPopover';
|
import DashboardsAndAlertsPopover from '../DashboardsAndAlertsPopover';
|
||||||
|
|
||||||
@ -24,9 +23,11 @@ const mockAlerts = [mockAlert1, mockAlert2];
|
|||||||
const mockDashboards = [mockDashboard1, mockDashboard2];
|
const mockDashboards = [mockDashboard1, mockDashboard2];
|
||||||
|
|
||||||
const mockSafeNavigate = jest.fn();
|
const mockSafeNavigate = jest.fn();
|
||||||
jest.spyOn(useSafeNavigate, 'useSafeNavigate').mockReturnValue({
|
jest.mock('hooks/useSafeNavigate', () => ({
|
||||||
|
useSafeNavigate: (): any => ({
|
||||||
safeNavigate: mockSafeNavigate,
|
safeNavigate: mockSafeNavigate,
|
||||||
});
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
const mockSetQuery = jest.fn();
|
const mockSetQuery = jest.fn();
|
||||||
const mockUrlQuery = {
|
const mockUrlQuery = {
|
||||||
|
|||||||
@ -11,19 +11,6 @@ import store from 'store';
|
|||||||
import Summary from '../Summary';
|
import Summary from '../Summary';
|
||||||
import { TreemapViewType } from '../types';
|
import { TreemapViewType } from '../types';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
jest.mock('d3-hierarchy', () => ({
|
jest.mock('d3-hierarchy', () => ({
|
||||||
stratify: jest.fn().mockReturnValue({
|
stratify: jest.fn().mockReturnValue({
|
||||||
id: jest.fn().mockReturnValue({
|
id: jest.fn().mockReturnValue({
|
||||||
|
|||||||
@ -10,20 +10,6 @@ import store from 'store';
|
|||||||
import ChangeHistory from '../index';
|
import ChangeHistory from '../index';
|
||||||
import { pipelineData, pipelineDataHistory } from './testUtils';
|
import { pipelineData, pipelineDataHistory } from './testUtils';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
|
|||||||
@ -5,20 +5,6 @@ import { PipelineData } from 'types/api/pipeline/def';
|
|||||||
import { pipelineMockData } from '../mocks/pipeline';
|
import { pipelineMockData } from '../mocks/pipeline';
|
||||||
import AddNewPipeline from '../PipelineListsView/AddNewPipeline';
|
import AddNewPipeline from '../PipelineListsView/AddNewPipeline';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export function matchMedia(): void {
|
export function matchMedia(): void {
|
||||||
Object.defineProperty(window, 'matchMedia', {
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
writable: true,
|
writable: true,
|
||||||
|
|||||||
@ -11,20 +11,6 @@ jest.mock('../PipelineListsView/AddNewProcessor/config', () => ({
|
|||||||
DEFAULT_PROCESSOR_TYPE: 'json_parser',
|
DEFAULT_PROCESSOR_TYPE: 'json_parser',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedProcessorData = {
|
const selectedProcessorData = {
|
||||||
id: '1',
|
id: '1',
|
||||||
orderId: 1,
|
orderId: 1,
|
||||||
|
|||||||
@ -6,20 +6,6 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import i18n from 'ReactI18';
|
import i18n from 'ReactI18';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PipelinePage container test', () => {
|
describe('PipelinePage container test', () => {
|
||||||
it('should render DeleteAction section', () => {
|
it('should render DeleteAction section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
|
|||||||
@ -6,20 +6,6 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import i18n from 'ReactI18';
|
import i18n from 'ReactI18';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PipelinePage container test', () => {
|
describe('PipelinePage container test', () => {
|
||||||
it('should render DragAction section', () => {
|
it('should render DragAction section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
|
|||||||
@ -6,20 +6,6 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import i18n from 'ReactI18';
|
import i18n from 'ReactI18';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PipelinePage container test', () => {
|
describe('PipelinePage container test', () => {
|
||||||
it('should render EditAction section', () => {
|
it('should render EditAction section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
|
|||||||
@ -8,20 +8,6 @@ import store from 'store';
|
|||||||
import { pipelineMockData } from '../mocks/pipeline';
|
import { pipelineMockData } from '../mocks/pipeline';
|
||||||
import PipelineActions from '../PipelineListsView/TableComponents/PipelineActions';
|
import PipelineActions from '../PipelineListsView/TableComponents/PipelineActions';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PipelinePage container test', () => {
|
describe('PipelinePage container test', () => {
|
||||||
it('should render PipelineActions section', () => {
|
it('should render PipelineActions section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
|
|||||||
@ -3,20 +3,6 @@ import { render } from 'tests/test-utils';
|
|||||||
import { pipelineMockData } from '../mocks/pipeline';
|
import { pipelineMockData } from '../mocks/pipeline';
|
||||||
import PipelineExpandView from '../PipelineListsView/PipelineExpandView';
|
import PipelineExpandView from '../PipelineListsView/PipelineExpandView';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
Object.defineProperty(window, 'matchMedia', {
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
writable: true,
|
writable: true,
|
||||||
|
|||||||
@ -6,20 +6,6 @@ import { findByText, fireEvent, render, waitFor } from 'tests/test-utils';
|
|||||||
import { pipelineApiResponseMockData } from '../mocks/pipeline';
|
import { pipelineApiResponseMockData } from '../mocks/pipeline';
|
||||||
import PipelineListsView from '../PipelineListsView';
|
import PipelineListsView from '../PipelineListsView';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mock useUrlQuery hook
|
// Mock useUrlQuery hook
|
||||||
const mockUrlQuery = {
|
const mockUrlQuery = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
|||||||
@ -4,20 +4,6 @@ import { v4 } from 'uuid';
|
|||||||
|
|
||||||
import PipelinePageLayout from '../Layouts/Pipeline';
|
import PipelinePageLayout from '../Layouts/Pipeline';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
Object.defineProperty(window, 'matchMedia', {
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
writable: true,
|
writable: true,
|
||||||
|
|||||||
@ -7,20 +7,6 @@ import store from 'store';
|
|||||||
|
|
||||||
import TagInput from '../components/TagInput';
|
import TagInput from '../components/TagInput';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Pipeline Page', () => {
|
describe('Pipeline Page', () => {
|
||||||
it('should render TagInput section', () => {
|
it('should render TagInput section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
|
|||||||
@ -1,24 +1,11 @@
|
|||||||
import { render } from '@testing-library/react';
|
|
||||||
import Tags from 'container/PipelinePage/PipelineListsView/TableComponents/Tags';
|
import Tags from 'container/PipelinePage/PipelineListsView/TableComponents/Tags';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { render } from 'tests/test-utils';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import i18n from 'ReactI18';
|
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
const tags = ['server', 'app'];
|
const tags = ['server', 'app'];
|
||||||
|
|
||||||
describe('PipelinePage container test', () => {
|
describe('PipelinePage container test', () => {
|
||||||
it('should render Tags section', () => {
|
it('should render Tags section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(<Tags tags={tags} />);
|
||||||
<MemoryRouter>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<Tags tags={tags} />
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,20 +11,6 @@ import {
|
|||||||
getTableColumn,
|
getTableColumn,
|
||||||
} from '../PipelineListsView/utils';
|
} from '../PipelineListsView/utils';
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Utils testing of Pipeline Page', () => {
|
describe('Utils testing of Pipeline Page', () => {
|
||||||
test('it should be check form field of add pipeline', () => {
|
test('it should be check form field of add pipeline', () => {
|
||||||
expect(pipelineFields.length).toBe(3);
|
expect(pipelineFields.length).toBe(3);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { PlannedDowntime } from '../PlannedDowntime';
|
|||||||
|
|
||||||
describe('PlannedDowntime Component', () => {
|
describe('PlannedDowntime Component', () => {
|
||||||
it('renders the PlannedDowntime component properly', () => {
|
it('renders the PlannedDowntime component properly', () => {
|
||||||
render(<PlannedDowntime />, {}, 'ADMIN');
|
render(<PlannedDowntime />, {}, { role: 'ADMIN' });
|
||||||
|
|
||||||
// Check if title is rendered
|
// Check if title is rendered
|
||||||
expect(screen.getByText('Planned Downtime')).toBeInTheDocument();
|
expect(screen.getByText('Planned Downtime')).toBeInTheDocument();
|
||||||
@ -30,7 +30,7 @@ describe('PlannedDowntime Component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('disables the "New downtime" button for users with VIEWER role', () => {
|
it('disables the "New downtime" button for users with VIEWER role', () => {
|
||||||
render(<PlannedDowntime />, {}, USER_ROLES.VIEWER);
|
render(<PlannedDowntime />, {}, { role: USER_ROLES.VIEWER });
|
||||||
|
|
||||||
// Check if "New downtime" button is disabled for VIEWER
|
// Check if "New downtime" button is disabled for VIEWER
|
||||||
const newDowntimeButton = screen.getByRole('button', {
|
const newDowntimeButton = screen.getByRole('button', {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { fireEvent, screen } from '@testing-library/react';
|
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { render } from 'tests/test-utils';
|
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||||
import { Span } from 'types/api/trace/getTraceV2';
|
import { Span } from 'types/api/trace/getTraceV2';
|
||||||
|
|
||||||
import { SpanDuration } from '../Success';
|
import { SpanDuration } from '../Success';
|
||||||
@ -15,7 +13,6 @@ const DIMMED_SPAN_CLASS = 'dimmed-span';
|
|||||||
const SELECTED_NON_MATCHING_SPAN_CLASS = 'selected-non-matching-span';
|
const SELECTED_NON_MATCHING_SPAN_CLASS = 'selected-non-matching-span';
|
||||||
|
|
||||||
// Mock the hooks
|
// Mock the hooks
|
||||||
jest.mock('hooks/useSafeNavigate');
|
|
||||||
jest.mock('hooks/useUrlQuery');
|
jest.mock('hooks/useUrlQuery');
|
||||||
jest.mock('@signozhq/badge', () => ({
|
jest.mock('@signozhq/badge', () => ({
|
||||||
Badge: jest.fn(),
|
Badge: jest.fn(),
|
||||||
@ -52,24 +49,17 @@ const mockTraceMetadata = {
|
|||||||
hasMissingSpans: false,
|
hasMissingSpans: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
const mockSafeNavigate = jest.fn();
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
jest.mock('hooks/useSafeNavigate', () => ({
|
||||||
bars: jest.fn(),
|
useSafeNavigate: (): any => ({
|
||||||
};
|
safeNavigate: mockSafeNavigate,
|
||||||
const uplotMock = jest.fn(() => ({
|
}),
|
||||||
paths,
|
}));
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('SpanDuration', () => {
|
describe('SpanDuration', () => {
|
||||||
const mockSetSelectedSpan = jest.fn();
|
const mockSetSelectedSpan = jest.fn();
|
||||||
const mockUrlQuerySet = jest.fn();
|
const mockUrlQuerySet = jest.fn();
|
||||||
const mockSafeNavigate = jest.fn();
|
|
||||||
const mockUrlQueryGet = jest.fn();
|
const mockUrlQueryGet = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -81,11 +71,6 @@ describe('SpanDuration', () => {
|
|||||||
get: mockUrlQueryGet,
|
get: mockUrlQueryGet,
|
||||||
toString: () => 'spanId=test-span-id',
|
toString: () => 'spanId=test-span-id',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock safe navigate hook
|
|
||||||
(useSafeNavigate as jest.Mock).mockReturnValue({
|
|
||||||
safeNavigate: mockSafeNavigate,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates URL and selected span when clicked', () => {
|
it('updates URL and selected span when clicked', () => {
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
// src/mocks/server.js
|
// src/mocks/server.js
|
||||||
|
import { rest } from 'msw';
|
||||||
import { setupServer } from 'msw/node';
|
import { setupServer } from 'msw/node';
|
||||||
|
|
||||||
import { handlers } from './handlers';
|
import { handlers } from './handlers';
|
||||||
|
|
||||||
// This configures a request mocking server with the given request handlers.
|
// This configures a request mocking server with the given request handlers.
|
||||||
export const server = setupServer(...handlers);
|
export const server = setupServer(...handlers);
|
||||||
|
|
||||||
|
export { rest };
|
||||||
|
|||||||
@ -28,20 +28,6 @@ jest.mock('react-router-dom', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// mocking the graph components in this test as this should be handled separately
|
// mocking the graph components in this test as this should be handled separately
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'container/TimeSeriesView/TimeSeriesView',
|
'container/TimeSeriesView/TimeSeriesView',
|
||||||
|
|||||||
@ -35,21 +35,16 @@ jest.mock('container/TraceFlameGraph/index.tsx', () => ({
|
|||||||
default: (): JSX.Element => <div>TraceFlameGraph</div>,
|
default: (): JSX.Element => <div>TraceFlameGraph</div>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('TraceDetail', () => {
|
describe('TraceDetail', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(new Date('2023-10-20'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
it('should render tracedetail', async () => {
|
it('should render tracedetail', async () => {
|
||||||
const { findByText, getByText, getAllByText, getByPlaceholderText } = render(
|
const { findByText, getByText, getAllByText, getByPlaceholderText } = render(
|
||||||
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
|
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import {
|
|||||||
} from 'mocks-server/__mockdata__/query_range';
|
} from 'mocks-server/__mockdata__/query_range';
|
||||||
import { server } from 'mocks-server/server';
|
import { server } from 'mocks-server/server';
|
||||||
import { rest } from 'msw';
|
import { rest } from 'msw';
|
||||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
|
||||||
import { MemoryRouter } from 'react-router-dom-v5-compat';
|
import { MemoryRouter } from 'react-router-dom-v5-compat';
|
||||||
import {
|
import {
|
||||||
act,
|
act,
|
||||||
@ -97,22 +96,6 @@ jest.mock('react-router-dom', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('uplot', () => {
|
|
||||||
const paths = {
|
|
||||||
spline: jest.fn(),
|
|
||||||
bars: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const uplotMock = jest.fn(() => ({
|
|
||||||
paths,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
default: uplotMock,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'components/Uplot/Uplot',
|
'components/Uplot/Uplot',
|
||||||
() =>
|
() =>
|
||||||
@ -181,32 +164,31 @@ const checkFilterValues = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderWithTracesExplorerRouter = (
|
const renderWithTracesExplorerRouter = (
|
||||||
component: React.ReactNode,
|
component: React.ReactElement,
|
||||||
initialEntries: string[] = [
|
initialEntries: string[] = [
|
||||||
'/traces-explorer/?panelType=list&selectedExplorerView=list',
|
'/traces-explorer/?panelType=list&selectedExplorerView=list',
|
||||||
],
|
],
|
||||||
): ReturnType<typeof render> =>
|
): ReturnType<typeof render> =>
|
||||||
render(
|
render(
|
||||||
<MemoryRouter initialEntries={initialEntries}>
|
component,
|
||||||
<QueryBuilderContext.Provider value={{ ...qbProviderValue }}>
|
{},
|
||||||
{component}
|
{
|
||||||
</QueryBuilderContext.Provider>
|
initialRoute: initialEntries[0],
|
||||||
</MemoryRouter>,
|
queryBuilderOverrides: qbProviderValue,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('TracesExplorer - Filters', () => {
|
describe('TracesExplorer - Filters', () => {
|
||||||
// Initial filter panel rendering
|
// Initial filter panel rendering
|
||||||
// Test the initial state like which filters section are opened, default state of duration slider, etc.
|
// Test the initial state like which filters section are opened, default state of duration slider, etc.
|
||||||
it('should render the Trace filter', async () => {
|
it('should render the Trace filter', async () => {
|
||||||
const { getByText, getAllByText, getByTestId } = render(
|
const {
|
||||||
<MemoryRouter
|
getByText,
|
||||||
initialEntries={[
|
getAllByText,
|
||||||
|
getByTestId,
|
||||||
|
} = renderWithTracesExplorerRouter(<Filter setOpen={jest.fn()} />, [
|
||||||
`${process.env.FRONTEND_API_ENDPOINT}${ROUTES.TRACES_EXPLORER}/?panelType=list&selectedExplorerView=list`,
|
`${process.env.FRONTEND_API_ENDPOINT}${ROUTES.TRACES_EXPLORER}/?panelType=list&selectedExplorerView=list`,
|
||||||
]}
|
]);
|
||||||
>
|
|
||||||
<Filter setOpen={jest.fn()} />
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
checkFilterValues(getByText, getAllByText);
|
checkFilterValues(getByText, getAllByText);
|
||||||
|
|
||||||
@ -249,8 +231,12 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
it('filter panel actions', async () => {
|
it('filter panel actions', async () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Filter setOpen={jest.fn()} />
|
<Filter setOpen={jest.fn()} />,
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
initialRoute: '/traces-explorer/?panelType=list&selectedExplorerView=list',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if the section is closed
|
// Check if the section is closed
|
||||||
@ -275,10 +261,12 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('checking filters should update the query', async () => {
|
it('checking filters should update the query', async () => {
|
||||||
const { getByText } = renderWithTracesExplorerRouter(
|
const { getByText } = render(
|
||||||
<QueryBuilderContext.Provider
|
<Filter setOpen={jest.fn()} />,
|
||||||
value={
|
{},
|
||||||
{
|
{
|
||||||
|
queryBuilderOverrides: {
|
||||||
|
...qbProviderValue,
|
||||||
currentQuery: {
|
currentQuery: {
|
||||||
...initialQueriesMap.traces,
|
...initialQueriesMap.traces,
|
||||||
builder: {
|
builder: {
|
||||||
@ -286,12 +274,8 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
queryData: [initialQueryBuilderFormValues],
|
queryData: [initialQueryBuilderFormValues],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
redirectWithQueryBuilderData,
|
},
|
||||||
} as any
|
},
|
||||||
}
|
|
||||||
>
|
|
||||||
<Filter setOpen={jest.fn()} />
|
|
||||||
</QueryBuilderContext.Provider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const okCheckbox = getByText('Ok');
|
const okCheckbox = getByText('Ok');
|
||||||
@ -343,9 +327,7 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
.spyOn(compositeQueryHook, 'useGetCompositeQueryParam')
|
.spyOn(compositeQueryHook, 'useGetCompositeQueryParam')
|
||||||
.mockReturnValue(compositeQuery);
|
.mockReturnValue(compositeQuery);
|
||||||
|
|
||||||
const { findByText, getByTestId } = renderWithTracesExplorerRouter(
|
const { findByText, getByTestId } = render(<Filter setOpen={jest.fn()} />);
|
||||||
<Filter setOpen={jest.fn()} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
// check if the default query is applied - composite query has filters - serviceName : demo-app and name : HTTP GET /customer
|
// check if the default query is applied - composite query has filters - serviceName : demo-app and name : HTTP GET /customer
|
||||||
expect(await findByText('demo-app')).toBeInTheDocument();
|
expect(await findByText('demo-app')).toBeInTheDocument();
|
||||||
@ -369,8 +351,12 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getByText, getAllByText } = renderWithTracesExplorerRouter(
|
const { getByText, getAllByText } = render(
|
||||||
<Filter setOpen={jest.fn()} />,
|
<Filter setOpen={jest.fn()} />,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
initialRoute: '/traces-explorer/?panelType=list&selectedExplorerView=list',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
checkFilterValues(getByText, getAllByText);
|
checkFilterValues(getByText, getAllByText);
|
||||||
@ -394,18 +380,18 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getByText, getAllByText } = renderWithTracesExplorerRouter(
|
const { getByText, getAllByText } = render(<Filter setOpen={jest.fn()} />);
|
||||||
<Filter setOpen={jest.fn()} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
checkFilterValues(getByText, getAllByText);
|
checkFilterValues(getByText, getAllByText);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear filter on clear & reset button click', async () => {
|
it('should clear filter on clear & reset button click', async () => {
|
||||||
const { getByText, getByTestId } = renderWithTracesExplorerRouter(
|
const { getByText, getByTestId } = render(
|
||||||
<QueryBuilderContext.Provider
|
<Filter setOpen={jest.fn()} />,
|
||||||
value={
|
{},
|
||||||
{
|
{
|
||||||
|
initialRoute: '/traces-explorer/?panelType=list&selectedExplorerView=list',
|
||||||
|
queryBuilderOverrides: {
|
||||||
currentQuery: {
|
currentQuery: {
|
||||||
...initialQueriesMap.traces,
|
...initialQueriesMap.traces,
|
||||||
builder: {
|
builder: {
|
||||||
@ -414,11 +400,8 @@ describe('TracesExplorer - Filters', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
redirectWithQueryBuilderData,
|
redirectWithQueryBuilderData,
|
||||||
} as any
|
},
|
||||||
}
|
},
|
||||||
>
|
|
||||||
<Filter setOpen={jest.fn()} />
|
|
||||||
</QueryBuilderContext.Provider>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// check for the status section content
|
// check for the status section content
|
||||||
|
|||||||
@ -55,7 +55,7 @@ describe('WorkspaceLocked', () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
render(<WorkspaceLocked />, {}, 'VIEWER');
|
render(<WorkspaceLocked />, {}, { role: 'VIEWER' });
|
||||||
const updateCreditCardBtn = await screen.queryByRole('button', {
|
const updateCreditCardBtn = await screen.queryByRole('button', {
|
||||||
name: /Continue My Journey/i,
|
name: /Continue My Journey/i,
|
||||||
});
|
});
|
||||||
|
|||||||
93
frontend/src/tests/README.md
Normal file
93
frontend/src/tests/README.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
### Testing Guide
|
||||||
|
|
||||||
|
#### Tech Stack
|
||||||
|
- React Testing Library (RTL)
|
||||||
|
- Jest (runner, assertions, mocking)
|
||||||
|
- MSW (Mock Service Worker) for HTTP
|
||||||
|
- TypeScript (type-safe tests)
|
||||||
|
- JSDOM (browser-like env)
|
||||||
|
|
||||||
|
#### Unit Testing: What, Why, How
|
||||||
|
- What: Small, isolated tests for components, hooks, and utilities to verify behavior and edge cases.
|
||||||
|
- Why: Confidence to refactor, faster feedback than E2E, catches regressions early, documents intended behavior.
|
||||||
|
- How: Use our test harness with providers, mock external boundaries (APIs, router), assert on visible behavior and accessible roles, not implementation details.
|
||||||
|
|
||||||
|
#### Basic Template
|
||||||
|
```ts
|
||||||
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
|
import { server, rest } from 'mocks-server/server';
|
||||||
|
import MyComponent from '../MyComponent';
|
||||||
|
|
||||||
|
describe('MyComponent', () => {
|
||||||
|
it('renders and interacts', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<MyComponent />, undefined, { initialRoute: '/foo' });
|
||||||
|
|
||||||
|
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
|
||||||
|
await user.click(screen.getByRole('button', { name: /refresh/i }));
|
||||||
|
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### .cursorrules (Highlights)
|
||||||
|
- Import from `tests/test-utils` only.
|
||||||
|
- Prefer `userEvent` for real interactions; use `fireEvent` only for low-level events (scroll/resize/setting `scrollTop`).
|
||||||
|
- Use MSW to mock network calls; large JSON goes in `mocks-server/__mockdata__`.
|
||||||
|
- Keep tests type-safe (`jest.MockedFunction<T>`, avoid `any`).
|
||||||
|
- Prefer accessible queries (`getByRole`, `findByRole`) before text and `data-testid`.
|
||||||
|
- Pin time only when asserting relative dates; avoid global fake timers otherwise.
|
||||||
|
|
||||||
|
Repo-specific reasons:
|
||||||
|
- The harness wires Redux, React Query, i18n, timezone, preferences, so importing from RTL directly bypasses critical providers.
|
||||||
|
- Some infra deps are globally mocked (e.g., `uplot`) to keep tests fast and stable.
|
||||||
|
- For virtualization (react-virtuoso), there is no `userEvent` scroll helper; use `fireEvent.scroll` after setting `element.scrollTop`.
|
||||||
|
|
||||||
|
#### Example patterns (from `components/QuickFilters/tests/QuickFilters.test.tsx`)
|
||||||
|
MSW overrides per test:
|
||||||
|
```ts
|
||||||
|
server.use(
|
||||||
|
rest.get(`${ENVIRONMENT.baseURL}/api/v1/orgs/me/filters/logs`, (_req, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json(quickFiltersListResponse)),
|
||||||
|
),
|
||||||
|
rest.put(`${ENVIRONMENT.baseURL}/api/v1/orgs/me/filters`, async (req, res, ctx) => {
|
||||||
|
// capture payload if needed
|
||||||
|
return res(ctx.status(200), ctx.json({}));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Mock hooks minimally at module level:
|
||||||
|
```ts
|
||||||
|
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||||
|
useQueryBuilder: jest.fn(),
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
Interact via accessible roles:
|
||||||
|
```ts
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
await user.click(screen.getByRole('button', { name: /save changes/i }));
|
||||||
|
expect(screen.getByText(/ADDED FILTERS/i)).toBeInTheDocument();
|
||||||
|
```
|
||||||
|
|
||||||
|
Virtualized scroll:
|
||||||
|
```ts
|
||||||
|
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
|
||||||
|
scroller.scrollTop = 500;
|
||||||
|
fireEvent.scroll(scroller);
|
||||||
|
```
|
||||||
|
|
||||||
|
Routing-dependent behavior:
|
||||||
|
```ts
|
||||||
|
render(<Page />, undefined, { initialRoute: '/logs-explorer?panelType=list' });
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
- Global mocks configured in Jest: `uplot` → `__mocks__/uplotMock.ts`.
|
||||||
|
- If a test needs custom behavior (e.g., different API response), override with `server.use(...)` locally.
|
||||||
@ -2,18 +2,20 @@
|
|||||||
import { render, RenderOptions, RenderResult } from '@testing-library/react';
|
import { render, RenderOptions, RenderResult } from '@testing-library/react';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||||
import { AppContext } from 'providers/App/App';
|
import { AppContext } from 'providers/App/App';
|
||||||
import { IAppContext } from 'providers/App/types';
|
import { IAppContext } from 'providers/App/types';
|
||||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
import {
|
||||||
|
QueryBuilderContext,
|
||||||
|
QueryBuilderProvider,
|
||||||
|
} from 'providers/QueryBuilder';
|
||||||
import TimezoneProvider from 'providers/Timezone';
|
import TimezoneProvider from 'providers/Timezone';
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from 'react';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
@ -23,24 +25,27 @@ import {
|
|||||||
LicenseState,
|
LicenseState,
|
||||||
LicenseStatus,
|
LicenseStatus,
|
||||||
} from 'types/api/licensesV3/getActive';
|
} from 'types/api/licensesV3/getActive';
|
||||||
|
import { QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||||
import { ROLES, USER_ROLES } from 'types/roles';
|
import { ROLES, USER_ROLES } from 'types/roles';
|
||||||
|
// import { MemoryRouter as V5MemoryRouter } from 'react-router-dom-v5-compat';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
retry: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
// jest.useFakeTimers();
|
||||||
jest.setSystemTime(new Date('2023-10-20'));
|
jest.setSystemTime(new Date('2023-10-20'));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
jest.useRealTimers();
|
// jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockStore = configureStore([thunk]);
|
const mockStore = configureStore([thunk]);
|
||||||
@ -85,24 +90,6 @@ jest.mock('react-i18next', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
|
||||||
...jest.requireActual('react-router-dom'),
|
|
||||||
useLocation: (): { pathname: string } => ({
|
|
||||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('hooks/useSafeNavigate', () => ({
|
|
||||||
useSafeNavigate: (): any => ({
|
|
||||||
safeNavigate: jest.fn(),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('react-router-dom-v5-compat', () => ({
|
|
||||||
...jest.requireActual('react-router-dom-v5-compat'),
|
|
||||||
useNavigationType: (): any => 'PUSH',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export function getAppContextMock(
|
export function getAppContextMock(
|
||||||
role: string,
|
role: string,
|
||||||
appContextOverrides?: Partial<IAppContext>,
|
appContextOverrides?: Partial<IAppContext>,
|
||||||
@ -253,48 +240,96 @@ export function getAppContextMock(
|
|||||||
|
|
||||||
export function AllTheProviders({
|
export function AllTheProviders({
|
||||||
children,
|
children,
|
||||||
role, // Accept the role as a prop
|
role,
|
||||||
appContextOverrides,
|
appContextOverrides,
|
||||||
|
queryBuilderOverrides,
|
||||||
|
initialRoute,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
role: string; // Define the role prop
|
role?: string;
|
||||||
appContextOverrides: Partial<IAppContext>;
|
appContextOverrides?: Partial<IAppContext>;
|
||||||
|
queryBuilderOverrides?: Partial<QueryBuilderContextType>;
|
||||||
|
initialRoute?: string;
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
|
// Set default values
|
||||||
|
const roleValue = role || 'ADMIN';
|
||||||
|
const appContextOverridesValue = appContextOverrides || {};
|
||||||
|
const initialRouteValue = initialRoute || '/';
|
||||||
|
|
||||||
|
const queryBuilderContent = queryBuilderOverrides ? (
|
||||||
|
<QueryBuilderContext.Provider
|
||||||
|
value={queryBuilderOverrides as QueryBuilderContextType}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</QueryBuilderContext.Provider>
|
||||||
|
) : (
|
||||||
|
<QueryBuilderProvider>{children}</QueryBuilderProvider>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<MemoryRouter initialEntries={[initialRouteValue]}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Provider store={mockStored(role)}>
|
<Provider store={mockStored(roleValue)}>
|
||||||
<AppContext.Provider value={getAppContextMock(role, appContextOverrides)}>
|
<AppContext.Provider
|
||||||
|
value={getAppContextMock(roleValue, appContextOverridesValue)}
|
||||||
|
>
|
||||||
<ResourceProvider>
|
<ResourceProvider>
|
||||||
<ErrorModalProvider>
|
<ErrorModalProvider>
|
||||||
<BrowserRouter>
|
|
||||||
<TimezoneProvider>
|
<TimezoneProvider>
|
||||||
<PreferenceContextProvider>
|
<PreferenceContextProvider>
|
||||||
<QueryBuilderProvider>{children}</QueryBuilderProvider>
|
{queryBuilderContent}
|
||||||
</PreferenceContextProvider>
|
</PreferenceContextProvider>
|
||||||
</TimezoneProvider>
|
</TimezoneProvider>
|
||||||
</BrowserRouter>
|
|
||||||
</ErrorModalProvider>
|
</ErrorModalProvider>
|
||||||
</ResourceProvider>
|
</ResourceProvider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
</Provider>
|
</Provider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllTheProviders.defaultProps = {
|
||||||
|
role: 'ADMIN',
|
||||||
|
appContextOverrides: {},
|
||||||
|
queryBuilderOverrides: undefined,
|
||||||
|
initialRoute: '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ProviderProps {
|
||||||
|
role?: string;
|
||||||
|
appContextOverrides?: Partial<IAppContext>;
|
||||||
|
queryBuilderOverrides?: Partial<QueryBuilderContextType>;
|
||||||
|
initialRoute?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const customRender = (
|
const customRender = (
|
||||||
ui: ReactElement,
|
ui: ReactElement,
|
||||||
options?: Omit<RenderOptions, 'wrapper'>,
|
options?: Omit<RenderOptions, 'wrapper'>,
|
||||||
role = 'ADMIN', // Set a default role
|
providerProps: ProviderProps = {},
|
||||||
appContextOverrides?: Partial<IAppContext>,
|
): RenderResult => {
|
||||||
): RenderResult =>
|
const {
|
||||||
render(ui, {
|
role = 'ADMIN',
|
||||||
|
appContextOverrides = {},
|
||||||
|
queryBuilderOverrides,
|
||||||
|
initialRoute = '/',
|
||||||
|
} = providerProps;
|
||||||
|
|
||||||
|
return render(ui, {
|
||||||
wrapper: () => (
|
wrapper: () => (
|
||||||
<AllTheProviders role={role} appContextOverrides={appContextOverrides || {}}>
|
<AllTheProviders
|
||||||
|
role={role}
|
||||||
|
appContextOverrides={appContextOverrides}
|
||||||
|
queryBuilderOverrides={queryBuilderOverrides}
|
||||||
|
initialRoute={initialRoute}
|
||||||
|
>
|
||||||
{ui}
|
{ui}
|
||||||
</AllTheProviders>
|
</AllTheProviders>
|
||||||
),
|
),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export * from '@testing-library/react';
|
export * from '@testing-library/react';
|
||||||
|
export { default as userEvent } from '@testing-library/user-event';
|
||||||
export { customRender as render };
|
export { customRender as render };
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user