Skip to content

Commit 31b410c

Browse files
test(caching): provide QueryClient to CellDiagram + metrics tests; add bare query wrapper
1 parent 39d0138 commit 31b410c

4 files changed

Lines changed: 62 additions & 16 deletions

File tree

packages/test-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ export {
1919
createQueryWrapper,
2020
createTestQueryClient,
2121
} from './queryClientWrapper';
22+
export { createQueryClientWrapper } from './queryClientBareWrapper';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ReactNode } from 'react';
2+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3+
4+
/**
5+
* A `wrapper` that mounts children inside a fresh `QueryClientProvider` ONLY —
6+
* no `TestApiProvider`. Use this for suites that already supply their APIs by
7+
* mocking `@backstage/core-plugin-api`'s `useApi` directly, where pulling in
8+
* `TestApiProvider` (and its `@backstage/core-app-api` internals) would clash
9+
* with that mock. Kept in its own module (importing only react-query, never
10+
* `@backstage/test-utils`) so consuming it doesn't drag `TestApiProvider` into
11+
* a suite that has mocked its dependencies away. Also keeps `@tanstack/react-query`
12+
* behind this package's seam so consuming plugins don't declare it just to wrap
13+
* a component test.
14+
*/
15+
export function createQueryClientWrapper() {
16+
const queryClient = new QueryClient({
17+
defaultOptions: { queries: { retry: false, gcTime: 0 } },
18+
});
19+
return ({ children }: { children: ReactNode }) => (
20+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
21+
);
22+
}

