datajunction-ui
Version:
DataJunction UI
844 lines (784 loc) • 28.1 kB
JSX
import * as domTestingLib from '@testing-library/dom';
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { mocks } from '../../../../mocks/mockNodes';
import DJClientContext from '../../../providers/djclient';
// Import NodePage directly (not via Loadable) — Loadable wraps it in a
// React.lazy + Suspense boundary which adds an async resolution step that
// can race testing-library's waitFor on slower CI runners.
import { NodePage } from '../index';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
// Mock cronstrue for NodePreAggregationsTab
vi.mock('cronstrue', () => {
const mock = { toString: () => 'Every day at midnight' };
return { ...mock, default: mock };
});
// Mock CSS imports
vi.mock('../../../../styles/preaggregations.css', () => ({}));
// Mock recharts for NodeDataFlowTab (Sankey doesn't work in jsdom)
vi.mock('recharts', () => ({
Sankey: () => <div data-testid="sankey-chart" />,
Tooltip: () => null,
}));
// ResizeObserver is not defined in jsdom
global.ResizeObserver = function (callback) {
return { observe: () => {}, disconnect: () => {}, unobserve: () => {} };
};
// jsdom doesn't implement canvas — stub getContext so text-measurement code doesn't crash
HTMLCanvasElement.prototype.getContext = () => ({
font: '',
measureText: () => ({ width: 0 }),
});
describe('<NodePage />', () => {
/* moved to top */
const { queryHelpers } = domTestingLib;
const queryByAttribute = attribute =>
queryHelpers.queryAllByAttribute.bind(null, attribute);
function getByAttribute(container, id, attribute, ...rest) {
const result = queryByAttribute(attribute)(container, id, ...rest);
return result[0];
}
const mockDJClient = () => {
return {
DataJunctionAPI: {
node: vi.fn(),
metric: vi.fn(),
getMetric: vi.fn(),
revalidate: vi.fn().mockReturnValue({ status: 'valid' }),
node_dag: vi.fn().mockReturnValue(mocks.mockNodeDAG),
clientCode: vi.fn().mockResolvedValue('dj_client = DJClient()'),
columns: vi.fn(),
history: vi.fn(),
revisions: vi.fn(),
materializations: vi.fn(),
availabilityStates: vi.fn(),
refreshLatestMaterialization: vi.fn(),
materializationInfo: vi.fn(),
sql: vi.fn(),
cube: vi.fn(),
compiledSql: vi.fn(),
node_lineage: vi.fn(),
nodesWithDimension: vi.fn(),
attributes: vi.fn(),
dimensions: vi.fn(),
setPartition: vi.fn(),
engines: vi.fn(),
streamNodeData: vi.fn(),
nodeDimensions: vi.fn(),
getNotificationPreferences: vi.fn().mockResolvedValue([]),
subscribeToNotifications: vi.fn().mockResolvedValue({ status: 200 }),
unsubscribeFromNotifications: vi
.fn()
.mockResolvedValue({ status: 200 }),
setAttributes: vi.fn().mockResolvedValue({ status: 200 }),
linkDimension: vi.fn().mockResolvedValue({ status: 200 }),
unlinkDimension: vi.fn().mockResolvedValue({ status: 200 }),
addReferenceDimensionLink: vi.fn().mockResolvedValue({ status: 200 }),
removeReferenceDimensionLink: vi
.fn()
.mockResolvedValue({ status: 200 }),
addComplexDimensionLink: vi.fn().mockResolvedValue({ status: 200 }),
removeComplexDimensionLink: vi.fn().mockResolvedValue({ status: 200 }),
listPreaggs: vi.fn().mockResolvedValue({ items: [] }),
deactivatePreaggWorkflow: vi.fn().mockResolvedValue({ status: 200 }),
namespaceSources: vi.fn().mockResolvedValue(null),
listDeployments: vi.fn().mockResolvedValue([]),
getNamespaceGitConfig: vi.fn().mockResolvedValue(null),
upstreamsGQL: vi.fn().mockResolvedValue([]),
downstreamsGQL: vi.fn().mockResolvedValue([]),
findCubesWithMetrics: vi.fn().mockResolvedValue([]),
},
};
};
const defaultProps = {
name: 'default.avg_repair_price',
djNode: {
namespace: 'default',
node_revision_id: 24,
node_id: 24,
type: 'metric',
name: 'default.avg_repair_price',
display_name: 'Default: Avg Repair Price',
version: 'v1.0',
status: 'valid',
mode: 'published',
catalog: {
id: 1,
uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
created_at: '2023-08-21T16:48:51.146121+00:00',
updated_at: '2023-08-21T16:48:51.146122+00:00',
extra_params: {},
name: 'warehouse',
},
schema_: null,
table: null,
description: 'Average repair price',
query:
'SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n',
availability: null,
dimension_links: [],
columns: [
{
name: 'default_DOT_avg_repair_price',
type: 'double',
display_name: 'Default DOT avg repair price',
attributes: [],
dimension: null,
},
],
updated_at: '2023-08-21T16:48:56.932231+00:00',
materializations: [],
parents: [
{
name: 'default.repair_order_details',
},
],
created_at: '2023-08-21T16:48:56.932162+00:00',
tags: [{ name: 'purpose', display_name: 'Purpose' }],
primary_key: [],
incompatible_druid_functions: ['IF'],
createNodeClientCode:
'dj = DJBuilder(DJ_URL)\n\navg_repair_price = dj.create_metric(\n description="Average repair price",\n display_name="Default: Avg Repair Price",\n name="default.avg_repair_price",\n primary_key=[],\n query="""SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n"""\n)',
dimensions: [
{
name: 'default.date_dim.dateint',
type: 'timestamp',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.birth_date',
],
},
{
name: 'default.date_dim.dateint',
type: 'timestamp',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.hire_date',
],
},
{
name: 'default.date_dim.day',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.birth_date',
],
},
{
name: 'default.date_dim.day',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.hire_date',
],
},
{
name: 'default.date_dim.month',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.birth_date',
],
},
{
name: 'default.date_dim.month',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.hire_date',
],
},
{
name: 'default.date_dim.year',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.birth_date',
],
},
{
name: 'default.date_dim.year',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
'default.hard_hat.hire_date',
],
},
{
name: 'default.hard_hat.address',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.birth_date',
type: 'date',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.city',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.contractor_id',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.country',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.first_name',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.hard_hat_id',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.hire_date',
type: 'date',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.last_name',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.manager',
type: 'int',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.postal_code',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.state',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
{
name: 'default.hard_hat.title',
type: 'string',
path: [
'default.repair_order_details.repair_order_id',
'default.repair_order.hard_hat_id',
],
},
],
},
};
it('renders the NodeInfo tab correctly for a metric node', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage {...defaultProps} />
</DJClientContext.Provider>
);
const { container } = render(
<MemoryRouter initialEntries={['/nodes/default.num_repair_orders/info']}>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(
() => {
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
'default.num_repair_orders',
);
expect(
screen.getByRole('dialog', { name: 'NodeName' }),
).toHaveTextContent('default.num_repair_orders');
expect(
screen.getByRole('button', { name: 'Info' }),
).toBeInTheDocument();
expect(
screen.getByRole('dialog', { name: 'Description' }),
).toHaveTextContent('Number of repair orders');
expect(
screen.getByRole('dialog', { name: 'Version' }),
).toHaveTextContent('v1.0');
expect(
screen.getByRole('dialog', { name: 'NodeStatus' }),
).toBeInTheDocument();
expect(screen.getByRole('dialog', { name: 'Tags' })).toHaveTextContent(
'Purpose',
);
expect(
screen.getByRole('dialog', { name: 'RequiredDimensions' }),
).toHaveTextContent('');
expect(
screen.getByRole('dialog', { name: 'DisplayName' }),
).toHaveTextContent('Default: Num Repair Orders');
expect(
screen.getByRole('dialog', { name: 'NodeType' }),
).toHaveTextContent('metric');
},
{ timeout: 4000 },
);
// Wait separately for getMetric to be called and data to render
await waitFor(() => {
expect(djClient.DataJunctionAPI.getMetric).toHaveBeenCalledWith(
'default.num_repair_orders',
);
});
// Wait for metric expression to appear (SyntaxHighlighter may split text)
await waitFor(() => {
expect(screen.getByText(/count/)).toBeInTheDocument();
});
expect(container.getElementsByClassName('language-sql')).toMatchSnapshot();
}, 60000);
it('hides Edit and shows the read-only badge for a node in a read-only (flat git) namespace', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
// magnesium.tech is a flat git-backed namespace (github_repo_path +
// git_branch, no parent) → read-only via shape, even though git_only is
// not set. Regression guard: node pages must honor shape-based read-only,
// not just the git_only flag.
djClient.DataJunctionAPI.getNamespaceGitConfig.mockResolvedValue({
github_repo_path: 'corp/tech-realtime-guardrails',
git_branch: 'main',
git_root_namespace: 'magnesium.tech',
});
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage {...defaultProps} />
</DJClientContext.Provider>
);
render(
<MemoryRouter
initialEntries={['/nodes/magnesium.tech.withinapp_ttr_log/info']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
// The read-only badge appears once the namespace git config resolves...
expect(await screen.findByLabelText('ReadOnly')).toBeInTheDocument();
// ...and the Edit button is not offered.
expect(
screen.queryByRole('button', { name: 'Edit' }),
).not.toBeInTheDocument();
}, 60000);
it('renders the NodeInfo tab correctly for cube nodes', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockCubeNode);
djClient.DataJunctionAPI.cube.mockResolvedValue(mocks.mockCubesCube);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage {...defaultProps} />
</DJClientContext.Provider>
);
const { container } = render(
<MemoryRouter initialEntries={['/nodes/default.repair_orders_cube/info']}>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(() => {
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
'default.repair_orders_cube',
);
userEvent.click(screen.getByRole('button', { name: 'Info' }));
expect(
screen.getByRole('dialog', { name: 'NodeName' }),
).toHaveTextContent('default.repair_orders_cube');
expect(screen.getByRole('button', { name: 'Info' })).toBeInTheDocument();
expect(
screen.getByRole('dialog', { name: 'Description' }),
).toHaveTextContent('Repair Orders');
expect(screen.getByRole('dialog', { name: 'Version' })).toHaveTextContent(
'v1.0',
);
expect(
screen.getByRole('dialog', { name: 'PrimaryKey' }),
).toHaveTextContent('');
expect(
screen.getByRole('dialog', { name: 'DisplayName' }),
).toHaveTextContent('Default: Repair Orders Cube');
expect(
screen.getByRole('dialog', { name: 'NodeType' }),
).toHaveTextContent('cube');
expect(
screen.getByRole('dialog', { name: 'NodeType' }),
).toHaveTextContent('cube');
expect(screen.getByText('Cube Elements')).toBeInTheDocument();
screen
.getAllByRole('cell', { name: 'CubeElement' })
.map(cube => cube.hasAttribute('a'));
});
}, 60000);
it('renders the NodeColumns tab correctly', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockResolvedValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
djClient.DataJunctionAPI.columns.mockResolvedValue(mocks.metricNodeColumns);
djClient.DataJunctionAPI.attributes.mockResolvedValue(mocks.attributes);
djClient.DataJunctionAPI.dimensions.mockResolvedValue(mocks.dimensions);
djClient.DataJunctionAPI.engines.mockReturnValue([]);
djClient.DataJunctionAPI.setPartition.mockResolvedValue({
status: 200,
json: { message: '' },
});
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
render(
<MemoryRouter
initialEntries={['/nodes/default.num_repair_orders/columns']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(() => {
expect(djClient.DataJunctionAPI.columns).toHaveBeenCalledWith(
expect.objectContaining({ name: mocks.mockMetricNode.name }),
);
expect(
screen.getByRole('columnheader', { name: 'ColumnName' }),
).toHaveTextContent('default_DOT_avg_repair_price');
expect(
screen.getByRole('columnheader', { name: 'ColumnDisplayName' }),
).toHaveTextContent('Default DOT avg repair price');
expect(
screen.getByRole('columnheader', { name: 'ColumnType' }),
).toHaveTextContent('double');
// check that the edit column popover can be clicked
const editColumnPopover = screen.getByRole('button', {
name: 'EditColumn',
});
expect(editColumnPopover).toBeInTheDocument();
fireEvent.click(editColumnPopover);
expect(
screen.getByRole('button', { name: 'SaveEditColumn' }),
).toBeInTheDocument();
// check that the manage dimension links dialog can be opened
const manageDimensionLinksButton = screen.getByRole('button', {
name: 'ManageDimensionLinksToggle',
});
expect(manageDimensionLinksButton).toBeInTheDocument();
fireEvent.click(manageDimensionLinksButton);
expect(
screen.getByRole('dialog', { name: 'ManageDimensionLinksDialog' }),
).toBeInTheDocument();
// check that the set column partition popover can be clicked
const partitionColumnPopover = screen.getByRole('button', {
name: 'PartitionColumn',
});
expect(partitionColumnPopover).toBeInTheDocument();
fireEvent.click(partitionColumnPopover);
const savePartition = screen.getByRole('button', {
name: 'SaveEditColumn',
});
expect(savePartition).toBeInTheDocument();
fireEvent.click(savePartition);
expect(screen.getByText('Saved!'));
});
}, 60000);
it('renders the NodeHistory tab correctly', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
djClient.DataJunctionAPI.history.mockReturnValue(mocks.metricNodeHistory);
djClient.DataJunctionAPI.revisions.mockReturnValue(
mocks.metricNodeRevisions,
);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
const { container } = render(
<MemoryRouter
initialEntries={['/nodes/default.num_repair_orders/history']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(async () => {
fireEvent.click(screen.getByRole('button', { name: 'History' }));
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
mocks.mockMetricNode.name,
);
expect(djClient.DataJunctionAPI.history).toHaveBeenCalledWith(
'node',
mocks.mockMetricNode.name,
);
expect(
screen.getByRole('list', { name: 'Activity' }),
).toBeInTheDocument();
screen
.queryAllByRole('cell', {
name: 'HistoryAttribute',
})
.forEach(cell => expect(cell).toHaveTextContent(/Set col1 as /));
screen
.queryAllByRole('cell', {
name: 'HistoryCreateLink',
})
.forEach(cell =>
expect(cell).toHaveTextContent(
'Linked col1 todefault.hard_hat viahard_hat_id',
),
);
screen
.queryAllByRole('cell', {
name: 'HistoryCreateMaterialization',
})
.forEach(cell =>
expect(cell).toHaveTextContent(
'Initialized materialization some_random_materialization',
),
);
screen
.queryAllByRole('cell', {
name: 'HistoryNodeStatusChange',
})
.forEach(cell =>
expect(cell).toHaveTextContent(
'Status changed from valid to invalid Caused by a change in upstream default.repair_order_details',
),
);
});
});
it('renders an empty NodeMaterialization tab correctly', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockResolvedValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
djClient.DataJunctionAPI.columns.mockResolvedValue(mocks.metricNodeColumns);
// For metric nodes, listPreaggs is called, not materializations
djClient.DataJunctionAPI.listPreaggs.mockResolvedValue({ items: [] });
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
render(
<MemoryRouter
initialEntries={['/nodes/default.num_repair_orders/materializations']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
// For metric nodes, NodePreAggregationsTab is used, which calls listPreaggs
await waitFor(() => {
expect(djClient.DataJunctionAPI.listPreaggs).toHaveBeenCalledWith({
node_name: mocks.mockMetricNode.name,
include_stale: true,
});
});
// Check for the empty state text (for NodePreAggregationsTab)
expect(
screen.getByText('No pre-aggregations found for this node.'),
).toBeInTheDocument();
});
it('renders the NodeMaterialization tab with materializations correctly', async () => {
const djClient = mockDJClient();
// Use cube node - only cubes use NodeMaterializationTab
// Override columns with explicit partition: null to avoid undefined.type_ error
const cubeNodeWithPartitions = {
...mocks.mockCubeNode,
columns: mocks.mockCubeNode.columns.map(col => ({
...col,
partition: null,
})),
};
djClient.DataJunctionAPI.node.mockResolvedValue(cubeNodeWithPartitions);
djClient.DataJunctionAPI.cube.mockResolvedValue(mocks.mockCubesCube);
djClient.DataJunctionAPI.columns.mockResolvedValue([]);
djClient.DataJunctionAPI.materializations.mockResolvedValue(
mocks.nodeMaterializations,
);
djClient.DataJunctionAPI.availabilityStates.mockResolvedValue([]);
djClient.DataJunctionAPI.materializationInfo.mockResolvedValue(
mocks.materializationInfo,
);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
render(
<MemoryRouter
initialEntries={['/nodes/default.repair_orders_cube/materializations']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
// Wait for the node to load first
await waitFor(() => {
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
'default.repair_orders_cube',
);
});
// Then wait for materializations to be fetched
await waitFor(() => {
expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
mocks.mockCubeNode.name,
);
});
});
it('renders a NodeColumnLineage tab correctly', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
djClient.DataJunctionAPI.node_lineage.mockReturnValue(
mocks.mockNodeLineage,
);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
render(
<MemoryRouter
initialEntries={['/nodes/default.num_repair_orders/lineage']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(() => {
fireEvent.click(screen.getByRole('button', { name: 'Lineage' }));
expect(djClient.DataJunctionAPI.node_lineage).toHaveBeenCalledWith(
mocks.mockMetricNode.name,
);
});
});
it('renders a NodeGraph tab correctly', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
djClient.DataJunctionAPI.getMetric.mockResolvedValue(
mocks.mockMetricNodeJson,
);
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
render(
<MemoryRouter
initialEntries={['/nodes/default.num_repair_orders/data-flow']}
>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(() => {
fireEvent.click(screen.getByRole('button', { name: 'Data Flow' }));
expect(djClient.DataJunctionAPI.upstreamsGQL).toHaveBeenCalledWith(
mocks.mockMetricNode.name,
);
});
});
it('renders Linked Nodes tab correctly', async () => {
const djClient = mockDJClient();
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockDimensionNode);
djClient.DataJunctionAPI.nodesWithDimension.mockReturnValue(
mocks.mockLinkedNodes,
);
const element = (
<DJClientContext.Provider value={djClient}>
<NodePage />
</DJClientContext.Provider>
);
render(
<MemoryRouter initialEntries={['/nodes/default.dispatcher/linked']}>
<Routes>
<Route path="nodes/:name/:tab" element={element} />
</Routes>
</MemoryRouter>,
);
await waitFor(() => {
fireEvent.click(screen.getByRole('button', { name: 'Linked Nodes' }));
expect(djClient.DataJunctionAPI.nodesWithDimension).toHaveBeenCalledWith(
mocks.mockDimensionNode.name,
);
});
});
});