From cd13d7c1f1075590fe2ba82f543724b60361a400 Mon Sep 17 00:00:00 2001 From: Yian Shang Date: Sun, 5 Jul 2026 10:12:58 -0700 Subject: [PATCH] Fix tests --- .../NodePage/__tests__/NodePage.test.jsx | 56 +++++++++++++++++++ .../src/app/pages/NodePage/index.jsx | 28 ++++++---- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/datajunction-ui/src/app/pages/NodePage/__tests__/NodePage.test.jsx b/datajunction-ui/src/app/pages/NodePage/__tests__/NodePage.test.jsx index 1e8f756c8..79565e8b6 100644 --- a/datajunction-ui/src/app/pages/NodePage/__tests__/NodePage.test.jsx +++ b/datajunction-ui/src/app/pages/NodePage/__tests__/NodePage.test.jsx @@ -25,6 +25,62 @@ vi.mock('recharts', () => ({ Tooltip: () => null, })); +// Mock reactflow (lineage/dimensions/graph tabs). Tabs are lazy-loaded, so +// reactflow is dynamically imported mid-render; loading the real lib inside +// jsdom during render blocks the event loop. The dedicated tab tests cover +// reactflow rendering — here we only need NodePage to mount the right tab. +vi.mock('reactflow/dist/style.css', () => ({})); +vi.mock('reactflow', async () => { + const React = await vi.importActual('react'); + const { useState } = React; + const Passthrough = ({ children }) =>
{children}
; + return { + __esModule: true, + default: ({ children, nodes, nodeTypes }) => ( +
+ {(nodes || []).map(n => { + const NodeComp = nodeTypes?.[n.type]; + return NodeComp ? ( + + ) : ( +
{n.id}
+ ); + })} + {children} +
+ ), + ReactFlowProvider: Passthrough, + Panel: Passthrough, + Background: () => null, + Controls: () => null, + MiniMap: () => null, + Handle: () => null, + Position: { Left: 'left', Right: 'right', Top: 'top', Bottom: 'bottom' }, + MarkerType: { ArrowClosed: 'arrowclosed', Arrow: 'arrow' }, + useNodesState: initial => { + const [n, s] = useState(initial || []); + return [n, s, () => {}]; + }, + useEdgesState: initial => { + const [e, s] = useState(initial || []); + return [e, s, () => {}]; + }, + addEdge: (params, eds) => [...(eds || []), params], + }; +}); + +// Mock CodeMirror (materialization config editor). Like reactflow, it's pulled +// in via a lazy tab and dynamically importing it (plus every language grammar +// from codemirror-extensions-langs) mid-render blocks jsdom. The materialization +// tab's own tests cover the editor. +vi.mock('@uiw/react-codemirror', () => ({ + __esModule: true, + default: ({ value }) =>
{value}
, +})); +vi.mock('@uiw/codemirror-extensions-langs', () => ({ + langs: new Proxy({}, { get: () => () => [] }), +})); + // ResizeObserver is not defined in jsdom global.ResizeObserver = function (callback) { return { observe: () => {}, disconnect: () => {}, unobserve: () => {} }; diff --git a/datajunction-ui/src/app/pages/NodePage/index.jsx b/datajunction-ui/src/app/pages/NodePage/index.jsx index d6ce87160..c38a19b00 100644 --- a/datajunction-ui/src/app/pages/NodePage/index.jsx +++ b/datajunction-ui/src/app/pages/NodePage/index.jsx @@ -1,28 +1,32 @@ import * as React from 'react'; import { useParams } from 'react-router-dom'; -import { useContext, useEffect, useState } from 'react'; +import { useContext, useEffect, useState, lazy, Suspense } from 'react'; import Tab from '../../components/Tab'; import NamespaceHeader from '../../components/NamespaceHeader'; -import NodeInfoTab from './NodeInfoTab'; -import NodeColumnTab from './NodeColumnTab'; -import NodeDataFlowTab from './NodeDataFlowTab'; -import NodeDimensionsTab from './NodeDimensionsTab'; -import NodeHistory from './NodeHistory'; import NotebookDownload from './NotebookDownload'; import DJClientContext from '../../providers/djclient'; -import NodeMaterializationTab from './NodeMaterializationTab'; -import NodePreAggregationsTab from './NodePreAggregationsTab'; import ClientCodePopover from './ClientCodePopover'; import WatchButton from './WatchNodeButton'; -import NodesWithDimension from './NodesWithDimension'; -import NodeColumnLineage from './NodeLineageTab'; import EditIcon from '../../icons/EditIcon'; import ChartIcon from '../../icons/ChartIcon'; import AlertIcon from '../../icons/AlertIcon'; import LoadingIcon from '../../icons/LoadingIcon'; -import NodeDependenciesTab from './NodeDependenciesTab'; import { useNavigate } from 'react-router-dom'; +// Tabs are lazy-loaded so a node page only pulls the deps of the tab actually +// shown. Otherwise every visit loads reactflow (lineage/dimensions/graph), +// recharts (data flow) and codemirror (materialization) even for the Info tab. +const NodeInfoTab = lazy(() => import('./NodeInfoTab')); +const NodeColumnTab = lazy(() => import('./NodeColumnTab')); +const NodeDataFlowTab = lazy(() => import('./NodeDataFlowTab')); +const NodeDimensionsTab = lazy(() => import('./NodeDimensionsTab')); +const NodeHistory = lazy(() => import('./NodeHistory')); +const NodeMaterializationTab = lazy(() => import('./NodeMaterializationTab')); +const NodePreAggregationsTab = lazy(() => import('./NodePreAggregationsTab')); +const NodesWithDimension = lazy(() => import('./NodesWithDimension')); +const NodeColumnLineage = lazy(() => import('./NodeLineageTab')); +const NodeDependenciesTab = lazy(() => import('./NodeDependenciesTab')); + export function NodePage() { const djClient = useContext(DJClientContext).DataJunctionAPI; const navigate = useNavigate(); @@ -317,7 +321,7 @@ export function NodePage() {
{tabsList(node).map(buildTabs)}
- {tabToDisplay} + }>{tabToDisplay} ) : node?.message !== undefined ? (