From ea37536db0c5f82e2f8e8e3859602cab71296552 Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Wed, 25 Jun 2025 14:55:44 -0400 Subject: [PATCH 01/12] React 19 compat fixes --- packages/jdm-editor/package.json | 13 +- .../decision-graph/nodes/decision-node.tsx | 48 +- pnpm-lock.yaml | 886 +++++++++--------- 3 files changed, 462 insertions(+), 485 deletions(-) diff --git a/packages/jdm-editor/package.json b/packages/jdm-editor/package.json index 6f9c2bdd..01c1b7ef 100644 --- a/packages/jdm-editor/package.json +++ b/packages/jdm-editor/package.json @@ -73,7 +73,6 @@ "react-resizable-panels": "^2.1.7", "reactflow": "11.11.4", "to-json-schema": "^0.2.5", - "transition-hook": "^1.5.2", "ts-pattern": "^5.7.0", "use-debounce": "^10.0.4", "zod": "^3.24.2", @@ -96,13 +95,13 @@ "@trivago/prettier-plugin-sort-imports": "5.2.2", "@types/big.js": "^6.2.2", "@types/lodash": "^4.17.16", - "@types/react": "18.3.11", - "@types/react-dom": "19.1.2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", "@types/to-json-schema": "^0.2.4", "@vitejs/plugin-react-swc": "^3.8.1", "dayjs": "^1.11.13", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "sass": "^1.86.3", "storybook": "8.6.12", "storybook-dark-mode": "^4.0.2", @@ -123,7 +122,7 @@ ] }, "peerDependencies": { - "react": ">= 18", - "react-dom": ">= 18" + "react": ">= 19", + "react-dom": ">= 19" } } diff --git a/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx b/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx index cf466255..6c36f197 100644 --- a/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx +++ b/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx @@ -2,7 +2,6 @@ import { CloseOutlined, MoreOutlined } from '@ant-design/icons'; import { Button, Dropdown, type MenuProps, Typography } from 'antd'; import clsx from 'clsx'; import React from 'react'; -import { Transition } from 'transition-hook'; import { match } from 'ts-pattern'; import { DiffIcon } from '../../diff-icon'; @@ -121,34 +120,25 @@ export const DecisionNode: React.FC = ({ )} - - {(stage, shouldMount) => - shouldMount && ( - -
-
- {detailsTitle} -
-
{details}
-
-
- ) - } -
+ {detailsOpen && ( + +
+
+ {detailsTitle} +
+
{details}
+
+
+ )} ); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70dbd700..9f85cc4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,7 +70,7 @@ importers: dependencies: '@ant-design/icons': specifier: 6.0.0 - version: 6.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@codemirror/autocomplete': specifier: ^6.18.6 version: 6.18.6 @@ -109,16 +109,16 @@ importers: version: 1.4.2 '@monaco-editor/react': specifier: ^4.7.0 - version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-table': specifier: 8.21.3 - version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-virtual': specifier: 3.13.6 - version: 3.13.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.13.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) antd: specifier: 5.21.2 - version: 5.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 5.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) clsx: specifier: 2.1.1 version: 2.1.1 @@ -145,65 +145,62 @@ importers: version: 4.17.21 lucide-react: specifier: ^0.488.0 - version: 0.488.0(react@18.3.1) + version: 0.488.0(react@19.1.0) monaco-editor: specifier: ^0.52.2 version: 0.52.2 re-resizable: specifier: ^6.11.2 - version: 6.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-dnd: specifier: ^16.0.1 - version: 16.0.1(@types/node@22.14.1)(@types/react@18.3.11)(react@18.3.1) + version: 16.0.1(@types/node@22.14.1)(@types/react@19.1.8)(react@19.1.0) react-dnd-html5-backend: specifier: 16.0.1 version: 16.0.1 react-intersection-observer: specifier: ^9.16.0 - version: 9.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-json-tree: specifier: ^0.20.0 - version: 0.20.0(@types/react@18.3.11)(react@18.3.1) + version: 0.20.0(@types/react@19.1.8)(react@19.1.0) react-resizable-panels: specifier: ^2.1.7 - version: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) reactflow: specifier: 11.11.4 - version: 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) to-json-schema: specifier: ^0.2.5 version: 0.2.5 - transition-hook: - specifier: ^1.5.2 - version: 1.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ts-pattern: specifier: ^5.7.0 version: 5.7.0 use-debounce: specifier: ^10.0.4 - version: 10.0.4(react@18.3.1) + version: 10.0.4(react@19.1.0) zod: specifier: ^3.24.2 version: 3.24.2 zustand: specifier: ^4.5.5 - version: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + version: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) devDependencies: '@storybook/addon-actions': specifier: 8.6.12 version: 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-docs': specifier: 8.6.12 - version: 8.6.12(@types/react@18.3.11)(storybook@8.6.12(prettier@3.5.3)) + version: 8.6.12(@types/react@19.1.8)(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-essentials': specifier: 8.6.12 - version: 8.6.12(@types/react@18.3.11)(storybook@8.6.12(prettier@3.5.3)) + version: 8.6.12(@types/react@19.1.8)(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-interactions': specifier: 8.6.12 version: 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-links': specifier: 8.6.12 - version: 8.6.12(react@18.3.1)(storybook@8.6.12(prettier@3.5.3)) + version: 8.6.12(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-storysource': specifier: ^8.6.12 version: 8.6.12(storybook@8.6.12(prettier@3.5.3)) @@ -212,10 +209,10 @@ importers: version: 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/react': specifier: ^8.6.12 - version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3) + version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3) '@storybook/react-vite': specifier: ^8.6.12 - version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.40.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) + version: 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rollup@4.40.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) '@storybook/test': specifier: ^8.6.12 version: 8.6.12(storybook@8.6.12(prettier@3.5.3)) @@ -224,7 +221,7 @@ importers: version: 6.6.3 '@testing-library/react': specifier: 16.3.0 - version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.2(@types/react@18.3.11))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@testing-library/user-event': specifier: 14.6.1 version: 14.6.1(@testing-library/dom@10.4.0) @@ -238,11 +235,11 @@ importers: specifier: ^4.17.16 version: 4.17.16 '@types/react': - specifier: 18.3.11 - version: 18.3.11 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': - specifier: 19.1.2 - version: 19.1.2(@types/react@18.3.11) + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) '@types/to-json-schema': specifier: ^0.2.4 version: 0.2.4 @@ -253,11 +250,11 @@ importers: specifier: ^1.11.13 version: 1.11.13 react: - specifier: 18.3.1 - version: 18.3.1 + specifier: ^19.1.0 + version: 19.1.0 react-dom: - specifier: 18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) sass: specifier: ^1.86.3 version: 1.86.3 @@ -266,7 +263,7 @@ importers: version: 8.6.12(prettier@3.5.3) storybook-dark-mode: specifier: ^4.0.2 - version: 4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3)) + version: 4.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)) vite: specifier: 6.2.6 version: 6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1) @@ -1860,14 +1857,17 @@ packages: '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/react-dom@19.1.2': - resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==} + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: '@types/react': ^19.0.0 '@types/react@18.3.11': resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -4674,10 +4674,10 @@ packages: resolution: {integrity: sha512-hlSJDQ2synMPKFZOsKo9Hi8WWZTC7POR8EmWvTSjow+VDgKzkmjQvFm2fk0tmRw+f0vTOIYKlarR0iL4996pdg==} engines: {node: '>=16.14.0'} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: - react: ^18.3.1 + react: ^19.1.0 react-intersection-observer@9.16.0: resolution: {integrity: sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==} @@ -4709,8 +4709,8 @@ packages: react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} reactflow@11.11.4: @@ -4895,8 +4895,8 @@ packages: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} @@ -5226,12 +5226,6 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - transition-hook@1.5.2: - resolution: {integrity: sha512-zVW5wP+hzOCk56jU5V5GAIsuaf+gZv43pzxyimXQ74JnD1yoprxTfzoPjr0yjC/Mrc9JyCh3Wb6UhXD+8dlO4w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -5655,24 +5649,24 @@ snapshots: dependencies: '@ant-design/fast-color': 3.0.0 - '@ant-design/cssinjs-utils@1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@ant-design/cssinjs-utils@1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@babel/runtime': 7.27.0 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@ant-design/cssinjs@1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@ant-design/cssinjs@1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 '@emotion/hash': 0.8.0 '@emotion/unitless': 0.7.5 classnames: 2.5.1 csstype: 3.1.3 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) stylis: 4.3.6 '@ant-design/fast-color@2.0.6': @@ -5683,31 +5677,31 @@ snapshots: '@ant-design/icons-svg@4.4.2': {} - '@ant-design/icons@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@ant-design/icons@5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@ant-design/colors': 7.2.0 '@ant-design/icons-svg': 4.4.2 '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@ant-design/icons@6.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@ant-design/icons@6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@ant-design/colors': 8.0.0 '@ant-design/icons-svg': 4.4.2 - '@rc-component/util': 1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/util': 1.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@ant-design/react-slick@1.1.2(react@18.3.1)': + '@ant-design/react-slick@1.1.2(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 json2mq: 0.2.0 - react: 18.3.1 + react: 19.1.0 resize-observer-polyfill: 1.5.1 throttle-debounce: 5.0.2 @@ -6172,11 +6166,11 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@mdx-js/react@3.1.0(@types/react@18.3.11)(react@18.3.1)': + '@mdx-js/react@3.1.0(@types/react@19.1.8)(react@19.1.0)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 18.3.11 - react: 18.3.1 + '@types/react': 19.1.8 + react: 19.1.0 '@microsoft/api-extractor-model@7.30.5(@types/node@22.14.1)': dependencies: @@ -6217,12 +6211,12 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@monaco-editor/loader': 1.5.0 monaco-editor: 0.52.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@napi-rs/wasm-runtime@0.2.4': dependencies: @@ -6547,75 +6541,75 @@ snapshots: dependencies: '@babel/runtime': 7.27.0 - '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/color-picker@2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@ant-design/fast-color': 2.0.6 '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/context@1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@rc-component/mini-decimal@1.1.0': dependencies: '@babel/runtime': 7.27.0 - '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/mutate-observer@1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/portal@1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@rc-component/qrcode@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/qrcode@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/tour@1.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@rc-component/trigger@2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/trigger@2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@rc-component/util@1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/util@1.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) react-is: 18.3.1 '@react-dnd/asap@5.0.2': {} @@ -6624,29 +6618,29 @@ snapshots: '@react-dnd/shallowequal@4.0.2': {} - '@reactflow/background@11.3.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@reactflow/background@11.3.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/core': 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/controls@11.2.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@reactflow/controls@11.2.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/core': 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/core@11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@reactflow/core@11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@types/d3': 7.4.3 '@types/d3-drag': 3.0.7 @@ -6656,48 +6650,48 @@ snapshots: d3-drag: 3.0.0 d3-selection: 3.0.0 d3-zoom: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/minimap@11.7.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@reactflow/minimap@11.7.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/core': 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/d3-selection': 3.0.11 '@types/d3-zoom': 3.0.8 classcat: 5.0.5 d3-selection: 3.0.0 d3-zoom: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/node-resizer@2.2.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@reactflow/node-resizer@2.2.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/core': 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classcat: 5.0.5 d3-drag: 3.0.0 d3-selection: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/node-toolbar@1.3.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@reactflow/node-toolbar@1.3.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/core': 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6871,25 +6865,25 @@ snapshots: storybook: 8.6.12(prettier@3.5.3) ts-dedent: 2.2.0 - '@storybook/addon-docs@8.6.12(@types/react@18.3.11)(storybook@8.6.12(prettier@3.5.3))': + '@storybook/addon-docs@8.6.12(@types/react@19.1.8)(storybook@8.6.12(prettier@3.5.3))': dependencies: - '@mdx-js/react': 3.1.0(@types/react@18.3.11)(react@18.3.1) - '@storybook/blocks': 8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3)) + '@mdx-js/react': 3.1.0(@types/react@19.1.8)(react@19.1.0) + '@storybook/blocks': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)) '@storybook/csf-plugin': 8.6.12(storybook@8.6.12(prettier@3.5.3)) - '@storybook/react-dom-shim': 8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3)) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@storybook/react-dom-shim': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) storybook: 8.6.12(prettier@3.5.3) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-essentials@8.6.12(@types/react@18.3.11)(storybook@8.6.12(prettier@3.5.3))': + '@storybook/addon-essentials@8.6.12(@types/react@19.1.8)(storybook@8.6.12(prettier@3.5.3))': dependencies: '@storybook/addon-actions': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-backgrounds': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-controls': 8.6.12(storybook@8.6.12(prettier@3.5.3)) - '@storybook/addon-docs': 8.6.12(@types/react@18.3.11)(storybook@8.6.12(prettier@3.5.3)) + '@storybook/addon-docs': 8.6.12(@types/react@19.1.8)(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-highlight': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-measure': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/addon-outline': 8.6.12(storybook@8.6.12(prettier@3.5.3)) @@ -6914,13 +6908,13 @@ snapshots: storybook: 8.6.12(prettier@3.5.3) ts-dedent: 2.2.0 - '@storybook/addon-links@8.6.12(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))': + '@storybook/addon-links@8.6.12(react@19.1.0)(storybook@8.6.12(prettier@3.5.3))': dependencies: '@storybook/global': 5.0.0 storybook: 8.6.12(prettier@3.5.3) ts-dedent: 2.2.0 optionalDependencies: - react: 18.3.1 + react: 19.1.0 '@storybook/addon-measure@8.6.12(storybook@8.6.12(prettier@3.5.3))': dependencies: @@ -6950,14 +6944,14 @@ snapshots: memoizerific: 1.11.3 storybook: 8.6.12(prettier@3.5.3) - '@storybook/blocks@8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))': + '@storybook/blocks@8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3))': dependencies: - '@storybook/icons': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/icons': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) storybook: 8.6.12(prettier@3.5.3) ts-dedent: 2.2.0 optionalDependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@storybook/builder-vite@8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))': dependencies: @@ -7003,10 +6997,10 @@ snapshots: '@storybook/global@5.0.0': {} - '@storybook/icons@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/icons@1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@storybook/instrumenter@8.6.12(storybook@8.6.12(prettier@3.5.3))': dependencies: @@ -7022,23 +7016,23 @@ snapshots: dependencies: storybook: 8.6.12(prettier@3.5.3) - '@storybook/react-dom-shim@8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))': + '@storybook/react-dom-shim@8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3))': dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) storybook: 8.6.12(prettier@3.5.3) - '@storybook/react-vite@8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.40.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))': + '@storybook/react-vite@8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rollup@4.40.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.3)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) '@rollup/pluginutils': 5.1.4(rollup@4.40.0) '@storybook/builder-vite': 8.6.12(storybook@8.6.12(prettier@3.5.3))(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) - '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3) + '@storybook/react': 8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3) find-up: 5.0.0 magic-string: 0.30.17 - react: 18.3.1 + react: 19.1.0 react-docgen: 7.1.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.1.0(react@19.1.0) resolve: 1.22.10 storybook: 8.6.12(prettier@3.5.3) tsconfig-paths: 4.2.0 @@ -7050,16 +7044,16 @@ snapshots: - supports-color - typescript - '@storybook/react@8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)': + '@storybook/react@8.6.12(@storybook/test@8.6.12(storybook@8.6.12(prettier@3.5.3)))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)': dependencies: '@storybook/components': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/global': 5.0.0 '@storybook/manager-api': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/preview-api': 8.6.12(storybook@8.6.12(prettier@3.5.3)) - '@storybook/react-dom-shim': 8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3)) + '@storybook/react-dom-shim': 8.6.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)) '@storybook/theming': 8.6.12(storybook@8.6.12(prettier@3.5.3)) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) storybook: 8.6.12(prettier@3.5.3) optionalDependencies: '@storybook/test': 8.6.12(storybook@8.6.12(prettier@3.5.3)) @@ -7139,17 +7133,17 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-table@8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/table-core': 8.21.3 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - '@tanstack/react-virtual@3.13.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.13.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/virtual-core': 3.13.6 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@tanstack/table-core@8.21.3': {} @@ -7186,15 +7180,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.2(@types/react@18.3.11))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.0 '@testing-library/dom': 10.4.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 18.3.11 - '@types/react-dom': 19.1.2(@types/react@18.3.11) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: @@ -7397,15 +7391,19 @@ snapshots: '@types/prop-types@15.7.14': {} - '@types/react-dom@19.1.2(@types/react@18.3.11)': + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: - '@types/react': 18.3.11 + '@types/react': 19.1.8 '@types/react@18.3.11': dependencies: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/react@19.1.8': + dependencies: + csstype: 3.1.3 + '@types/resolve@1.20.2': {} '@types/resolve@1.20.6': {} @@ -7663,55 +7661,55 @@ snapshots: ansi-styles@6.2.1: {} - antd@5.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + antd@5.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@ant-design/colors': 7.2.0 - '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@ant-design/cssinjs-utils': 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@ant-design/icons': 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@ant-design/react-slick': 1.1.2(react@18.3.1) + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/react-slick': 1.1.2(react@19.1.0) '@babel/runtime': 7.27.0 '@ctrl/tinycolor': 3.6.1 - '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rc-component/qrcode': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/color-picker': 2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/mutate-observer': 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/qrcode': 1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/tour': 1.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 copy-to-clipboard: 3.3.3 dayjs: 1.11.13 - rc-cascader: 3.28.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-checkbox: 3.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-collapse: 3.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-drawer: 7.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-field-form: 2.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-image: 7.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-input: 1.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-input-number: 9.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-mentions: 2.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-menu: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-notification: 5.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-pagination: 4.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-picker: 4.6.15(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-rate: 2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-segmented: 2.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-select: 14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-slider: 11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-table: 7.47.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tabs: 15.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-textarea: 1.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tooltip: 6.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree: 5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree-select: 5.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-upload: 4.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-cascader: 3.28.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-checkbox: 3.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-collapse: 3.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-dialog: 9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-drawer: 7.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-dropdown: 4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-field-form: 2.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-image: 7.11.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-input: 1.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-input-number: 9.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-mentions: 2.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-notification: 5.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-pagination: 4.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-picker: 4.6.15(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-progress: 4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-rate: 2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-segmented: 2.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-select: 14.15.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-slider: 11.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-steps: 6.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-switch: 4.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-table: 7.47.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tabs: 15.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-textarea: 1.8.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tooltip: 6.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree-select: 5.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-upload: 4.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) scroll-into-view-if-needed: 3.1.0 throttle-debounce: 5.0.2 transitivePeerDependencies: @@ -9664,9 +9662,9 @@ snapshots: dependencies: yallist: 4.0.0 - lucide-react@0.488.0(react@18.3.1): + lucide-react@0.488.0(react@19.1.0): dependencies: - react: 18.3.1 + react: 19.1.0 lz-string@1.5.0: {} @@ -10397,329 +10395,329 @@ snapshots: dependencies: safe-buffer: 5.2.1 - rc-cascader@3.28.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-cascader@3.28.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 array-tree-filter: 2.1.0 classnames: 2.5.1 - rc-select: 14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree: 5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-select: 14.15.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-checkbox@3.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-checkbox@3.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-collapse@3.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-collapse@3.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-dialog@9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-drawer@7.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-drawer@7.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-dropdown@4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-dropdown@4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-field-form@2.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-field-form@2.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 '@rc-component/async-validator': 5.0.4 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-image@7.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-image@7.11.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-dialog: 9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-input-number@9.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-input-number@9.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 '@rc-component/mini-decimal': 1.1.0 classnames: 2.5.1 - rc-input: 1.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-input: 1.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-input@1.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-input@1.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-mentions@2.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-mentions@2.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-input: 1.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-menu: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-textarea: 1.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-input: 1.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-textarea: 1.8.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-menu@9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-menu@9.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-motion@2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-notification@5.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-notification@5.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-overflow@1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-overflow@1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-pagination@4.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-pagination@4.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-picker@4.6.15(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-picker@4.6.15(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) optionalDependencies: dayjs: 1.11.13 - rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-progress@4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-rate@2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-rate@2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-resize-observer@1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) resize-observer-polyfill: 1.5.1 - rc-segmented@2.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-segmented@2.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-select@14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-select@14.15.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-slider@11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-slider@11.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-steps@6.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-switch@4.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-table@7.47.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-table@7.47.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/context': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-tabs@15.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-tabs@15.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-menu: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-dropdown: 4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-textarea@1.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-textarea@1.8.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-input: 1.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-input: 1.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-tooltip@6.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-tooltip@6.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) classnames: 2.5.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-tree-select@5.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-tree-select@5.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-select: 14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree: 5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-select: 14.15.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-tree@5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-tree@5.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-upload@4.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-upload@4.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-util@5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) react-is: 18.3.1 - rc-virtual-list@3.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-virtual-list@3.18.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - re-resizable@6.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + re-resizable@6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) react-base16-styling@0.10.0: dependencies: @@ -10732,17 +10730,17 @@ snapshots: dependencies: dnd-core: 16.0.1 - react-dnd@16.0.1(@types/node@22.14.1)(@types/react@18.3.11)(react@18.3.1): + react-dnd@16.0.1(@types/node@22.14.1)(@types/react@19.1.8)(react@19.1.0): dependencies: '@react-dnd/invariant': 4.0.2 '@react-dnd/shallowequal': 4.0.2 dnd-core: 16.0.1 fast-deep-equal: 3.1.3 hoist-non-react-statics: 3.3.2 - react: 18.3.1 + react: 19.1.0 optionalDependencies: '@types/node': 22.14.1 - '@types/react': 18.3.11 + '@types/react': 19.1.8 react-docgen-typescript@2.2.2(typescript@5.8.3): dependencies: @@ -10763,17 +10761,16 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@18.3.1(react@18.3.1): + react-dom@19.1.0(react@19.1.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.1.0 + scheduler: 0.26.0 - react-intersection-observer@9.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-intersection-observer@9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - react: 18.3.1 + react: 19.1.0 optionalDependencies: - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.1.0(react@19.1.0) react-is@16.13.1: {} @@ -10781,32 +10778,30 @@ snapshots: react-is@18.3.1: {} - react-json-tree@0.20.0(@types/react@18.3.11)(react@18.3.1): + react-json-tree@0.20.0(@types/react@19.1.8)(react@19.1.0): dependencies: '@types/lodash': 4.17.16 - '@types/react': 18.3.11 - react: 18.3.1 + '@types/react': 19.1.8 + react: 19.1.0 react-base16-styling: 0.10.0 - react-resizable-panels@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-resizable-panels@2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.1.0: {} - reactflow@11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + reactflow@11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@reactflow/background': 11.3.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/controls': 11.2.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/core': 11.11.4(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/minimap': 11.7.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/node-resizer': 2.2.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/node-toolbar': 1.3.14(@types/react@18.3.11)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@reactflow/background': 11.3.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@reactflow/controls': 11.2.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@reactflow/core': 11.11.4(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@reactflow/minimap': 11.7.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@reactflow/node-resizer': 2.2.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@reactflow/node-toolbar': 1.3.14(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -11033,9 +11028,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.26.0: {} scroll-into-view-if-needed@3.1.0: dependencies: @@ -11203,12 +11196,12 @@ snapshots: state-local@1.0.7: {} - storybook-dark-mode@4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3)): + storybook-dark-mode@4.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)): dependencies: '@storybook/components': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/core-events': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/global': 5.0.0 - '@storybook/icons': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/icons': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@storybook/manager-api': 8.6.12(storybook@8.6.12(prettier@3.5.3)) '@storybook/theming': 8.6.12(storybook@8.6.12(prettier@3.5.3)) fast-deep-equal: 3.1.3 @@ -11410,11 +11403,6 @@ snapshots: tr46@0.0.3: {} - transition-hook@1.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - traverse@0.3.9: {} treeverse@3.0.0: {} @@ -11554,13 +11542,13 @@ snapshots: dependencies: punycode: 2.3.1 - use-debounce@10.0.4(react@18.3.1): + use-debounce@10.0.4(react@19.1.0): dependencies: - react: 18.3.1 + react: 19.1.0 - use-sync-external-store@1.2.2(react@18.3.1): + use-sync-external-store@1.2.2(react@19.1.0): dependencies: - react: 18.3.1 + react: 19.1.0 util-deprecate@1.0.2: {} @@ -11808,10 +11796,10 @@ snapshots: zod@3.24.2: {} - zustand@4.5.5(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1): + zustand@4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0): dependencies: - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.2.2(react@19.1.0) optionalDependencies: - '@types/react': 18.3.11 + '@types/react': 19.1.8 immer: 10.1.1 - react: 18.3.1 + react: 19.1.0 From b24d5cf5d0e0955adf7f0f6e7f6ef6ac2b7f7120 Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Mon, 14 Jul 2025 16:16:22 -0300 Subject: [PATCH 02/12] Update package.json for jdm-editor --- packages/jdm-editor/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/jdm-editor/package.json b/packages/jdm-editor/package.json index ff19d86d..27848c26 100644 --- a/packages/jdm-editor/package.json +++ b/packages/jdm-editor/package.json @@ -1,9 +1,9 @@ { - "name": "@gorules/jdm-editor", + "name": "giorules-jdm-editor", "version": "1.45.0", - "description": "", - "author": "GoRules (https://gorules.io)", - "homepage": "https://github.com/gorules/jdm-editor", + "description": "React 19 Compatible Fork of GoRules JDM Editor", + "author": "Giorgio Delgado & CoPlane Inc.", + "homepage": "https://github.com/supermacro/gio-rules", "license": "MIT", "keywords": [], "type": "module", From e640b99b1900710391addfe77462be4c2478404b Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Mon, 2 Mar 2026 20:34:43 -0500 Subject: [PATCH 03/12] Upgrade to react19 --- package.json | 2 +- .../decision-graph/context/dg-store.context.tsx | 2 +- .../src/components/decision-graph/dg-panel.tsx | 3 ++- .../decision-graph/nodes/decision-node.tsx | 4 +--- .../src/components/decision-table/dt-empty.tsx | 2 +- .../decision-table/table/table-row.tsx | 8 +++++++- .../components/decision-table/table/table.tsx | 2 +- .../components/expression/expression-item.tsx | 8 +++++++- .../jdm-editor/src/components/spaced-text.tsx | 2 +- pnpm-lock.yaml | 17 ++--------------- 10 files changed, 24 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 9d493793..a9c860b9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@swc/core": "^1.11.21", "@trivago/prettier-plugin-sort-imports": "^5.2.2", "@types/node": "^22.14.1", - "@types/react": "^18.3.11", + "@types/react": "^19.1.8", "@typescript-eslint/eslint-plugin": "^8.30.1", "@typescript-eslint/parser": "^8.30.1", "eslint": "9.24.0", diff --git a/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx b/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx index 1e464f55..f7cf4837 100644 --- a/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx +++ b/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx @@ -22,7 +22,7 @@ export type PanelType = { id: string; icon: React.ReactNode; title: string; - renderPanel?: React.FC; + renderPanel?: () => React.ReactNode; hideHeader?: boolean; onClick?: () => void; }; diff --git a/packages/jdm-editor/src/components/decision-graph/dg-panel.tsx b/packages/jdm-editor/src/components/decision-graph/dg-panel.tsx index ac0d9e57..fe7717a5 100644 --- a/packages/jdm-editor/src/components/decision-graph/dg-panel.tsx +++ b/packages/jdm-editor/src/components/decision-graph/dg-panel.tsx @@ -23,6 +23,7 @@ export const GraphPanel: React.FC = () => { }, [activePanel]); if (!activePanel) return null; + const RenderPanel = activePanel.renderPanel; return ( { )} -
{activePanel?.renderPanel?.({})}
+
{RenderPanel ? : null}
); }; diff --git a/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx b/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx index 6c36f197..21d3941e 100644 --- a/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx +++ b/packages/jdm-editor/src/components/decision-graph/nodes/decision-node.tsx @@ -121,9 +121,7 @@ export const DecisionNode: React.FC = ({ )} {detailsOpen && ( - +
{detailsTitle} diff --git a/packages/jdm-editor/src/components/decision-table/dt-empty.tsx b/packages/jdm-editor/src/components/decision-table/dt-empty.tsx index 1c410237..85b7ec56 100644 --- a/packages/jdm-editor/src/components/decision-table/dt-empty.tsx +++ b/packages/jdm-editor/src/components/decision-table/dt-empty.tsx @@ -25,7 +25,7 @@ export type DecisionTableEmptyType = { value?: DecisionTableType; disabled?: boolean; disableHitPolicy?: boolean; - cellRenderer?: (props: TableCellProps) => JSX.Element | null | undefined; + cellRenderer?: (props: TableCellProps) => React.ReactElement | null | undefined; inputsSchema?: SchemaSelectProps[]; outputsSchema?: SchemaSelectProps[]; permission?: DecisionTableStoreType['state']['permission']; diff --git a/packages/jdm-editor/src/components/decision-table/table/table-row.tsx b/packages/jdm-editor/src/components/decision-table/table/table-row.tsx index c5812616..1abfb2ae 100644 --- a/packages/jdm-editor/src/components/decision-table/table/table-row.tsx +++ b/packages/jdm-editor/src/components/decision-table/table/table-row.tsx @@ -96,7 +96,13 @@ export const TableRow: React.FC<{ > { + dragRef(node); + } + } onContextMenuCapture={() => tableActions.setCursor({ x: 'id', y: virtualItem.index })} >
diff --git a/packages/jdm-editor/src/components/decision-table/table/table.tsx b/packages/jdm-editor/src/components/decision-table/table/table.tsx index a3d4c0e5..17e6b0cd 100644 --- a/packages/jdm-editor/src/components/decision-table/table/table.tsx +++ b/packages/jdm-editor/src/components/decision-table/table/table.tsx @@ -233,7 +233,7 @@ export const Table: React.FC = ({ id, maxHeight }) => { }; type TableBodyProps = { - tableContainerRef: React.RefObject; + tableContainerRef: React.RefObject; table: ReactTable; } & Omit, 'children'>; diff --git a/packages/jdm-editor/src/components/expression/expression-item.tsx b/packages/jdm-editor/src/components/expression/expression-item.tsx index 90fe6188..458037a3 100644 --- a/packages/jdm-editor/src/components/expression/expression-item.tsx +++ b/packages/jdm-editor/src/components/expression/expression-item.tsx @@ -77,7 +77,13 @@ export const ExpressionItem: React.FC = ({ expression, inde )} style={{ opacity: !isDragging ? 1 : 0.5 }} > -
+
{ + dragRef(node); + }} + className='expression-list-item__drag' + aria-disabled={permission !== 'edit:full' || disabled} + >
{expression?._diff?.status ? ( = ({ left, right, gap = 16 }) => { +export const SpacedText: React.FC = ({ left, right, gap = 16 }) => { return ( {left} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f85cc4e..9e3acd67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: ^22.14.1 version: 22.14.1 '@types/react': - specifier: ^18.3.11 - version: 18.3.11 + specifier: ^19.1.8 + version: 19.1.8 '@typescript-eslint/eslint-plugin': specifier: ^8.30.1 version: 8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.24.0)(typescript@5.8.3))(eslint@9.24.0)(typescript@5.8.3) @@ -1854,17 +1854,11 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/react-dom@19.1.6': resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: '@types/react': ^19.0.0 - '@types/react@18.3.11': - resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} - '@types/react@19.1.8': resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} @@ -7389,17 +7383,10 @@ snapshots: '@types/normalize-package-data@2.4.4': {} - '@types/prop-types@15.7.14': {} - '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: '@types/react': 19.1.8 - '@types/react@18.3.11': - dependencies: - '@types/prop-types': 15.7.14 - csstype: 3.1.3 - '@types/react@19.1.8': dependencies: csstype: 3.1.3 From 44d920864cacf8f44070ec0bc49be9a22af80a81 Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Mon, 2 Mar 2026 21:02:53 -0500 Subject: [PATCH 04/12] Handle null value --- .../components/decision-table/components/input-field-edit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jdm-editor/src/components/decision-table/components/input-field-edit.tsx b/packages/jdm-editor/src/components/decision-table/components/input-field-edit.tsx index b2595553..3b4d8349 100644 --- a/packages/jdm-editor/src/components/decision-table/components/input-field-edit.tsx +++ b/packages/jdm-editor/src/components/decision-table/components/input-field-edit.tsx @@ -83,7 +83,7 @@ export const InputFieldEdit: React.FC = ({ Input Field Date: Mon, 2 Mar 2026 21:10:40 -0500 Subject: [PATCH 05/12] Bump patch version --- packages/jdm-editor/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jdm-editor/package.json b/packages/jdm-editor/package.json index 27848c26..bfcfad86 100644 --- a/packages/jdm-editor/package.json +++ b/packages/jdm-editor/package.json @@ -1,6 +1,6 @@ { "name": "giorules-jdm-editor", - "version": "1.45.0", + "version": "1.45.1", "description": "React 19 Compatible Fork of GoRules JDM Editor", "author": "Giorgio Delgado & CoPlane Inc.", "homepage": "https://github.com/supermacro/gio-rules", From e5d5778289eb728de9455ef0ef83bf9f0e378b7d Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 10:28:08 -0500 Subject: [PATCH 06/12] Upgrade zustand and added regression test --- .gitignore | 1 + packages/jdm-editor/package.json | 12 +- .../edit-expression.regression.test.tsx | 68 ++ .../context/dg-store.context.tsx | 35 +- .../context/dt-store.context.tsx | 32 +- .../context/expression-store.context.tsx | 29 +- .../expression/expression-command-bar.tsx | 22 +- packages/jdm-editor/src/setupTests.js | 8 +- packages/jdm-editor/vitest.config.ts | 19 + pnpm-lock.yaml | 793 +++++++++++++++++- 10 files changed, 984 insertions(+), 35 deletions(-) create mode 100644 packages/jdm-editor/src/components/decision-graph/__tests__/edit-expression.regression.test.tsx create mode 100644 packages/jdm-editor/vitest.config.ts diff --git a/.gitignore b/.gitignore index 3c24bae4..1abd5a48 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ node_modules *.log* *.tgz docs +packages/jdm-editor/src/**/__screenshots__/ diff --git a/packages/jdm-editor/package.json b/packages/jdm-editor/package.json index bfcfad86..28f7e957 100644 --- a/packages/jdm-editor/package.json +++ b/packages/jdm-editor/package.json @@ -35,7 +35,9 @@ "storybook": "storybook dev -p 9009", "build:storybook": "storybook build -o docs", "typecheck": "tsc --noEmit", - "prepublishOnly": "vite build" + "prepublishOnly": "vite build", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@ant-design/icons": "6.0.0", @@ -76,7 +78,7 @@ "ts-pattern": "^5.7.0", "use-debounce": "^10.0.4", "zod": "^3.24.2", - "zustand": "^4.5.5" + "zustand": "^5.0.11" }, "devDependencies": { "@storybook/addon-actions": "8.6.12", @@ -99,7 +101,10 @@ "@types/react-dom": "^19.1.6", "@types/to-json-schema": "^0.2.4", "@vitejs/plugin-react-swc": "^3.8.1", + "@vitest/browser": "^4.0.18", + "@vitest/browser-playwright": "^4.0.18", "dayjs": "^1.11.13", + "playwright": "^1.58.2", "react": "^19.1.0", "react-dom": "^19.1.0", "sass": "^1.86.3", @@ -107,7 +112,8 @@ "storybook-dark-mode": "^4.0.2", "vite": "6.2.6", "vite-plugin-dts": "^4.5.3", - "vite-plugin-wasm": "^3.4.1" + "vite-plugin-wasm": "^3.4.1", + "vitest": "^4.0.18" }, "jest": { "collectCoverageFrom": [ diff --git a/packages/jdm-editor/src/components/decision-graph/__tests__/edit-expression.regression.test.tsx b/packages/jdm-editor/src/components/decision-graph/__tests__/edit-expression.regression.test.tsx new file mode 100644 index 00000000..faa74625 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/__tests__/edit-expression.regression.test.tsx @@ -0,0 +1,68 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraph } from '../dg'; +import type { DecisionGraphType } from '../dg-types'; + +vi.mock('../../../../helpers/wasm', () => ({ + isWasmAvailable: () => false, +})); + +const graph: DecisionGraphType = { + nodes: [ + { + id: 'input', + type: 'inputNode', + name: 'Input', + content: { schema: '{"type":"object"}' }, + position: { x: 15, y: 195 }, + }, + { + id: 'expr', + type: 'expressionNode', + name: 'AutoApproved?', + content: { + expressions: [], + passThrough: true, + inputField: null, + outputPath: null, + executionMode: 'single', + }, + position: { x: 430, y: 320 }, + }, + { + id: 'output', + type: 'outputNode', + name: 'Output', + content: { schema: '{"type":"object"}' }, + position: { x: 790, y: 160 }, + }, + ], + edges: [ + { id: 'e1', type: 'edge', sourceId: 'input', targetId: 'expr', sourceHandle: null }, + { id: 'e2', type: 'edge', sourceId: 'expr', targetId: 'output', sourceHandle: null }, + ], +}; + +describe('DecisionGraph expression tab', () => { + it('opens Edit Expression tab without maximum update depth loops', async () => { + const user = userEvent.setup(); + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); + + render(); + + await user.click(await screen.findByRole('button', { name: /edit expression/i })); + + expect(await screen.findByText('Add row')).toBeInTheDocument(); + + const hasMaxDepthError = errorSpy.mock.calls.some((call) => + call.some((arg) => typeof arg === 'string' && arg.includes('Maximum update depth exceeded')), + ); + + expect(hasMaxDepthError).toBe(false); + + errorSpy.mockRestore(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx b/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx index f7cf4837..c1233f7f 100644 --- a/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx +++ b/packages/jdm-editor/src/components/decision-graph/context/dg-store.context.tsx @@ -657,25 +657,54 @@ export const DecisionGraphProvider: React.FC{children}; }; +function useStoreSelectorWithEquality( + selector: (state: TState) => TSlice, + equals: (a: TSlice, b: TSlice) => boolean, +) { + const selectorRef = React.useRef(selector); + selectorRef.current = selector; + const equalsRef = React.useRef(equals); + equalsRef.current = equals; + const prevRef = React.useRef(undefined); + const hasPrevRef = React.useRef(false); + + return React.useCallback( + (state: TState) => { + const next = selectorRef.current(state); + if (hasPrevRef.current && equalsRef.current(prevRef.current as TSlice, next)) { + return prevRef.current as TSlice; + } + + prevRef.current = next; + hasPrevRef.current = true; + return next; + }, + [], + ); +} + export function useDecisionGraphState( selector: (state: DecisionGraphStoreType['state']) => T, equals: (a: any, b: any) => boolean = equal, ): T { - return React.useContext(DecisionGraphStoreContext).stateStore(selector, equals); + const stableSelector = useStoreSelectorWithEquality(selector, equals); + return React.useContext(DecisionGraphStoreContext).stateStore(stableSelector); } export function useDecisionGraphListeners( selector: (state: DecisionGraphStoreType['listeners']) => T, equals: (a: any, b: any) => boolean = equal, ): T { - return React.useContext(DecisionGraphStoreContext).listenerStore(selector, equals); + const stableSelector = useStoreSelectorWithEquality(selector, equals); + return React.useContext(DecisionGraphStoreContext).listenerStore(stableSelector); } export function useDecisionGraphReferences( selector: (state: DecisionGraphStoreType['references']) => T, equals: (a: any, b: any) => boolean = equal, ): T { - return React.useContext(DecisionGraphStoreContext).referenceStore(selector, equals); + const stableSelector = useStoreSelectorWithEquality(selector, equals); + return React.useContext(DecisionGraphStoreContext).referenceStore(stableSelector); } export function useDecisionGraphActions(): DecisionGraphStoreType['actions'] { diff --git a/packages/jdm-editor/src/components/decision-table/context/dt-store.context.tsx b/packages/jdm-editor/src/components/decision-table/context/dt-store.context.tsx index 68dcf8a8..00551c1a 100644 --- a/packages/jdm-editor/src/components/decision-table/context/dt-store.context.tsx +++ b/packages/jdm-editor/src/components/decision-table/context/dt-store.context.tsx @@ -404,18 +404,46 @@ export const DecisionTableProvider: React.FC{children}; }; +function useStoreSelectorWithEquality( + selector: (state: TState) => TSlice, + equals: (a: TSlice, b: TSlice) => boolean, +) { + const selectorRef = React.useRef(selector); + selectorRef.current = selector; + const equalsRef = React.useRef(equals); + equalsRef.current = equals; + const prevRef = React.useRef(undefined); + const hasPrevRef = React.useRef(false); + + return React.useCallback( + (state: TState) => { + const next = selectorRef.current(state); + if (hasPrevRef.current && equalsRef.current(prevRef.current as TSlice, next)) { + return prevRef.current as TSlice; + } + + prevRef.current = next; + hasPrevRef.current = true; + return next; + }, + [], + ); +} + export function useDecisionTableState( selector: (state: DecisionTableStoreType['state']) => T, equals: (a: any, b: any) => boolean = equal, ): T { - return React.useContext(DecisionTableStoreContext).stateStore(selector, equals); + const stableSelector = useStoreSelectorWithEquality(selector, equals); + return React.useContext(DecisionTableStoreContext).stateStore(stableSelector); } export function useDecisionTableListeners( selector: (state: DecisionTableStoreType['listeners']) => T, equals: (a: any, b: any) => boolean = equal, ): T { - return React.useContext(DecisionTableStoreContext).listenerStore(selector, equals); + const stableSelector = useStoreSelectorWithEquality(selector, equals); + return React.useContext(DecisionTableStoreContext).listenerStore(stableSelector); } export function useDecisionTableActions(): DecisionTableStoreType['actions'] { diff --git a/packages/jdm-editor/src/components/expression/context/expression-store.context.tsx b/packages/jdm-editor/src/components/expression/context/expression-store.context.tsx index b0b2a14b..768f27cd 100644 --- a/packages/jdm-editor/src/components/expression/context/expression-store.context.tsx +++ b/packages/jdm-editor/src/components/expression/context/expression-store.context.tsx @@ -130,11 +130,38 @@ export const ExpressionStoreProvider: React.FC{children}; }; +function useStoreSelectorWithEquality( + selector: (state: TState) => TSlice, + equals: (a: TSlice, b: TSlice) => boolean, +) { + const selectorRef = React.useRef(selector); + selectorRef.current = selector; + const equalsRef = React.useRef(equals); + equalsRef.current = equals; + const prevRef = React.useRef(undefined); + const hasPrevRef = React.useRef(false); + + return React.useCallback( + (state: TState) => { + const next = selectorRef.current(state); + if (hasPrevRef.current && equalsRef.current(prevRef.current as TSlice, next)) { + return prevRef.current as TSlice; + } + + prevRef.current = next; + hasPrevRef.current = true; + return next; + }, + [], + ); +} + export function useExpressionStore( selector: (state: ExpressionStore) => T, equals: (a: any, b: any) => boolean = equal, ): T { - return React.useContext(ExpressionStoreContext)(selector, equals); + const stableSelector = useStoreSelectorWithEquality(selector, equals); + return React.useContext(ExpressionStoreContext)(stableSelector); } export const useExpressionStoreRaw = () => React.useContext(ExpressionStoreContext); diff --git a/packages/jdm-editor/src/components/expression/expression-command-bar.tsx b/packages/jdm-editor/src/components/expression/expression-command-bar.tsx index 8cdb3c8e..3527ff8c 100644 --- a/packages/jdm-editor/src/components/expression/expression-command-bar.tsx +++ b/packages/jdm-editor/src/components/expression/expression-command-bar.tsx @@ -1,29 +1,25 @@ import { Select, Typography } from 'antd'; -import React, { useMemo } from 'react'; +import React from 'react'; import { P, match } from 'ts-pattern'; import { Stack } from '../stack'; -import { useExpressionStoreRaw } from './context/expression-store.context'; +import { useExpressionStore, useExpressionStoreRaw } from './context/expression-store.context'; export const ExpressionCommandBar: React.FC = () => { const expressionStore = useExpressionStoreRaw(); - const { debugIndex, traceCount } = expressionStore(({ debug, debugIndex }) => ({ + const { debugIndex, traceCount } = useExpressionStore(({ debug, debugIndex }) => ({ debugIndex, traceCount: match(debug?.trace?.traceData) .with(P.array(), (some) => some.length) .otherwise(() => null), })); - const traceIndexOptions = useMemo(() => { - if (!traceCount) { - return null; - } - - return Array.from({ length: traceCount }).map((_, i) => ({ - label: String(i), - value: i, - })); - }, [debugIndex, traceCount]); + const traceIndexOptions = traceCount + ? Array.from({ length: traceCount }).map((_, i) => ({ + label: String(i), + value: i, + })) + : null; if (!traceIndexOptions) { return null; diff --git a/packages/jdm-editor/src/setupTests.js b/packages/jdm-editor/src/setupTests.js index a23988be..9347c17d 100644 --- a/packages/jdm-editor/src/setupTests.js +++ b/packages/jdm-editor/src/setupTests.js @@ -1,3 +1,7 @@ -// react-testing-library renders your components to document.body, -// this adds jest-dom's custom assertions import '@testing-library/jest-dom'; +import { cleanup } from '@testing-library/react'; +import { afterEach } from 'vitest'; + +afterEach(() => { + cleanup(); +}); diff --git a/packages/jdm-editor/vitest.config.ts b/packages/jdm-editor/vitest.config.ts new file mode 100644 index 00000000..a224b6c5 --- /dev/null +++ b/packages/jdm-editor/vitest.config.ts @@ -0,0 +1,19 @@ +import react from '@vitejs/plugin-react-swc'; +import { playwright } from '@vitest/browser-playwright'; +import { defineConfig } from 'vitest/config'; +import wasm from 'vite-plugin-wasm'; + +export default defineConfig({ + plugins: [react(), wasm()], + test: { + globals: true, + setupFiles: ['./src/setupTests.js'], + css: true, + browser: { + enabled: true, + provider: playwright(), + headless: true, + instances: [{ browser: 'chromium' }], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e3acd67..2c135231 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,8 +183,8 @@ importers: specifier: ^3.24.2 version: 3.24.2 zustand: - specifier: ^4.5.5 - version: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + specifier: ^5.0.11 + version: 5.0.11(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.2.2(react@19.1.0)) devDependencies: '@storybook/addon-actions': specifier: 8.6.12 @@ -246,9 +246,18 @@ importers: '@vitejs/plugin-react-swc': specifier: ^3.8.1 version: 3.8.1(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) + '@vitest/browser': + specifier: ^4.0.18 + version: 4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))(vitest@4.0.18) + '@vitest/browser-playwright': + specifier: ^4.0.18 + version: 4.0.18(playwright@1.58.2)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))(vitest@4.0.18) dayjs: specifier: ^1.11.13 version: 1.11.13 + playwright: + specifier: ^1.58.2 + version: 1.58.2 react: specifier: ^19.1.0 version: 19.1.0 @@ -273,6 +282,9 @@ importers: vite-plugin-wasm: specifier: ^3.4.1 version: 3.4.1(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@22.14.1)(@vitest/browser-playwright@4.0.18)(jsdom@28.1.0)(sass@1.86.3)(yaml@2.7.1) packages/lezer-zen: dependencies: @@ -332,6 +344,9 @@ importers: packages: + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + '@adobe/css-tools@4.4.2': resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==} @@ -387,6 +402,16 @@ packages: peerDependencies: react: '>=16.9.0' + '@asamuzakjp/css-color@5.0.1': + resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -454,6 +479,10 @@ packages: resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + '@codemirror/autocomplete@6.18.6': resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} @@ -472,6 +501,37 @@ packages: '@codemirror/view@6.36.5': resolution: {integrity: sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==} + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.0.2': + resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.0.29': + resolution: {integrity: sha512-jx9GjkkP5YHuTmko2eWAvpPnb0mB4mGRr2U7XwVNwevm8nlpobZEVk+GNmiYMk2VuA75v+plfXWyroWKmICZXg==} + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@ctrl/tinycolor@3.6.1': resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} engines: {node: '>=10'} @@ -692,6 +752,15 @@ packages: resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.14.1': + resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@fast-csv/format@4.3.5': resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} @@ -757,6 +826,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -1088,6 +1160,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rc-component/async-validator@5.0.4': resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} engines: {node: '>=14.x'} @@ -1361,6 +1436,9 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@storybook/addon-actions@8.6.12': resolution: {integrity: sha512-B5kfiRvi35oJ0NIo53CGH66H471A3XTzrfaa6SxXEJsgxxSeKScG5YeXcCvLiZfvANRQ7QDsmzPUgg0o3hdMXw==} peerDependencies: @@ -1728,6 +1806,9 @@ packages: '@types/big.js@6.2.2': resolution: {integrity: sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -1821,6 +1902,9 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} @@ -1926,24 +2010,64 @@ packages: peerDependencies: vite: ^4 || ^5 || ^6 + '@vitest/browser-playwright@4.0.18': + resolution: {integrity: sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==} + peerDependencies: + playwright: '*' + vitest: 4.0.18 + + '@vitest/browser@4.0.18': + resolution: {integrity: sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==} + peerDependencies: + vitest: 4.0.18 + '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@2.0.5': resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/utils@2.0.5': resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@volar/language-core@2.4.12': resolution: {integrity: sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==} @@ -2196,6 +2320,9 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -2211,6 +2338,7 @@ packages: binary-install@1.1.0: resolution: {integrity: sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==} engines: {node: '>=10'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. binary@0.3.0: resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} @@ -2302,6 +2430,10 @@ packages: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chainsaw@0.1.0: resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} @@ -2530,6 +2662,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -2538,6 +2674,10 @@ packages: engines: {node: '>=4'} hasBin: true + cssstyle@6.2.0: + resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} + engines: {node: '>=20'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -2583,6 +2723,10 @@ packages: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -2625,6 +2769,9 @@ packages: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -2761,6 +2908,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -2792,6 +2943,9 @@ packages: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2921,6 +3075,10 @@ packages: resolution: {integrity: sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==} engines: {node: '>=10'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + exponential-backoff@3.1.2: resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} @@ -2951,6 +3109,15 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -3031,6 +3198,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3087,6 +3259,7 @@ packages: git-raw-commits@3.0.0: resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==} engines: {node: '>=14'} + deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. hasBin: true git-remote-origin-url@2.0.0: @@ -3096,6 +3269,7 @@ packages: git-semver-tags@5.0.1: resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==} engines: {node: '>=14'} + deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. hasBin: true git-up@7.0.0: @@ -3214,6 +3388,10 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -3421,6 +3599,9 @@ packages: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -3537,6 +3718,15 @@ packages: resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} engines: {node: '>=12.0.0'} + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -3751,6 +3941,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3774,6 +3968,9 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -3801,6 +3998,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -3935,6 +4135,10 @@ packages: monaco-editor@0.52.2: resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4093,6 +4297,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -4221,6 +4428,9 @@ packages: parse-url@8.1.0: resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -4277,6 +4487,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pidtree@0.3.1: resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} engines: {node: '>=0.10'} @@ -4298,6 +4512,10 @@ packages: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} engines: {node: '>=10'} + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -4308,6 +4526,20 @@ packages: pkg-types@2.1.0: resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + polished@4.3.1: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} @@ -4889,6 +5121,10 @@ packages: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -4974,6 +5210,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4988,6 +5227,10 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -5044,9 +5287,15 @@ packages: resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + storybook-dark-mode@4.0.2: resolution: {integrity: sha512-zjcwwQ01R5t1VsakA6alc2JDIRVtavryW8J3E3eKLDIlAMcvsgtpxlelWkZs2cuNspk6Z10XzhQVrUWtYc3F0w==} @@ -5162,6 +5411,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -5169,6 +5421,7 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me temp-dir@1.0.0: resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} @@ -5191,14 +5444,36 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@7.0.24: + resolution: {integrity: sha512-pj7yygNMoMRqG7ML2SDQ0xNIOfN3IBDUcPVM2Sg6hP96oFNN2nqnzHreT3z9xLq85IWJyNTvD38O002DdOrPMw==} + + tldts@7.0.24: + resolution: {integrity: sha512-1r6vQTTt1rUiJkI5vX7KG8PR342Ru/5Oh13kEQP2SMbRSZpOey9SrBe27IDxkoWulx8ShWu4K6C0BkctP8Z1bQ==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -5217,9 +5492,21 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -5323,6 +5610,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.22.0: + resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} + engines: {node: '>=20.18.1'} + unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -5448,12 +5739,50 @@ packages: yaml: optional: true + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + walk-up-path@3.0.1: resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} @@ -5467,9 +5796,21 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -5503,6 +5844,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -5558,6 +5904,22 @@ packages: utf-8-validate: optional: true + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -5611,8 +5973,8 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} - zustand@4.5.5: - resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} engines: {node: '>=12.7.0'} peerDependencies: '@types/react': '>=16.8' @@ -5626,8 +5988,29 @@ packages: react: optional: true + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: + '@acemir/cssom@0.9.31': + optional: true + '@adobe/css-tools@4.4.2': {} '@ampproject/remapping@2.3.0': @@ -5699,6 +6082,27 @@ snapshots: resize-observer-polyfill: 1.5.1 throttle-debounce: 5.0.2 + '@asamuzakjp/css-color@5.0.1': + dependencies: + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.2.6 + optional: true + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.6 + optional: true + + '@asamuzakjp/nwsapi@2.3.9': + optional: true + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -5801,6 +6205,11 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.1.0 + optional: true + '@codemirror/autocomplete@6.18.6': dependencies: '@codemirror/language': 6.11.0 @@ -5840,6 +6249,34 @@ snapshots: style-mod: 4.1.2 w3c-keyname: 2.2.8 + '@csstools/color-helpers@6.0.2': + optional: true + + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + optional: true + + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + optional: true + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + optional: true + + '@csstools/css-syntax-patches-for-csstree@1.0.29': + optional: true + + '@csstools/css-tokenizer@4.0.0': + optional: true + '@ctrl/tinycolor@3.6.1': {} '@emnapi/core@1.4.1': @@ -5986,6 +6423,9 @@ snapshots: '@eslint/core': 0.13.0 levn: 0.4.1 + '@exodus/bytes@1.14.1': + optional: true + '@fast-csv/format@4.3.5': dependencies: '@types/node': 14.18.63 @@ -6056,6 +6496,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -6531,6 +6973,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@polka/url@1.0.0-next.29': {} + '@rc-component/async-validator@5.0.4': dependencies: '@babel/runtime': 7.27.0 @@ -6618,7 +7062,7 @@ snapshots: classcat: 5.0.5 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6629,7 +7073,7 @@ snapshots: classcat: 5.0.5 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6646,7 +7090,7 @@ snapshots: d3-zoom: 3.0.0 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6661,7 +7105,7 @@ snapshots: d3-zoom: 3.0.0 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6674,7 +7118,7 @@ snapshots: d3-selection: 3.0.0 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6685,7 +7129,7 @@ snapshots: classcat: 5.0.5 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - zustand: 4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) + zustand: 4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer @@ -6836,6 +7280,8 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@standard-schema/spec@1.1.0': {} + '@storybook/addon-actions@8.6.12(storybook@8.6.12(prettier@3.5.3))': dependencies: '@storybook/global': 5.0.0 @@ -7242,6 +7688,11 @@ snapshots: '@types/big.js@6.2.2': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/d3-array@3.2.1': {} '@types/d3-axis@3.0.6': @@ -7359,6 +7810,8 @@ snapshots: '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 + '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} '@types/estree@1.0.7': {} @@ -7485,6 +7938,36 @@ snapshots: transitivePeerDependencies: - '@swc/helpers' + '@vitest/browser-playwright@4.0.18(playwright@1.58.2)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))(vitest@4.0.18)': + dependencies: + '@vitest/browser': 4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))(vitest@4.0.18) + '@vitest/mocker': 4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) + playwright: 1.58.2 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@22.14.1)(@vitest/browser-playwright@4.0.18)(jsdom@28.1.0)(sass@1.86.3)(yaml@2.7.1) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))(vitest@4.0.18)': + dependencies: + '@vitest/mocker': 4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) + '@vitest/utils': 4.0.18 + magic-string: 0.30.21 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@22.14.1)(@vitest/browser-playwright@4.0.18)(jsdom@28.1.0)(sass@1.86.3)(yaml@2.7.1) + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + '@vitest/expect@2.0.5': dependencies: '@vitest/spy': 2.0.5 @@ -7492,6 +7975,23 @@ snapshots: chai: 5.2.0 tinyrainbow: 1.2.0 + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1) + '@vitest/pretty-format@2.0.5': dependencies: tinyrainbow: 1.2.0 @@ -7500,10 +8000,27 @@ snapshots: dependencies: tinyrainbow: 1.2.0 + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@2.0.5': dependencies: tinyspy: 3.0.2 + '@vitest/spy@4.0.18': {} + '@vitest/utils@2.0.5': dependencies: '@vitest/pretty-format': 2.0.5 @@ -7517,6 +8034,11 @@ snapshots: loupe: 3.1.3 tinyrainbow: 1.2.0 + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + '@volar/language-core@2.4.12': dependencies: '@volar/source-map': 2.4.12 @@ -7862,6 +8384,11 @@ snapshots: dependencies: open: 8.4.2 + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + optional: true + big-integer@1.6.52: {} bin-links@4.0.4: @@ -7987,6 +8514,8 @@ snapshots: loupe: 3.1.3 pathval: 2.0.0 + chai@6.2.2: {} + chainsaw@0.1.0: dependencies: traverse: 0.3.9 @@ -8236,10 +8765,24 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + optional: true + css.escape@1.5.1: {} cssesc@3.0.0: {} + cssstyle@6.2.0: + dependencies: + '@asamuzakjp/css-color': 5.0.1 + '@csstools/css-syntax-patches-for-csstree': 1.0.29 + css-tree: 3.1.0 + lru-cache: 11.2.6 + optional: true + csstype@3.1.3: {} d3-color@3.1.0: {} @@ -8280,6 +8823,14 @@ snapshots: dargs@7.0.0: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + optional: true + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -8319,6 +8870,9 @@ snapshots: decamelize@4.0.0: {} + decimal.js@10.6.0: + optional: true + dedent@1.5.3: {} deep-eql@5.0.2: {} @@ -8431,6 +8985,9 @@ snapshots: entities@4.5.0: {} + entities@6.0.1: + optional: true + env-paths@2.2.1: {} envinfo@7.13.0: {} @@ -8518,6 +9075,8 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -8719,6 +9278,8 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + expect-type@1.3.0: {} + exponential-backoff@3.1.2: {} exsolve@1.0.4: {} @@ -8752,6 +9313,10 @@ snapshots: dependencies: reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -8831,6 +9396,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -9032,6 +9600,13 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.14.1 + transitivePeerDependencies: + - '@noble/hashes' + optional: true + http-cache-semantics@4.1.1: {} http-proxy-agent@7.0.2: @@ -9244,6 +9819,9 @@ snapshots: dependencies: isobject: 3.0.1 + is-potential-custom-element-name@1.0.1: + optional: true + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -9359,6 +9937,34 @@ snapshots: jsdoc-type-pratt-parser@4.1.0: {} + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.2 + '@exodus/bytes': 1.14.1 + cssstyle: 6.2.0 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + undici: 7.22.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + optional: true + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -9641,6 +10247,9 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.6: + optional: true + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -9663,6 +10272,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -9697,6 +10310,9 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.12.2: + optional: true + memoizerific@1.11.3: dependencies: map-or-similar: 1.5.0 @@ -9851,6 +10467,8 @@ snapshots: monaco-editor@0.52.2: {} + mrmime@2.0.1: {} + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -10079,6 +10697,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + obug@2.1.1: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -10243,6 +10863,11 @@ snapshots: dependencies: parse-path: 7.1.0 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + optional: true + path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -10278,6 +10903,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + pidtree@0.3.1: {} pify@2.3.0: {} @@ -10288,6 +10915,10 @@ snapshots: pify@5.0.0: {} + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -10304,6 +10935,16 @@ snapshots: exsolve: 1.0.4 pathe: 2.0.3 + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@7.0.0: {} + polished@4.3.1: dependencies: '@babel/runtime': 7.27.0 @@ -11015,6 +11656,11 @@ snapshots: dependencies: xmlchars: 2.2.0 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + optional: true + scheduler@0.26.0: {} scroll-into-view-if-needed@3.1.0: @@ -11107,6 +11753,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -11126,6 +11774,12 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + slash@3.0.0: {} smart-buffer@4.2.0: {} @@ -11181,8 +11835,12 @@ snapshots: dependencies: minipass: 7.1.2 + stackback@0.0.2: {} + state-local@1.0.7: {} + std-env@3.10.0: {} + storybook-dark-mode@4.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@8.6.12(prettier@3.5.3)): dependencies: '@storybook/components': 8.6.12(storybook@8.6.12(prettier@3.5.3)) @@ -11331,6 +11989,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: + optional: true + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -11363,10 +12024,29 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinyrainbow@1.2.0: {} + tinyrainbow@3.0.3: {} + tinyspy@3.0.2: {} + tldts-core@7.0.24: + optional: true + + tldts@7.0.24: + dependencies: + tldts-core: 7.0.24 + optional: true + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -11388,8 +12068,20 @@ snapshots: toggle-selection@1.0.6: {} + totalist@3.0.1: {} + + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.24 + optional: true + tr46@0.0.3: {} + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + optional: true + traverse@0.3.9: {} treeverse@3.0.0: {} @@ -11487,6 +12179,9 @@ snapshots: undici-types@6.21.0: {} + undici@7.22.0: + optional: true + unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 @@ -11594,10 +12289,54 @@ snapshots: sass: 1.86.3 yaml: 2.7.1 + vitest@4.0.18(@types/node@22.14.1)(@vitest/browser-playwright@4.0.18)(jsdom@28.1.0)(sass@1.86.3)(yaml@2.7.1): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.14.1 + '@vitest/browser-playwright': 4.0.18(playwright@1.58.2)(vite@6.2.6(@types/node@22.14.1)(sass@1.86.3)(yaml@2.7.1))(vitest@4.0.18) + jsdom: 28.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + vscode-uri@3.1.0: {} w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + optional: true + walk-up-path@3.0.1: {} wasm-pack@0.13.1: @@ -11612,8 +12351,23 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@8.0.1: + optional: true + webpack-virtual-modules@0.6.2: {} + whatwg-mimetype@5.0.0: + optional: true + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.14.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + optional: true + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -11672,6 +12426,11 @@ snapshots: dependencies: isexe: 3.1.1 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 @@ -11730,6 +12489,11 @@ snapshots: ws@8.18.1: {} + ws@8.19.0: {} + + xml-name-validator@5.0.0: + optional: true + xmlchars@2.2.0: {} xtend@4.0.2: {} @@ -11783,10 +12547,17 @@ snapshots: zod@3.24.2: {} - zustand@4.5.5(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0): + zustand@4.5.7(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0): dependencies: use-sync-external-store: 1.2.2(react@19.1.0) optionalDependencies: '@types/react': 19.1.8 immer: 10.1.1 react: 19.1.0 + + zustand@5.0.11(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.2.2(react@19.1.0)): + optionalDependencies: + '@types/react': 19.1.8 + immer: 10.1.1 + react: 19.1.0 + use-sync-external-store: 1.2.2(react@19.1.0) From 826daba45eb2380695c7b5bc2c6d62bb61d526b3 Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 11:52:27 -0500 Subject: [PATCH 07/12] Add basic set of tests --- .../decision-graph/__tests__/dg-util.test.ts | 70 +++++++++ .../diff/__tests__/comparison.test.ts | 78 ++++++++++ .../diff/__tests__/utility.test.ts | 140 ++++++++++++++++++ .../helpers/__tests__/determine-type.test.ts | 24 +++ .../src/helpers/__tests__/components.test.ts | 47 ++++++ .../src/helpers/__tests__/node-data.test.ts | 52 +++++++ .../src/helpers/__tests__/schema.test.ts | 75 ++++++++++ .../src/helpers/__tests__/trace.test.ts | 21 +++ .../src/helpers/__tests__/traversal.test.ts | 63 ++++++++ 9 files changed, 570 insertions(+) create mode 100644 packages/jdm-editor/src/components/decision-graph/__tests__/dg-util.test.ts create mode 100644 packages/jdm-editor/src/components/decision-graph/diff/__tests__/comparison.test.ts create mode 100644 packages/jdm-editor/src/components/decision-graph/diff/__tests__/utility.test.ts create mode 100644 packages/jdm-editor/src/components/function/helpers/__tests__/determine-type.test.ts create mode 100644 packages/jdm-editor/src/helpers/__tests__/components.test.ts create mode 100644 packages/jdm-editor/src/helpers/__tests__/node-data.test.ts create mode 100644 packages/jdm-editor/src/helpers/__tests__/schema.test.ts create mode 100644 packages/jdm-editor/src/helpers/__tests__/trace.test.ts create mode 100644 packages/jdm-editor/src/helpers/__tests__/traversal.test.ts diff --git a/packages/jdm-editor/src/components/decision-graph/__tests__/dg-util.test.ts b/packages/jdm-editor/src/components/decision-graph/__tests__/dg-util.test.ts new file mode 100644 index 00000000..c797eafc --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/__tests__/dg-util.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest'; +import type { Edge } from 'reactflow'; + +import { privateSymbol } from '../dg-types'; +import { mapToDecisionEdge, mapToGraphEdges, mapToGraphNode } from '../dg-util'; + +describe('dg-util mappers', () => { + it('maps reactflow edge to decision edge', () => { + const edge: Edge = { + id: 'edge-1', + source: 'node-a', + target: 'node-b', + sourceHandle: null, + targetHandle: 'target-handle', + label: 'Flow', + type: 'edge', + }; + const mapped = mapToDecisionEdge(edge); + + expect(mapped).toEqual({ + id: 'edge-1', + sourceId: 'node-a', + targetId: 'node-b', + name: 'Flow', + sourceHandle: undefined, + targetHandle: 'target-handle', + type: 'edge', + }); + }); + + it('maps decision node to graph node including private layout metadata', () => { + const mapped = mapToGraphNode({ + id: 'n-1', + type: 'expressionNode', + name: 'Expression', + position: { x: 11, y: 22 }, + content: { kind: 'calc' }, + [privateSymbol]: { + dimensions: { width: 400, height: 120 }, + selected: true, + }, + }); + + expect(mapped).toMatchObject({ + id: 'n-1', + type: 'expressionNode', + position: { x: 11, y: 22 }, + width: 400, + height: 120, + selected: true, + data: { name: 'Expression', kind: 'calc' }, + }); + }); + + it('filters invalid edges when mapping decision edges to graph edges', () => { + const mapped = mapToGraphEdges([ + { id: 'e-1', sourceId: 'a', targetId: 'b', type: 'edge' }, + { id: 'e-2', sourceId: '', targetId: 'b', type: 'edge' }, + { id: 'e-3', sourceId: 'a', targetId: '', type: 'edge' }, + ]); + + expect(mapped).toHaveLength(1); + expect(mapped[0]).toMatchObject({ + id: 'e-1', + source: 'a', + target: 'b', + type: 'edge', + }); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/diff/__tests__/comparison.test.ts b/packages/jdm-editor/src/components/decision-graph/diff/__tests__/comparison.test.ts new file mode 100644 index 00000000..ed0a427d --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/diff/__tests__/comparison.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; + +import { + addStrikethrough, + buildDiffString, + compareAndUnifyLists, + compareStringFields, +} from '../comparison'; + +const getDiffStatus = (item: unknown): unknown => { + if (!item || typeof item !== 'object') return undefined; + const diff = Reflect.get(item, '_diff'); + if (!diff || typeof diff !== 'object') return undefined; + return Reflect.get(diff, 'status'); +}; + +const getPreviousLabel = (item: unknown): unknown => { + if (!item || typeof item !== 'object') return undefined; + const diff = Reflect.get(item, '_diff'); + if (!diff || typeof diff !== 'object') return undefined; + const fields = Reflect.get(diff, 'fields'); + if (!fields || typeof fields !== 'object') return undefined; + const label = Reflect.get(fields, 'label'); + if (!label || typeof label !== 'object') return undefined; + return Reflect.get(label, 'previousValue'); +}; + +describe('diff/comparison helpers', () => { + it('creates strikethrough content and diff strings', () => { + expect(addStrikethrough()).toBe(''); + expect(addStrikethrough('ab')).toBe('a\u0336b\u0336'); + expect(buildDiffString('new', 'old')).toBe('o\u0336l\u0336d\u0336 -> new'); + }); + + it('compares strings ignoring nullish values and whitespace', () => { + expect(compareStringFields(' value ', 'value')).toBe(true); + expect(compareStringFields(null, undefined)).toBe(true); + expect(compareStringFields('a', 'b')).toBe(false); + }); + + it('unifies lists and marks added/removed/unchanged statuses', () => { + const oldList = [ + { id: '1', label: 'one' }, + { id: '2', label: 'two' }, + { id: '3', label: 'three' }, + ]; + const newList = [ + { id: '1', label: 'one' }, + { id: '3', label: 'three' }, + { id: '4', label: 'four' }, + ]; + + const unified = compareAndUnifyLists(newList, oldList); + + expect(unified.map((x) => x.id)).toEqual(['1', '2', '3', '4']); + expect(unified.map(getDiffStatus)).toEqual(['unchanged', 'removed', 'unchanged', 'added']); + }); + + it('marks item as modified and includes field-level metadata from compareFields', () => { + const oldList = [{ id: '1', label: 'old' }]; + const newList = [{ id: '1', label: 'new' }]; + + const unified = compareAndUnifyLists(newList, oldList, { + compareFields: (current, previous) => ({ + hasChanges: current.label !== previous.label, + fields: { + label: { + status: 'modified', + previousValue: previous.label, + }, + }, + }), + }); + + expect(getDiffStatus(unified[0])).toBe('modified'); + expect(getPreviousLabel(unified[0])).toBe('old'); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/diff/__tests__/utility.test.ts b/packages/jdm-editor/src/components/decision-graph/diff/__tests__/utility.test.ts new file mode 100644 index 00000000..b5b4b75b --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/diff/__tests__/utility.test.ts @@ -0,0 +1,140 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { DecisionEdge, DecisionNode } from '../../dg-types'; +import { calculateDiffGraph, processEdges, processNodes } from '../utility'; + +describe('diff/utility', () => { + it('processEdges marks added, unchanged, and removed edges', () => { + const current: DecisionEdge[] = [ + { id: 'same', sourceId: 'a', targetId: 'b', type: 'edge' }, + { id: 'add', sourceId: 'b', targetId: 'c', type: 'edge' }, + ]; + const previous: DecisionEdge[] = [ + { id: 'same', sourceId: 'a', targetId: 'b', type: 'edge' }, + { id: 'del', sourceId: 'x', targetId: 'y', type: 'edge' }, + ]; + + const result = processEdges(current, previous); + const byId = Object.fromEntries(result.map((edge) => [edge.id, edge])); + + expect(byId.add._diff?.status).toBe('added'); + expect(byId.same._diff?.status).toBe('unchanged'); + expect(byId.del._diff?.status).toBe('removed'); + }); + + it('processNodes marks moved, modified, unchanged, added, and removed statuses', () => { + const current: DecisionNode[] = [ + { id: 'moved', type: 'vendor-node', name: 'Moved', content: {}, position: { x: 10, y: 0 } }, + { id: 'modified', type: 'vendor-node', name: 'Renamed', content: {}, position: { x: 0, y: 0 } }, + { id: 'same', type: 'vendor-node', name: 'Same', content: {}, position: { x: 0, y: 0 } }, + { id: 'added', type: 'vendor-node', name: 'Added', content: {}, position: { x: 0, y: 0 } }, + ]; + const previous: DecisionNode[] = [ + { id: 'moved', type: 'vendor-node', name: 'Moved', content: {}, position: { x: 0, y: 0 } }, + { id: 'modified', type: 'vendor-node', name: 'OldName', content: {}, position: { x: 0, y: 0 } }, + { id: 'same', type: 'vendor-node', name: 'Same', content: {}, position: { x: 0, y: 0 } }, + { id: 'removed', type: 'vendor-node', name: 'Removed', content: {}, position: { x: 0, y: 0 } }, + ]; + + const result = processNodes(current, previous); + const byId = Object.fromEntries(result.map((node) => [node.id, node])); + + expect(byId.moved._diff?.status).toBe('moved'); + expect(byId.modified._diff?.status).toBe('modified'); + expect(byId.same._diff?.status).toBe('unchanged'); + expect(byId.added._diff?.status).toBe('added'); + expect(byId.removed._diff?.status).toBe('removed'); + }); + + it('uses custom component getDiffContent when provided', () => { + const current: DecisionNode[] = [ + { id: 'comp', type: 'component-x', name: 'Component', content: { next: true }, position: { x: 0, y: 0 } }, + ]; + + const previous: DecisionNode[] = [ + { id: 'comp', type: 'component-x', name: 'Component', content: { prev: true }, position: { x: 0, y: 0 } }, + ]; + + const result = processNodes(current, previous, { + components: [ + { + type: 'component-x', + icon: null, + color: '#000', + displayName: 'X', + group: 'custom', + renderNode: () => null, + renderTab: () => null, + generateNode: () => ({ name: 'component-x', content: {} }), + getDiffContent: () => ({ _diff: { status: 'modified' }, from: 'component' }), + }, + ], + customNodes: [], + }); + + const byId = Object.fromEntries(result.map((node) => [node.id, node])); + + expect(byId.comp._diff?.status).toBe('modified'); + expect(byId.comp.content).toEqual({ _diff: { status: 'modified' }, from: 'component' }); + }); + + it('calls custom node calculateDiff seam for matching custom node kinds', () => { + const calculateDiff = vi.fn((current: unknown, previous: unknown): [unknown, unknown] => [current, previous]); + const current: DecisionNode[] = [ + { + id: 'custom', + type: 'customNode', + name: 'Custom', + content: { kind: 'custom-kind', config: { next: true } }, + position: { x: 0, y: 0 }, + }, + ]; + const previous: DecisionNode[] = [ + { + id: 'custom', + type: 'customNode', + name: 'Custom', + content: { kind: 'custom-kind', config: { prev: true } }, + position: { x: 0, y: 0 }, + }, + ]; + + const result = processNodes(current, previous, { + customNodes: [ + { + kind: 'custom-kind', + displayName: 'Custom', + group: 'custom', + icon: null, + renderNode: () => null, + renderTab: () => null, + generateNode: () => ({ name: 'custom-node' }), + calculateDiff, + }, + ], + components: [], + }); + + expect(calculateDiff).toHaveBeenCalledTimes(1); + expect(result[0]?._diff?.status).toBe('unchanged'); + + expect(calculateDiff.mock.calls[0]?.[0]).toEqual({ kind: 'custom-kind', config: { next: true } }); + expect(calculateDiff.mock.calls[0]?.[1]).toEqual({ kind: 'custom-kind', config: { prev: true } }); + }); + + it('calculateDiffGraph combines node and edge diffs', () => { + const diff = calculateDiffGraph( + { + nodes: [{ id: 'n1', type: 'vendor-node', name: 'N1', content: {}, position: { x: 0, y: 0 } }], + edges: [{ id: 'e1', sourceId: 'n1', targetId: 'n2', type: 'edge' }], + }, + { + nodes: [{ id: 'n2', type: 'vendor-node', name: 'N2', content: {}, position: { x: 0, y: 0 } }], + edges: [{ id: 'e2', sourceId: 'n2', targetId: 'n1', type: 'edge' }], + }, + ); + + expect(diff.nodes.map((n) => n._diff?.status).sort()).toEqual(['added', 'removed']); + expect(diff.edges.map((e) => e._diff?.status).sort()).toEqual(['added', 'removed']); + }); +}); diff --git a/packages/jdm-editor/src/components/function/helpers/__tests__/determine-type.test.ts b/packages/jdm-editor/src/components/function/helpers/__tests__/determine-type.test.ts new file mode 100644 index 00000000..27b0abb0 --- /dev/null +++ b/packages/jdm-editor/src/components/function/helpers/__tests__/determine-type.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; + +import { variableTypeToTypescript } from '../determine-type'; + +const makeVariableType = (value: unknown) => ({ toJson: () => value }) as never; + +describe('variableTypeToTypescript', () => { + it('maps primitive variable types', () => { + expect(variableTypeToTypescript(makeVariableType('String'))).toBe('string'); + expect(variableTypeToTypescript(makeVariableType('Bool'))).toBe('boolean'); + expect(variableTypeToTypescript(makeVariableType('Number'))).toBe('number'); + expect(variableTypeToTypescript(makeVariableType('Null'))).toBe('null'); + expect(variableTypeToTypescript(makeVariableType('Any'))).toBe('any'); + }); + + it('maps array, object, const and enum variable types', () => { + expect(variableTypeToTypescript(makeVariableType({ Array: 'String' }))).toBe('string[]'); + expect(variableTypeToTypescript(makeVariableType({ Object: { a: 'Number', b: { Array: 'Bool' } } }))).toBe( + '{a:number,b:boolean[]}', + ); + expect(variableTypeToTypescript(makeVariableType({ Const: 'usd' }))).toBe('"usd"'); + expect(variableTypeToTypescript(makeVariableType({ Enum: ['String', ['usd', 'eur']] }))).toBe('"usd" | "eur"'); + }); +}); diff --git a/packages/jdm-editor/src/helpers/__tests__/components.test.ts b/packages/jdm-editor/src/helpers/__tests__/components.test.ts new file mode 100644 index 00000000..bef4fb9e --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/components.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; + +import { columnIdSelector, getPath, recursiveSelect, type SchemaSelectProps } from '../components'; + +const fields: SchemaSelectProps[] = [ + { + field: 'header', + items: [ + { field: 'invoice_number' }, + { + field: 'vendor', + items: [{ field: 'name' }], + }, + ], + }, + { field: 'amount_total' }, +]; + +describe('components helpers', () => { + it('recursiveSelect returns deeply nested field', () => { + expect(recursiveSelect(['header', 'vendor', 'name'], fields)).toEqual({ field: 'name' }); + }); + + it('recursiveSelect returns undefined when selector does not match', () => { + expect(recursiveSelect(['header', 'missing'], fields)).toBeUndefined(); + }); + + it('getPath returns full path for nested field', () => { + expect(getPath('name', fields)).toEqual(['header', 'vendor', 'name']); + }); + + it('getPath returns undefined for missing field', () => { + expect(getPath('missing', fields)).toBeUndefined(); + }); + + it('columnIdSelector resolves both input and output columns and marks colType', () => { + const state = { + decisionTable: { + inputs: [{ id: 'i-1', name: 'in' }], + outputs: [{ id: 'o-1', name: 'out' }], + }, + }; + + expect(columnIdSelector('i-1')(state as never)).toMatchObject({ id: 'i-1', colType: 'input' }); + expect(columnIdSelector('o-1')(state as never)).toMatchObject({ id: 'o-1', colType: 'output' }); + }); +}); diff --git a/packages/jdm-editor/src/helpers/__tests__/node-data.test.ts b/packages/jdm-editor/src/helpers/__tests__/node-data.test.ts new file mode 100644 index 00000000..13089f77 --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/node-data.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest'; + +import type { DecisionGraphType } from '../../components/decision-graph/dg-types'; +import { getNodeData } from '../node-data'; + +const graph: DecisionGraphType = { + nodes: [ + { id: 'input', name: 'Input', type: 'inputNode', position: { x: 0, y: 0 }, content: {} }, + { id: 'expr', name: 'AutoApproved', type: 'expressionNode', position: { x: 1, y: 1 }, content: {} }, + { id: 'output', name: 'Output', type: 'outputNode', position: { x: 2, y: 2 }, content: {} }, + ], + edges: [], +}; + +describe('getNodeData', () => { + it('returns node input data and $nodes outputs excluding output nodes', () => { + const trace = { + in: { + id: 'input', + name: 'Input', + input: { request: true }, + output: { extracted: true }, + performance: null, + traceData: null, + }, + ex: { + id: 'expr', + name: 'AutoApproved', + input: { amount: 10 }, + output: { auto_approved: true }, + performance: null, + traceData: null, + }, + out: { + id: 'output', + name: 'Output', + input: { any: true }, + output: { final: true }, + performance: null, + traceData: null, + }, + }; + + const result = getNodeData('expr', { trace, decisionGraph: graph }); + + expect(result.data).toEqual({ amount: 10 }); + expect(result.$nodes).toEqual({ + Input: { extracted: true }, + AutoApproved: { auto_approved: true }, + }); + }); +}); diff --git a/packages/jdm-editor/src/helpers/__tests__/schema.test.ts b/packages/jdm-editor/src/helpers/__tests__/schema.test.ts new file mode 100644 index 00000000..c862ab8a --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/schema.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from 'vitest'; + +import { + CustomKind, + NodeKind, + decisionModelSchema, + edgeSchema, + expressionNodeSchema, + inputNodeSchema, + nodeSchema, +} from '../schema'; + +describe('schema helpers', () => { + it('applies model defaults for nodes and edges', () => { + expect(decisionModelSchema.parse({})).toEqual({ nodes: [], edges: [] }); + }); + + it('normalizes nullish schema fields in input nodes', () => { + const node = inputNodeSchema.parse({ + type: NodeKind.Input, + name: 'Input', + content: { schema: null }, + }); + + expect(node.content.schema).toBe(''); + expect(node.position).toEqual({ x: 0, y: 0 }); + }); + + it('normalizes expression execution fields', () => { + const node = expressionNodeSchema.parse({ + type: NodeKind.Expression, + name: 'Expr', + content: { + expressions: [], + passThrough: null, + inputField: ' ', + outputPath: '', + executionMode: null, + }, + }); + + expect(node.content.passThrough).toBe(false); + expect(node.content.inputField).toBeNull(); + expect(node.content.outputPath).toBeNull(); + expect(node.content.executionMode).toBe('single'); + }); + + it('accepts custom nodes and unknown node types via nodeSchema', () => { + const custom = nodeSchema.parse({ + type: CustomKind, + name: 'Custom', + content: { kind: 'x', config: { enabled: true } }, + }); + + const unknown = nodeSchema.parse({ + type: 'vendor-special-node', + name: 'Unknown', + content: { anything: true }, + }); + + expect(custom.type).toBe(CustomKind); + expect(unknown.type).toBe('vendor-special-node'); + }); + + it('validates edge type as edge', () => { + expect( + edgeSchema.parse({ + id: 'e1', + sourceId: 'a', + targetId: 'b', + type: 'edge', + }), + ).toMatchObject({ id: 'e1', type: 'edge' }); + }); +}); diff --git a/packages/jdm-editor/src/helpers/__tests__/trace.test.ts b/packages/jdm-editor/src/helpers/__tests__/trace.test.ts new file mode 100644 index 00000000..741e40ea --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/trace.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; + +import { getTrace } from '../trace'; + +describe('getTrace', () => { + it('returns the value directly for non-array inputs', () => { + expect(getTrace('value', 0)).toBe('value'); + }); + + it('returns indexed array element when index is valid', () => { + expect(getTrace(['a', 'b', 'c'], 2)).toBe('c'); + }); + + it('falls back to first element when index is out of range', () => { + expect(getTrace(['a', 'b', 'c'], 99)).toBe('a'); + }); + + it('falls back to first element for negative index', () => { + expect(getTrace(['a', 'b', 'c'], -1)).toBe('a'); + }); +}); diff --git a/packages/jdm-editor/src/helpers/__tests__/traversal.test.ts b/packages/jdm-editor/src/helpers/__tests__/traversal.test.ts new file mode 100644 index 00000000..622431d0 --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/traversal.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; + +import type { DecisionGraphType } from '../../components/decision-graph/dg-types'; +import { createGraphWalker } from '../traversal'; + +const baseGraph: DecisionGraphType = { + nodes: [ + { id: 'input', type: 'inputNode', name: 'Input', position: { x: 0, y: 0 }, content: {} }, + { id: 'a', type: 'expressionNode', name: 'A', position: { x: 1, y: 0 }, content: {} }, + { id: 'b', type: 'expressionNode', name: 'B', position: { x: 2, y: 0 }, content: {} }, + { id: 'c', type: 'outputNode', name: 'C', position: { x: 3, y: 0 }, content: {} }, + ], + edges: [ + { id: 'e1', sourceId: 'input', targetId: 'a', type: 'edge' }, + { id: 'e2', sourceId: 'input', targetId: 'b', type: 'edge' }, + { id: 'e3', sourceId: 'a', targetId: 'c', type: 'edge' }, + { id: 'e4', sourceId: 'b', targetId: 'c', type: 'edge' }, + ], +}; + +describe('createGraphWalker', () => { + it('walks graph and yields nodes after incomers are satisfied', () => { + const walker = createGraphWalker(); + const result = Array.from(walker.walk(baseGraph)); + + expect(result[0]?.node.id).toBe('input'); + + const final = result.find((step) => step.node.id === 'c'); + expect(final?.incomers.map((n) => n.id).sort()).toEqual(['a', 'b']); + }); + + it('returns empty iterator when graph has a cycle', () => { + const walker = createGraphWalker(); + + const cycleGraph: DecisionGraphType = { + nodes: [ + { id: 'input', type: 'inputNode', name: 'Input', position: { x: 0, y: 0 }, content: {} }, + { id: 'a', type: 'expressionNode', name: 'A', position: { x: 1, y: 0 }, content: {} }, + ], + edges: [ + { id: 'e1', sourceId: 'input', targetId: 'a', type: 'edge' }, + { id: 'e2', sourceId: 'a', targetId: 'input', type: 'edge' }, + ], + }; + + expect(Array.from(walker.walk(cycleGraph))).toEqual([]); + }); + + it('resolves current node data by id on subsequent walks', () => { + const walker = createGraphWalker(); + const firstPass = Array.from(walker.walk(baseGraph)); + + const renamedGraph: DecisionGraphType = { + ...baseGraph, + nodes: baseGraph.nodes.map((n) => (n.id === 'a' ? { ...n, name: 'A-Renamed' } : n)), + }; + + const secondPass = Array.from(walker.walk(renamedGraph)); + + expect(firstPass.find((x) => x.node.id === 'a')?.node.name).toBe('A'); + expect(secondPass.find((x) => x.node.id === 'a')?.node.name).toBe('A-Renamed'); + }); +}); From 9ee8a440b590f11f1a971e7790ac4f0ba24386f1 Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 12:23:22 -0500 Subject: [PATCH 08/12] Implement code editor tests --- .../extensions/__tests__/completion.test.ts | 30 ++++++ .../extensions/__tests__/diagnostic.test.ts | 29 +++++ .../extensions/__tests__/linter.test.ts | 102 ++++++++++++++++++ .../extensions/__tests__/types.test.ts | 64 +++++++++++ .../extensions/__tests__/zen.test.ts | 81 ++++++++++++++ .../code-editor/extensions/completion.ts | 3 +- .../src/helpers/__tests__/wasm.test.ts | 25 +++++ packages/jdm-editor/src/setupTests.js | 7 +- 8 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 packages/jdm-editor/src/components/code-editor/extensions/__tests__/completion.test.ts create mode 100644 packages/jdm-editor/src/components/code-editor/extensions/__tests__/diagnostic.test.ts create mode 100644 packages/jdm-editor/src/components/code-editor/extensions/__tests__/linter.test.ts create mode 100644 packages/jdm-editor/src/components/code-editor/extensions/__tests__/types.test.ts create mode 100644 packages/jdm-editor/src/components/code-editor/extensions/__tests__/zen.test.ts create mode 100644 packages/jdm-editor/src/helpers/__tests__/wasm.test.ts diff --git a/packages/jdm-editor/src/components/code-editor/extensions/__tests__/completion.test.ts b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/completion.test.ts new file mode 100644 index 00000000..8e774198 --- /dev/null +++ b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/completion.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; + +import { getCompletions } from '../completion'; + +describe('getCompletions integration', () => { + it('returns the same array reference across repeated calls (cache contract)', () => { + const first = getCompletions(); + const second = getCompletions(); + + expect(Array.isArray(first)).toBe(true); + expect(second).toBe(first); + }); + + it('returns completion entries with expected shape when available', () => { + const completions = getCompletions(); + + if (completions.length === 0) { + expect(completions).toEqual([]); + return; + } + + const first = completions[0]; + expect(first).toMatchObject({ + type: expect.any(String), + label: expect.any(String), + detail: expect.any(String), + info: expect.any(String), + }); + }); +}); diff --git a/packages/jdm-editor/src/components/code-editor/extensions/__tests__/diagnostic.test.ts b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/diagnostic.test.ts new file mode 100644 index 00000000..d1d62479 --- /dev/null +++ b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/diagnostic.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; + +import { renderDiagnosticMessage } from '../diagnostic'; + +describe('renderDiagnosticMessage', () => { + it('renders inline code spans with string color', () => { + const result = renderDiagnosticMessage({ text: 'Type is `"usd"`', className: 'tok' }); + expect(result).toContain('class="tok"'); + expect(result).toContain('color: #6aab73'); + expect(result).toContain('"usd"'); + }); + + it('renders number code spans with number color', () => { + const result = renderDiagnosticMessage({ text: 'Value is `123`' }); + expect(result).toContain('color: #57a8f5'); + expect(result).toContain('123'); + }); + + it('escapes html-sensitive characters in code spans', () => { + const result = renderDiagnosticMessage({ text: 'Bad token ``' }); + expect(result).toContain('<img src=x onerror=1>'); + expect(result).not.toContain(''); + }); + + it('returns unchanged text when no backtick spans exist', () => { + const text = 'Plain text only'; + expect(renderDiagnosticMessage({ text })).toBe(text); + }); +}); diff --git a/packages/jdm-editor/src/components/code-editor/extensions/__tests__/linter.test.ts b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/linter.test.ts new file mode 100644 index 00000000..333723bb --- /dev/null +++ b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/linter.test.ts @@ -0,0 +1,102 @@ +import { VariableType } from '@gorules/zen-engine-wasm'; +import { EditorState } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; +import { describe, expect, it } from 'vitest'; + +import { isWasmAvailable } from '../../../../helpers/wasm'; +import { validateZenExpression } from '../linter'; + +describe('validateZenExpression integration', () => { + it('returns no diagnostics for empty source', () => { + expect( + validateZenExpression({ + source: ' ', + expressionType: 'standard', + types: [], + }), + ).toEqual([]); + }); + + it('maps type diagnostics severities from hint/info/warning prefixes', () => { + const diagnostics = validateZenExpression({ + source: 'amount', + types: [ + { error: 'Hint: consider cast', kind: 'Any', nodeKind: 'Expr', span: [0, 6] }, + { error: 'Info: this narrows type', kind: 'Any', nodeKind: 'Expr', span: [0, 6] }, + { error: 'Type mismatch', kind: 'Any', nodeKind: 'Expr', span: [0, 6] }, + ], + }); + + const severities = diagnostics.map((d) => d.severity); + expect(severities).toContain('hint'); + expect(severities).toContain('info'); + expect(severities).toContain('warning'); + }); + + it('enforces unary bool result with strictness-aware severity', () => { + const warningDiagnostics = validateZenExpression({ + source: 'amount', + expressionType: 'unary', + strict: false, + types: [{ error: null, kind: 'Number', nodeKind: 'Expr', span: [0, 6] }], + }); + + const errorDiagnostics = validateZenExpression({ + source: 'amount', + expressionType: 'unary', + strict: true, + types: [{ error: null, kind: 'Number', nodeKind: 'Expr', span: [0, 6] }], + }); + + const warningUnary = warningDiagnostics.find((d) => d.message.includes('Expected unary expression')); + const errorUnary = errorDiagnostics.find((d) => d.message.includes('Expected unary expression')); + + expect(warningUnary?.severity).toBe('warning'); + expect(errorUnary?.severity).toBe('error'); + }); + + it('reports expected type mismatch and attaches renderMessage', () => { + const expected = VariableType.fromJson('Bool'); + const diagnostics = validateZenExpression({ + source: 'amount', + strict: true, + types: [{ error: null, kind: 'Any', nodeKind: 'Expr', span: [0, 6] }], + expectedVariableType: expected, + }); + + const mismatch = diagnostics.find((d) => d.message.includes('Expected expression to evaluate to type')); + expect(mismatch?.severity).toBe('error'); + + const host = document.createElement('div'); + document.body.appendChild(host); + const view = new EditorView({ + state: EditorState.create({ doc: '' }), + parent: host, + }); + + const rendered = mismatch?.renderMessage?.(view); + expect(rendered instanceof HTMLElement).toBe(true); + expect(rendered?.textContent).toContain('Expected expression to evaluate to type'); + + view.destroy(); + host.remove(); + }); + + it('returns parser diagnostics for invalid syntax when wasm is available', () => { + const diagnostics = validateZenExpression({ + source: 'a +', + expressionType: 'standard', + types: [], + }); + + if (!isWasmAvailable()) { + expect(diagnostics).toEqual([]); + return; + } + + expect(diagnostics.length).toBeGreaterThan(0); + expect( + diagnostics.some((d) => ['Parser error', 'Lexer error', 'Compiler error', 'VM error'].includes(d.source ?? '')), + ).toBe(true); + }); +}); diff --git a/packages/jdm-editor/src/components/code-editor/extensions/__tests__/types.test.ts b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/types.test.ts new file mode 100644 index 00000000..0c17b5e0 --- /dev/null +++ b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/types.test.ts @@ -0,0 +1,64 @@ +import { EditorState } from '@codemirror/state'; +import { createVariableType } from '@gorules/zen-engine-wasm'; +import { describe, expect, it } from 'vitest'; + +import { + buildTypeCompletion, + typeField, + updateExpectedVariableTypeEffect, + updateExpressionTypeEffect, + updateStrictModeEffect, + updateVariableTypeEffect, + zenKindToString, +} from '../types'; + +const createTestVariableType = () => + createVariableType({ + Object: { + customer: { Object: { id: 'String' } }, + amount: 'Number', + }, + }); + +describe('types helpers', () => { + it('buildTypeCompletion builds object property completions', () => { + const completions = buildTypeCompletion({ kind: { Object: { amount: 'Number', approved: 'Bool' } }, type: 'variable' }); + expect(completions).toHaveLength(2); + expect(completions[0]).toMatchObject({ label: 'amount', type: 'variable', detail: 'number' }); + }); + + it('zenKindToString converts primitive and structured kinds', () => { + expect(zenKindToString('String')).toBe('string'); + expect(zenKindToString({ Array: 'Bool' })).toBe('bool[]'); + expect(zenKindToString({ Const: 'usd' })).toBe('"usd"'); + expect(zenKindToString({ Enum: ['usd', 'eur'] })).toBe('"usd" | "eur"'); + }); + + it('typeField computes type info on variable type update and doc change', () => { + const variableType = createTestVariableType(); + let state = EditorState.create({ doc: 'amount', extensions: [typeField] }); + + state = state.update({ + changes: { from: 0, to: state.doc.length, insert: 'amount' }, + effects: [updateVariableTypeEffect.of(variableType)], + }).state; + + const field = state.field(typeField); + expect(field.rootKind).toEqual({ Object: { customer: { Object: { id: 'String' } }, amount: 'Number' } }); + expect(field.types[0]?.kind).toBe('Number'); + }); + + it('typeField supports unary mode, strict mode, and expected type updates', () => { + const variableType = createTestVariableType(); + let state = EditorState.create({ doc: 'amount > 10', extensions: [typeField] }); + + state = state.update({ effects: [updateVariableTypeEffect.of(variableType)] }).state; + state = state.update({ effects: [updateExpressionTypeEffect.of('unary'), updateStrictModeEffect.of(true)] }).state; + state = state.update({ effects: [updateExpectedVariableTypeEffect.of(variableType)] }).state; + + const field = state.field(typeField); + expect(field.expressionType).toBe('unary'); + expect(field.strict).toBe(true); + expect(field.expectedVariableType).toBe(variableType); + }); +}); diff --git a/packages/jdm-editor/src/components/code-editor/extensions/__tests__/zen.test.ts b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/zen.test.ts new file mode 100644 index 00000000..412bd9ea --- /dev/null +++ b/packages/jdm-editor/src/components/code-editor/extensions/__tests__/zen.test.ts @@ -0,0 +1,81 @@ +import { parser as zenParser } from '@gorules/lezer-zen'; +import { parser as zenTemplateParser } from '@gorules/lezer-zen-template'; +import { EditorState } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; +import { describe, expect, it, vi } from 'vitest'; + +import { applyCompletion, zenExtensions } from '../zen'; + +describe('zen parser seams', () => { + it('parses valid standard expressions', () => { + const tree = zenParser.parse('amount > 100 && customer.id != ""'); + expect(tree.length).toBeGreaterThan(0); + expect(tree.topNode.name).toBe('Standard'); + }); + + it('parses templates with embedded expressions', () => { + const tree = zenTemplateParser.parse('Hello {{ customer.name }}'); + expect(tree.length).toBeGreaterThan(0); + }); + + it('handles malformed expressions without throwing', () => { + const tree = zenParser.parse('amount > '); + expect(tree.length).toBeGreaterThan(0); + expect(tree.toString()).toContain('âš '); + }); +}); + +describe('zen editor extensions', () => { + it('returns extension set with linter by default', () => { + const extensions = zenExtensions({ type: 'standard' }); + expect(extensions.length).toBeGreaterThan(5); + }); + + it('can disable linter extension', () => { + const withLint = zenExtensions({ type: 'standard', lint: true }); + const withoutLint = zenExtensions({ type: 'standard', lint: false }); + expect(withoutLint.length).toBe(withLint.length - 1); + }); +}); + +describe('applyCompletion', () => { + it('adds parens for function/method completions and places cursor inside', () => { + const host = document.createElement('div'); + document.body.appendChild(host); + const view = new EditorView({ + state: EditorState.create({ doc: '' }), + parent: host, + }); + + const dispatchSpy = vi.spyOn(view, 'dispatch'); + applyCompletion(view, { label: 'len', type: 'function', boost: 1 }, 0, 0); + + expect(view.state.doc.toString()).toBe('len()'); + expect(view.state.selection.main.anchor).toBe(4); + expect(dispatchSpy).toHaveBeenCalledTimes(1); + + dispatchSpy.mockRestore(); + view.destroy(); + host.remove(); + }); + + it('inserts plain labels for non-function completions', () => { + const host = document.createElement('div'); + document.body.appendChild(host); + const view = new EditorView({ + state: EditorState.create({ doc: '' }), + parent: host, + }); + + const dispatchSpy = vi.spyOn(view, 'dispatch'); + applyCompletion(view, { label: 'customer', type: 'variable', boost: 1 }, 0, 0); + + expect(view.state.doc.toString()).toBe('customer'); + expect(view.state.selection.main.anchor).toBe(8); + expect(dispatchSpy).toHaveBeenCalledTimes(1); + + dispatchSpy.mockRestore(); + view.destroy(); + host.remove(); + }); +}); diff --git a/packages/jdm-editor/src/components/code-editor/extensions/completion.ts b/packages/jdm-editor/src/components/code-editor/extensions/completion.ts index 45dc3130..3266637f 100644 --- a/packages/jdm-editor/src/components/code-editor/extensions/completion.ts +++ b/packages/jdm-editor/src/components/code-editor/extensions/completion.ts @@ -3,10 +3,11 @@ import { getCompletions as getCompletionsWasm } from '@gorules/zen-engine-wasm'; import { isWasmAvailable } from '../../../helpers/wasm'; type Completion = { - kind: string; + type?: string; label: string; detail: string; info: string; + methodFor?: unknown; boost?: number; }; diff --git a/packages/jdm-editor/src/helpers/__tests__/wasm.test.ts b/packages/jdm-editor/src/helpers/__tests__/wasm.test.ts new file mode 100644 index 00000000..174f9fe5 --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/wasm.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; + +import { isWasmAvailable } from '../wasm'; + +describe('isWasmAvailable integration contracts', () => { + it('never throws and always returns a readiness-state value', () => { + const sample = Array.from({ length: 3 }, () => isWasmAvailable()); + sample.forEach((value) => { + expect([true, false, undefined]).toContain(value); + }); + }); + + it('once readiness becomes true, all subsequent calls remain true (cache monotonicity)', () => { + const values = Array.from({ length: 5 }, () => isWasmAvailable()); + const firstTrueIndex = values.findIndex((v) => v === true); + + if (firstTrueIndex === -1) { + expect(values.some((v) => v === true)).toBe(false); + return; + } + + const tail = values.slice(firstTrueIndex); + tail.forEach((value) => expect(value).toBe(true)); + }); +}); diff --git a/packages/jdm-editor/src/setupTests.js b/packages/jdm-editor/src/setupTests.js index 9347c17d..bb93094c 100644 --- a/packages/jdm-editor/src/setupTests.js +++ b/packages/jdm-editor/src/setupTests.js @@ -1,6 +1,11 @@ import '@testing-library/jest-dom'; +import initZenEngineWasm from '@gorules/zen-engine-wasm'; import { cleanup } from '@testing-library/react'; -import { afterEach } from 'vitest'; +import { afterEach, beforeAll } from 'vitest'; + +beforeAll(async () => { + await initZenEngineWasm(); +}); afterEach(() => { cleanup(); From 7471e7cef32f3d5f923a996d230bb913ae87995b Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 12:55:01 -0500 Subject: [PATCH 09/12] Add tests for state stores and controllers --- .../__tests__/dg-empty.test.tsx | 113 +++++++++++++++++ .../__tests__/dg-infer.test.tsx | 115 ++++++++++++++++++ .../__tests__/dg-store.context.test.tsx | 72 +++++++++++ .../__tests__/dt-empty.test.tsx | 82 +++++++++++++ .../__tests__/dt-store.context.test.tsx | 63 ++++++++++ .../__tests__/expression-controller.test.tsx | 85 +++++++++++++ .../expression-store.context.test.tsx | 71 +++++++++++ 7 files changed, 601 insertions(+) create mode 100644 packages/jdm-editor/src/components/decision-graph/__tests__/dg-empty.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-graph/__tests__/dg-infer.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-graph/context/__tests__/dg-store.context.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-table/__tests__/dt-empty.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-table/context/__tests__/dt-store.context.test.tsx create mode 100644 packages/jdm-editor/src/components/expression/__tests__/expression-controller.test.tsx create mode 100644 packages/jdm-editor/src/components/expression/context/__tests__/expression-store.context.test.tsx diff --git a/packages/jdm-editor/src/components/decision-graph/__tests__/dg-empty.test.tsx b/packages/jdm-editor/src/components/decision-graph/__tests__/dg-empty.test.tsx new file mode 100644 index 00000000..c28f3cdf --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/__tests__/dg-empty.test.tsx @@ -0,0 +1,113 @@ +import { act, render } from '@testing-library/react'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraphProvider, useDecisionGraphRaw } from '../context/dg-store.context'; +import { DecisionGraphEmpty } from '../dg-empty'; +import type { DecisionGraphType } from '../dg-types'; + +const StoreProbe = ({ onStore }: { onStore: ReturnType) => void>> }) => { + const store = useDecisionGraphRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +const graph: DecisionGraphType = { + nodes: [ + { id: 'node-1', name: 'Input', type: 'inputNode', content: { schema: '{}' }, position: { x: 0, y: 0 } }, + { id: 'node-2', name: 'Output', type: 'outputNode', content: { schema: '{}' }, position: { x: 100, y: 0 } }, + ], + edges: [], +}; + +describe('DecisionGraphEmpty', () => { + it('sanitizes open tabs and active tab when viewConfig is enabled', () => { + const onStore = vi.fn<(store: ReturnType) => void>(); + const { rerender } = render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + act(() => { + store?.actions.openTab('node-1'); + store?.actions.openTab('node-2'); + }); + expect(store?.stateStore.getState().activeTab).toBe('node-2'); + + rerender( + + + + , + ); + + const state = store?.stateStore.getState(); + expect(state?.openTabs).toEqual(['node-1']); + expect(state?.activeTab).toBe('graph'); + }); + + it('updates debounced onChange listener on prop changes and emits latest graph update once per debounce window', () => { + vi.useFakeTimers(); + const onStore = vi.fn<(store: ReturnType) => void>(); + const onChangeA = vi.fn(); + const onChangeB = vi.fn(); + + const { rerender } = render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + act(() => { + store?.actions.setDecisionGraph({ + nodes: [ + ...graph.nodes, + { id: 'node-3', name: 'Expr', type: 'expressionNode', content: { expressions: [] }, position: { x: 200, y: 0 } }, + ], + }); + store?.actions.setDecisionGraph({ + nodes: [ + ...graph.nodes, + { id: 'node-4', name: 'Expr2', type: 'expressionNode', content: { expressions: [] }, position: { x: 220, y: 0 } }, + ], + }); + vi.advanceTimersByTime(110); + }); + + expect(onChangeA).toHaveBeenCalledTimes(1); + expect(onChangeA.mock.calls[0]?.[0].nodes).toHaveLength(3); + expect(onChangeA.mock.calls[0]?.[0].nodes.at(-1)?.id).toBe('node-4'); + + rerender( + + + + , + ); + + act(() => { + store?.actions.setDecisionGraph({ + nodes: [ + ...graph.nodes, + { id: 'node-5', name: 'Expr3', type: 'expressionNode', content: { expressions: [] }, position: { x: 240, y: 0 } }, + ], + }); + vi.advanceTimersByTime(110); + }); + + expect(onChangeA).toHaveBeenCalledTimes(1); + expect(onChangeB).toHaveBeenCalledTimes(1); + expect(onChangeB.mock.calls[0]?.[0].nodes.at(-1)?.id).toBe('node-5'); + vi.useRealTimers(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/__tests__/dg-infer.test.tsx b/packages/jdm-editor/src/components/decision-graph/__tests__/dg-infer.test.tsx new file mode 100644 index 00000000..c7e2b952 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/__tests__/dg-infer.test.tsx @@ -0,0 +1,115 @@ +import { act, render, waitFor } from '@testing-library/react'; +import { VariableType } from '@gorules/zen-engine-wasm'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraphInferTypes } from '../dg-infer'; +import { DecisionGraphProvider, NodeTypeKind, useDecisionGraphRaw } from '../context/dg-store.context'; +import type { Simulation } from '../simulator/simulation.types'; + +const StoreProbe = ({ onStore }: { onStore: ReturnType) => void>> }) => { + const store = useDecisionGraphRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +describe('DecisionGraphInferTypes', () => { + it('derives global $nodes type from node names and output types', async () => { + const onStore = vi.fn<(store: ReturnType) => void>(); + render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store).toBeDefined(); + + act(() => { + store?.stateStore.setState({ + decisionGraph: { + nodes: [{ id: 'n1', name: 'AutoApproved', type: 'expressionNode', position: { x: 0, y: 0 }, content: {} }], + edges: [], + }, + nodeTypes: { + n1: { + [NodeTypeKind.Output]: VariableType.fromJson('Bool'), + }, + }, + }); + }); + + await waitFor(() => { + const globalType = store?.stateStore.getState().globalType.$nodes; + expect(globalType?.get('AutoApproved').toJson()).toBe('Bool'); + }); + + const firstNodesType = store?.stateStore.getState().globalType.$nodes; + act(() => { + store?.stateStore.setState({ hoveredEdgeId: 'edge-1' }); + }); + + await waitFor(() => { + expect(store?.stateStore.getState().globalType.$nodes).toBe(firstNodesType); + }); + }); + + it('removes trace-driven Input/Output node types when simulation trace becomes null', async () => { + const onStore = vi.fn<(store: ReturnType) => void>(); + render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store).toBeDefined(); + + const simulateWithTrace: Simulation = { + result: { + performance: '1ms', + result: {}, + snapshot: { nodes: [], edges: [] }, + trace: {}, + }, + }; + + act(() => { + store?.stateStore.setState({ + decisionGraph: { + nodes: [{ id: 'n1', name: 'Node', type: 'expressionNode', position: { x: 0, y: 0 }, content: {} }], + edges: [], + }, + nodeTypes: { + n1: { + [NodeTypeKind.Input]: VariableType.fromJson('String'), + [NodeTypeKind.Output]: VariableType.fromJson('Number'), + [NodeTypeKind.InferredOutput]: VariableType.fromJson('Bool'), + }, + }, + simulate: simulateWithTrace, + }); + }); + + await waitFor(() => { + const nodeType = store?.stateStore.getState().nodeTypes.n1; + expect(nodeType?.[NodeTypeKind.Input]?.toJson()).toBe('String'); + expect(nodeType?.[NodeTypeKind.Output]?.toJson()).toBe('Number'); + }); + + act(() => { + store?.stateStore.setState({ simulate: {} }); + }); + + await waitFor(() => { + const nodeType = store?.stateStore.getState().nodeTypes.n1; + expect(nodeType?.[NodeTypeKind.Input]).toBeUndefined(); + expect(nodeType?.[NodeTypeKind.Output]).toBeUndefined(); + expect(nodeType?.[NodeTypeKind.InferredOutput]?.toJson()).toBe('Bool'); + }); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/context/__tests__/dg-store.context.test.tsx b/packages/jdm-editor/src/components/decision-graph/context/__tests__/dg-store.context.test.tsx new file mode 100644 index 00000000..8ea1911e --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/context/__tests__/dg-store.context.test.tsx @@ -0,0 +1,72 @@ +import { act, renderHook } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraphProvider, useDecisionGraphRaw } from '../dg-store.context'; + +const wrapper = ({ children }: { children: React.ReactNode }) => {children}; + +describe('decision graph store', () => { + it('openTab and closeTab apply tab lifecycle actions consistently', () => { + const { result } = renderHook(() => useDecisionGraphRaw(), { wrapper }); + + act(() => { + result.current.actions.openTab('node-1'); + result.current.actions.openTab('node-2'); + result.current.actions.openTab('node-3'); + result.current.actions.closeTab('node-2', 'close-right'); + }); + + expect(result.current.stateStore.getState().openTabs).toEqual(['node-1', 'node-2']); + expect(result.current.stateStore.getState().activeTab).toBe('node-1'); + + act(() => { + result.current.actions.closeTab('node-2', 'close-left'); + }); + + expect(result.current.stateStore.getState().openTabs).toEqual(['node-2']); + expect(result.current.stateStore.getState().activeTab).toBe('node-2'); + + act(() => { + result.current.actions.closeTab('node-2'); + }); + + expect(result.current.stateStore.getState().openTabs).toEqual([]); + expect(result.current.stateStore.getState().activeTab).toBe('graph'); + }); + + it('setDecisionGraph respects skipOnChangeEvent option', () => { + const { result } = renderHook(() => useDecisionGraphRaw(), { wrapper }); + const onChange = vi.fn(); + + act(() => { + result.current.listenerStore.setState({ onChange }); + result.current.actions.setDecisionGraph( + { + nodes: [{ id: 'n1', name: 'Input', type: 'inputNode', position: { x: 0, y: 0 }, content: { schema: '{}' } }], + edges: [], + }, + { skipOnChangeEvent: true }, + ); + }); + + expect(onChange).not.toHaveBeenCalled(); + expect(result.current.stateStore.getState().decisionGraph.nodes).toHaveLength(1); + + act(() => { + result.current.actions.setDecisionGraph({ edges: [] }); + }); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('setCompactMode persists preference to localStorage', () => { + const { result } = renderHook(() => useDecisionGraphRaw(), { wrapper }); + + act(() => { + result.current.actions.setCompactMode(true); + }); + + expect(result.current.stateStore.getState().compactMode).toBe(true); + expect(localStorage.getItem('jdm-compact-mode')).toBe('true'); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-table/__tests__/dt-empty.test.tsx b/packages/jdm-editor/src/components/decision-table/__tests__/dt-empty.test.tsx new file mode 100644 index 00000000..b11b7a3b --- /dev/null +++ b/packages/jdm-editor/src/components/decision-table/__tests__/dt-empty.test.tsx @@ -0,0 +1,82 @@ +import { act, render } from '@testing-library/react'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionTableEmpty } from '../dt-empty'; +import { DecisionTableProvider, parseDecisionTable, useDecisionTableRaw } from '../context/dt-store.context'; + +const StoreProbe = ({ onStore }: { onStore: ReturnType) => void>> }) => { + const store = useDecisionTableRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +describe('DecisionTableEmpty', () => { + it('syncs top-level props into state store', () => { + const onStore = vi.fn<(store: ReturnType) => void>(); + render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + const state = store?.stateStore.getState(); + expect(state?.id).toBe('dt-1'); + expect(state?.name).toBe('table.json'); + expect(state?.disabled).toBe(true); + expect(state?.permission).toBe('edit:rules'); + expect(state?.colWidth).toBe(222); + expect(state?.minColWidth).toBe(111); + }); + + it('updates debounced onChange listener on prop changes and emits only latest payload per debounce window', () => { + vi.useFakeTimers(); + const onStore = vi.fn<(store: ReturnType) => void>(); + const onChangeA = vi.fn(); + const onChangeB = vi.fn(); + const value = parseDecisionTable({ + hitPolicy: 'first', + inputs: [{ id: 'in', name: 'Input' }], + outputs: [{ id: 'out', name: 'Output', field: 'output' }], + rules: [{ _id: 'r1', in: 'start', out: '' }], + }); + + const { rerender } = render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + act(() => { + store?.actions.commitData('changed-1', { x: 'in', y: 0 }); + store?.actions.commitData('changed-2', { x: 'in', y: 0 }); + vi.advanceTimersByTime(110); + }); + + expect(onChangeA).toHaveBeenCalledTimes(1); + expect(onChangeA.mock.calls[0]?.[0].rules[0].in).toBe('changed-2'); + + rerender( + + + + , + ); + + act(() => { + store?.actions.commitData('changed-3', { x: 'in', y: 0 }); + vi.advanceTimersByTime(110); + }); + + expect(onChangeA).toHaveBeenCalledTimes(1); + expect(onChangeB).toHaveBeenCalledTimes(1); + expect(onChangeB.mock.calls[0]?.[0].rules[0].in).toBe('changed-3'); + vi.useRealTimers(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-table/context/__tests__/dt-store.context.test.tsx b/packages/jdm-editor/src/components/decision-table/context/__tests__/dt-store.context.test.tsx new file mode 100644 index 00000000..ef96e1b0 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-table/context/__tests__/dt-store.context.test.tsx @@ -0,0 +1,63 @@ +import { act, renderHook } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { + DecisionTableProvider, + parseDecisionTable, + useDecisionTableRaw, +} from '../dt-store.context'; + +const wrapper = ({ children }: { children: React.ReactNode }) => {children}; + +describe('decision table store', () => { + it('parseDecisionTable creates required schema defaults', () => { + const table = parseDecisionTable({ hitPolicy: 'first', inputs: [], outputs: [], rules: [] }); + expect(table.inputs).toHaveLength(1); + expect(table.outputs).toHaveLength(1); + expect(table.executionMode).toBe('single'); + expect(table.passThorough).toBe(false); + }); + + it('commitData updates target cell and emits onChange', () => { + const { result } = renderHook(() => useDecisionTableRaw(), { wrapper }); + const onChange = vi.fn(); + + const seed = parseDecisionTable({ + hitPolicy: 'first', + inputs: [{ id: 'in', name: 'Input' }], + outputs: [{ id: 'out', name: 'Output', field: 'output' }], + rules: [{ _id: 'r1', in: 'old', out: '' }], + }); + + act(() => { + result.current.listenerStore.setState({ onChange }); + result.current.actions.setDecisionTable(seed); + result.current.actions.commitData('updated', { x: 'in', y: 0 }); + }); + + const table = result.current.stateStore.getState().decisionTable; + expect(table.rules[0].in).toBe('updated'); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('addRowAbove shifts cursor when inserting at current row', () => { + const { result } = renderHook(() => useDecisionTableRaw(), { wrapper }); + const seed = parseDecisionTable({ + hitPolicy: 'first', + inputs: [{ id: 'in', name: 'Input' }], + outputs: [{ id: 'out', name: 'Output', field: 'output' }], + rules: [{ _id: 'r1', in: '', out: '' }], + }); + + act(() => { + result.current.actions.setDecisionTable(seed); + result.current.actions.setCursor({ x: 'in', y: 0 }); + result.current.actions.addRowAbove(0); + }); + + const state = result.current.stateStore.getState(); + expect(state.decisionTable.rules).toHaveLength(2); + expect(state.cursor).toEqual({ x: 'in', y: 1 }); + }); +}); diff --git a/packages/jdm-editor/src/components/expression/__tests__/expression-controller.test.tsx b/packages/jdm-editor/src/components/expression/__tests__/expression-controller.test.tsx new file mode 100644 index 00000000..548bdac4 --- /dev/null +++ b/packages/jdm-editor/src/components/expression/__tests__/expression-controller.test.tsx @@ -0,0 +1,85 @@ +import { act, render } from '@testing-library/react'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { ExpressionController } from '../expression-controller'; +import type { ExpressionEntry } from '../context/expression-store.context'; +import { ExpressionStoreProvider, useExpressionStoreRaw } from '../context/expression-store.context'; + +type StoreProbeProps = { + onStore: ReturnType) => void>>; +}; + +const StoreProbe = ({ onStore }: StoreProbeProps) => { + const store = useExpressionStoreRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +const initialRows: ExpressionEntry[] = [{ id: 'one', key: 'k1', value: 'v1' }]; + +describe('ExpressionController', () => { + it('hydrates initial state from defaultValue when uncontrolled', () => { + const onStore = vi.fn<(store: ReturnType) => void>(); + render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store).toBeDefined(); + expect(store?.getState().expressions).toEqual(initialRows); + }); + + it('emits onChange only when expressions change and stays silent for unrelated state updates', () => { + const onStore = vi.fn<(store: ReturnType) => void>(); + const onChange = vi.fn<(rows: ExpressionEntry[]) => void>(); + const { rerender } = render( + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store).toBeDefined(); + onChange.mockClear(); + + rerender( + + + + , + ); + + expect(onChange).not.toHaveBeenCalled(); + + const nextRows: ExpressionEntry[] = [{ id: 'two', key: 'k2', value: 'v2' }]; + rerender( + + + + , + ); + + expect(store?.getState().expressions).toEqual(nextRows); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenLastCalledWith(nextRows); + + act(() => { + store?.setState({ disabled: false, permission: 'view' }); + }); + expect(onChange).toHaveBeenCalledTimes(1); + + act(() => { + store?.getState().setExpressions([{ id: 'three', key: 'k3', value: 'v3' }]); + }); + + expect(onChange).toHaveBeenCalledTimes(2); + expect(onChange).toHaveBeenLastCalledWith([{ id: 'three', key: 'k3', value: 'v3' }]); + }); +}); diff --git a/packages/jdm-editor/src/components/expression/context/__tests__/expression-store.context.test.tsx b/packages/jdm-editor/src/components/expression/context/__tests__/expression-store.context.test.tsx new file mode 100644 index 00000000..5b580133 --- /dev/null +++ b/packages/jdm-editor/src/components/expression/context/__tests__/expression-store.context.test.tsx @@ -0,0 +1,71 @@ +import { act, renderHook } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it } from 'vitest'; + +import { + ExpressionStoreProvider, + createExpression, + useExpressionStoreRaw, +} from '../expression-store.context'; + +const wrapper = ({ children }: { children: React.ReactNode }) => {children}; + +describe('expression store', () => { + it('createExpression uses defaults and applies partial overrides', () => { + const expression = createExpression({ key: 'k', value: 'v' }); + expect(expression.id).toEqual(expect.any(String)); + expect(expression.key).toBe('k'); + expect(expression.value).toBe('v'); + }); + + it('addRowAbove inserts a new expression at the requested index', () => { + const { result } = renderHook(() => useExpressionStoreRaw(), { wrapper }); + + act(() => { + result.current.getState().setExpressions([ + createExpression({ id: 'a', key: 'first', value: '1' }), + createExpression({ id: 'b', key: 'second', value: '2' }), + ]); + result.current.getState().addRowAbove(1); + }); + + const expressions = result.current.getState().expressions; + expect(expressions).toHaveLength(3); + expect(expressions[1].id).not.toBe('a'); + expect(expressions[1].id).not.toBe('b'); + }); + + it('updateRow updates only the targeted row fields', () => { + const { result } = renderHook(() => useExpressionStoreRaw(), { wrapper }); + + act(() => { + result.current.getState().setExpressions([ + createExpression({ id: 'a', key: 'first', value: '1' }), + createExpression({ id: 'b', key: 'second', value: '2' }), + ]); + result.current.getState().updateRow(1, { key: 'updated' }); + }); + + const expressions = result.current.getState().expressions; + expect(expressions).toHaveLength(2); + expect(expressions[0]).toMatchObject({ id: 'a', key: 'first' }); + expect(expressions[1]).toMatchObject({ id: 'b', key: 'updated' }); + }); + + it('swapRows reorders rows and removeRow removes by index', () => { + const { result } = renderHook(() => useExpressionStoreRaw(), { wrapper }); + + act(() => { + result.current.getState().setExpressions([ + createExpression({ id: 'a', key: 'first', value: '1' }), + createExpression({ id: 'b', key: 'second', value: '2' }), + createExpression({ id: 'c', key: 'third', value: '3' }), + ]); + result.current.getState().swapRows(0, 2); + result.current.getState().removeRow(1); + }); + + const expressions = result.current.getState().expressions; + expect(expressions.map((row) => row.id)).toEqual(['b', 'a']); + }); +}); From ab9b1bf42c7d27b14944b14918483be5e305590c Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 13:14:01 -0500 Subject: [PATCH 10/12] Implement graph, decision table and expression interaction tests --- .../graph/__tests__/graph-nodes.test.tsx | 72 +++++++++++ .../graph/__tests__/graph-tabs.test.tsx | 119 ++++++++++++++++++ .../__tests__/dt-command-bar.test.tsx | 80 ++++++++++++ .../expression/__tests__/expression.test.tsx | 38 ++++++ 4 files changed, 309 insertions(+) create mode 100644 packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-nodes.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-tabs.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-table/__tests__/dt-command-bar.test.tsx create mode 100644 packages/jdm-editor/src/components/expression/__tests__/expression.test.tsx diff --git a/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-nodes.test.tsx b/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-nodes.test.tsx new file mode 100644 index 00000000..c4cfcba4 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-nodes.test.tsx @@ -0,0 +1,72 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraphProvider, useDecisionGraphRaw } from '../../context/dg-store.context'; +import { DecisionGraphEmpty } from '../../dg-empty'; +import type { DecisionGraphType } from '../../dg-types'; +import { GraphNodes } from '../graph-nodes'; + +const graph: DecisionGraphType = { + nodes: [ + { + id: 'dt-1', + type: 'decisionTableNode', + name: 'Allowed Table', + content: { hitPolicy: 'first', inputs: [], outputs: [], rules: [] }, + position: { x: 0, y: 0 }, + }, + { + id: 'expr-1', + type: 'expressionNode', + name: 'Hidden Expression', + content: { expressions: [], passThrough: true, inputField: null, outputPath: null, executionMode: 'single' }, + position: { x: 200, y: 0 }, + }, + ], + edges: [], +}; + +const StoreProbe = ({ onStore }: { onStore: ReturnType) => void>> }) => { + const store = useDecisionGraphRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +describe('GraphNodes', () => { + it('shows only permitted nodes in view mode and opens tab from node card click', async () => { + const user = userEvent.setup(); + const onStore = vi.fn<(store: ReturnType) => void>(); + + render( + + + + + , + ); + + expect(await screen.findByText('Decision View')).toBeInTheDocument(); + expect(screen.getByText('1 configurable items')).toBeInTheDocument(); + expect(screen.getByText('Allowed Table')).toBeInTheDocument(); + expect(screen.queryByText('Hidden Expression')).not.toBeInTheDocument(); + + await user.click(screen.getByText('Allowed Table')); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store?.stateStore.getState().activeTab).toBe('dt-1'); + expect(store?.stateStore.getState().openTabs).toEqual(['dt-1']); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-tabs.test.tsx b/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-tabs.test.tsx new file mode 100644 index 00000000..43bdb7fe --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-tabs.test.tsx @@ -0,0 +1,119 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraphProvider, useDecisionGraphRaw } from '../../context/dg-store.context'; +import { DecisionGraphEmpty } from '../../dg-empty'; +import type { DecisionGraphType } from '../../dg-types'; +import { GraphTabs } from '../graph-tabs'; + +const graph: DecisionGraphType = { + nodes: [ + { id: 'input', type: 'inputNode', name: 'Input', content: { schema: '{}' }, position: { x: 0, y: 0 } }, + { + id: 'expr-1', + type: 'expressionNode', + name: 'Expr 1', + content: { expressions: [], passThrough: true, inputField: null, outputPath: null, executionMode: 'single' }, + position: { x: 200, y: 0 }, + }, + { + id: 'expr-2', + type: 'expressionNode', + name: 'Expr 2', + content: { expressions: [], passThrough: true, inputField: null, outputPath: null, executionMode: 'single' }, + position: { x: 400, y: 0 }, + }, + { + id: 'expr-3', + type: 'expressionNode', + name: 'Expr 3', + content: { expressions: [], passThrough: true, inputField: null, outputPath: null, executionMode: 'single' }, + position: { x: 600, y: 0 }, + }, + ], + edges: [], +}; + +const StoreProbe = ({ onStore }: { onStore: ReturnType) => void>> }) => { + const store = useDecisionGraphRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +describe('GraphTabs', () => { + it('opens tabs on click and closes the selected tab through tab close action', async () => { + const user = userEvent.setup(); + const onStore = vi.fn<(store: ReturnType) => void>(); + + render( + + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store).toBeDefined(); + + act(() => { + store?.actions.openTab('expr-1'); + store?.actions.openTab('expr-2'); + }); + + await user.click(await screen.findByText('Expr 1')); + expect(store?.stateStore.getState().activeTab).toBe('expr-1'); + + const tabLabel = screen.getByText('Expr 1').closest('.grl-graph-tabs__tab'); + expect(tabLabel).toBeTruthy(); + + const closeIcon = tabLabel?.querySelector('button.grl-graph-tabs__closeIcon'); + expect(closeIcon).toBeTruthy(); + if (!(closeIcon instanceof HTMLButtonElement)) { + throw new Error('Expected close icon button for Expr 1 tab'); + } + + await user.click(closeIcon); + + expect(store?.stateStore.getState().openTabs).toEqual(['expr-2']); + expect(store?.stateStore.getState().activeTab).toBe('graph'); + expect(screen.queryByText('Expr 1')).not.toBeInTheDocument(); + expect(screen.getByText('Expr 2')).toBeInTheDocument(); + }); + + it('applies context-menu close-right action to open tabs', async () => { + const user = userEvent.setup(); + const onStore = vi.fn<(store: ReturnType) => void>(); + + render( + + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + + act(() => { + store?.actions.openTab('expr-1'); + store?.actions.openTab('expr-2'); + store?.actions.openTab('expr-3'); + }); + + const expr2Label = await screen.findByText('Expr 2'); + fireEvent.contextMenu(expr2Label); + + const closeRight = await screen.findByText('Close Tabs to the right'); + await user.click(closeRight); + + expect(store?.stateStore.getState().openTabs).toEqual(['expr-1', 'expr-2']); + expect(store?.stateStore.getState().activeTab).toBe('expr-2'); + expect(screen.queryByText('Expr 3')).not.toBeInTheDocument(); + expect(screen.getByText('Expr 2')).toBeInTheDocument(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-table/__tests__/dt-command-bar.test.tsx b/packages/jdm-editor/src/components/decision-table/__tests__/dt-command-bar.test.tsx new file mode 100644 index 00000000..100e3f69 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-table/__tests__/dt-command-bar.test.tsx @@ -0,0 +1,80 @@ +import { act, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionTableCommandBar } from '../dt-command-bar'; +import { DecisionTableProvider, parseDecisionTable, useDecisionTableRaw } from '../context/dt-store.context'; +import { DecisionTableEmpty } from '../dt-empty'; + +const StoreProbe = ({ onStore }: { onStore: ReturnType) => void>> }) => { + const store = useDecisionTableRaw(); + useEffect(() => { + onStore(store); + }, [onStore, store]); + return null; +}; + +describe('DecisionTableCommandBar', () => { + it('adds rows above and below cursor through command bar buttons', async () => { + const user = userEvent.setup(); + const onStore = vi.fn<(store: ReturnType) => void>(); + const value = parseDecisionTable({ + hitPolicy: 'first', + inputs: [{ id: 'in', name: 'Input' }], + outputs: [{ id: 'out', name: 'Output', field: 'output' }], + rules: [{ _id: 'r1', in: 'start', out: 'ok' }], + }); + + const { getByRole } = render( + + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + expect(store).toBeDefined(); + + act(() => { + store?.actions.setCursor({ x: 'in', y: 0 }); + }); + + await user.click(getByRole('button', { name: 'arrow-down' })); + + expect(store?.stateStore.getState().decisionTable.rules).toHaveLength(2); + + await user.click(getByRole('button', { name: 'arrow-up' })); + + expect(store?.stateStore.getState().decisionTable.rules).toHaveLength(3); + expect(store?.stateStore.getState().cursor?.y).toBe(1); + }); + + it('clears cursor when Deselect is clicked', async () => { + const user = userEvent.setup(); + const onStore = vi.fn<(store: ReturnType) => void>(); + const value = parseDecisionTable({ + hitPolicy: 'first', + inputs: [{ id: 'in', name: 'Input' }], + outputs: [{ id: 'out', name: 'Output', field: 'output' }], + rules: [{ _id: 'r1', in: 'start', out: 'ok' }], + }); + + const { getByRole } = render( + + + + + , + ); + + const store = onStore.mock.calls.at(-1)?.[0]; + act(() => { + store?.actions.setCursor({ x: 'in', y: 0 }); + }); + + await user.click(getByRole('button', { name: /deselect/i })); + expect(store?.stateStore.getState().cursor).toBeNull(); + }); +}); diff --git a/packages/jdm-editor/src/components/expression/__tests__/expression.test.tsx b/packages/jdm-editor/src/components/expression/__tests__/expression.test.tsx new file mode 100644 index 00000000..133748a5 --- /dev/null +++ b/packages/jdm-editor/src/components/expression/__tests__/expression.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { Expression } from '../expression'; +import type { ExpressionEntry } from '../context/expression-store.context'; + +const defaultValue: ExpressionEntry[] = [{ id: 'row-1', key: 'approved', value: 'true' }]; + +describe('Expression', () => { + it('shows Add row for full-edit permission and emits updated rows on command action', async () => { + const user = userEvent.setup(); + const onChange = vi.fn<(rows: ExpressionEntry[]) => void>(); + + render(); + + await user.click(await screen.findByRole('button', { name: /add row/i })); + + expect(onChange).toHaveBeenCalled(); + const nextRows = onChange.mock.calls.at(-1)?.[0]; + expect(nextRows).toBeDefined(); + if (!nextRows) { + throw new Error('Expected onChange payload after adding expression row'); + } + expect(nextRows).toHaveLength(2); + expect(nextRows?.[0]?.key).toBe('approved'); + expect(nextRows[1]).toMatchObject({ key: '', value: '' }); + expect(nextRows[1]?.id).toEqual(expect.any(String)); + }); + + it('hides Add row when permission is view-only', async () => { + render(); + + expect(await screen.findByText('Key')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /add row/i })).not.toBeInTheDocument(); + }); +}); From 47c1de74c1540264fcb4cf8375daca13a835755a Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 14:32:29 -0500 Subject: [PATCH 11/12] Add tests for browser side effects and IO contracts --- .../__tests__/graph-side-toolbar.test.tsx | 105 +++++++++++++ .../__tests__/use-graph-clipboard.test.tsx | 138 ++++++++++++++++++ .../nodes/__tests__/graph-node.test.tsx | 78 ++++++++++ .../custom-node/__tests__/index.test.tsx | 39 +++++ .../decision-table/__tests__/excel.test.ts | 85 +++++++++++ .../__tests__/table.persistence.test.tsx | 44 ++++++ .../src/helpers/__tests__/utility.test.ts | 52 +++++++ packages/jdm-editor/src/helpers/utility.ts | 2 +- 8 files changed, 542 insertions(+), 1 deletion(-) create mode 100644 packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-side-toolbar.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-graph/hooks/__tests__/use-graph-clipboard.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-graph/nodes/__tests__/graph-node.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-graph/nodes/custom-node/__tests__/index.test.tsx create mode 100644 packages/jdm-editor/src/components/decision-table/__tests__/excel.test.ts create mode 100644 packages/jdm-editor/src/components/decision-table/table/__tests__/table.persistence.test.tsx create mode 100644 packages/jdm-editor/src/helpers/__tests__/utility.test.ts diff --git a/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-side-toolbar.test.tsx b/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-side-toolbar.test.tsx new file mode 100644 index 00000000..55c08b9c --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/graph/__tests__/graph-side-toolbar.test.tsx @@ -0,0 +1,105 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionGraphProvider } from '../../context/dg-store.context'; +import { DecisionGraphEmpty } from '../../dg-empty'; +import { GraphSideToolbar } from '../graph-side-toolbar'; + +vi.mock('../../../decision-table/excel', () => ({ + exportDecisionTable: vi.fn(), + readDecisionTableFile: vi.fn(), +})); + +import { exportDecisionTable } from '../../../decision-table/excel'; + +const exportDecisionTableMock = vi.mocked(exportDecisionTable); + +const graph = { + nodes: [ + { + id: 'dt-allowed', + type: 'decisionTableNode', + name: 'Allowed Table', + content: { hitPolicy: 'first', inputs: [], outputs: [], rules: [] }, + position: { x: 0, y: 0 }, + }, + { + id: 'dt-hidden', + type: 'decisionTableNode', + name: 'Hidden Table', + content: { hitPolicy: 'first', inputs: [], outputs: [], rules: [] }, + position: { x: 250, y: 0 }, + }, + ], + edges: [], +}; + +describe('GraphSideToolbar', () => { + it('toggles active panel and emits onPanelsChange callback', async () => { + const user = userEvent.setup(); + const onPanelsChange = vi.fn<(panel?: string) => void>(); + + render( + + Inspector, + renderPanel: () =>
Panel
, + }, + ]} + onPanelsChange={onPanelsChange} + /> + +
, + ); + + const panelButton = screen.getByRole('button', { name: 'Inspector' }); + + await user.click(panelButton); + expect(onPanelsChange).toHaveBeenLastCalledWith('inspector'); + + await user.click(panelButton); + expect(onPanelsChange).toHaveBeenLastCalledWith(undefined); + }); + + it('exports only permitted decision tables when view mode is enabled', async () => { + const user = userEvent.setup(); + + render( + + + + , + ); + + await user.click(screen.getByRole('button', { name: 'cloud-download' })); + await user.click(await screen.findByText('Download Excel')); + + expect(exportDecisionTableMock).toHaveBeenCalledTimes(1); + expect(exportDecisionTableMock).toHaveBeenCalledWith( + 'decision-model', + expect.arrayContaining([ + expect.objectContaining({ id: 'dt-allowed', name: 'Allowed Table' }), + ]), + ); + + const payload = exportDecisionTableMock.mock.calls[0]?.[1] ?? []; + expect(payload).toHaveLength(1); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/hooks/__tests__/use-graph-clipboard.test.tsx b/packages/jdm-editor/src/components/decision-graph/hooks/__tests__/use-graph-clipboard.test.tsx new file mode 100644 index 00000000..80db35f2 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/hooks/__tests__/use-graph-clipboard.test.tsx @@ -0,0 +1,138 @@ +import { act, render } from '@testing-library/react'; +import { message } from 'antd'; +import React, { useEffect } from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import type { Node } from 'reactflow'; + +import { copyToClipboard, pasteFromClipboard } from '../../../../helpers/utility'; +import { DecisionGraphProvider } from '../../context/dg-store.context'; +import { DecisionGraphEmpty } from '../../dg-empty'; +import { useGraphClipboard } from '../use-graph-clipboard'; + +vi.mock('../../../../helpers/utility', () => ({ + copyToClipboard: vi.fn(), + pasteFromClipboard: vi.fn(), +})); + +const copyToClipboardMock = vi.mocked(copyToClipboard); +const pasteFromClipboardMock = vi.mocked(pasteFromClipboard); + +type ClipboardApi = ReturnType; + +type ProbeProps = { + onReady: (api: ClipboardApi) => void; + reactFlowRef: React.RefObject; + wrapperRef: React.RefObject; +}; + +const Probe: React.FC = ({ onReady, reactFlowRef, wrapperRef }) => { + const api = useGraphClipboard(reactFlowRef, wrapperRef); + useEffect(() => { + onReady(api); + }, [api, onReady]); + return null; +}; + +const initialGraph = { + nodes: [ + { id: 'a', type: 'expressionNode', name: 'A', content: { expressions: [] }, position: { x: 10, y: 20 } }, + { id: 'b', type: 'expressionNode', name: 'B', content: { expressions: [] }, position: { x: 50, y: 80 } }, + ], + edges: [{ id: 'e1', type: 'edge', sourceId: 'a', targetId: 'b', sourceHandle: null, targetHandle: null }], +}; + +describe('useGraphClipboard', () => { + it('copies selected nodes and inner edges to clipboard payload', async () => { + const successSpy = vi.spyOn(message, 'success'); + const onReady = vi.fn<(api: ClipboardApi) => void>(); + + const reactFlowRef = { + current: { + getEdges: () => [{ id: 'e1', type: 'edge', source: 'a', target: 'b', sourceHandle: null, targetHandle: null }], + getNodes: () => [{ id: 'a', position: { x: 10, y: 20 } }, { id: 'b', position: { x: 50, y: 80 } }], + }, + }; + + const wrapperRef = { + current: document.createElement('div'), + }; + + render( + + + + , + ); + + const api = onReady.mock.calls.at(-1)?.[0]; + if (!api) { + throw new Error('Clipboard API was not initialized'); + } + const selectedNodes: Node[] = [ + { id: 'a', position: { x: 10, y: 20 }, data: {}, type: 'expressionNode' }, + { id: 'b', position: { x: 50, y: 80 }, data: {}, type: 'expressionNode' }, + ]; + + await act(async () => { + await api.copyNodes(selectedNodes); + }); + + expect(copyToClipboardMock).toHaveBeenCalledTimes(1); + const serialized = copyToClipboardMock.mock.calls[0]?.[0]; + if (!serialized) { + throw new Error('Expected serialized clipboard payload'); + } + + const payload = JSON.parse(serialized) as { + nodes: Array<{ id: string }>; + edges: Array<{ sourceId: string; targetId: string }>; + }; + + expect(payload.nodes.map((n) => n.id)).toEqual(['a', 'b']); + expect(payload.edges).toEqual([{ sourceId: 'a', targetId: 'b', id: 'e1', type: 'edge' }]); + expect(successSpy).toHaveBeenCalledWith('Copied to clipboard!'); + + successSpy.mockRestore(); + }); + + it('rejects invalid clipboard payloads on paste', async () => { + const errorSpy = vi.spyOn(message, 'error'); + const onReady = vi.fn<(api: ClipboardApi) => void>(); + + pasteFromClipboardMock.mockResolvedValue('{"foo":1}'); + + const reactFlowRef = { + current: { + getEdges: () => [], + getNodes: () => [{ id: 'a', position: { x: 0, y: 0 } }], + project: ({ x, y }: { x: number; y: number }) => ({ x, y }), + }, + }; + + const wrapper = document.createElement('div'); + Object.defineProperty(wrapper, 'getBoundingClientRect', { + value: () => ({ left: 0, top: 0, width: 600, height: 400, right: 600, bottom: 400 }), + configurable: true, + }); + + render( + + + + , + ); + + const api = onReady.mock.calls.at(-1)?.[0]; + if (!api) { + throw new Error('Clipboard API was not initialized'); + } + + await act(async () => { + await api.pasteNodes(); + }); + + expect(errorSpy).toHaveBeenCalledWith('Failed to paste from clipboard'); + + errorSpy.mockRestore(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/nodes/__tests__/graph-node.test.tsx b/packages/jdm-editor/src/components/decision-graph/nodes/__tests__/graph-node.test.tsx new file mode 100644 index 00000000..413dd4a9 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/nodes/__tests__/graph-node.test.tsx @@ -0,0 +1,78 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { ReactFlowProvider } from 'reactflow'; +import { describe, expect, it } from 'vitest'; + +import { DecisionGraphProvider, useDecisionGraphRaw } from '../../context/dg-store.context'; +import { DecisionGraphEmpty } from '../../dg-empty'; +import { GraphNode } from '../graph-node'; + +const decisionGraph = { + nodes: [ + { + id: 'node-1', + type: 'expressionNode', + name: 'Node 1', + content: { expressions: [] }, + position: { x: 0, y: 0 }, + }, + ], + edges: [], +}; + +const GraphStoreSetup: React.FC = () => { + const store = useDecisionGraphRaw(); + + React.useEffect(() => { + store.referenceStore.setState({ + nodesState: { + current: [[], () => undefined, () => undefined], + } as never, + edgesState: { + current: [[], () => undefined, () => undefined], + } as never, + }); + }, [store]); + + return null; +}; + +describe('GraphNode persistent details state', () => { + it('keeps settings panel open state across remount for the same node id', async () => { + const user = userEvent.setup(); + + const specification = { + displayName: 'Expression', + icon: E, + renderSettings: () =>
Node Settings Content
, + }; + + const { unmount } = render( + + + + + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Settings' })); + expect(screen.getByText('Node Settings Content')).toBeInTheDocument(); + + unmount(); + + render( + + + + + + + , + ); + + expect(screen.getByText('Node Settings Content')).toBeInTheDocument(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-graph/nodes/custom-node/__tests__/index.test.tsx b/packages/jdm-editor/src/components/decision-graph/nodes/custom-node/__tests__/index.test.tsx new file mode 100644 index 00000000..94c692f4 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-graph/nodes/custom-node/__tests__/index.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { createJdmNode } from '../index'; + +describe('createJdmNode', () => { + it('provides a default generateNode implementation when omitted', () => { + const node = createJdmNode({ + kind: 'httpRequest', + displayName: 'HTTP Request', + inputs: [{ name: 'url', control: 'text', label: 'URL' }], + icon: H, + }); + + expect(node.kind).toBe('httpRequest'); + expect(node.displayName).toBe('HTTP Request'); + expect(node.generateNode({ index: 2 }).name).toBe('httpRequest2'); + }); + + it('keeps custom generateNode and onNodeAdd contracts', async () => { + const onNodeAdd = vi.fn(async (n: { id: string; name: string; position: { x: number; y: number } }) => n); + const node = createJdmNode({ + kind: 'customA', + displayName: 'Custom A', + generateNode: ({ index }) => ({ name: `custom-${index}` }), + onNodeAdd, + }); + + expect(node.generateNode({ index: 5 }).name).toBe('custom-5'); + expect(node.onNodeAdd).toBe(onNodeAdd); + + if (!node.onNodeAdd) { + throw new Error('Expected onNodeAdd to be available'); + } + + await node.onNodeAdd({ id: 'n1', name: 'n1', position: { x: 0, y: 0 } }); + expect(onNodeAdd).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-table/__tests__/excel.test.ts b/packages/jdm-editor/src/components/decision-table/__tests__/excel.test.ts new file mode 100644 index 00000000..dd7a7184 --- /dev/null +++ b/packages/jdm-editor/src/components/decision-table/__tests__/excel.test.ts @@ -0,0 +1,85 @@ +import { message } from 'antd'; +import exceljs from 'exceljs'; +import { describe, expect, it, vi } from 'vitest'; + +import { exportDecisionTable, readDecisionTableFile } from '../excel'; + +vi.mock('../../../helpers/file-helpers', () => ({ + saveFile: vi.fn(), +})); + +import { saveFile } from '../../../helpers/file-helpers'; + +const saveFileMock = vi.mocked(saveFile); + +describe('decision table excel io', () => { + it('exports workbook blob with xlsx file name', async () => { + await exportDecisionTable('my-table', [ + { + id: 'dt-1', + name: 'Validation', + hitPolicy: 'first', + inputField: undefined, + outputPath: undefined, + passThorough: false, + executionMode: 'single', + inputs: [{ id: 'in', name: 'Input', field: 'input', defaultValue: '' }], + outputs: [{ id: 'out', name: 'Output', field: 'output', defaultValue: '' }], + rules: [{ _id: 'r1', in: 'x', out: 'y', _description: 'rule' }], + }, + ]); + + expect(saveFileMock).toHaveBeenCalledTimes(1); + expect(saveFileMock.mock.calls[0]?.[0]).toBe('my-table.xlsx'); + expect(saveFileMock.mock.calls[0]?.[1]).toBeInstanceOf(Blob); + }); + + it('reads worksheet into decision table nodes', async () => { + const workbook = new exceljs.Workbook(); + const ws = workbook.addWorksheet('Imported Table'); + + ws.addRow(['dt-77']); + ws.addRow(['Inputs', 'Outputs', '', '']); + + const header = ws.addRow(['Input', 'Output', 'DESCRIPTION', 'Rule ID']); + header.getCell(1).note = JSON.stringify({ id: 'in', name: 'input', type: 'input' }); + header.getCell(2).note = JSON.stringify({ id: 'out', name: 'output', type: 'output' }); + + ws.addRow(['a > 1', 'true', 'sample', 'rule-1']); + + const buffer = await workbook.xlsx.writeBuffer(); + const result = await readDecisionTableFile(buffer as ArrayBuffer); + + expect(result).toHaveLength(1); + expect(result[0]?.id).toBe('dt-77'); + expect(result[0]?.name).toBe('Imported Table'); + expect(result[0]?.content.inputs[0]).toMatchObject({ id: 'in', name: 'Input', field: 'input' }); + expect(result[0]?.content.outputs[0]).toMatchObject({ id: 'out', name: 'Output', field: 'output' }); + expect(result[0]?.content.rules[0]).toMatchObject({ in: 'a > 1', out: 'true', _description: 'sample', _id: 'rule-1' }); + }); + + it('reports malformed header metadata and still returns parsed structure', async () => { + const errorSpy = vi.spyOn(message, 'error'); + + const workbook = new exceljs.Workbook(); + const ws = workbook.addWorksheet('Bad Header'); + + ws.addRow(['dt-bad']); + ws.addRow(['Inputs', 'Outputs', '', '']); + + const header = ws.addRow(['Input', 'Output', 'DESCRIPTION', 'Rule ID']); + header.getCell(1).note = '{bad-json'; + header.getCell(2).note = JSON.stringify({ id: 'out', name: 'output', type: 'output' }); + + ws.addRow(['x', 'ok', '', 'rule-x']); + + const buffer = await workbook.xlsx.writeBuffer(); + const result = await readDecisionTableFile(buffer as ArrayBuffer); + + expect(errorSpy).toHaveBeenCalled(); + expect(result).toHaveLength(1); + expect(result[0]?.name).toBe('Bad Header'); + + errorSpy.mockRestore(); + }); +}); diff --git a/packages/jdm-editor/src/components/decision-table/table/__tests__/table.persistence.test.tsx b/packages/jdm-editor/src/components/decision-table/table/__tests__/table.persistence.test.tsx new file mode 100644 index 00000000..ac83d32d --- /dev/null +++ b/packages/jdm-editor/src/components/decision-table/table/__tests__/table.persistence.test.tsx @@ -0,0 +1,44 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DecisionTableProvider, parseDecisionTable } from '../../context/dt-store.context'; +import { DecisionTable } from '../../dt'; +import { DecisionTableEmpty } from '../../dt-empty'; + +const tableValue = parseDecisionTable({ + hitPolicy: 'first', + inputs: [{ id: 'in', name: 'Input' }], + outputs: [{ id: 'out', name: 'Output', field: 'result' }], + rules: [{ _id: 'r1', in: 'x', out: 'y' }], +}); + +describe('Table column sizing persistence', () => { + it('writes column sizing to localStorage when table id is provided', async () => { + const setItemSpy = vi.spyOn(Storage.prototype, 'setItem'); + + render( + + + + , + ); + + expect(setItemSpy).toHaveBeenCalledWith('jdm-editor:decisionTable:columns:table-1', expect.any(String)); + + setItemSpy.mockRestore(); + }); + + it('handles malformed persisted sizing payload without throwing', () => { + localStorage.setItem('jdm-editor:decisionTable:columns:table-bad', '{not-json'); + + expect(() => + render( + + + + , + ), + ).not.toThrow(); + }); +}); diff --git a/packages/jdm-editor/src/helpers/__tests__/utility.test.ts b/packages/jdm-editor/src/helpers/__tests__/utility.test.ts new file mode 100644 index 00000000..90b74eda --- /dev/null +++ b/packages/jdm-editor/src/helpers/__tests__/utility.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { copyToClipboard, get, pasteFromClipboard } from '../utility'; + +describe('utility clipboard helpers', () => { + it('uses navigator clipboard in secure contexts', async () => { + const writeText = vi.fn<(content: string) => Promise>().mockResolvedValue(); + Object.defineProperty(window, 'isSecureContext', { value: true, configurable: true }); + Object.defineProperty(navigator, 'clipboard', { + value: { writeText, readText: vi.fn() }, + configurable: true, + }); + + await copyToClipboard('hello'); + + expect(writeText).toHaveBeenCalledWith('hello'); + }); + + it('falls back to document.execCommand when clipboard api is unavailable', async () => { + const execCommandSpy = vi.spyOn(document, 'execCommand').mockReturnValue(true); + Object.defineProperty(window, 'isSecureContext', { value: false, configurable: true }); + Object.defineProperty(navigator, 'clipboard', { value: undefined, configurable: true }); + + await copyToClipboard('fallback-text'); + + expect(execCommandSpy).toHaveBeenCalledWith('copy'); + expect(document.querySelector('textarea')).toBeNull(); + + execCommandSpy.mockRestore(); + }); + + it('returns empty string when clipboard read fails', async () => { + Object.defineProperty(navigator, 'clipboard', { + value: { + readText: vi.fn().mockRejectedValue(new Error('no permission')), + }, + configurable: true, + }); + + await expect(pasteFromClipboard()).resolves.toBe(''); + }); +}); + +describe('utility get', () => { + it('returns nested values and default fallback', () => { + const value = get({ a: { b: { c: 42 } } }, 'a.b.c', 0); + const fallback = get({ a: {} }, 'a.b.c', 'missing'); + + expect(value).toBe(42); + expect(fallback).toBe('missing'); + }); +}); diff --git a/packages/jdm-editor/src/helpers/utility.ts b/packages/jdm-editor/src/helpers/utility.ts index 73f64cf9..6ad577c4 100644 --- a/packages/jdm-editor/src/helpers/utility.ts +++ b/packages/jdm-editor/src/helpers/utility.ts @@ -22,7 +22,7 @@ export const copyToClipboard = async (content: string) => { export const pasteFromClipboard = async (): Promise => { try { - return navigator.clipboard.readText(); + return await navigator.clipboard.readText(); } catch { return ''; } From e50612f9e712d53c1771b7736a25caafe4879df4 Mon Sep 17 00:00:00 2001 From: Giorgio Delgado Date: Tue, 3 Mar 2026 14:37:28 -0500 Subject: [PATCH 12/12] Bump to 1.46.0 --- packages/jdm-editor/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jdm-editor/package.json b/packages/jdm-editor/package.json index 28f7e957..64e4eb29 100644 --- a/packages/jdm-editor/package.json +++ b/packages/jdm-editor/package.json @@ -1,6 +1,6 @@ { "name": "giorules-jdm-editor", - "version": "1.45.1", + "version": "1.46.0", "description": "React 19 Compatible Fork of GoRules JDM Editor", "author": "Giorgio Delgado & CoPlane Inc.", "homepage": "https://github.com/supermacro/gio-rules",