plugins/openchoreo-observability/src/components/Metrics/ObservabilityMetricsPage.test.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,10 @@ describe('ObservabilityMetricsPage', () => {
269269
it('refetches both resource and HTTP metrics when refresh is clicked', async () => {
270270
const user = userEvent.setup();
271271
const resourceRefresh = jest.fn();
272-
const httpFetchMetrics = jest.fn();
272+
// Both sections now refetch via `refresh()` (the query keys on the filters,
273+
// so a manual refresh re-runs the current key) — the HTTP section watches
274+
// the parent's `refreshNonce` bump and calls its own `refresh`.
275+
const httpRefresh = jest.fn();
273276

274277
mockUseMetrics.mockImplementation(
275278
(
@@ -296,8 +299,8 @@ describe('ObservabilityMetricsPage', () => {
296299
},
297300
loading: false,
298301
error: null,
299-
fetchMetrics: httpFetchMetrics,
300-
refresh: jest.fn(),
302+
fetchMetrics: jest.fn(),
303+
refresh: httpRefresh,
301304
};
302305
}
303306
return {
@@ -319,12 +322,13 @@ describe('ObservabilityMetricsPage', () => {
319322

320323
await renderPage();
321324

322-
httpFetchMetrics.mockClear();
325+
resourceRefresh.mockClear();
326+
httpRefresh.mockClear();
323327

324328
await user.click(screen.getByTestId('refresh-btn'));
325329

326330
expect(resourceRefresh).toHaveBeenCalledTimes(1);
327-
await waitFor(() => expect(httpFetchMetrics).toHaveBeenCalledTimes(1));
331+
await waitFor(() => expect(httpRefresh).toHaveBeenCalledTimes(1));
328332
});
329333

330334
it('renders nothing for namespace error', async () => {

plugins/openchoreo/src/components/CellDiagram/CellDiagram.test.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { render, screen, waitFor, act } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
3+
// Import from the sub-path (not the barrel) so this suite — which mocks
4+
// @backstage/core-plugin-api — doesn't transitively load @backstage/test-utils'
5+
// TestApiProvider (which runs attachComponentData at import and would break).
6+
import { createQueryClientWrapper } from '@openchoreo/test-utils/src/queryClientBareWrapper';
37
import { CellDiagram } from './CellDiagram';
48

9+
// useCellEnvironments is now backed by useOpenChoreoQuery, which needs a
10+
// QueryClientProvider in the tree. Wrap in a fresh, retry-free cache each render
11+
// (a bare provider, not TestApiProvider — this suite mocks core-plugin-api and
12+
// supplies APIs through the useApi mock, so TestApiProvider isn't wanted).
13+
const renderCell = () =>
14+
render(<CellDiagram />, { wrapper: createQueryClientWrapper() });
15+
516
// ---- Mocks ----
617

718
jest.mock('@backstage/plugin-catalog-react', () => ({
@@ -46,11 +57,19 @@ jest.mock('@openchoreo/backstage-design-system', () => ({
4657
jest.mock('@openchoreo/backstage-plugin-react', () => {
4758
// eslint-disable-next-line @typescript-eslint/no-require-imports
4859
const React = require('react');
60+
// Keep the real caching wrapper (useCellEnvironments now calls
61+
// useOpenChoreoQuery). Pull it from its own module rather than the barrel so
62+
// we don't drag in the whole package (which imports the mocked core-plugin-api
63+
// and breaks on attachComponentData).
64+
const { useOpenChoreoQuery } =
65+
// eslint-disable-next-line @typescript-eslint/no-require-imports
66+
jest.requireActual('@openchoreo/backstage-plugin-react/src/hooks/useOpenChoreoQuery');
4967
// Each recompute advances "now" (mirroring the real calculateTimeRange,
5068
// which uses `new Date()`), so the Refresh button produces a new fetch key
5169
// and triggers a refetch.
5270
let rangeCall = 0;
5371
return {
72+
useOpenChoreoQuery,
5473
EmptyState: ({ title, description, action }: any) => (
5574
<div data-testid="empty-state">
5675
<div>{title}</div>
@@ -408,7 +427,7 @@ describe('CellDiagram', () => {
408427
const mockClient = setupMockClient();
409428

410429
await act(async () => {
411-
render(<CellDiagram />);
430+
renderCell();
412431
});
413432

414433
await waitFor(() => {
@@ -438,7 +457,7 @@ describe('CellDiagram', () => {
438457
const mockClient = setupMockClient();
439458

440459
await act(async () => {
441-
render(<CellDiagram />);
460+
renderCell();
442461
});
443462

444463
await waitFor(() => {
@@ -480,7 +499,7 @@ describe('CellDiagram', () => {
480499
setupMockClient();
481500

482501
await act(async () => {
483-
render(<CellDiagram />);
502+
renderCell();
484503
});
485504

486505
await waitFor(() => {
@@ -522,7 +541,7 @@ describe('CellDiagram', () => {
522541
setupMockClient();
523542

524543
await act(async () => {
525-
render(<CellDiagram />);
544+
renderCell();
526545
});
527546

528547
await waitFor(() => {
@@ -570,7 +589,7 @@ describe('CellDiagram', () => {
570589
});
571590

572591
await act(async () => {
573-
render(<CellDiagram />);
592+
renderCell();
574593
});
575594

576595
await waitFor(() => {
@@ -615,7 +634,7 @@ describe('CellDiagram', () => {
615634
});
616635

617636
await act(async () => {
618-
render(<CellDiagram />);
637+
renderCell();
619638
});
620639

621640
const toggle = screen.getByRole('switch', {
@@ -658,7 +677,7 @@ describe('CellDiagram', () => {
658677
});
659678

660679
await act(async () => {
661-
render(<CellDiagram />);
680+
renderCell();
662681
});
663682

664683
// Toggle OFF → architecture
@@ -706,7 +725,7 @@ describe('CellDiagram', () => {
706725
});
707726

708727
await act(async () => {
709-
render(<CellDiagram />);
728+
renderCell();
710729
});
711730

712731
const toggle = screen.getByRole('switch', {
@@ -728,7 +747,7 @@ describe('CellDiagram', () => {
728747
const mockClient = setupMockClient();
729748

730749
await act(async () => {
731-
render(<CellDiagram />);
750+
renderCell();
732751
});
733752

734753
await waitFor(() => {
@@ -768,7 +787,7 @@ describe('CellDiagram', () => {
768787
});
769788

770789
await act(async () => {
771-
render(<CellDiagram />);
790+
renderCell();
772791
});
773792

774793
await waitFor(() => {
@@ -795,7 +814,7 @@ describe('CellDiagram', () => {
795814
});
796815

797816
await act(async () => {
798-
render(<CellDiagram />);
817+
renderCell();
799818
});
800819

801820
await waitFor(() => {

0 commit comments

Comments
 (0)