diff --git a/js/package.json b/js/package.json index c6d04403a..81765325b 100644 --- a/js/package.json +++ b/js/package.json @@ -19,7 +19,8 @@ "test": "vitest run", "test:watch": "vitest", "test:cov": "vitest run --coverage", - "type:check": "tsc --noEmit", + "type:check": "pnpm type:check:root && pnpm --filter @datarecce/ui type:check && pnpm --filter @datarecce/storybook type:check", + "type:check:root": "tsc --noEmit", "clean": "rimraf ../recce/data", "prepare": "cd .. && husky js/.husky" }, diff --git a/js/packages/storybook/package.json b/js/packages/storybook/package.json index 8a058a984..91335b95b 100644 --- a/js/packages/storybook/package.json +++ b/js/packages/storybook/package.json @@ -8,6 +8,7 @@ "build": "storybook build -o dist", "test": "vitest run", "test:watch": "vitest", + "type:check": "tsc --noEmit", "test:visual": "playwright test", "test:visual:update": "playwright test --update-snapshots" }, diff --git a/js/packages/storybook/tsconfig.json b/js/packages/storybook/tsconfig.json index 1f0f53c12..88ac10412 100644 --- a/js/packages/storybook/tsconfig.json +++ b/js/packages/storybook/tsconfig.json @@ -8,5 +8,12 @@ "@datarecce/ui/*": ["../ui/src/*"] } }, - "include": ["stories/**/*", ".storybook/**/*", "vitest.config.ts"] + "include": [ + "stories/**/*", + ".storybook/**/*", + "vitest.config.ts", + "../../mui-augmentations.d.ts", + "../ui/src/components/ui/mui-utils.ts", + "../ui/src/css.d.ts" + ] } diff --git a/js/packages/ui/src/components/lineage/changeCategory.ts b/js/packages/ui/src/components/lineage/changeCategory.ts index 8f9f8e26d..16666178c 100644 --- a/js/packages/ui/src/components/lineage/changeCategory.ts +++ b/js/packages/ui/src/components/lineage/changeCategory.ts @@ -1,3 +1,4 @@ +import { hasOwn } from "../../utils/hasOwn"; import type { ChangeCategory } from "./nodes/LineageNode"; export const CHANGE_CATEGORY_LABELS: Record = { @@ -30,7 +31,7 @@ export const CHANGE_CATEGORY_DETAILS: ReadonlyArray<{ ]; function isChangeCategory(value: string | undefined): value is ChangeCategory { - return value !== undefined && Object.hasOwn(CHANGE_CATEGORY_LABELS, value); + return value !== undefined && hasOwn(CHANGE_CATEGORY_LABELS, value); } export function getChangeCategoryLabel( diff --git a/js/packages/ui/src/components/lineage/hooks/useTrackLineageRender.ts b/js/packages/ui/src/components/lineage/hooks/useTrackLineageRender.ts index 8c0004cc8..110b3ad0e 100644 --- a/js/packages/ui/src/components/lineage/hooks/useTrackLineageRender.ts +++ b/js/packages/ui/src/components/lineage/hooks/useTrackLineageRender.ts @@ -22,16 +22,14 @@ export const useTrackLineageRender = () => { rightSidebarOpen: boolean, ) => { const lineageGraphNodesOnly = nodes.filter(isLineageGraphNode); - const grouped = Object.groupBy( - lineageGraphNodesOnly, - (node) => node.data.changeStatus ?? "unchanged", - ); // Prefix status counts with "nodes_" - const statusCounts = Object.fromEntries( - Object.entries(grouped).map(([status, nodes]) => [ - `nodes_${status}`, - nodes?.length ?? 0, - ]), + const statusCounts = lineageGraphNodesOnly.reduce>( + (counts, node) => { + const key = `nodes_${node.data.changeStatus ?? "unchanged"}`; + counts[key] = (counts[key] ?? 0) + 1; + return counts; + }, + {}, ); const trackingData = { node_count: lineageGraphNodesOnly.length, diff --git a/js/packages/ui/src/components/result/createResultView.test.tsx b/js/packages/ui/src/components/result/createResultView.test.tsx index cc0ff04fe..a7e2be607 100644 --- a/js/packages/ui/src/components/result/createResultView.test.tsx +++ b/js/packages/ui/src/components/result/createResultView.test.tsx @@ -32,7 +32,7 @@ vi.mock("ag-grid-community", () => ({ // Mock ScreenshotDataGrid component vi.mock("../data/ScreenshotDataGrid", async () => { - const utils = await vi.importActual("@/testing-utils/resultViewTestUtils"); + const utils = await vi.importActual("./createResultView.testUtils"); return { ScreenshotDataGrid: (utils as { screenshotDataGridMock: unknown }) .screenshotDataGridMock, @@ -44,7 +44,7 @@ vi.mock("../data/ScreenshotDataGrid", async () => { // Mock ScreenshotBox component vi.mock("../ui/ScreenshotBox", async () => { - const utils = await vi.importActual("@/testing-utils/resultViewTestUtils"); + const utils = await vi.importActual("./createResultView.testUtils"); return { ScreenshotBox: (utils as { screenshotBoxMock: unknown }).screenshotBoxMock, }; @@ -62,10 +62,9 @@ vi.mock("../../hooks", () => ({ import { screen } from "@testing-library/react"; import { createRef, type ReactNode } from "react"; -import type { MockDataGridHandle } from "@/testing-utils/resultViewTestUtils"; -import { renderWithProviders } from "@/testing-utils/resultViewTestUtils"; import type { DataGridHandle } from "../data/ScreenshotDataGrid"; import { createResultView } from "./createResultView"; +import { renderWithProviders } from "./createResultView.testUtils"; import type { ResultViewData } from "./types"; // ============================================================================ diff --git a/js/packages/ui/src/components/result/createResultView.testUtils.tsx b/js/packages/ui/src/components/result/createResultView.testUtils.tsx new file mode 100644 index 000000000..bd53860d8 --- /dev/null +++ b/js/packages/ui/src/components/result/createResultView.testUtils.tsx @@ -0,0 +1,71 @@ +import { ThemeProvider } from "@mui/material/styles"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { type RenderOptions, render } from "@testing-library/react"; +import React, { type ReactNode } from "react"; +import { theme } from "../../theme"; + +export const screenshotBoxMock = React.forwardRef< + HTMLDivElement, + { children?: ReactNode } +>(function MockScreenshotBox({ children }, ref) { + return ( +
+ {children} +
+ ); +}); + +export const screenshotDataGridMock = React.forwardRef< + { + api: null; + element: null; + }, + { + columns?: unknown[]; + rows?: unknown[]; + children?: ReactNode; + } +>(function MockScreenshotDataGrid({ columns, rows, children }, ref) { + React.useImperativeHandle(ref, () => ({ + api: null, + element: null, + })); + + const columnCount = columns?.length ?? 0; + const rowCount = rows?.length ?? 0; + + return ( +
+ {children ?? `Mock Grid: ${rowCount} rows, ${columnCount} columns`} +
+ ); +}); + +function TestProviders({ children }: { children: ReactNode }) { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + refetchOnWindowFocus: false, + refetchOnMount: false, + }, + }, + }); + + return ( + + {children} + + ); +} + +export function renderWithProviders( + ui: React.ReactElement, + options?: Omit, +): ReturnType { + return render(ui, { wrapper: TestProviders, ...options }); +} diff --git a/js/packages/ui/src/components/ui/dataGrid/dataGridFactory.tsx b/js/packages/ui/src/components/ui/dataGrid/dataGridFactory.tsx index d7d2b912f..3b9f3f785 100644 --- a/js/packages/ui/src/components/ui/dataGrid/dataGridFactory.tsx +++ b/js/packages/ui/src/components/ui/dataGrid/dataGridFactory.tsx @@ -56,6 +56,7 @@ import { toRowCountDiffDataGrid, toValueDiffGridConfigured as toValueDiffGrid, } from "../../../utils/dataGrid"; +import { hasOwn } from "../../../utils/hasOwn"; import { getCaseInsensitive } from "../../../utils/transforms"; import { buildColumnTooltip, DataTypeIcon } from "../DataTypeIcon"; import { toValueDataGrid } from "./generators/toValueDataGrid"; @@ -330,7 +331,7 @@ export function injectProfileColumnNameRenderer( result: DataGridResult, ): DataGridResult { const isInlineDiff = - result.rows.length > 0 && Object.hasOwn(result.rows[0], "base__data_type"); + result.rows.length > 0 && hasOwn(result.rows[0], "base__data_type"); const columns = result.columns .filter((col) => { diff --git a/js/packages/ui/src/components/ui/dataGrid/inlineRenderCell.tsx b/js/packages/ui/src/components/ui/dataGrid/inlineRenderCell.tsx index 43a750689..8e26a0720 100644 --- a/js/packages/ui/src/components/ui/dataGrid/inlineRenderCell.tsx +++ b/js/packages/ui/src/components/ui/dataGrid/inlineRenderCell.tsx @@ -22,6 +22,7 @@ import { isCellChanged, toRenderedValue, } from "../../../utils/dataGrid/gridUtils"; +import { hasOwn } from "../../../utils/hasOwn"; import { DiffText, type DiffTextProps } from "../DiffText"; import { DiffTextWithToast } from "../DiffTextWithToast"; @@ -100,12 +101,12 @@ export function createInlineRenderCell(config: InlineRenderCellConfig = {}) { const currentKey = `current__${columnKey}`.toLowerCase(); // Handle case where neither base nor current values exist - if (!Object.hasOwn(row, baseKey) && !Object.hasOwn(row, currentKey)) { + if (!hasOwn(row, baseKey) && !hasOwn(row, currentKey)) { return "-"; } - const hasBase = Object.hasOwn(row, baseKey); - const hasCurrent = Object.hasOwn(row, currentKey); + const hasBase = hasOwn(row, baseKey); + const hasCurrent = hasOwn(row, currentKey); const [baseValue, baseGrayOut] = toRenderedValue( row, diff --git a/js/packages/ui/src/css.d.ts b/js/packages/ui/src/css.d.ts new file mode 100644 index 000000000..cbe652dbe --- /dev/null +++ b/js/packages/ui/src/css.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/js/packages/ui/src/utils/hasOwn.ts b/js/packages/ui/src/utils/hasOwn.ts new file mode 100644 index 000000000..a8df73703 --- /dev/null +++ b/js/packages/ui/src/utils/hasOwn.ts @@ -0,0 +1,4 @@ +export function hasOwn(object: object, property: PropertyKey): boolean { + // biome-ignore lint/suspicious/noPrototypeBuiltins: Storybook's ES2020 target does not provide Object.hasOwn. + return Object.prototype.hasOwnProperty.call(object, property); +}