@datarecce/ui 0.1.30 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.mts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/components.d.mts +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/hooks.d.mts +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/{index-BNUP2V_N.d.ts → index-B9lSPJTi.d.ts} +184 -2
- package/dist/index-B9lSPJTi.d.ts.map +1 -0
- package/dist/{index-DOPZuhD8.d.mts → index-IIXVIoOL.d.mts} +253 -71
- package/dist/index-IIXVIoOL.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +4 -0
- package/dist/theme.d.mts +2 -185
- package/dist/theme.d.ts +2 -185
- package/dist/types.d.mts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +4 -2
- package/recce-source/.editorconfig +26 -0
- package/recce-source/.flake8 +37 -0
- package/recce-source/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
- package/recce-source/.github/ISSUE_TEMPLATE/custom.md +10 -0
- package/recce-source/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/recce-source/.github/PULL_REQUEST_TEMPLATE.md +21 -0
- package/recce-source/.github/copilot-instructions.md +331 -0
- package/recce-source/.github/instructions/backend-instructions.md +541 -0
- package/recce-source/.github/instructions/frontend-instructions.md +317 -0
- package/recce-source/.github/workflows/build-statics.yaml +72 -0
- package/recce-source/.github/workflows/bump.yaml +48 -0
- package/recce-source/.github/workflows/integration-tests-cloud.yaml +92 -0
- package/recce-source/.github/workflows/integration-tests-sqlmesh.yaml +33 -0
- package/recce-source/.github/workflows/integration-tests.yaml +52 -0
- package/recce-source/.github/workflows/nightly.yaml +246 -0
- package/recce-source/.github/workflows/release.yaml +196 -0
- package/recce-source/.github/workflows/tests-js.yaml +58 -0
- package/recce-source/.github/workflows/tests-python.yaml +128 -0
- package/recce-source/.pre-commit-config.yaml +26 -0
- package/recce-source/CLAUDE.md +483 -0
- package/recce-source/CODE_OF_CONDUCT.md +128 -0
- package/recce-source/CONTRIBUTING.md +107 -0
- package/recce-source/LICENSE +201 -0
- package/recce-source/Makefile +126 -0
- package/recce-source/README.md +182 -0
- package/recce-source/RECCE_CLOUD.md +81 -0
- package/recce-source/SECURITY.md +25 -0
- package/recce-source/docs/PACKAGING.md +340 -0
- package/recce-source/docs/README.md +1 -0
- package/recce-source/integration_tests/dbt/dbt_project.yml +26 -0
- package/recce-source/integration_tests/dbt/models/customers.sql +69 -0
- package/recce-source/integration_tests/dbt/models/docs.md +14 -0
- package/recce-source/integration_tests/dbt/models/orders.sql +56 -0
- package/recce-source/integration_tests/dbt/models/schema.yml +82 -0
- package/recce-source/integration_tests/dbt/models/staging/schema.yml +31 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_customers.sql +22 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_orders.sql +23 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_payments.sql +25 -0
- package/recce-source/integration_tests/dbt/packages.yml +7 -0
- package/recce-source/integration_tests/dbt/profiles.yml +8 -0
- package/recce-source/integration_tests/dbt/seeds/raw_customers.csv +101 -0
- package/recce-source/integration_tests/dbt/seeds/raw_orders.csv +100 -0
- package/recce-source/integration_tests/dbt/seeds/raw_payments.csv +114 -0
- package/recce-source/integration_tests/dbt/seeds/raw_statuses.csv +5 -0
- package/recce-source/integration_tests/dbt/smoke_test.sh +72 -0
- package/recce-source/integration_tests/dbt/smoke_test_cloud.sh +71 -0
- package/recce-source/integration_tests/sqlmesh/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/audits/assert_item_price_above_zero.sql +9 -0
- package/recce-source/integration_tests/sqlmesh/audits/items.sql +7 -0
- package/recce-source/integration_tests/sqlmesh/audits/order_items.sql +7 -0
- package/recce-source/integration_tests/sqlmesh/config.py +171 -0
- package/recce-source/integration_tests/sqlmesh/helper.py +20 -0
- package/recce-source/integration_tests/sqlmesh/hooks/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/macros/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/macros/macros.py +8 -0
- package/recce-source/integration_tests/sqlmesh/macros/macros.sql +8 -0
- package/recce-source/integration_tests/sqlmesh/macros/utils.py +11 -0
- package/recce-source/integration_tests/sqlmesh/metrics/metrics.sql +25 -0
- package/recce-source/integration_tests/sqlmesh/models/customer_revenue_by_day.sql +41 -0
- package/recce-source/integration_tests/sqlmesh/models/customer_revenue_lifetime.sql +60 -0
- package/recce-source/integration_tests/sqlmesh/models/customers.sql +32 -0
- package/recce-source/integration_tests/sqlmesh/models/items.py +95 -0
- package/recce-source/integration_tests/sqlmesh/models/marketing.sql +15 -0
- package/recce-source/integration_tests/sqlmesh/models/order_items.py +95 -0
- package/recce-source/integration_tests/sqlmesh/models/orders.py +70 -0
- package/recce-source/integration_tests/sqlmesh/models/raw_marketing.py +62 -0
- package/recce-source/integration_tests/sqlmesh/models/top_waiters.sql +23 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_as_customer_by_day.sql +29 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_names.sql +10 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_revenue_by_day.sql +29 -0
- package/recce-source/integration_tests/sqlmesh/models/waiters.py +62 -0
- package/recce-source/integration_tests/sqlmesh/prep_env.sh +16 -0
- package/recce-source/integration_tests/sqlmesh/schema.yaml +5 -0
- package/recce-source/integration_tests/sqlmesh/seeds/waiter_names.csv +11 -0
- package/recce-source/integration_tests/sqlmesh/test_server.sh +29 -0
- package/recce-source/integration_tests/sqlmesh/tests/test_customer_revenue_by_day.yaml +63 -0
- package/recce-source/integration_tests/sqlmesh/tests/test_order_items.yaml +72 -0
- package/recce-source/js/.editorconfig +27 -0
- package/recce-source/js/.env.development +5 -0
- package/recce-source/js/.husky/pre-commit +29 -0
- package/recce-source/js/.nvmrc +1 -0
- package/recce-source/js/README.md +39 -0
- package/recce-source/js/app/(mainComponents)/DisplayModeToggle.tsx +65 -0
- package/recce-source/js/app/(mainComponents)/NavBar.tsx +228 -0
- package/recce-source/js/app/(mainComponents)/RecceVersionBadge.tsx +107 -0
- package/recce-source/js/app/(mainComponents)/TopBar.tsx +252 -0
- package/recce-source/js/app/@lineage/default.tsx +20 -0
- package/recce-source/js/app/@lineage/page.tsx +14 -0
- package/recce-source/js/app/MainLayout.tsx +170 -0
- package/recce-source/js/app/Providers.tsx +49 -0
- package/recce-source/js/app/checks/page.tsx +296 -0
- package/recce-source/js/app/error.tsx +93 -0
- package/recce-source/js/app/favicon.ico +0 -0
- package/recce-source/js/app/global-error.tsx +115 -0
- package/recce-source/js/app/global.css +82 -0
- package/recce-source/js/app/layout.tsx +48 -0
- package/recce-source/js/app/lineage/page.tsx +15 -0
- package/recce-source/js/app/page.tsx +12 -0
- package/recce-source/js/app/query/page.tsx +8 -0
- package/recce-source/js/biome.json +313 -0
- package/recce-source/js/jest.config.js +34 -0
- package/recce-source/js/jest.globals.d.ts +32 -0
- package/recce-source/js/jest.setup.js +91 -0
- package/recce-source/js/next.config.js +16 -0
- package/recce-source/js/package-lock.json +13843 -0
- package/recce-source/js/package.json +123 -0
- package/recce-source/js/pnpm-lock.yaml +9235 -0
- package/recce-source/js/pnpm-workspace.yaml +6 -0
- package/recce-source/js/postcss.config.js +5 -0
- package/recce-source/js/public/auth_callback.html +68 -0
- package/recce-source/js/public/imgs/feedback/thumbs-down.png +0 -0
- package/recce-source/js/public/imgs/feedback/thumbs-up.png +0 -0
- package/recce-source/js/public/imgs/reload-image.svg +4 -0
- package/recce-source/js/public/logo/recce-logo-white.png +0 -0
- package/recce-source/js/src/components/AuthModal/AuthModal.tsx +202 -0
- package/recce-source/js/src/components/app/AvatarDropdown.tsx +159 -0
- package/recce-source/js/src/components/app/EnvInfo.tsx +357 -0
- package/recce-source/js/src/components/app/Filename.tsx +388 -0
- package/recce-source/js/src/components/app/SetupConnectionPopover.tsx +91 -0
- package/recce-source/js/src/components/app/StateExporter.tsx +57 -0
- package/recce-source/js/src/components/app/StateImporter.tsx +198 -0
- package/recce-source/js/src/components/app/StateSharing.tsx +145 -0
- package/recce-source/js/src/components/app/StateSynchronizer.tsx +205 -0
- package/recce-source/js/src/components/charts/HistogramChart.tsx +291 -0
- package/recce-source/js/src/components/charts/SquareIcon.tsx +51 -0
- package/recce-source/js/src/components/charts/TopKSummaryList.tsx +457 -0
- package/recce-source/js/src/components/charts/chartTheme.ts +74 -0
- package/recce-source/js/src/components/check/CheckBreadcrumb.tsx +97 -0
- package/recce-source/js/src/components/check/CheckDescription.tsx +134 -0
- package/recce-source/js/src/components/check/CheckDetail.tsx +797 -0
- package/recce-source/js/src/components/check/CheckEmptyState.tsx +84 -0
- package/recce-source/js/src/components/check/CheckList.tsx +320 -0
- package/recce-source/js/src/components/check/LineageDiffView.tsx +32 -0
- package/recce-source/js/src/components/check/PresetCheckTemplateView.tsx +48 -0
- package/recce-source/js/src/components/check/SchemaDiffView.tsx +290 -0
- package/recce-source/js/src/components/check/check.ts +25 -0
- package/recce-source/js/src/components/check/timeline/CheckTimeline.tsx +163 -0
- package/recce-source/js/src/components/check/timeline/CommentInput.tsx +84 -0
- package/recce-source/js/src/components/check/timeline/TimelineEvent.tsx +468 -0
- package/recce-source/js/src/components/check/timeline/index.ts +12 -0
- package/recce-source/js/src/components/check/utils.ts +12 -0
- package/recce-source/js/src/components/data-grid/ScreenshotDataGrid.tsx +333 -0
- package/recce-source/js/src/components/data-grid/agGridStyles.css +55 -0
- package/recce-source/js/src/components/data-grid/agGridTheme.ts +43 -0
- package/recce-source/js/src/components/editor/CodeEditor.tsx +107 -0
- package/recce-source/js/src/components/editor/DiffEditor.tsx +162 -0
- package/recce-source/js/src/components/editor/index.ts +12 -0
- package/recce-source/js/src/components/errorboundary/ErrorBoundary.tsx +87 -0
- package/recce-source/js/src/components/histogram/HistogramDiffForm.tsx +147 -0
- package/recce-source/js/src/components/histogram/HistogramDiffResultView.tsx +63 -0
- package/recce-source/js/src/components/icons/index.tsx +142 -0
- package/recce-source/js/src/components/lineage/ActionControl.tsx +63 -0
- package/recce-source/js/src/components/lineage/ActionTag.tsx +141 -0
- package/recce-source/js/src/components/lineage/ChangeStatusLegend.tsx +46 -0
- package/recce-source/js/src/components/lineage/ColumnLevelLineageControl.tsx +327 -0
- package/recce-source/js/src/components/lineage/ColumnLevelLineageLegend.tsx +57 -0
- package/recce-source/js/src/components/lineage/GraphColumnNode.tsx +199 -0
- package/recce-source/js/src/components/lineage/GraphEdge.tsx +59 -0
- package/recce-source/js/src/components/lineage/GraphNode.tsx +555 -0
- package/recce-source/js/src/components/lineage/LineagePage.tsx +10 -0
- package/recce-source/js/src/components/lineage/LineageView.tsx +1384 -0
- package/recce-source/js/src/components/lineage/LineageViewContext.tsx +86 -0
- package/recce-source/js/src/components/lineage/LineageViewContextMenu.tsx +637 -0
- package/recce-source/js/src/components/lineage/LineageViewNotification.tsx +64 -0
- package/recce-source/js/src/components/lineage/LineageViewTopBar.tsx +596 -0
- package/recce-source/js/src/components/lineage/NodeSqlView.tsx +136 -0
- package/recce-source/js/src/components/lineage/NodeTag.tsx +278 -0
- package/recce-source/js/src/components/lineage/NodeView.tsx +642 -0
- package/recce-source/js/src/components/lineage/SandboxView.tsx +436 -0
- package/recce-source/js/src/components/lineage/ServerDisconnectedModalContent.tsx +105 -0
- package/recce-source/js/src/components/lineage/SetupConnectionBanner.tsx +52 -0
- package/recce-source/js/src/components/lineage/SingleEnvironmentQueryView.tsx +152 -0
- package/recce-source/js/src/components/lineage/graph.test.ts +31 -0
- package/recce-source/js/src/components/lineage/graph.ts +58 -0
- package/recce-source/js/src/components/lineage/lineage.test.ts +169 -0
- package/recce-source/js/src/components/lineage/lineage.ts +521 -0
- package/recce-source/js/src/components/lineage/styles.css +42 -0
- package/recce-source/js/src/components/lineage/styles.tsx +165 -0
- package/recce-source/js/src/components/lineage/useMultiNodesAction.ts +352 -0
- package/recce-source/js/src/components/lineage/useValueDiffAlertDialog.tsx +108 -0
- package/recce-source/js/src/components/onboarding-guide/Notification.tsx +62 -0
- package/recce-source/js/src/components/profile/ProfileDiffForm.tsx +134 -0
- package/recce-source/js/src/components/profile/ProfileDiffResultView.tsx +245 -0
- package/recce-source/js/src/components/query/ChangedOnlyCheckbox.tsx +29 -0
- package/recce-source/js/src/components/query/DiffText.tsx +120 -0
- package/recce-source/js/src/components/query/QueryDiffResultView.tsx +470 -0
- package/recce-source/js/src/components/query/QueryForm.tsx +80 -0
- package/recce-source/js/src/components/query/QueryPage.tsx +282 -0
- package/recce-source/js/src/components/query/QueryResultView.tsx +180 -0
- package/recce-source/js/src/components/query/SetupConnectionGuide.tsx +57 -0
- package/recce-source/js/src/components/query/SqlEditor.tsx +245 -0
- package/recce-source/js/src/components/query/ToggleSwitch.tsx +84 -0
- package/recce-source/js/src/components/query/styles.css +21 -0
- package/recce-source/js/src/components/routing/DirectUrlAccess.test.tsx +428 -0
- package/recce-source/js/src/components/routing/LineageStatePreservation.test.tsx +311 -0
- package/recce-source/js/src/components/routing/Navigation.test.tsx +256 -0
- package/recce-source/js/src/components/rowcount/RowCountDiffResultView.tsx +109 -0
- package/recce-source/js/src/components/rowcount/delta.ts +11 -0
- package/recce-source/js/src/components/run/RunList.tsx +303 -0
- package/recce-source/js/src/components/run/RunModal.tsx +191 -0
- package/recce-source/js/src/components/run/RunPage.tsx +26 -0
- package/recce-source/js/src/components/run/RunResultPane.tsx +454 -0
- package/recce-source/js/src/components/run/RunStatusAndDate.tsx +106 -0
- package/recce-source/js/src/components/run/RunToolbar.tsx +70 -0
- package/recce-source/js/src/components/run/RunView.tsx +196 -0
- package/recce-source/js/src/components/run/registry.ts +214 -0
- package/recce-source/js/src/components/run/types.ts +14 -0
- package/recce-source/js/src/components/schema/ColumnNameCell.test.tsx +169 -0
- package/recce-source/js/src/components/schema/ColumnNameCell.tsx +198 -0
- package/recce-source/js/src/components/schema/SchemaView.tsx +337 -0
- package/recce-source/js/src/components/schema/schemaDiff.ts +32 -0
- package/recce-source/js/src/components/schema/style.css +134 -0
- package/recce-source/js/src/components/screenshot/ScreenshotBox.tsx +39 -0
- package/recce-source/js/src/components/shared/HistoryToggle.tsx +35 -0
- package/recce-source/js/src/components/split/Split.tsx +40 -0
- package/recce-source/js/src/components/split/styles.css +24 -0
- package/recce-source/js/src/components/summary/ChangeSummary.tsx +264 -0
- package/recce-source/js/src/components/summary/SchemaSummary.tsx +123 -0
- package/recce-source/js/src/components/summary/SummaryView.tsx +29 -0
- package/recce-source/js/src/components/timeout/IdleTimeoutBadge.tsx +48 -0
- package/recce-source/js/src/components/top-k/TopKDiffForm.tsx +58 -0
- package/recce-source/js/src/components/top-k/TopKDiffResultView.tsx +73 -0
- package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnGroupHeader.tsx +228 -0
- package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnHeader.tsx +113 -0
- package/recce-source/js/src/components/ui/dataGrid/defaultRenderCell.tsx +72 -0
- package/recce-source/js/src/components/ui/dataGrid/index.ts +23 -0
- package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.test.tsx +607 -0
- package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.tsx +211 -0
- package/recce-source/js/src/components/ui/dataGrid/schemaCells.test.tsx +452 -0
- package/recce-source/js/src/components/ui/dataGrid/schemaCells.tsx +142 -0
- package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.test.tsx +178 -0
- package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.tsx +275 -0
- package/recce-source/js/src/components/ui/markdown/ExternalLinkConfirmDialog.tsx +134 -0
- package/recce-source/js/src/components/ui/markdown/MarkdownContent.tsx +364 -0
- package/recce-source/js/src/components/ui/mui/index.ts +13 -0
- package/recce-source/js/src/components/ui/mui-provider.tsx +67 -0
- package/recce-source/js/src/components/ui/mui-theme.ts +1039 -0
- package/recce-source/js/src/components/ui/mui-utils.ts +113 -0
- package/recce-source/js/src/components/ui/toaster.tsx +288 -0
- package/recce-source/js/src/components/valuediff/ValueDiffDetailResultView.tsx +217 -0
- package/recce-source/js/src/components/valuediff/ValueDiffForm.tsx +246 -0
- package/recce-source/js/src/components/valuediff/ValueDiffResultView.tsx +82 -0
- package/recce-source/js/src/components/valuediff/shared.ts +33 -0
- package/recce-source/js/src/constants/tooltipMessage.ts +3 -0
- package/recce-source/js/src/constants/urls.ts +1 -0
- package/recce-source/js/src/lib/UrlHash.ts +12 -0
- package/recce-source/js/src/lib/api/adhocQuery.ts +70 -0
- package/recce-source/js/src/lib/api/axiosClient.ts +9 -0
- package/recce-source/js/src/lib/api/cacheKeys.ts +13 -0
- package/recce-source/js/src/lib/api/checkEvents.ts +252 -0
- package/recce-source/js/src/lib/api/checks.ts +129 -0
- package/recce-source/js/src/lib/api/cll.ts +53 -0
- package/recce-source/js/src/lib/api/connectToCloud.ts +13 -0
- package/recce-source/js/src/lib/api/flag.ts +37 -0
- package/recce-source/js/src/lib/api/info.ts +198 -0
- package/recce-source/js/src/lib/api/instanceInfo.ts +25 -0
- package/recce-source/js/src/lib/api/keepAlive.ts +108 -0
- package/recce-source/js/src/lib/api/lineagecheck.ts +35 -0
- package/recce-source/js/src/lib/api/localStorageKeys.ts +7 -0
- package/recce-source/js/src/lib/api/models.ts +59 -0
- package/recce-source/js/src/lib/api/profile.ts +65 -0
- package/recce-source/js/src/lib/api/rowcount.ts +19 -0
- package/recce-source/js/src/lib/api/runs.ts +174 -0
- package/recce-source/js/src/lib/api/schemacheck.ts +31 -0
- package/recce-source/js/src/lib/api/select.ts +25 -0
- package/recce-source/js/src/lib/api/sessionStorageKeys.ts +8 -0
- package/recce-source/js/src/lib/api/state.ts +117 -0
- package/recce-source/js/src/lib/api/track.ts +281 -0
- package/recce-source/js/src/lib/api/types.ts +284 -0
- package/recce-source/js/src/lib/api/user.ts +42 -0
- package/recce-source/js/src/lib/api/valuediff.ts +46 -0
- package/recce-source/js/src/lib/api/version.ts +40 -0
- package/recce-source/js/src/lib/const.ts +9 -0
- package/recce-source/js/src/lib/dataGrid/crossFunctionConsistency.test.ts +626 -0
- package/recce-source/js/src/lib/dataGrid/dataGridFactory.test.ts +2140 -0
- package/recce-source/js/src/lib/dataGrid/dataGridFactory.ts +397 -0
- package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.test.ts +132 -0
- package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.ts +126 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.test.ts +1627 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.ts +140 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataGrid.ts +67 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.test.ts +142 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.ts +71 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.test.ts +258 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.ts +153 -0
- package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.test.ts +951 -0
- package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.ts +221 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.test.ts +395 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.ts +184 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.test.ts +884 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.ts +113 -0
- package/recce-source/js/src/lib/dataGrid/index.ts +51 -0
- package/recce-source/js/src/lib/dataGrid/propertyBased.test.ts +858 -0
- package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.test.ts +482 -0
- package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.ts +345 -0
- package/recce-source/js/src/lib/dataGrid/shared/dataTypeEdgeCases.test.ts +698 -0
- package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.test.tsx +820 -0
- package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.tsx +277 -0
- package/recce-source/js/src/lib/dataGrid/shared/gridUtils.test.ts +785 -0
- package/recce-source/js/src/lib/dataGrid/shared/gridUtils.ts +370 -0
- package/recce-source/js/src/lib/dataGrid/shared/index.ts +81 -0
- package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.test.ts +909 -0
- package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.ts +325 -0
- package/recce-source/js/src/lib/dataGrid/shared/simpleColumnBuilder.tsx +240 -0
- package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.test.tsx +719 -0
- package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.tsx +231 -0
- package/recce-source/js/src/lib/dataGrid/shared/validation.test.ts +559 -0
- package/recce-source/js/src/lib/dataGrid/shared/validation.ts +367 -0
- package/recce-source/js/src/lib/dataGrid/warehouseNamingConventions.test.ts +1117 -0
- package/recce-source/js/src/lib/formatSelect.ts +50 -0
- package/recce-source/js/src/lib/hooks/ApiConfigContext.tsx +181 -0
- package/recce-source/js/src/lib/hooks/IdleTimeoutContext.tsx +177 -0
- package/recce-source/js/src/lib/hooks/LineageGraphContext.tsx +512 -0
- package/recce-source/js/src/lib/hooks/RecceActionContext.tsx +269 -0
- package/recce-source/js/src/lib/hooks/RecceCheckContext.tsx +33 -0
- package/recce-source/js/src/lib/hooks/RecceContextProvider.tsx +54 -0
- package/recce-source/js/src/lib/hooks/RecceInstanceContext.tsx +129 -0
- package/recce-source/js/src/lib/hooks/RecceQueryContext.tsx +98 -0
- package/recce-source/js/src/lib/hooks/RecceShareStateContext.tsx +59 -0
- package/recce-source/js/src/lib/hooks/ScreenShot.tsx +399 -0
- package/recce-source/js/src/lib/hooks/useAppRouter.test.ts +211 -0
- package/recce-source/js/src/lib/hooks/useAppRouter.ts +200 -0
- package/recce-source/js/src/lib/hooks/useCheckEvents.ts +99 -0
- package/recce-source/js/src/lib/hooks/useCheckToast.tsx +14 -0
- package/recce-source/js/src/lib/hooks/useClipBoardToast.tsx +27 -0
- package/recce-source/js/src/lib/hooks/useCountdownToast.tsx +102 -0
- package/recce-source/js/src/lib/hooks/useFeedbackCollectionToast.tsx +130 -0
- package/recce-source/js/src/lib/hooks/useGuideToast.tsx +45 -0
- package/recce-source/js/src/lib/hooks/useIdleDetection.tsx +185 -0
- package/recce-source/js/src/lib/hooks/useModelColumns.tsx +113 -0
- package/recce-source/js/src/lib/hooks/useRecceInstanceInfo.tsx +13 -0
- package/recce-source/js/src/lib/hooks/useRecceServerFlag.tsx +13 -0
- package/recce-source/js/src/lib/hooks/useRun.tsx +89 -0
- package/recce-source/js/src/lib/hooks/useThemeColors.ts +115 -0
- package/recce-source/js/src/lib/mergeKeys.test.ts +89 -0
- package/recce-source/js/src/lib/mergeKeys.ts +86 -0
- package/recce-source/js/src/lib/result/ResultErrorFallback.tsx +9 -0
- package/recce-source/js/src/lib/utils/formatTime.ts +84 -0
- package/recce-source/js/src/lib/utils/urls.ts +16 -0
- package/recce-source/js/src/utils/DropdownValuesInput.tsx +297 -0
- package/recce-source/js/src/utils/formatters.tsx +237 -0
- package/recce-source/js/src/utils/transforms.ts +81 -0
- package/recce-source/js/tsconfig.json +47 -0
- package/recce-source/macros/README.md +8 -0
- package/recce-source/macros/recce_athena.sql +73 -0
- package/recce-source/pyproject.toml +109 -0
- package/recce-source/recce/VERSION +1 -0
- package/recce-source/recce/__init__.py +84 -0
- package/recce-source/recce/adapter/__init__.py +0 -0
- package/recce-source/recce/adapter/base.py +109 -0
- package/recce-source/recce/adapter/dbt_adapter/__init__.py +1699 -0
- package/recce-source/recce/adapter/dbt_adapter/dbt_version.py +42 -0
- package/recce-source/recce/adapter/sqlmesh_adapter.py +141 -0
- package/recce-source/recce/apis/__init__.py +0 -0
- package/recce-source/recce/apis/check_api.py +203 -0
- package/recce-source/recce/apis/check_events_api.py +353 -0
- package/recce-source/recce/apis/check_func.py +130 -0
- package/recce-source/recce/apis/run_api.py +130 -0
- package/recce-source/recce/apis/run_func.py +258 -0
- package/recce-source/recce/artifact.py +266 -0
- package/recce-source/recce/cli.py +1846 -0
- package/recce-source/recce/config.py +127 -0
- package/recce-source/recce/connect_to_cloud.py +138 -0
- package/recce-source/recce/core.py +334 -0
- package/recce-source/recce/diff.py +26 -0
- package/recce-source/recce/event/CONFIG +1 -0
- package/recce-source/recce/event/SENTRY_DNS +1 -0
- package/recce-source/recce/event/__init__.py +304 -0
- package/recce-source/recce/event/collector.py +184 -0
- package/recce-source/recce/event/track.py +158 -0
- package/recce-source/recce/exceptions.py +21 -0
- package/recce-source/recce/git.py +77 -0
- package/recce-source/recce/github.py +222 -0
- package/recce-source/recce/mcp_server.py +861 -0
- package/recce-source/recce/models/__init__.py +6 -0
- package/recce-source/recce/models/check.py +473 -0
- package/recce-source/recce/models/run.py +46 -0
- package/recce-source/recce/models/types.py +218 -0
- package/recce-source/recce/pull_request.py +124 -0
- package/recce-source/recce/run.py +390 -0
- package/recce-source/recce/server.py +877 -0
- package/recce-source/recce/state/__init__.py +31 -0
- package/recce-source/recce/state/cloud.py +644 -0
- package/recce-source/recce/state/const.py +26 -0
- package/recce-source/recce/state/local.py +56 -0
- package/recce-source/recce/state/state.py +119 -0
- package/recce-source/recce/state/state_loader.py +174 -0
- package/recce-source/recce/summary.py +575 -0
- package/recce-source/recce/tasks/__init__.py +23 -0
- package/recce-source/recce/tasks/core.py +134 -0
- package/recce-source/recce/tasks/dataframe.py +170 -0
- package/recce-source/recce/tasks/histogram.py +433 -0
- package/recce-source/recce/tasks/lineage.py +19 -0
- package/recce-source/recce/tasks/profile.py +298 -0
- package/recce-source/recce/tasks/query.py +450 -0
- package/recce-source/recce/tasks/rowcount.py +277 -0
- package/recce-source/recce/tasks/schema.py +65 -0
- package/recce-source/recce/tasks/top_k.py +172 -0
- package/recce-source/recce/tasks/utils.py +147 -0
- package/recce-source/recce/tasks/valuediff.py +497 -0
- package/recce-source/recce/util/__init__.py +4 -0
- package/recce-source/recce/util/api_token.py +80 -0
- package/recce-source/recce/util/breaking.py +330 -0
- package/recce-source/recce/util/cache.py +25 -0
- package/recce-source/recce/util/cll.py +355 -0
- package/recce-source/recce/util/cloud/__init__.py +15 -0
- package/recce-source/recce/util/cloud/base.py +115 -0
- package/recce-source/recce/util/cloud/check_events.py +190 -0
- package/recce-source/recce/util/cloud/checks.py +242 -0
- package/recce-source/recce/util/io.py +120 -0
- package/recce-source/recce/util/lineage.py +83 -0
- package/recce-source/recce/util/logger.py +25 -0
- package/recce-source/recce/util/onboarding_state.py +45 -0
- package/recce-source/recce/util/perf_tracking.py +85 -0
- package/recce-source/recce/util/pydantic_model.py +22 -0
- package/recce-source/recce/util/recce_cloud.py +454 -0
- package/recce-source/recce/util/singleton.py +18 -0
- package/recce-source/recce/util/startup_perf.py +121 -0
- package/recce-source/recce/yaml/__init__.py +58 -0
- package/recce-source/recce_cloud/README.md +780 -0
- package/recce-source/recce_cloud/VERSION +1 -0
- package/recce-source/recce_cloud/__init__.py +24 -0
- package/recce-source/recce_cloud/api/__init__.py +17 -0
- package/recce-source/recce_cloud/api/base.py +132 -0
- package/recce-source/recce_cloud/api/client.py +186 -0
- package/recce-source/recce_cloud/api/exceptions.py +26 -0
- package/recce-source/recce_cloud/api/factory.py +63 -0
- package/recce-source/recce_cloud/api/github.py +106 -0
- package/recce-source/recce_cloud/api/gitlab.py +111 -0
- package/recce-source/recce_cloud/artifact.py +57 -0
- package/recce-source/recce_cloud/ci_providers/__init__.py +9 -0
- package/recce-source/recce_cloud/ci_providers/base.py +82 -0
- package/recce-source/recce_cloud/ci_providers/detector.py +147 -0
- package/recce-source/recce_cloud/ci_providers/github_actions.py +136 -0
- package/recce-source/recce_cloud/ci_providers/gitlab_ci.py +130 -0
- package/recce-source/recce_cloud/cli.py +434 -0
- package/recce-source/recce_cloud/download.py +230 -0
- package/recce-source/recce_cloud/hatch_build.py +20 -0
- package/recce-source/recce_cloud/pyproject.toml +49 -0
- package/recce-source/recce_cloud/upload.py +214 -0
- package/recce-source/test.py +0 -0
- package/recce-source/tests/__init__.py +0 -0
- package/recce-source/tests/adapter/__init__.py +0 -0
- package/recce-source/tests/adapter/dbt_adapter/__init__.py +0 -0
- package/recce-source/tests/adapter/dbt_adapter/conftest.py +17 -0
- package/recce-source/tests/adapter/dbt_adapter/dbt_test_helper.py +298 -0
- package/recce-source/tests/adapter/dbt_adapter/test_dbt_adapter.py +25 -0
- package/recce-source/tests/adapter/dbt_adapter/test_dbt_cll.py +717 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/dbt_project.yml +4 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/manifest.json +1 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/package-lock.yml +8 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/packages.yml +7 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/profiles.yml +6 -0
- package/recce-source/tests/adapter/dbt_adapter/test_selector.py +205 -0
- package/recce-source/tests/apis/__init__.py +0 -0
- package/recce-source/tests/apis/row_count_diff.json +59 -0
- package/recce-source/tests/apis/test_check_events_api.py +615 -0
- package/recce-source/tests/apis/test_run_func.py +433 -0
- package/recce-source/tests/catalog.json +527 -0
- package/recce-source/tests/data/manifest/base/catalog.json +1 -0
- package/recce-source/tests/data/manifest/base/manifest.json +1 -0
- package/recce-source/tests/data/manifest/pr2/catalog.json +1 -0
- package/recce-source/tests/data/manifest/pr2/manifest.json +1 -0
- package/recce-source/tests/manifest.json +10655 -0
- package/recce-source/tests/models/__init__.py +0 -0
- package/recce-source/tests/models/test_check.py +731 -0
- package/recce-source/tests/models/test_run_models.py +295 -0
- package/recce-source/tests/recce_cloud/__init__.py +0 -0
- package/recce-source/tests/recce_cloud/test_ci_providers.py +351 -0
- package/recce-source/tests/recce_cloud/test_cli.py +735 -0
- package/recce-source/tests/recce_cloud/test_client.py +379 -0
- package/recce-source/tests/recce_cloud/test_platform_clients.py +483 -0
- package/recce-source/tests/recce_state.json +1 -0
- package/recce-source/tests/state/test_cloud.py +719 -0
- package/recce-source/tests/state/test_local.py +164 -0
- package/recce-source/tests/state/test_state_loader.py +211 -0
- package/recce-source/tests/tasks/__init__.py +0 -0
- package/recce-source/tests/tasks/conftest.py +4 -0
- package/recce-source/tests/tasks/test_histogram.py +129 -0
- package/recce-source/tests/tasks/test_lineage.py +55 -0
- package/recce-source/tests/tasks/test_preset_checks.py +64 -0
- package/recce-source/tests/tasks/test_profile.py +397 -0
- package/recce-source/tests/tasks/test_query.py +528 -0
- package/recce-source/tests/tasks/test_row_count.py +133 -0
- package/recce-source/tests/tasks/test_schema.py +122 -0
- package/recce-source/tests/tasks/test_top_k.py +77 -0
- package/recce-source/tests/tasks/test_utils.py +439 -0
- package/recce-source/tests/tasks/test_valuediff.py +361 -0
- package/recce-source/tests/test_cli.py +236 -0
- package/recce-source/tests/test_cli_mcp_optional.py +45 -0
- package/recce-source/tests/test_cloud_listing_cli.py +324 -0
- package/recce-source/tests/test_config.py +43 -0
- package/recce-source/tests/test_connect_to_cloud.py +82 -0
- package/recce-source/tests/test_core.py +174 -0
- package/recce-source/tests/test_dbt.py +36 -0
- package/recce-source/tests/test_mcp_server.py +505 -0
- package/recce-source/tests/test_pull_request.py +130 -0
- package/recce-source/tests/test_server.py +202 -0
- package/recce-source/tests/test_server_lifespan.py +138 -0
- package/recce-source/tests/test_summary.py +73 -0
- package/recce-source/tests/util/__init__.py +0 -0
- package/recce-source/tests/util/cloud/__init__.py +0 -0
- package/recce-source/tests/util/cloud/test_check_events.py +255 -0
- package/recce-source/tests/util/cloud/test_checks.py +204 -0
- package/recce-source/tests/util/test_api_token.py +119 -0
- package/recce-source/tests/util/test_breaking.py +1427 -0
- package/recce-source/tests/util/test_cll.py +706 -0
- package/recce-source/tests/util/test_lineage.py +122 -0
- package/recce-source/tests/util/test_onboarding_state.py +84 -0
- package/recce-source/tests/util/test_recce_cloud.py +231 -0
- package/recce-source/tox.ini +40 -0
- package/recce-source/uv.lock +3928 -0
- package/src/api/index.ts +32 -0
- package/src/components/index.ts +154 -0
- package/src/global.d.ts +14 -0
- package/src/hooks/index.ts +56 -0
- package/src/index.ts +17 -0
- package/src/lib/hooks/RouteConfigContext.ts +139 -0
- package/src/lib/hooks/useAppRouter.ts +240 -0
- package/src/mui-augmentation.d.ts +139 -0
- package/src/theme/index.ts +13 -0
- package/src/theme.ts +23 -0
- package/src/types/index.ts +23 -0
- package/dist/index-BNUP2V_N.d.ts.map +0 -1
- package/dist/index-DOPZuhD8.d.mts.map +0 -1
- package/dist/theme.d.mts.map +0 -1
- package/dist/theme.d.ts.map +0 -1
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file propertyBased.test.ts
|
|
3
|
+
* @description Property-based tests for data grid functions using fast-check 4.4.0
|
|
4
|
+
*
|
|
5
|
+
* These tests verify invariants that should hold for ANY valid input,
|
|
6
|
+
* automatically generating test cases and shrinking failures to minimal examples.
|
|
7
|
+
*
|
|
8
|
+
* Key invariants tested:
|
|
9
|
+
* - Structural consistency: output rows have consistent shape
|
|
10
|
+
* - Row count preservation: no data loss or duplication
|
|
11
|
+
* - Primary key uniqueness: PK values are unique in output
|
|
12
|
+
* - Diff symmetry: identical inputs produce no modifications
|
|
13
|
+
* - Column alignment: output columns match input schema
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ColDef, ColGroupDef } from "ag-grid-community";
|
|
17
|
+
import fc from "fast-check";
|
|
18
|
+
import {
|
|
19
|
+
ColumnRenderMode,
|
|
20
|
+
ColumnType,
|
|
21
|
+
DataFrame,
|
|
22
|
+
RowObjectType,
|
|
23
|
+
} from "@/lib/api/types";
|
|
24
|
+
import { toDataDiffGrid } from "@/lib/dataGrid/generators/toDataDiffGrid";
|
|
25
|
+
import { toDataGrid } from "@/lib/dataGrid/generators/toDataGrid";
|
|
26
|
+
import { toValueDiffGrid } from "@/lib/dataGrid/generators/toValueDiffGrid";
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Types
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extended column type returned by grid functions (AG Grid)
|
|
34
|
+
*/
|
|
35
|
+
type ExtendedColumn = (ColDef<RowObjectType> | ColGroupDef<RowObjectType>) & {
|
|
36
|
+
columnType?: ColumnType;
|
|
37
|
+
columnRenderMode?: ColumnRenderMode;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Mocks
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
jest.mock("ag-grid-community", () => ({
|
|
45
|
+
ModuleRegistry: { registerModules: jest.fn() },
|
|
46
|
+
AllCommunityModule: {},
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
jest.mock("@/components/ui/mui", () => ({
|
|
50
|
+
Box: ({ children }: { children: React.ReactNode }) => children,
|
|
51
|
+
Flex: ({ children }: { children: React.ReactNode }) => children,
|
|
52
|
+
Icon: () => null,
|
|
53
|
+
IconButton: () => null,
|
|
54
|
+
Menu: {
|
|
55
|
+
Root: ({ children }: { children: React.ReactNode }) => children,
|
|
56
|
+
Trigger: ({ children }: { children: React.ReactNode }) => children,
|
|
57
|
+
Content: ({ children }: { children: React.ReactNode }) => children,
|
|
58
|
+
Item: ({ children }: { children: React.ReactNode }) => children,
|
|
59
|
+
ItemGroup: ({ children }: { children: React.ReactNode }) => children,
|
|
60
|
+
Positioner: ({ children }: { children: React.ReactNode }) => children,
|
|
61
|
+
},
|
|
62
|
+
Portal: ({ children }: { children: React.ReactNode }) => children,
|
|
63
|
+
Text: ({ children }: { children: React.ReactNode }) => children,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Custom Arbitraries for Data Grid Types
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Arbitrary for ColumnType enum values
|
|
72
|
+
*/
|
|
73
|
+
const columnTypeArb: fc.Arbitrary<ColumnType> = fc.constantFrom(
|
|
74
|
+
"number",
|
|
75
|
+
"integer",
|
|
76
|
+
"text",
|
|
77
|
+
"boolean",
|
|
78
|
+
"date",
|
|
79
|
+
"datetime",
|
|
80
|
+
"timedelta",
|
|
81
|
+
"unknown",
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Arbitrary for column names - valid identifiers without special chars
|
|
86
|
+
*/
|
|
87
|
+
const columnNameArb: fc.Arbitrary<string> = fc
|
|
88
|
+
.stringMatching(/^[a-z][a-z0-9_]{0,19}$/)
|
|
89
|
+
.filter((s) => s.length > 0 && s !== "id");
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Arbitrary for cell values based on column type
|
|
93
|
+
* Excludes NaN/Infinity by default for cleaner property tests
|
|
94
|
+
*/
|
|
95
|
+
function cellValueArb(colType: ColumnType): fc.Arbitrary<unknown> {
|
|
96
|
+
switch (colType) {
|
|
97
|
+
case "integer":
|
|
98
|
+
return fc.oneof(
|
|
99
|
+
fc.integer({ min: -1000000, max: 1000000 }),
|
|
100
|
+
fc.constant(null),
|
|
101
|
+
);
|
|
102
|
+
case "number":
|
|
103
|
+
return fc.oneof(
|
|
104
|
+
fc.double({
|
|
105
|
+
noNaN: true,
|
|
106
|
+
noDefaultInfinity: true,
|
|
107
|
+
min: -1e6,
|
|
108
|
+
max: 1e6,
|
|
109
|
+
}),
|
|
110
|
+
fc.constant(null),
|
|
111
|
+
);
|
|
112
|
+
case "text":
|
|
113
|
+
return fc.oneof(
|
|
114
|
+
fc.string({ minLength: 0, maxLength: 50 }),
|
|
115
|
+
fc.constant(null),
|
|
116
|
+
);
|
|
117
|
+
case "boolean":
|
|
118
|
+
return fc.oneof(fc.boolean(), fc.constant(null));
|
|
119
|
+
case "date":
|
|
120
|
+
return fc.oneof(
|
|
121
|
+
fc
|
|
122
|
+
.date({ noInvalidDate: true })
|
|
123
|
+
.map((d) => d.toISOString().split("T")[0]),
|
|
124
|
+
fc.constant(null),
|
|
125
|
+
);
|
|
126
|
+
case "datetime":
|
|
127
|
+
return fc.oneof(
|
|
128
|
+
fc.date({ noInvalidDate: true }).map((d) => d.toISOString()),
|
|
129
|
+
fc.constant(null),
|
|
130
|
+
);
|
|
131
|
+
case "timedelta":
|
|
132
|
+
return fc.oneof(
|
|
133
|
+
fc
|
|
134
|
+
.tuple(fc.nat({ max: 23 }), fc.nat({ max: 59 }), fc.nat({ max: 59 }))
|
|
135
|
+
.map(
|
|
136
|
+
([h, m, s]) =>
|
|
137
|
+
`${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`,
|
|
138
|
+
),
|
|
139
|
+
fc.constant(null),
|
|
140
|
+
);
|
|
141
|
+
case "unknown":
|
|
142
|
+
default:
|
|
143
|
+
return fc.oneof(
|
|
144
|
+
fc.string({ maxLength: 20 }),
|
|
145
|
+
fc.integer(),
|
|
146
|
+
fc.boolean(),
|
|
147
|
+
fc.constant(null),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Arbitrary for unique primary key values (integers)
|
|
154
|
+
*/
|
|
155
|
+
const primaryKeyValueArb: fc.Arbitrary<number> = fc.integer({
|
|
156
|
+
min: 1,
|
|
157
|
+
max: 100000,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Configuration for DataFrame generation
|
|
162
|
+
*/
|
|
163
|
+
interface DataFrameConfig {
|
|
164
|
+
minColumns?: number;
|
|
165
|
+
maxColumns?: number;
|
|
166
|
+
minRows?: number;
|
|
167
|
+
maxRows?: number;
|
|
168
|
+
includePrimaryKey?: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Arbitrary for a complete DataFrame with consistent structure
|
|
173
|
+
*
|
|
174
|
+
* Generates columns with types, then generates row data that matches
|
|
175
|
+
* the column types. Optionally includes a guaranteed integer primary key.
|
|
176
|
+
*/
|
|
177
|
+
function dataFrameArb(config: DataFrameConfig = {}): fc.Arbitrary<DataFrame> {
|
|
178
|
+
const {
|
|
179
|
+
minColumns = 1,
|
|
180
|
+
maxColumns = 5,
|
|
181
|
+
minRows = 0,
|
|
182
|
+
maxRows = 20,
|
|
183
|
+
includePrimaryKey = true,
|
|
184
|
+
} = config;
|
|
185
|
+
|
|
186
|
+
return fc
|
|
187
|
+
.tuple(
|
|
188
|
+
// Generate unique column names
|
|
189
|
+
fc.uniqueArray(columnNameArb, {
|
|
190
|
+
minLength: minColumns,
|
|
191
|
+
maxLength: maxColumns,
|
|
192
|
+
}),
|
|
193
|
+
// Generate column types for each column
|
|
194
|
+
fc.array(columnTypeArb, { minLength: minColumns, maxLength: maxColumns }),
|
|
195
|
+
// Number of rows
|
|
196
|
+
fc.nat({ max: maxRows - minRows }),
|
|
197
|
+
)
|
|
198
|
+
.chain(([names, types, extraRows]) => {
|
|
199
|
+
// Ensure we have matching lengths
|
|
200
|
+
const columnCount = Math.min(names.length, types.length);
|
|
201
|
+
const trimmedNames = names.slice(0, columnCount);
|
|
202
|
+
const trimmedTypes = types.slice(0, columnCount);
|
|
203
|
+
const rowCount = minRows + extraRows;
|
|
204
|
+
|
|
205
|
+
// Build column definitions
|
|
206
|
+
// Filter out "id" from generated names when includePrimaryKey is true
|
|
207
|
+
// to avoid duplicate column keys
|
|
208
|
+
const filteredNames = includePrimaryKey
|
|
209
|
+
? trimmedNames.filter((name) => name !== "id")
|
|
210
|
+
: trimmedNames;
|
|
211
|
+
const filteredTypes = includePrimaryKey
|
|
212
|
+
? trimmedTypes.filter((_, i) => trimmedNames[i] !== "id")
|
|
213
|
+
: trimmedTypes;
|
|
214
|
+
|
|
215
|
+
const columns: DataFrame["columns"] = includePrimaryKey
|
|
216
|
+
? [
|
|
217
|
+
{ key: "id", name: "id", type: "integer" },
|
|
218
|
+
...filteredNames.map((name, i) => ({
|
|
219
|
+
key: name,
|
|
220
|
+
name: name,
|
|
221
|
+
type: filteredTypes[i],
|
|
222
|
+
})),
|
|
223
|
+
]
|
|
224
|
+
: filteredNames.map((name, i) => ({
|
|
225
|
+
key: name,
|
|
226
|
+
name: name,
|
|
227
|
+
type: filteredTypes[i],
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
// Generate row data matching column types
|
|
231
|
+
// Primary key uses dedicated arbitrary (non-null, unique), other columns use type-based
|
|
232
|
+
const rowArb = includePrimaryKey
|
|
233
|
+
? fc.tuple(primaryKeyValueArb, ...filteredTypes.map(cellValueArb))
|
|
234
|
+
: fc.tuple(...filteredTypes.map(cellValueArb));
|
|
235
|
+
|
|
236
|
+
return fc
|
|
237
|
+
.uniqueArray(rowArb, {
|
|
238
|
+
minLength: rowCount,
|
|
239
|
+
maxLength: rowCount,
|
|
240
|
+
comparator: (a, b) => {
|
|
241
|
+
// Ensure unique primary keys when included
|
|
242
|
+
if (includePrimaryKey) {
|
|
243
|
+
return a[0] === b[0];
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
.map((data) => ({
|
|
249
|
+
columns,
|
|
250
|
+
data: data as DataFrame["data"],
|
|
251
|
+
}));
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Arbitrary for in_a/in_b flag pairs
|
|
257
|
+
* At least one must be true (rows with both false are invalid - not in either dataset)
|
|
258
|
+
*/
|
|
259
|
+
const validInFlagsArb: fc.Arbitrary<[boolean, boolean]> = fc.constantFrom(
|
|
260
|
+
[true, true] as [boolean, boolean], // In both (unchanged or modified)
|
|
261
|
+
[true, false] as [boolean, boolean], // In base only (removed)
|
|
262
|
+
[false, true] as [boolean, boolean], // In current only (added)
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Arbitrary for joined DataFrame (used by toValueDiffGrid)
|
|
267
|
+
*
|
|
268
|
+
* The input format for toValueDiffGrid is:
|
|
269
|
+
* - Columns: [pk, ...valueColumns, in_a, in_b]
|
|
270
|
+
* - Data: [pkValue, ...values, inA, inB]
|
|
271
|
+
*
|
|
272
|
+
* For modified rows, there are TWO input rows with the same PK:
|
|
273
|
+
* - One with in_a=true, in_b=false (base version)
|
|
274
|
+
* - One with in_a=false, in_b=true (current version)
|
|
275
|
+
*
|
|
276
|
+
* The function then merges these into base__/current__ prefixed output columns.
|
|
277
|
+
*/
|
|
278
|
+
function joinedDataFrameArb(config: DataFrameConfig = {}): fc.Arbitrary<{
|
|
279
|
+
dataframe: DataFrame;
|
|
280
|
+
primaryKeys: string[];
|
|
281
|
+
}> {
|
|
282
|
+
const { minRows = 1, maxRows = 20 } = config;
|
|
283
|
+
|
|
284
|
+
return fc
|
|
285
|
+
.tuple(
|
|
286
|
+
// Generate 1-3 value column names
|
|
287
|
+
fc.uniqueArray(columnNameArb, { minLength: 1, maxLength: 3 }),
|
|
288
|
+
// Generate column types
|
|
289
|
+
fc.array(columnTypeArb, { minLength: 1, maxLength: 3 }),
|
|
290
|
+
// Number of unique PKs
|
|
291
|
+
fc.nat({ max: Math.max(0, maxRows - minRows) }),
|
|
292
|
+
)
|
|
293
|
+
.chain(([names, types, extraRows]) => {
|
|
294
|
+
const columnCount = Math.min(names.length, types.length);
|
|
295
|
+
const trimmedNames = names.slice(0, columnCount);
|
|
296
|
+
const trimmedTypes = types.slice(0, columnCount);
|
|
297
|
+
const pkCount = minRows + extraRows;
|
|
298
|
+
|
|
299
|
+
// Build column definitions: [id, ...valueColumns, in_a, in_b]
|
|
300
|
+
const columns: DataFrame["columns"] = [
|
|
301
|
+
{ key: "id", name: "id", type: "integer" },
|
|
302
|
+
...trimmedNames.map((name, i) => ({
|
|
303
|
+
key: name,
|
|
304
|
+
name: name,
|
|
305
|
+
type: trimmedTypes[i],
|
|
306
|
+
})),
|
|
307
|
+
{ key: "in_a", name: "in_a", type: "boolean" },
|
|
308
|
+
{ key: "in_b", name: "in_b", type: "boolean" },
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
// Generate unique PK values
|
|
312
|
+
return fc
|
|
313
|
+
.uniqueArray(primaryKeyValueArb, {
|
|
314
|
+
minLength: pkCount,
|
|
315
|
+
maxLength: pkCount,
|
|
316
|
+
})
|
|
317
|
+
.chain((pkValues) => {
|
|
318
|
+
// For each PK, generate status and values
|
|
319
|
+
const rowGenArb = fc.tuple(
|
|
320
|
+
validInFlagsArb,
|
|
321
|
+
fc.tuple(...trimmedTypes.map(cellValueArb)),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return fc
|
|
325
|
+
.array(rowGenArb, { minLength: pkCount, maxLength: pkCount })
|
|
326
|
+
.map((rowSpecs) => {
|
|
327
|
+
const data: unknown[][] = [];
|
|
328
|
+
|
|
329
|
+
pkValues.forEach((pk, i) => {
|
|
330
|
+
const [[inA, inB], values] = rowSpecs[i];
|
|
331
|
+
|
|
332
|
+
if (inA && inB) {
|
|
333
|
+
// Row in both - single row with in_a=true, in_b=true
|
|
334
|
+
data.push([pk, ...values, true, true]);
|
|
335
|
+
} else if (inA && !inB) {
|
|
336
|
+
// Removed - single row with in_a=true, in_b=false
|
|
337
|
+
data.push([pk, ...values, true, false]);
|
|
338
|
+
} else if (!inA && inB) {
|
|
339
|
+
// Added - single row with in_a=false, in_b=true
|
|
340
|
+
data.push([pk, ...values, false, true]);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
dataframe: {
|
|
346
|
+
columns,
|
|
347
|
+
data: data as DataFrame["data"],
|
|
348
|
+
},
|
|
349
|
+
primaryKeys: ["id"],
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Helper Functions
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Extract column field from a column or column group
|
|
362
|
+
* Fields are lowercased for consistent comparison
|
|
363
|
+
*/
|
|
364
|
+
function getColumnKey(col: ExtendedColumn): string | undefined {
|
|
365
|
+
if ("field" in col && col.field) {
|
|
366
|
+
return col.field.toLowerCase();
|
|
367
|
+
}
|
|
368
|
+
if ("children" in col && Array.isArray(col.children) && col.children[0]) {
|
|
369
|
+
const firstChild = col.children[0] as ColDef<RowObjectType>;
|
|
370
|
+
if ("field" in firstChild && firstChild.field) {
|
|
371
|
+
// Extract base column name from "base__colname"
|
|
372
|
+
const field = firstChild.field;
|
|
373
|
+
if (field.startsWith("base__")) {
|
|
374
|
+
return field.replace("base__", "").toLowerCase();
|
|
375
|
+
}
|
|
376
|
+
return field.toLowerCase();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Extract all column keys from grid result, handling column groups
|
|
384
|
+
* Keys are lowercased for consistent comparison
|
|
385
|
+
*/
|
|
386
|
+
function getResultColumnKeys(columns: ExtendedColumn[]): string[] {
|
|
387
|
+
return columns.map(getColumnKey).filter((k): k is string => k !== undefined);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Check if all rows have consistent keys
|
|
392
|
+
*/
|
|
393
|
+
function rowsHaveConsistentKeys(rows: RowObjectType[]): boolean {
|
|
394
|
+
if (rows.length === 0) return true;
|
|
395
|
+
|
|
396
|
+
const referenceKeys = new Set(
|
|
397
|
+
Object.keys(rows[0]).filter((k) => !k.startsWith("__") && k !== "_index"),
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
return rows.every((row) => {
|
|
401
|
+
const rowKeys = new Set(
|
|
402
|
+
Object.keys(row).filter((k) => !k.startsWith("__") && k !== "_index"),
|
|
403
|
+
);
|
|
404
|
+
if (rowKeys.size !== referenceKeys.size) return false;
|
|
405
|
+
for (const key of referenceKeys) {
|
|
406
|
+
if (!rowKeys.has(key)) return false;
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Property-Based Tests: toDataGrid
|
|
414
|
+
// ============================================================================
|
|
415
|
+
|
|
416
|
+
describe("Property-based tests: toDataGrid", () => {
|
|
417
|
+
test("produces correct row count for any valid DataFrame", () => {
|
|
418
|
+
fc.assert(
|
|
419
|
+
fc.property(dataFrameArb({ minRows: 0, maxRows: 50 }), (df) => {
|
|
420
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
421
|
+
return result.rows.length === df.data.length;
|
|
422
|
+
}),
|
|
423
|
+
{ numRuns: 100 },
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("all rows have consistent structure", () => {
|
|
428
|
+
fc.assert(
|
|
429
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 30 }), (df) => {
|
|
430
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
431
|
+
return rowsHaveConsistentKeys(result.rows);
|
|
432
|
+
}),
|
|
433
|
+
{ numRuns: 100 },
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("row data contains all column keys from input", () => {
|
|
438
|
+
fc.assert(
|
|
439
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 20 }), (df) => {
|
|
440
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
441
|
+
const inputKeys = new Set(df.columns.map((c) => c.key.toLowerCase()));
|
|
442
|
+
|
|
443
|
+
// Every row should have all input column keys
|
|
444
|
+
return result.rows.every((row) => {
|
|
445
|
+
for (const key of inputKeys) {
|
|
446
|
+
if (!(key in row)) return false;
|
|
447
|
+
}
|
|
448
|
+
return true;
|
|
449
|
+
});
|
|
450
|
+
}),
|
|
451
|
+
{ numRuns: 100 },
|
|
452
|
+
);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("primary key values are preserved in output rows", () => {
|
|
456
|
+
fc.assert(
|
|
457
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 30 }), (df) => {
|
|
458
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
459
|
+
const inputPKs = new Set(df.data.map((row) => row[0]));
|
|
460
|
+
const outputPKs = new Set(result.rows.map((row) => row.id));
|
|
461
|
+
|
|
462
|
+
// All input PKs should appear in output
|
|
463
|
+
for (const pk of inputPKs) {
|
|
464
|
+
if (!outputPKs.has(pk as number)) return false;
|
|
465
|
+
}
|
|
466
|
+
return true;
|
|
467
|
+
}),
|
|
468
|
+
{ numRuns: 100 },
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("handles empty DataFrame without error", () => {
|
|
473
|
+
fc.assert(
|
|
474
|
+
fc.property(
|
|
475
|
+
dataFrameArb({ minRows: 0, maxRows: 0, minColumns: 1 }),
|
|
476
|
+
(df) => {
|
|
477
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
478
|
+
return result.rows.length === 0 && result.columns.length > 0;
|
|
479
|
+
},
|
|
480
|
+
),
|
|
481
|
+
{ numRuns: 50 },
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("__status is undefined for single DataFrame (no diff)", () => {
|
|
486
|
+
fc.assert(
|
|
487
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 20 }), (df) => {
|
|
488
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
489
|
+
return result.rows.every((row) => row.__status === undefined);
|
|
490
|
+
}),
|
|
491
|
+
{ numRuns: 100 },
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("output columns include all input column keys", () => {
|
|
496
|
+
fc.assert(
|
|
497
|
+
fc.property(
|
|
498
|
+
dataFrameArb({ minRows: 1, maxRows: 10, minColumns: 2 }),
|
|
499
|
+
(df) => {
|
|
500
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
501
|
+
const resultKeys = getResultColumnKeys(result.columns);
|
|
502
|
+
const inputKeys = df.columns.map((c) => c.key.toLowerCase());
|
|
503
|
+
|
|
504
|
+
// All input column keys should appear in result columns
|
|
505
|
+
return inputKeys.every((key) => resultKeys.includes(key));
|
|
506
|
+
},
|
|
507
|
+
),
|
|
508
|
+
{ numRuns: 100 },
|
|
509
|
+
);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// ============================================================================
|
|
514
|
+
// Property-Based Tests: toDataDiffGrid
|
|
515
|
+
// ============================================================================
|
|
516
|
+
|
|
517
|
+
describe("Property-based tests: toDataDiffGrid", () => {
|
|
518
|
+
test("identical DataFrames produce no modified/added/removed rows", () => {
|
|
519
|
+
fc.assert(
|
|
520
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 30 }), (df) => {
|
|
521
|
+
const result = toDataDiffGrid(df, df, { primaryKeys: ["id"] });
|
|
522
|
+
|
|
523
|
+
// All rows should have undefined status (unchanged)
|
|
524
|
+
return result.rows.every((row) => row.__status === undefined);
|
|
525
|
+
}),
|
|
526
|
+
{ numRuns: 100 },
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("row count with identical inputs equals input row count", () => {
|
|
531
|
+
fc.assert(
|
|
532
|
+
fc.property(dataFrameArb({ minRows: 0, maxRows: 30 }), (df) => {
|
|
533
|
+
const result = toDataDiffGrid(df, df, { primaryKeys: ["id"] });
|
|
534
|
+
return result.rows.length === df.data.length;
|
|
535
|
+
}),
|
|
536
|
+
{ numRuns: 100 },
|
|
537
|
+
);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("empty base produces all 'added' rows", () => {
|
|
541
|
+
fc.assert(
|
|
542
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 20 }), (df) => {
|
|
543
|
+
const emptyBase: DataFrame = { columns: df.columns, data: [] };
|
|
544
|
+
const result = toDataDiffGrid(emptyBase, df, { primaryKeys: ["id"] });
|
|
545
|
+
|
|
546
|
+
return (
|
|
547
|
+
result.rows.length === df.data.length &&
|
|
548
|
+
result.rows.every((row) => row.__status === "added")
|
|
549
|
+
);
|
|
550
|
+
}),
|
|
551
|
+
{ numRuns: 100 },
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test("empty current produces all 'removed' rows", () => {
|
|
556
|
+
fc.assert(
|
|
557
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 20 }), (df) => {
|
|
558
|
+
const emptyCurrent: DataFrame = { columns: df.columns, data: [] };
|
|
559
|
+
const result = toDataDiffGrid(df, emptyCurrent, {
|
|
560
|
+
primaryKeys: ["id"],
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
return (
|
|
564
|
+
result.rows.length === df.data.length &&
|
|
565
|
+
result.rows.every((row) => row.__status === "removed")
|
|
566
|
+
);
|
|
567
|
+
}),
|
|
568
|
+
{ numRuns: 100 },
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("all rows have both base__ and current__ prefixed columns", () => {
|
|
573
|
+
fc.assert(
|
|
574
|
+
fc.property(
|
|
575
|
+
dataFrameArb({ minRows: 1, maxRows: 20, minColumns: 2 }),
|
|
576
|
+
(df) => {
|
|
577
|
+
const result = toDataDiffGrid(df, df, {
|
|
578
|
+
primaryKeys: ["id"],
|
|
579
|
+
displayMode: "side_by_side",
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// For side_by_side mode, non-PK columns should have base__/current__ versions
|
|
583
|
+
const valueColumns = df.columns.slice(1); // Skip 'id'
|
|
584
|
+
return result.rows.every((row) =>
|
|
585
|
+
valueColumns.every((col) => {
|
|
586
|
+
const baseKey = `base__${col.key}`.toLowerCase();
|
|
587
|
+
const currentKey = `current__${col.key}`.toLowerCase();
|
|
588
|
+
return baseKey in row && currentKey in row;
|
|
589
|
+
}),
|
|
590
|
+
);
|
|
591
|
+
},
|
|
592
|
+
),
|
|
593
|
+
{ numRuns: 100 },
|
|
594
|
+
);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("changedOnly=true with identical inputs returns empty rows", () => {
|
|
598
|
+
fc.assert(
|
|
599
|
+
fc.property(dataFrameArb({ minRows: 1, maxRows: 20 }), (df) => {
|
|
600
|
+
const result = toDataDiffGrid(df, df, {
|
|
601
|
+
primaryKeys: ["id"],
|
|
602
|
+
changedOnly: true,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// No changes means no rows when changedOnly is true
|
|
606
|
+
return result.rows.length === 0;
|
|
607
|
+
}),
|
|
608
|
+
{ numRuns: 100 },
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test("total rows = base-only + current-only + matched", () => {
|
|
613
|
+
fc.assert(
|
|
614
|
+
fc.property(
|
|
615
|
+
dataFrameArb({ minRows: 1, maxRows: 15 }),
|
|
616
|
+
dataFrameArb({ minRows: 1, maxRows: 15 }),
|
|
617
|
+
(base, current) => {
|
|
618
|
+
const result = toDataDiffGrid(base, current, { primaryKeys: ["id"] });
|
|
619
|
+
|
|
620
|
+
const basePKs = new Set(base.data.map((r) => r[0]));
|
|
621
|
+
const currentPKs = new Set(current.data.map((r) => r[0]));
|
|
622
|
+
|
|
623
|
+
// Count expected rows
|
|
624
|
+
const baseOnly = [...basePKs].filter(
|
|
625
|
+
(pk) => !currentPKs.has(pk),
|
|
626
|
+
).length;
|
|
627
|
+
const currentOnly = [...currentPKs].filter(
|
|
628
|
+
(pk) => !basePKs.has(pk),
|
|
629
|
+
).length;
|
|
630
|
+
const matched = [...basePKs].filter((pk) =>
|
|
631
|
+
currentPKs.has(pk),
|
|
632
|
+
).length;
|
|
633
|
+
|
|
634
|
+
const expectedTotal = baseOnly + currentOnly + matched;
|
|
635
|
+
return result.rows.length === expectedTotal;
|
|
636
|
+
},
|
|
637
|
+
),
|
|
638
|
+
{ numRuns: 100 },
|
|
639
|
+
);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ============================================================================
|
|
644
|
+
// Property-Based Tests: toValueDiffGrid
|
|
645
|
+
// ============================================================================
|
|
646
|
+
|
|
647
|
+
describe("Property-based tests: toValueDiffGrid", () => {
|
|
648
|
+
test("row count matches input DataFrame row count", () => {
|
|
649
|
+
fc.assert(
|
|
650
|
+
fc.property(
|
|
651
|
+
joinedDataFrameArb({ minRows: 1, maxRows: 30 }),
|
|
652
|
+
({ dataframe, primaryKeys }) => {
|
|
653
|
+
const result = toValueDiffGrid(dataframe, primaryKeys, {});
|
|
654
|
+
return result.rows.length === dataframe.data.length;
|
|
655
|
+
},
|
|
656
|
+
),
|
|
657
|
+
{ numRuns: 100 },
|
|
658
|
+
);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test("primary key values are unique in output", () => {
|
|
662
|
+
fc.assert(
|
|
663
|
+
fc.property(
|
|
664
|
+
joinedDataFrameArb({ minRows: 1, maxRows: 30 }),
|
|
665
|
+
({ dataframe, primaryKeys }) => {
|
|
666
|
+
const result = toValueDiffGrid(dataframe, primaryKeys, {});
|
|
667
|
+
const pkValues = result.rows.map((row) => row[primaryKeys[0]]);
|
|
668
|
+
const uniquePKs = new Set(pkValues);
|
|
669
|
+
return uniquePKs.size === pkValues.length;
|
|
670
|
+
},
|
|
671
|
+
),
|
|
672
|
+
{ numRuns: 100 },
|
|
673
|
+
);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ============================================================================
|
|
678
|
+
// Property-Based Tests: Edge Cases with Special Values
|
|
679
|
+
// ============================================================================
|
|
680
|
+
|
|
681
|
+
describe("Property-based tests: special values", () => {
|
|
682
|
+
/**
|
|
683
|
+
* Arbitrary that includes NaN and Infinity for stress testing
|
|
684
|
+
*/
|
|
685
|
+
const numericWithSpecialsArb = fc.oneof(
|
|
686
|
+
fc.double({ noNaN: true, noDefaultInfinity: true }),
|
|
687
|
+
fc.constant(NaN),
|
|
688
|
+
fc.constant(Infinity),
|
|
689
|
+
fc.constant(-Infinity),
|
|
690
|
+
fc.constant(null),
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
test("toDataGrid handles any numeric value without throwing", () => {
|
|
694
|
+
fc.assert(
|
|
695
|
+
fc.property(
|
|
696
|
+
fc.array(numericWithSpecialsArb, { minLength: 1, maxLength: 20 }),
|
|
697
|
+
(values) => {
|
|
698
|
+
const df: DataFrame = {
|
|
699
|
+
columns: [
|
|
700
|
+
{ key: "id", name: "id", type: "integer" },
|
|
701
|
+
{ key: "value", name: "value", type: "number" },
|
|
702
|
+
],
|
|
703
|
+
data: values.map((v, i) => [i + 1, v]),
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// Should not throw
|
|
707
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
708
|
+
return result.rows.length === values.length;
|
|
709
|
+
},
|
|
710
|
+
),
|
|
711
|
+
{ numRuns: 100 },
|
|
712
|
+
);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("toDataDiffGrid handles NaN comparison correctly", () => {
|
|
716
|
+
fc.assert(
|
|
717
|
+
fc.property(fc.nat({ max: 100 }), (seed) => {
|
|
718
|
+
// NaN === NaN should be true via lodash.isEqual
|
|
719
|
+
const df: DataFrame = {
|
|
720
|
+
columns: [
|
|
721
|
+
{ key: "id", name: "id", type: "integer" },
|
|
722
|
+
{ key: "value", name: "value", type: "number" },
|
|
723
|
+
],
|
|
724
|
+
data: [[seed, NaN]],
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const result = toDataDiffGrid(df, df, { primaryKeys: ["id"] });
|
|
728
|
+
|
|
729
|
+
// NaN in both base and current should NOT be marked as modified
|
|
730
|
+
// (lodash.isEqual treats NaN === NaN as true)
|
|
731
|
+
return result.rows.every((row) => row.__status === undefined);
|
|
732
|
+
}),
|
|
733
|
+
{ numRuns: 50 },
|
|
734
|
+
);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("null vs undefined handling in comparisons", () => {
|
|
738
|
+
fc.assert(
|
|
739
|
+
fc.property(fc.nat({ max: 100 }), (id) => {
|
|
740
|
+
const base: DataFrame = {
|
|
741
|
+
columns: [
|
|
742
|
+
{ key: "id", name: "id", type: "integer" },
|
|
743
|
+
{ key: "value", name: "value", type: "text" },
|
|
744
|
+
],
|
|
745
|
+
data: [[id, null]],
|
|
746
|
+
};
|
|
747
|
+
const current: DataFrame = {
|
|
748
|
+
columns: [
|
|
749
|
+
{ key: "id", name: "id", type: "integer" },
|
|
750
|
+
{ key: "value", name: "value", type: "text" },
|
|
751
|
+
],
|
|
752
|
+
data: [[id, undefined]],
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// Should handle both without throwing
|
|
756
|
+
const result = toDataDiffGrid(base, current, { primaryKeys: ["id"] });
|
|
757
|
+
return result.rows.length === 1;
|
|
758
|
+
}),
|
|
759
|
+
{ numRuns: 50 },
|
|
760
|
+
);
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// ============================================================================
|
|
765
|
+
// Property-Based Tests: Stress Testing with Large Data
|
|
766
|
+
// ============================================================================
|
|
767
|
+
|
|
768
|
+
describe("Property-based tests: stress testing", () => {
|
|
769
|
+
test("handles wide DataFrames (many columns)", () => {
|
|
770
|
+
fc.assert(
|
|
771
|
+
fc.property(
|
|
772
|
+
dataFrameArb({
|
|
773
|
+
minColumns: 10,
|
|
774
|
+
maxColumns: 20,
|
|
775
|
+
minRows: 5,
|
|
776
|
+
maxRows: 10,
|
|
777
|
+
}),
|
|
778
|
+
(df) => {
|
|
779
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
780
|
+
return result.rows.length === df.data.length;
|
|
781
|
+
},
|
|
782
|
+
),
|
|
783
|
+
{ numRuns: 50 },
|
|
784
|
+
);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
test("handles tall DataFrames (many rows)", () => {
|
|
788
|
+
fc.assert(
|
|
789
|
+
fc.property(
|
|
790
|
+
dataFrameArb({
|
|
791
|
+
minColumns: 2,
|
|
792
|
+
maxColumns: 3,
|
|
793
|
+
minRows: 50,
|
|
794
|
+
maxRows: 100,
|
|
795
|
+
}),
|
|
796
|
+
(df) => {
|
|
797
|
+
const result = toDataGrid(df, { primaryKeys: ["id"] });
|
|
798
|
+
return result.rows.length === df.data.length;
|
|
799
|
+
},
|
|
800
|
+
),
|
|
801
|
+
{ numRuns: 30 },
|
|
802
|
+
);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test("diff grid handles large mismatched DataFrames", () => {
|
|
806
|
+
fc.assert(
|
|
807
|
+
fc.property(
|
|
808
|
+
// Generate a shared column schema first, then two DataFrames with same schema
|
|
809
|
+
fc
|
|
810
|
+
.tuple(
|
|
811
|
+
fc.uniqueArray(columnNameArb, { minLength: 2, maxLength: 4 }),
|
|
812
|
+
fc.array(columnTypeArb, { minLength: 2, maxLength: 4 }),
|
|
813
|
+
)
|
|
814
|
+
.chain(([names, types]) => {
|
|
815
|
+
const columnCount = Math.min(names.length, types.length);
|
|
816
|
+
const columns: DataFrame["columns"] = [
|
|
817
|
+
{ key: "id", name: "id", type: "integer" },
|
|
818
|
+
...names.slice(0, columnCount).map((name, i) => ({
|
|
819
|
+
key: name,
|
|
820
|
+
name: name,
|
|
821
|
+
type: types[i],
|
|
822
|
+
})),
|
|
823
|
+
];
|
|
824
|
+
|
|
825
|
+
// Generate two DataFrames with the same schema but different data
|
|
826
|
+
const rowArb = fc.tuple(
|
|
827
|
+
primaryKeyValueArb,
|
|
828
|
+
...types.slice(0, columnCount).map(cellValueArb),
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
return fc.tuple(
|
|
832
|
+
fc
|
|
833
|
+
.uniqueArray(rowArb, {
|
|
834
|
+
minLength: 20,
|
|
835
|
+
maxLength: 40,
|
|
836
|
+
comparator: (a, b) => a[0] === b[0],
|
|
837
|
+
})
|
|
838
|
+
.map((data) => ({ columns, data: data as DataFrame["data"] })),
|
|
839
|
+
fc
|
|
840
|
+
.uniqueArray(rowArb, {
|
|
841
|
+
minLength: 20,
|
|
842
|
+
maxLength: 40,
|
|
843
|
+
comparator: (a, b) => a[0] === b[0],
|
|
844
|
+
})
|
|
845
|
+
.map((data) => ({ columns, data: data as DataFrame["data"] })),
|
|
846
|
+
);
|
|
847
|
+
}),
|
|
848
|
+
([base, current]) => {
|
|
849
|
+
const result = toDataDiffGrid(base, current, { primaryKeys: ["id"] });
|
|
850
|
+
|
|
851
|
+
// Should complete without error and produce reasonable output
|
|
852
|
+
return result.rows.length >= 0 && result.columns.length > 0;
|
|
853
|
+
},
|
|
854
|
+
),
|
|
855
|
+
{ numRuns: 30 },
|
|
856
|
+
);
|
|
857
|
+
});
|
|
858
|
+
});
|