From 6651ca0e1059d08e0283d8c3243fadce2df63ba6 Mon Sep 17 00:00:00 2001 From: smouillour Date: Mon, 9 Mar 2026 17:24:31 +0100 Subject: [PATCH 01/16] add gitignore + tmp copilot instruction --- .github/copilot-instructions.md | 492 ++++------------------------ .github/copilot-instructions.md.bak | 434 ++++++++++++++++++++++++ .gitignore | 7 + 3 files changed, 499 insertions(+), 434 deletions(-) create mode 100644 .github/copilot-instructions.md.bak diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1cb3b93f38f..c1c8e5596db 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,434 +1,58 @@ -# Talend/UI — AI Coding Instructions - -## Repository Overview - -This is **Talend/UI**, a Yarn workspaces monorepo containing shared front-end libraries for Talend products. - -- **Workspaces**: `packages/*`, `tools/*`, `fork/*` -- **Stack**: React 18, TypeScript 5, Babel 7 -- **Build tooling**: shared `@talend/scripts-*` packages (see `tools/`) -- **Versioning**: [Changesets](https://github.com/changesets/changesets) (`@changesets/cli`) -- **Package manager**: Yarn 1 (classic) - -Run `yarn install` at the root. The `postinstall` script builds all libraries (`build:lib` + `build:lib:esm`). - ---- - -## Code Style & Formatting - -### Prettier - -Config: `@talend/scripts-config-prettier` (see `tools/scripts-config-prettier/.prettierrc.js`). - -| Setting | Value | -| ---------------- | ------------------ | -| Print width | 100 | -| Quotes | Single (`'`) | -| Trailing commas | All | -| Semicolons | Yes | -| Indentation | **Tabs** | -| Arrow parens | Avoid (`x => x`) | -| JSON / rc files | 2-space indent | -| SCSS files | 1000 print width | - -Prettier runs automatically on commit via `lint-staged` on `*.{json,md,mdx,html,js,jsx,ts,tsx}`. - -### EditorConfig - -- LF line endings, UTF-8 -- Trim trailing whitespace, insert final newline -- Tabs for `.js`, `.jsx`, `.css`, `.scss` -- 2-space indent for `.json` - -### ESLint - -Each package has an `.eslintrc.json` extending `@talend` (resolved from `@talend/eslint-config` → `tools/scripts-config-eslint`). - -Key rules and extends: - -- `eslint:recommended`, `airbnb-base`, `plugin:prettier/recommended` -- `plugin:react/recommended`, `plugin:react/jsx-runtime` -- `plugin:react-hooks/recommended` — `rules-of-hooks` is error, `exhaustive-deps` is warning -- `plugin:jsx-a11y/recommended` -- `plugin:testing-library/react`, `plugin:jest-dom/recommended` -- `plugin:storybook/recommended` - -Important rules: - -- **No `console.log`** — only `console.warn` and `console.error` allowed -- JSX only in `.jsx` / `.tsx` files (`react/jsx-filename-extension`) -- `@talend/import-depth` (error) — controls import depth into packages -- `import/prefer-default-export`: off — named exports are fine -- `react/jsx-props-no-spreading`: off — spread is allowed -- `react/require-default-props`: off -- `@typescript-eslint/no-explicit-any`: warning (not error) in `.ts`/`.tsx` files -- `import/no-extraneous-dependencies`: off in test and story files - -For TypeScript projects, the config auto-detects `tsconfig.json` and adds `@typescript-eslint` with `airbnb-typescript`. - -### Stylelint - -Config: `stylelint-config-sass-guidelines` (see `tools/scripts-config-stylelint/.stylelintrc.js`). - -- Tab indentation -- No `!important` (`declaration-no-important`) -- No `transition: all` — be specific about transitioned properties -- Max nesting depth: 5 -- Lowercase hex colors, named colors where possible -- No unspaced `calc()` operators - ---- - -## TypeScript - -Base config: `@talend/scripts-config-typescript/tsconfig.json` (see `tools/scripts-config-typescript/`). - -| Setting | Value | -| ---------------------------- | ---------- | -| `strict` | `true` | -| `target` | `ES2015` | -| `module` | `esnext` | -| `moduleResolution` | `bundler` | -| `jsx` | `react-jsx`| -| `declaration` | `true` | -| `sourceMap` | `true` | -| `isolatedModules` | `true` | -| `esModuleInterop` | `true` | -| `forceConsistentCasingInFileNames` | `true` | -| `skipLibCheck` | `true` | - -Each package has a local `tsconfig.json` that extends this base: - -```jsonc -{ - "extends": "@talend/scripts-config-typescript/tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - "rootDirs": ["src"] - } -} -``` - ---- - -## Component Architecture - -### Closed API Pattern (Design System) - -Design system components (`packages/design-system`) use **closed APIs** — consumers cannot pass `className`, `style`, or `css` props. This ensures visual homogeneity across all products. - -- **Atoms** (Button, Link, Input): single-tag elements, accept `string` children, typed to mirror their HTML counterparts. Props extend native HTML attributes minus `className`/`style`. -- **Molecules/Organisms** (Modal, Dropdown, Combobox): assembled components with rich props-based APIs. No composition — consumers hydrate via typed props. -- **Templates/Layouts**: may use composition (`children`) for page-level arrangement. - -### Styling - -- **CSS Modules** with `.module.css` files — this is the standard for all new code. No Styled Components. -- **Design tokens** via CSS custom properties from `@talend/design-tokens`. Use them for all colors, spacing, fonts, border-radius, shadows, transitions, etc. -- Use the `classnames` library for conditional class merging. - -### Component Conventions - -- Support `ForwardRef` — wrap components with `forwardRef` so consumers can pass refs. -- Match native HTML element types — component props should extend the underlying element's attributes (e.g., `HTMLButtonElement` for buttons). -- Export components from the package's root `index.ts`. -- Use `DataAttributes` type from `src/types` to support `data-*` attributes. - -### `data-testid` Convention - -All interactive elements must have `data-testid` attributes following this pattern: - -``` -[data-testid=".[?]."] -``` - -| Segment | Required | Example | -| -------------------- | -------- | --------------------------- | -| `block_name` | Yes | `modal`, `inlineediting` | -| `element_type` | Yes | `button`, `input`, `textarea` | -| `element_index` | No | `[1]`, `[2]` | -| `element_identifier` | No | `close`, `reveal`, `edit` | - -Examples: -- `modal.button.close` -- `password.button.reveal` -- `inlineediting.textarea` -- `switch.radio[1]` - -Components should support a `data-testid` prefix prop so consumers can namespace their test IDs (e.g., `my-prefix.inlineediting.button.edit`). - ---- - -## Component Folder Structure - -``` -ComponentName/ -├── ComponentName.tsx # Main component implementation -├── ComponentName.test.tsx # Jest + RTL + jest-axe tests -├── ComponentName.module.css # CSS Modules styles (with design tokens) -├── index.ts # Clean public exports -├── Primitive/ # Internal building-block sub-components -│ ├── ComponentPrimitive.tsx -│ └── ComponentStyles.module.css -└── variations/ # Standalone variant sub-components - ├── ComponentVariantA.tsx - └── ComponentVariantA.module.css -``` - -- Stories live under `src/stories/` in the design-system package, grouped by category (e.g., `clickable/`, `feedback/`). -- The `index.ts` barrel file re-exports everything consumers need. All components must be exported from the package root `src/index.ts`. - ---- - -## Testing - -### Framework & Setup - -- **Vitest** as test runner -- **@testing-library/react** for component rendering and queries -- **jest-axe** for automated accessibility checks -- Timezone forced to `UTC` (`TZ=UTC`) - -### Test File Conventions - -- Name test files `*.test.tsx` or `*.test.ts`, co-located next to the source file. -- Test file regex: `(/__tests__/.*|src/).*\.test.(js|jsx|ts|tsx)$` - -### Writing Tests - -Import test globals explicitly: - -```tsx -import { describe, it, expect } from '@jest/globals'; -``` - -Use `@testing-library/react` for rendering: - -```tsx -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -``` - -**Every component test must include an accessibility check:** - -```tsx -import { axe } from 'jest-axe'; - -it('should render a11y html', async () => { - const { container } = render( -
- -
, - ); - expect(container.firstChild).toMatchSnapshot(); - const results = await axe(document.body); - expect(results).toHaveNoViolations(); -}); -``` - -**Interaction tests** — use `userEvent.setup()`, not `fireEvent` for user interactions: - -```tsx -it('should handle click', async () => { - const user = userEvent.setup(); - render(); - await user.click(screen.getByRole('button')); -}); -``` - -**Querying elements:** -- Prefer `screen.getByRole()`, `screen.getByText()`, `screen.getByLabelText()` -- Use `screen.getByTestId()` for `data-testid` attributes -- Use `screen.queryBy*` for asserting absence - -**Mocking:** -- Use `jest.fn()` for callback mocks -- Mock `@talend/utils` when components generate IDs: - -```tsx -jest.mock('@talend/utils', () => { - let i = 0; - return { - randomUUID: () => `mocked-uuid-${i++}`, - }; -}); -``` - -**Snapshots** — use `container.firstChild` with `toMatchSnapshot()`. - ---- - -## Internationalization (i18n) - -Uses `react-i18next` backed by `i18next`. - -### Namespaces - -Each package has its own i18n namespace: - -| Package | Namespace | -| ------------ | ---------------- | -| components | `tui-components` | -| forms | `tui-forms` | - -### Translation Keys - -- Format: `COMPONENTNAME_KEY` — prefix by the parent component name -- Examples: `LIST_DISPLAY`, `HEADERBAR_GO_PORTAL`, `DELETE_RESOURCE_MESSAGE` - -Always provide a `defaultValue`: - -```tsx -t('SUFFIX_COMPONENT_KEY', { defaultValue: 'Displayed text' }); -``` - -For markup in translations, use the `Trans` component: - -```tsx -import { Trans } from 'react-i18next'; - - - Are you sure you want to remove the {{ resourceLabel }} - {{ resourceName }}? - -``` - -Extract translation catalogs with `yarn extract-i18n` in the relevant package. - ---- - -## Dependencies Management - -Follow these rules when adding dependencies to a package's `package.json`: - -### `devDependencies` - -For build-only tools or packages that are also a `peerDependency`. No runtime impact. - -Examples: `@talend/scripts-core`, `react` (when also in peerDeps), `@types/*` (unless exported types depend on them), `i18next-scanner` - -### `dependencies` - -For packages used at runtime that consumers don't need to configure themselves. - -Examples: `@talend/design-tokens`, `classnames`, `lodash`, `date-fns`, `react-transition-group` - -### `peerDependencies` - -Only for packages the **consumer must import or configure** for the library to work. - -Examples: `react`, `react-dom`, `i18next`, `react-i18next`, `@talend/icons` - -### Type Dependencies - -`@types/*` packages go in `devDependencies` unless the library's **exported types** depend on them — in that case, add to `dependencies`. - ---- - -## Build & Module Formats - -Libraries produce dual output: - -| Format | Directory | Module | -| -------- | --------- | --------- | -| CommonJS | `lib/` | `main` | -| ESM | `lib-esm/`| `module` | - -Build commands: - -```bash -talend-scripts build # CJS → lib/ -talend-scripts build --esm # ESM → lib-esm/ -``` - -Package `exports` field should map both: - -```json -{ - "main": "lib/index.js", - "module": "lib-esm/index.js", - "exports": { - ".": { - "import": "./lib-esm/index.js", - "require": "./lib/index.js" - } - } -} -``` - -Babel config (`@talend/scripts-config-babel`): -- `@babel/preset-env` (targets: last 1 year of browsers, no IE/Samsung/Opera mini) -- `@babel/preset-react` with `runtime: 'automatic'` (no need to import React) -- `@babel/preset-typescript` with `allExtensions: true, isTSX: true` - ---- - -## Storybook - -- Stories go in `.stories.tsx` files -- Type stories with `StoryFn` or `StoryObj` from `@storybook/react` -- Use `action()` from `storybook/actions` for callback args -- Documentation pages use `.stories.mdx` format -- Stories should cover all component variations, states, and edge cases -- Use design tokens and the design system's own components in stories - -Example structure: - -```tsx -import { StoryFn, StoryObj } from '@storybook/react'; -import { action } from 'storybook/actions'; -import { MyComponent } from '../../'; - -export default { - component: MyComponent, - title: 'Category/MyComponent', -} as StoryObj; - -export const Default: StoryFn = args => ( - -); -``` - ---- - -## Versioning & Releases - -- Uses **Changesets** for version management. -- Run `yarn changeset` to create a changeset file describing your change before opening a PR. -- Base branch: `master` -- Internal dependency updates use `patch` bumps. -- Release: `yarn release` (runs `pre-release` then `changeset publish`). - ---- - -## PR Checklist - -Before opening a pull request: - -- [ ] Run `yarn changeset` if a release is needed -- [ ] Tests added for bug fixes and features -- [ ] Documentation updated if applicable -- [ ] Related design links or discussions included in the PR description -- [ ] Breaking changes documented (update the [breaking change wiki](https://github.com/Talend/ui/wiki/BREAKING-CHANGE)) - ---- - -## Git Hooks - -- **Husky** pre-commit hook runs `lint-staged` -- `lint-staged` auto-formats all staged `*.{json,md,mdx,html,js,jsx,ts,tsx}` files with Prettier -- Code is automatically formatted on every commit — no manual formatting needed - ---- - -## Key ADRs (Architecture Decision Records) - -These documents in `docs/` define architectural choices. Read them before making structural changes: - -| ADR | Summary | -| --- | ------- | -| `adr-css-modules.md` | CSS Modules replace Styled Components for all new styling | -| `adr-composition-vs-api.md` | Design system uses closed APIs over composition | -| `adr-data-test.md` | `data-testid` naming convention for QA automation | -| `adr-dependencies.md` | Guidelines for `dependencies` vs `peerDependencies` vs `devDependencies` | -| `adr-2024-04-add-support-to-esm.md` | ESM support strategy and dual CJS/ESM output | + +# BMAD Method — Project Instructions + +## Project Configuration + +- **Project**: tui +- **User**: Smouillour +- **Communication Language**: Français +- **Document Output Language**: English +- **User Skill Level**: expert +- **Output Folder**: {project-root}/_bmad-output +- **Planning Artifacts**: {project-root}/_bmad-output/planning-artifacts +- **Implementation Artifacts**: {project-root}/_bmad-output/implementation-artifacts +- **Project Knowledge**: {project-root}/_bmad-docs + +## BMAD Runtime Structure + +- **Agent definitions**: `_bmad/bmm/agents/` (BMM module) and `_bmad/core/agents/` (core) +- **Workflow definitions**: `_bmad/bmm/workflows/` (organized by phase) +- **Core tasks**: `_bmad/core/tasks/` (help, editorial review, indexing, sharding, adversarial review) +- **Core workflows**: `_bmad/core/workflows/` (brainstorming, party-mode, advanced-elicitation) +- **Workflow engine**: `_bmad/core/tasks/workflow.xml` (executes YAML-based workflows) +- **Module configuration**: `_bmad/bmm/config.yaml` +- **Core configuration**: `_bmad/core/config.yaml` +- **Agent manifest**: `_bmad/_config/agent-manifest.csv` +- **Workflow manifest**: `_bmad/_config/workflow-manifest.csv` +- **Help manifest**: `_bmad/_config/bmad-help.csv` +- **Agent memory**: `_bmad/_memory/` + +## Key Conventions + +- Always load `_bmad/bmm/config.yaml` before any agent activation or workflow execution +- Store all config fields as session variables: `{user_name}`, `{communication_language}`, `{output_folder}`, `{planning_artifacts}`, `{implementation_artifacts}`, `{project_knowledge}` +- MD-based workflows execute directly — load and follow the `.md` file +- YAML-based workflows require the workflow engine — load `workflow.xml` first, then pass the `.yaml` config +- Follow step-based workflow execution: load steps JIT, never multiple at once +- Save outputs after EACH step when using the workflow engine +- The `{project-root}` variable resolves to the workspace root at runtime + +## Available Agents + +| Agent | Persona | Title | Capabilities | +|---|---|---|---| +| bmad-master | BMad Master | BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator | runtime resource management, workflow orchestration, task execution, knowledge custodian | +| analyst | Mary | Business Analyst | market research, competitive analysis, requirements elicitation, domain expertise | +| architect | Winston | Architect | distributed systems, cloud infrastructure, API design, scalable patterns | +| dev | Amelia | Developer Agent | story execution, test-driven development, code implementation | +| pm | John | Product Manager | PRD creation, requirements discovery, stakeholder alignment, user interviews | +| qa | Quinn | QA Engineer | test automation, API testing, E2E testing, coverage analysis | +| quick-flow-solo-dev | Barry | Quick Flow Solo Dev | rapid spec creation, lean implementation, minimum ceremony | +| sm | Bob | Scrum Master | sprint planning, story preparation, agile ceremonies, backlog management | +| tech-writer | Paige | Technical Writer | documentation, Mermaid diagrams, standards compliance, concept explanation | +| ux-designer | Sally | UX Designer | user research, interaction design, UI patterns, experience strategy | + +## Slash Commands + +Type `/bmad-` in Copilot Chat to see all available BMAD workflows and agent activators. Agents are also available in the agents dropdown. + diff --git a/.github/copilot-instructions.md.bak b/.github/copilot-instructions.md.bak new file mode 100644 index 00000000000..1cb3b93f38f --- /dev/null +++ b/.github/copilot-instructions.md.bak @@ -0,0 +1,434 @@ +# Talend/UI — AI Coding Instructions + +## Repository Overview + +This is **Talend/UI**, a Yarn workspaces monorepo containing shared front-end libraries for Talend products. + +- **Workspaces**: `packages/*`, `tools/*`, `fork/*` +- **Stack**: React 18, TypeScript 5, Babel 7 +- **Build tooling**: shared `@talend/scripts-*` packages (see `tools/`) +- **Versioning**: [Changesets](https://github.com/changesets/changesets) (`@changesets/cli`) +- **Package manager**: Yarn 1 (classic) + +Run `yarn install` at the root. The `postinstall` script builds all libraries (`build:lib` + `build:lib:esm`). + +--- + +## Code Style & Formatting + +### Prettier + +Config: `@talend/scripts-config-prettier` (see `tools/scripts-config-prettier/.prettierrc.js`). + +| Setting | Value | +| ---------------- | ------------------ | +| Print width | 100 | +| Quotes | Single (`'`) | +| Trailing commas | All | +| Semicolons | Yes | +| Indentation | **Tabs** | +| Arrow parens | Avoid (`x => x`) | +| JSON / rc files | 2-space indent | +| SCSS files | 1000 print width | + +Prettier runs automatically on commit via `lint-staged` on `*.{json,md,mdx,html,js,jsx,ts,tsx}`. + +### EditorConfig + +- LF line endings, UTF-8 +- Trim trailing whitespace, insert final newline +- Tabs for `.js`, `.jsx`, `.css`, `.scss` +- 2-space indent for `.json` + +### ESLint + +Each package has an `.eslintrc.json` extending `@talend` (resolved from `@talend/eslint-config` → `tools/scripts-config-eslint`). + +Key rules and extends: + +- `eslint:recommended`, `airbnb-base`, `plugin:prettier/recommended` +- `plugin:react/recommended`, `plugin:react/jsx-runtime` +- `plugin:react-hooks/recommended` — `rules-of-hooks` is error, `exhaustive-deps` is warning +- `plugin:jsx-a11y/recommended` +- `plugin:testing-library/react`, `plugin:jest-dom/recommended` +- `plugin:storybook/recommended` + +Important rules: + +- **No `console.log`** — only `console.warn` and `console.error` allowed +- JSX only in `.jsx` / `.tsx` files (`react/jsx-filename-extension`) +- `@talend/import-depth` (error) — controls import depth into packages +- `import/prefer-default-export`: off — named exports are fine +- `react/jsx-props-no-spreading`: off — spread is allowed +- `react/require-default-props`: off +- `@typescript-eslint/no-explicit-any`: warning (not error) in `.ts`/`.tsx` files +- `import/no-extraneous-dependencies`: off in test and story files + +For TypeScript projects, the config auto-detects `tsconfig.json` and adds `@typescript-eslint` with `airbnb-typescript`. + +### Stylelint + +Config: `stylelint-config-sass-guidelines` (see `tools/scripts-config-stylelint/.stylelintrc.js`). + +- Tab indentation +- No `!important` (`declaration-no-important`) +- No `transition: all` — be specific about transitioned properties +- Max nesting depth: 5 +- Lowercase hex colors, named colors where possible +- No unspaced `calc()` operators + +--- + +## TypeScript + +Base config: `@talend/scripts-config-typescript/tsconfig.json` (see `tools/scripts-config-typescript/`). + +| Setting | Value | +| ---------------------------- | ---------- | +| `strict` | `true` | +| `target` | `ES2015` | +| `module` | `esnext` | +| `moduleResolution` | `bundler` | +| `jsx` | `react-jsx`| +| `declaration` | `true` | +| `sourceMap` | `true` | +| `isolatedModules` | `true` | +| `esModuleInterop` | `true` | +| `forceConsistentCasingInFileNames` | `true` | +| `skipLibCheck` | `true` | + +Each package has a local `tsconfig.json` that extends this base: + +```jsonc +{ + "extends": "@talend/scripts-config-typescript/tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "rootDirs": ["src"] + } +} +``` + +--- + +## Component Architecture + +### Closed API Pattern (Design System) + +Design system components (`packages/design-system`) use **closed APIs** — consumers cannot pass `className`, `style`, or `css` props. This ensures visual homogeneity across all products. + +- **Atoms** (Button, Link, Input): single-tag elements, accept `string` children, typed to mirror their HTML counterparts. Props extend native HTML attributes minus `className`/`style`. +- **Molecules/Organisms** (Modal, Dropdown, Combobox): assembled components with rich props-based APIs. No composition — consumers hydrate via typed props. +- **Templates/Layouts**: may use composition (`children`) for page-level arrangement. + +### Styling + +- **CSS Modules** with `.module.css` files — this is the standard for all new code. No Styled Components. +- **Design tokens** via CSS custom properties from `@talend/design-tokens`. Use them for all colors, spacing, fonts, border-radius, shadows, transitions, etc. +- Use the `classnames` library for conditional class merging. + +### Component Conventions + +- Support `ForwardRef` — wrap components with `forwardRef` so consumers can pass refs. +- Match native HTML element types — component props should extend the underlying element's attributes (e.g., `HTMLButtonElement` for buttons). +- Export components from the package's root `index.ts`. +- Use `DataAttributes` type from `src/types` to support `data-*` attributes. + +### `data-testid` Convention + +All interactive elements must have `data-testid` attributes following this pattern: + +``` +[data-testid=".[?]."] +``` + +| Segment | Required | Example | +| -------------------- | -------- | --------------------------- | +| `block_name` | Yes | `modal`, `inlineediting` | +| `element_type` | Yes | `button`, `input`, `textarea` | +| `element_index` | No | `[1]`, `[2]` | +| `element_identifier` | No | `close`, `reveal`, `edit` | + +Examples: +- `modal.button.close` +- `password.button.reveal` +- `inlineediting.textarea` +- `switch.radio[1]` + +Components should support a `data-testid` prefix prop so consumers can namespace their test IDs (e.g., `my-prefix.inlineediting.button.edit`). + +--- + +## Component Folder Structure + +``` +ComponentName/ +├── ComponentName.tsx # Main component implementation +├── ComponentName.test.tsx # Jest + RTL + jest-axe tests +├── ComponentName.module.css # CSS Modules styles (with design tokens) +├── index.ts # Clean public exports +├── Primitive/ # Internal building-block sub-components +│ ├── ComponentPrimitive.tsx +│ └── ComponentStyles.module.css +└── variations/ # Standalone variant sub-components + ├── ComponentVariantA.tsx + └── ComponentVariantA.module.css +``` + +- Stories live under `src/stories/` in the design-system package, grouped by category (e.g., `clickable/`, `feedback/`). +- The `index.ts` barrel file re-exports everything consumers need. All components must be exported from the package root `src/index.ts`. + +--- + +## Testing + +### Framework & Setup + +- **Vitest** as test runner +- **@testing-library/react** for component rendering and queries +- **jest-axe** for automated accessibility checks +- Timezone forced to `UTC` (`TZ=UTC`) + +### Test File Conventions + +- Name test files `*.test.tsx` or `*.test.ts`, co-located next to the source file. +- Test file regex: `(/__tests__/.*|src/).*\.test.(js|jsx|ts|tsx)$` + +### Writing Tests + +Import test globals explicitly: + +```tsx +import { describe, it, expect } from '@jest/globals'; +``` + +Use `@testing-library/react` for rendering: + +```tsx +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +``` + +**Every component test must include an accessibility check:** + +```tsx +import { axe } from 'jest-axe'; + +it('should render a11y html', async () => { + const { container } = render( +
+ +
, + ); + expect(container.firstChild).toMatchSnapshot(); + const results = await axe(document.body); + expect(results).toHaveNoViolations(); +}); +``` + +**Interaction tests** — use `userEvent.setup()`, not `fireEvent` for user interactions: + +```tsx +it('should handle click', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByRole('button')); +}); +``` + +**Querying elements:** +- Prefer `screen.getByRole()`, `screen.getByText()`, `screen.getByLabelText()` +- Use `screen.getByTestId()` for `data-testid` attributes +- Use `screen.queryBy*` for asserting absence + +**Mocking:** +- Use `jest.fn()` for callback mocks +- Mock `@talend/utils` when components generate IDs: + +```tsx +jest.mock('@talend/utils', () => { + let i = 0; + return { + randomUUID: () => `mocked-uuid-${i++}`, + }; +}); +``` + +**Snapshots** — use `container.firstChild` with `toMatchSnapshot()`. + +--- + +## Internationalization (i18n) + +Uses `react-i18next` backed by `i18next`. + +### Namespaces + +Each package has its own i18n namespace: + +| Package | Namespace | +| ------------ | ---------------- | +| components | `tui-components` | +| forms | `tui-forms` | + +### Translation Keys + +- Format: `COMPONENTNAME_KEY` — prefix by the parent component name +- Examples: `LIST_DISPLAY`, `HEADERBAR_GO_PORTAL`, `DELETE_RESOURCE_MESSAGE` + +Always provide a `defaultValue`: + +```tsx +t('SUFFIX_COMPONENT_KEY', { defaultValue: 'Displayed text' }); +``` + +For markup in translations, use the `Trans` component: + +```tsx +import { Trans } from 'react-i18next'; + + + Are you sure you want to remove the {{ resourceLabel }} + {{ resourceName }}? + +``` + +Extract translation catalogs with `yarn extract-i18n` in the relevant package. + +--- + +## Dependencies Management + +Follow these rules when adding dependencies to a package's `package.json`: + +### `devDependencies` + +For build-only tools or packages that are also a `peerDependency`. No runtime impact. + +Examples: `@talend/scripts-core`, `react` (when also in peerDeps), `@types/*` (unless exported types depend on them), `i18next-scanner` + +### `dependencies` + +For packages used at runtime that consumers don't need to configure themselves. + +Examples: `@talend/design-tokens`, `classnames`, `lodash`, `date-fns`, `react-transition-group` + +### `peerDependencies` + +Only for packages the **consumer must import or configure** for the library to work. + +Examples: `react`, `react-dom`, `i18next`, `react-i18next`, `@talend/icons` + +### Type Dependencies + +`@types/*` packages go in `devDependencies` unless the library's **exported types** depend on them — in that case, add to `dependencies`. + +--- + +## Build & Module Formats + +Libraries produce dual output: + +| Format | Directory | Module | +| -------- | --------- | --------- | +| CommonJS | `lib/` | `main` | +| ESM | `lib-esm/`| `module` | + +Build commands: + +```bash +talend-scripts build # CJS → lib/ +talend-scripts build --esm # ESM → lib-esm/ +``` + +Package `exports` field should map both: + +```json +{ + "main": "lib/index.js", + "module": "lib-esm/index.js", + "exports": { + ".": { + "import": "./lib-esm/index.js", + "require": "./lib/index.js" + } + } +} +``` + +Babel config (`@talend/scripts-config-babel`): +- `@babel/preset-env` (targets: last 1 year of browsers, no IE/Samsung/Opera mini) +- `@babel/preset-react` with `runtime: 'automatic'` (no need to import React) +- `@babel/preset-typescript` with `allExtensions: true, isTSX: true` + +--- + +## Storybook + +- Stories go in `.stories.tsx` files +- Type stories with `StoryFn` or `StoryObj` from `@storybook/react` +- Use `action()` from `storybook/actions` for callback args +- Documentation pages use `.stories.mdx` format +- Stories should cover all component variations, states, and edge cases +- Use design tokens and the design system's own components in stories + +Example structure: + +```tsx +import { StoryFn, StoryObj } from '@storybook/react'; +import { action } from 'storybook/actions'; +import { MyComponent } from '../../'; + +export default { + component: MyComponent, + title: 'Category/MyComponent', +} as StoryObj; + +export const Default: StoryFn = args => ( + +); +``` + +--- + +## Versioning & Releases + +- Uses **Changesets** for version management. +- Run `yarn changeset` to create a changeset file describing your change before opening a PR. +- Base branch: `master` +- Internal dependency updates use `patch` bumps. +- Release: `yarn release` (runs `pre-release` then `changeset publish`). + +--- + +## PR Checklist + +Before opening a pull request: + +- [ ] Run `yarn changeset` if a release is needed +- [ ] Tests added for bug fixes and features +- [ ] Documentation updated if applicable +- [ ] Related design links or discussions included in the PR description +- [ ] Breaking changes documented (update the [breaking change wiki](https://github.com/Talend/ui/wiki/BREAKING-CHANGE)) + +--- + +## Git Hooks + +- **Husky** pre-commit hook runs `lint-staged` +- `lint-staged` auto-formats all staged `*.{json,md,mdx,html,js,jsx,ts,tsx}` files with Prettier +- Code is automatically formatted on every commit — no manual formatting needed + +--- + +## Key ADRs (Architecture Decision Records) + +These documents in `docs/` define architectural choices. Read them before making structural changes: + +| ADR | Summary | +| --- | ------- | +| `adr-css-modules.md` | CSS Modules replace Styled Components for all new styling | +| `adr-composition-vs-api.md` | Design system uses closed APIs over composition | +| `adr-data-test.md` | `data-testid` naming convention for QA automation | +| `adr-dependencies.md` | Guidelines for `dependencies` vs `peerDependencies` vs `devDependencies` | +| `adr-2024-04-add-support-to-esm.md` | ESM support strategy and dual CJS/ESM output | diff --git a/.gitignore b/.gitignore index ed9f9e57949..4fc76121a4a 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,10 @@ eslint-report.json stylelint-report.json i18n-extract .test-cache + +# TMP +_bmad +_bmad-* +.github/agents/bmad-* +.github/prompts/bmad_* +.github/prompts/bmad-* From 8d089d8ea2ca80274ced13afc8cf7f6ea13108bb Mon Sep 17 00:00:00 2001 From: smouillour Date: Tue, 10 Mar 2026 10:43:56 +0100 Subject: [PATCH 02/16] epic 1 done and reviewed --- packages/cmf/src/mock/collections.js | 4 +- packages/cmf/src/mock/components.js | 4 +- packages/cmf/src/mock/index.js | 6 ++ .../ActionDropdown.snapshot.test.jsx | 22 -------- .../ActionDropdown/Dropdown.stories.jsx | 22 ++++---- .../src/HeaderBar/HeaderBar.stories.jsx | 31 +++++------ .../src/AboutDialog/AboutDialog.test.js | 14 ++--- .../src/AppLoader/AppLoader.connect.test.js | 15 +++-- .../ComponentForm.selectors.test.js | 31 ++++++----- .../src/ComponentForm/ComponentForm.test.js | 52 +++++++++++++----- .../src/DeleteResource/DeleteResource.test.js | 15 +++-- .../src/DeleteResource/sagas.test.js | 13 +++-- .../src/EditableText/EditableText.test.js | 43 ++++++++++----- .../src/FilterBar/FilterBar.test.js | 55 +++++++++++++++---- packages/containers/src/Form/Form.test.js | 45 +++++++++++---- .../src/GuidedTour/GuidedTour.test.js | 5 +- .../src/HeaderBar/HeaderBar.test.js | 26 ++++----- .../HomeListView/HomeListView.connect.test.js | 5 +- .../containers/src/List/List.sagas.test.js | 13 ++--- .../src/PieChartButton/PieChartButton.test.js | 22 ++++++-- .../SelectObject.component.test.js | 5 +- .../SelectObject/SelectObject.connect.test.js | 43 ++++++++++----- .../ShortcutManager/ShortcutManager.test.js | 3 +- packages/containers/src/Slider/Slider.test.js | 29 ++++++---- .../src/SubHeaderBar/SubHeaderBar.test.js | 15 ++++- packages/containers/src/TabBar/TabBar.test.js | 19 ++++++- .../src/Typeahead/Typeahead.test.js | 14 +++-- packages/sagas/src/pending/pending.test.js | 28 +++++++--- 28 files changed, 379 insertions(+), 220 deletions(-) diff --git a/packages/cmf/src/mock/collections.js b/packages/cmf/src/mock/collections.js index b619773c076..ff8b4c56321 100644 --- a/packages/cmf/src/mock/collections.js +++ b/packages/cmf/src/mock/collections.js @@ -1,3 +1 @@ -import { Map } from 'immutable'; - -export default new Map(); +export default {}; diff --git a/packages/cmf/src/mock/components.js b/packages/cmf/src/mock/components.js index 4d3f2ae6970..c8201800b20 100644 --- a/packages/cmf/src/mock/components.js +++ b/packages/cmf/src/mock/components.js @@ -1,5 +1,3 @@ -import { fromJS } from 'immutable'; - const components = { componentName: { componentKey: { @@ -9,4 +7,4 @@ const components = { }, }; -export default fromJS(components); +export default components; diff --git a/packages/cmf/src/mock/index.js b/packages/cmf/src/mock/index.js index b9cc6507b9e..28583687ff8 100644 --- a/packages/cmf/src/mock/index.js +++ b/packages/cmf/src/mock/index.js @@ -9,6 +9,12 @@ * You have to import the mock from the complete path, ie: * * ```import mock from 'react-cmf/lib/mock';``` + * + * MIGRATION NOTE (Story 1.3): + * `mock.store.getState().cmf.collections` is now a plain object `{}` (was Immutable.Map). + * `mock.store.getState().cmf.components` is now a plain object (was Immutable.Map via fromJS()). + * Consumers should use plain object access (`collections['key']`) instead of `.get('key')`. + * This is aligned with the Epic 5 direction where the real store will also use plain objects. */ import store from './store'; import Provider from './provider'; diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx index b0e3ba26459..92a26dae74d 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx @@ -1,4 +1,3 @@ -import Immutable from 'immutable'; import { render, screen, within } from '@testing-library/react'; import ActionDropdown from './ActionDropdown.component'; @@ -13,7 +12,6 @@ const items = [ onClick: jest.fn(), }, ]; -const immutableItems = Immutable.fromJS(items); describe('ActionDropdown', () => { it('should render a button dropdown with its menu', () => { @@ -32,26 +30,6 @@ describe('ActionDropdown', () => { expect(screen.getByRole('menu')).toBeInTheDocument(); }); - it('should render the same as when plain object or immutable list', () => { - // given - const props = { - id: 'dropdown-id', - label: 'related items', - items, - }; - const immutableProps = { - ...props, - items: immutableItems, - }; - - // when - render(); - - // then - expect(screen.getByRole('button')).toBeInTheDocument(); - expect(screen.getByRole('menu')).toBeInTheDocument(); - }); - it('should render a button with icon and label', () => { // given const props = { diff --git a/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx b/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx index e2b9783bcf8..3484f01c117 100644 --- a/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx +++ b/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx @@ -1,6 +1,4 @@ /* eslint-disable no-console */ -import Immutable from 'immutable'; - import FilterBar from '../../FilterBar'; import Action from '../Action'; import ActionDropdown from './ActionDropdown.component'; @@ -59,10 +57,10 @@ const contentAndLoadingAdditionalContent = { ], }; -const withImmutable = { +const withPlainItems = { id: 'context-dropdown-related-items', - label: 'related immutable items', - items: Immutable.fromJS([ + label: 'related items', + items: [ { id: 'context-dropdown-item-document-1', icon: 'talend-file-json-o', @@ -79,10 +77,10 @@ const withImmutable = { 'data-feature': 'actiondropdown.items', onClick: () => console.log('document 2 click'), }, - ]), + ], }; -const openWithImmutable = { ...withImmutable, open: true }; +const openWithPlainItems = { ...withPlainItems, open: true }; const withComponents = { id: 'context-dropdown-custom-items', @@ -288,9 +286,9 @@ export const Default = {
-

With immutable items :

+

With plain object items :

- +

Loading additional content

@@ -300,9 +298,9 @@ export const Default = {
-

Opened and with immutable items :

-
- +

Opened and with plain object items :

+
+
), diff --git a/packages/components/src/HeaderBar/HeaderBar.stories.jsx b/packages/components/src/HeaderBar/HeaderBar.stories.jsx index a314d23c18f..b75d94664de 100644 --- a/packages/components/src/HeaderBar/HeaderBar.stories.jsx +++ b/packages/components/src/HeaderBar/HeaderBar.stories.jsx @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import Immutable from 'immutable'; import assetsApi from '@talend/assets-api'; import tokens from '@talend/design-tokens'; import AppSwitcher from '../AppSwitcher'; @@ -102,7 +101,7 @@ export default meta; export const Default = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); return ; }, parameters: { info: { styles: infoStyle } }, @@ -110,7 +109,7 @@ export const Default = { export const WithFullLogo = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.logo.isFull = true; return ; }, @@ -119,10 +118,10 @@ export const WithFullLogo = { export const WithoutProducts = { render: () => { - const headerProps = Immutable.fromJS({ + const headerProps = structuredClone({ ...props, products: null, - }).toJS(); + }); headerProps.logo.isFull = true; return ; }, @@ -131,13 +130,13 @@ export const WithoutProducts = { export const WithBrandIcon = { render: () => { - const headerProps = Immutable.fromJS({ + const headerProps = structuredClone({ ...props, brand: { ...props.brand, icon: 'talend-tmc-negative', }, - }).toJS(); + }); return ; }, parameters: { info: { styles: infoStyle } }, @@ -145,13 +144,13 @@ export const WithBrandIcon = { export const WithBrandIconUrl = { render: () => { - const headerProps = Immutable.fromJS({ + const headerProps = structuredClone({ ...props, brand: { ...props.brand, iconUrl: assetsApi.getURL('/src/svg/products/tmc-negative.svg', '@talend/icons'), }, - }).toJS(); + }); return ; }, parameters: { info: { styles: infoStyle } }, @@ -159,7 +158,7 @@ export const WithBrandIconUrl = { export const WithEnvironmentDropdown = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.env = { id: 'header-environment', items: [ @@ -177,7 +176,7 @@ export const WithEnvironmentDropdown = { export const WithUnreadNotifications = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.notification = { hasUnread: true, }; @@ -188,7 +187,7 @@ export const WithUnreadNotifications = { export const WithReadNotifications = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.notification = { hasUnread: false, }; @@ -199,7 +198,7 @@ export const WithReadNotifications = { export const WithHelpSplitDropdown = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.help.items = [ { icon: 'talend-board', @@ -219,7 +218,7 @@ export const WithHelpSplitDropdown = { export const WithCallToAction = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.callToAction = { bsStyle: 'info', className: 'btn-inverse', @@ -234,7 +233,7 @@ export const WithCallToAction = { export const WithGenericAction = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.genericAction = { bsStyle: 'link', id: 'header-generic-action', @@ -249,7 +248,7 @@ export const WithGenericAction = { export const WithoutUserAndWithInformation = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = structuredClone(props); headerProps.user = null; headerProps.information = { id: 'header-info', diff --git a/packages/containers/src/AboutDialog/AboutDialog.test.js b/packages/containers/src/AboutDialog/AboutDialog.test.js index 3ace64157b7..346fd05ec3f 100644 --- a/packages/containers/src/AboutDialog/AboutDialog.test.js +++ b/packages/containers/src/AboutDialog/AboutDialog.test.js @@ -1,6 +1,10 @@ -import { Map, List } from 'immutable'; - import Container from './AboutDialog.container'; + +/** + * cmf.selectors.collections.toJS calls getIn([COLLECTION_ID]) and then .toJS() on the result. + * Returning the default value (undefined) causes `{...undefined} = {}`, matching the test expectation. + */ +const makeCollections = () => ({ getIn: (_keys, def) => def }); import Connected, { mapStateToProps } from './AboutDialog.connect'; import Constants from './AboutDialog.constant'; @@ -13,11 +17,7 @@ describe('Connected AboutDialog', () => { it('should mapStateToProps with an empty list of products', () => { const state = { cmf: { - collections: new Map({ - AboutDialog: { - [Constants.COLLECTION_ID]: new List(), - }, - }), + collections: makeCollections(), }, }; const ownProps = {}; diff --git a/packages/containers/src/AppLoader/AppLoader.connect.test.js b/packages/containers/src/AppLoader/AppLoader.connect.test.js index f2feea27f5a..47249d8799e 100644 --- a/packages/containers/src/AppLoader/AppLoader.connect.test.js +++ b/packages/containers/src/AppLoader/AppLoader.connect.test.js @@ -1,7 +1,14 @@ -import Immutable from 'immutable'; import { render, screen } from '@testing-library/react'; import { AppLoaderContainer, mapStateToProps } from './AppLoader.connect'; +/** + * Returns a plain object shim with the Immutable.Map interface required by + * AppLoader.connect.jsx's mapStateToProps which calls `.has(collectionName)`. + */ +const makeCollections = (keys = []) => ({ + has: key => keys.includes(key), +}); + describe('AppLoader container', () => { describe('rendering', () => { it('should render child if not loading', () => { @@ -27,7 +34,7 @@ describe('AppLoader container', () => { describe('mapStateToProps', () => { it('should return loading to false if we have nothing to wait', () => { // given - const state = { cmf: { collections: Immutable.Map() } }; + const state = { cmf: { collections: makeCollections() } }; const ownProps = {}; // when const result = mapStateToProps(state, ownProps); @@ -37,7 +44,7 @@ describe('AppLoader container', () => { it('should return loading to true if there is something to wait', () => { // given - const state = { cmf: { collections: Immutable.Map({ test2: Immutable.Map() }) } }; + const state = { cmf: { collections: makeCollections(['test2']) } }; const ownProps = { hasCollections: ['test', 'test2'] }; // when const result = mapStateToProps(state, ownProps); @@ -49,7 +56,7 @@ describe('AppLoader container', () => { // given const state = { cmf: { - collections: Immutable.Map({ test2: Immutable.Map(), test: Immutable.Map() }), + collections: makeCollections(['test2', 'test']), }, }; const ownProps = { hasCollections: ['test', 'test2'] }; diff --git a/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js b/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js index a96d2fa17b6..c3807bcd76f 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js @@ -1,7 +1,18 @@ -import Immutable from 'immutable'; import { isComponentFormDirty } from './ComponentForm.selectors'; import { TCompForm } from './ComponentForm.component'; +/** Plain-object shim implementing .get(key, def) for component state. */ +const makeCompState = (data = {}) => ({ get: (k, def) => (k in data ? data[k] : def) }); +/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ +const makeComponents = (data = {}) => ({ + getIn([outer, inner], def) { + const outerVal = data[outer]; + if (outerVal == null) return def; + const innerVal = outerVal[inner]; + return innerVal !== undefined ? innerVal : def; + }, +}); + describe('ComponentForm selectors', () => { const componentName = 'comp'; describe('isComponentFormDirty', () => { @@ -9,10 +20,8 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: Immutable.fromJS({ - [TCompForm.displayName]: { - [componentName]: {}, - }, + components: makeComponents({ + [TCompForm.displayName]: { [componentName]: makeCompState({}) }, }), }, }; @@ -26,10 +35,8 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: Immutable.fromJS({ - [TCompForm.displayName]: { - [componentName]: { dirty: false }, - }, + components: makeComponents({ + [TCompForm.displayName]: { [componentName]: makeCompState({ dirty: false }) }, }), }, }; @@ -43,10 +50,8 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: Immutable.fromJS({ - [TCompForm.displayName]: { - [componentName]: { dirty: true }, - }, + components: makeComponents({ + [TCompForm.displayName]: { [componentName]: makeCompState({ dirty: true }) }, }), }, }; diff --git a/packages/containers/src/ComponentForm/ComponentForm.test.js b/packages/containers/src/ComponentForm/ComponentForm.test.js index c928fff7cdf..e1e7aeb7a0b 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.test.js @@ -1,11 +1,40 @@ import { render, screen } from '@testing-library/react'; -import { fromJS, Map } from 'immutable'; import cmf, { mock } from '@talend/react-cmf'; import { resolveNameForTitleMap, TCompForm, toJS } from './ComponentForm.component'; import addSchemaMock from './ComponentForm.test.schema.json'; +/** + * Minimal shim mimicking Immutable.Map for production code that calls + * .get(key, def), .getIn([...keys], def), .set(key, val), .toJS(). + * Uses a per-instance cache so that repeated .get(key) calls return the same + * object reference (avoids infinite loops in componentDidUpdate identity checks). + */ +const makeImmutableMap = obj => { + const wrapCache = Object.create(null); + const wrap = (key, v) => { + if (v == null || typeof v !== 'object') return v; + if (!(key in wrapCache)) { + wrapCache[key] = Array.isArray(v) + ? Object.assign([], v, { toJS: () => v }) + : makeImmutableMap(v); + } + return wrapCache[key]; + }; + return { + get: (key, def) => (key in obj ? wrap(key, obj[key]) : def), + getIn([key, ...rest], def) { + if (!(key in obj)) return def; + const child = wrap(key, obj[key]); + if (rest.length === 0) return child; + return child?.getIn?.(rest, def) ?? def; + }, + set: (key, val) => makeImmutableMap({ ...obj, [key]: val?.toJS?.() ?? val }), + toJS: () => obj, + }; +}; + vi.mock('./kit', () => { const createTriggers = ({ url, customRegistry, security }) => { function trigger() { @@ -47,7 +76,7 @@ describe('ComponentForm', () => { it('should return js object', () => { // given - const immutableObject = new Map({ a: 1, b: 2 }); + const immutableObject = { a: 1, b: 2, toJS: () => ({ a: 1, b: 2 }) }; // when const result = toJS(immutableObject); @@ -313,7 +342,7 @@ describe('ComponentForm', () => { describe('#render', () => { it("should render a CircularProgress when we don't have the schema", () => { // given - const state = new Map({}); + const state = makeImmutableMap({}); // when render( @@ -334,7 +363,7 @@ describe('ComponentForm', () => { it('should render a response status', () => { // given - const state = fromJS({ response: { statusText: 'we had an error' } }); + const state = makeImmutableMap({ response: { statusText: 'we had an error' } }); // when render( @@ -353,7 +382,7 @@ describe('ComponentForm', () => { it('should render a UIForm', () => { // given - const state = fromJS(addSchemaMock.ui); + const state = makeImmutableMap(addSchemaMock.ui); // when const { container } = render( @@ -374,7 +403,7 @@ describe('ComponentForm', () => { describe('#security', () => { it('should pass security props to createTrigger', () => { - const state = fromJS(addSchemaMock.ui); + const state = makeImmutableMap(addSchemaMock.ui); const instance = new TCompForm({ state, triggerURL: 'http://trigger', @@ -391,7 +420,7 @@ describe('ComponentForm', () => { describe('#update', () => { it('should recreate trigger if triggerURL or customTriggers props change', () => { // given - const state = fromJS(addSchemaMock.ui); + const state = makeImmutableMap(addSchemaMock.ui); const oldTriggerURL = 'http://old'; const newTriggerURL = 'http://new'; const oldCustomTriggers = { oldCustomReload: () => {} }; @@ -433,7 +462,7 @@ describe('ComponentForm', () => { it('should dispatch new definitionURL props', () => { // given - const state = fromJS(addSchemaMock.ui); + const state = makeImmutableMap(addSchemaMock.ui); const dispatch = jest.fn(); const oldUrl = 'http://old'; const newUrl = 'http://new'; @@ -460,7 +489,7 @@ describe('ComponentForm', () => { }); describe('events', () => { - const state = fromJS({ ...addSchemaMock.ui, initialState: addSchemaMock.ui }); + const state = makeImmutableMap({ ...addSchemaMock.ui, initialState: addSchemaMock.ui }); // extract type field schema const typeSchema = { @@ -501,10 +530,7 @@ describe('ComponentForm', () => { it('should NOT dispatch dirty state if it is already dirty', () => { // given - const dirtyState = fromJS({ - ...addSchemaMock.ui, - dirty: true, - }); + const dirtyState = makeImmutableMap({ ...addSchemaMock.ui, dirty: true }); const setState = jest.fn(); const instance = new TCompForm({ state: dirtyState, diff --git a/packages/containers/src/DeleteResource/DeleteResource.test.js b/packages/containers/src/DeleteResource/DeleteResource.test.js index e57643bbf4b..066a6702daa 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.test.js +++ b/packages/containers/src/DeleteResource/DeleteResource.test.js @@ -1,18 +1,23 @@ import { render, screen } from '@testing-library/react'; import cmf, { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; import { DeleteResource } from './DeleteResource.container'; import Connected, { mapStateToProps } from './DeleteResource.connect'; +/** Plain-object shim implementing .get(key, def) for Immutable-Map-like objects. */ +const makeMapItem = data => ({ get: (k, def) => (k in data ? data[k] : def) }); +/** Plain-object shim implementing .find(fn) / .get(index) for Immutable-List-like objects. */ +const makeList = items => ({ find: fn => items.find(fn), get: idx => items[idx] }); +/** Plain-object shim implementing .get(key, def) for the top-level collections Map. */ +const makeCollections = (data = {}) => ({ get: key => data[key] }); + const state = mock.store.state(); const settings = {}; state.cmf = { settings, }; -state.cmf.collections = new Immutable.Map({ - foo: new Immutable.List([new Immutable.Map({ id: '123' })]), -}); +const fooItem = makeMapItem({ id: '123' }); +state.cmf.collections = makeCollections({ foo: makeList([fooItem]) }); describe('Container DeleteResource', () => { let App; @@ -27,7 +32,7 @@ describe('Container DeleteResource', () => { const props = { uri: '/myEndpoint', resourceType: 'myResourceType', - resource: new Immutable.Map({ label: 'myLabel' }), + resource: makeMapItem({ label: 'myLabel' }), header: 'My header title', params: { id: 'myResourceID' }, resourceTypeLabel: 'resourceLabel', diff --git a/packages/containers/src/DeleteResource/sagas.test.js b/packages/containers/src/DeleteResource/sagas.test.js index 0e988b34d9d..e31e108c0d4 100644 --- a/packages/containers/src/DeleteResource/sagas.test.js +++ b/packages/containers/src/DeleteResource/sagas.test.js @@ -1,11 +1,12 @@ // import SagaTester from 'redux-saga-tester'; -import { Map } from 'immutable'; import cmf from '@talend/react-cmf'; import { take, put } from 'redux-saga/effects'; import CONSTANTS from './constants'; // import actions from './actions'; import sagas, * as internals from './sagas'; +const makeMapItem = data => ({ get: (k, def) => (k in data ? data[k] : def) }); + describe('internals', () => { describe('getResourceLocator', () => { it('should return resourceType if no no resourcePath', () => { @@ -54,7 +55,7 @@ describe('internals', () => { }, }, }; - const resource = new Map({ id: '123', label: 'Foo' }); + const resource = makeMapItem({ id: '123', label: 'Foo' }); const gen = internals.deleteResourceValidate(); let effect = gen.next().value; @@ -97,7 +98,11 @@ describe('internals', () => { }, }; - const resource = new Map({ id: 'profileId', type: 'advanced', name: 'deleteThisRunProfile' }); + const resource = makeMapItem({ + id: 'profileId', + type: 'advanced', + name: 'deleteThisRunProfile', + }); const gen = internals.deleteResourceValidate(); gen.next(); @@ -122,7 +127,7 @@ describe('internals', () => { }, }, }; - const resource = new Map({ + const resource = makeMapItem({ id: 'profileId', type: 'advanced', name: 'deleteThisRunProfile', diff --git a/packages/containers/src/EditableText/EditableText.test.js b/packages/containers/src/EditableText/EditableText.test.js index 09a50864909..091621cb088 100644 --- a/packages/containers/src/EditableText/EditableText.test.js +++ b/packages/containers/src/EditableText/EditableText.test.js @@ -1,6 +1,5 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { Map } from 'immutable'; import cmf, { mock } from '@talend/react-cmf'; @@ -8,6 +7,21 @@ import Connect from './EditableText.connect'; import Container, { DISPLAY_NAME } from './EditableText.container'; import { getEditMode } from './EditableText.selectors'; +/** Plain-object shim implementing .get(key, def) and .toJS() for EditableText container state prop. */ +const makeState = editMode => ({ + get: (k, def) => (k === 'editMode' ? editMode : def), + toJS: () => ({ editMode }), +}); +/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ +const makeComponents = (data = {}) => ({ + getIn([outer, inner], def) { + const outerVal = data[outer]; + if (outerVal == null) return def; + const innerVal = outerVal[inner]; + return innerVal !== undefined ? innerVal : def; + }, +}); + describe('Connect', () => { it('should connect EditableText', () => { expect(Connect.displayName).toBe(`Connect(CMF(${Container.displayName}))`); @@ -35,7 +49,7 @@ describe('EditableText container', () => { it('should setState when submit event trigger', async () => { let state; const props = { - state: Map({ editMode: true }), + state: makeState(true), setState: jest.fn(fn => { state = fn; }), @@ -53,11 +67,10 @@ describe('EditableText container', () => { }); }); it('should call ActionCreatorSubmit when submit event trigger', async () => { - const event = {}; const props = { actionCreatorSubmit: 'mySubmitActionCreator', dispatchActionCreator: jest.fn(), - state: Map({ editMode: true }), + state: makeState(true), setState: jest.fn(), text: 'my text', }; @@ -78,7 +91,7 @@ describe('EditableText container', () => { it('should setState when cancel event trigger', async () => { let state; const props = { - state: Map({ editMode: true }), + state: makeState(true), setState: jest.fn(fn => { state = fn; }), @@ -93,7 +106,7 @@ describe('EditableText container', () => { it('should call onCancel when cancel event trigger', async () => { const props = { setState: jest.fn(), - state: Map({ editMode: true }), + state: makeState(true), onCancel: jest.fn(), text: 'my text', }; @@ -104,7 +117,7 @@ describe('EditableText container', () => { it('should call actionCreatorCancel when cancel event trigger', async () => { const props = { setState: jest.fn(), - state: Map({ editMode: true }), + state: makeState(true), actionCreatorCancel: 'myCancelActionCreator', dispatchActionCreator: jest.fn(), text: 'my text', @@ -122,7 +135,7 @@ describe('EditableText container', () => { it('should call setState when edit event trigger', async () => { let state; const props = { - state: Map({ editMode: false }), + state: makeState(false), setState: jest.fn(fn => { state = fn; }), @@ -137,7 +150,7 @@ describe('EditableText container', () => { it('should call onEdit when edit event trigger', async () => { const props = { setState: jest.fn(), - state: Map({ editMode: false }), + state: makeState(false), onEdit: jest.fn(), text: 'my text', }; @@ -145,10 +158,10 @@ describe('EditableText container', () => { await userEvent.click(screen.getByLabelText('Rename')); expect(props.onEdit).toHaveBeenCalledWith(expect.anything()); }); - it('should call onEdit when edit event trigger', async () => { + it('should call actionCreatorEdit via dispatchActionCreator when edit event trigger', async () => { const props = { setState: jest.fn(), - state: Map({ editMode: false }), + state: makeState(false), dispatchActionCreator: jest.fn(), actionCreatorEdit: 'myEditActionCreator', text: 'my text', @@ -168,7 +181,7 @@ describe('EditableText container', () => { const props = { setState: jest.fn(), - state: Map({ editMode: true }), + state: makeState(true), onChange: jest.fn(), text: 'my text', }; @@ -184,7 +197,7 @@ describe('EditableText container', () => { const props = { setState: jest.fn(), - state: Map({ editMode: true }), + state: makeState(true), dispatchActionCreator: jest.fn(), actionCreatorChange: 'myChangeActionCreator', text: 'my text', @@ -207,11 +220,11 @@ describe('EditableText container', () => { describe('EditableText selectors', () => { let mockState; - const componentState = Map({ editMode: true }); + const componentState = makeState(true); beforeEach(() => { mockState = { cmf: { - components: Map({ [DISPLAY_NAME]: Map({ myEditableText: componentState }) }), + components: makeComponents({ [DISPLAY_NAME]: { myEditableText: componentState } }), }, }; }); diff --git a/packages/containers/src/FilterBar/FilterBar.test.js b/packages/containers/src/FilterBar/FilterBar.test.js index 56d44e56ad8..08302bbd4d1 100644 --- a/packages/containers/src/FilterBar/FilterBar.test.js +++ b/packages/containers/src/FilterBar/FilterBar.test.js @@ -1,9 +1,40 @@ -import { Map } from 'immutable'; -import { screen, render, fireEvent } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import Container, { DEFAULT_STATE, DISPLAY_NAME } from './FilterBar.container'; import Connected from './FilterBar.connect'; import { getComponentState, getQuery } from './FilterBar.selectors'; +/** + * Returns a plain-object shim with the Immutable.Map interface required by FilterBar.container + * and FilterBar.selectors (production code still uses Immutable). + * Only implements the subset of Immutable.Map used in these tests. + */ +const makeState = (obj = {}) => ({ + _data: { ...obj }, + get(key, def) { + const val = this._data[key]; + return val !== undefined ? val : def; + }, + set(key, val) { + return makeState({ ...this._data, [key]: val }); + }, + has(key) { + return key in this._data; + }, + hasIn([key, ...rest]) { + if (!(key in this._data)) return false; + const val = this._data[key]; + if (!rest.length) return true; + return val && typeof val.hasIn === 'function' ? val.hasIn(rest) : rest[0] in (val || {}); + }, + getIn([key, ...rest]) { + const val = this._data[key]; + if (!rest.length) return val; + if (val && typeof val.getIn === 'function') return val.getIn(rest); + return (val || {})[rest[0]]; + }, +}); + describe('Filter connected', () => { it('should connect filter', () => { expect(Connected.displayName).toBe(`Connect(CMF(${Container.displayName}))`); @@ -29,7 +60,7 @@ describe('Filter container', () => { const props = { onFilter: jest.fn(), setState: jest.fn(), - state: Map({ docked: false }), + state: makeState({ docked: false }), }; render(); const query = 'foo'; @@ -40,7 +71,7 @@ describe('Filter container', () => { const props = { onFilter: jest.fn(), setState: jest.fn(), - state: Map({ docked: false }), + state: makeState({ docked: false }), }; const query = 'foo'; render(); @@ -58,7 +89,7 @@ describe('Filter container', () => { const props = { onBlur: jest.fn(), setState: jest.fn(), - state: Map({ docked: false }), + state: makeState({ docked: false }), }; render(); fireEvent.blur(document.querySelector('input')); @@ -68,7 +99,7 @@ describe('Filter container', () => { const props = { onBlur: jest.fn(), setState: jest.fn(), - state: Map({ docked: false }), + state: makeState({ docked: false }), onFocus: jest.fn(), }; render(); @@ -76,7 +107,7 @@ describe('Filter container', () => { expect(props.onFocus).toHaveBeenCalled(); }); it('should call setState when onToggle event trigger', () => { - const state = Map({ docked: false }); + const state = makeState({ docked: false }); const prevState = { state }; const setState = jest.fn(fn => { prevState.state = fn(prevState); @@ -98,13 +129,13 @@ describe('Filter container', () => { describe('Filter Selectors', () => { it('should return the filter component state', () => { - const componentState = Map({ + const componentState = makeState({ query: 'Toto was here', docked: true, }); const state = { cmf: { - components: Map({ [DISPLAY_NAME]: Map({ myFilterComponent: componentState }) }), + components: makeState({ [DISPLAY_NAME]: makeState({ myFilterComponent: componentState }) }), }, }; expect(getComponentState(state, 'myFilterComponent')).toEqual(componentState); @@ -112,19 +143,19 @@ describe('Filter Selectors', () => { it('should return the default filter component state', () => { const state = { cmf: { - components: Map(), + components: makeState(), }, }; expect(getComponentState(state, 'myFilterComponent')).toEqual(DEFAULT_STATE); }); it('should return the query', () => { - const componentState = Map({ + const componentState = makeState({ query: 'Hello world', docked: true, }); const state = { cmf: { - components: Map({ [DISPLAY_NAME]: Map({ myFilterComponent: componentState }) }), + components: makeState({ [DISPLAY_NAME]: makeState({ myFilterComponent: componentState }) }), }, }; expect(getQuery(state, 'myFilterComponent')).toEqual('Hello world'); diff --git a/packages/containers/src/Form/Form.test.js b/packages/containers/src/Form/Form.test.js index 5ca8c98b793..c342bcfc314 100644 --- a/packages/containers/src/Form/Form.test.js +++ b/packages/containers/src/Form/Form.test.js @@ -1,9 +1,34 @@ import { render } from '@testing-library/react'; -import { fromJS } from 'immutable'; import Connected from './Form.connect'; import Container from './Form.container'; +/** + * Minimal Immutable.Map-like shim for plain objects. + * Implements the subset of the Immutable.Map API used by Form.container and these tests. + */ +const makePlainState = (obj = {}) => ({ + _data: obj, + get size() { + return Object.keys(obj).length; + }, + get(key, def) { + const val = obj[key]; + return val !== undefined ? val : def; + }, + set(key, val) { + return makePlainState({ ...obj, [key]: val }); + }, + getIn(keys, def) { + let current = obj; + for (const key of keys) { + if (current == null) return def; + current = current._data ? current._data[key] : current[key]; + } + return current !== undefined ? current : def; + }, +}); + const jsonSchema = { type: 'object', title: 'Comment', @@ -45,7 +70,7 @@ describe('Container(Form)', () => { const setState = jest.fn(); const event = { target: 'test' }; const form = new Container({ - state: fromJS({ data: { schema: true } }), + state: makePlainState({ data: { schema: true } }), onErrors, setState, }); @@ -59,7 +84,7 @@ describe('Container(Form)', () => { const dispatchActionCreator = jest.fn(); const setState = jest.fn(); const form = new Container({ - state: fromJS({ data: { schema: true } }), + state: makePlainState({ data: { schema: true } }), setState, onSubmitActionCreator: 'myaction', onSubmit, @@ -79,7 +104,7 @@ describe('Container(Form)', () => { const setState = jest.fn(); const event = { target: 'test' }; const form = new Container({ - state: fromJS({ data: { schema: true } }), + state: makePlainState({ data: { schema: true } }), onChange, setState, }); @@ -93,12 +118,12 @@ describe('Container(Form)', () => { const formId = 'my-form'; const state = { cmf: { - components: fromJS({ - 'Container(Form)': { - [formId]: { - data: { foo: 'bar' }, - }, - }, + components: makePlainState({ + 'Container(Form)': makePlainState({ + [formId]: makePlainState({ + data: makePlainState({ foo: 'bar' }), + }), + }), }), }, }; diff --git a/packages/containers/src/GuidedTour/GuidedTour.test.js b/packages/containers/src/GuidedTour/GuidedTour.test.js index b5b99a30ebb..1557e76a74d 100644 --- a/packages/containers/src/GuidedTour/GuidedTour.test.js +++ b/packages/containers/src/GuidedTour/GuidedTour.test.js @@ -1,10 +1,11 @@ import { screen, render } from '@testing-library/react'; -import { fromJS } from 'immutable'; import Connect from './GuidedTour.connect'; import Container from './GuidedTour.container'; +const makeState = obj => ({ get: key => obj[key] }); + const defaultProps = { - state: fromJS({ + state: makeState({ show: true, }), steps: [ diff --git a/packages/containers/src/HeaderBar/HeaderBar.test.js b/packages/containers/src/HeaderBar/HeaderBar.test.js index c15484d3479..d10db977385 100644 --- a/packages/containers/src/HeaderBar/HeaderBar.test.js +++ b/packages/containers/src/HeaderBar/HeaderBar.test.js @@ -1,8 +1,14 @@ -import { Map, List } from 'immutable'; import { render, screen } from '@testing-library/react'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; + +/** Plain-object shim implementing .get(key, def) for the HeaderBar container state prop. */ +const makeHeaderBarState = (data = {}) => ({ get: (k, def) => (k in data ? data[k] : def) }); +/** Plain-object shim for state.cmf.collections — wraps a value in a .toJS()-able object. */ +const makeToJSList = arr => ({ toJS: () => [...arr] }); +/** Plain-object shim implementing .getIn([key], def) for state.cmf.collections. */ +const makeCollections = (data = {}) => ({ getIn: ([key], def) => (key in data ? data[key] : def) }); import Container, { DEFAULT_STATE } from './HeaderBar.container'; import Connected, { mapStateToProps } from './HeaderBar.connect'; import Constants from './HeaderBar.constant'; @@ -28,7 +34,7 @@ describe('Container HeaderBar', () => { url: 'http://foo.bar', }, ], - state: new Map({ + state: makeHeaderBarState({ productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS, }), }; @@ -65,7 +71,7 @@ describe('Container HeaderBar', () => { }, ], }, - state: new Map({ + state: makeHeaderBarState({ productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS, }), }; @@ -90,7 +96,7 @@ describe('Container HeaderBar', () => { prepareProducts: jest.fn(products => products.map(product => ({ ...product, label: `${product.label} and bar` })), ), - state: new Map({ + state: makeHeaderBarState({ productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS, }), }; @@ -103,7 +109,7 @@ describe('Container HeaderBar', () => { it('should render HeaderBar container while fetching items', async () => { const props = { ...containerProps, - state: new Map({ + state: makeHeaderBarState({ productsFetchState: Constants.FETCHING_PRODUCTS, }), }; @@ -124,11 +130,7 @@ describe('Connected HeaderBar', () => { it('should mapStateToProps with an empty list of products', () => { const state = { cmf: { - collections: new Map({ - HeaderBar: { - [Constants.COLLECTION_ID]: new List(), - }, - }), + collections: makeCollections(), }, }; const ownProps = {}; @@ -146,9 +148,7 @@ describe('Connected HeaderBar', () => { const apps = [{ url: 'foobar' }]; const state = { cmf: { - collections: new Map({ - [Constants.COLLECTION_ID]: new List(apps), - }), + collections: makeCollections({ [Constants.COLLECTION_ID]: makeToJSList(apps) }), }, }; const ownProps = {}; diff --git a/packages/containers/src/HomeListView/HomeListView.connect.test.js b/packages/containers/src/HomeListView/HomeListView.connect.test.js index 8c0d68fe22e..fe6482c4436 100644 --- a/packages/containers/src/HomeListView/HomeListView.connect.test.js +++ b/packages/containers/src/HomeListView/HomeListView.connect.test.js @@ -1,6 +1,5 @@ // rewrite tests using react-testing-library import { screen, render } from '@testing-library/react'; -import { fromJS } from 'immutable'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; @@ -36,7 +35,7 @@ const toolbar = { }, }; -const items = fromJS([ +const items = [ { id: 1, label: 'Title with actions', @@ -47,7 +46,7 @@ const items = fromJS([ display: 'text', className: 'item-0-class', }, -]); +]; const listProps = { list, diff --git a/packages/containers/src/List/List.sagas.test.js b/packages/containers/src/List/List.sagas.test.js index 4aa9a5fb8cf..4e567bfc001 100644 --- a/packages/containers/src/List/List.sagas.test.js +++ b/packages/containers/src/List/List.sagas.test.js @@ -1,12 +1,11 @@ import { put } from 'redux-saga/effects'; -import { fromJS } from 'immutable'; import { mock } from '@talend/react-cmf'; import { onToggleFilter, onFilterChange, onChangeSortChange } from './List.sagas'; import Connected from './List.connect'; const localConfig = { collectionId: 'default', - items: fromJS([ + items: [ { id: 'id1', value: 'value1', @@ -17,7 +16,7 @@ const localConfig = { value: 'value2', text: 'text', }, - ]), + ], list: { columns: [ { key: 'id', name: 'ID' }, @@ -27,7 +26,7 @@ const localConfig = { }; const state = mock.store.state(); -state.cmf.collections = fromJS({ +state.cmf.collections = { default: { columns: [ { key: 'id', name: 'ID' }, @@ -35,7 +34,7 @@ state.cmf.collections = fromJS({ ], items: localConfig.items, }, -}); +}; const context = mock.store.context(); const event = { type: 'click' }; @@ -47,14 +46,14 @@ const data = { props: { config: localConfig }, }; -state.cmf.components = fromJS({ +state.cmf.components = { 'Container(List)': { default: { itemsPerPage: 1, startIndex: 1, }, }, -}); +}; describe('List sagas', () => { it('should check onToggleFilter action', () => { diff --git a/packages/containers/src/PieChartButton/PieChartButton.test.js b/packages/containers/src/PieChartButton/PieChartButton.test.js index f2e7d72dc0d..7c1115919fc 100644 --- a/packages/containers/src/PieChartButton/PieChartButton.test.js +++ b/packages/containers/src/PieChartButton/PieChartButton.test.js @@ -1,7 +1,21 @@ -import Immutable from 'immutable'; import { screen, render } from '@testing-library/react'; import Connected, { ContainerPieChartButton } from './PieChartButton.connect'; +/** + * Shim for Immutable.Map used by PieChartButton.connect: + * - .has(key) + * - .get(key, default) — wraps arrays with { toJS() } + * - .get(key) + */ +const makePieState = (obj = {}) => ({ + has: key => key in obj, + get: (key, def) => { + if (!(key in obj)) return def; + const val = obj[key]; + return Array.isArray(val) ? { toJS: () => val } : val; + }, +}); + describe('PieChartButton connected', () => { it('should connect filter', () => { expect(Connected.displayName).toBe(`Connect(CMF(${ContainerPieChartButton.displayName}))`); @@ -11,7 +25,7 @@ describe('PieChartButton connected', () => { describe('PieChartButton container', () => { it('should render', () => { - const initialState = Immutable.fromJS({ + const initialState = makePieState({ model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -25,7 +39,7 @@ describe('PieChartButton container', () => { }); it('should render not available pie chart button', () => { - const initialState = Immutable.fromJS({ + const initialState = makePieState({ model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -40,7 +54,7 @@ describe('PieChartButton container', () => { }); it('should render loading pie chart button', () => { - const initialState = Immutable.fromJS({ + const initialState = makePieState({ model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, diff --git a/packages/containers/src/SelectObject/SelectObject.component.test.js b/packages/containers/src/SelectObject/SelectObject.component.test.js index 3a7421577e8..7c8c6172dc2 100644 --- a/packages/containers/src/SelectObject/SelectObject.component.test.js +++ b/packages/containers/src/SelectObject/SelectObject.component.test.js @@ -1,13 +1,12 @@ import { render } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; import Component from './SelectObject.component'; describe('Component SelectObject', () => { it('should render', () => { const context = mock.store.context(); - const item = new Immutable.Map({ id: '1', name: 'foo' }); + const item = { id: '1', name: 'foo', get: k => ({ id: '1', name: 'foo' })[k] }; const props = { id: 'my-tree', schema: { @@ -21,7 +20,7 @@ describe('Component SelectObject', () => { }, }, }, - sourceData: new Immutable.List([item]), + sourceData: Object.assign([item], { toJS: () => [{ id: '1', name: 'foo' }] }), filter: { className: 'my-custom-filter', }, diff --git a/packages/containers/src/SelectObject/SelectObject.connect.test.js b/packages/containers/src/SelectObject/SelectObject.connect.test.js index 46cc330f9e3..b2bcb884aac 100644 --- a/packages/containers/src/SelectObject/SelectObject.connect.test.js +++ b/packages/containers/src/SelectObject/SelectObject.connect.test.js @@ -1,9 +1,31 @@ import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; import Container from './SelectObject.container'; import Connected, { mapStateToProps } from './SelectObject.connect'; +/** Plain-object shim implementing .get(key, def) for a nested Immutable-Map-like componentState. */ +const makeCompState = (data = {}) => ({ ...data, get: (k, def) => (k in data ? data[k] : def) }); +/** Plain-object shim implementing .getIn(keys, def) for state.cmf.collections. */ +const makeCollections = (data = {}) => ({ + getIn(keys, def) { + let curr = data; + for (const key of keys) { + if (curr == null || typeof curr !== 'object') return def; + curr = curr[key]; + } + return curr !== undefined ? curr : def; + }, +}); +/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ +const makeComponents = (data = {}) => ({ + getIn([outer, inner], def) { + const outerVal = data[outer]; + if (outerVal == null) return def; + const innerVal = outerVal[inner]; + return innerVal !== undefined ? innerVal : def; + }, +}); + describe('Connected SelectObject', () => { it('should connect SelectObject', () => { expect(Connected.displayName).toBe(`Connect(CMF(${Container.displayName}))`); @@ -11,20 +33,11 @@ describe('Connected SelectObject', () => { }); it('should map state to props', () => { const state = mock.store.state(); - const data = new Immutable.List([ - new Immutable.Map({ label: 'foo' }), - new Immutable.Map({ label: 'bar' }), - ]); - state.cmf.collections = new Immutable.Map({ - width: new Immutable.Map({ data }), - }); - state.cmf.components = new Immutable.Map({ - 'Container(FilterBar)': new Immutable.Map({ - test: new Immutable.Map({ query: 'foo' }), - }), - 'Container(Tree)': new Immutable.Map({ - test: new Immutable.Map({ selectedId: '27' }), - }), + const data = [{ label: 'foo' }, { label: 'bar' }]; + state.cmf.collections = makeCollections({ width: { data } }); + state.cmf.components = makeComponents({ + 'Container(FilterBar)': { test: makeCompState({ query: 'foo' }) }, + 'Container(Tree)': { test: makeCompState({ selectedId: '27' }) }, }); const props = mapStateToProps(state, { id: 'test', diff --git a/packages/containers/src/ShortcutManager/ShortcutManager.test.js b/packages/containers/src/ShortcutManager/ShortcutManager.test.js index 2a9758541b3..e4284c14593 100644 --- a/packages/containers/src/ShortcutManager/ShortcutManager.test.js +++ b/packages/containers/src/ShortcutManager/ShortcutManager.test.js @@ -1,6 +1,5 @@ import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { Map } from 'immutable'; import { mock } from '@talend/react-cmf'; @@ -21,7 +20,7 @@ describe('handles routes', () => { esc: { '/test': '/test/next' }, }, }; - state.cmf.components = new Map(); + state.cmf.components = {}; state.routing = { locationBeforeTransitions: { pathname: '/test', diff --git a/packages/containers/src/Slider/Slider.test.js b/packages/containers/src/Slider/Slider.test.js index f0cd2ce791d..ca63f31f3c4 100644 --- a/packages/containers/src/Slider/Slider.test.js +++ b/packages/containers/src/Slider/Slider.test.js @@ -1,7 +1,18 @@ /* eslint-disable testing-library/no-container */ -import { Map } from 'immutable'; import { render } from '@testing-library/react'; import Container, { DISPLAY_NAME } from './Slider.container'; + +/** Plain-object shim implementing .get(key, def) for component state. */ +const makeCompState = (data = {}) => ({ ...data, get: (k, def) => (k in data ? data[k] : def) }); +/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ +const makeComponents = (data = {}) => ({ + getIn([outer, inner], def) { + const outerVal = data[outer]; + if (outerVal == null) return def; + const innerVal = outerVal[inner]; + return innerVal !== undefined ? innerVal : def; + }, +}); import Connected from './Slider.connect'; import { getComponentState, getValue } from './Slider.selectors'; @@ -17,9 +28,7 @@ describe('Filter container', () => { const props = { id: 'filter', }; - const initialState = new Map({ - value: 15, - }); + const initialState = { value: 15 }; const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); @@ -58,24 +67,20 @@ describe('Filter container', () => { describe('Slider Selectors', () => { it('should return the slider component state', () => { - const componentState = Map({ - value: '12', - }); + const componentState = makeCompState({ value: '12' }); const state = { cmf: { - components: Map({ [DISPLAY_NAME]: Map({ mySliderComponent: componentState }) }), + components: makeComponents({ [DISPLAY_NAME]: { mySliderComponent: componentState } }), }, }; expect(getComponentState(state, 'mySliderComponent')).toEqual(componentState); }); it('should return the value', () => { - const componentState = Map({ - value: 12, - }); + const componentState = makeCompState({ value: 12 }); const state = { cmf: { - components: Map({ [DISPLAY_NAME]: Map({ mySliderComponent: componentState }) }), + components: makeComponents({ [DISPLAY_NAME]: { mySliderComponent: componentState } }), }, }; expect(getValue(state, 'mySliderComponent')).toEqual(12); diff --git a/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js b/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js index bf9d111fcc0..dced54597ed 100644 --- a/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js +++ b/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js @@ -1,6 +1,15 @@ import { screen, render, fireEvent } from '@testing-library/react'; -import { Map } from 'immutable'; import Container, { DEFAULT_STATE, DISPLAY_NAME } from './SubHeaderBar.container'; + +/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ +const makeComponents = (data = {}) => ({ + getIn([outer, inner], def) { + const outerVal = data[outer]; + if (outerVal == null) return def; + const innerVal = outerVal[inner]; + return innerVal !== undefined ? innerVal : def; + }, +}); import Connect from './SubHeaderBar.connect'; import { getComponentState } from './SubHeaderBar.selectors'; @@ -50,11 +59,11 @@ describe('SubHeaderBar container', () => { describe('SubHeaderBar selectors', () => { let mockState; - const componentState = Map({}); + const componentState = {}; beforeEach(() => { mockState = { cmf: { - components: Map({ [DISPLAY_NAME]: Map({ mySubHeaderBar: componentState }) }), + components: makeComponents({ [DISPLAY_NAME]: { mySubHeaderBar: componentState } }), }, }; }); diff --git a/packages/containers/src/TabBar/TabBar.test.js b/packages/containers/src/TabBar/TabBar.test.js index 1f76111fad6..c9f5eafcf98 100644 --- a/packages/containers/src/TabBar/TabBar.test.js +++ b/packages/containers/src/TabBar/TabBar.test.js @@ -1,5 +1,16 @@ -import { Map } from 'immutable'; import Component from '@talend/react-components/lib/TabBar'; + +/** Plain-object shim implementing .get(key, def) for component state. */ +const makeCompState = (data = {}) => ({ ...data, get: (k, def) => (k in data ? data[k] : def) }); +/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ +const makeComponents = (data = {}) => ({ + getIn([outer, inner], def) { + const outerVal = data[outer]; + if (outerVal == null) return def; + const innerVal = outerVal[inner]; + return innerVal !== undefined ? innerVal : def; + }, +}); import Connected, { DEFAULT_STATE } from './TabBar.connect'; import { getComponentState, getSelectedKey } from './TabBar.selectors'; @@ -12,10 +23,12 @@ describe('TabBar connected', () => { describe('TabBar selectors', () => { let mockState; - const componentState = Map({ selectedKey: 'hello' }); + const componentState = makeCompState({ selectedKey: 'hello' }); beforeEach(() => { mockState = { - cmf: { components: Map({ [Component.displayName]: Map({ thisTabBar: componentState }) }) }, + cmf: { + components: makeComponents({ [Component.displayName]: { thisTabBar: componentState } }), + }, }; }); diff --git a/packages/containers/src/Typeahead/Typeahead.test.js b/packages/containers/src/Typeahead/Typeahead.test.js index 1a73269b8e5..2ca69725a2b 100644 --- a/packages/containers/src/Typeahead/Typeahead.test.js +++ b/packages/containers/src/Typeahead/Typeahead.test.js @@ -1,9 +1,13 @@ import { fireEvent, render } from '@testing-library/react'; -import { Map } from 'immutable'; import Connect from './Typeahead.connect'; import Container, { DEFAULT_STATE } from './Typeahead.container'; +const makeTypeaheadState = (obj = {}) => ({ + get: (k, def) => (k in obj ? obj[k] : def), + toJS: () => ({ ...obj }), +}); + const defaultProps = { id: 42, icon: { @@ -30,7 +34,7 @@ describe('Typeahead container', () => { let state; const props = { ...defaultProps, - state: Map({ docked: true }), + state: makeTypeaheadState({ docked: true }), setState: jest.fn(fn => { state = fn(); }), @@ -63,7 +67,7 @@ describe('Typeahead container', () => { const event = { key: KEYS.DOWN, preventDefault: () => {} }; const props = { ...defaultProps, - state: Map({ docked: true }), + state: makeTypeaheadState({ docked: true }), setState: jest.fn(), onKeyDown: jest.fn(), onBlur: jest.fn(), @@ -78,7 +82,7 @@ describe('Typeahead container', () => { const event = { key: 'Esc', preventDefault: () => {} }; const props = { ...defaultProps, - state: Map({ docked: true }), + state: makeTypeaheadState({ docked: true }), setState: jest.fn(), onKeyDown: jest.fn(), onBlur: jest.fn(), @@ -93,7 +97,7 @@ describe('Typeahead container', () => { const event = { key: 'Enter', preventDefault: () => {} }; const props = { ...defaultProps, - state: Map({ docked: true }), + state: makeTypeaheadState({ docked: true }), setState: jest.fn(), onKeyDown: jest.fn(), onSelect: jest.fn(), diff --git a/packages/sagas/src/pending/pending.test.js b/packages/sagas/src/pending/pending.test.js index 4cf9171587b..60adf4affa8 100644 --- a/packages/sagas/src/pending/pending.test.js +++ b/packages/sagas/src/pending/pending.test.js @@ -1,6 +1,6 @@ import { delay, select, put, call, take } from 'redux-saga/effects'; +import { vi } from 'vitest'; import cmf from '@talend/react-cmf'; -import { Map } from 'immutable'; import pendingMaybeNeeded, { ensurePendersCollectionExists, findPenders, @@ -11,14 +11,23 @@ import { PENDING_DELAY_TO_SHOW, SHOW_PENDING, PENDING_COLLECTION_NAME } from '.. const addOrReplace = cmf.actions.collections.addOrReplace; +/** + * Returns a minimal mock with the Immutable.Map interface required by the production saga. + * Production code (pending.js) still uses Immutable — this decouples the test from that dependency. + */ +const makeCollection = () => ({ + set: vi.fn(() => makeCollection()), + delete: vi.fn(() => makeCollection()), +}); + describe('test pending status', () => { it('should create penders collection in cmf.collections', () => { const gen = ensurePendersCollectionExists(); expect(gen.next().value).toEqual(select(findPenders)); - // if penders collection has been create + // when no collection exists, saga puts a new empty collection expect(gen.next(undefined).value).toEqual( - put(addOrReplace(PENDING_COLLECTION_NAME, new Map())), + put(addOrReplace(PENDING_COLLECTION_NAME, expect.any(Object))), ); // the saga is finished expect(gen.next()).toEqual({ done: true, value: undefined }); @@ -27,28 +36,29 @@ describe('test pending status', () => { const gen = ensurePendersCollectionExists(); expect(gen.next().value).toEqual(select(findPenders)); - expect(gen.next(new Map())).toEqual({ done: true, value: undefined }); + // existing truthy collection → saga skips creation and finishes + expect(gen.next(makeCollection())).toEqual({ done: true, value: undefined }); }); it('should pend and then clear pending', () => { const gen = pendingMaybeNeeded('', 'streams:create'); - let pendersCollection = new Map(); + const pendersCollection = makeCollection(); expect(gen.next().value).toEqual(delay(PENDING_DELAY_TO_SHOW)); expect(gen.next().value).toEqual(call(ensurePendersCollectionExists)); expect(gen.next().value).toEqual(select(findPenders)); - pendersCollection = pendersCollection.set('#streams:create', SHOW_PENDING); + // saga receives collection, calls .set(), yields put with updated collection expect(gen.next(pendersCollection).value).toEqual( - put(addOrReplace(PENDING_COLLECTION_NAME, pendersCollection)), + put(addOrReplace(PENDING_COLLECTION_NAME, expect.any(Object))), ); expect(gen.next().value).toEqual(take('DO_NOT_QUIT')); expect(gen.next().value).toEqual(call(ensurePendersCollectionExists)); expect(gen.next().value).toEqual(select(findPenderById, '#streams:create')); expect(gen.next(SHOW_PENDING).value).toEqual(select(findPenders)); - pendersCollection = pendersCollection.delete('#streams:create'); + // saga receives collection, calls .delete(), yields put with updated collection expect(gen.next(pendersCollection).value).toEqual( - put(addOrReplace(PENDING_COLLECTION_NAME, pendersCollection)), + put(addOrReplace(PENDING_COLLECTION_NAME, expect.any(Object))), ); // the saga is finished expect(gen.next()).toEqual({ done: true, value: undefined }); From 7f4be7fc4463ee89eee3cd62f74a53af3e7d13ea Mon Sep 17 00:00:00 2001 From: smouillour Date: Tue, 10 Mar 2026 17:39:30 +0100 Subject: [PATCH 03/16] epic 2 done and reviewed --- packages/flow-designer/package.json | 5 +- .../src/actions/link.actions.test.ts | 26 +- .../src/actions/node.actions.test.ts | 35 +- .../src/actions/nodeType.actions.test.ts | 3 +- .../src/actions/nodeType.actions.ts | 5 +- .../src/actions/port.actions.test.ts | 13 +- .../flow-designer/src/api/data/data.test.ts | 84 +- packages/flow-designer/src/api/data/data.ts | 47 +- .../flow-designer/src/api/link/link.test.ts | 14 +- .../flow-designer/src/api/node/node.test.ts | 28 +- packages/flow-designer/src/api/node/node.ts | 138 +- .../flow-designer/src/api/port/port.test.ts | 30 +- .../src/api/position/position.test.ts | 12 +- .../src/api/position/position.ts | 21 +- .../flow-designer/src/api/size/size.test.ts | 16 +- packages/flow-designer/src/api/size/size.ts | 29 +- .../FlowDesigner.container.test.tsx | 9 +- .../src/components/FlowDesigner.container.tsx | 20 +- .../link/LinksRenderer.component.tsx | 6 +- .../components/link/LinksRenderer.test.tsx | 52 +- .../node/AbstractNode.component.tsx | 41 +- .../node/NodesRenderer.component.tsx | 2 +- .../components/node/NodesRenderer.test.tsx | 19 +- .../port/PortsRenderer.component.tsx | 2 +- .../components/port/PortsRenderer.test.tsx | 14 +- .../src/constants/flowdesigner.model.ts | 664 +++- .../src/constants/flowdesigner.proptypes.ts | 11 +- .../src/customTypings/index.d.ts | 97 +- .../__snapshots__/flow.reducer.test.ts.snap | 384 +-- .../__snapshots__/link.reducer.test.ts.snap | 2837 +++++------------ .../__snapshots__/node.reducer.test.ts.snap | 921 +++--- .../__snapshots__/port.reducer.test.ts.snap | 877 ++--- .../src/reducers/flow.reducer.test.ts | 48 +- .../src/reducers/flow.reducer.ts | 103 +- .../src/reducers/link.reducer.test.ts | 127 +- .../src/reducers/link.reducer.ts | 315 +- .../src/reducers/node.reducer.test.ts | 52 +- .../src/reducers/node.reducer.ts | 290 +- .../src/reducers/nodeType.reducer.ts | 8 +- .../src/reducers/port.reducer.test.ts | 75 +- .../src/reducers/port.reducer.ts | 253 +- .../flow-designer/src/reducers/state-utils.ts | 27 + .../__snapshots__/nodeSelectors.test.ts.snap | 20 +- .../src/selectors/linkSelectors.ts | 45 +- .../src/selectors/nodeSelectors.test.ts | 180 +- .../src/selectors/nodeSelectors.ts | 33 +- .../src/selectors/portSelectors.test.ts | 56 +- .../src/selectors/portSelectors.ts | 77 +- 48 files changed, 3439 insertions(+), 4732 deletions(-) create mode 100644 packages/flow-designer/src/reducers/state-utils.ts diff --git a/packages/flow-designer/package.json b/packages/flow-designer/package.json index 9ed798a3c10..a6641772dd3 100644 --- a/packages/flow-designer/package.json +++ b/packages/flow-designer/package.json @@ -53,7 +53,6 @@ "@types/redux-thunk": "^2.1.0", "eslint": "^10.0.3", "i18next": "^23.16.8", - "immutable": "^3.8.2", "lodash": "^4.17.23", "prop-types": "^15.8.1", "react": "^18.3.1", @@ -68,7 +67,6 @@ "vitest": "^4.0.18" }, "peerDependencies": { - "immutable": "3", "lodash": "^4.17.23", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -81,8 +79,7 @@ "classnames": "^2.5.1", "d3": "^7.9.0", "invariant": "^2.2.4", - "prop-types": "^15.8.1", - "react-immutable-proptypes": "^2.2.0" + "prop-types": "^15.8.1" }, "files": [ "/dist", diff --git a/packages/flow-designer/src/actions/link.actions.test.ts b/packages/flow-designer/src/actions/link.actions.test.ts index fa00f36d505..818943ab989 100644 --- a/packages/flow-designer/src/actions/link.actions.test.ts +++ b/packages/flow-designer/src/actions/link.actions.test.ts @@ -1,7 +1,5 @@ -/* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { Map } from 'immutable'; import * as linkActions from './link.actions'; @@ -12,11 +10,11 @@ describe('Check that link action creators generate proper action objects and per it('addLink', () => { const store = mockStore({ flowDesigner: { - links: Map(), - ports: Map({ + links: {}, + ports: { id1: { id: 'portId', portType: 'type' }, id2: { id: 'portId', portType: 'type' }, - }), + }, }, }); @@ -31,8 +29,8 @@ describe('Check that link action creators generate proper action objects and per it('setLinkTarget', () => { const store = mockStore({ flowDesigner: { - links: Map({ linkId: { id: 'linkId' } }), - ports: Map({ id1: { id: 'portId', portType: 'type' } }), + links: { linkId: { id: 'linkId' } }, + ports: { id1: { id: 'portId', portType: 'type' } }, }, }); @@ -44,8 +42,8 @@ describe('Check that link action creators generate proper action objects and per it('setLinkSource', () => { const store = mockStore({ flowDesigner: { - links: Map({ linkId: { id: 'linkId' } }), - ports: Map({ id1: { id: 'portId', portType: 'type' } }), + links: { linkId: { id: 'linkId' } }, + ports: { id1: { id: 'portId', portType: 'type' } }, }, }); @@ -57,7 +55,7 @@ describe('Check that link action creators generate proper action objects and per it('setLinkGraphicalAttributes', () => { const store = mockStore({ flowDesigner: { - links: Map({ id: { id: 'linkId', linkType: 'type' } }), + links: { id: { id: 'linkId', linkType: 'type' } }, }, }); @@ -69,7 +67,7 @@ describe('Check that link action creators generate proper action objects and per it('removeLinkGrahicalAttribute', () => { const store = mockStore({ flowDesigner: { - links: Map({ id: { id: 'linkId', linkType: 'type' } }), + links: { id: { id: 'linkId', linkType: 'type' } }, }, }); @@ -81,7 +79,7 @@ describe('Check that link action creators generate proper action objects and per it('setLinkData', () => { const store = mockStore({ flowDesigner: { - links: Map({ id: { id: 'linkId', linkType: 'type' } }), + links: { id: { id: 'linkId', linkType: 'type' } }, }, }); @@ -93,7 +91,7 @@ describe('Check that link action creators generate proper action objects and per it('removeLinkData', () => { const store = mockStore({ flowDesigner: { - links: Map({ id: { id: 'linkId', linkType: 'type' } }), + links: { id: { id: 'linkId', linkType: 'type' } }, }, }); @@ -105,7 +103,7 @@ describe('Check that link action creators generate proper action objects and per it('removeLink', () => { const store = mockStore({ flowDesigner: { - links: Map({ id: { id: 'linkId' } }), + links: { id: { id: 'linkId' } }, }, }); diff --git a/packages/flow-designer/src/actions/node.actions.test.ts b/packages/flow-designer/src/actions/node.actions.test.ts index a0b4212b746..c1a40a0b396 100644 --- a/packages/flow-designer/src/actions/node.actions.test.ts +++ b/packages/flow-designer/src/actions/node.actions.test.ts @@ -1,8 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { Map, OrderedMap } from 'immutable'; - import * as nodeActions from './node.actions'; import { FLOWDESIGNER_NODE_SET_TYPE } from '../constants/flowdesigner.constants'; @@ -13,7 +11,7 @@ describe('Check that node action creators generate proper action objects and per it('addNode generate action with 0 configuration', () => { const store = mockStore({ flowDesigner: { - nodes: Map({}), + nodes: {}, }, }); @@ -34,14 +32,13 @@ describe('Check that node action creators generate proper action objects and per it('moveNode generate a proper action object witch nodeId and nodePosition parameter', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ nodeId: { id: 'nodeId', type: 'type' } }), - nodeTypes: Map({ - type: Map({ + nodes: { nodeId: { id: 'nodeId', type: 'type' } }, + nodeTypes: { + type: { component: { calculatePortPosition: () => ({}) }, - }), - }), - // eslint-disable-next-line new-cap - ports: OrderedMap(), + }, + }, + ports: {}, }, }); @@ -53,7 +50,7 @@ describe('Check that node action creators generate proper action objects and per it('setNodeSize', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ nodeId: { id: 'nodeId', type: 'type' } }), + nodes: { nodeId: { id: 'nodeId', type: 'type' } }, }, }); @@ -66,7 +63,7 @@ describe('Check that node action creators generate proper action objects and per const nodeType = 'newNodeType'; const store = mockStore({ flowDesigner: { - nodes: Map({ nodeId: { id: nodeId, type: 'type' } }), + nodes: { nodeId: { id: nodeId, type: 'type' } }, }, }); @@ -81,7 +78,7 @@ describe('Check that node action creators generate proper action objects and per it('setNodeGraphicalAttributes', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ id: { id: 'nodeId', type: 'type' } }), + nodes: { id: { id: 'nodeId', type: 'type' } }, }, }); @@ -93,7 +90,7 @@ describe('Check that node action creators generate proper action objects and per it('removeNodeGraphicalAttribute', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ id: { id: 'nodeId', type: 'type' } }), + nodes: { id: { id: 'nodeId', type: 'type' } }, }, }); @@ -105,7 +102,7 @@ describe('Check that node action creators generate proper action objects and per it('setNodeData', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ id: { id: 'nodeId', type: 'type' } }), + nodes: { id: { id: 'nodeId', type: 'type' } }, }, }); @@ -117,13 +114,13 @@ describe('Check that node action creators generate proper action objects and per it('removeNodeData', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ + nodes: { id: { id: 'nodeId', type: 'type', - data: Map({ testProperties: 'testProperties' }), + data: { testProperties: 'testProperties' }, }, - }), + }, }, }); @@ -135,7 +132,7 @@ describe('Check that node action creators generate proper action objects and per it('removeNode', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ id: { id: 'nodeId', type: 'type' } }), + nodes: { id: { id: 'nodeId', type: 'type' } }, }, }); diff --git a/packages/flow-designer/src/actions/nodeType.actions.test.ts b/packages/flow-designer/src/actions/nodeType.actions.test.ts index c3593d8e9fb..5e7d642d3c8 100644 --- a/packages/flow-designer/src/actions/nodeType.actions.test.ts +++ b/packages/flow-designer/src/actions/nodeType.actions.test.ts @@ -1,9 +1,8 @@ -import { Map } from 'immutable'; import * as nodeTypeActions from './nodeType.actions'; describe('Check that nodeType action creators generate the right action objects', () => { it('setNodeTypes', () => { - const nodeTypes = Map().set('anything', { something: true }); + const nodeTypes = { anything: { something: true } }; expect(nodeTypeActions.setNodeTypes(nodeTypes)).toEqual({ type: 'FLOWDESIGNER_NODETYPE_SET', nodeTypes, diff --git a/packages/flow-designer/src/actions/nodeType.actions.ts b/packages/flow-designer/src/actions/nodeType.actions.ts index 5736f05c062..03285ffdcf6 100644 --- a/packages/flow-designer/src/actions/nodeType.actions.ts +++ b/packages/flow-designer/src/actions/nodeType.actions.ts @@ -1,11 +1,10 @@ -import { Map } from 'immutable'; import { FLOWDESIGNER_NODETYPE_SET } from '../constants/flowdesigner.constants'; /** * Ask to set a map for nodeTypes - * @param {Map} nodeTypes + * @param {Record} nodeTypes */ -export const setNodeTypes = (nodeTypes: Map) => ({ +export const setNodeTypes = (nodeTypes: Record) => ({ type: FLOWDESIGNER_NODETYPE_SET, nodeTypes, }); diff --git a/packages/flow-designer/src/actions/port.actions.test.ts b/packages/flow-designer/src/actions/port.actions.test.ts index 5d24789952e..5f1ff72b327 100644 --- a/packages/flow-designer/src/actions/port.actions.test.ts +++ b/packages/flow-designer/src/actions/port.actions.test.ts @@ -1,6 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; -import { Map } from 'immutable'; import * as portActions from './port.actions'; import { PORT_SINK } from '../constants/flowdesigner.constants'; @@ -11,8 +10,8 @@ describe('Check that port action creators generate proper action objects and per it('addPort', () => { const store = mockStore({ flowDesigner: { - nodes: Map({ nodeId: { id: 'nodeId', nodeType: 'type' } }), - ports: Map(), + nodes: { nodeId: { id: 'nodeId', nodeType: 'type' } }, + ports: {}, }, }); @@ -35,7 +34,7 @@ describe('Check that port action creators generate proper action objects and per it('setPortGraphicalAttribute', () => { const store = mockStore({ flowDesigner: { - ports: Map({ id: { id: 'portId', portType: 'type' } }), + ports: { id: { id: 'portId', portType: 'type' } }, }, }); @@ -47,7 +46,7 @@ describe('Check that port action creators generate proper action objects and per it('removePortAttribute', () => { const store = mockStore({ flowDesigner: { - ports: Map({ id: { id: 'portId' } }), + ports: { id: { id: 'portId' } }, }, }); @@ -59,7 +58,7 @@ describe('Check that port action creators generate proper action objects and per it('setPortData', () => { const store = mockStore({ flowDesigner: { - ports: Map({ id: { id: 'portId', portType: 'type' } }), + ports: { id: { id: 'portId', portType: 'type' } }, }, }); @@ -71,7 +70,7 @@ describe('Check that port action creators generate proper action objects and per it('removePortData', () => { const store = mockStore({ flowDesigner: { - ports: Map({ id: { id: 'portId' }, data: Map({ type: 'test' }) }), + ports: { id: { id: 'portId' }, data: { type: 'test' } }, }, }); diff --git a/packages/flow-designer/src/api/data/data.test.ts b/packages/flow-designer/src/api/data/data.test.ts index 7be703b559c..077e80ea1a3 100644 --- a/packages/flow-designer/src/api/data/data.test.ts +++ b/packages/flow-designer/src/api/data/data.test.ts @@ -1,32 +1,24 @@ -import Immutable from 'immutable'; - import * as Data from './data'; -export const isNotMapException = `Immutable.Map should be a Immutable.Map, was given -""" -object -""" -[object Map] -""" -`; +export const isNotMapException = 'plain object should be a plain object'; export const isNotKeyException = 'key should be a string, was given 8 of type number'; describe('isMapElseThrow', () => { - it('return true if parameter is an Map', () => { + it('return true if parameter is a plain object', () => { // given - const testMap = Immutable.Map(); + const testMap = { key: 'value' }; // when const test = Data.isMapElseThrow(testMap); // expect expect(test).toEqual(true); }); - it('throw an error if parameter is not an Map', () => { + it('throw an error if parameter is not a plain object', () => { // given - const testMap = new Map(); + const testMap: any = []; // when // expect - expect(() => Data.isMapElseThrow(testMap as any)).toThrow(isNotMapException); + expect(() => Data.isMapElseThrow(testMap)).toThrow(isNotMapException); }); }); @@ -55,22 +47,18 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = Immutable.Map({ - withValue: 'value', - }); + const map = { withValue: 'value' }; // when const test = Data.set(key, value, map); // expect - expect(test.get(key)).toEqual(value); + expect(test[key]).toEqual(value); }); it('given an improper key throw', () => { // given const key = 8; const value = 'value'; - const map = Immutable.Map({ - withValue: 'value', - }); + const map = { withValue: 'value' }; // when // expect expect(() => Data.set(key, value, map)).toThrow(isNotKeyException); @@ -80,10 +68,10 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = new Map(); + const map: any = []; // when // expect - expect(() => Data.set(key, value, map as any)).toThrow(isNotMapException); + expect(() => Data.set(key, value, map)).toThrow(isNotMapException); }); }); @@ -92,9 +80,7 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = Immutable.Map({ - key: value, - }); + const map = { key: value }; // when const test = Data.get(key, map); // expect @@ -105,9 +91,7 @@ describe('Data', () => { // given const key = 'anotherKey'; const value = 'value'; - const map = Immutable.Map({ - key: value, - }); + const map = { key: value }; // when const test = Data.get(key, map); // expect @@ -117,9 +101,7 @@ describe('Data', () => { it('given an improper key throw', () => { // given const key = 8; - const map = Immutable.Map({ - withValue: 'value', - }); + const map = { withValue: 'value' }; // when // expect expect(() => Data.get(key, map)).toThrow(isNotKeyException); @@ -128,10 +110,10 @@ describe('Data', () => { it('given an improper map throw', () => { // given const key = 'key'; - const map = new Map(); + const map: any = []; // when // expect - expect(() => Data.get(key, map as any)).toThrow(isNotMapException); + expect(() => Data.get(key, map)).toThrow(isNotMapException); }); }); @@ -140,9 +122,7 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = Immutable.Map({ - key: value, - }); + const map = { key: value }; // when const test = Data.has(key, map); // expect @@ -153,9 +133,7 @@ describe('Data', () => { // given const key = 'anotherKey'; const value = 'value'; - const map = Immutable.Map({ - key: value, - }); + const map = { key: value }; // when const test = Data.has(key, map); // expect @@ -165,9 +143,7 @@ describe('Data', () => { it('given an improper key throw', () => { // given const key = 8; - const map = Immutable.Map({ - withValue: 'value', - }); + const map = { withValue: 'value' }; // when // expect expect(() => Data.has(key, map)).toThrow(isNotKeyException); @@ -176,10 +152,10 @@ describe('Data', () => { it('given an improper map throw', () => { // given const key = 'key'; - const map = new Map(); + const map: any = []; // when // expect - expect(() => Data.has(key, map as any)).toThrow(isNotMapException); + expect(() => Data.has(key, map)).toThrow(isNotMapException); }); }); @@ -188,22 +164,18 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = Immutable.Map({ - key: value, - }); + const map = { key: value }; // when const test = Data.deleteKey(key, map); // expect - expect(test).toEqual(Immutable.Map()); + expect(test).toEqual({}); }); it('given a key and map not containing said key return same map', () => { // given const key = 'anotherKey'; const value = 'value'; - const map = Immutable.Map({ - key: value, - }); + const map = { key: value }; // when const test = Data.deleteKey(key, map); // expect @@ -213,9 +185,7 @@ describe('Data', () => { it('given an improper key throw', () => { // given const key = 8; - const map = Immutable.Map({ - withValue: 'value', - }); + const map = { withValue: 'value' }; // when // expect expect(() => Data.deleteKey(key, map)).toThrow(isNotKeyException); @@ -224,10 +194,10 @@ describe('Data', () => { it('given an improper map throw', () => { // given const key = 'key'; - const map = new Map(); + const map: any = []; // when // expect - expect(() => Data.deleteKey(key, map as any)).toThrow(isNotMapException); + expect(() => Data.deleteKey(key, map)).toThrow(isNotMapException); }); }); }); diff --git a/packages/flow-designer/src/api/data/data.ts b/packages/flow-designer/src/api/data/data.ts index 3b9ef0965eb..7ce2d53c717 100644 --- a/packages/flow-designer/src/api/data/data.ts +++ b/packages/flow-designer/src/api/data/data.ts @@ -1,22 +1,21 @@ /** - * This module is private and deal with updating a graph object internal Immutable.Map + * This module is private and deal with updating a graph object */ import curry from 'lodash/curry'; import isString from 'lodash/isString'; -import { Map } from 'immutable'; import { throwInDev, throwTypeError } from '../throwInDev'; /** - * return true if the parameter is an Immutable.Map throw otherwise + * return true if the parameter is a plain object, throw otherwise * @private - * @param {any} map - the value to be checkd as Immutable.Map + * @param {any} map - the value to be checked as plain object * @return {bool} */ -export function isMapElseThrow(map: Map) { - const test = Map.isMap(map); +export function isMapElseThrow(map: Record) { + const test = typeof map === 'object' && map !== null && !Array.isArray(map); if (!test) { - throwTypeError('Immutable.Map', map, 'map'); + throwTypeError('plain object', map, 'map'); } return test; } @@ -30,9 +29,7 @@ export function isMapElseThrow(map: Map) { export function isKeyElseThrow(key: string | number) { const test = isString(key); if (!test) { - throwInDev( - `key should be a string, was given ${key && key.toString()} of type ${typeof key}`, - ); + throwInDev(`key should be a string, was given ${key && key.toString()} of type ${typeof key}`); } return test; } @@ -42,12 +39,12 @@ export function isKeyElseThrow(key: string | number) { * @function * @param {string} key * @param {any} value - * @param {Immutable.Map} map - * @returns {Immutable.Map} + * @param {Record} map + * @returns {Record} */ -export const set = curry((key: any, value: any, map: Map) => { +export const set = curry((key: any, value: any, map: Record) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return map.set(key, value); + return { ...map, [key]: value }; } return map; }); @@ -56,12 +53,12 @@ export const set = curry((key: any, value: any, map: Map) => { * given a key and a map return the value associated if exist * @function * @param {string} key - * @param {Immutable.Map} map + * @param {Record} map * @returns {any | null} */ -export const get = curry((key: any, map: Map) => { +export const get = curry((key: any, map: Record) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return map.get(key); + return map[key]; } return null; }); @@ -70,12 +67,12 @@ export const get = curry((key: any, map: Map) => { * Given a key and a map check if this key exist on the map * @function * @param {string} key - * @param {Immutable.Map} map + * @param {Record} map * @return {bool} */ -export const has = curry((key: any, map: Map) => { +export const has = curry((key: any, map: Record) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return map.has(key); + return Object.prototype.hasOwnProperty.call(map, key); } return false; }); @@ -84,12 +81,14 @@ export const has = curry((key: any, map: Map) => { * remove given key and its value from the map * @function * @param {string} key - * @param {Immutable.Map} map - * @returns {Immutable.Map} + * @param {Record} map + * @returns {Record} */ -export const deleteKey = curry((key: any, map: Map) => { +export const deleteKey = curry((key: any, map: Record) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return map.delete(key); + const result = { ...map }; + delete result[key]; + return result; } return map; }); diff --git a/packages/flow-designer/src/api/link/link.test.ts b/packages/flow-designer/src/api/link/link.test.ts index 0a4932cd800..d1fb5edd1b2 100644 --- a/packages/flow-designer/src/api/link/link.test.ts +++ b/packages/flow-designer/src/api/link/link.test.ts @@ -11,8 +11,6 @@ because the underlying module data is itself tested. */ -import { Map } from 'immutable'; - import { LinkRecord } from '../../constants/flowdesigner.model'; import * as Link from './link'; @@ -20,7 +18,7 @@ const isNotLinkException = `Linkrecord should be a Linkrecord, was given """ object """ -Map {} +[object Object] """ you should use Link module functions to create and transform Link`; const protectedValueException = @@ -38,10 +36,10 @@ describe('isLinkElseThrow', () => { it('thow an error if parameter is not a LinkRecord', () => { // given - const testLink = Map(); + const testLink2 = {}; // when // expect - expect(() => Link.isLinkElseThrow(testLink)).toThrow(isNotLinkException); + expect(() => Link.isLinkElseThrow(testLink2)).toThrow(isNotLinkException); }); }); @@ -58,7 +56,7 @@ describe('Link', () => { const improperSourceId = 42; const improperTargetId = 64; const improperLinkType = {}; - const improperLink = Map(); + const improperLink = {}; describe('create', () => { it('given proper id, sourceId, targetid and componentType return a Link', () => { @@ -509,9 +507,7 @@ describe('Link', () => { // given // when // expect - expect(() => Link.deleteGraphicalAttribute(key, improperLink)).toThrow( - isNotLinkException, - ); + expect(() => Link.deleteGraphicalAttribute(key, improperLink)).toThrow(isNotLinkException); }); }); }); diff --git a/packages/flow-designer/src/api/node/node.test.ts b/packages/flow-designer/src/api/node/node.test.ts index 7422038bea8..7b53ffaecc8 100644 --- a/packages/flow-designer/src/api/node/node.test.ts +++ b/packages/flow-designer/src/api/node/node.test.ts @@ -11,8 +11,6 @@ because the underlying module data is itself tested. */ -import { Map } from 'immutable'; - import { NodeRecord, NestedNodeRecord } from '../../constants/flowdesigner.model'; import * as Node from './node'; @@ -23,14 +21,14 @@ const isNotNodeException = `NodeRecord should be a NodeRecord, was given """ object """ -Map {} +[object Object] """`; const improperSizeMessage = `SizeRecord should be a SizeRecord, was given """ object """ -Map { "width": 20, "height": 50 } +[object Object] """ you should use Size module functions to create and transform Size`; @@ -38,7 +36,7 @@ const improperPositionMessage = `PositionRecord should be a PositionRecord, was """ object """ -Map { "x": 10, "y": 10 } +[object Object] """ `; @@ -66,10 +64,10 @@ describe('isNodeElseThrow', () => { it('thow an error if parameter is not a NodeRecord', () => { // given - const testNode = Map(); + const testNode2 = {}; // when // expect - expect(() => Node.isNodeElseThrow(testNode)).toThrow(isNotNodeException); + expect(() => Node.isNodeElseThrow(testNode2)).toThrow(isNotNodeException); }); }); @@ -83,10 +81,10 @@ describe('Node', () => { const value = { whatever: 'whatever' }; const improperId = 34; - const improperPosition = Map({ x: 10, y: 10 }); - const improperSize = Map({ width: 20, height: 50 }); + const improperPosition = { x: 10, y: 10 }; + const improperSize = { width: 20, height: 50 }; const improperNodeType = {}; - const improperNode = Map(); + const improperNode = {}; describe('create', () => { it('given proper id, position, size and componentType return a Node', () => { // given @@ -115,9 +113,7 @@ describe('Node', () => { // given // when // expect - expect(() => Node.create(id, position, improperSize, nodeType)).toThrow( - improperSizeMessage, - ); + expect(() => Node.create(id, position, improperSize, nodeType)).toThrow(improperSizeMessage); }); it('throw if given an improper componentType', () => { // given @@ -190,7 +186,7 @@ describe('Node', () => { """ object """ -Map { "x": 10, "y": 10 } +[object Object] """ you should use Position module functions to create and transform Position`); }); @@ -545,9 +541,7 @@ you should use Position module functions to create and transform Position`); // given // when // expect - expect(() => Node.deleteGraphicalAttribute(key, improperNode)).toThrow( - isNotNodeException, - ); + expect(() => Node.deleteGraphicalAttribute(key, improperNode)).toThrow(isNotNodeException); }); }); }); diff --git a/packages/flow-designer/src/api/node/node.ts b/packages/flow-designer/src/api/node/node.ts index 8917e4d0015..728eb45c731 100644 --- a/packages/flow-designer/src/api/node/node.ts +++ b/packages/flow-designer/src/api/node/node.ts @@ -6,7 +6,6 @@ import flow from 'lodash/flow'; import indexOf from 'lodash/indexOf'; import isString from 'lodash/isString'; import upperFirst from 'lodash/upperFirst'; -import { Map } from 'immutable'; import { throwInDev, throwTypeError } from '../throwInDev'; import { NodeRecord, NestedNodeRecord } from '../../constants/flowdesigner.model'; @@ -102,12 +101,14 @@ export function getPosition(node: NodeRecordType | NestedNodeRecordType) { * @param {NodeRecord} node * @returns {NodeRecord} */ -export const setPosition = curry((position: PositionRecord, node: NodeRecordType | NestedNodeRecordType) => { - if (isPositionElseThrow(position) && isNodeElseThrow(node)) { - return node.setIn(positionSelector, position); - } - return node; -}); +export const setPosition = curry( + (position: PositionRecord, node: NodeRecordType | NestedNodeRecordType) => { + if (isPositionElseThrow(position) && isNodeElseThrow(node)) { + return node.setIn(positionSelector, position); + } + return node; + }, +); /** * @param {NodeRecord} node @@ -150,13 +151,15 @@ export function getComponentType(node: NodeRecordType | NestedNodeRecordType) { * @param {NodeRecord} node * @returns {NodeRecord} */ -export const setComponentType = curry((nodeType: string, node: NodeRecordType | NestedNodeRecordType) => { - if (isString(nodeType) && isNodeElseThrow(node)) { - return node.setIn(componentTypeSelector, nodeType); - } - throwInDev(`nodeType should be a string, was given ${nodeType && nodeType.toString()}`); - return node; -}); +export const setComponentType = curry( + (nodeType: string, node: NodeRecordType | NestedNodeRecordType) => { + if (isString(nodeType) && isNodeElseThrow(node)) { + return node.setIn(componentTypeSelector, nodeType); + } + throwInDev(`nodeType should be a string, was given ${nodeType && nodeType.toString()}`); + return node; + }, +); /** * @function @@ -164,15 +167,17 @@ export const setComponentType = curry((nodeType: string, node: NodeRecordType | * @param {NodeRecord} node * @returns {NodeRecord} */ -export const setComponents = curry((components: Map, node: NestedNodeRecordType) => { - if (Map.isMap(components) && isNodeElseThrow(node)) { - return node.setIn(componentsSelector, components); - } - throwInDev( - `components should be a Immutable.List, was given ${components && components.toString()}`, - ); - return node; -}); +export const setComponents = curry( + (components: Record, node: NestedNodeRecordType) => { + if (components != null && isNodeElseThrow(node)) { + return node.setIn(componentsSelector, components); + } + throwInDev( + `components should be a plain object, was given ${components && components.toString()}`, + ); + return node; + }, +); /** * @param {NodeRecord} node @@ -192,12 +197,14 @@ export function getComponents(node: NestedNodeRecordType) { * @param {NodeRecord} node * @returns {NodeRecord} */ -export const setData = curry((key: string, value: any, node: NodeRecordType | NestedNodeRecordType) => { - if (isNodeElseThrow(node)) { - return node.set('data', Data.set(key, value, node.get('data'))); - } - return node; -}); +export const setData = curry( + (key: string, value: any, node: NodeRecordType | NestedNodeRecordType) => { + if (isNodeElseThrow(node)) { + return node.set('data', Data.set(key, value, node.get('data'))); + } + return node; + }, +); /** * @function @@ -262,15 +269,14 @@ export function isWhiteListAttribute(key: string) { * @param {NodeRecord} node * @returns {NodeRecord} */ -export const setGraphicalAttribute = curry((key: string, value: any, node: NodeRecordType | NestedNodeRecordType) => { - if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { - return node.set( - 'graphicalAttributes', - Data.set(key, value, node.get('graphicalAttributes')), - ); - } - return node; -}); +export const setGraphicalAttribute = curry( + (key: string, value: any, node: NodeRecordType | NestedNodeRecordType) => { + if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { + return node.set('graphicalAttributes', Data.set(key, value, node.get('graphicalAttributes'))); + } + return node; + }, +); /** * @function @@ -278,12 +284,14 @@ export const setGraphicalAttribute = curry((key: string, value: any, node: NodeR * @param {NodeRecord} node * @returns {any | null} */ -export const getGraphicalAttribute = curry((key: string, node: NodeRecordType | NestedNodeRecordType) => { - if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { - return Data.get(key, node.get('graphicalAttributes')); - } - return null; -}); +export const getGraphicalAttribute = curry( + (key: string, node: NodeRecordType | NestedNodeRecordType) => { + if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { + return Data.get(key, node.get('graphicalAttributes')); + } + return null; + }, +); /** * @function @@ -291,12 +299,14 @@ export const getGraphicalAttribute = curry((key: string, node: NodeRecordType | * @param {NodeRecord} node * @returns {bool} */ -export const hasGraphicalAttribute = curry((key: string, node: NodeRecordType | NestedNodeRecordType) => { - if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { - return Data.has(key, node.get('graphicalAttributes')); - } - return false; -}); +export const hasGraphicalAttribute = curry( + (key: string, node: NodeRecordType | NestedNodeRecordType) => { + if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { + return Data.has(key, node.get('graphicalAttributes')); + } + return false; + }, +); /** * @function @@ -304,15 +314,14 @@ export const hasGraphicalAttribute = curry((key: string, node: NodeRecordType | * @param {NodeRecord} node * @returns {NodeRecord} */ -export const deleteGraphicalAttribute = curry((key: string, node: NodeRecordType | NestedNodeRecordType) => { - if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { - return node.set( - 'graphicalAttributes', - Data.deleteKey(key, node.get('graphicalAttributes')), - ); - } - return node; -}); +export const deleteGraphicalAttribute = curry( + (key: string, node: NodeRecordType | NestedNodeRecordType) => { + if (isNodeElseThrow(node) && isWhiteListAttribute(key)) { + return node.set('graphicalAttributes', Data.deleteKey(key, node.get('graphicalAttributes'))); + } + return node; + }, +); /** * Create a new Node @@ -338,15 +347,12 @@ export const create = curry( setPosition(position), setSize(size), setComponentType(componentType), - setComponents(Map()), + setComponents({}), ])(new NestedNodeRecord()); } - return flow([ - setId(id), - setPosition(position), - setSize(size), - setComponentType(componentType), - ])(new NodeRecord()); + return flow([setId(id), setPosition(position), setSize(size), setComponentType(componentType)])( + new NodeRecord(), + ); }, ); diff --git a/packages/flow-designer/src/api/port/port.test.ts b/packages/flow-designer/src/api/port/port.test.ts index b40b23d3727..b2cab2f74c6 100644 --- a/packages/flow-designer/src/api/port/port.test.ts +++ b/packages/flow-designer/src/api/port/port.test.ts @@ -11,8 +11,6 @@ because the underlying module data is itself tested. */ -import { Map } from 'immutable'; - import { PortRecord } from '../../constants/flowdesigner.model'; import * as Port from './port'; import * as Position from '../position/position'; @@ -28,10 +26,10 @@ describe('isPortElseThrow', () => { }); it('throw if given parameter is not a PortRecord', () => { // given - const testPort = Map(); + const testPort2 = {}; // when // expect - expect(() => Port.isPortElseThrow(testPort)).toThrow(); + expect(() => Port.isPortElseThrow(testPort2)).toThrow(); }); }); @@ -62,9 +60,9 @@ describe('port api', () => { const improperNodeId = 42; const improperIndex = '10'; const impropertopology = {}; - const improperPosition = Map({ x: 10, y: 10 }); + const improperPosition = { x: 10, y: 10 }; const improperPortType = {}; - const improperPort = Map(); + const improperPort = {}; describe('create', () => { it('given proper id, nodeId, index, topology and componentType return a Node', () => { @@ -78,41 +76,31 @@ describe('port api', () => { // given // when // expect - expect(() => - Port.create(improperId as any, nodeId, index, topology, portType), - ).toThrow(); + expect(() => Port.create(improperId as any, nodeId, index, topology, portType)).toThrow(); }); it('throw if given an improper NodeId', () => { // given // when // expect - expect(() => - Port.create(id, improperNodeId as any, index, topology, portType), - ).toThrow(); + expect(() => Port.create(id, improperNodeId as any, index, topology, portType)).toThrow(); }); it('throw if given an improper index', () => { // given // when // expect - expect(() => - Port.create(id, nodeId, improperIndex as any, topology, portType), - ).toThrow(); + expect(() => Port.create(id, nodeId, improperIndex as any, topology, portType)).toThrow(); }); it('throw if given an improper topology', () => { // given // when // expect - expect(() => - Port.create(id, nodeId, index, impropertopology as any, portType), - ).toThrow(); + expect(() => Port.create(id, nodeId, index, impropertopology as any, portType)).toThrow(); }); it('throw if given an improper componentType', () => { // given // when // expect - expect(() => - Port.create(id, nodeId, index, topology, improperPortType as any), - ).toThrow(); + expect(() => Port.create(id, nodeId, index, topology, improperPortType as any)).toThrow(); }); }); diff --git a/packages/flow-designer/src/api/position/position.test.ts b/packages/flow-designer/src/api/position/position.test.ts index 9714ce697f2..66f7b2cd47d 100644 --- a/packages/flow-designer/src/api/position/position.test.ts +++ b/packages/flow-designer/src/api/position/position.test.ts @@ -1,5 +1,3 @@ -import { Map } from 'immutable'; - import { PositionRecord } from '../../constants/flowdesigner.model'; import * as Position from './position'; @@ -8,14 +6,14 @@ const isNotPositionException = `PositionRecord should be a PositionRecord, was g """ object """ -Map {} +[object Object] """ you should use Position module functions to create and transform Position`; const improperPositionException = `PositionRecord should be a PositionRecord, was given """ object """ -Map { "x": 10, "y": 10 } +[object Object] """ you should use Position module functions to create and transform Position`; const isImproperXCoordinate = 'x should be a number, was given 10 of type string'; @@ -33,10 +31,10 @@ describe('isPositionElseThrow', () => { it('thow an error if parameter is not a PositionRecord', () => { // given - const testPosition = Map(); + const testPosition2 = {}; // when // expect - expect(() => Position.isPositionElseThrow(testPosition)).toThrow(isNotPositionException); + expect(() => Position.isPositionElseThrow(testPosition2)).toThrow(isNotPositionException); }); }); @@ -47,7 +45,7 @@ describe('Position', () => { const improperX = '10'; const improperY = '50'; - const improperTestPosition = Map({ x: 10, y: 10 }); + const improperTestPosition = { x: 10, y: 10 }; describe('create', () => { it('given proper x and y coordinate return a Position', () => { // given diff --git a/packages/flow-designer/src/api/position/position.ts b/packages/flow-designer/src/api/position/position.ts index e218740135e..823d82337e3 100644 --- a/packages/flow-designer/src/api/position/position.ts +++ b/packages/flow-designer/src/api/position/position.ts @@ -1,5 +1,4 @@ import curry from 'lodash/curry'; -import flow from 'lodash/flow'; import isNumber from 'lodash/isNumber'; import { throwInDev, throwTypeError } from '../throwInDev'; @@ -51,7 +50,7 @@ export function isPositionElseThrow(position: PositionRecordType) { */ export function getXCoordinate(position: PositionRecordType) { if (isPositionElseThrow(position)) { - return position.get('x'); + return position.x; } return null; } @@ -65,7 +64,7 @@ export function getXCoordinate(position: PositionRecordType) { */ export const setXCoordinate = curry((x: number, position: PositionRecordType) => { if (isPositionElseThrow(position) && isNumber(x)) { - return position.set('x', x); + return new PositionRecord({ ...position, x }); } throwInDev(`x should be a number, was given ${x && x.toString()} of type ${typeof x}`); return position; @@ -78,7 +77,7 @@ export const setXCoordinate = curry((x: number, position: PositionRecordType) => */ export function getYCoordinate(position: PositionRecordType) { if (isPositionElseThrow(position)) { - return position.get('y'); + return position.y; } return null; } @@ -91,7 +90,7 @@ export function getYCoordinate(position: PositionRecordType) { */ export const setYCoordinate = curry((y: number, position: PositionRecordType) => { if (isPositionElseThrow(position) && isNumber(y)) { - return position.set('y', y); + return new PositionRecord({ ...position, y }); } throwInDev(`y should be a number, was given ${y && y.toString()} of type ${typeof y}`); return position; @@ -103,6 +102,12 @@ export const setYCoordinate = curry((y: number, position: PositionRecordType) => * @param {number} y * @return {PositionRecord} */ -export const create = curry((x: number, y: number) => - flow([setXCoordinate(x), setYCoordinate(y)])(new PositionRecord()), -); +export const create = curry((x: number, y: number) => { + if (!isNumber(x)) { + throwInDev(`x should be a number, was given ${x && (x as any).toString()} of type ${typeof x}`); + } + if (!isNumber(y)) { + throwInDev(`y should be a number, was given ${y && (y as any).toString()} of type ${typeof y}`); + } + return new PositionRecord({ x, y }); +}); diff --git a/packages/flow-designer/src/api/size/size.test.ts b/packages/flow-designer/src/api/size/size.test.ts index 2f914e84cc3..676afed81d5 100644 --- a/packages/flow-designer/src/api/size/size.test.ts +++ b/packages/flow-designer/src/api/size/size.test.ts @@ -1,5 +1,3 @@ -import { Map } from 'immutable'; - import { SizeRecord } from '../../constants/flowdesigner.model'; import * as Size from './size'; @@ -8,14 +6,14 @@ const isNotSizeException = `SizeRecord should be a SizeRecord, was given """ object """ -Map {} +[object Object] """ you should use Size module functions to create and transform Size`; const isNotProperSizeException = `SizeRecord should be a SizeRecord, was given """ object """ -Map { "width": 10, "height": 10 } +[object Object] """ you should use Size module functions to create and transform Size`; const isImproperWidth = 'width should be a number, was given 10 of type string'; @@ -33,10 +31,10 @@ describe('isSizeElseThrow', () => { it('throw an error if parameter is not a SizeRecord', () => { // given - const testSize = Map(); + const testSize2 = {}; // when // expect - expect(() => Size.isSizeElseThrow(testSize)).toThrow(isNotSizeException); + expect(() => Size.isSizeElseThrow(testSize2)).toThrow(isNotSizeException); }); }); @@ -47,7 +45,7 @@ describe('Size', () => { const improperWidth = '10'; const improperHeight = '50'; - const improperTestSize = Map({ width: 10, height: 10 }); + const improperTestSize = { width: 10, height: 10 }; describe('create', () => { it('given proper width and height return a Size', () => { // given @@ -155,9 +153,7 @@ describe('Size', () => { // given // when // expect - expect(() => Size.setHeight(height, improperTestSize)).toThrow( - isNotProperSizeException, - ); + expect(() => Size.setHeight(height, improperTestSize)).toThrow(isNotProperSizeException); }); }); }); diff --git a/packages/flow-designer/src/api/size/size.ts b/packages/flow-designer/src/api/size/size.ts index 9437370d6ad..adb516d774d 100644 --- a/packages/flow-designer/src/api/size/size.ts +++ b/packages/flow-designer/src/api/size/size.ts @@ -1,5 +1,4 @@ import curry from 'lodash/curry'; -import flow from 'lodash/flow'; import { throwInDev, throwTypeError } from '../throwInDev'; import { SizeRecord } from '../../constants/flowdesigner.model'; @@ -50,7 +49,7 @@ export function isSizeElseThrow(size: SizeRecordType) { */ export function getWidth(size: SizeRecordType) { if (isSizeElseThrow(size)) { - return size.get('width'); + return size.width; } return null; } @@ -64,7 +63,7 @@ export function getWidth(size: SizeRecordType) { */ export const setWidth = curry((width: number, size: SizeRecordType) => { if (isSizeElseThrow(size) && typeof width === 'number') { - return size.set('width', width); + return new SizeRecord({ ...size, width }); } throwInDev(`width should be a number, was given ${width.toString()} of type ${typeof width}`); return size; @@ -77,7 +76,7 @@ export const setWidth = curry((width: number, size: SizeRecordType) => { */ export function getHeight(size: SizeRecordType) { if (isSizeElseThrow(size)) { - return size.get('height'); + return size.height; } return null; } @@ -91,11 +90,9 @@ export function getHeight(size: SizeRecordType) { */ export const setHeight = curry((height: number, size: SizeRecordType) => { if (isSizeElseThrow(size) && typeof height === 'number') { - return size.set('height', height); + return new SizeRecord({ ...size, height }); } - throwInDev( - `height should be a number, was given ${height.toString()} of type ${typeof height}`, - ); + throwInDev(`height should be a number, was given ${height.toString()} of type ${typeof height}`); return size; }); @@ -106,6 +103,16 @@ export const setHeight = curry((height: number, size: SizeRecordType) => { * @param {number} height * @return {SizeRecord} */ -export const create = curry((width: number, height: number) => - flow([setWidth(width), setHeight(height)])(new SizeRecord()), -); +export const create = curry((width: number, height: number) => { + if (typeof width !== 'number') { + throwInDev( + `width should be a number, was given ${(width as any).toString()} of type ${typeof width}`, + ); + } + if (typeof height !== 'number') { + throwInDev( + `height should be a number, was given ${(height as any).toString()} of type ${typeof height}`, + ); + } + return new SizeRecord({ width, height }); +}); diff --git a/packages/flow-designer/src/components/FlowDesigner.container.test.tsx b/packages/flow-designer/src/components/FlowDesigner.container.test.tsx index 2f2d85080e2..8ead6e8da77 100644 --- a/packages/flow-designer/src/components/FlowDesigner.container.test.tsx +++ b/packages/flow-designer/src/components/FlowDesigner.container.test.tsx @@ -1,9 +1,8 @@ import renderer from 'react-test-renderer'; -import { Map } from 'immutable'; import { FlowDesigner } from './FlowDesigner.container'; import NodeType from './configuration/NodeType.component'; -import { NodeRecord, Id, PortRecord, LinkRecord } from '../customTypings/index.d'; +import { NodeRecordMap, PortRecordMap, LinkRecordMap } from '../customTypings/index.d'; vi.mock('./ZoomHandler.component'); vi.mock('./grid/Grid.component', () => { @@ -16,9 +15,9 @@ const noOp = () => {}; describe(' renders correctly', () => { it(' renders correctly', () => { - const nodes = Map(); - const ports = Map(); - const links = Map(); + const nodes: NodeRecordMap = {}; + const ports: PortRecordMap = {}; + const links: LinkRecordMap = {}; const tree = renderer .create( ({ - nodes: get(state, ownProps.reduxMountPoint).get('nodes'), - links: get(state, ownProps.reduxMountPoint).get('links'), - ports: get(state, ownProps.reduxMountPoint).get('ports'), - transform: get(state, ownProps.reduxMountPoint).get('transform'), - transformToApply: get(state, ownProps.reduxMountPoint).get('transformToApply'), -}); +const mapStateToProps = (state: State, ownProps: Props) => { + const slice = get(state, ownProps.reduxMountPoint); + return { + nodes: slice.nodes, + links: slice.links, + ports: slice.ports, + transform: slice.transform, + transformToApply: slice.transformToApply, + }; +}; const mapDispatchToProps = (dispatch: any) => ({ - setNodeTypes: (nodeTypeMap: Map) => dispatch(setNodeTypes(nodeTypeMap)), + setNodeTypes: (nodeTypeMap: StateMap) => dispatch(setNodeTypes(nodeTypeMap)), startMoveNodeTo: (nodeId: Id, nodePosition: string) => dispatch(startMoveNodeTo(nodeId, nodePosition)), moveNodeTo: (nodeId: Id, nodePosition: Position) => dispatch(moveNodeTo(nodeId, nodePosition)), diff --git a/packages/flow-designer/src/components/link/LinksRenderer.component.tsx b/packages/flow-designer/src/components/link/LinksRenderer.component.tsx index 62c0199aeac..f554bbfd07c 100644 --- a/packages/flow-designer/src/components/link/LinksRenderer.component.tsx +++ b/packages/flow-designer/src/components/link/LinksRenderer.component.tsx @@ -9,13 +9,13 @@ type Props = { class LinksRender extends Component { render() { - const links = this.props.links.toArray(); + const links = Object.values(this.props.links); return ( {links.map(link => { const ConcreteLink = this.props.linkTypeMap[link.getLinkType()].component; - const source = this.props.ports.get(link.sourceId); - const target = this.props.ports.get(link.targetId); + const source = this.props.ports[link.sourceId]; + const target = this.props.ports[link.targetId]; return ; })} diff --git a/packages/flow-designer/src/components/link/LinksRenderer.test.tsx b/packages/flow-designer/src/components/link/LinksRenderer.test.tsx index cdef1674dd0..40ad0c52b80 100644 --- a/packages/flow-designer/src/components/link/LinksRenderer.test.tsx +++ b/packages/flow-designer/src/components/link/LinksRenderer.test.tsx @@ -1,51 +1,33 @@ -/* eslint-disable new-cap */ import renderer from 'react-test-renderer'; -import { Map, OrderedMap } from 'immutable'; import LinksRenderer from './LinksRenderer.component'; import { LinkRecord, PortRecord, PositionRecord } from '../../constants/flowdesigner.model'; -import { - Id, - LinkRecord as LinkRecordType, - PortRecord as PortRecordType, -} from '../../customTypings/index.d'; +import { LinkRecordMap, PortRecordMap } from '../../customTypings/index.d'; const MockLink = () => MockLink; describe('', () => { it('renders correctly', () => { - const links = Map().set( - 'id', - new LinkRecord({ + const links: LinkRecordMap = { + id: new LinkRecord({ id: 'id', sourceId: 'port1', targetId: 'port2', - graphicalAttributes: Map({ - linkType: 'id', - }), + graphicalAttributes: { linkType: 'id' }, }), - ); - const ports = OrderedMap() - .set( - 'port1', - new PortRecord({ - id: 'port1', - nodeId: 'nodeId', - graphicalAttributes: Map({ - position: new PositionRecord({ x: 100, y: 100 }), - }), - }), - ) - .set( - 'port2', - new PortRecord({ - id: 'port2', - nodeId: 'nodeId', - graphicalAttributes: Map({ - position: new PositionRecord({ x: 200, y: 200 }), - }), - }), - ); + }; + const ports: PortRecordMap = { + port1: new PortRecord({ + id: 'port1', + nodeId: 'nodeId', + graphicalAttributes: { position: new PositionRecord({ x: 100, y: 100 }) }, + }), + port2: new PortRecord({ + id: 'port2', + nodeId: 'nodeId', + graphicalAttributes: { position: new PositionRecord({ x: 200, y: 200 }) }, + }), + }; const linkTypeMap = { id: { id: 'id', component: MockLink } }; const tree = renderer .create() diff --git a/packages/flow-designer/src/components/node/AbstractNode.component.tsx b/packages/flow-designer/src/components/node/AbstractNode.component.tsx index 7a10bacff39..e483cf376f6 100644 --- a/packages/flow-designer/src/components/node/AbstractNode.component.tsx +++ b/packages/flow-designer/src/components/node/AbstractNode.component.tsx @@ -1,7 +1,6 @@ import { Component } from 'react'; import type { MouseEventHandler, MouseEvent } from 'react'; import { scaleLinear, drag, select } from 'd3'; -import { Map } from 'immutable'; import invariant from 'invariant'; @@ -29,18 +28,16 @@ function calculatePortPosition( nodePosition: PositionType, nodeSize: SizeType, ) { - let portsWithPosition = Map(); - const emitterPorts = ports.filter(port => Port.getTopology(port) === PORT_SOURCE); - const sinkPorts = ports.filter(port => Port.getTopology(port) === PORT_SINK); - const range = [ - Position.getYCoordinate(nodePosition), - Position.getYCoordinate(nodePosition) + Size.getHeight(nodeSize), - ]; + let portsWithPosition: Record = {}; + const emitterPorts = Object.values(ports).filter(port => Port.getTopology(port) === PORT_SOURCE); + const sinkPorts = Object.values(ports).filter(port => Port.getTopology(port) === PORT_SINK); + const yStart = Position.getYCoordinate(nodePosition) as number; + const range: [number, number] = [yStart, yStart + (Size.getHeight(nodeSize) as number)]; const scaleYEmitter = scaleLinear() - .domain([0, emitterPorts.size + 1]) + .domain([0, emitterPorts.length + 1]) .range(range); const scaleYSink = scaleLinear() - .domain([0, sinkPorts.size + 1]) + .domain([0, sinkPorts.length + 1]) .range(range); let emitterNumber = 0; let sinkNumber = 0; @@ -58,10 +55,13 @@ function calculatePortPosition( emitterNumber += 1; const position = Position.create( - Position.getXCoordinate(nodePosition) + Size.getWidth(nodeSize), - scaleYEmitter(emitterNumber), - ); - portsWithPosition = portsWithPosition.set(Port.getId(port), Port.setPosition(port, position)); + (Position.getXCoordinate(nodePosition) as number) + (Size.getWidth(nodeSize) as number), + scaleYEmitter(emitterNumber) as number, + ) as PositionType; + portsWithPosition = { + ...portsWithPosition, + [Port.getId(port)!]: Port.setPosition(position, port), + }; }); sinkPorts .sort((a, b) => { @@ -76,10 +76,13 @@ function calculatePortPosition( .forEach(port => { sinkNumber += 1; const position = Position.create( - Position.getXCoordinate(nodePosition), - scaleYSink(sinkNumber), - ); - portsWithPosition = portsWithPosition.set(Port.getId(port), Port.setPosition(port, position)); + Position.getXCoordinate(nodePosition) as number, + scaleYSink(sinkNumber) as number, + ) as PositionType; + portsWithPosition = { + ...portsWithPosition, + [Port.getId(port)!]: Port.setPosition(position, port), + }; }); return portsWithPosition; } @@ -120,7 +123,7 @@ class AbstractNode extends Component { componentDidMount() { this.d3Node = select(this.nodeElement); - this.d3Node.data([this.props.node.getPosition()]); + this.d3Node.data([Node.getPosition(this.props.node)]); this.d3Node.call( drag().on('start', this.onDragStart).on('drag', this.onDrag).on('end', this.onDragEnd), ); diff --git a/packages/flow-designer/src/components/node/NodesRenderer.component.tsx b/packages/flow-designer/src/components/node/NodesRenderer.component.tsx index 42677a5700c..51a51136c00 100644 --- a/packages/flow-designer/src/components/node/NodesRenderer.component.tsx +++ b/packages/flow-designer/src/components/node/NodesRenderer.component.tsx @@ -41,7 +41,7 @@ class NodesRenderer extends Component { } render() { - return {this.props.nodes.toArray().map(this.renderNode)}; + return {Object.values(this.props.nodes).map(this.renderNode)}; } } diff --git a/packages/flow-designer/src/components/node/NodesRenderer.test.tsx b/packages/flow-designer/src/components/node/NodesRenderer.test.tsx index 13b2d5d00e1..2af888114e9 100644 --- a/packages/flow-designer/src/components/node/NodesRenderer.test.tsx +++ b/packages/flow-designer/src/components/node/NodesRenderer.test.tsx @@ -1,5 +1,4 @@ import renderer from 'react-test-renderer'; -import { List, Map } from 'immutable'; import NodesRenderer from './NodesRenderer.component'; import { @@ -7,7 +6,7 @@ import { NodeRecord, NodeGraphicalAttributes, } from '../../constants/flowdesigner.model'; -import { NodeRecord as NodeRecordType } from '../../customTypings/index.d'; +import { NodeRecordMap } from '../../customTypings/index.d'; const MockNode = () => MockNodes; @@ -15,16 +14,15 @@ const noOp = () => {}; describe('', () => { it('renders correctly', () => { - const nodes = Map().set( - 'id', - new NodeRecord({ + const nodes: NodeRecordMap = { + id: new NodeRecord({ id: 'id', type: 'id', graphicalAttributes: new NodeGraphicalAttributes({ nodeType: 'id', }), }), - ); + }; const nodeTypeMap = { id: { id: 'id', component: MockNode } }; const tree = renderer .create( @@ -41,17 +39,16 @@ describe('', () => { expect(tree).toMatchSnapshot(); }); it('renders correctly if nested', () => { - const nodes = Map().set( - 'id', - new NestedNodeRecord({ + const nodes: NodeRecordMap = { + id: new NestedNodeRecord({ id: 'id', type: 'id', graphicalAttributes: new NodeGraphicalAttributes({ nodeType: 'id', }), - components: List(), + components: {}, }), - ); + }; const nodeTypeMap = { id: { id: 'id', component: MockNode } }; const tree = renderer .create( diff --git a/packages/flow-designer/src/components/port/PortsRenderer.component.tsx b/packages/flow-designer/src/components/port/PortsRenderer.component.tsx index 9861a8aafda..252e0e7aa1a 100644 --- a/packages/flow-designer/src/components/port/PortsRenderer.component.tsx +++ b/packages/flow-designer/src/components/port/PortsRenderer.component.tsx @@ -10,7 +10,7 @@ function PortsRenderer({ ports, portTypeMap }: { ports: PortRecordMap; portTypeM return ; }; - return {ports.toArray().map(renderPort)}; + return {Object.values(ports).map(renderPort)}; } export default PortsRenderer; diff --git a/packages/flow-designer/src/components/port/PortsRenderer.test.tsx b/packages/flow-designer/src/components/port/PortsRenderer.test.tsx index 301c24d02ff..aa836795128 100644 --- a/packages/flow-designer/src/components/port/PortsRenderer.test.tsx +++ b/packages/flow-designer/src/components/port/PortsRenderer.test.tsx @@ -1,24 +1,20 @@ import renderer from 'react-test-renderer'; -import { Map } from 'immutable'; import PortsRenderer from './PortsRenderer.component'; import { PortRecord } from '../../constants/flowdesigner.model'; -import { Id, PortRecord as PortRecordType } from '../../customTypings/index.d'; +import { PortRecordMap } from '../../customTypings/index.d'; const MockPort = () => MockPort; describe('', () => { it('renders correctly', () => { - const ports = Map().set( - 'id', - new PortRecord({ + const ports: PortRecordMap = { + id: new PortRecord({ id: 'id', nodeId: 'nodeId', - graphicalAttributes: Map({ - portType: 'id', - }), + graphicalAttributes: { portType: 'id' }, }), - ); + }; const portTypeMap = { id: { id: 'id', diff --git a/packages/flow-designer/src/constants/flowdesigner.model.ts b/packages/flow-designer/src/constants/flowdesigner.model.ts index fda0409af81..d4bea60d933 100644 --- a/packages/flow-designer/src/constants/flowdesigner.model.ts +++ b/packages/flow-designer/src/constants/flowdesigner.model.ts @@ -1,176 +1,562 @@ /* eslint-disable new-cap */ -import { Record, Map, List } from 'immutable'; -import { - Size, - Position, - PortDirection, - PortRecord as PortRecordType, -} from '../customTypings/index.d'; +import { Size, Position, PortDirection } from '../customTypings/index.d'; export const NONE = 'NONE'; export const SELECTED = 'SELECTED'; export const DROP_TARGET = 'DROP_TARGET'; export const FORBIDDEN_DROP_TARGET = 'FORBIDDEN_DROP_TARGET'; -export const PositionRecord = Record({ - x: undefined, - y: undefined, -}); +function getInHelper(obj: any, path: string[]): any { + return path.reduce((v: any, k) => { + if (v == null) return v; + return typeof v.get === 'function' ? v.get(k) : v[k]; + }, obj); +} -export const SizeRecord = Record({ - width: undefined, - height: undefined, -}); +function setInHelper(obj: any, path: string[], value: any): any { + if (path.length === 0) return obj; + if (path.length === 1) { + if (typeof obj.set === 'function') return obj.set(path[0], value); + return { ...obj, [path[0]]: value }; + } + const current = typeof obj.get === 'function' ? obj.get(path[0]) : obj[path[0]]; + const updated = + current != null && typeof current.setIn === 'function' + ? current.setIn(path.slice(1), value) + : setInHelper(current ?? {}, path.slice(1), value); + if (typeof obj.set === 'function') return obj.set(path[0], updated); + return { ...obj, [path[0]]: updated }; +} + +export class PositionRecord { + readonly x: number | undefined; + readonly y: number | undefined; + + constructor({ x, y }: { x?: number; y?: number } = {}) { + this.x = x; + this.y = y; + } + + get(key: string): any { + return (this as any)[key]; + } + + set(key: string, value: any): PositionRecord { + return new PositionRecord({ ...this, [key]: value }); + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): PositionRecord { + return setInHelper(this, path, value) as PositionRecord; + } + + toJS(): Position { + return { x: this.x as number, y: this.y as number }; + } + + toJSON(): Position { + return this.toJS(); + } +} + +export class SizeRecord { + readonly width: number | undefined; + readonly height: number | undefined; + + constructor({ width, height }: { width?: number; height?: number } = {}) { + this.width = width; + this.height = height; + } + + get(key: string): any { + return (this as any)[key]; + } + + set(key: string, value: any): SizeRecord { + return new SizeRecord({ ...this, [key]: value }); + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): SizeRecord { + return setInHelper(this, path, value) as SizeRecord; + } + + toJS(): Size { + return { width: this.width as number, height: this.height as number }; + } + + toJSON(): Size { + return this.toJS(); + } +} /** TO BE REMOVED */ -export const NodeGraphicalAttributes = Record({ - position: new PositionRecord(), - nodeSize: new SizeRecord(), - nodeType: undefined, - label: '', - description: '', - properties: Map(), -}); +export class NodeGraphicalAttributes { + [key: string]: any; + + constructor(params: any = {}) { + // Default for properties so nested setIn paths (properties.startPosition) work + this.properties = {}; + if (params instanceof NodeGraphicalAttributes) { + // Shallow copy: preserves PositionRecord, Map instances etc. + Object.assign(this, params); + } else { + const data = + params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; + Object.assign(this, data); + } + } + + get(key: string): any { + return this[key]; + } + + set(key: string, value: any): NodeGraphicalAttributes { + const nga = new NodeGraphicalAttributes(this); + nga[key] = value; + return nga; + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): NodeGraphicalAttributes { + return setInHelper(this, path, value) as NodeGraphicalAttributes; + } + + merge(values: any): NodeGraphicalAttributes { + const data = values != null && typeof values.toJS === 'function' ? values.toJS() : values || {}; + const nga = new NodeGraphicalAttributes(this); + Object.assign(nga, data); + return nga; + } + + delete(key: string): NodeGraphicalAttributes { + const nga = new NodeGraphicalAttributes(this); + delete nga[key]; + return nga; + } + + deleteIn(path: string[]): NodeGraphicalAttributes { + if (path.length === 0) return this; + if (path.length === 1) return this.delete(path[0]); + const current = this.get(path[0]); + const updated = + current != null && typeof current.deleteIn === 'function' + ? current.deleteIn(path.slice(1)) + : current; + return this.set(path[0], updated); + } + + toJS(): any { + const result: any = {}; + for (const key of Object.keys(this)) { + const val = this[key]; + result[key] = val && typeof val.toJS === 'function' ? val.toJS() : val; + } + return result; + } +} /** TO BE REMOVED */ -export const NodeData = Record({ - properties: Map(), - label: '', - description: '', - datasetInfo: Map(), -}); +export class NodeData { + [key: string]: any; + + constructor(params: any = {}) { + const data = params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; + Object.assign(this, data); + } + + get(key: string): any { + return this[key]; + } + + set(key: string, value: any): NodeData { + const nd = new NodeData(this); + nd[key] = value; + return nd; + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): NodeData { + return setInHelper(this, path, value) as NodeData; + } +} /** TO BE REMOVED */ -export const LinkGraphicalAttributes = Record({ - linkType: undefined, - properties: Map(), -}); +export class LinkGraphicalAttributes { + [key: string]: any; + + constructor(params: any = {}) { + this.properties = {}; + if (params instanceof LinkGraphicalAttributes) { + Object.assign(this, params); + } else { + const data = + params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; + Object.assign(this, data); + } + } + + get(key: string): any { + return this[key]; + } + + set(key: string, value: any): LinkGraphicalAttributes { + const lga = new LinkGraphicalAttributes(this); + lga[key] = value; + return lga; + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): LinkGraphicalAttributes { + return setInHelper(this, path, value) as LinkGraphicalAttributes; + } +} /** TO BE REMOVED */ -export const LinkData = Record({ - properties: Map(), -}); +export class LinkData { + [key: string]: any; + + constructor(params: any = {}) { + const data = params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; + Object.assign(this, data); + } + + get(key: string): any { + return this[key]; + } + + set(key: string, value: any): LinkData { + const ld = new LinkData(this); + ld[key] = value; + return ld; + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): LinkData { + return setInHelper(this, path, value) as LinkData; + } +} /** TO BE REMOVED */ -export const PortGraphicalAttributes = Record({ - position: PositionRecord, - portType: undefined, - properties: Map(), -}); +export class PortGraphicalAttributes { + [key: string]: any; + + constructor(params: any = {}) { + this.properties = {}; + if (params instanceof PortGraphicalAttributes) { + Object.assign(this, params); + } else { + const data = + params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; + Object.assign(this, data); + } + } + + get(key: string): any { + return this[key]; + } + + set(key: string, value: any): PortGraphicalAttributes { + const pga = new PortGraphicalAttributes(this); + pga[key] = value; + return pga; + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): PortGraphicalAttributes { + return setInHelper(this, path, value) as PortGraphicalAttributes; + } +} /** TO BE REMOVED */ -export const PortData = Record({ - properties: Map(), - flowType: undefined, -}); - -const nodeRecordDefinition = { - id: undefined, - type: undefined, - data: Map({ - properties: Map(), - label: '', - description: '', - datasetInfo: Map(), - }), - graphicalAttributes: Map({ - position: new PositionRecord(), - nodeSize: new SizeRecord(), - nodeType: undefined, - label: '', - description: '', - properties: Map(), - }), -}; - -export class NodeRecord extends Record({ - ...nodeRecordDefinition, - getPosition(): Position { - return this.getIn(['graphicalAttributes', 'position']); - }, +export class PortData { + [key: string]: any; - getSize(): Size { - return this.getIn(['graphicalAttributes', 'nodeSize']); - }, + constructor(params: any = {}) { + const data = params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; + Object.assign(this, data); + } - getNodeType(): string { - return this.getIn(['graphicalAttributes', 'nodeType']); - }, -}) {} - -export class NestedNodeRecord extends Record({ - ...nodeRecordDefinition, - components: List(), - getComponents(): Map { - return this.get('components'); - }, - setComponents(components: Map) { - return this.set('components', components); - }, + get(key: string): any { + return this[key]; + } + + set(key: string, value: any): PortData { + const pd = new PortData(this); + pd[key] = value; + return pd; + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): PortData { + return setInHelper(this, path, value) as PortData; + } +} + +export class NodeRecord { + readonly id: string | undefined; + readonly type: string | undefined; + readonly data: any; + readonly graphicalAttributes: any; + + constructor({ + id, + type, + data, + graphicalAttributes, + }: { + id?: string; + type?: string; + data?: any; + graphicalAttributes?: any; + } = {}) { + this.id = id; + this.type = type; + this.data = data ?? new NodeData(); + // Default graphicalAttributes as NodeGraphicalAttributes for API compatibility + this.graphicalAttributes = graphicalAttributes ?? new NodeGraphicalAttributes(); + } + + get(key: string): any { + return (this as any)[key]; + } + + set(key: string, value: any): NodeRecord { + return new NodeRecord({ ...this, [key]: value }); + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): NodeRecord { + return setInHelper(this, path, value) as NodeRecord; + } + + /** methods TO BE REMOVED */ getPosition(): Position { - return this.getIn(['graphicalAttributes', 'position']); - }, + const pos = this.graphicalAttributes?.get + ? this.graphicalAttributes.get('position') + : this.graphicalAttributes?.position; + return pos; + } getSize(): Size { - return this.getIn(['graphicalAttributes', 'nodeSize']); - }, + const size = this.graphicalAttributes?.get + ? this.graphicalAttributes.get('nodeSize') + : this.graphicalAttributes?.nodeSize; + return size; + } getNodeType(): string { - return this.getIn(['graphicalAttributes', 'nodeType']); - }, -}) {} - -export const LinkRecord = Record({ - id: undefined, - sourceId: undefined, - targetId: undefined, - data: Map({ - properties: Map(), - }), - graphicalAttributes: Map({ - linkType: undefined, - properties: Map(), - }), + const nt = this.graphicalAttributes?.get + ? this.graphicalAttributes.get('nodeType') + : this.graphicalAttributes?.nodeType; + return nt; + } +} + +export class NestedNodeRecord extends NodeRecord { + readonly components: any; + + constructor( + params: { + id?: string; + type?: string; + data?: any; + graphicalAttributes?: any; + components?: any; + } = {}, + ) { + super(params); + this.components = params.components ?? {}; + } + + set(key: string, value: any): NestedNodeRecord { + return new NestedNodeRecord({ ...this, [key]: value }); + } + + setIn(path: string[], value: any): NestedNodeRecord { + return setInHelper(this, path, value) as NestedNodeRecord; + } + + /** methods TO BE REMOVED */ + getComponents(): any { + return this.components; + } + + setComponents(components: any): NestedNodeRecord { + return new NestedNodeRecord({ ...this, components }); + } +} + +export class LinkRecord { + readonly id: string | undefined; + readonly sourceId: string | undefined; + readonly targetId: string | undefined; + readonly data: any; + readonly graphicalAttributes: any; + + constructor({ + id, + sourceId, + targetId, + data, + graphicalAttributes, + }: { + id?: string; + sourceId?: string; + targetId?: string; + data?: any; + graphicalAttributes?: any; + } = {}) { + this.id = id; + this.sourceId = sourceId; + this.targetId = targetId; + this.data = data ?? new LinkData(); + this.graphicalAttributes = graphicalAttributes ?? new LinkGraphicalAttributes(); + } + + get(key: string): any { + return (this as any)[key]; + } + + set(key: string, value: any): LinkRecord { + return new LinkRecord({ ...this, [key]: value }); + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): LinkRecord { + return setInHelper(this, path, value) as LinkRecord; + } /** methods TO BE REMOVED */ getLinkType(): string { - return this.getIn(['graphicalAttributes', 'linkType']); - }, -}); - -export const PortRecord = Record({ - id: undefined, - nodeId: undefined, - data: Map({ - properties: Map(), - flowType: undefined, - }), - graphicalAttributes: Map({ - position: PositionRecord, - portType: undefined, - properties: Map(), - }), + const lt = this.graphicalAttributes?.get + ? this.graphicalAttributes.get('linkType') + : this.graphicalAttributes?.linkType; + return lt; + } +} + +export class PortRecord { + readonly id: string | undefined; + readonly nodeId: string | undefined; + readonly data: any; + readonly graphicalAttributes: any; + + constructor({ + id, + nodeId, + data, + graphicalAttributes, + }: { + id?: string; + nodeId?: string; + data?: any; + graphicalAttributes?: any; + } = {}) { + this.id = id; + this.nodeId = nodeId; + this.data = data ?? new PortData(); + this.graphicalAttributes = graphicalAttributes ?? new PortGraphicalAttributes(); + } + + get(key: string): any { + return (this as any)[key]; + } + + set(key: string, value: any): PortRecord { + return new PortRecord({ ...this, [key]: value }); + } + + getIn(path: string[]): any { + return getInHelper(this, path); + } + + setIn(path: string[], value: any): PortRecord { + return setInHelper(this, path, value) as PortRecord; + } /** methods TO BE REMOVED */ getPosition(): Position { - return this.getIn(['graphicalAttributes', 'position']); - }, - setPosition(position: Position): PortRecordType { - return this.setIn(['graphicalAttributes', 'position'], position); - }, + const pos = this.graphicalAttributes?.get + ? this.graphicalAttributes.get('position') + : this.graphicalAttributes?.position; + return pos; + } + + setPosition(position: Position): PortRecord { + return new PortRecord({ + ...this, + graphicalAttributes: this.graphicalAttributes?.set + ? this.graphicalAttributes.set('position', position) + : { ...this.graphicalAttributes, position }, + }); + } + getPortType(): string { - return this.getIn(['graphicalAttributes', 'portType']); - }, + const pt = this.graphicalAttributes?.get + ? this.graphicalAttributes.get('portType') + : this.graphicalAttributes?.portType; + return pt; + } + getPortDirection(): PortDirection { - return this.getIn(['graphicalAttributes', 'properties', 'type']); - }, + const direction = this.graphicalAttributes?.getIn + ? this.graphicalAttributes.getIn(['properties', 'type']) + : this.graphicalAttributes?.properties?.type; + return direction; + } + getPortFlowType(): string { - return this.getIn(['data', 'flowType']); - }, + const ft = this.data?.get ? this.data.get('flowType') : this.data?.flowType; + return ft; + } + getIndex(): number { - return this.getIn(['graphicalAttributes', 'properties', 'index']); - }, - setIndex(index: number): PortRecordType { - return this.setIn(['graphicalAttributes', 'properties', 'index'], index); - }, -}); + const idx = this.graphicalAttributes?.getIn + ? this.graphicalAttributes.getIn(['properties', 'index']) + : this.graphicalAttributes?.properties?.index; + return idx; + } + + setIndex(index: number): PortRecord { + return new PortRecord({ + ...this, + graphicalAttributes: this.graphicalAttributes?.setIn + ? this.graphicalAttributes.setIn(['properties', 'index'], index) + : { + ...this.graphicalAttributes, + properties: { ...this.graphicalAttributes?.properties, index }, + }, + }); + } +} diff --git a/packages/flow-designer/src/constants/flowdesigner.proptypes.ts b/packages/flow-designer/src/constants/flowdesigner.proptypes.ts index 83005e828a6..0f04d68a91a 100644 --- a/packages/flow-designer/src/constants/flowdesigner.proptypes.ts +++ b/packages/flow-designer/src/constants/flowdesigner.proptypes.ts @@ -1,24 +1,23 @@ import PropTypes from 'prop-types'; -import { recordOf } from 'react-immutable-proptypes'; -export const NodeType = recordOf({ +export const NodeType = PropTypes.shape({ id: PropTypes.string.isRequired, - position: recordOf({ + position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), }); -export const PortType = recordOf({ +export const PortType = PropTypes.shape({ id: PropTypes.string.isRequired, nodeId: PropTypes.string.isRequired, - position: recordOf({ + position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), }); -export const LinkType = recordOf({ +export const LinkType = PropTypes.shape({ id: PropTypes.string.isRequired, sourceId: PropTypes.string.isRequired, targetId: PropTypes.string.isRequired, diff --git a/packages/flow-designer/src/customTypings/index.d.ts b/packages/flow-designer/src/customTypings/index.d.ts index 43f1957527d..b2969a9ee8f 100644 --- a/packages/flow-designer/src/customTypings/index.d.ts +++ b/packages/flow-designer/src/customTypings/index.d.ts @@ -1,4 +1,3 @@ -import { Record, Map } from 'immutable'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; /** $BASIC */ @@ -64,7 +63,7 @@ export interface Node { type: string; data: NodeData; graphicalAttributes: NodeGraphicalAttributes; - components?: List; + components?: Record; } export interface LinkGraphicalAttributes { @@ -85,60 +84,60 @@ export interface Link { } /** $RECORDS */ -export type PositionRecord = Record & Position; - -export type SizeRecord = Record & Size; - -export type PortRecord = Record & { - getPosition: () => Position; - getPortType: () => string; - getPortDirection: () => PortDirection; - getPortFlowType: () => string; - getIndex: () => number; - setIndex: (index: number) => PortRecord; -} & Port; - -// TODO add record -export type NodeRecord = Record & { - getPosition: () => Position; - getSize: () => Size; - getNodeType: () => string; -} & Node; - -export type NestedNodeRecord = Record & { - getPosition: () => Position; - getSize: () => Size; - getNodeType: () => string; -} & Node; - -export type LinkRecord = Record & { - getLinkType: () => string; -} & Link; +export interface PositionRecord { + readonly x: number | undefined; + readonly y: number | undefined; +} -/** $STATE */ +export interface SizeRecord { + readonly width: number | undefined; + readonly height: number | undefined; +} + +export interface PortRecord { + readonly id: Id; + readonly nodeId: Id; + readonly data: Record; + readonly graphicalAttributes: Record; +} + +export interface NodeRecord { + readonly id: Id; + readonly type: string; + readonly data: Record; + readonly graphicalAttributes: any; +} + +export interface NestedNodeRecord extends NodeRecord { + readonly components: any; +} -export type PortRecordMap = Map; -export type NodeRecordMap = Map; -export type LinkRecordMap = Map; +export interface LinkRecord { + readonly id: Id; + readonly sourceId: Id; + readonly targetId: Id; + readonly data: Record; + readonly graphicalAttributes: Record; +} + +/** $STATE */ -type GetStateNodes = (selector: ['nodes', Id]) => NodeRecord; -type GetStatePorts = (selector: ['ports', Id]) => PortRecord; -type GetStateLinks = (selector: ['links', Id]) => LinkRecord; -type GetStateIn = (selector: ['in', Id]) => Id; -type GetStateOut = (selector: ['out', Id]) => Id; +export type PortRecordMap = Record; +export type NodeRecordMap = Record; +export type LinkRecordMap = Record; export type State = { - in: Map>; - parents: Map>; + in: Record>>; + parents: Record>; transform: Transform; transformToApply?: Transform; - out: Map>; - nodes: Map>; - ports: Map>; - children: Map>; - nodeTypes: Map>; - links: Map>; -} & Map & { getIn: getStateNodes | getStatePorts | getStateLinks | getStateIn | getStateOut }; + out: Record>>; + nodes: Record; + ports: Record; + childrens: Record>; + nodeTypes: Record; + links: Record; +}; /** $ACTIONS */ export interface PortActionAdd { diff --git a/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap index f21939035b6..de9872a900b 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap @@ -1,291 +1,247 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`FLOWDESIGNER_FLOW_ADD_ELEMENTS > should batch many elements creation 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "nodeId": Immutable.Map {}, - "node2": Immutable.Map {}, - }, - "transformToApply": undefined, - "parents": Immutable.Map { - "nodeId": Immutable.Map {}, - "node2": Immutable.Map {}, - }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "out": Immutable.Map { - "nodeId": Immutable.Map { - "portId": Immutable.Map {}, - }, - "node2": Immutable.Map {}, - }, - "nodes": Immutable.Map { - "nodeId": Immutable.Record { - "id": "nodeId", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, +{ + "childrens": { + "node2": {}, + "nodeId": {}, + }, + "in": { + "node2": {}, + "nodeId": {}, + }, + "links": {}, + "nodeTypes": {}, + "nodes": { + "node2": NodeRecord { + "data": NodeData { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": 10, + "width": 10, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": 10, - "height": 10, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + "properties": {}, }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], - }, - "node2": Immutable.Record { "id": "node2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, + }, + "nodeId": NodeRecord { + "data": NodeData { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": 10, + "width": 10, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": 10, - "height": 10, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + "properties": {}, }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "id": "nodeId", + "type": undefined, }, }, - "ports": Immutable.Map { - "portId": Immutable.Record { - "id": "portId", - "nodeId": "nodeId", - "data": Immutable.Map { + "out": { + "node2": {}, + "nodeId": { + "portId": {}, + }, + }, + "parents": { + "node2": {}, + "nodeId": {}, + }, + "ports": { + "portId": PortRecord { + "data": PortData { "flowType": "batch", - "properties": Immutable.Map {}, + "properties": {}, }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { - "index": 0, - "type": "OUTGOING", - }, - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": undefined, "y": undefined, }, + "properties": { + "index": 0, + "type": "OUTGOING", + }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "portId", + "nodeId": "nodeId", }, }, - "childrens": Immutable.Map { - "nodeId": Immutable.Map {}, - "node2": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`FLOWDESIGNER_FLOW_ADD_ELEMENTS > should batch one element creation 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "nodeId": Immutable.Map {}, - }, - "transformToApply": undefined, - "parents": Immutable.Map { - "nodeId": Immutable.Map {}, - }, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map { - "nodeId": Immutable.Map {}, - }, - "nodes": Immutable.Map { - "nodeId": Immutable.Record { - "id": "nodeId", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`FLOWDESIGNER_FLOW_ADD_ELEMENTS > should batch one element creation 1`] = ` +{ + "childrens": { + "nodeId": {}, + }, + "in": { + "nodeId": {}, + }, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": NodeRecord { + "data": NodeData { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": 10, + "width": 10, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": 10, - "height": 10, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + "properties": {}, }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "id": "nodeId", + "type": undefined, }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map { - "nodeId": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`FLOWDESIGNER_FLOW_LOAD should reset old flow state and load news not touching flow config > should load elements 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "nodeId": Immutable.Map {}, - "node2": Immutable.Map {}, + "out": { + "nodeId": {}, }, - "transformToApply": undefined, - "parents": Immutable.Map { - "nodeId": Immutable.Map {}, - "node2": Immutable.Map {}, + "parents": { + "nodeId": {}, }, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map { - "nodeId": Immutable.Map { - "portId": Immutable.Map {}, - }, - "node2": Immutable.Map {}, - }, - "nodes": Immutable.Map { - "nodeId": Immutable.Record { - "id": "nodeId", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`FLOWDESIGNER_FLOW_LOAD should reset old flow state and load news not touching flow config > should load elements 1`] = ` +{ + "childrens": { + "node2": {}, + "nodeId": {}, + }, + "in": { + "node2": {}, + "nodeId": {}, + }, + "links": {}, + "nodeTypes": {}, + "nodes": { + "node2": NodeRecord { + "data": NodeData { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": 10, + "width": 10, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": 10, - "height": 10, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + "properties": {}, }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], - }, - "node2": Immutable.Record { "id": "node2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, + }, + "nodeId": NodeRecord { + "data": NodeData { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": 10, + "width": 10, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": 10, - "height": 10, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + "properties": {}, }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "id": "nodeId", + "type": undefined, }, }, - "ports": Immutable.Map { - "portId": Immutable.Record { - "id": "portId", - "nodeId": "nodeId", - "data": Immutable.Map { - "properties": Immutable.Map {}, + "out": { + "node2": {}, + "nodeId": { + "portId": {}, + }, + }, + "parents": { + "node2": {}, + "nodeId": {}, + }, + "ports": { + "portId": PortRecord { + "data": PortData { + "properties": {}, }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { - "index": 0, - "type": "OUTGOING", - }, - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": undefined, "y": undefined, }, + "properties": { + "index": 0, + "type": "OUTGOING", + }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "portId", + "nodeId": "nodeId", }, }, - "childrens": Immutable.Map { - "nodeId": Immutable.Map {}, - "node2": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, + "transformToApply": undefined, } `; exports[`FLOWDESIGNER_PAN_TO set a calculated transformation into transformToApply > 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": Transform { - "k": 1, - "x": -400, - "y": -400, - }, - "parents": Immutable.Map {}, +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map {}, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, + "transformToApply": Transform { + "k": 1, + "x": -400, + "y": -400, + }, } `; diff --git a/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap index 7fc45954769..c75e0256b19 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap @@ -1,2312 +1,1137 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`check linkreducer > FLOWDESIGNER_LINK_ADD should add a new link to the state 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "id2": Immutable.Map { - "id2": Immutable.Map { +{ + "childrens": { + "id1": { + "id2": "id2", + }, + "id2": {}, + "id3": {}, + }, + "in": { + "id2": { + "id2": { "id2": "id2", }, }, }, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", + "links": { + "id1": LinkRecord { + "data": LinkData { + "attr": "attr", + }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { + "attr": "attr", + }, + }, + "id": "id1", + "sourceId": "id1", + "targetId": "id2", }, - "id3": Immutable.Map {}, - }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "out": Immutable.Map { - "id1": Immutable.Map { - "id1": Immutable.Map { - "id2": "id2", + "id2": LinkRecord { + "data": LinkData { + "properties": {}, }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": {}, + }, + "id": "id2", + "sourceId": "id1", + "targetId": "id2", }, }, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + }, + }, + "out": { + "id1": { + "id1": { + "id2": "id2", }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { + }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "nodeId": "id3", }, }, - "childrens": Immutable.Map { - "id1": Immutable.Map { + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_ADD should add a new link to the state 2`] = ` +{ + "childrens": { + "id1": { "id2": "id2", }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, + "id2": { + "id3": "id3", + }, + "id3": {}, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Map { + "in": { + "id2": { + "id2": { + "id2": "id2", + }, + }, + "id3": { + "id5": { + "id3": "id3", + }, + }, + }, + "links": { + "id1": LinkRecord { + "data": LinkData { "attr": "attr", }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { "attr": "attr", }, }, - "getLinkType": [Function], + "id": "id1", + "sourceId": "id1", + "targetId": "id2", }, - "id2": Immutable.Record { + "id2": LinkRecord { + "data": LinkData { + "properties": {}, + }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": {}, + }, "id": "id2", "sourceId": "id1", "targetId": "id2", - "data": Immutable.Record { - "properties": Immutable.Map {}, + }, + "id3": LinkRecord { + "data": LinkData { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "linkType": undefined, - "properties": Immutable.Map {}, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": {}, }, - "getLinkType": [Function], + "id": "id3", + "sourceId": "id3", + "targetId": "id5", }, }, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_ADD should add a new link to the state 2`] = ` -Immutable.Map { - "in": Immutable.Map { - "id2": Immutable.Map { - "id2": Immutable.Map { - "id2": "id2", + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, }, + "id": "id1", + "type": undefined, }, - "id3": Immutable.Map { - "id5": Immutable.Map { - "id3": "id3", + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, }, + "id": "id2", + "type": undefined, }, - }, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", - }, - "id3": Immutable.Map { - "id2": "id2", + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, + "id": "id3", + "type": undefined, }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "out": Immutable.Map { - "id1": Immutable.Map { - "id1": Immutable.Map { + "out": { + "id1": { + "id1": { "id2": "id2", }, }, - "id2": Immutable.Map { - "id3": Immutable.Map { + "id2": { + "id3": { "id3": "id3", }, }, }, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], - }, - "id2": Immutable.Record { - "id": "id2", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "parents": { + "id1": {}, + "id2": { + "id1": "id1", }, - "id3": Immutable.Record { - "id": "id3", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "id3": { + "id2": "id2", }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { - "id": "id4", - "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { - "id": "id5", - "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { - "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - }, - "childrens": Immutable.Map { - "id1": Immutable.Map { - "id2": "id2", - }, - "id2": Immutable.Map { - "id3": "id3", }, - "id3": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Map { - "attr": "attr", + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { - "attr": "attr", - }, - }, - "getLinkType": [Function], + "id": "id4", + "nodeId": "id1", }, - "id2": Immutable.Record { - "id": "id2", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Record { - "properties": Immutable.Map {}, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "graphicalAttributes": Immutable.Record { - "linkType": undefined, - "properties": Immutable.Map {}, - }, - "getLinkType": [Function], + "id": "id5", + "nodeId": "id3", }, - "id3": Immutable.Record { - "id": "id3", - "sourceId": "id3", - "targetId": "id5", - "data": Immutable.Record { - "properties": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Record { - "linkType": undefined, - "properties": Immutable.Map {}, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "getLinkType": [Function], + "id": "id6", + "nodeId": "id3", }, }, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE should remove link from state 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, - }, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "transformToApply": undefined, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE should remove link from state 1`] = ` +{ + "childrens": { + "id1": {}, + "id2": {}, + "id3": {}, + }, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "out": {}, + "parents": { + "id1": {}, + "id2": {}, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { + }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "nodeId": "id3", }, }, - "childrens": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, + "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE_DATA should remove 'attr'from data map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", +{ + "childrens": { + "id1": { + "id2": "id2", }, - "id3": Immutable.Map {}, + "id2": {}, + "id3": {}, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "in": {}, + "links": { + "id1": LinkRecord { + "data": LinkData { + "attr": "attr", + }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { + "attr": "attr", + }, + }, + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + }, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "out": {}, + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { - "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], }, - }, - "childrens": Immutable.Map { - "id1": Immutable.Map { - "id2": "id2", - }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Map { - "attr": "attr", - }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { - "attr": "attr", - }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "getLinkType": [Function], + "id": "id6", + "nodeId": "id3", }, }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE_GRAPHICAL_ATTRIBUTES should remove a specific attributes from attr map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", +{ + "childrens": { + "id1": { + "id2": "id2", }, - "id3": Immutable.Map {}, + "id2": {}, + "id3": {}, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "in": {}, + "links": { + "id1": LinkRecord { + "data": LinkData { + "attr": "attr", + }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": {}, + }, + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + }, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "out": {}, + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { - "id": "id5", - "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { - "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - }, - "childrens": Immutable.Map { - "id1": Immutable.Map { - "id2": "id2", }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Map { - "attr": "attr", + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map {}, + "id": "id5", + "nodeId": "id3", + }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "getLinkType": [Function], + "id": "id6", + "nodeId": "id3", }, }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_SET_DATA should add a data attribute type: 'test' from data map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", +{ + "childrens": { + "id1": { + "id2": "id2", }, - "id3": Immutable.Map {}, + "id2": {}, + "id3": {}, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "in": {}, + "links": { + "id1": LinkRecord { + "data": LinkData { + "attr": "attr", + "type": "test", + }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { + "attr": "attr", + }, + }, + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + }, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "out": {}, + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { + }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "nodeId": "id3", }, }, - "childrens": Immutable.Map { - "id1": Immutable.Map { + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES should merge attributes within link attr property 1`] = ` +{ + "childrens": { + "id1": { "id2": "id2", }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, + "id2": {}, + "id3": {}, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Map { + "in": {}, + "links": { + "id1": LinkRecord { + "data": LinkData { "attr": "attr", - "type": "test", }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { "attr": "attr", + "selected": false, }, }, - "getLinkType": [Function], - }, - }, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES should merge attributes within link attr property 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", + "id": "id1", + "sourceId": "id1", + "targetId": "id2", }, - "id3": Immutable.Map {}, - }, - "transform": { - "k": 1, - "x": 0, - "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "out": {}, + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { + }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "nodeId": "id3", }, }, - "childrens": Immutable.Map { - "id1": Immutable.Map { + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_SET_SOURCE switch source to correct port if it exist 1`] = ` +{ + "childrens": { + "id1": { "id2": "id2", }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, + "id2": {}, + "id3": {}, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - "data": Immutable.Map { + "in": {}, + "links": { + "id1": LinkRecord { + "data": LinkData { "attr": "attr", }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { "attr": "attr", }, - "selected": false, }, - "getLinkType": [Function], - }, - }, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_SET_SOURCE switch source to correct port if it exist 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", + "id": "id1", + "sourceId": "id4", + "targetId": "id2", }, - "id3": Immutable.Map {}, - }, - "transform": { - "k": 1, - "x": 0, - "y": 0, }, - "out": Immutable.Map { - "id1": Immutable.Map { - "id4": Immutable.Map { - "id1": "id1", + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, }, - }, - }, - "nodes": Immutable.Map { - "id1": Immutable.Record { "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, + }, + }, + "out": { + "id1": { + "id4": { + "id1": "id1", }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { - "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - }, - "childrens": Immutable.Map { - "id1": Immutable.Map { - "id2": "id2", }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id4", - "targetId": "id2", - "data": Immutable.Map { - "attr": "attr", - }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { - "attr": "attr", - }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "getLinkType": [Function], + "id": "id6", + "nodeId": "id3", }, }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_SET_TARGET switch target to correct port if it exist 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "id3": Immutable.Map { - "id5": Immutable.Map { +{ + "childrens": { + "id1": { + "id3": "id3", + }, + "id2": {}, + "id3": {}, + }, + "in": { + "id3": { + "id5": { "id1": "id1", }, }, }, - "transformToApply": undefined, - "parents": Immutable.Map { - "id1": Immutable.Map {}, - "id2": Immutable.Map { - "id1": "id1", + "links": { + "id1": LinkRecord { + "data": LinkData { + "attr": "attr", + }, + "graphicalAttributes": LinkGraphicalAttributes { + "properties": { + "attr": "attr", + }, + }, + "id": "id1", + "sourceId": "id1", + "targetId": "id5", }, - "id3": Immutable.Map {}, - }, - "transform": { - "k": 1, - "x": 0, - "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id2": Immutable.Record { + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, - "id3": Immutable.Record { + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "properties": {}, + }, "id": "id3", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { - "x": undefined, - "y": undefined, - }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], }, }, - "ports": Immutable.Map { - "id1": Immutable.Record { + "out": {}, + "parents": { + "id1": {}, + "id2": { + "id1": "id1", + }, + "id3": {}, + }, + "ports": { + "id1": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id1", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id2": Immutable.Record { + }, + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id2", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id3", "nodeId": "id2", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id4": Immutable.Record { + }, + "id4": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id4", "nodeId": "id1", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id5": Immutable.Record { + }, + "id5": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, + }, "id": "id5", "nodeId": "id3", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id6": Immutable.Record { - "id": "id6", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": [Function], - "portType": undefined, - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - }, - "childrens": Immutable.Map { - "id1": Immutable.Map { - "id3": "id3", }, - "id2": Immutable.Map {}, - "id3": Immutable.Map {}, - }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "sourceId": "id1", - "targetId": "id5", - "data": Immutable.Map { - "attr": "attr", - }, - "graphicalAttributes": Immutable.Map { - "properties": Immutable.Map { - "attr": "attr", - }, + "id6": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "properties": {}, }, - "getLinkType": [Function], + "id": "id6", + "nodeId": "id3", }, }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; diff --git a/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap index 37225280d00..02ffccbc8c4 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap @@ -1,728 +1,559 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Check node reducer > FLOWDESIGNER_NODE_ADD add a new node to the node collection with the right type 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "id": Immutable.Map {}, +{ + "childrens": { + "id": {}, }, - "transformToApply": undefined, - "parents": Immutable.Map { - "id": Immutable.Map {}, - }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "out": Immutable.Map { - "id": Immutable.Map {}, + "in": { + "id": {}, }, - "nodes": Immutable.Map { - "id": Immutable.Record { - "id": "id", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { - "x": undefined, - "y": undefined, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id": NodeRecord { + "data": NodeData { + "properties": {}, + }, + "graphicalAttributes": NodeGraphicalAttributes { + "name": "test", + "nodePosition": { + "x": 10, + "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, + "nodeSize": { "height": undefined, + "width": undefined, + }, + "position": { + "x": undefined, + "y": undefined, }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "type": "MY_NODE_TYPE", + }, + "id": "id", + "type": undefined, }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map { - "id": Immutable.Map {}, + "out": { + "id": {}, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_ADD properly add a new node to the node collection 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "id": Immutable.Map {}, - }, - "transformToApply": undefined, - "parents": Immutable.Map { - "id": Immutable.Map {}, + "parents": { + "id": {}, }, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map { - "id": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_ADD properly add a new node to the node collection 1`] = ` +{ + "childrens": { + "id": {}, }, - "nodes": Immutable.Map { - "id": Immutable.Record { - "id": "id", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "in": { + "id": {}, + }, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id": NodeRecord { + "data": NodeData { + "properties": {}, + }, + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": undefined, + "width": undefined, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + }, + "id": "id", + "type": undefined, }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map { - "id": Immutable.Map {}, + "out": { + "id": {}, }, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_MOVE update node position 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "parents": { + "id": {}, + }, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "type1", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_MOVE update node position 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": true, + "type": "type1", + }, + "id": "id1", + "type": "type1", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 50, "y": 50, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE should remove node from node collection 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE should remove node from node collection 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_DATA should remove {type} attribute to node data map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "type1", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_DATA should remove {type} attribute to node data map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": true, + "type": "type1", + }, + "id": "id1", + "type": "type1", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES should remove {selected} attribute to node graphicalAttributes map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "type1", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES should remove {selected} attribute to node graphicalAttributes map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": true, + "type": "type1", + }, + "id": "id1", + "type": "type1", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_DATA should add { type: 'string' } attribute to node data map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "type1", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_DATA should add { type: 'string' } attribute to node data map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "string", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": true, + "type": "type1", + }, + "id": "id1", + "type": "type1", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES should add { selected: false } attribute to node graphicalAttributes map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "type1", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES should add { selected: false } attribute to node graphicalAttributes map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map { - "selected": false, - }, + "properties": {}, + "selected": false, + "type": "type1", }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "id": "id1", + "type": "type1", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_SIZE update node size property 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "type1", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_SIZE update node size property 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "nodeSize": { + "height": 200, + "width": 200, + }, + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": 200, - "height": 200, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": true, + "type": "type1", + }, + "id": "id1", + "type": "type1", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_TYPE update node type 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": "nodetype", - "data": Immutable.Map { + "transformToApply": undefined, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_TYPE update node type 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": true, + "type": "type1", + }, + "id": "id1", + "type": "nodetype", }, - "id2": Immutable.Record { - "id": "id2", - "type": "type2", - "data": Immutable.Map { + "id2": NodeRecord { + "data": NodeData { "type": "test", }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + "selected": false, + "type": "type2", + }, + "id": "id2", + "type": "type2", }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`FLOWDESIGNER_NODE_APPLY_MOVEMENT > should apply the same relative movement to each node listed 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "id1": Immutable.Record { - "id": "id1", - "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + "transformToApply": undefined, +} +`; + +exports[`FLOWDESIGNER_NODE_APPLY_MOVEMENT > should apply the same relative movement to each node listed 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "id1": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 20, "y": 15, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], - }, - "id2": Immutable.Record { - "id": "id2", + "properties": {}, + }, + "id": "id1", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + }, + "id2": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 20, "y": 15, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], - }, - "id3": Immutable.Record { - "id": "id3", + "properties": {}, + }, + "id": "id2", "type": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "label": "", - "description": "", - "datasetInfo": Immutable.Map {}, - }, - "graphicalAttributes": Immutable.Record { - "position": Immutable.Record { + }, + "id3": NodeRecord { + "data": NodeData {}, + "graphicalAttributes": NodeGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "nodeSize": Immutable.Record { - "width": undefined, - "height": undefined, - }, - "nodeType": undefined, - "label": "", - "description": "", - "properties": Immutable.Map {}, - }, - "getPosition": [Function], - "getSize": [Function], - "getNodeType": [Function], + "properties": {}, + }, + "id": "id3", + "type": undefined, }, }, - "ports": Immutable.Map {}, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, + "out": {}, + "parents": {}, + "ports": {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; diff --git a/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap index c4be4977e59..f1f9c110f40 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap @@ -1,760 +1,555 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Check port reducer > FLOWDESIGNER_PORT_ADD properly add the port to the port Map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, - "transform": { - "k": 1, - "x": 0, - "y": 0, +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "out": Immutable.Map { - "nodeId": Immutable.Map { - "portId": Immutable.Map {}, + "out": { + "nodeId": { + "portId": {}, }, }, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, - }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "test", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id1", + "nodeId": undefined, }, - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, - "portId": Immutable.Record { - "id": "portId", - "nodeId": "nodeId", - "data": Immutable.Map { + "portId": PortRecord { + "data": PortData { "flowType": "string", - "properties": Immutable.Map {}, + "properties": {}, }, - "graphicalAttributes": Immutable.Map { + "graphicalAttributes": PortGraphicalAttributes { "portType": "portType", - "properties": Immutable.Map { - "index": 1, - "type": "OUTGOING", - }, - "position": Immutable.Record { + "position": { "x": undefined, "y": undefined, }, + "properties": { + "index": 1, + "type": "OUTGOING", + }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "portId", + "nodeId": "nodeId", }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; exports[`Check port reducer > FLOWDESIGNER_PORT_ADDS to add multiple ports (portId1, portId2) to port collection 1`] = ` -Immutable.Map { - "in": Immutable.Map { - "nodeId": Immutable.Map { - "portId2": Immutable.Map {}, - "portId3": Immutable.Map {}, +{ + "childrens": {}, + "in": { + "nodeId": { + "portId2": {}, + "portId3": {}, }, }, - "transformToApply": undefined, - "parents": Immutable.Map {}, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "out": Immutable.Map { - "nodeId": Immutable.Map { - "portId1": Immutable.Map {}, + "out": { + "nodeId": { + "portId1": {}, }, }, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, - }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "test", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id1", + "nodeId": undefined, }, - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, - "portId1": Immutable.Record { - "id": "portId1", - "nodeId": "nodeId", - "data": Immutable.Map { + "portId1": PortRecord { + "data": PortData { "flowType": "string", - "properties": Immutable.Map {}, + "properties": {}, }, - "graphicalAttributes": Immutable.Map { + "graphicalAttributes": PortGraphicalAttributes { "portType": "portType", - "properties": Immutable.Map { - "index": 0, - "type": "OUTGOING", - }, - "position": Immutable.Record { + "position": { "x": undefined, "y": undefined, }, + "properties": { + "index": 0, + "type": "OUTGOING", + }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "portId2": Immutable.Record { - "id": "portId2", + "id": "portId1", "nodeId": "nodeId", - "data": Immutable.Map { + }, + "portId2": PortRecord { + "data": PortData { "flowType": "string", - "properties": Immutable.Map {}, + "properties": {}, }, - "graphicalAttributes": Immutable.Map { + "graphicalAttributes": PortGraphicalAttributes { "portType": "portType", - "properties": Immutable.Map { - "index": 0, - "type": "INCOMING", - }, - "position": Immutable.Record { + "position": { "x": undefined, "y": undefined, }, + "properties": { + "index": 0, + "type": "INCOMING", + }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "portId3": Immutable.Record { - "id": "portId3", + "id": "portId2", "nodeId": "nodeId", - "data": Immutable.Map { + }, + "portId3": PortRecord { + "data": PortData { "flowType": "string", - "properties": Immutable.Map {}, + "properties": {}, }, - "graphicalAttributes": Immutable.Map { + "graphicalAttributes": PortGraphicalAttributes { "portType": "portType", - "properties": Immutable.Map { - "index": 1, - "type": "INCOMING", - }, - "position": Immutable.Record { + "position": { "x": undefined, "y": undefined, }, + "properties": { + "index": 1, + "type": "INCOMING", + }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "portId3", + "nodeId": "nodeId", }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id1 from ports collection 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id1 from ports collection 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "ports": Immutable.OrderedMap { - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "out": {}, + "parents": {}, + "ports": { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "properties": Immutable.Map { + "properties": { "index": 0, }, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id2 from ports collection if its parent node does not exist 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id2 from ports collection if its parent node does not exist 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "out": {}, + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "test", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], - }, - "id3": Immutable.Record { - "id": "id3", + "id": "id1", "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + }, + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_DATA remove type on port id1 on port data map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_DATA remove type on port id1 on port data map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "out": {}, + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "test", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id1", + "nodeId": undefined, }, - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES to remove attr on port id1 graphicalAttribute map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES to remove attr on port id1 graphicalAttribute map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "out": {}, + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "test", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id1", + "nodeId": undefined, }, - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_SET_DATA to merge { type: 'string' } on port id1 data map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_SET_DATA to merge { type: 'string' } on port id1 data map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "out": {}, + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "string", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id1", + "nodeId": undefined, }, - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES to merge { selected: true } on port id1 graphicalAttribute map 1`] = ` -Immutable.Map { - "in": Immutable.Map {}, - "transformToApply": undefined, - "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "out": Immutable.Map {}, - "nodes": Immutable.Map { - "nodeId": Immutable.Map {}, + "transformToApply": undefined, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES to merge { selected: true } on port id1 graphicalAttribute map 1`] = ` +{ + "childrens": {}, + "in": {}, + "links": {}, + "nodeTypes": {}, + "nodes": { + "nodeId": {}, }, - "ports": Immutable.OrderedMap { - "id1": Immutable.Record { - "id": "id1", - "nodeId": undefined, - "data": Immutable.Map { + "out": {}, + "parents": {}, + "ports": { + "id1": PortRecord { + "data": PortData { "type": "test", }, - "graphicalAttributes": Immutable.Map { - "type": "test", - "position": Immutable.Record { + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, - "selected": true, + "properties": { + "selected": true, + }, + "type": "test", }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id1", + "nodeId": undefined, }, - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id2": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id2", + "nodeId": "test", }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, - "data": Immutable.Map { - "properties": Immutable.Map {}, - "flowType": undefined, - }, - "graphicalAttributes": Immutable.Map { - "position": Immutable.Record { + "id3": PortRecord { + "data": PortData {}, + "graphicalAttributes": PortGraphicalAttributes { + "position": { "x": 10, "y": 10, }, + "properties": {}, }, - "getPosition": [Function], - "setPosition": [Function], - "getPortType": [Function], - "getPortDirection": [Function], - "getPortFlowType": [Function], - "getIndex": [Function], - "setIndex": [Function], + "id": "id3", + "nodeId": undefined, }, }, - "childrens": Immutable.Map {}, - "nodeTypes": Immutable.Map {}, - "links": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "transformToApply": undefined, } `; diff --git a/packages/flow-designer/src/reducers/flow.reducer.test.ts b/packages/flow-designer/src/reducers/flow.reducer.test.ts index 425e524bbf0..bd94452957f 100644 --- a/packages/flow-designer/src/reducers/flow.reducer.test.ts +++ b/packages/flow-designer/src/reducers/flow.reducer.test.ts @@ -1,5 +1,3 @@ -import { Map } from 'immutable'; - import { reducer, calculatePortsPosition, defaultState } from './flow.reducer'; import * as nodeActions from '../actions/node.actions'; import * as portActions from '../actions/port.actions'; @@ -133,34 +131,26 @@ describe('FLOWDESIGNER_PAN_TO set a calculated transformation into transformToAp }); describe('calculatePortsPosition behavior', () => { - const state = defaultState - .set( - 'nodes', - Map().set( - '42', - new NodeRecord({ - id: '42', - graphicalAttributes: Map({ - nodeType: '42', - }), - }), - ), - ) - .set( - 'ports', - Map().set( - '42', - new PortRecord({ - id: '42', - nodeId: '42', - }), - ), - ) - .set('nodeTypes', Map().set('42', Map().set('component', {}))); + const state = { + ...defaultState, + nodes: { + '42': new NodeRecord({ + id: '42', + graphicalAttributes: { nodeType: '42' }, + }), + }, + ports: { + '42': new PortRecord({ + id: '42', + nodeId: '42', + }), + }, + nodeTypes: { '42': { component: {} } }, + }; it('should trigger only if NODE/PORT/FLOW action are dispatched', () => { const calculatePortPosition = vi.fn(); - const givenState = state.setIn(['nodeTypes', '42', 'component'], { calculatePortPosition }); + const givenState = { ...state, nodeTypes: { '42': { component: { calculatePortPosition } } } }; calculatePortsPosition(givenState, { type: 'FLOWDESIGNER_NODE_MOVE', }); @@ -175,7 +165,7 @@ describe('calculatePortsPosition behavior', () => { it('should not trigger on FLOWDESIGNER_NODE_REMOVE and FLOWDESIGNER_PORT_REMOVE', () => { const calculatePortPosition = vi.fn(); - const givenState = state.setIn(['nodeTypes', '42', 'component'], { calculatePortPosition }); + const givenState = { ...state, nodeTypes: { '42': { component: { calculatePortPosition } } } }; calculatePortsPosition(givenState, { type: 'FLOWDESIGNER_NODE_REMOVE', }); @@ -187,7 +177,7 @@ describe('calculatePortsPosition behavior', () => { it('should trigger using action with port id', () => { const calculatePortPosition = vi.fn(); - const givenState = state.setIn(['nodeTypes', '42', 'component'], { calculatePortPosition }); + const givenState = { ...state, nodeTypes: { '42': { component: { calculatePortPosition } } } }; const action = { type: 'FLOWDESIGNER_PORT_SET_DATA', portId: '42', diff --git a/packages/flow-designer/src/reducers/flow.reducer.ts b/packages/flow-designer/src/reducers/flow.reducer.ts index 7da71b56926..73ba31d6d70 100644 --- a/packages/flow-designer/src/reducers/flow.reducer.ts +++ b/packages/flow-designer/src/reducers/flow.reducer.ts @@ -1,4 +1,3 @@ -import { Map } from 'immutable'; import { zoomIdentity } from 'd3'; import { FLOWDESIGNER_FLOW_ADD_ELEMENTS, @@ -14,20 +13,20 @@ import nodesReducer from './node.reducer'; import linksReducer from './link.reducer'; import portsReducer from './port.reducer'; import nodeTypeReducer from './nodeType.reducer'; -import { State, NodeRecord, Id, LinkRecord, PortRecord } from '../customTypings/index.d'; +import { State, NodeRecord, PortRecord } from '../customTypings/index.d'; -export const defaultState: Partial = Map({ - nodes: Map(), - links: Map(), - ports: Map(), - out: Map>(), - in: Map>(), - childrens: Map>(), - parents: Map>(), - nodeTypes: Map(), +export const defaultState: State = { + nodes: {}, + links: {}, + ports: {}, + out: {}, + in: {}, + childrens: {}, + parents: {}, + nodeTypes: {}, transform: { k: 1, x: 0, y: 0 }, transformToApply: undefined, -}); +}; function combinedReducer(state = defaultState, action: any) { return [nodesReducer, linksReducer, portsReducer, nodeTypeReducer].reduce( @@ -92,52 +91,50 @@ export function reducer(state: State, action: any) { state, ); case FLOWDESIGNER_FLOW_RESET: - return defaultState.set('nodeTypes', state.get('nodeTypes')); + return { ...defaultState, nodeTypes: state.nodeTypes }; case FLOWDESIGNER_FLOW_LOAD: return action.listOfActionCreation.reduce( (cumulativeState: State, actionCreation: any) => combinedReducer(cumulativeState, actionCreation), - defaultState.set('nodeTypes', state.get('nodeTypes')), + { ...defaultState, nodeTypes: state.nodeTypes }, ); case FLOWDESIGNER_FLOW_SET_ZOOM: - return state.set('transform', action.transform); + return { ...state, transform: action.transform }; case FLOWDESIGNER_FLOW_ZOOM_IN: - return state.set( - 'transformToApply', - zoomIdentity - .translate(state.get('transform').x, state.get('transform').y) + return { + ...state, + transformToApply: zoomIdentity + .translate(state.transform.x, state.transform.y) .scale( calculateZoomScale( - state.get('transform').k, + state.transform.k, ZoomDirection.IN, action.scale || DEFAULT_ZOOM_SCALE_STEP, ), ), - ); + }; case FLOWDESIGNER_FLOW_ZOOM_OUT: - return state.set( - 'transformToApply', - zoomIdentity - .translate(state.get('transform').x, state.get('transform').y) + return { + ...state, + transformToApply: zoomIdentity + .translate(state.transform.x, state.transform.y) .scale( calculateZoomScale( - state.get('transform').k, + state.transform.k, ZoomDirection.OUT, action.scale || DEFAULT_ZOOM_SCALE_STEP, ), ), - ); + }; case FLOWDESIGNER_PAN_TO: - return state.update('transformToApply', () => - zoomIdentity - .translate(state.get('transform').x, state.get('transform').y) - .scale(state.get('transform').k) - .scale(1 / state.get('transform').k) - .translate( - -(state.get('transform').x + action.x), - -(state.get('transform').y + action.y), - ), - ); + return { + ...state, + transformToApply: zoomIdentity + .translate(state.transform.x, state.transform.y) + .scale(state.transform.k) + .scale(1 / state.transform.k) + .translate(-(state.transform.x + action.x), -(state.transform.y + action.y)), + }; default: return combinedReducer(state, action); } @@ -155,7 +152,7 @@ export function reducer(state: State, action: any) { * @return {object} new state */ export function calculatePortsPosition(state: State, action: any) { - let nodes = []; + let nodes: NodeRecord[] = []; // TODO: NOT a big fan of this way to optimize port recalculations, don't feel future proof if ( (/FLOWDESIGNER_NODE_/.exec(action.type) && action.type !== 'FLOWDESIGNER_NODE_REMOVE') || @@ -164,25 +161,31 @@ export function calculatePortsPosition(state: State, action: any) { action.type === FLOWDESIGNER_NODETYPE_SET ) { if (action.nodeId) { - nodes.push(state.getIn(['nodes', action.nodeId])); + nodes.push(state.nodes?.[action.nodeId] as NodeRecord); } else if (action.portId) { - nodes.push(state.getIn(['nodes', state.getIn(['ports', action.portId]).nodeId])); + const portNodeId = state.ports?.[action.portId]?.nodeId; + if (portNodeId) nodes.push(state.nodes?.[portNodeId as string] as NodeRecord); } else { - nodes = state.get('nodes'); + nodes = Object.values(state.nodes || {}) as NodeRecord[]; } return nodes.reduce((cumulativeState: State, node: NodeRecord) => { const nodeType = node.getNodeType(); - const ports = state.get('ports').filter((port: PortRecord) => port.nodeId === node.id); - - const calculatePortPosition = state.getIn( - ['nodeTypes', nodeType, 'component', 'calculatePortPosition'], - state.getIn(['nodeTypes', nodeType, 'component'])?.calculatePortPosition, + const ports = Object.fromEntries( + Object.entries(state.ports || {}).filter( + ([, port]) => (port as PortRecord).nodeId === node.id, + ), ); + + const calculatePortPosition = + state.nodeTypes?.[nodeType as string]?.component?.calculatePortPosition; if (calculatePortPosition) { - return cumulativeState.mergeIn( - ['ports'], - calculatePortPosition(ports, node.getPosition(), node.getSize()), - ); + return { + ...cumulativeState, + ports: { + ...cumulativeState.ports, + ...calculatePortPosition(ports, node.getPosition(), node.getSize()), + }, + }; } return state; }, state); diff --git a/packages/flow-designer/src/reducers/link.reducer.test.ts b/packages/flow-designer/src/reducers/link.reducer.test.ts index fad7dba49d0..b40e576bc4c 100644 --- a/packages/flow-designer/src/reducers/link.reducer.test.ts +++ b/packages/flow-designer/src/reducers/link.reducer.test.ts @@ -1,100 +1,41 @@ -import { Map, fromJS } from 'immutable'; - import { defaultState } from './flow.reducer'; import linkReducer from './link.reducer'; -import { LinkRecord, PortRecord, NodeRecord } from '../constants/flowdesigner.model'; +import { + LinkRecord, + LinkData, + LinkGraphicalAttributes, + PortRecord, + NodeRecord, +} from '../constants/flowdesigner.model'; describe('check linkreducer', () => { - const initialState = defaultState - .set( - 'nodes', - Map() - .set( - 'id1', - new NodeRecord({ - id: 'id1', - }), - ) - .set( - 'id2', - new NodeRecord({ - id: 'id2', - }), - ) - .set( - 'id3', - new NodeRecord({ - id: 'id3', - }), - ), - ) - .set( - 'links', - Map().set( - 'id1', - new LinkRecord({ - id: 'id1', - sourceId: 'id1', - targetId: 'id2', - data: Map().set('attr', 'attr'), - graphicalAttributes: Map().set('properties', fromJS({ attr: 'attr' })), - }), - ), - ) - .set( - 'ports', - Map() - .set( - 'id1', - new PortRecord({ - id: 'id1', - nodeId: 'id1', - }), - ) - .set( - 'id2', - new PortRecord({ - id: 'id2', - nodeId: 'id2', - }), - ) - .set( - 'id3', - new PortRecord({ - id: 'id3', - nodeId: 'id2', - }), - ) - .set( - 'id4', - new PortRecord({ - id: 'id4', - nodeId: 'id1', - }), - ) - .set( - 'id5', - new PortRecord({ - id: 'id5', - nodeId: 'id3', - }), - ) - .set( - 'id6', - new PortRecord({ - id: 'id6', - nodeID: 'id3', - }), - ), - ) - .set( - 'parents', - Map().set('id1', Map()).set('id2', Map().set('id1', 'id1')).set('id3', Map()), - ) - .set( - 'childrens', - Map().set('id1', Map().set('id2', 'id2')).set('id2', Map()).set('id3', Map()), - ); + const initialState = { + ...defaultState, + nodes: { + id1: new NodeRecord({ id: 'id1' }), + id2: new NodeRecord({ id: 'id2' }), + id3: new NodeRecord({ id: 'id3' }), + }, + links: { + id1: new LinkRecord({ + id: 'id1', + sourceId: 'id1', + targetId: 'id2', + data: new LinkData({ attr: 'attr' }), + graphicalAttributes: new LinkGraphicalAttributes({ properties: { attr: 'attr' } }), + }), + }, + ports: { + id1: new PortRecord({ id: 'id1', nodeId: 'id1' }), + id2: new PortRecord({ id: 'id2', nodeId: 'id2' }), + id3: new PortRecord({ id: 'id3', nodeId: 'id2' }), + id4: new PortRecord({ id: 'id4', nodeId: 'id1' }), + id5: new PortRecord({ id: 'id5', nodeId: 'id3' }), + id6: new PortRecord({ id: 'id6', nodeId: 'id3' }), + }, + parents: { id1: {}, id2: { id1: 'id1' }, id3: {} }, + childrens: { id1: { id2: 'id2' }, id2: {}, id3: {} }, + }; it('FLOWDESIGNER_LINK_ADD should add a new link to the state', () => { const newState = linkReducer(initialState, { diff --git a/packages/flow-designer/src/reducers/link.reducer.ts b/packages/flow-designer/src/reducers/link.reducer.ts index f206990e8d4..0d816d6ab4f 100644 --- a/packages/flow-designer/src/reducers/link.reducer.ts +++ b/packages/flow-designer/src/reducers/link.reducer.ts @@ -1,5 +1,5 @@ import invariant from 'invariant'; -import { Map, fromJS } from 'immutable'; +import cloneDeep from 'lodash/cloneDeep'; import { FLOWDESIGNER_LINK_ADD, @@ -13,230 +13,187 @@ import { } from '../constants/flowdesigner.constants'; import { LinkRecord, LinkGraphicalAttributes, LinkData } from '../constants/flowdesigner.model'; +import { setIn, deleteIn } from './state-utils'; +import { State } from '../customTypings/index.d'; -const defaultState = Map(); - -export default function linkReducer(state = defaultState, action: any) { +export default function linkReducer(state: State, action: any) { switch (action.type) { - case FLOWDESIGNER_LINK_ADD: - if (state.getIn(['links', action.linkId])) { + case FLOWDESIGNER_LINK_ADD: { + if (state.links?.[action.linkId]) { invariant(false, `can't create a link ${action.linkId} when it already exist`); } - if (!state.getIn(['ports', action.targetId])) { + if (!state.ports?.[action.targetId]) { invariant( false, `can't set a non existing target with id ${action.targetId} on link ${action.linkId}`, ); } - if (!state.getIn(['ports', action.sourceId])) { + if (!state.ports?.[action.sourceId]) { invariant( false, `can't set a non existing source with id ${action.sourceId} on link ${action.linkId}`, ); } - return state - .setIn( - ['links', action.linkId], - new LinkRecord({ + const sourcePort = state.ports[action.sourceId]; + const targetPort = state.ports[action.targetId]; + const sourceNodeId = sourcePort.nodeId as string; + const targetNodeId = targetPort.nodeId as string; + + return { + ...state, + links: { + ...state.links, + [action.linkId]: new LinkRecord({ id: action.linkId, sourceId: action.sourceId, targetId: action.targetId, - data: new LinkData(action.data).set( - 'properties', - fromJS(action.data && action.data.properties) || Map(), - ), - graphicalAttributes: new LinkGraphicalAttributes( - action.graphicalAttributes, - ).set( + data: new LinkData({ + ...action.data, + properties: cloneDeep(action.data?.properties) || {}, + }), + graphicalAttributes: new LinkGraphicalAttributes(action.graphicalAttributes).set( 'properties', - fromJS( - action.graphicalAttributes && action.graphicalAttributes.properties, - ) || Map(), + cloneDeep(action.graphicalAttributes?.properties) || {}, ), }), - ) // parcourir l'ensemble des parents et set le composant cible en tant que sucessors ' - .setIn( - [ - 'childrens', - state.getIn(['ports', action.sourceId]).nodeId, - state.getIn(['ports', action.targetId]).nodeId, - ], - state.getIn(['ports', action.targetId]).nodeId, - ) - .setIn( - [ - 'parents', - state.getIn(['ports', action.targetId]).nodeId, - state.getIn(['ports', action.sourceId]).nodeId, - ], - state.getIn(['ports', action.sourceId]).nodeId, - ) - .setIn( - [ - 'out', - state.getIn(['ports', action.sourceId]).nodeId, - action.sourceId, - action.linkId, - ], - action.linkId, - ) - .setIn( - [ - 'in', - state.getIn(['ports', action.targetId]).nodeId, - action.targetId, - action.linkId, - ], - action.linkId, - ); - case FLOWDESIGNER_LINK_SET_TARGET: - if (!state.getIn(['links', action.linkId])) { + }, + childrens: { + ...state.childrens, + [sourceNodeId]: { + ...(state.childrens?.[sourceNodeId] || {}), + [targetNodeId]: targetNodeId, + }, + }, + parents: { + ...state.parents, + [targetNodeId]: { + ...(state.parents?.[targetNodeId] || {}), + [sourceNodeId]: sourceNodeId, + }, + }, + out: setIn(state.out, [sourceNodeId, action.sourceId, action.linkId], action.linkId), + in: setIn(state.in, [targetNodeId, action.targetId, action.linkId], action.linkId), + }; + } + case FLOWDESIGNER_LINK_SET_TARGET: { + if (!state.links?.[action.linkId]) { invariant( false, `can't set a target ${action.targetId} on non existing link with id ${action.linkId}`, ); } - if (!state.getIn(['ports', action.targetId])) { + if (!state.ports?.[action.targetId]) { invariant( false, `can't set a non existing target with id ${action.targetId} on link ${action.linkId}`, ); } - return state - .setIn(['links', action.linkId, 'targetId'], action.targetId) - .deleteIn([ - 'in', - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, - state.getIn(['links', action.linkId]).targetId, - action.linkId, - ]) - .setIn( - [ - 'in', - state.getIn(['ports', action.targetId]).nodeId, - action.targetId, - action.linkId, - ], - action.linkId, - ) - .deleteIn([ - 'childrens', - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, - ]) - .setIn( - [ - 'childrens', - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]) - .nodeId, - state.getIn(['ports', action.targetId]).nodeId, - ], - state.getIn(['ports', action.targetId]).nodeId, - ); - case FLOWDESIGNER_LINK_SET_SOURCE: - if (!state.getIn(['links', action.linkId])) { + const link = state.links[action.linkId] as LinkRecord; + const oldTargetPort = state.ports[link.targetId as string]; + const newTargetPort = state.ports[action.targetId]; + const sourcePort = state.ports[link.sourceId as string]; + + let s: State = { + ...state, + links: { ...state.links, [action.linkId]: link.set('targetId', action.targetId) }, + }; + s = deleteIn(s, ['in', oldTargetPort?.nodeId, link.targetId, action.linkId]); + s = setIn(s, ['in', newTargetPort?.nodeId, action.targetId, action.linkId], action.linkId); + s = deleteIn(s, ['childrens', sourcePort?.nodeId, oldTargetPort?.nodeId]); + s = setIn(s, ['childrens', sourcePort?.nodeId, newTargetPort?.nodeId], newTargetPort?.nodeId); + return s; + } + case FLOWDESIGNER_LINK_SET_SOURCE: { + if (!state.links?.[action.linkId]) { invariant( false, `can't set a source ${action.sourceId} on non existing link with id ${action.linkId}`, ); } - if (!state.getIn(['ports', action.sourceId])) { + if (!state.ports?.[action.sourceId]) { invariant( false, `can't set a non existing target with id ${action.sourceId} on link ${action.linkId}`, ); } - return state - .setIn(['links', action.linkId, 'sourceId'], action.sourceId) - .deleteIn([ - 'out', - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, - state.getIn(['links', action.linkId]).sourceId, - action.linkId, - ]) - .setIn( - [ - 'out', - state.getIn(['ports', action.sourceId]).nodeId, - action.sourceId, - action.linkId, - ], - action.linkId, - ) - .deleteIn([ - 'parents', - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, - ]) - .setIn( - [ - 'parents', - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]) - .nodeId, - state.getIn(['ports', action.sourceId]).nodeId, - ], - state.getIn(['ports', action.sourceId]).nodeId, - ); - case FLOWDESIGNER_LINK_REMOVE: - if (!state.getIn(['links', action.linkId])) { + const link = state.links[action.linkId] as LinkRecord; + const oldSourcePort = state.ports[link.sourceId as string]; + const newSourcePort = state.ports[action.sourceId]; + const targetPort = state.ports[link.targetId as string]; + + let s: State = { + ...state, + links: { ...state.links, [action.linkId]: link.set('sourceId', action.sourceId) }, + }; + s = deleteIn(s, ['out', oldSourcePort?.nodeId, link.sourceId, action.linkId]); + s = setIn(s, ['out', newSourcePort?.nodeId, action.sourceId, action.linkId], action.linkId); + s = deleteIn(s, ['parents', targetPort?.nodeId, oldSourcePort?.nodeId]); + s = setIn(s, ['parents', targetPort?.nodeId, newSourcePort?.nodeId], newSourcePort?.nodeId); + return s; + } + case FLOWDESIGNER_LINK_REMOVE: { + if (!state.links?.[action.linkId]) { invariant(false, `can't remove non existing link ${action.linkId}`); } - return state - .deleteIn([ - 'in', - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, - state.getIn(['links', action.linkId]).targetId, - action.linkId, - ]) - .deleteIn([ - 'out', - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, - state.getIn(['links', action.linkId]).sourceId, - action.linkId, - ]) - .deleteIn([ - 'childrens', - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, - ]) - .deleteIn([ - 'parents', - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, - ]) - .deleteIn(['links', action.linkId]); - case FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES: - if (!state.getIn(['links', action.linkId])) { + const link = state.links[action.linkId] as LinkRecord; + const targetPort = state.ports?.[link.targetId as string]; + const sourcePort = state.ports?.[link.sourceId as string]; + + let s = deleteIn(state, ['in', targetPort?.nodeId, link.targetId, action.linkId]); + s = deleteIn(s, ['out', sourcePort?.nodeId, link.sourceId, action.linkId]); + s = deleteIn(s, ['childrens', sourcePort?.nodeId, targetPort?.nodeId]); + s = deleteIn(s, ['parents', targetPort?.nodeId, sourcePort?.nodeId]); + s = deleteIn(s, ['links', action.linkId]); + return s; + } + case FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES: { + if (!state.links?.[action.linkId]) { invariant(false, `Can't set an attribute on non existing link ${action.linkId}`); } + const link = state.links[action.linkId] as LinkRecord; try { - return state.mergeIn( - ['links', action.linkId, 'graphicalAttributes'], - fromJS(action.graphicalAttributes), - ); + return { + ...state, + links: { + ...state.links, + [action.linkId]: link.set( + 'graphicalAttributes', + link.graphicalAttributes.merge(action.graphicalAttributes), + ), + }, + }; } catch (error) { console.error(error); - return state.mergeIn( - ['links', action.linkId, 'graphicalAttributes', 'properties'], - fromJS(action.graphicalAttributes), - ); + return { + ...state, + links: { + ...state.links, + [action.linkId]: link.set( + 'graphicalAttributes', + link.graphicalAttributes.set('properties', { + ...(link.graphicalAttributes?.properties || {}), + ...action.graphicalAttributes, + }), + ), + }, + }; } - + } case FLOWDESIGNER_LINK_REMOVE_GRAPHICAL_ATTRIBUTES: - if (!state.getIn(['links', action.linkId])) { + if (!state.links?.[action.linkId]) { invariant(false, `Can't remove an attribute on non existing link ${action.linkId}`); } - return state.deleteIn([ + return deleteIn(state, [ 'links', action.linkId, 'graphicalAttributes', @@ -244,27 +201,37 @@ export default function linkReducer(state = defaultState, action: any) { action.graphicalAttributesKey, ]); - case FLOWDESIGNER_LINK_SET_DATA: - if (!state.getIn(['links', action.linkId])) { + case FLOWDESIGNER_LINK_SET_DATA: { + if (!state.links?.[action.linkId]) { invariant(false, `Can't set an attribute on non existing link ${action.linkId}`); } + const link = state.links[action.linkId] as LinkRecord; try { - return state.mergeIn(['links', action.linkId, 'data'], fromJS(action.data)); + return { + ...state, + links: { + ...state.links, + [action.linkId]: link.set( + 'data', + new LinkData({ ...(link.data as any), ...action.data }), + ), + }, + }; } catch (error) { console.error(error); - return state.mergeIn( - ['links', action.linkId, 'data', 'properties'], - fromJS(action.data), - ); + return setIn(state, ['links', action.linkId, 'data', 'properties'], { + ...(link.data as any)?.properties, + ...action.data, + }); } - + } case FLOWDESIGNER_LINK_REMOVE_DATA: - if (!state.getIn(['links', action.linkId])) { + if (!state.links?.[action.linkId]) { invariant(false, `Can't remove an attribute on non existing link ${action.linkId}`); } - return state.deleteIn(['links', action.linkId, 'data', 'properties', action.dataKey]); + return deleteIn(state, ['links', action.linkId, 'data', 'properties', action.dataKey]); default: return state; diff --git a/packages/flow-designer/src/reducers/node.reducer.test.ts b/packages/flow-designer/src/reducers/node.reducer.test.ts index 03942ac0d52..17bffc1c04a 100644 --- a/packages/flow-designer/src/reducers/node.reducer.test.ts +++ b/packages/flow-designer/src/reducers/node.reducer.test.ts @@ -1,9 +1,8 @@ -import { Map } from 'immutable'; - import { defaultState } from './flow.reducer'; import nodeReducer from './node.reducer'; import { NodeRecord, + NodeData, PositionRecord, NodeGraphicalAttributes, } from '../constants/flowdesigner.model'; @@ -15,33 +14,32 @@ import { } from '../constants/flowdesigner.constants'; describe('Check node reducer', () => { - const initialState = defaultState - .setIn( - ['nodes', 'id1'], - new NodeRecord({ + const initialState = { + ...defaultState, + nodes: { + ...defaultState.nodes, + id1: new NodeRecord({ id: 'id1', type: 'type1', - data: Map({ type: 'test' }), + data: new NodeData({ type: 'test' }), graphicalAttributes: new NodeGraphicalAttributes({ type: 'type1', selected: true, position: new PositionRecord({ x: 10, y: 10 }), }), }), - ) - .setIn( - ['nodes', 'id2'], - new NodeRecord({ + id2: new NodeRecord({ id: 'id2', type: 'type2', - data: Map({ type: 'test' }), + data: new NodeData({ type: 'test' }), graphicalAttributes: new NodeGraphicalAttributes({ type: 'type2', selected: false, position: new PositionRecord({ x: 10, y: 10 }), }), }), - ); + }, + }; it('FLOWDESIGNER_NODE_ADD properly add a new node to the node collection', () => { expect( @@ -75,9 +73,7 @@ describe('Check node reducer', () => { type: FLOWDESIGNER_NODE_MOVE_START, nodeId: 'id1', nodePosition: { x: 50, y: 50 }, - }) - .getIn(['nodes', 'id1', 'graphicalAttributes', 'properties', 'startPosition']) - .toJS(), + }).nodes['id1'].graphicalAttributes.properties.startPosition, ).toEqual({ x: 50, y: 50 }); }); @@ -97,7 +93,7 @@ describe('Check node reducer', () => { type: FLOWDESIGNER_NODE_MOVE_END, nodeId: 'id1', nodePosition: { x: 50, y: 50 }, - }).getIn(['nodes', 'id1', 'graphicalAttributes', 'properties', 'startPosition']), + }).nodes['id1'].graphicalAttributes.properties.startPosition, ).toEqual(undefined); }); @@ -175,37 +171,33 @@ describe('Check node reducer', () => { }); describe('FLOWDESIGNER_NODE_APPLY_MOVEMENT', () => { - const initialState = defaultState - .setIn( - ['nodes', 'id1'], - new NodeRecord({ + const initialState = { + ...defaultState, + nodes: { + ...defaultState.nodes, + id1: new NodeRecord({ id: 'id1', nodeType: 'type1', graphicalAttributes: new NodeGraphicalAttributes({ position: new PositionRecord({ x: 10, y: 10 }), }), }), - ) - .setIn( - ['nodes', 'id2'], - new NodeRecord({ + id2: new NodeRecord({ id: 'id2', nodeType: 'type2', graphicalAttributes: new NodeGraphicalAttributes({ position: new PositionRecord({ x: 10, y: 10 }), }), }), - ) - .setIn( - ['nodes', 'id3'], - new NodeRecord({ + id3: new NodeRecord({ id: 'id3', nodeType: 'type2', graphicalAttributes: new NodeGraphicalAttributes({ position: new PositionRecord({ x: 10, y: 10 }), }), }), - ); + }, + }; it('should apply the same relative movement to each node listed', () => { expect( nodeReducer(initialState, { diff --git a/packages/flow-designer/src/reducers/node.reducer.ts b/packages/flow-designer/src/reducers/node.reducer.ts index 3bf8c708ec2..7f12538ab3b 100644 --- a/packages/flow-designer/src/reducers/node.reducer.ts +++ b/packages/flow-designer/src/reducers/node.reducer.ts @@ -1,8 +1,9 @@ -import Immutable, { Map, fromJS } from 'immutable'; import invariant from 'invariant'; +import cloneDeep from 'lodash/cloneDeep'; import { removePort } from '../actions/port.actions'; import portReducer from './port.reducer'; import { outPort, inPort } from '../selectors/portSelectors'; +import { setIn, deleteIn } from './state-utils'; import { FLOWDESIGNER_NODE_ADD, @@ -25,207 +26,218 @@ import { PositionRecord, SizeRecord, NodeGraphicalAttributes, + NodeData, } from '../constants/flowdesigner.model'; -import { PortRecord, Id, State, NodeRecordMap } from '../customTypings/index.d'; +import { Id, State, NodeRecord as NodeRecordType } from '../customTypings/index.d'; -const defaultState = Map(); -const nodeReducer = (state: State = defaultState, action: any) => { +const nodeReducer = (state: State, action: any) => { switch (action.type) { - case FLOWDESIGNER_NODE_ADD: - if (state.getIn(['nodes', action.nodeId])) { - invariant( - false, - `Can not create node ${action.nodeId} since it does already exist`, - ); + case FLOWDESIGNER_NODE_ADD: { + if (state.nodes?.[action.nodeId]) { + invariant(false, `Can not create node ${action.nodeId} since it does already exist`); } - - return state - .setIn( - ['nodes', action.nodeId], - new NodeRecord({ + return { + ...state, + nodes: { + ...state.nodes, + [action.nodeId]: new NodeRecord({ id: action.nodeId, type: action.nodeType, - data: Immutable.Map(action.data).set( - 'properties', - fromJS(action.data && action.data.properties) || Map(), - ), - graphicalAttributes: new NodeGraphicalAttributes( - fromJS(action.graphicalAttributes), - ) - .set('nodeSize', new SizeRecord(action.graphicalAttributes.nodeSize)) - .set( - 'position', - new PositionRecord(action.graphicalAttributes.position), - ) - .set( - 'properties', - fromJS(action.graphicalAttributes.properties) || Map(), - ), + data: new NodeData({ + ...action.data, + properties: cloneDeep(action.data?.properties) || {}, + }), + graphicalAttributes: new NodeGraphicalAttributes(action.graphicalAttributes) + .set('nodeSize', new SizeRecord(action.graphicalAttributes?.nodeSize)) + .set('position', new PositionRecord(action.graphicalAttributes?.position)) + .set('properties', cloneDeep(action.graphicalAttributes?.properties) || {}), }), - ) - .setIn(['out', action.nodeId], Map()) - .setIn(['in', action.nodeId], Map()) - .setIn(['childrens', action.nodeId], Map()) - .setIn(['parents', action.nodeId], Map()); - case FLOWDESIGNER_NODE_UPDATE: - if (action.nodeId === Node.getId(action.node)) { - return state.setIn(['nodes', Node.getId(action.node)], action.node); - } // special case here, the id got changed and it have lots of implication - - return state - .setIn(['nodes', Node.getId(action.node)], action.node) - .deleteIn(['nodes', action.nodeId]) - .setIn(['out', Node.getId(action.node)], Map()) - .setIn(['in', Node.getId(action.node)], Map()) - .setIn(['childrens', Node.getId(action.node)], Map()) - .setIn(['parents', Node.getId(action.node)], Map()); + }, + out: { ...state.out, [action.nodeId]: {} }, + in: { ...state.in, [action.nodeId]: {} }, + childrens: { ...state.childrens, [action.nodeId]: {} }, + parents: { ...state.parents, [action.nodeId]: {} }, + }; + } + case FLOWDESIGNER_NODE_UPDATE: { + const newId = Node.getId(action.node) as string; + if (action.nodeId === newId) { + return { ...state, nodes: { ...state.nodes, [newId]: action.node } }; + } + const { [action.nodeId]: _removed, ...remainingNodes } = state.nodes || {}; + return { + ...state, + nodes: { ...remainingNodes, [newId]: action.node }, + out: { ...state.out, [newId]: {} }, + in: { ...state.in, [newId]: {} }, + childrens: { ...state.childrens, [newId]: {} }, + parents: { ...state.parents, [newId]: {} }, + }; + } case FLOWDESIGNER_NODE_MOVE_START: - if (!state.getIn('nodes', action.nodeId)) { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } - - return state.setIn( + return setIn( + state, ['nodes', action.nodeId, 'graphicalAttributes', 'properties', 'startPosition'], new PositionRecord(action.nodePosition), ); case FLOWDESIGNER_NODE_MOVE: - if (!state.getIn('nodes', action.nodeId)) { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } - - return state.setIn( + return setIn( + state, ['nodes', action.nodeId, 'graphicalAttributes', 'position'], new PositionRecord(action.nodePosition), ); case FLOWDESIGNER_NODE_MOVE_END: - if (!state.getIn('nodes', action.nodeId)) { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } - - return state - .setIn( + return deleteIn( + setIn( + state, ['nodes', action.nodeId, 'graphicalAttributes', 'position'], new PositionRecord(action.nodePosition), - ) - .deleteIn([ - 'nodes', - action.nodeId, - 'graphicalAttributes', - 'properties', - 'startPosition', - ]); - case FLOWDESIGNER_NODE_APPLY_MOVEMENT: - return state.update('nodes', (nodes: NodeRecordMap) => - nodes.map(node => { - if (action.nodesId.find((id: Id) => id === node.id)) { - return node - .setIn( - ['graphicalAttributes', 'position', 'x'], - node.getPosition().x + action.movement.x, - ) - .setIn( - ['graphicalAttributes', 'position', 'y'], - node.getPosition().y + action.movement.y, - ); + ), + ['nodes', action.nodeId, 'graphicalAttributes', 'properties', 'startPosition'], + ); + case FLOWDESIGNER_NODE_APPLY_MOVEMENT: { + const updatedNodes = Object.fromEntries( + Object.entries(state.nodes || {}).map(([id, node]) => { + const n = node as NodeRecord; + if (action.nodesId.find((nId: Id) => nId === n.id)) { + return [ + id, + n + .setIn( + ['graphicalAttributes', 'position', 'x'], + n.getPosition().x + action.movement.x, + ) + .setIn( + ['graphicalAttributes', 'position', 'y'], + n.getPosition().y + action.movement.y, + ), + ]; } - return node; + return [id, node]; }), ); + return { ...state, nodes: updatedNodes }; + } case FLOWDESIGNER_NODE_SET_SIZE: - if (!state.getIn(['nodes', action.nodeId])) { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't set size on node ${action.nodeId} since it doesn't exist`); } - - return state.setIn( + return setIn( + state, ['nodes', action.nodeId, 'graphicalAttributes', 'nodeSize'], new SizeRecord(action.nodeSize), ); case FLOWDESIGNER_NODE_SET_TYPE: - if (!state.getIn(['nodes', action.nodeId])) { - invariant( - false, - `Can't set node.type on node ${action.nodeid} since it doesn't exist`, - ); + if (!state.nodes?.[action.nodeId]) { + invariant(false, `Can't set node.type on node ${action.nodeid} since it doesn't exist`); } - - return state.setIn(['nodes', action.nodeId, 'type'], action.nodeType); - case FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES: - if (!state.getIn(['nodes', action.nodeId])) { - invariant( - false, - `Can't set a graphical attribute on non existing node ${action.nodeId}`, - ); + return setIn(state, ['nodes', action.nodeId, 'type'], action.nodeType); + case FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES: { + if (!state.nodes?.[action.nodeId]) { + invariant(false, `Can't set a graphical attribute on non existing node ${action.nodeId}`); } - + const node = state.nodes[action.nodeId] as NodeRecord; try { - return state.mergeIn( - ['nodes', action.nodeId, 'graphicalAttributes'], - fromJS(action.graphicalAttributes), - ); + return { + ...state, + nodes: { + ...state.nodes, + [action.nodeId]: node.set( + 'graphicalAttributes', + node.graphicalAttributes.merge(action.graphicalAttributes), + ), + }, + }; } catch (error) { console.error(error); - return state.mergeIn( - ['nodes', action.nodeId, 'graphicalAttributes', 'properties'], - fromJS(action.graphicalAttributes), - ); + return { + ...state, + nodes: { + ...state.nodes, + [action.nodeId]: node.set( + 'graphicalAttributes', + node.graphicalAttributes.set('properties', { + ...(node.graphicalAttributes?.properties || {}), + ...action.graphicalAttributes, + }), + ), + }, + }; } - + } case FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES: - if (!state.getIn(['nodes', action.nodeId])) { + if (!state.nodes?.[action.nodeId]) { invariant( false, `Can't remove a graphical attribute on non existing node ${action.nodeId}`, ); } - - return state.deleteIn([ + return deleteIn(state, [ 'nodes', action.nodeId, 'graphicalAttributes', 'properties', action.graphicalAttributesKey, ]); - case FLOWDESIGNER_NODE_SET_DATA: - if (!state.getIn(['nodes', action.nodeId])) { + case FLOWDESIGNER_NODE_SET_DATA: { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't set a data on non existing node ${action.nodeId}`); } - + const node = state.nodes[action.nodeId] as NodeRecord; try { - return state.mergeIn(['nodes', action.nodeId, 'data'], fromJS(action.data)); + return { + ...state, + nodes: { + ...state.nodes, + [action.nodeId]: node.set( + 'data', + new NodeData({ ...(node.data as any), ...action.data }), + ), + }, + }; } catch (error) { console.error(error); - return state.mergeIn( - ['nodes', action.nodeId, 'data', 'properties'], - fromJS(action.data), - ); + return setIn(state, ['nodes', action.nodeId, 'data', 'properties'], { + ...(node.data as any)?.properties, + ...action.data, + }); } - + } case FLOWDESIGNER_NODE_REMOVE_DATA: - if (!state.getIn(['nodes', action.nodeId])) { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't remove a data on non existing node ${action.nodeId}`); } - - return state.deleteIn(['nodes', action.nodeId, 'data', 'properties', action.dataKey]); - case FLOWDESIGNER_NODE_REMOVE: - if (!state.getIn(['nodes', action.nodeId])) { + return deleteIn(state, ['nodes', action.nodeId, 'data', 'properties', action.dataKey]); + case FLOWDESIGNER_NODE_REMOVE: { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can not remove node ${action.nodeId} since it doesn't exist`); } - - return inPort(state, action.nodeId) - .reduce( - (cumulativeState: State, port: PortRecord, key: Id) => - portReducer(cumulativeState, removePort(key)), - outPort(state, action.nodeId).reduce( - (cumulativeState: State, port: PortRecord, key: Id) => - portReducer(cumulativeState, removePort(key)), - state, - ), - ) - .deleteIn(['nodes', action.nodeId]) - .deleteIn(['out', action.nodeId]) - .deleteIn(['in', action.nodeId]) - .deleteIn(['childrens', action.nodeId]) - .deleteIn(['parents', action.nodeId]); + const outPorts = outPort(state, action.nodeId); + const inPorts = inPort(state, action.nodeId); + let newState = Object.entries(outPorts).reduce( + (cumulativeState, [key]) => portReducer(cumulativeState, removePort(key)), + state, + ); + newState = Object.entries(inPorts).reduce( + (cumulativeState, [key]) => portReducer(cumulativeState, removePort(key)), + newState, + ); + newState = deleteIn(newState, ['nodes', action.nodeId]); + newState = deleteIn(newState, ['out', action.nodeId]); + newState = deleteIn(newState, ['in', action.nodeId]); + newState = deleteIn(newState, ['childrens', action.nodeId]); + newState = deleteIn(newState, ['parents', action.nodeId]); + return newState; + } default: return state; } diff --git a/packages/flow-designer/src/reducers/nodeType.reducer.ts b/packages/flow-designer/src/reducers/nodeType.reducer.ts index 32640d7c12e..a5252461a61 100644 --- a/packages/flow-designer/src/reducers/nodeType.reducer.ts +++ b/packages/flow-designer/src/reducers/nodeType.reducer.ts @@ -1,12 +1,10 @@ -import { Map } from 'immutable'; - import { FLOWDESIGNER_NODETYPE_SET } from '../constants/flowdesigner.constants'; +import { State } from '../customTypings/index.d'; -const defaultState = Map(); -const nodeTypeReducer = (state = defaultState, action: any) => { +const nodeTypeReducer = (state: State, action: any) => { switch (action.type) { case FLOWDESIGNER_NODETYPE_SET: - return state.mergeIn(['nodeTypes'], action.nodeTypes); + return { ...state, nodeTypes: { ...state.nodeTypes, ...action.nodeTypes } }; default: return state; } diff --git a/packages/flow-designer/src/reducers/port.reducer.test.ts b/packages/flow-designer/src/reducers/port.reducer.test.ts index b87af0d362f..3dfcfb8a6e3 100644 --- a/packages/flow-designer/src/reducers/port.reducer.test.ts +++ b/packages/flow-designer/src/reducers/port.reducer.test.ts @@ -1,49 +1,42 @@ -import { Map, OrderedMap } from 'immutable'; - import { defaultState } from './flow.reducer'; import portReducer from './port.reducer'; -import { PortRecord, PositionRecord } from '../constants/flowdesigner.model'; +import { + PortRecord, + PortData, + PortGraphicalAttributes, + PositionRecord, +} from '../constants/flowdesigner.model'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; describe('Check port reducer', () => { - const initialState = defaultState - .set( - 'ports', - // eslint-disable-next-line new-cap - OrderedMap() - .set( - 'id1', - new PortRecord({ - id: 'id1', - data: Map({ type: 'test' }), - graphicalAttributes: Map({ - type: 'test', - position: new PositionRecord({ x: 10, y: 10 }), - }), - }), - ) - .set( - 'id2', - new PortRecord({ - id: 'id2', - nodeId: 'test', - graphicalAttributes: Map({ - position: new PositionRecord({ x: 10, y: 10 }), - }), - }), - ) - .set( - 'id3', - new PortRecord({ - id: 'id3', - graphicalAttributes: Map({ - position: new PositionRecord({ x: 10, y: 10 }), - }), - }), - ), - ) - .set('nodes', Map().set('nodeId', Map())) - .set('links', Map()); + const initialState = { + ...defaultState, + ports: { + id1: new PortRecord({ + id: 'id1', + data: new PortData({ type: 'test' }), + graphicalAttributes: new PortGraphicalAttributes({ + type: 'test', + position: new PositionRecord({ x: 10, y: 10 }), + }), + }), + id2: new PortRecord({ + id: 'id2', + nodeId: 'test', + graphicalAttributes: new PortGraphicalAttributes({ + position: new PositionRecord({ x: 10, y: 10 }), + }), + }), + id3: new PortRecord({ + id: 'id3', + graphicalAttributes: new PortGraphicalAttributes({ + position: new PositionRecord({ x: 10, y: 10 }), + }), + }), + }, + nodes: { nodeId: {} }, + links: {}, + }; it('FLOWDESIGNER_PORT_ADD properly add the port to the port Map', () => { expect( diff --git a/packages/flow-designer/src/reducers/port.reducer.ts b/packages/flow-designer/src/reducers/port.reducer.ts index 63a7da00fb6..0caa126e6e3 100644 --- a/packages/flow-designer/src/reducers/port.reducer.ts +++ b/packages/flow-designer/src/reducers/port.reducer.ts @@ -1,7 +1,12 @@ import invariant from 'invariant'; -import { Map, fromJS } from 'immutable'; +import cloneDeep from 'lodash/cloneDeep'; -import { PortRecord, PositionRecord } from '../constants/flowdesigner.model'; +import { + PortRecord, + PortData, + PortGraphicalAttributes, + PositionRecord, +} from '../constants/flowdesigner.model'; import { removeLink } from '../actions/link.actions'; import linkReducer from './link.reducer'; import { portOutLink, portInLink } from '../selectors/linkSelectors'; @@ -26,20 +31,25 @@ import { State, PortAction, } from '../customTypings/index.d'; +import { setIn, deleteIn } from './state-utils'; /** * get ports attached to a node */ function filterPortsByNode(ports: PortRecordMap, nodeId: Id): PortRecordMap { - return ports.filter((port: PortRecordType) => port.nodeId === nodeId) as PortRecordMap; + return Object.fromEntries( + Object.entries(ports).filter(([, port]) => (port as PortRecordType).nodeId === nodeId), + ) as PortRecordMap; } /** * get ports of direction EMITTER or SINK */ function filterPortsByDirection(ports: PortRecordMap, direction: PortDirection): PortRecordMap { - return ports.filter( - (port: PortRecordType) => port.getPortDirection() === direction, + return Object.fromEntries( + Object.entries(ports).filter( + ([, port]) => (port as PortRecordType).getPortDirection() === direction, + ), ) as PortRecordMap; } @@ -47,28 +57,30 @@ function filterPortsByDirection(ports: PortRecordMap, direction: PortDirection): * for a new port calculate its index by retrieving all its siblings */ function calculateNewPortIndex(ports: PortRecordMap, port: PortRecordType): number { - return filterPortsByDirection( - filterPortsByNode(ports, port.nodeId), - port.graphicalAttributes.properties.type, - ).size; + return Object.keys( + filterPortsByDirection( + filterPortsByNode(ports, port.nodeId), + port.graphicalAttributes.properties.type, + ), + ).length; } function indexPortMap(ports: PortRecordMap): PortRecordMap { let i = 0; - return ports - .sort((a, b) => { - if (a.getIndex() < b.getIndex()) { - return -1; - } - if (a.getIndex() > b.getIndex()) { - return 1; - } - return 0; - }) - .map(port => { - i += 1; - return port.setIndex(i - 1); - }) as PortRecordMap; + return Object.fromEntries( + Object.entries(ports) + .sort(([, a], [, b]) => { + const pa = a as PortRecordType; + const pb = b as PortRecordType; + if (pa.getIndex() < pb.getIndex()) return -1; + if (pa.getIndex() > pb.getIndex()) return 1; + return 0; + }) + .map(([k, port]) => { + i += 1; + return [k, (port as PortRecordType).setIndex(i - 1)]; + }), + ) as PortRecordMap; } /** @@ -78,35 +90,34 @@ function indexPortMap(ports: PortRecordMap): PortRecordMap { */ function setPort(state: State, port: PortRecordType) { const index: number = - port.graphicalAttributes.properties.index || - calculateNewPortIndex(state.get('ports'), port); - const newState = state.setIn( - ['ports', port.id], - new PortRecord({ - id: port.id, - nodeId: port.nodeId, - data: Map(port.data).set( - 'properties', - fromJS(port.data && port.data.properties) || Map(), - ), - graphicalAttributes: Map(port.graphicalAttributes) - .set('position', new PositionRecord(port.graphicalAttributes.position)) - .set( - 'properties', - fromJS( - port.graphicalAttributes && { - index, - ...port.graphicalAttributes.properties, - }, - ) || Map(), - ), - }), - ); + port.graphicalAttributes.properties.index || calculateNewPortIndex(state.ports, port); + const newState: State = { + ...state, + ports: { + ...state.ports, + [port.id]: new PortRecord({ + id: port.id, + nodeId: port.nodeId, + data: new PortData({ + ...port.data, + properties: cloneDeep((port.data as any)?.properties) || {}, + }), + graphicalAttributes: new PortGraphicalAttributes({ + ...port.graphicalAttributes, + position: new PositionRecord(port.graphicalAttributes.position), + properties: { + index, + ...(port.graphicalAttributes?.properties || {}), + }, + }), + }), + }, + }; const type = port.graphicalAttributes.properties.type; if (type === PORT_SOURCE) { - return newState.setIn(['out', port.nodeId, port.id], Map()); + return setIn(newState, ['out', port.nodeId, port.id], {}); } else if (type === PORT_SINK) { - return newState.setIn(['in', port.nodeId, port.id], Map()); + return setIn(newState, ['in', port.nodeId, port.id], {}); } invariant( false, @@ -120,11 +131,8 @@ function setPort(state: State, port: PortRecordType) { export default function portReducer(state: State, action: PortAction): State { switch (action.type) { case FLOWDESIGNER_PORT_ADD: - if (!state.getIn(['nodes', action.nodeId])) { - invariant( - false, - `Can't set a new port ${action.id} on non existing node ${action.nodeId}`, - ); + if (!state.nodes?.[action.nodeId]) { + invariant(false, `Can't set a new port ${action.id} on non existing node ${action.nodeId}`); } return setPort(state, { @@ -135,7 +143,7 @@ export default function portReducer(state: State, action: PortAction): State { }); case FLOWDESIGNER_PORT_ADDS: { const localAction = action; - if (!state.getIn(['nodes', action.nodeId])) { + if (!state.nodes?.[action.nodeId]) { invariant(false, `Can't set a new ports on non existing node ${action.nodeId}`); } return action.ports.reduce( @@ -149,98 +157,119 @@ export default function portReducer(state: State, action: PortAction): State { state, ); } - case FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES: - if (!state.getIn(['ports', action.portId])) { - invariant( - false, - `Can't set an graphical attribute on non existing port ${action.portId}`, - ); + case FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES: { + if (!state.ports?.[action.portId]) { + invariant(false, `Can't set an graphical attribute on non existing port ${action.portId}`); } + const port = state.ports[action.portId] as PortRecord; try { - return state.mergeIn( - ['ports', action.portId, 'graphicalAttributes'], - fromJS(action.graphicalAttributes), - ); + return { + ...state, + ports: { + ...state.ports, + [action.portId]: port.set( + 'graphicalAttributes', + port.graphicalAttributes.merge(action.graphicalAttributes), + ), + }, + }; } catch (error) { console.error(error); - return state.mergeIn( - ['ports', action.portId, 'graphicalAttributes', 'properties'], - fromJS(action.graphicalAttributes), - ); + return { + ...state, + ports: { + ...state.ports, + [action.portId]: port.set( + 'graphicalAttributes', + port.graphicalAttributes.set('properties', { + ...(port.graphicalAttributes?.properties || {}), + ...action.graphicalAttributes, + }), + ), + }, + }; } - + } case FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES: - if (!state.getIn(['ports', action.portId])) { + if (!state.ports?.[action.portId]) { invariant( false, `Can't remove a graphical attribute on non existing port ${action.portId}`, ); } - return state.deleteIn([ + return deleteIn(state, [ 'ports', action.portId, 'graphicalAttributes', 'properties', action.graphicalAttributesKey, ]); - case FLOWDESIGNER_PORT_SET_DATA: - if (!state.getIn(['ports', action.portId])) { + case FLOWDESIGNER_PORT_SET_DATA: { + if (!state.ports?.[action.portId]) { invariant(false, `Can't set a data on non existing port ${action.portId}`); } + const port = state.ports[action.portId] as PortRecord; try { - return state.mergeIn(['ports', action.portId, 'data'], fromJS(action.data)); + return { + ...state, + ports: { + ...state.ports, + [action.portId]: port.set( + 'data', + new PortData({ ...(port.data as any), ...action.data }), + ), + }, + }; } catch (error) { console.error(error); - return state.mergeIn( - ['ports', action.portId, 'data', 'properties'], - fromJS(action.data), - ); + return setIn(state, ['ports', action.portId, 'data', 'properties'], { + ...(port.data as any)?.properties, + ...action.data, + }); } - + } case FLOWDESIGNER_PORT_REMOVE_DATA: - if (!state.getIn(['ports', action.portId])) { + if (!state.ports?.[action.portId]) { invariant(false, `Can't remove a data on non existing port ${action.portId}`); } - return state.deleteIn(['ports', action.portId, 'data', 'properties', action.dataKey]); + return deleteIn(state, ['ports', action.portId, 'data', 'properties', action.dataKey]); case FLOWDESIGNER_PORT_REMOVE: { - if (!state.getIn(['ports', action.portId])) { + if (!state.ports?.[action.portId]) { invariant(false, `Can not remove port ${action.portId} since it doesn't exist`); } - const port: PortRecordType | null | undefined = state.getIn(['ports', action.portId]); + const port = state.ports[action.portId] as PortRecord; if (port) { - const newState = portInLink(state, action.portId) - .reduce( - (cumulativeState: State, link: LinkRecordType) => - linkReducer(cumulativeState, removeLink(link.id)), - portOutLink(state, action.portId).reduce( - (cumulativeState: State, link: LinkRecordType) => - linkReducer(cumulativeState, removeLink(link.id)), - state, - ), - ) - .deleteIn(['ports', action.portId]) - .deleteIn([ - 'out', - state.getIn(['ports', action.portId, 'nodeId']), - action.portId, - ]) - .deleteIn([ - 'in', - state.getIn(['ports', action.portId, 'nodeId']), - action.portId, - ]); - return newState.mergeDeep({ - ports: indexPortMap( - filterPortsByDirection( - filterPortsByNode(newState.get('ports'), port.nodeId), - port.getPortDirection(), - ), + const outLinks = Object.values(portOutLink(state, action.portId)); + const inLinks = Object.values(portInLink(state, action.portId)); + + let newState: State = [...outLinks, ...inLinks].reduce( + (cumulativeState, link) => + linkReducer(cumulativeState, removeLink((link as LinkRecordType).id)), + state, + ); + + newState = deleteIn(newState, ['ports', action.portId]); + newState = deleteIn(newState, ['out', port.nodeId, action.portId]); + newState = deleteIn(newState, ['in', port.nodeId, action.portId]); + + const reindexed = indexPortMap( + filterPortsByDirection( + filterPortsByNode(newState.ports, port.nodeId), + port.getPortDirection(), ), - }); + ); + + return { + ...newState, + ports: { + ...newState.ports, + ...reindexed, + }, + }; } return state; } diff --git a/packages/flow-designer/src/reducers/state-utils.ts b/packages/flow-designer/src/reducers/state-utils.ts new file mode 100644 index 00000000000..a8ad3678858 --- /dev/null +++ b/packages/flow-designer/src/reducers/state-utils.ts @@ -0,0 +1,27 @@ +import cloneDeep from 'lodash/cloneDeep'; +import get from 'lodash/get'; +import set from 'lodash/set'; + +/** + * Immutable setIn helper for plain Redux state objects. + * Deep-clones obj then sets the value at path. + */ +export function setIn(obj: T, path: (string | number)[], value: any): T { + const clone = cloneDeep(obj); + set(clone, path, value); + return clone; +} + +/** + * Immutable deleteIn helper for plain Redux state objects. + * Deep-clones obj then deletes the key at path. + */ +export function deleteIn(obj: T, path: (string | number)[]): T { + const clone = cloneDeep(obj); + if (path.length === 0) return clone; + const parent = path.length === 1 ? clone : get(clone, path.slice(0, -1)); + if (parent != null) { + delete (parent as any)[path[path.length - 1]]; + } + return clone; +} diff --git a/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap b/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap index 0ecdd881426..922765ec579 100644 --- a/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap +++ b/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Testing node selectors > node1 should have 7 successors 1`] = ` -Immutable.Set [ +Set { "id8", "id7", "id5", @@ -9,28 +9,28 @@ Immutable.Set [ "id6", "id4", "id2", -] +} `; -exports[`Testing node selectors > node1 should not have any predecessors 1`] = `Immutable.Set []`; +exports[`Testing node selectors > node1 should not have any predecessors 1`] = `Set {}`; exports[`Testing node selectors > node4 should have node1, node2 as predecessors 1`] = ` -Immutable.Set [ +Set { "id1", "id2", -] +} `; exports[`Testing node selectors > node4 should have node6, node7, node8 as successors 1`] = ` -Immutable.Set [ +Set { "id8", "id7", "id6", -] +} `; exports[`Testing node selectors > node8 should have 7 predecessors 1`] = ` -Immutable.Set [ +Set { "id1", "id2", "id3", @@ -38,7 +38,7 @@ Immutable.Set [ "id4", "id6", "id7", -] +} `; -exports[`Testing node selectors > node8 should not have any successors 1`] = `Immutable.Set []`; +exports[`Testing node selectors > node8 should not have any successors 1`] = `Set {}`; diff --git a/packages/flow-designer/src/selectors/linkSelectors.ts b/packages/flow-designer/src/selectors/linkSelectors.ts index 58473f865b2..8546df7f89a 100644 --- a/packages/flow-designer/src/selectors/linkSelectors.ts +++ b/packages/flow-designer/src/selectors/linkSelectors.ts @@ -1,5 +1,4 @@ import { createSelector } from 'reselect'; -import { Map } from 'immutable'; import { State, PortRecordMap, @@ -9,16 +8,22 @@ import { Id, } from '../customTypings/index.d'; -const getPorts = (state: State): PortRecordMap => state.get('ports'); -const getLinks = (state: State): LinkRecordMap => state.get('links'); +const getPorts = (state: State): PortRecordMap => state.ports; +const getLinks = (state: State): LinkRecordMap => state.links; export const getDetachedLinks = createSelector( [getLinks, getPorts], (links: LinkRecordMap, ports: PortRecordMap) => - links.filter( - (link: LinkRecord) => - !ports.find((port: PortRecord) => port.id === link.sourceId) || - !ports.find((port: PortRecord) => port.id === link.targetId), + Object.fromEntries( + Object.entries(links || {}).filter( + ([, link]) => + !Object.values(ports || {}).find( + (port: PortRecord) => port.id === (link as LinkRecord).sourceId, + ) || + !Object.values(ports || {}).find( + (port: PortRecord) => port.id === (link as LinkRecord).targetId, + ), + ), ), ); @@ -28,7 +33,10 @@ export const getDetachedLinks = createSelector( * @return {Link} */ export function portOutLink(state: State, portId: Id) { - return state.get('links').filter((link: LinkRecord) => link.sourceId === portId) || Map(); + const links = state.links || {}; + return Object.fromEntries( + Object.entries(links).filter(([, link]) => (link as LinkRecord).sourceId === portId), + ); } /** @@ -37,7 +45,10 @@ export function portOutLink(state: State, portId: Id) { * @return {Link} */ export function portInLink(state: State, portId: Id) { - return state.get('links').filter((link: LinkRecord) => link.targetId === portId) || Map(); + const links = state.links || {}; + return Object.fromEntries( + Object.entries(links).filter(([, link]) => (link as LinkRecord).targetId === portId), + ); } /** @@ -46,9 +57,11 @@ export function portInLink(state: State, portId: Id) { * @return number */ export function outLink(state: State, nodeId: Id) { - return state - .getIn(['out', nodeId]) - .reduce((reduction: PortRecordMap, port: PortRecord) => reduction.merge(port), Map()); + const outMap = state.out?.[nodeId] || {}; + return Object.values(outMap).reduce>( + (reduction, portLinks) => ({ ...reduction, ...(portLinks as Record) }), + {}, + ); } /** @@ -57,9 +70,11 @@ export function outLink(state: State, nodeId: Id) { * @return number */ export function inLink(state: State, nodeId: Id) { - return state - .getIn(['in', nodeId]) - .reduce((reduction: PortRecordMap, port: PortRecord) => reduction.merge(port), Map()); + const inMap = state.in?.[nodeId] || {}; + return Object.values(inMap).reduce>( + (reduction, portLinks) => ({ ...reduction, ...(portLinks as Record) }), + {}, + ); } export default getDetachedLinks; diff --git a/packages/flow-designer/src/selectors/nodeSelectors.test.ts b/packages/flow-designer/src/selectors/nodeSelectors.test.ts index 5afa3b1bc85..b34db50b956 100644 --- a/packages/flow-designer/src/selectors/nodeSelectors.test.ts +++ b/packages/flow-designer/src/selectors/nodeSelectors.test.ts @@ -1,19 +1,12 @@ -import { List, Map, OrderedMap } from 'immutable'; import * as Selectors from './nodeSelectors'; import { NodeRecord, NestedNodeRecord, PortRecord, LinkRecord, + LinkGraphicalAttributes, } from '../constants/flowdesigner.model'; -import { - State, - NodeRecord as NodeRecordType, - NestedNodeRecord as NestedNodeRecordType, - PortRecord as PortRecordType, - LinkRecord as LinkRecordType, - Id, -} from '../customTypings/index.d'; +import { State } from '../customTypings/index.d'; describe('Testing node selectors', () => { const node1 = new NodeRecord({ @@ -132,108 +125,117 @@ describe('Testing node selectors', () => { id: 'id1', sourceId: 'id1', targetId: 'id2', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link2 = new LinkRecord({ id: 'id2', sourceId: 'id3', targetId: 'id5', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link3 = new LinkRecord({ id: 'id3', sourceId: 'id6', targetId: 'id9', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link4 = new LinkRecord({ id: 'id4', sourceId: 'id10', targetId: 'id13', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link5 = new LinkRecord({ id: 'id5', sourceId: 'id15', targetId: 'id16', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link6 = new LinkRecord({ id: 'id6', sourceId: 'id4', targetId: 'id7', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link7 = new LinkRecord({ id: 'id7', sourceId: 'id8', targetId: 'id11', - graphicalAttributes: Map().set('attr', 'attr'), + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), }); const link8 = new LinkRecord({ id: 'id8', sourceId: 'id12', targetId: 'id14', - graphicalAttributes: Map().set('attr', 'attr'), - }); - - const givenState: State = Map({ - nodes: Map() - .set('id1', node1) - .set('id2', node2) - .set('id3', node3) - .set('id4', node4) - .set('id5', node5) - .set('id6', node6) - .set('id7', node7) - .set('id8', node8), - // eslint-disable-next-line new-cap - ports: OrderedMap() - .set('id1', port1) - .set('id2', port2) - .set('id3', port3) - .set('id4', port4) - .set('id5', port5) - .set('id6', port6) - .set('id7', port7) - .set('id8', port8) - .set('id9', port9) - .set('id10', port10) - .set('id11', port11) - .set('id12', port12) - .set('id13', port13) - .set('id14', port14) - .set('id15', port15) - .set('id16', port16), - links: Map() - .set('id1', link1) - .set('id2', link2) - .set('id3', link3) - .set('id4', link4) - .set('id5', link5) - .set('id6', link6) - .set('id7', link7) - .set('id8', link8), - parents: Map>() - .set('id1', Map({})) - .set('id2', Map({ id1: 'id1' })) - .set('id3', Map({ id2: 'id2' })) - .set('id4', Map({ id2: 'id2' })) - .set('id5', Map({ id3: 'id3' })) - .set('id6', Map({ id4: 'id4' })) - .set('id7', Map({ id5: 'id5', id6: 'id6' })) - .set('id8', Map({ id7: 'id7' })), - childrens: Map>() - .set('id1', Map({ id2: 'id2' })) - .set('id2', Map({ id3: 'id3', id4: 'id4' })) - .set('id3', Map({ id5: 'id5' })) - .set('id4', Map({ id6: 'id6' })) - .set('id5', Map({ id7: 'id7' })) - .set('id6', Map({ id7: 'id7' })) - .set('id7', Map({ id8: 'id8' })) - .set('id8', Map({})), - }); + graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + }); + + const givenState: State = { + nodes: { + id1: node1, + id2: node2, + id3: node3, + id4: node4, + id5: node5, + id6: node6, + id7: node7, + id8: node8, + }, + ports: { + id1: port1, + id2: port2, + id3: port3, + id4: port4, + id5: port5, + id6: port6, + id7: port7, + id8: port8, + id9: port9, + id10: port10, + id11: port11, + id12: port12, + id13: port13, + id14: port14, + id15: port15, + id16: port16, + }, + links: { + id1: link1, + id2: link2, + id3: link3, + id4: link4, + id5: link5, + id6: link6, + id7: link7, + id8: link8, + }, + parents: { + id1: {}, + id2: { id1: 'id1' }, + id3: { id2: 'id2' }, + id4: { id2: 'id2' }, + id5: { id3: 'id3' }, + id6: { id4: 'id4' }, + id7: { id5: 'id5', id6: 'id6' }, + id8: { id7: 'id7' }, + }, + childrens: { + id1: { id2: 'id2' }, + id2: { id3: 'id3', id4: 'id4' }, + id3: { id5: 'id5' }, + id4: { id6: 'id6' }, + id5: { id7: 'id7' }, + id6: { id7: 'id7' }, + id7: { id8: 'id8' }, + id8: {}, + }, + out: {}, + in: {}, + nodeTypes: {}, + transform: { k: 1, x: 0, y: 0 }, + transformToApply: undefined, + }; it('node1 should not have any predecessors', () => { expect(Selectors.getPredecessors(givenState, 'id1')).toMatchSnapshot(); @@ -263,28 +265,32 @@ describe('Testing node selectors', () => { describe('Testing node selectors on nested nodes', () => { const nodeA1 = new NestedNodeRecord({ id: 'nodeIdA1', - components: List(), + components: {}, }); const nodeA = new NestedNodeRecord({ id: 'nodeIdA', - components: List([nodeA1]), + components: { nodeIdA1: nodeA1 }, }); const nodeB = new NestedNodeRecord({ id: 'nodeIdB', }); - const givenState: State = Map({ - nodes: Map().set('nodeIdA', nodeA).set('nodeIdB', nodeB), - // eslint-disable-next-line new-cap - ports: OrderedMap(), - links: Map(), - parents: Map>(), - childrens: Map>(), - }); + const givenState: State = { + nodes: { nodeIdA: nodeA, nodeIdB: nodeB }, + ports: {}, + links: {}, + parents: {}, + childrens: {}, + out: {}, + in: {}, + nodeTypes: {}, + transform: { k: 1, x: 0, y: 0 }, + transformToApply: undefined, + }; it('nodeA should not have 1 embeded child and node B 0 children', () => { - expect(givenState.get('nodes').get('nodeIdA').get('components').size).toBe(1); - expect(givenState.get('nodes').get('nodeIdB').get('components').size).toBe(0); + expect(Object.keys(givenState.nodes['nodeIdA'].components).length).toBe(1); + expect(Object.keys(givenState.nodes['nodeIdB'].components).length).toBe(0); }); }); diff --git a/packages/flow-designer/src/selectors/nodeSelectors.ts b/packages/flow-designer/src/selectors/nodeSelectors.ts index b2ee84486d6..3b4f33131bd 100644 --- a/packages/flow-designer/src/selectors/nodeSelectors.ts +++ b/packages/flow-designer/src/selectors/nodeSelectors.ts @@ -1,30 +1,29 @@ -import { Set } from 'immutable'; import { State, Id } from '../customTypings/index.d'; /** - * @param state Map flow state + * @param state plain flow state * @param nodeId String * @param predecessors Set list of already determined predecessors */ -export function getPredecessors(state: State, nodeId: Id, predecessors?: Set) { - return state.getIn(['parents', nodeId]).reduce( - (accumulator: Set, parentId: Id) => - getPredecessors(state, parentId, accumulator).add(parentId), - // eslint-disable-next-line new-cap - predecessors || Set(), - ); +export function getPredecessors(state: State, nodeId: Id, predecessors?: Set): Set { + const parents = state.parents?.[nodeId] ?? {}; + return Object.values(parents).reduce>((acc, parentId) => { + const deeper = getPredecessors(state, parentId as Id, acc); + deeper.add(parentId as Id); + return deeper; + }, predecessors || new Set()); } /** - * @param state Map flow state + * @param state plain flow state * @param nodeId String * @param successors Set list of already determined successors */ -export function getSuccessors(state: State, nodeId: Id, successors?: Set) { - return state.getIn(['childrens', nodeId]).reduce( - (accumulator: Set, childrenId: Id) => - getSuccessors(state, childrenId, accumulator).add(childrenId), - // eslint-disable-next-line new-cap - successors || Set(), - ); +export function getSuccessors(state: State, nodeId: Id, successors?: Set): Set { + const childrens = state.childrens?.[nodeId] ?? {}; + return Object.values(childrens).reduce>((acc, childrenId) => { + const deeper = getSuccessors(state, childrenId as Id, acc); + deeper.add(childrenId as Id); + return deeper; + }, successors || new Set()); } diff --git a/packages/flow-designer/src/selectors/portSelectors.test.ts b/packages/flow-designer/src/selectors/portSelectors.test.ts index 8f033b13ef6..35bfdd00001 100644 --- a/packages/flow-designer/src/selectors/portSelectors.test.ts +++ b/packages/flow-designer/src/selectors/portSelectors.test.ts @@ -1,41 +1,31 @@ -import { Map } from 'immutable'; import * as Selectors from './portSelectors'; import { defaultState } from '../reducers/flow.reducer'; import { LinkRecord } from '../constants/flowdesigner.model'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; import { Port } from '../api'; -import { - State, - Id, - LinkRecord as LinkRecordType, - PortRecord as PortRecordType, -} from '../customTypings/index.d'; +import { State } from '../customTypings/index.d'; const port1 = Port.create('id1', 'nodeId1', 0, PORT_SINK, 'reactComponentType'); const port2 = Port.create('id2', 'nodeId1', 0, PORT_SOURCE, 'reactComponentType'); const port3 = Port.create('id3', 'nodeId2', 0, PORT_SINK, 'reactComponentType'); const port4 = Port.create('id4', 'nodeId2', 0, PORT_SOURCE, 'reactComponentType'); -const givenState: Partial = defaultState - .set( - 'links', - Map().set( - 'id1', - new LinkRecord({ - id: 'id1', - source: 'id1', - target: 'id2', - }), - ), - ) - .set( - 'ports', - Map() - .set('id1', port1) - .set('id2', port2) - .set('id3', port3) - .set('id4', port4), - ); +const givenState: Partial = { + ...defaultState, + links: { + id1: new LinkRecord({ + id: 'id1', + source: 'id1', + target: 'id2', + }), + }, + ports: { + id1: port1, + id2: port2, + id3: port3, + id4: port4, + }, +}; describe('getEmitterPorts', () => { it('return a map with port id2 && id4', () => { @@ -43,8 +33,8 @@ describe('getEmitterPorts', () => { // when const result = Selectors.getEmitterPorts(givenState); // expect - expect(result.has('id2')).toBe(true); - expect(result.has('id4')).toBe(true); + expect('id2' in result).toBe(true); + expect('id4' in result).toBe(true); }); }); @@ -54,8 +44,8 @@ describe('getSinkPorts', () => { // when const result = Selectors.getSinkPorts(givenState); // expect - expect(result.has('id1')).toBe(true); - expect(result.has('id3')).toBe(true); + expect('id1' in result).toBe(true); + expect('id3' in result).toBe(true); }); }); @@ -65,7 +55,7 @@ describe('getEmitterPortsForNode', () => { // when const result = Selectors.getEmitterPortsForNode(givenState)('nodeId1'); // expect - expect(result.has('id2')).toBe(true); + expect('id2' in result).toBe(true); }); }); @@ -75,6 +65,6 @@ describe('getSinkPortsForNode', () => { // when const result = Selectors.getSinkPortsForNode(givenState)('nodeId1'); // expect - expect(result.has('id1')).toBe(true); + expect('id1' in result).toBe(true); }); }); diff --git a/packages/flow-designer/src/selectors/portSelectors.ts b/packages/flow-designer/src/selectors/portSelectors.ts index 55b16526ad6..60cda106408 100644 --- a/packages/flow-designer/src/selectors/portSelectors.ts +++ b/packages/flow-designer/src/selectors/portSelectors.ts @@ -1,6 +1,5 @@ import { createSelector } from 'reselect'; import memoize from 'lodash/memoize'; -import { Map } from 'immutable'; import { Port } from '../api'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; @@ -13,22 +12,22 @@ import { LinkRecord, } from '../customTypings/index.d'; -const getNodes = (state: State): NodeRecordMap => state.get('nodes'); -const getPorts = (state: State): PortRecordMap => state.get('ports'); -const getLinks = (state: State): LinkRecordMap => state.get('links'); +const getNodes = (state: State): NodeRecordMap => state.nodes; +const getPorts = (state: State): PortRecordMap => state.ports; +const getLinks = (state: State): LinkRecordMap => state.links; /** * return a list of outgoing port for this node */ export function outPort(state: State, nodeId: string) { - return state.getIn(['out', nodeId]) || Map(); + return state.out?.[nodeId] || {}; } /** * return a list of ingoing port for this node */ export function inPort(state: State, nodeId: string) { - return state.getIn(['in', nodeId]) || Map(); + return state.in?.[nodeId] || {}; } /** @@ -39,7 +38,11 @@ export const getPortsForNode = createSelector( getPorts, (ports: PortRecordMap): PortRecord => memoize((nodeId: string) => - ports.filter((port: PortRecord) => Port.getNodeId(port) === nodeId), + Object.fromEntries( + Object.entries(ports || {}).filter( + ([, port]) => Port.getNodeId(port as PortRecord) === nodeId, + ), + ), ), ); @@ -51,7 +54,11 @@ export const getPortsForNode = createSelector( export const getEmitterPorts = createSelector( getPorts, (ports: PortRecordMap): PortRecord => - ports.filter((port: any) => Port.getTopology(port) === PORT_SOURCE), + Object.fromEntries( + Object.entries(ports || {}).filter( + ([, port]) => Port.getTopology(port as any) === PORT_SOURCE, + ), + ) as any, ); /** @@ -62,7 +69,9 @@ export const getEmitterPorts = createSelector( export const getSinkPorts = createSelector( getPorts, (ports: PortRecordMap): PortRecord => - ports.filter((port: any) => Port.getTopology(port) === PORT_SINK), + Object.fromEntries( + Object.entries(ports || {}).filter(([, port]) => Port.getTopology(port as any) === PORT_SINK), + ) as any, ); /** @@ -70,8 +79,11 @@ export const getSinkPorts = createSelector( */ export const getEmitterPortsForNode = createSelector( getEmitterPorts as any, - (ports: PortRecordMap): PortRecord => (nodeId: string) => - ports.filter((port: any) => Port.getNodeId(port) === nodeId), + (ports: PortRecordMap): PortRecord => + (nodeId: string) => + Object.fromEntries( + Object.entries(ports || {}).filter(([, port]) => Port.getNodeId(port as any) === nodeId), + ), ); /** @@ -79,8 +91,11 @@ export const getEmitterPortsForNode = createSelector( */ export const getSinkPortsForNode = createSelector( getSinkPorts as any, - (ports: PortRecordMap): PortRecord => (nodeId: string) => - ports.filter((port: any) => Port.getNodeId(port) === nodeId), + (ports: PortRecordMap): PortRecord => + (nodeId: string) => + Object.fromEntries( + Object.entries(ports || {}).filter(([, port]) => Port.getNodeId(port as any) === nodeId), + ), ); /** @@ -91,12 +106,15 @@ export const getSinkPortsForNode = createSelector( */ export const getFreeSinkPorts = createSelector( [getSinkPorts, getLinks], - (sinkPorts: PortRecordMap, links: LinkRecordMap) => { - return sinkPorts.filter( - (sinkPort: PortRecord) => - !links.find((link: LinkRecord) => link.targetId === Port.getId(sinkPort)), - ) as PortRecordMap; - }, + (sinkPorts: PortRecordMap, links: LinkRecordMap) => + Object.fromEntries( + Object.entries(sinkPorts || {}).filter( + ([, sinkPort]) => + !Object.values(links || {}).find( + (link: LinkRecord) => link.targetId === Port.getId(sinkPort as PortRecord), + ), + ), + ) as PortRecordMap, ); /** @@ -108,9 +126,13 @@ export const getFreeSinkPorts = createSelector( export const getFreeEmitterPorts = createSelector( [getEmitterPorts as any, getLinks as any], (emitterPorts: PortRecordMap, links: LinkRecordMap) => - emitterPorts.filter( - (emitterPort: PortRecord) => - !links.find((link: LinkRecord) => link.sourceId === Port.getId(emitterPort)), + Object.fromEntries( + Object.entries(emitterPorts || {}).filter( + ([, emitterPort]) => + !Object.values(links || {}).find( + (link: LinkRecord) => link.sourceId === Port.getId(emitterPort as PortRecord), + ), + ), ), ); @@ -123,13 +145,18 @@ export const getFreeEmitterPorts = createSelector( export const getActionKeyedPorts = createSelector( getFreeSinkPorts as any, (freeSinkPorts: PortRecordMap) => - freeSinkPorts.filter((sinkPort: { accessKey: any }) => sinkPort.accessKey), + Object.fromEntries( + Object.entries(freeSinkPorts || {}).filter(([, sinkPort]) => (sinkPort as any).accessKey), + ), ); export const getDetachedPorts = createSelector( [getPorts as any, getNodes as any], (ports: PortRecordMap, nodes: NodeRecordMap) => - ports.filter( - (port: any) => !nodes.find((node: { id: any }) => node.id === Port.getNodeId(port)), + Object.fromEntries( + Object.entries(ports || {}).filter( + ([, port]) => + !Object.values(nodes || {}).find((node: any) => node.id === Port.getNodeId(port as any)), + ), ), ); From c75791c146548d726cc0509b62eb8cba9529c8f0 Mon Sep 17 00:00:00 2001 From: smouillour Date: Tue, 10 Mar 2026 18:27:37 +0100 Subject: [PATCH 04/16] epic 3 done and reviewed --- packages/components/package.json | 1 - .../ActionDropdown.component.jsx | 22 +++++++++---------- .../ActionDropdown/ActionDropdown.test.jsx | 17 ++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 5fdeb746307..36f134b255b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -68,7 +68,6 @@ "react-debounce-input": "^3.3.0", "react-draggable": "^4.5.0", "react-grid-layout": "^1.5.3", - "react-immutable-proptypes": "^2.2.0", "react-is": "^18.3.1", "react-popper": "^2.3.0", "react-transition-group": "^2.9.0", diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx index 36c0350e3c3..578be68de7d 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx @@ -1,9 +1,10 @@ import { Component } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; import get from 'lodash/get'; import classNames from 'classnames'; +// TODO(epic-3+): remove once all consumers have migrated away from ImmutableJS. +// Kept for runtime backward-compat: callers (e.g. containers/ActionDropdown.connect) may still pass an ImmutableList. import { Iterable } from 'immutable'; import { DropdownButton, MenuItem } from '@talend/react-bootstrap'; import { withTranslation } from 'react-i18next'; @@ -96,6 +97,7 @@ function renderMutableMenuItem(item, index, getComponent) { } function getMenuItem(item, index, getComponent) { + // TODO(epic-3+): remove this branch once all consumers pass plain JS objects. if (Iterable.isIterable(item)) { return renderMutableMenuItem(item.toJS(), index, getComponent); } @@ -265,6 +267,7 @@ class ActionDropdown extends Component { }} noCaret > + {/* items.size: ImmutableList backward-compat guard during migration — TODO(epic-3+): remove */} {!children && !items.length && !items.size && !loading && !components && ( {t('ACTION_DROPDOWN_EMPTY', { defaultValue: 'No options' })} @@ -315,16 +318,13 @@ ActionDropdown.propTypes = { pullRight: PropTypes.bool, icon: PropTypes.string, iconTransform: PropTypes.string, - items: PropTypes.oneOfType([ - PropTypes.arrayOf( - PropTypes.shape({ - icon: PropTypes.string, - label: PropTypes.string, - ...MenuItem.propTypes, - }), - ), - ImmutablePropTypes.list, - ]), + items: PropTypes.arrayOf( + PropTypes.shape({ + icon: PropTypes.string, + label: PropTypes.string, + ...MenuItem.propTypes, + }), + ), badge: PropTypes.shape({ className: PropTypes.string, label: PropTypes.string, diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx index 521ba6be240..4413b64a9ea 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx @@ -126,6 +126,23 @@ describe('getMenuItem', () => { }); }); +// Regression: react-immutable-proptypes was removed in story 3-1. Verify items accepts plain arrays. +describe('ActionDropdown — plain array items (regression: story 3-1)', () => { + it('should render all items from a plain JS array', () => { + const props = { + id: 'dropdown-id', + label: 'Dropdown', + items: [ + { id: 'item1', label: 'Alpha' }, + { id: 'item2', label: 'Beta' }, + ], + }; + render(); + expect(screen.getByRole('menuitem', { name: 'Alpha' })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: 'Beta' })).toBeInTheDocument(); + }); +}); + describe('InjectDropdownMenuItem', () => { it('should render MenuItem with props divider', () => { render( From f98568924b2fb0074ecc4d1797b420969ab563d9 Mon Sep 17 00:00:00 2001 From: smouillour Date: Wed, 11 Mar 2026 10:46:45 +0100 Subject: [PATCH 05/16] epic 4 done and reviewed --- .../ACKDispatcher/ACKDispatcher.container.js | 16 +- .../ACKDispatcher/ACKDispatcher.test.jsx | 49 +++-- .../__snapshots__/ACKDispatcher.test.jsx.snap | 4 +- .../reducers/__snapshots__/ack.test.js.snap | 32 ++-- packages/cmf-cqrs/src/reducers/ack.js | 28 ++- packages/cmf-cqrs/src/reducers/ack.test.js | 23 +-- .../__tests__/selectors/collections.test.js | 120 ------------ .../cmf/__tests__/selectors/index.test.js | 13 -- packages/cmf/src/selectors/collections.js | 62 +++++- .../cmf/src/selectors/collections.test.js | 179 ++++++++++++++++++ packages/cmf/src/selectors/components.js | 62 ++++++ packages/cmf/src/selectors/components.test.js | 110 +++++++++++ packages/cmf/src/selectors/index.js | 2 + packages/cmf/src/selectors/index.test.js | 19 ++ .../src/ComponentForm/ComponentForm.sagas.js | 3 +- .../DeleteResource/DeleteResource.connect.js | 7 +- .../src/GuidedTour/GuidedTour.connect.js | 3 +- packages/containers/src/List/List.connect.js | 3 +- packages/containers/src/List/selector.js | 43 +++-- packages/containers/src/List/selector.test.js | 10 +- .../src/Notification/Notification.test.jsx | 10 +- .../src/Notification/clearNotifications.js | 12 +- .../src/Notification/pushNotification.js | 17 +- .../containers/src/TabBar/TabBar.connect.js | 3 +- .../containers/src/TabBar/TabBar.selectors.js | 13 +- packages/sagas/src/pending/pending.js | 20 +- packages/sagas/src/pending/pending.test.js | 32 +--- 27 files changed, 603 insertions(+), 292 deletions(-) delete mode 100644 packages/cmf/__tests__/selectors/collections.test.js delete mode 100644 packages/cmf/__tests__/selectors/index.test.js create mode 100644 packages/cmf/src/selectors/collections.test.js create mode 100644 packages/cmf/src/selectors/components.js create mode 100644 packages/cmf/src/selectors/components.test.js create mode 100644 packages/cmf/src/selectors/index.test.js diff --git a/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.container.js b/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.container.js index 40ad69670bf..14dab60cf43 100644 --- a/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.container.js +++ b/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.container.js @@ -1,12 +1,11 @@ import { useState } from 'react'; import PropTypes from 'prop-types'; -import { Map } from 'immutable'; import cmf, { cmfConnect, useCMFContext } from '@talend/react-cmf'; import { randomUUID } from '@talend/utils'; import { deleteACK } from '../../actions/ack'; -export const DEFAULT_STATE = new Map({}); +export const DEFAULT_STATE = {}; /** * { @@ -40,14 +39,11 @@ function ACKDispatcher(props) { cache[uuid].push(requestId); } } - (props.acks || []) - .filter(ack => ack.get('received') === true && ack.get('actionCreator')) - .forEach((ack, requestId) => { - let data = ack.get('data'); - if (data === undefined) { - data = {}; - } - dispatchAndUpdateAck(ack.get('actionCreator'), data, requestId); + Object.entries(props.acks || {}) + .filter(([, ack]) => ack.received === true && ack.actionCreator) + .forEach(([requestId, ack]) => { + const data = ack.data !== undefined ? ack.data : {}; + dispatchAndUpdateAck(ack.actionCreator, data, requestId); }); return null; diff --git a/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.jsx b/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.jsx index 5eab8cb186f..7feba7100a7 100644 --- a/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.jsx +++ b/packages/cmf-cqrs/src/components/ACKDispatcher/ACKDispatcher.test.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Map } from 'immutable'; import { render } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; @@ -16,23 +15,23 @@ describe('Container ACKDispatcher', () => { it('should render nothing', () => { const { container } = render( - + , ); expect(container.firstChild).toBeEmptyDOMElement(); }); it('should processACK call dispatch', () => { - const acks = Map({ - 123: new Map({ + const acks = { + 123: { actionCreator: 'actionCreator', data: { foo: 'bar' }, received: true, - }), - 456: new Map({ + }, + 456: { actionCreator: 'actionCreatorBis', data: { foo: 'baz' }, - }), - }); + }, + }; const registry = mock.store.registry(); const mocked = vi.fn(); @@ -70,14 +69,14 @@ describe('Container ACKDispatcher', () => { registry['actionCreator:myActionCreator'] = myActionCreator; const { rerender } = render( - + , ); rerender( , ); @@ -95,20 +94,20 @@ describe('Container ACKDispatcher', () => { }); it(`should dispatch call props.dispatch even when ack only when both received is true, and action exist meaning that we can receive ack before creation request is resolve`, () => { - const acks = Map({ - 42: new Map({ + const acks = { + 42: { received: true, - }), - 123: new Map({ + }, + 123: { actionCreator: 'actionCreator', data: { foo: 'bar' }, received: true, - }), - 456: new Map({ + }, + 456: { actionCreator: 'actionCreatorBis', data: { foo: 'baz' }, - }), - }); + }, + }; const registry = mock.store.registry(); const mocked = vi.fn(); @@ -141,19 +140,15 @@ describe('Connected ACKDispatcher', () => { it('should mapStateToProps', () => { const state = { cmf: { - components: new Map({ - ACKDispatcher: { - ACKDispatcher: DEFAULT_STATE.toJS(), - }, - }), + components: {}, }, - ack: new Map({ - 123: new Map({ + ack: { + 123: { actionCreator: 'test', data: { foo: 'bar' }, received: true, - }), - }), + }, + }, }; const props = mapStateToProps(state); expect(typeof props).toBe('object'); diff --git a/packages/cmf-cqrs/src/components/ACKDispatcher/__snapshots__/ACKDispatcher.test.jsx.snap b/packages/cmf-cqrs/src/components/ACKDispatcher/__snapshots__/ACKDispatcher.test.jsx.snap index ceff06e09ae..aad99a2f177 100644 --- a/packages/cmf-cqrs/src/components/ACKDispatcher/__snapshots__/ACKDispatcher.test.jsx.snap +++ b/packages/cmf-cqrs/src/components/ACKDispatcher/__snapshots__/ACKDispatcher.test.jsx.snap @@ -2,8 +2,8 @@ exports[`Connected ACKDispatcher > should mapStateToProps 1`] = ` { - "acks": Immutable.Map { - "123": Immutable.Map { + "acks": { + "123": { "actionCreator": "test", "data": { "foo": "bar", diff --git a/packages/cmf-cqrs/src/reducers/__snapshots__/ack.test.js.snap b/packages/cmf-cqrs/src/reducers/__snapshots__/ack.test.js.snap index 408d3520256..48a7b2098fb 100644 --- a/packages/cmf-cqrs/src/reducers/__snapshots__/ack.test.js.snap +++ b/packages/cmf-cqrs/src/reducers/__snapshots__/ack.test.js.snap @@ -1,31 +1,31 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`reducers.ack.ackReducer > should reduce addContext 1`] = ` -Immutable.Map { - "123": Immutable.Map { +{ + "123": { + "actionCreator": "my super action creator", "data": { "foo": "bar", }, - "actionCreator": "my super action creator", }, } `; exports[`reducers.ack.ackReducer > should reduce deleteACK 1`] = ` -Immutable.Map { - "456": Immutable.Map { +{ + "456": { "foo": "bar", }, } `; exports[`reducers.ack.ackReducer > should reduce receiveMessage 1`] = ` -Immutable.Map { - "123": Immutable.Map { +{ + "123": { "foo": "bar", "received": true, }, - "456": Immutable.Map { + "456": { "foo": "bar", }, } @@ -33,12 +33,12 @@ Immutable.Map { exports[`reducers.ackProcessed higher order reducer > should reduce addContext 1`] = ` { - "ack": Immutable.Map { - "123": Immutable.Map { + "ack": { + "123": { + "actionCreator": "my super action creator", "data": { "foo": "bar", }, - "actionCreator": "my super action creator", }, }, } @@ -46,8 +46,8 @@ exports[`reducers.ackProcessed higher order reducer > should reduce addContext 1 exports[`reducers.ackProcessed higher order reducer > should reduce deleteACK 1`] = ` { - "ack": Immutable.Map { - "456": Immutable.Map { + "ack": { + "456": { "foo": "bar", }, }, @@ -56,12 +56,12 @@ exports[`reducers.ackProcessed higher order reducer > should reduce deleteACK 1` exports[`reducers.ackProcessed higher order reducer > should reduce receiveMessage 1`] = ` { - "ack": Immutable.Map { - "123": Immutable.Map { + "ack": { + "123": { "foo": "bar", "received": true, }, - "456": Immutable.Map { + "456": { "foo": "bar", }, }, diff --git a/packages/cmf-cqrs/src/reducers/ack.js b/packages/cmf-cqrs/src/reducers/ack.js index a7d45120f54..8608ce7a15c 100644 --- a/packages/cmf-cqrs/src/reducers/ack.js +++ b/packages/cmf-cqrs/src/reducers/ack.js @@ -1,7 +1,6 @@ -import { Map } from 'immutable'; import { ACK_ADD_CONTEXT, ACK_RECEIVE_MESSAGE, ACK_DELETE } from '../constants/index'; -const DEFAULT_STATE = new Map({}); +const DEFAULT_STATE = {}; /** * ackReducer @@ -12,13 +11,26 @@ const DEFAULT_STATE = new Map({}); export default function ackReducer(state = DEFAULT_STATE, action) { switch (action.type) { case ACK_ADD_CONTEXT: - return state - .setIn([action.requestId, 'data'], action.data) - .setIn([action.requestId, 'actionCreator'], action.actionCreator); + return { + ...state, + [action.requestId]: { + ...(state[action.requestId] || {}), + data: action.data, + actionCreator: action.actionCreator, + }, + }; case ACK_RECEIVE_MESSAGE: - return state.setIn([action.requestId, 'received'], true); - case ACK_DELETE: - return state.remove(action.requestId); + return { + ...state, + [action.requestId]: { + ...(state[action.requestId] || {}), + received: true, + }, + }; + case ACK_DELETE: { + const { [action.requestId]: _, ...rest } = state; + return rest; + } default: return state; } diff --git a/packages/cmf-cqrs/src/reducers/ack.test.js b/packages/cmf-cqrs/src/reducers/ack.test.js index af7ff0121b0..00563b0cb05 100644 --- a/packages/cmf-cqrs/src/reducers/ack.test.js +++ b/packages/cmf-cqrs/src/reducers/ack.test.js @@ -1,4 +1,3 @@ -import { Map, fromJS } from 'immutable'; import ackReducer, { ackProcessed } from './ack'; import { addContext, receiveMessage, deleteACK } from '../actions/ack'; @@ -16,10 +15,10 @@ describe('reducers.ack.ackReducer', () => { }); it('should reduce receiveMessage', () => { const state = ackReducer( - fromJS({ + { 123: { foo: 'bar' }, 456: { foo: 'bar' }, - }), + }, receiveMessage(null, { requestId: '123', }), @@ -28,10 +27,10 @@ describe('reducers.ack.ackReducer', () => { }); it('should reduce deleteACK', () => { const state = ackReducer( - fromJS({ + { 123: { foo: 'bar' }, 456: { foo: 'bar' }, - }), + }, deleteACK(null, { requestId: '123', }), @@ -41,11 +40,13 @@ describe('reducers.ack.ackReducer', () => { it('should return default state', () => { const state = ackReducer(undefined, { type: 'notsupported' }); - expect(Map.isMap(state)).toBe(true); + expect(typeof state).toBe('object'); + expect(state).not.toBeNull(); + expect(Array.isArray(state)).toBe(false); }); it('should return input state on non supported action', () => { - const state = new Map(); + const state = {}; const newState = ackReducer(state, { type: 'notsupported' }); expect(state).toBe(newState); }); @@ -73,10 +74,10 @@ describe('reducers.ackProcessed higher order reducer', () => { it('should reduce receiveMessage', () => { const state = ackProcessed( { - ack: fromJS({ + ack: { 123: { foo: 'bar' }, 456: { foo: 'bar' }, - }), + }, }, { ack: receiveMessage(null, { @@ -89,10 +90,10 @@ describe('reducers.ackProcessed higher order reducer', () => { it('should reduce deleteACK', () => { const state = ackProcessed( { - ack: fromJS({ + ack: { 123: { foo: 'bar' }, 456: { foo: 'bar' }, - }), + }, }, { ack: deleteACK(null, { diff --git a/packages/cmf/__tests__/selectors/collections.test.js b/packages/cmf/__tests__/selectors/collections.test.js deleted file mode 100644 index d2fdf5f0d2d..00000000000 --- a/packages/cmf/__tests__/selectors/collections.test.js +++ /dev/null @@ -1,120 +0,0 @@ -import { Map, List } from 'immutable'; -import selectors from '../../src/selectors'; - -describe('selectors.collections', () => { - describe('toJS', () => { - const state = { - cmf: { - collections: new Map({ - foo: new Map({ - bar: new Map({ - hello: 'world', - }), - }), - }), - }, - }; - const result1 = selectors.collections.toJS(state, 'foo.bar'); - const result2 = selectors.collections.toJS(state, 'foo.bar'); - expect(result1).toEqual({ hello: 'world' }); - expect(result1).toBe(result2); - }); - describe('get', () => { - const collection = new Map({ id: 'id' }); - const collectionSubset = new Map({ subset: 'subset' }); - const collectionWithSubset = new Map({ collectionSubset }); - const state = { - cmf: { - collections: new Map({ - collection, - collectionWithSubset, - }), - }, - }; - it('try to find the collection if collectionPath is a string', () => { - expect(selectors.collections.get(state, 'collection')).toEqual(collection); - }); - - it('try to find the collection subset if collectionPath is an array', () => { - expect( - selectors.collections.get(state, ['collectionWithSubset', 'collectionSubset']), - ).toEqual(collectionSubset); - }); - - it('throw an exception if collection path is neither a string or an array', () => { - expect(() => { - selectors.collections.get(state, {}); - }).toThrowError(`Type mismatch: collectionPath should be a string or an array of string -got [object Object]`); - }); - }); -}); - -const id = 'id'; -const item = new Map({ id }); -const collectionSubset = new List([item]); -const collectionWithSubset = new Map({ collectionSubset }); -const state = { - cmf: { - collections: new Map({ - isList: new List([item]), - isNotList: new Map({ id: item }), - collectionWithSubset, - }), - }, -}; - -describe('find(state, pathDescriptor, resourceId)', () => { - it.each([ - { - name: 'work if collection path is a string', - state, - pathDescriptor: 'isList', - resourceId: id, - result: item, - }, - { - name: 'work if collection path is a Array', - state, - pathDescriptor: ['collectionWithSubset', 'collectionSubset'], - resourceId: id, - result: item, - }, - { - name: "undefined if id doens't match", - state, - pathDescriptor: 'isList', - resourceId: 'notFound', - result: undefined, - }, - ])('$name', opts => { - expect( - selectors.collections.findListItem(opts.state, opts.pathDescriptor, opts.resourceId), - ).toBe(opts.result); - }); -}); - -describe('selectors.collections.findListItem(state, pathDescriptor, resourceId)', () => { - it.each([ - { - name: 'throw if collection path is not a List', - state, - pathDescriptor: 'isNotList', - resourceId: id, - result: `Type mismatch: isNotList does not resolve as an instance of Immutable.List, -got Map { "id": Map { "id": "id" } }`, - }, - { - name: "throw if collection can't be found", - state, - pathDescriptor: 'notFound', - resourceId: id, - result: `Type mismatch: notFound does not resolve as an instance of Immutable.List, -got undefined`, - }, - ])('$name', opts => { - expect(() => { - selectors.collections.findListItem(opts.state, opts.pathDescriptor, opts.resourceId); - }).toThrow(opts.result); - }); -}); diff --git a/packages/cmf/__tests__/selectors/index.test.js b/packages/cmf/__tests__/selectors/index.test.js deleted file mode 100644 index ad1bb8fb54c..00000000000 --- a/packages/cmf/__tests__/selectors/index.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import selectors from '../../src/selectors'; -import * as collections from '../../src/selectors/collections'; -import toJS from '../../src/selectors/toJS'; - -describe('selectors', () => { - it('should expose collections selectors', () => { - expect(selectors.collections).toEqual(collections); - }); - it('should expose toJS selectors', () => { - expect(selectors.toJS).toEqual(toJS); - }); -}); - diff --git a/packages/cmf/src/selectors/collections.js b/packages/cmf/src/selectors/collections.js index 92c483b2e7b..7366b282cd3 100644 --- a/packages/cmf/src/selectors/collections.js +++ b/packages/cmf/src/selectors/collections.js @@ -1,4 +1,4 @@ -import { List } from 'immutable'; +import { Map, List } from 'immutable'; import getToJSMemoized from './toJS'; export function getAll(state) { @@ -53,3 +53,63 @@ export function toJS(state, path) { } return selectors[joinedPath](state); } + +/** + * Get a collection as a plain JS object/array (no Immutable types leaked). + * @param {Object} state + * @param {String} collectionId + * @returns {Object|Array|undefined} + */ +export function getCollectionPlain(state, collectionId) { + const collection = state.cmf.collections.get(collectionId); + if (collection == null) return undefined; + if (typeof collection.toJS === 'function') return collection.toJS(); + return collection; +} + +/** + * Get the items from a collection, handling both Map-wrapped and direct List forms. + * Covers the `Map.isMap(collection) ? collection.get('items') : collection` pattern + * from containers/src/List/selector.js. + * @param {Object} state + * @param {String} collectionId + * @returns {Array|undefined} + */ +function extractItems(collection) { + if (Map.isMap(collection)) { + return collection.get('items'); + } + return collection; +} + +export function getCollectionItems(state, collectionId) { + const collection = state.cmf.collections.get(collectionId); + if (collection == null) return undefined; + const items = extractItems(collection); + if (items == null) return undefined; + if (typeof items.toJS === 'function') return items.toJS(); + return items; +} + +/** + * Find an item in a collection by its id field, returning a plain JS object. + * Covers the `.find(r => r.get('id') === id)` pattern from + * containers/src/DeleteResource/DeleteResource.connect.js. + * @param {Object} state + * @param {String} collectionId + * @param {String} itemId + * @returns {Object|undefined} + */ +export function getCollectionItem(state, collectionId, itemId) { + const collection = state.cmf.collections.get(collectionId); + if (collection == null) return undefined; + const items = extractItems(collection); + if (!items || typeof items.find !== 'function') return undefined; + const found = items.find(item => { + if (item && typeof item.get === 'function') return item.get('id') === itemId; + return item && item.id === itemId; + }); + if (found == null) return undefined; + if (typeof found.toJS === 'function') return found.toJS(); + return found; +} diff --git a/packages/cmf/src/selectors/collections.test.js b/packages/cmf/src/selectors/collections.test.js new file mode 100644 index 00000000000..c6b8eae72d1 --- /dev/null +++ b/packages/cmf/src/selectors/collections.test.js @@ -0,0 +1,179 @@ +import { Map, List } from 'immutable'; +import { describe, it, expect } from 'vitest'; +import { + get, + getAll, + findListItem, + toJS, + getCollectionPlain, + getCollectionItems, + getCollectionItem, +} from './collections'; + +describe('collections.get', () => { + const collection = new Map({ id: 'id' }); + const collectionSubset = new Map({ subset: 'subset' }); + const collectionWithSubset = new Map({ collectionSubset }); + const state = { + cmf: { + collections: new Map({ + collection, + collectionWithSubset, + }), + }, + }; + + it('returns collection when path is a string', () => { + expect(get(state, 'collection')).toEqual(collection); + }); + + it('returns collection subset when path is an array', () => { + expect(get(state, ['collectionWithSubset', 'collectionSubset'])).toEqual(collectionSubset); + }); + + it('throws when path is not a string or array', () => { + expect(() => get(state, {})).toThrow( + 'Type mismatch: collectionPath should be a string or an array of string', + ); + }); +}); + +describe('collections.getAll', () => { + const collectionsMap = new Map({ foo: new Map({ bar: 'baz' }) }); + const state = { cmf: { collections: collectionsMap } }; + + it('returns the entire collections map', () => { + expect(getAll(state)).toBe(collectionsMap); + }); +}); + +describe('collections.findListItem', () => { + const id = 'id'; + const item = new Map({ id }); + const state = { + cmf: { + collections: new Map({ + isList: new List([item]), + isNotList: new Map({ id: item }), + }), + }, + }; + + it('finds an item by id in a List collection', () => { + expect(findListItem(state, 'isList', id)).toBe(item); + }); + + it('returns undefined when id does not match', () => { + expect(findListItem(state, 'isList', 'notFound')).toBeUndefined(); + }); + + it('throws when collection is not a List', () => { + expect(() => findListItem(state, 'isNotList', id)).toThrow('Type mismatch'); + }); +}); + +describe('collections.toJS', () => { + const state = { + cmf: { + collections: new Map({ + foo: new Map({ bar: new Map({ hello: 'world' }) }), + }), + }, + }; + + it('converts an Immutable object to plain JS', () => { + expect(toJS(state, 'foo.bar')).toEqual({ hello: 'world' }); + }); + + it('memoizes the result across identical state references', () => { + const result1 = toJS(state, 'foo.bar'); + const result2 = toJS(state, 'foo.bar'); + expect(result1).toBe(result2); + }); +}); + +describe('collections.getCollectionPlain', () => { + const state = { + cmf: { + collections: new Map({ + myList: new List([new Map({ id: '1', name: 'Alice' })]), + }), + }, + }; + + it('returns a plain JS array for an Immutable List collection', () => { + const result = getCollectionPlain(state, 'myList'); + expect(result).toEqual([{ id: '1', name: 'Alice' }]); + expect(result).not.toHaveProperty('get'); + }); + + it('returns undefined when collectionId is not found', () => { + expect(getCollectionPlain(state, 'notFound')).toBeUndefined(); + }); +}); + +describe('collections.getCollectionItems', () => { + const state = { + cmf: { + collections: new Map({ + directList: new List([new Map({ id: '1' })]), + wrappedList: new Map({ items: new List([new Map({ id: '2' })]) }), + emptyWrapped: new Map({ other: 'value' }), + }), + }, + }; + + it('returns plain array when collection is a direct List', () => { + const result = getCollectionItems(state, 'directList'); + expect(result).toEqual([{ id: '1' }]); + expect(Array.isArray(result)).toBe(true); + }); + + it('returns plain array from items key when collection is a Map wrapping items', () => { + const result = getCollectionItems(state, 'wrappedList'); + expect(result).toEqual([{ id: '2' }]); + expect(Array.isArray(result)).toBe(true); + }); + + it('returns undefined when collection is a Map without items key', () => { + expect(getCollectionItems(state, 'emptyWrapped')).toBeUndefined(); + }); + + it('returns undefined when collectionId is not found', () => { + expect(getCollectionItems(state, 'notFound')).toBeUndefined(); + }); +}); + +describe('collections.getCollectionItem', () => { + const items = new List([ + new Map({ id: 'a', label: 'Alpha' }), + new Map({ id: 'b', label: 'Beta' }), + ]); + const state = { + cmf: { + collections: new Map({ + directList: items, + wrappedList: new Map({ items }), + }), + }, + }; + + it('finds item by id in a direct List collection', () => { + const result = getCollectionItem(state, 'directList', 'a'); + expect(result).toEqual({ id: 'a', label: 'Alpha' }); + expect(result).not.toHaveProperty('get'); + }); + + it('finds item by id in a Map-wrapped collection', () => { + const result = getCollectionItem(state, 'wrappedList', 'b'); + expect(result).toEqual({ id: 'b', label: 'Beta' }); + }); + + it('returns undefined when item id is not found', () => { + expect(getCollectionItem(state, 'directList', 'xxx')).toBeUndefined(); + }); + + it('returns undefined when collection is not found', () => { + expect(getCollectionItem(state, 'notFound', 'a')).toBeUndefined(); + }); +}); diff --git a/packages/cmf/src/selectors/components.js b/packages/cmf/src/selectors/components.js new file mode 100644 index 00000000000..4ba5093e9cc --- /dev/null +++ b/packages/cmf/src/selectors/components.js @@ -0,0 +1,62 @@ +/** + * Selectors for CMF component state (state.cmf.components). + * All selectors return plain JS values — no Immutable objects are leaked. + */ + +/** + * Get the state of a specific component instance as a plain JS object. + * Covers the `state.cmf.components.getIn([componentName, instanceId])` pattern. + * @param {Object} state - Redux state + * @param {String} componentName - Component name (e.g., 'Container(Notification)') + * @param {String} instanceId - Component instance id (e.g., 'default') + * @returns {Object|undefined} + */ +export function getComponentState(state, componentName, instanceId) { + const compState = state.cmf.components.getIn([componentName, instanceId]); + if (compState == null) return undefined; + if (typeof compState.toJS === 'function') return compState.toJS(); + return compState; +} + +/** + * Get all instances of a component as a map of instanceId → plain JS state. + * @param {Object} state - Redux state + * @param {String} componentName - Component name + * @returns {Object|undefined} + */ +export function getAllComponentStates(state, componentName) { + const instances = state.cmf.components.get(componentName); + if (instances == null) return undefined; + if (typeof instances.toJS === 'function') return instances.toJS(); + return instances; +} + +/** + * Get a specific property from a component instance state as a plain value. + * Covers the `state.cmf.components.getIn([...path], default)` pattern used in + * containers/src/Notification/pushNotification.js. + * @param {Object} state - Redux state + * @param {String} componentName - Component name + * @param {String} instanceId - Instance id + * @param {String} property - Property key + * @returns {*} plain JS value + */ +export function getComponentStateProperty( + state, + componentName, + instanceId, + property, + defaultValue, +) { + const compState = state.cmf.components.getIn([componentName, instanceId]); + if (compState == null) return defaultValue; + let val; + if (typeof compState.get === 'function') { + val = compState.get(property); + } else { + val = compState[property]; + } + if (val == null) return defaultValue; + if (typeof val.toJS === 'function') return val.toJS(); + return val; +} diff --git a/packages/cmf/src/selectors/components.test.js b/packages/cmf/src/selectors/components.test.js new file mode 100644 index 00000000000..21c3a116181 --- /dev/null +++ b/packages/cmf/src/selectors/components.test.js @@ -0,0 +1,110 @@ +import { Map, List, fromJS } from 'immutable'; +import { describe, it, expect } from 'vitest'; +import { getComponentState, getAllComponentStates, getComponentStateProperty } from './components'; + +const makeState = componentsMap => ({ + cmf: { + components: fromJS(componentsMap), + }, +}); + +describe('components.getComponentState', () => { + const state = makeState({ + 'Container(List)': { + default: { searchQuery: 'hello', sortBy: 'name' }, + other: { searchQuery: 'world' }, + }, + }); + + it('returns plain JS object for a known component instance', () => { + const result = getComponentState(state, 'Container(List)', 'default'); + expect(result).toEqual({ searchQuery: 'hello', sortBy: 'name' }); + expect(typeof result.get).toBe('undefined'); + }); + + it('returns undefined for an unknown component name', () => { + expect(getComponentState(state, 'Unknown', 'default')).toBeUndefined(); + }); + + it('returns undefined for an unknown instance id', () => { + expect(getComponentState(state, 'Container(List)', 'missing')).toBeUndefined(); + }); +}); + +describe('components.getAllComponentStates', () => { + const state = makeState({ + 'Container(List)': { + default: { searchQuery: '' }, + secondary: { searchQuery: 'test' }, + }, + }); + + it('returns all instances as plain JS object', () => { + const result = getAllComponentStates(state, 'Container(List)'); + expect(result).toEqual({ + default: { searchQuery: '' }, + secondary: { searchQuery: 'test' }, + }); + expect(typeof result.get).toBe('undefined'); + }); + + it('returns undefined for an unknown component name', () => { + expect(getAllComponentStates(state, 'Unknown')).toBeUndefined(); + }); +}); + +describe('components.getComponentStateProperty', () => { + const notifications = new List([{ id: '1', message: 'Hi' }]); + const state = { + cmf: { + components: new Map({ + 'Container(Notification)': new Map({ + Notification: new Map({ + notifications, + }), + }), + 'Container(List)': new Map({ + default: new Map({ + searchQuery: 'test', + }), + }), + }), + }, + }; + + it('returns plain scalar value for a string property', () => { + const result = getComponentStateProperty(state, 'Container(List)', 'default', 'searchQuery'); + expect(result).toBe('test'); + }); + + it('returns plain JS array for an Immutable List property', () => { + const result = getComponentStateProperty( + state, + 'Container(Notification)', + 'Notification', + 'notifications', + ); + expect(result).toEqual([{ id: '1', message: 'Hi' }]); + expect(Array.isArray(result)).toBe(true); + }); + + it('returns undefined for an unknown component', () => { + expect(getComponentStateProperty(state, 'Unknown', 'default', 'foo')).toBeUndefined(); + }); + + it('returns undefined for a missing property', () => { + expect( + getComponentStateProperty(state, 'Container(List)', 'default', 'notExist'), + ).toBeUndefined(); + }); + + it('returns defaultValue when component state is not found', () => { + expect(getComponentStateProperty(state, 'Unknown', 'default', 'foo', [])).toEqual([]); + }); + + it('returns defaultValue when property is not found', () => { + expect( + getComponentStateProperty(state, 'Container(List)', 'default', 'notExist', 'fallback'), + ).toBe('fallback'); + }); +}); diff --git a/packages/cmf/src/selectors/index.js b/packages/cmf/src/selectors/index.js index cbedd0f3a45..aedec3e58a7 100644 --- a/packages/cmf/src/selectors/index.js +++ b/packages/cmf/src/selectors/index.js @@ -1,7 +1,9 @@ import * as collections from './collections'; +import * as components from './components'; import toJS from './toJS'; export default { collections, + components, toJS, }; diff --git a/packages/cmf/src/selectors/index.test.js b/packages/cmf/src/selectors/index.test.js new file mode 100644 index 00000000000..aa9a35f6151 --- /dev/null +++ b/packages/cmf/src/selectors/index.test.js @@ -0,0 +1,19 @@ +import { describe, it, expect } from 'vitest'; +import selectors from './index'; +import * as collections from './collections'; +import * as components from './components'; +import toJS from './toJS'; + +describe('selectors index', () => { + it('exposes collections selectors', () => { + expect(selectors.collections).toEqual(collections); + }); + + it('exposes components selectors', () => { + expect(selectors.components).toEqual(components); + }); + + it('exposes toJS selector', () => { + expect(selectors.toJS).toEqual(toJS); + }); +}); diff --git a/packages/containers/src/ComponentForm/ComponentForm.sagas.js b/packages/containers/src/ComponentForm/ComponentForm.sagas.js index eab521a4e2c..aabef03751f 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.sagas.js +++ b/packages/containers/src/ComponentForm/ComponentForm.sagas.js @@ -1,6 +1,5 @@ import { call, put, select, take, takeEvery, takeLatest } from 'redux-saga/effects'; import cmf from '@talend/react-cmf'; -import { fromJS } from 'immutable'; import get from 'lodash/get'; import Component from './ComponentForm.component'; import { COMPONENT_FORM_SET_DIRTY } from './ComponentForm.actions'; @@ -78,7 +77,7 @@ export function* onFormSubmit(componentId, submitURL, action) { prev .setIn(['initialState', 'jsonSchema'], prev.get('jsonSchema')) .setIn(['initialState', 'uiSchema'], prev.get('uiSchema')) - .setIn(['initialState', 'properties'], fromJS(action.properties)), + .setIn(['initialState', 'properties'], action.properties), componentId, )(undefined, getReduxState), ); diff --git a/packages/containers/src/DeleteResource/DeleteResource.connect.js b/packages/containers/src/DeleteResource/DeleteResource.connect.js index 6f3970497e1..03355cb5ca3 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.connect.js +++ b/packages/containers/src/DeleteResource/DeleteResource.connect.js @@ -1,5 +1,4 @@ -import { cmfConnect } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import cmf, { cmfConnect } from '@talend/react-cmf'; import get from 'lodash/get'; import Container from './DeleteResource.container'; @@ -16,9 +15,7 @@ export function mapStateToProps(state, ownProps) { } else { const collectionId = ownProps.collectionId || ownProps.resourceType; if (collectionId) { - props.resource = state.cmf.collections - .get(collectionId, new Immutable.Map()) - .find(currentResource => currentResource.get('id') === resourceId); + props.resource = cmf.selectors.collections.getCollectionItem(state, collectionId, resourceId); } } diff --git a/packages/containers/src/GuidedTour/GuidedTour.connect.js b/packages/containers/src/GuidedTour/GuidedTour.connect.js index 8d56e8ce315..d253f8a72d2 100644 --- a/packages/containers/src/GuidedTour/GuidedTour.connect.js +++ b/packages/containers/src/GuidedTour/GuidedTour.connect.js @@ -1,9 +1,8 @@ -import { Map } from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; import GuidedTourContainer from './GuidedTour.container'; -export const DEFAULT_STATE = Map({}); +export const DEFAULT_STATE = {}; export default cmfConnect({ componentId: ownProps => ownProps.componentId || ownProps.id, diff --git a/packages/containers/src/List/List.connect.js b/packages/containers/src/List/List.connect.js index c05069134d7..7ec15bfb02b 100644 --- a/packages/containers/src/List/List.connect.js +++ b/packages/containers/src/List/List.connect.js @@ -1,5 +1,4 @@ import get from 'lodash/get'; -import { List } from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; import Container, { DEFAULT_STATE } from './List.container'; import { @@ -81,7 +80,7 @@ export default cmfConnect({ defaultProps: { saga: 'List#root', - listItems: new List(), + listItems: [], }, componentId, diff --git a/packages/containers/src/List/selector.js b/packages/containers/src/List/selector.js index 578e2cbd89f..0b7189e4f55 100644 --- a/packages/containers/src/List/selector.js +++ b/packages/containers/src/List/selector.js @@ -2,11 +2,15 @@ import cmf from '@talend/react-cmf'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import { createSelector } from 'reselect'; -import { Map, List } from 'immutable'; function contains(listItem, query, columns) { let item = listItem; - if (Map.isMap(listItem)) { + if ( + listItem != null && + !Array.isArray(listItem) && + typeof listItem === 'object' && + typeof listItem.toJS === 'function' + ) { item = listItem.toJS(); } return columns.some( @@ -17,14 +21,16 @@ function contains(listItem, query, columns) { } function getCollection(state, collectionId) { - return state.cmf.collections.get(collectionId); + if (collectionId == null) return undefined; + return cmf.selectors.collections.get(state, collectionId); } export function getCollectionItems(state, collectionId) { const collection = getCollection(state, collectionId); - if (Map.isMap(collection)) { - return collection.get('items'); + if (collection != null && !Array.isArray(collection) && typeof collection === 'object') { + const items = typeof collection.get === 'function' ? collection.get('items') : collection.items; + return items !== undefined ? items : collection; } return collection; } @@ -32,15 +38,18 @@ export function getCollectionItems(state, collectionId) { export function configureGetPagination(state, { collectionId }) { const collection = getCollection(state, collectionId); - if (Map.isMap(collection)) { - return collection.get('pagination'); + if (collection != null && !Array.isArray(collection) && typeof collection === 'object') { + return typeof collection.get === 'function' + ? collection.get('pagination') + : collection.pagination; } return null; } function getComponentState(collectionId) { - return state => state.cmf.components.getIn(['Container(List)', collectionId || 'default']); + return state => + cmf.selectors.components.getComponentState(state, 'Container(List)', collectionId || 'default'); } export function configureGetFilteredItems(configure) { @@ -51,7 +60,7 @@ export function configureGetFilteredItems(configure) { componentState => { let results = localConfig.items; if (componentState) { - const searchQuery = componentState.get('searchQuery'); + const searchQuery = componentState?.searchQuery; if (searchQuery && results) { results = results.filter(item => contains(item, searchQuery, localConfig.columns)); } @@ -65,8 +74,8 @@ export function configureGetFilteredItems(configure) { export function compare(sortBy) { return (a, b) => { - let aValue = a.get(sortBy); - let bValue = b.get(sortBy); + let aValue = typeof a.get === 'function' ? a.get(sortBy) : a[sortBy]; + let bValue = typeof b.get === 'function' ? b.get(sortBy) : b[sortBy]; if (typeof aValue === 'string' && typeof bValue === 'string') { aValue = aValue.toLowerCase(); @@ -93,13 +102,13 @@ export function compare(sortBy) { } export function getSortedResults(componentState, config, listItems) { - if (!List.isList(listItems)) { - return new List(); + if (listItems == null || typeof listItems.filter !== 'function') { + return []; } let results = listItems; if (!isEmpty(componentState)) { - const sortBy = componentState.get('sortOn'); - const sortAsc = componentState.get('sortAsc'); + const sortBy = componentState.sortOn; + const sortAsc = componentState.sortAsc; const sortedColumn = get(config, 'columns', []).find(column => column.key === sortBy); if (get(sortedColumn, 'sortFunction')) { @@ -130,8 +139,8 @@ export function configureGetPagedItems(configure, listItems) { const getPagedList = createSelector(getComponentState(configure.collectionId), componentState => { let results = listItems; if (componentState) { - const startIndex = componentState.get('startIndex'); - const itemsPerPage = componentState.get('itemsPerPage'); + const startIndex = componentState.startIndex; + const itemsPerPage = componentState.itemsPerPage; if (itemsPerPage > 0 && startIndex > 0) { results = results.slice( diff --git a/packages/containers/src/List/selector.test.js b/packages/containers/src/List/selector.test.js index b9525d7d579..23d7a6e21b8 100644 --- a/packages/containers/src/List/selector.test.js +++ b/packages/containers/src/List/selector.test.js @@ -163,10 +163,10 @@ describe('List Selector tests', () => { return 0; }); - const componentState = fromJS({ + const componentState = { sortOn: 'data', sortAsc: true, - }); + }; const config = { columns: [ { @@ -190,7 +190,7 @@ describe('List Selector tests', () => { // Sorting by column and custom sort function expect( getSortedResults( - fromJS({ sortOn: 'a', sortAsc: true }), + { sortOn: 'a', sortAsc: true }, { columns: [{ key: 'a', sortFunction: 'myCustomSortFn' }] }, fromJS([{ a: 1 }, { a: 3 }, { a: 2 }]), ), @@ -199,7 +199,7 @@ describe('List Selector tests', () => { // Desc sort expect( getSortedResults( - fromJS({ sortOn: 'key', sortAsc: false }), + { sortOn: 'key', sortAsc: false }, config, fromJS([{ key: 1 }, { key: 3 }, { key: 2 }]), ), @@ -213,6 +213,6 @@ describe('List Selector tests', () => { ); // With no items - expect(getSortedResults(componentState, config, null)).toEqual(new List()); + expect(getSortedResults(componentState, config, null)).toEqual([]); }); }); diff --git a/packages/containers/src/Notification/Notification.test.jsx b/packages/containers/src/Notification/Notification.test.jsx index c38ad6f2e6b..270a626e603 100644 --- a/packages/containers/src/Notification/Notification.test.jsx +++ b/packages/containers/src/Notification/Notification.test.jsx @@ -83,8 +83,8 @@ describe('Notification.pushNotification', () => { 'Notification', 'notifications', ]); - expect(notifications.size).toBe(1); - expect(notifications.get(0).message).toBe('hello world'); + expect(notifications.length).toBe(1); + expect(notifications[0].message).toBe('hello world'); }); it('should add a Notification in the state even if the state slot is not yet available', () => { @@ -97,8 +97,8 @@ describe('Notification.pushNotification', () => { 'Notification', 'notifications', ]); - expect(notifications.size).toBe(1); - expect(notifications.get(0).message).toBe('hello world'); + expect(notifications.length).toBe(1); + expect(notifications[0].message).toBe('hello world'); }); it('should delete all Notification in the state', () => { @@ -117,7 +117,7 @@ describe('Notification.pushNotification', () => { 'Notification', 'notifications', ]); - expect(notifications.size).toBe(0); + expect(notifications.length).toBe(0); }); it('should not change the state if no notification', () => { diff --git a/packages/containers/src/Notification/clearNotifications.js b/packages/containers/src/Notification/clearNotifications.js index 4c6abd0b661..68fc93b22b6 100644 --- a/packages/containers/src/Notification/clearNotifications.js +++ b/packages/containers/src/Notification/clearNotifications.js @@ -1,13 +1,19 @@ +import cmf from '@talend/react-cmf'; + export default function clearNotifications(state) { const path = ['Container(Notification)', 'Notification', 'notifications']; - let notifs = state.cmf.components.getIn(path); + const notifs = cmf.selectors.components.getComponentStateProperty( + state, + 'Container(Notification)', + 'Notification', + 'notifications', + ); if (!notifs) { return state; } - notifs = notifs.clear(); const newState = { ...state }; - newState.cmf.components = state.cmf.components.setIn(path, notifs); + newState.cmf.components = state.cmf.components.setIn(path, []); return newState; } diff --git a/packages/containers/src/Notification/pushNotification.js b/packages/containers/src/Notification/pushNotification.js index 3e8e51ca99c..3c3734de45e 100644 --- a/packages/containers/src/Notification/pushNotification.js +++ b/packages/containers/src/Notification/pushNotification.js @@ -1,5 +1,5 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; +import cmf from '@talend/react-cmf'; import { randomUUID } from '@talend/utils'; /** @@ -14,12 +14,15 @@ export default function pushNotification(state, notification) { return state; } const path = ['Container(Notification)', 'Notification', 'notifications']; - let notifs = state.cmf.components.getIn(path, new Immutable.List()); - notifs = notifs.push({ - id: randomUUID(), - ...notification, - }); + const notifs = + cmf.selectors.components.getComponentStateProperty( + state, + 'Container(Notification)', + 'Notification', + 'notifications', + ) || []; + const newNotifs = [...notifs, { id: randomUUID(), ...notification }]; const newState = { ...state }; - newState.cmf.components = state.cmf.components.setIn(path, notifs); + newState.cmf.components = state.cmf.components.setIn(path, newNotifs); return newState; } diff --git a/packages/containers/src/TabBar/TabBar.connect.js b/packages/containers/src/TabBar/TabBar.connect.js index 35cfb527c7a..35e93f8ef7c 100644 --- a/packages/containers/src/TabBar/TabBar.connect.js +++ b/packages/containers/src/TabBar/TabBar.connect.js @@ -1,8 +1,7 @@ import { cmfConnect } from '@talend/react-cmf'; import TabBar from '@talend/react-components/lib/TabBar'; -import Immutable from 'immutable'; -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = {}; export default cmfConnect({ componentId: ownProps => ownProps.componentId || ownProps.id, diff --git a/packages/containers/src/TabBar/TabBar.selectors.js b/packages/containers/src/TabBar/TabBar.selectors.js index 72994c9ce03..a8951ef3819 100644 --- a/packages/containers/src/TabBar/TabBar.selectors.js +++ b/packages/containers/src/TabBar/TabBar.selectors.js @@ -1,3 +1,4 @@ +import cmf from '@talend/react-cmf'; import TabBar from '@talend/react-components/lib/TabBar'; import { DEFAULT_STATE } from './TabBar.connect'; @@ -7,7 +8,10 @@ import { DEFAULT_STATE } from './TabBar.connect'; * @param {string} idComponent */ export function getComponentState(state, idComponent) { - return state.cmf.components.getIn([TabBar.displayName, idComponent], DEFAULT_STATE); + return ( + cmf.selectors.components.getComponentState(state, TabBar.displayName, idComponent) ?? + DEFAULT_STATE + ); } /** @@ -16,5 +20,10 @@ export function getComponentState(state, idComponent) { * @param {string} idComponent */ export function getSelectedKey(state, idComponent) { - return getComponentState(state, idComponent).get('selectedKey', undefined); + const compState = getComponentState(state, idComponent); + if (compState == null) return undefined; + if (typeof compState.get === 'function') { + return compState.get('selectedKey', undefined); + } + return compState.selectedKey; } diff --git a/packages/sagas/src/pending/pending.js b/packages/sagas/src/pending/pending.js index 1530c6b40b6..e8587ff4036 100644 --- a/packages/sagas/src/pending/pending.js +++ b/packages/sagas/src/pending/pending.js @@ -1,6 +1,5 @@ import { delay, call, put, select, take } from 'redux-saga/effects'; import cmf from '@talend/react-cmf'; -import { Map } from 'immutable'; import { PENDING_DELAY_TO_SHOW, PENDING_COLLECTION_NAME, SHOW_PENDING } from '../constants'; @@ -12,11 +11,12 @@ const addOrReplace = cmf.actions.collections.addOrReplace; * @param {string} asyncActionId the unique contextualized actionId */ export function findPenderById(state, asyncActionId) { - return state.cmf.collections.getIn([PENDING_COLLECTION_NAME, asyncActionId]); + const collection = cmf.selectors.collections.getCollectionPlain(state, PENDING_COLLECTION_NAME); + return collection ? collection[asyncActionId] : undefined; } export function findPenders(state) { - return state.cmf.collections.get(PENDING_COLLECTION_NAME); + return cmf.selectors.collections.getCollectionPlain(state, PENDING_COLLECTION_NAME); } /** @@ -25,7 +25,7 @@ export function findPenders(state) { export function* ensurePendersCollectionExists() { const collection = yield select(findPenders); if (!collection) { - yield put(addOrReplace(PENDING_COLLECTION_NAME, new Map())); + yield put(addOrReplace(PENDING_COLLECTION_NAME, {})); } } @@ -42,9 +42,9 @@ export default function* pendingMaybeNeeded(asyncCallerId, actionId) { yield delay(PENDING_DELAY_TO_SHOW); pending = true; yield call(ensurePendersCollectionExists); - let pendersCollection = yield select(findPenders); - pendersCollection = pendersCollection.set(asyncActionId, SHOW_PENDING); - yield put(addOrReplace(PENDING_COLLECTION_NAME, pendersCollection)); + const pendersCollection = yield select(findPenders); + const newPendersCollection = { ...pendersCollection, [asyncActionId]: SHOW_PENDING }; + yield put(addOrReplace(PENDING_COLLECTION_NAME, newPendersCollection)); yield take('DO_NOT_QUIT'); } finally { if (pending) { @@ -52,9 +52,9 @@ export default function* pendingMaybeNeeded(asyncCallerId, actionId) { const penderStatus = yield select(findPenderById, asyncActionId); if (penderStatus) { - let pendersCollection = yield select(findPenders); - pendersCollection = pendersCollection.delete(asyncActionId); - yield put(addOrReplace(PENDING_COLLECTION_NAME, pendersCollection)); + const pendersCollection = yield select(findPenders); + const { [asyncActionId]: _, ...updatedPendersCollection } = pendersCollection; + yield put(addOrReplace(PENDING_COLLECTION_NAME, updatedPendersCollection)); } } } diff --git a/packages/sagas/src/pending/pending.test.js b/packages/sagas/src/pending/pending.test.js index 60adf4affa8..f37f94d7b83 100644 --- a/packages/sagas/src/pending/pending.test.js +++ b/packages/sagas/src/pending/pending.test.js @@ -1,5 +1,4 @@ import { delay, select, put, call, take } from 'redux-saga/effects'; -import { vi } from 'vitest'; import cmf from '@talend/react-cmf'; import pendingMaybeNeeded, { ensurePendersCollectionExists, @@ -11,24 +10,13 @@ import { PENDING_DELAY_TO_SHOW, SHOW_PENDING, PENDING_COLLECTION_NAME } from '.. const addOrReplace = cmf.actions.collections.addOrReplace; -/** - * Returns a minimal mock with the Immutable.Map interface required by the production saga. - * Production code (pending.js) still uses Immutable — this decouples the test from that dependency. - */ -const makeCollection = () => ({ - set: vi.fn(() => makeCollection()), - delete: vi.fn(() => makeCollection()), -}); - describe('test pending status', () => { it('should create penders collection in cmf.collections', () => { const gen = ensurePendersCollectionExists(); expect(gen.next().value).toEqual(select(findPenders)); - // when no collection exists, saga puts a new empty collection - expect(gen.next(undefined).value).toEqual( - put(addOrReplace(PENDING_COLLECTION_NAME, expect.any(Object))), - ); + // when no collection exists, saga puts a new empty plain object collection + expect(gen.next(undefined).value).toEqual(put(addOrReplace(PENDING_COLLECTION_NAME, {}))); // the saga is finished expect(gen.next()).toEqual({ done: true, value: undefined }); }); @@ -36,29 +24,29 @@ describe('test pending status', () => { const gen = ensurePendersCollectionExists(); expect(gen.next().value).toEqual(select(findPenders)); - // existing truthy collection → saga skips creation and finishes - expect(gen.next(makeCollection())).toEqual({ done: true, value: undefined }); + // existing truthy plain object → saga skips creation and finishes + expect(gen.next({})).toEqual({ done: true, value: undefined }); }); it('should pend and then clear pending', () => { const gen = pendingMaybeNeeded('', 'streams:create'); - const pendersCollection = makeCollection(); + const pendersCollection = {}; expect(gen.next().value).toEqual(delay(PENDING_DELAY_TO_SHOW)); expect(gen.next().value).toEqual(call(ensurePendersCollectionExists)); expect(gen.next().value).toEqual(select(findPenders)); - // saga receives collection, calls .set(), yields put with updated collection + // saga spreads collection + new entry, yields put with updated plain object expect(gen.next(pendersCollection).value).toEqual( - put(addOrReplace(PENDING_COLLECTION_NAME, expect.any(Object))), + put(addOrReplace(PENDING_COLLECTION_NAME, { '#streams:create': SHOW_PENDING })), ); expect(gen.next().value).toEqual(take('DO_NOT_QUIT')); expect(gen.next().value).toEqual(call(ensurePendersCollectionExists)); expect(gen.next().value).toEqual(select(findPenderById, '#streams:create')); expect(gen.next(SHOW_PENDING).value).toEqual(select(findPenders)); - // saga receives collection, calls .delete(), yields put with updated collection - expect(gen.next(pendersCollection).value).toEqual( - put(addOrReplace(PENDING_COLLECTION_NAME, expect.any(Object))), + // saga destructures entry out, yields put with empty plain object + expect(gen.next({ '#streams:create': SHOW_PENDING }).value).toEqual( + put(addOrReplace(PENDING_COLLECTION_NAME, {})), ); // the saga is finished expect(gen.next()).toEqual({ done: true, value: undefined }); From 2b764a6594863b6055bafab7cadabdf40b4bc288 Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 12 Mar 2026 09:10:43 +0100 Subject: [PATCH 06/16] epic-5 partial --- .changeset/remove-immutable.md | 10 + packages/cmf-cqrs/package.json | 1 - .../src/middleware/socketMiddleware.test.js | 9 +- .../__snapshots__/componentState.test.js.snap | 2 +- .../actions/collectionsActions.test.js | 21 +- packages/cmf/__tests__/cmfConnect.test.jsx | 50 +- packages/cmf/__tests__/componentState.test.js | 15 +- .../cmf/__tests__/expressions/index.test.js | 239 +++++++- packages/cmf/__tests__/localStorage.test.js | 39 +- packages/cmf/__tests__/onEvent.test.js | 3 +- .../reducers/collectionsReducers.test.js | 318 +++++----- .../reducers/componentsReducers.test.js | 45 +- .../cmf/__tests__/sagas/collection.test.js | 9 +- packages/cmf/__tests__/selectors/toJS.test.js | 27 +- packages/cmf/package.json | 2 - packages/cmf/src/cmfConnect.jsx | 7 +- packages/cmf/src/componentState.js | 7 +- packages/cmf/src/expressions/allOf.js | 12 +- packages/cmf/src/expressions/getInState.js | 9 +- packages/cmf/src/expressions/includes.js | 9 +- packages/cmf/src/expressions/index.md | 13 +- packages/cmf/src/expressions/oneOf.js | 11 +- packages/cmf/src/localStorage.js | 18 +- packages/cmf/src/onEvent.js | 7 +- .../cmf/src/reducers/collectionsReducers.js | 104 ++-- .../src/reducers/collectionsReducers.test.js | 558 ++++++++++++++++++ .../cmf/src/reducers/componentsReducers.js | 49 +- .../src/reducers/componentsReducers.test.js | 142 +++++ packages/cmf/src/selectors/collections.js | 40 +- .../cmf/src/selectors/collections.test.js | 79 ++- packages/cmf/src/selectors/components.js | 27 +- packages/cmf/src/selectors/components.test.js | 32 +- packages/cmf/src/selectors/toJS.js | 16 +- packages/components/package.json | 1 - .../ActionDropdown.component.jsx | 12 +- .../TreeManager/TreeManager.container.js | 30 +- .../Managers/TreeManager/TreeManager.test.jsx | 14 +- .../containers/.storybook/cmfModule/index.js | 5 +- packages/containers/package.json | 2 - .../src/AboutDialog/AboutDialog.container.jsx | 13 +- .../src/AboutDialog/AboutDialog.test.js | 7 +- .../ActionDropdown/ActionDropdown.connect.jsx | 3 +- .../ActionDropdown/ActionDropdown.stories.jsx | 5 +- .../src/AppLoader/AppLoader.connect.jsx | 4 +- .../src/AppLoader/AppLoader.connect.test.js | 14 +- .../src/Breadcrumbs/Breadcrumbs.connect.jsx | 11 +- .../src/Breadcrumbs/Breadcrumbs.stories.jsx | 5 +- .../ComponentForm/ComponentForm.component.jsx | 49 +- .../ComponentForm/ComponentForm.saga.test.js | 32 +- .../src/ComponentForm/ComponentForm.sagas.js | 24 +- .../ComponentForm/ComponentForm.selectors.js | 2 +- .../ComponentForm.selectors.test.js | 30 +- .../src/ComponentForm/ComponentForm.test.js | 50 +- .../ConfirmDialog/ConfirmDialog.connect.js | 6 +- .../ConfirmDialog/ConfirmDialog.container.jsx | 7 +- .../ConfirmDialog/ConfirmDialog.stories.jsx | 5 +- .../src/ConfirmDialog/ConfirmDialog.test.jsx | 39 +- .../ConfirmDialog/showHideConfirmDialog.js | 42 +- .../DeleteResource.container.jsx | 2 +- .../src/DeleteResource/DeleteResource.test.js | 21 +- .../containers/src/DeleteResource/sagas.js | 2 +- .../src/DeleteResource/sagas.test.js | 12 +- .../EditableText/EditableText.container.jsx | 7 +- .../EditableText/EditableText.selectors.js | 2 +- .../src/EditableText/EditableText.test.js | 39 +- .../src/FilterBar/FilterBar.container.jsx | 19 +- .../src/FilterBar/FilterBar.selectors.js | 7 +- .../src/FilterBar/FilterBar.test.js | 64 +- .../containers/src/Form/Form.container.jsx | 19 +- packages/containers/src/Form/Form.test.js | 50 +- .../src/GuidedTour/GuidedTour.container.jsx | 2 +- .../src/GuidedTour/GuidedTour.stories.jsx | 5 +- .../src/GuidedTour/GuidedTour.test.js | 6 +- .../src/HeaderBar/HeaderBar.container.jsx | 9 +- .../src/HeaderBar/HeaderBar.test.js | 26 +- .../src/HomeListView/HomeListView.stories.jsx | 5 +- packages/containers/src/List/List.connect.js | 20 +- .../containers/src/List/List.container.jsx | 28 +- packages/containers/src/List/List.stories.jsx | 150 +++-- packages/containers/src/List/List.test.jsx | 90 ++- .../src/List/__snapshots__/List.test.jsx.snap | 144 ++--- packages/containers/src/List/selector.js | 31 +- packages/containers/src/List/selector.test.js | 123 ++-- .../src/Notification/Notification.connect.js | 6 +- .../Notification/Notification.container.jsx | 9 +- .../src/Notification/Notification.sagas.js | 16 +- .../Notification/Notification.sagas.test.js | 8 +- .../src/Notification/Notification.stories.jsx | 9 +- .../src/Notification/Notification.test.jsx | 54 +- .../src/Notification/clearNotifications.js | 20 +- .../src/Notification/pushNotification.js | 19 +- .../ObjectViewer/ObjectViewer.container.jsx | 47 +- .../src/ObjectViewer/ObjectViewer.test.jsx | 18 +- .../PieChartButton/PieChartButton.connect.jsx | 11 +- .../src/PieChartButton/PieChartButton.test.js | 27 +- .../SelectObject/SelectObject.component.jsx | 14 +- .../src/SelectObject/SelectObject.connect.js | 16 +- .../SelectObject/SelectObject.connect.test.js | 33 +- .../SelectObject/SelectObject.container.jsx | 62 +- .../SelectObject.container.test.jsx | 243 ++++---- .../src/SidePanel/SidePanel.container.jsx | 9 +- .../src/Slider/Slider.container.jsx | 9 +- .../containers/src/Slider/Slider.selectors.js | 4 +- .../containers/src/Slider/Slider.stories.jsx | 8 +- packages/containers/src/Slider/Slider.test.js | 19 +- .../Slider/__snapshots__/Slider.test.js.snap | 30 +- .../SubHeaderBar/SubHeaderBar.container.jsx | 5 +- .../SubHeaderBar/SubHeaderBar.selectors.js | 2 +- .../src/SubHeaderBar/SubHeaderBar.test.js | 11 +- packages/containers/src/TabBar/TabBar.test.js | 15 +- .../src/TreeView/TreeView.container.jsx | 48 +- .../containers/src/TreeView/TreeView.test.js | 54 +- .../src/Typeahead/Typeahead.container.jsx | 15 +- .../src/Typeahead/Typeahead.test.js | 20 +- packages/sagas/package.json | 1 - packages/stepper/package.json | 1 - yarn.lock | 14 +- 117 files changed, 2367 insertions(+), 1735 deletions(-) create mode 100644 .changeset/remove-immutable.md create mode 100644 packages/cmf/src/reducers/collectionsReducers.test.js create mode 100644 packages/cmf/src/reducers/componentsReducers.test.js diff --git a/.changeset/remove-immutable.md b/.changeset/remove-immutable.md new file mode 100644 index 00000000000..105ca94cb47 --- /dev/null +++ b/.changeset/remove-immutable.md @@ -0,0 +1,10 @@ +--- +'@talend/react-cmf': major +'@talend/react-containers': major +'@talend/react-cmf-cqrs': major +'@talend/react-components': patch +'@talend/react-sagas': minor +'@talend/react-stepper': patch +--- + +feat: remove immutable dependency diff --git a/packages/cmf-cqrs/package.json b/packages/cmf-cqrs/package.json index af60330ca68..52e172787c5 100644 --- a/packages/cmf-cqrs/package.json +++ b/packages/cmf-cqrs/package.json @@ -43,7 +43,6 @@ "dependencies": { "@talend/react-cmf": "^12.1.1", "@talend/utils": "^3.7.1", - "immutable": "^3.8.2", "redux-saga": "^1.4.2" }, "devDependencies": { diff --git a/packages/cmf-cqrs/src/middleware/socketMiddleware.test.js b/packages/cmf-cqrs/src/middleware/socketMiddleware.test.js index 08400f83baf..b5c1472a7f9 100644 --- a/packages/cmf-cqrs/src/middleware/socketMiddleware.test.js +++ b/packages/cmf-cqrs/src/middleware/socketMiddleware.test.js @@ -1,5 +1,4 @@ import configureStore from 'redux-mock-store'; -import { Map } from 'immutable'; import createStatePatchLogger, { isAbsoluteWebSocketUrl } from './socketMiddleware'; import smartWebsocket from './smartWebsocket'; @@ -17,15 +16,15 @@ const mockStore = configureStore([createStatePatchLogger([], [])]); const state = { path: {}, - test: new Map({ + test: { in: {}, out: {}, parents: {}, childrens: {}, - nodes: new Map(), + nodes: {}, transforms: {}, nodeTypes: {}, - }), + }, routing: { locationBeforeTransitions: { pathname: '/datastream-designer/50/', @@ -65,7 +64,7 @@ describe('pathToServer', () => { call = 1; return state; } - return { ...state, test: state.test.setIn(['nodes', 'test'], 'test') }; + return { ...state, test: { ...state.test, nodes: { ...state.test.nodes, test: 'test' } } }; }); const dispatch = vi.fn(); const middleware = createStatePatchLogger('test', []); diff --git a/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap b/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap index 28b0ee89b18..6dcad201bd6 100644 --- a/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap +++ b/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap @@ -5,7 +5,7 @@ exports[`state should getStateAccessors return accessors 1`] = ` "cmf": { "componentState": { "componentName": "name", - "initialComponentState": Immutable.Map { + "initialComponentState": { "foo": "bar", }, "key": "id", diff --git a/packages/cmf/__tests__/actions/collectionsActions.test.js b/packages/cmf/__tests__/actions/collectionsActions.test.js index 6be7bd7b94e..5ba63afc7bf 100644 --- a/packages/cmf/__tests__/actions/collectionsActions.test.js +++ b/packages/cmf/__tests__/actions/collectionsActions.test.js @@ -1,12 +1,7 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { Map } from 'immutable'; -import { - addOrReplace, - remove, - mutate, -} from '../../src/actions/collectionsActions'; +import { addOrReplace, remove, mutate } from '../../src/actions/collectionsActions'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); @@ -22,7 +17,7 @@ describe('test collection management action creators', () => { it('addOrReplaceCollection dispatch with path to nested collections (path can be either strings separated with dots or array of strings)', () => { expect( - addOrReplace('collectionId.nestedCollection.nestedObjField', 'data can be anything') + addOrReplace('collectionId.nestedCollection.nestedObjField', 'data can be anything'), ).toEqual({ type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', collectionId: 'collectionId.nestedCollection.nestedObjField', @@ -31,14 +26,16 @@ describe('test collection management action creators', () => { }); it('removeCollection dispatch well formed action object', () => { - const expectedActions = [{ - type: 'REACT_CMF.COLLECTION_REMOVE', - collectionId: 'collectionId', - }]; + const expectedActions = [ + { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: 'collectionId', + }, + ]; const store = mockStore({ cmf: { - collections: new Map().set('collectionId', 'data'), + collections: { collectionId: 'data' }, }, }); diff --git a/packages/cmf/__tests__/cmfConnect.test.jsx b/packages/cmf/__tests__/cmfConnect.test.jsx index 99b910f8731..a66ef88d6d8 100644 --- a/packages/cmf/__tests__/cmfConnect.test.jsx +++ b/packages/cmf/__tests__/cmfConnect.test.jsx @@ -4,7 +4,6 @@ import { Component } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { fromJS, Map } from 'immutable'; import PropTypes from 'prop-types'; import { mock } from '../src'; @@ -54,25 +53,25 @@ describe('cmfConnect', () => { describe('#getStateToProps', () => { it('should call getStateProps', () => { const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { TestComponent: { testId: { foo: 'bar', }, }, - }); + }; const props = getStateToProps({ componentId: 'testId', ownProps: {}, state, WrappedComponent: { displayName: 'TestComponent' }, }); - expect(props.state.get('foo')).toBe('bar'); + expect(props.state.foo).toBe('bar'); }); it('should inject view settings using props.view', () => { const state = mock.store.state(); - state.cmf.components = fromJS({}); + state.cmf.components = {}; const props = getStateToProps({ componentId: 'testId', ownProps: { view: 'homepage' }, @@ -84,7 +83,7 @@ describe('cmfConnect', () => { it('should inject view settings using displayName and componentId', () => { const state = mock.store.state(); - state.cmf.components = fromJS({}); + state.cmf.components = {}; state.cmf.settings.props['TestComponent#default'] = { foo: 'from-displayName' }; state.cmf.settings.props['TestComponent#props-id'] = { foo: 'from-props-componentId' }; state.cmf.settings.props['TestComponent#connect-id'] = { foo: 'from-connect-componentId' }; @@ -115,7 +114,7 @@ describe('cmfConnect', () => { }); it('should evaluate expression using all props', () => { const state = mock.store.state(); - state.cmf.components = fromJS({}); + state.cmf.components = {}; expression.register('hasModel', ({ payload }) => payload.model !== undefined); const props = getStateToProps({ ownProps: { @@ -239,18 +238,18 @@ describe('cmfConnect', () => { it('should expose getState static function to get the state', () => { expect(typeof CMFConnectedButton.getState).toBe('function'); const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { Button: { default: { foo: 'bar' }, other: { foo: 'baz' }, }, - }); - expect(CMFConnectedButton.getState(state).get('foo')).toBe('bar'); - expect(CMFConnectedButton.getState(state, 'other').get('foo')).toBe('baz'); + }; + expect(CMFConnectedButton.getState(state).foo).toBe('bar'); + expect(CMFConnectedButton.getState(state, 'other').foo).toBe('baz'); }); it('should expose setStateAction static function to get the redux action to setState', () => { expect(typeof CMFConnectedButton.setStateAction).toBe('function'); - const state = new Map({ foo: 'bar' }); + const state = { foo: 'bar' }; let action = CMFConnectedButton.setStateAction(state); expect(action).toEqual({ type: 'Button.setState', @@ -271,15 +270,16 @@ describe('cmfConnect', () => { it('should expose setStateAction static function to get the redux action to setState', () => { expect(typeof CMFConnectedButton.setStateAction).toBe('function'); const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { Button: { default: { foo: 'foo' }, other: { foo: 'baz' }, }, - }); - let actionCreator = CMFConnectedButton.setStateAction(prevState => - prevState.set('foo', 'bar'), - ); + }; + let actionCreator = CMFConnectedButton.setStateAction(prevState => ({ + ...prevState, + foo: 'bar', + })); expect(typeof actionCreator).toBe('function'); let action = actionCreator(null, () => state); expect(action).toMatchObject({ @@ -292,16 +292,16 @@ describe('cmfConnect', () => { }, }, }); - expect(action.cmf.componentState.componentState.get('foo')).toBe('bar'); + expect(action.cmf.componentState.componentState.foo).toBe('bar'); actionCreator = CMFConnectedButton.setStateAction( - prevState => prevState.set('foo', 'baz'), + prevState => ({ ...prevState, foo: 'baz' }), 'other', 'MY_ACTION', ); action = actionCreator(null, () => state); expect(action.type).toBe('MY_ACTION'); expect(action.cmf.componentState.key).toBe('other'); - expect(action.cmf.componentState.componentState.get('foo')).toBe('baz'); + expect(action.cmf.componentState.componentState.foo).toBe('baz'); }); it('should support no context in dispatchActionCreator', () => { const event = {}; @@ -346,12 +346,12 @@ describe('cmfConnect', () => { }); it('should pass defaultState when there is no component state in store', () => { - const TestComponent = props => ; + const TestComponent = props => ; TestComponent.displayName = 'MyComponentWithoutStateInStore'; TestComponent.propTypes = { state: PropTypes.any, }; - const defaultState = new Map({ toto: 'lol' }); + const defaultState = { toto: 'lol' }; const CMFConnected = cmfConnect({ defaultState })(TestComponent); render( @@ -365,7 +365,7 @@ describe('cmfConnect', () => { it('should componentDidMount initState and dispatchActionCreator after the saga', () => { const TestComponent = jest.fn(() => null); TestComponent.displayName = 'TestComponent'; - const STATE = new Map(); + const STATE = {}; const CMFConnected = cmfConnect({})(TestComponent); const props = { didMountActionCreator: 'hello', @@ -493,7 +493,7 @@ describe('cmfConnect', () => { const TestComponent = () =>
; TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({ - defaultState: new Map(), + defaultState: {}, })(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); @@ -519,7 +519,7 @@ describe('cmfConnect', () => { const TestComponent = () =>
; TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({ - defaultState: new Map(), + defaultState: {}, keepComponentState: true, })(TestComponent); expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); diff --git a/packages/cmf/__tests__/componentState.test.js b/packages/cmf/__tests__/componentState.test.js index 04609ddee19..82850abf743 100644 --- a/packages/cmf/__tests__/componentState.test.js +++ b/packages/cmf/__tests__/componentState.test.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import Immutable, { Map } from 'immutable'; import state, { getStateAccessors, @@ -18,7 +17,7 @@ describe('state', () => { it('should getStateAccessors should support no DEFAULT_STATE', () => { const dispatch = jest.fn(); - const props = getStateAccessors(dispatch, 'name', 'id', new Map()); + const props = getStateAccessors(dispatch, 'name', 'id', {}); expect(typeof props.setState).toBe('function'); props.setState(); @@ -31,7 +30,7 @@ describe('state', () => { it('should getStateAccessors return accessors', () => { const dispatch = jest.fn(); - const DEFAULT_STATE = new Map({ foo: 'bar' }); + const DEFAULT_STATE = { foo: 'bar' }; const props = getStateAccessors(dispatch, 'name', 'id', DEFAULT_STATE); expect(typeof props.setState).toBe('function'); expect(typeof props.initState).toBe('function'); @@ -56,7 +55,7 @@ describe('state', () => { via applyCallback`, () => { const dispatch = jest.fn(); const callBack = jest.fn(); - const DEFAULT_STATE = new Map({ foo: 'bar' }); + const DEFAULT_STATE = { foo: 'bar' }; const props = getStateAccessors(dispatch, 'name', 'id', DEFAULT_STATE); props.setState(callBack); @@ -67,17 +66,17 @@ describe('state', () => { it('should getStateProps return state', () => { const storeState = { cmf: { - components: Immutable.fromJS({ + components: { foo: { bar: { open: true, }, }, - }), + }, }, }; const props = getStateProps(storeState, 'foo', 'bar'); - expect(props.state.get('open')).toBe(true); + expect(props.state.open).toBe(true); }); it('should initState call props.initState with initialState', () => { @@ -90,7 +89,7 @@ describe('state', () => { expect(props.initState.mock.calls.length).toBe(1); expect(props.initState.mock.calls[0][0]).toBe(); - props.initialState = new Map({ foo: 'bar' }); + props.initialState = { foo: 'bar' }; initState(props); expect(props.initState.mock.calls[1][0]).toBe(props.initialState); }); diff --git a/packages/cmf/__tests__/expressions/index.test.js b/packages/cmf/__tests__/expressions/index.test.js index f13a854920d..068a39e5bdf 100644 --- a/packages/cmf/__tests__/expressions/index.test.js +++ b/packages/cmf/__tests__/expressions/index.test.js @@ -1,7 +1,244 @@ -import Immutable from 'immutable'; import expressions from '../../src/expressions'; import { mock } from '../../src'; +describe('expressions', () => { + it('should export some expressions', () => { + expect(expressions['cmf.collections.get']).toBeDefined(); + expect(expressions['cmf.components.get']).toBeDefined(); + expect(expressions['cmf.collections.includes']).toBeDefined(); + expect(expressions['cmf.components.includes']).toBeDefined(); + }); + describe('cmf.collections.get', () => { + it('should get collection content', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'my title', + }, + }; + context.store.getState = () => state; + expect(expressions['cmf.collections.get']({ context }, 'article.title', 'no title')).toBe( + 'my title', + ); + }); + it("should return default value if collection doesn't exists", () => { + const context = mock.store.context(); + const state = mock.store.state(); + context.store.getState = () => state; + state.cmf.collections = {}; + expect(expressions['cmf.collections.get']({ context }, 'article.title', 'no title')).toBe( + 'no title', + ); + }); + }); + + describe('cmf.collections.includes', () => { + it('should return true if the value is present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + context.store.getState = () => state; + expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test2')).toBe( + true, + ); + }); + it('should return false if the value is not present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + context.store.getState = () => state; + expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test4')).toBe( + false, + ); + }); + it("should return false if collection doesn't exists", () => { + const context = mock.store.context(); + const state = mock.store.state(); + context.store.getState = () => state; + state.cmf.collections = {}; + expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test')).toBe( + false, + ); + }); + }); + describe('cmf.collections.oneOf', () => { + it('should return true if one of the values is present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + context.store.getState = () => state; + expect( + expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test2', 'test4']), + ).toBe(true); + }); + it('should return false if all values are not present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + context.store.getState = () => state; + expect( + expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test4', 'test5']), + ).toBe(false); + }); + it("should return false if collection doesn't exist", () => { + const context = mock.store.context(); + const state = mock.store.state(); + context.store.getState = () => state; + state.cmf.collections = {}; + expect( + expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test0', 'test1']), + ).toBe(false); + }); + it('should throw an error if values are not an array', () => { + const context = mock.store.context(); + const state = mock.store.state(); + context.store.getState = () => state; + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + expect(() => + expressions['cmf.collections.oneOf']({ context }, 'article.tags', 'test'), + ).toThrow(/^You should pass an array of values to check if one of them is present$/); + }); + }); + describe('cmf.collections.allOf', () => { + it('should return true if all of the values are present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + context.store.getState = () => state; + expect( + expressions['cmf.collections.allOf']({ context }, 'article.tags', [ + 'test', + 'test2', + 'test3', + ]), + ).toBe(true); + }); + it('should return false if not all values are not present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + context.store.getState = () => state; + expect( + expressions['cmf.collections.allOf']({ context }, 'article.tags', ['test2', 'test3']), + ).toBe(false); + }); + it("should return false if collection doesn't exist", () => { + const context = mock.store.context(); + const state = mock.store.state(); + context.store.getState = () => state; + state.cmf.collections = {}; + expect( + expressions['cmf.collections.allOf']({ context }, 'article.tags', ['test0', 'test1']), + ).toBe(false); + }); + it('should throw an error if values are not an array', () => { + const context = mock.store.context(); + const state = mock.store.state(); + context.store.getState = () => state; + state.cmf.collections = { + article: { + title: 'title', + tags: ['test', 'test2', 'test3'], + }, + }; + expect(() => + expressions['cmf.collections.allOf']({ context }, 'article.tags', 'test'), + ).toThrow(/^You should pass an array of values to check if all of them are present$/); + }); + }); + + describe('cmf.components.get', () => { + it('should get component state', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.components = { + MyComponent: { + default: { + show: true, + }, + }, + }; + context.store.getState = () => state; + expect( + expressions['cmf.components.get']({ context }, 'MyComponent.default.show', false), + ).toBe(true); + }); + it('should return default value if no component state', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.components = {}; + context.store.getState = () => state; + expect( + expressions['cmf.components.get']({ context }, 'MyComponent.default.show', false), + ).toBe(false); + }); + }); + + describe('cmf.components.includes', () => { + it('should return true if the value is present in the list', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.components = { + MyComponent: { + default: { + tags: ['tag1', 'tag2', 'tag3'], + show: true, + }, + }, + }; + context.store.getState = () => state; + expect( + expressions['cmf.components.includes']({ context }, 'MyComponent.default.tags', 'tag1'), + ).toBe(true); + }); + it('should return default false if there is no component state', () => { + const context = mock.store.context(); + const state = mock.store.state(); + state.cmf.components = {}; + context.store.getState = () => state; + expect( + expressions['cmf.components.includes']({ context }, 'MyComponent.default.tags', 'tag1'), + ).toBe(false); + }); + }); +}); + describe('expressions', () => { it('should export some expressions', () => { expect(expressions['cmf.collections.get']).toBeDefined(); diff --git a/packages/cmf/__tests__/localStorage.test.js b/packages/cmf/__tests__/localStorage.test.js index 6c31efa4beb..ea755b7c8bd 100644 --- a/packages/cmf/__tests__/localStorage.test.js +++ b/packages/cmf/__tests__/localStorage.test.js @@ -1,4 +1,3 @@ -import Immutable from 'immutable'; import localStorageAPI from '../src/localStorage'; const PATHS = [ @@ -12,21 +11,6 @@ const state = { app: { extra: true, }, - components: new Immutable.Map({ - Foo: new Immutable.Map({ - default: new Immutable.Map({ - foo: 'foo', - }), - }), - }), - collections: new Immutable.Map({ - data: new Immutable.Map({}), - }), - }, -}; - -const serializedState = JSON.stringify(Object.assign({}, state, { - cmf: { components: { Foo: { default: { @@ -38,7 +22,24 @@ const serializedState = JSON.stringify(Object.assign({}, state, { data: {}, }, }, -})); +}; + +const serializedState = JSON.stringify( + Object.assign({}, state, { + cmf: { + components: { + Foo: { + default: { + foo: 'foo', + }, + }, + }, + collections: { + data: {}, + }, + }, + }), +); const KEY = 'test-cmf-localStorage'; describe('reduxLocalStorage', () => { @@ -56,8 +57,8 @@ describe('reduxLocalStorage', () => { it('should getState return parsed state from localStorage', () => { localStorage.setItem(KEY, serializedState); const initialState = localStorageAPI.getState(KEY); - expect(initialState.cmf.components.getIn(['Foo', 'default', 'foo'])).toBe('foo'); - expect(initialState.cmf.collections.getIn(['data']).toJS()).toEqual({}); + expect(initialState.cmf.components?.Foo?.default?.foo).toBe('foo'); + expect(initialState.cmf.collections?.data).toEqual({}); localStorage.setItem(KEY, undefined); }); it('should getStoreCallback return a function', () => { diff --git a/packages/cmf/__tests__/onEvent.test.js b/packages/cmf/__tests__/onEvent.test.js index 4d7a0d3d769..3ef754b9612 100644 --- a/packages/cmf/__tests__/onEvent.test.js +++ b/packages/cmf/__tests__/onEvent.test.js @@ -1,4 +1,3 @@ -import Immutable from 'immutable'; import onEvent from '../src/onEvent'; describe('onEvent', () => { @@ -10,7 +9,7 @@ describe('onEvent', () => { instance = { props: { setState: jest.fn(), - state: new Immutable.Map({ docked: false }), + state: { docked: false }, }, }; config = {}; diff --git a/packages/cmf/__tests__/reducers/collectionsReducers.test.js b/packages/cmf/__tests__/reducers/collectionsReducers.test.js index 09fda3399e9..74c2417dc6a 100644 --- a/packages/cmf/__tests__/reducers/collectionsReducers.test.js +++ b/packages/cmf/__tests__/reducers/collectionsReducers.test.js @@ -1,17 +1,25 @@ -import { Map, List, fromJS } from 'immutable'; - -import collectionsReducers, { defaultState, getId, getActionWithCollectionIdAsArray } from '../../src/reducers/collectionsReducers'; - -const initialState = defaultState.set('collection1', 'super data'); - -const listInitialState = defaultState.set( - 'collectionid', - new List().set(0, { id: 0, label: 'test data 0' }).set(1, { id: 1, label: 'test data 1' }) -); -const mapInitialState = defaultState.set( - 'collectionid', - new Map().set('test0', 'test data 0').set('test1', 'test data 1') -); +import collectionsReducers, { + defaultState, + getId, + getActionWithCollectionIdAsArray, +} from '../../src/reducers/collectionsReducers'; + +const initialState = { ...defaultState, collection1: 'super data' }; + +const listInitialState = { + ...defaultState, + collectionid: [ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + ], +}; +const mapInitialState = { + ...defaultState, + collectionid: { + test0: 'test data 0', + test1: 'test data 1', + }, +}; describe('check collection management reducer', () => { it('should return state if no action passed', () => { @@ -19,97 +27,127 @@ describe('check collection management reducer', () => { }); it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly add data into store', () => { - expect(collectionsReducers(initialState, { - type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', - collectionId: ['collectionId'], - data: 'data can be anything', - })).toEqual(new Map() - .set('collection1', 'super data') - .set('collectionId', 'data can be anything')); + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collectionId'], + data: 'data can be anything', + }), + ).toEqual({ + collection1: 'super data', + collectionId: 'data can be anything', + }); }); it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly replace data into store', () => { - expect(collectionsReducers(initialState, { - type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', - collectionId: ['collection1'], - data: 'data can be anything', - })).toEqual(new Map().set('collection1', 'data can be anything')); + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collection1'], + data: 'data can be anything', + }), + ).toEqual({ collection1: 'data can be anything' }); }); it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly add nested collection into store', () => { - const initState = fromJS({ + const initState = { collection1: { data: 'data can be anything', }, - }); - const expectedResult = initState.setIn(['collection1', 'nestedCollection'], fromJS(['item 1', 'item 2'])); - expect(collectionsReducers(initState, { - type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', - collectionId: ['collection1', 'nestedCollection'], - data: ['item 1', 'item 2'], - })).toEqual(expectedResult); + }; + const expectedResult = { + collection1: { + data: 'data can be anything', + nestedCollection: ['item 1', 'item 2'], + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collection1', 'nestedCollection'], + data: ['item 1', 'item 2'], + }), + ).toEqual(expectedResult); }); it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly replace nested collection into store', () => { - const initState = fromJS({ + const initState = { collection1: { nestedCollection: 'data can be anything', }, - }); - const expectedResult = initState.setIn(['collection1', 'nestedCollection'], fromJS(['item 1', 'item 2'])); - expect(collectionsReducers(initState, { - type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', - collectionId: ['collection1', 'nestedCollection'], - data: ['item 1', 'item 2'], - })).toEqual(expectedResult); + }; + const expectedResult = { + collection1: { + nestedCollection: ['item 1', 'item 2'], + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collection1', 'nestedCollection'], + data: ['item 1', 'item 2'], + }), + ).toEqual(expectedResult); }); it('REACT_CMF.COLLECTION_REMOVE should properly remove collection from the store', () => { - expect(collectionsReducers(initialState, { - type: 'REACT_CMF.COLLECTION_REMOVE', - collectionId: ['collection1'], - })).toEqual(new Map()); + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: ['collection1'], + }), + ).toEqual({}); }); it('REACT_CMF.COLLECTION_REMOVE should properly remove nested collection from the store', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { list: ['item 1', 'item 2'], }, }, - }); - const expectedResult = initState.deleteIn(['collection', 'nestedCollection', 'list']); - expect(collectionsReducers(initState, { - type: 'REACT_CMF.COLLECTION_REMOVE', - collectionId: ['collection', 'nestedCollection', 'list'], - })).toEqual(expectedResult); + }; + const expectedResult = { + collection: { + nestedCollection: {}, + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: ['collection', 'nestedCollection', 'list'], + }), + ).toEqual(expectedResult); }); - it('REACT_CMF.COLLECTION_REMOVE should throw when collection doesn\'t exist', () => { + it("REACT_CMF.COLLECTION_REMOVE should throw when collection doesn't exist", () => { expect(() => { collectionsReducers(initialState, { type: 'REACT_CMF.COLLECTION_REMOVE', collectionId: ['unknown collection'], }); - }).toThrowError('Can\'t remove collection unknown collection since it doesn\'t exist.'); + }).toThrowError("Can't remove collection unknown collection since it doesn't exist."); }); }); describe('REACT_CMF.COLLECTION_MUTATE', () => { - it('shouldn\'t mutate if id doesn\'t exist', () => { - expect(collectionsReducers(mapInitialState, { - type: 'REACT_CMF.COLLECTION_MUTATE', - id: ['wrongCollectionid'], - operation: {}, - })).toEqual(mapInitialState); + it("shouldn't mutate if id doesn't exist", () => { + expect( + collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['wrongCollectionid'], + operation: {}, + }), + ).toEqual(mapInitialState); }); - it('shouldn\'t mutate if no operations', () => { - expect(collectionsReducers(mapInitialState, { - type: 'REACT_CMF.COLLECTION_MUTATE', - id: ['collectionid'], - })).toEqual(mapInitialState); + it("shouldn't mutate if no operations", () => { + expect( + collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + }), + ).toEqual(mapInitialState); }); describe('#add', () => { @@ -121,7 +159,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { add: [{ id: 2, label: 'test data 2' }], }, }); - expect(nextState.get('collectionid').toJS()).toEqual([ + expect(nextState.collectionid).toEqual([ { id: 0, label: 'test data 0' }, { id: 1, label: 'test data 1' }, { id: 2, label: 'test data 2' }, @@ -129,15 +167,13 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }); it('should insert elements to nested List properly', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { - list: [ - { id: 0, label: 'test data 0' }, - ], + list: [{ id: 0, label: 'test data 0' }], }, }, - }); + }; const nextState = collectionsReducers(initState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collection', 'nestedCollection', 'list'], @@ -148,7 +184,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { ], }, }); - expect(nextState.getIn(['collection', 'nestedCollection', 'list']).toJS()).toEqual([ + expect(nextState.collection.nestedCollection.list).toEqual([ { id: 0, label: 'test data 0' }, { id: 1, label: 'test data 1' }, { id: 2, label: 'test data 2' }, @@ -163,17 +199,17 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { add: [{ test2: 'test data 2' }], }, }); - expect(nextState).toEqual( - new Map().set('collectionid', fromJS({ + expect(nextState).toEqual({ + collectionid: { test0: 'test data 0', test1: 'test data 1', test2: 'test data 2', - })) - ); + }, + }); }); it('should insert elements to nested Map properly', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { obj: { @@ -182,7 +218,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }, - }); + }; const nextState = collectionsReducers(initState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collection', 'nestedCollection', 'obj'], @@ -190,19 +226,17 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { add: [{ test2: 'test data 2' }], }, }); - expect(nextState).toEqual( - fromJS({ - collection: { - nestedCollection: { - obj: { - test0: 'test data 0', - test1: 'test data 1', - test2: 'test data 2', - }, + expect(nextState).toEqual({ + collection: { + nestedCollection: { + obj: { + test0: 'test data 0', + test1: 'test data 1', + test2: 'test data 2', }, }, - }), - ); + }, + }); }); }); @@ -215,13 +249,11 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { delete: [0], }, }); - expect(nextState.get('collectionid').toJS()).toEqual([ - { id: 1, label: 'test data 1' }, - ]); + expect(nextState.collectionid).toEqual([{ id: 1, label: 'test data 1' }]); }); it('should delete elements from nested List properly', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { list: [ @@ -231,7 +263,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { ], }, }, - }); + }; const nextState = collectionsReducers(initState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collection', 'nestedCollection', 'list'], @@ -239,9 +271,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { delete: [0, 1], }, }); - expect(nextState.getIn(['collection', 'nestedCollection', 'list']).toJS()).toEqual([ - { id: 2, label: 'test data 2' }, - ]); + expect(nextState.collection.nestedCollection.list).toEqual([{ id: 2, label: 'test data 2' }]); }); it('should delete elements from Map properly', () => { @@ -252,15 +282,15 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { delete: ['test0'], }, }); - expect(nextState).toEqual( - new Map().set('collectionid', fromJS({ + expect(nextState).toEqual({ + collectionid: { test1: 'test data 1', - })) - ); + }, + }); }); it('should delete elements from nested Map properly', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { obj: { @@ -270,7 +300,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }, - }); + }; const nextState = collectionsReducers(initState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collection', 'nestedCollection', 'obj'], @@ -278,12 +308,12 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { delete: ['test0', 'test1'], }, }); - expect(nextState.getIn(['collection', 'nestedCollection', 'obj']).toJS()).toEqual({ + expect(nextState.collection.nestedCollection.obj).toEqual({ test2: 'test data 2', }); }); - it('should delete nothing when ids don\'t match in List', () => { + it("should delete nothing when ids don't match in List", () => { const nextState = collectionsReducers(listInitialState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collectionid'], @@ -294,7 +324,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { expect(nextState).toEqual(listInitialState); }); - it('should delete nothing when ids don\'t match in Map', () => { + it("should delete nothing when ids don't match in Map", () => { const nextState = collectionsReducers(mapInitialState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collectionid'], @@ -317,14 +347,14 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }); - expect(nextState.get('collectionid').toJS()).toEqual([ + expect(nextState.collectionid).toEqual([ { id: 0, label: 'new test data 0' }, { id: 1, label: 'test data 1' }, ]); }); it('should update elements of nested List properly', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { list: [ @@ -334,7 +364,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { ], }, }, - }); + }; const nextState = collectionsReducers(initState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collection', 'nestedCollection', 'list'], @@ -345,7 +375,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }); - expect(nextState.getIn(['collection', 'nestedCollection', 'list']).toJS()).toEqual([ + expect(nextState.collection.nestedCollection.list).toEqual([ { id: 0, label: 'new test data 0' }, { id: 1, label: 'new test data 1' }, { id: 2, label: 'test data 2' }, @@ -362,16 +392,16 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }); - expect(nextState).toEqual( - new Map().set('collectionid', fromJS({ + expect(nextState).toEqual({ + collectionid: { test0: 'new test data 0', test1: 'test data 1', - })) - ); + }, + }); }); it('should update elements of nested Map properly', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { obj: { @@ -381,7 +411,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }, - }); + }; const nextState = collectionsReducers(initState, { type: 'REACT_CMF.COLLECTION_MUTATE', id: ['collection', 'nestedCollection', 'obj'], @@ -392,7 +422,7 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { }, }, }); - expect(nextState.getIn(['collection', 'nestedCollection', 'obj']).toJS()).toEqual({ + expect(nextState.collection.nestedCollection.obj).toEqual({ test0: 'new test data 0', test1: 'new test data 1', test2: 'test data 2', @@ -403,28 +433,37 @@ describe('REACT_CMF.COLLECTION_MUTATE', () => { describe('should properly perform all operations if collectionId is string', () => { it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly add data into store', () => { - expect(collectionsReducers(initialState, { - type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', - collectionId: 'collectionId', - data: 'data can be anything', - })).toEqual(new Map() - .set('collection1', 'super data') - .set('collectionId', 'data can be anything')); + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: 'collectionId', + data: 'data can be anything', + }), + ).toEqual({ + collection1: 'super data', + collectionId: 'data can be anything', + }); }); it('REACT_CMF.COLLECTION_REMOVE should properly remove collection from the store', () => { - const initState = fromJS({ + const initState = { collection: { nestedCollection: { list: ['item 1', 'item 2'], }, }, - }); - const expectedResult = initState.deleteIn(['collection', 'nestedCollection', 'list']); - expect(collectionsReducers(initState, { - type: 'REACT_CMF.COLLECTION_REMOVE', - collectionId: 'collection.nestedCollection.list', - })).toEqual(expectedResult); + }; + const expectedResult = { + collection: { + nestedCollection: {}, + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: 'collection.nestedCollection.list', + }), + ).toEqual(expectedResult); }); it('REACT_CMF.COLLECTION_MUTATE should mutate List properly', () => { @@ -435,7 +474,7 @@ describe('should properly perform all operations if collectionId is string', () add: [{ id: 2, label: 'test data 2' }], }, }); - expect(nextState.get('collectionid').toJS()).toEqual([ + expect(nextState.collectionid).toEqual([ { id: 0, label: 'test data 0' }, { id: 1, label: 'test data 1' }, { id: 2, label: 'test data 2' }, @@ -444,7 +483,7 @@ describe('should properly perform all operations if collectionId is string', () }); describe('getId', () => { - it('should return mutable element id', () => { + it('should return element id', () => { // given const element = { id: 'toto' }; @@ -454,17 +493,6 @@ describe('getId', () => { // then expect(id).toBe('toto'); }); - - it('should return immutable element id', () => { - // given - const element = fromJS({ id: 'toto' }); - - // when - const id = getId(element); - - // then - expect(id).toBe('toto'); - }); }); describe('getActionWithCollectionIdAsArray', () => { diff --git a/packages/cmf/__tests__/reducers/componentsReducers.test.js b/packages/cmf/__tests__/reducers/componentsReducers.test.js index 4a70df80df0..3caf0b55d16 100644 --- a/packages/cmf/__tests__/reducers/componentsReducers.test.js +++ b/packages/cmf/__tests__/reducers/componentsReducers.test.js @@ -1,14 +1,12 @@ -import { Map } from 'immutable'; - import reducer, { defaultState } from '../../src/reducers/componentsReducers'; global.console = { warn: jest.fn() }; describe('check component management reducer', () => { - const initialState = defaultState.set( - 'component1', - new Map().set('key1', new Map().set('searchQuery', '')), - ); + const initialState = { + ...defaultState, + component1: { key1: { searchQuery: '' } }, + }; beforeEach(() => { jest.clearAllMocks(); @@ -23,11 +21,10 @@ describe('check component management reducer', () => { key: 'key', initialComponentState: { searchQuery: 'data' }, }), - ).toEqual( - new Map() - .set('component1', new Map().set('key1', new Map().set('searchQuery', ''))) - .set('componentName', new Map().set('key', new Map().set('searchQuery', 'data'))), - ); + ).toEqual({ + component1: { key1: { searchQuery: '' } }, + componentName: { key: { searchQuery: 'data' } }, + }); }); it(`REACT_CMF.COMPONENT_ADD_STATE should properly add component/collection @@ -40,11 +37,10 @@ describe('check component management reducer', () => { key: 'key', initialComponentState: undefined, }), - ).toEqual( - new Map() - .set('component1', new Map().set('key1', new Map().set('searchQuery', ''))) - .set('componentName', new Map().set('key', new Map())), - ); + ).toEqual({ + component1: { key1: { searchQuery: '' } }, + componentName: { key: {} }, + }); }); it(`REACT_CMF.COMPONENT_ADD_STATE should properly add component/collection @@ -56,12 +52,9 @@ describe('check component management reducer', () => { key: 'key', initialComponentState: 'initialState', }), - ).toEqual( - new Map().set( - 'component1', - new Map().set('key1', new Map().set('searchQuery', '')).set('key', 'initialState'), - ), - ); + ).toEqual({ + component1: { key1: { searchQuery: '' }, key: 'initialState' }, + }); }); it('REACT_CMF.COMPONENT_ADD_STATE throw when a couple of componentName, key already exist', () => { @@ -86,9 +79,9 @@ describe('check component management reducer', () => { key: 'key1', componentState: { searchQuery: 'data' }, }), - ).toEqual( - new Map().set('component1', new Map().set('key1', new Map().set('searchQuery', 'data'))), - ); + ).toEqual({ + component1: { key1: { searchQuery: 'data' } }, + }); }); it(`REACT_CMF.COMPONENT_MERGE_STATE should throw when a couple of @@ -112,7 +105,7 @@ describe('check component management reducer', () => { componentName: 'component1', key: 'key1', }), - ).toEqual(new Map().set('component1', new Map())); + ).toEqual({ component1: {} }); }); it(`removeComponentState throw when a couple of componentName, collectionId doesn't exist`, () => { diff --git a/packages/cmf/__tests__/sagas/collection.test.js b/packages/cmf/__tests__/sagas/collection.test.js index 3cc85213091..732d12f3cb1 100644 --- a/packages/cmf/__tests__/sagas/collection.test.js +++ b/packages/cmf/__tests__/sagas/collection.test.js @@ -1,18 +1,15 @@ import { delay, call, select } from 'redux-saga/effects'; -import Immutable from 'immutable'; import selectors from '../../src/selectors'; -import { - waitFor, -} from '../../src/sagas/collection'; +import { waitFor } from '../../src/sagas/collection'; describe('waitFor', () => { it('should waitFor wait for a collection to exists', () => { const withoutCollection = { cmf: { - collections: new Immutable.Map({}), + collections: {}, }, }; - const withCollection = withoutCollection.cmf.collections.set('foo', new Immutable.Map({})); + const withCollection = { ...withoutCollection.cmf.collections, foo: {} }; const gen = waitFor('foo'); expect(gen.next().value).toEqual(select(selectors.collections.get, 'foo')); expect(gen.next().value).toEqual(delay(10)); diff --git a/packages/cmf/__tests__/selectors/toJS.test.js b/packages/cmf/__tests__/selectors/toJS.test.js index 553be88fcef..2c36d325238 100644 --- a/packages/cmf/__tests__/selectors/toJS.test.js +++ b/packages/cmf/__tests__/selectors/toJS.test.js @@ -1,4 +1,3 @@ -import Immutable from 'immutable'; import toJS from '../../src/selectors/toJS'; describe('toJS', () => { @@ -9,10 +8,10 @@ describe('toJS', () => { it('should return a function', () => { expect(typeof toJS(selector)).toBe('function'); }); - it('the returned function should call toJS on the results', () => { + it('the returned function should return the result of the selector', () => { const myselector = toJS(selector); const state = { - foo: new Immutable.Map({ bar: 'bar' }), + foo: { bar: 'bar' }, }; const result = myselector(state); expect(result).toEqual({ bar: 'bar' }); @@ -20,38 +19,30 @@ describe('toJS', () => { it('the returned function should return same reference on multiple calls', () => { const myselector = toJS(selector); const state = { - foo: new Immutable.Map({ bar: 'bar' }), + foo: { bar: 'bar' }, }; const result1 = myselector(state); const result2 = myselector(state); expect(result1).toBe(result2); }); - it('the returned function should return a different result if store is has been modified', () => { + it('the returned function should return a different result if store has been modified', () => { const myselector = toJS(selector); const state = { - foo: new Immutable.Map({ bar: 'bar' }), + foo: { bar: 'bar' }, }; const result1 = myselector(state); - state.foo = state.foo.set('bar', 'baz'); + state.foo = { bar: 'baz' }; const result2 = myselector(state); expect(result1).not.toBe(result2); expect(result2.bar).toBe('baz'); }); - it('the returned function should throw an error if the selector return a not immutable data', () => { + it('the returned function should return undefined if the selector doesn t return data', () => { const myselector = toJS(selector); - const state = { - foo: { bar: 'bar' }, - }; - const toThrow = () => myselector(state); - expect(toThrow).toThrow(); + const state = {}; + expect(myselector(state)).toBeUndefined(); }); it('should throw if selector is not a function', () => { const toThrow = () => toJS({}); expect(toThrow).toThrow(); }); - it('the returned function should return undefined if the selector doesn t return data', () => { - const myselector = toJS(selector); - const state = {}; - expect(myselector(state)).toBeUndefined(); - }); }); diff --git a/packages/cmf/package.json b/packages/cmf/package.json index 2e12bd6de07..cfe13934099 100644 --- a/packages/cmf/package.json +++ b/packages/cmf/package.json @@ -46,13 +46,11 @@ "@talend/utils": "^3.7.1", "commander": "^6.2.1", "hoist-non-react-statics": "^3.3.2", - "immutable": "^3.8.2", "invariant": "^2.2.4", "lodash": "^4.17.23", "nested-combine-reducers": "^1.2.2", "path-to-regexp": "^8.3.0", "prop-types": "^15.8.1", - "react-immutable-proptypes": "^2.2.0", "react-redux": "^7.2.9", "redux": "^4.2.1", "redux-batched-actions": "^0.5.0", diff --git a/packages/cmf/src/cmfConnect.jsx b/packages/cmf/src/cmfConnect.jsx index 3950c99b989..37dd836942f 100644 --- a/packages/cmf/src/cmfConnect.jsx +++ b/packages/cmf/src/cmfConnect.jsx @@ -24,7 +24,6 @@ export default cmfConnect({ import PropTypes from 'prop-types'; import { useState, useContext, useEffect, forwardRef } from 'react'; import hoistStatics from 'hoist-non-react-statics'; -import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect, useStore } from 'react-redux'; import { randomUUID } from '@talend/utils'; import actions from './actions'; @@ -219,7 +218,7 @@ export default function cmfConnect({ ); } function getState(state, id = 'default') { - return state.cmf.components.getIn([getComponentName(WrappedComponent), id], defaultState); + return state.cmf.components?.[getComponentName(WrappedComponent)]?.[id] ?? defaultState; } function getSetStateAction(state, id, type) { return { @@ -392,8 +391,8 @@ cmfConnect.omit = omit; cmfConnect.omitAllProps = props => cmfConnect.omit(props, cmfConnect.ALL_INJECTED_PROPS); cmfConnect.propTypes = { - state: ImmutablePropTypes.map, - initialState: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.object]), + state: PropTypes.object, + initialState: PropTypes.object, getComponent: PropTypes.func, setState: PropTypes.func, initState: PropTypes.func, diff --git a/packages/cmf/src/componentState.js b/packages/cmf/src/componentState.js index d63c1417f81..66114fb2fc6 100644 --- a/packages/cmf/src/componentState.js +++ b/packages/cmf/src/componentState.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import Immutable from 'immutable'; import actions from './actions'; /** @@ -23,7 +22,7 @@ export default cmfConnect({})(MyComponent); export function getStateProps(state, name, id = 'default') { return { - state: state.cmf.components.getIn([name, id]), + state: state.cmf.components?.[name]?.[id], }; } @@ -67,9 +66,9 @@ export function getStateAccessors(dispatch, name, id, DEFAULT_STATE) { initState(initialState) { let state; if (DEFAULT_STATE) { - state = DEFAULT_STATE.merge(initialState); + state = { ...DEFAULT_STATE, ...(initialState || {}) }; } else if (initialState) { - state = Immutable.Map.isMap(initialState) ? initialState : Immutable.fromJS(initialState); + state = initialState; } if (state) { const componentState = actions.components.addState(name, id, state); diff --git a/packages/cmf/src/expressions/allOf.js b/packages/cmf/src/expressions/allOf.js index 38e1b28c964..53a1aeffe0b 100644 --- a/packages/cmf/src/expressions/allOf.js +++ b/packages/cmf/src/expressions/allOf.js @@ -1,15 +1,13 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; export default function getAllOfFunction(statePath) { - return function includes({ context }, immutablePath, values) { + return function includes({ context }, path, values) { if (!Array.isArray(values)) { throw new Error('You should pass an array of values to check if all of them are present'); } - const arr = get(context.store.getState(), statePath, new Immutable.Map()).getIn( - immutablePath.split('.'), - new Immutable.List(), - ); - return arr.size > 0 && arr.every(value => values.includes(value)); + const stateSlice = get(context.store.getState(), statePath, {}); + const arr = get(stateSlice, path.split('.'), []); + // Checks that every item in the stored array is among the provided values (subset check) + return Array.isArray(arr) && arr.length > 0 && arr.every(value => values.includes(value)); }; } diff --git a/packages/cmf/src/expressions/getInState.js b/packages/cmf/src/expressions/getInState.js index 67eadc66f23..89d7face6fb 100644 --- a/packages/cmf/src/expressions/getInState.js +++ b/packages/cmf/src/expressions/getInState.js @@ -1,12 +1,9 @@ import _get from 'lodash/get'; -import Immutable from 'immutable'; import curry from 'lodash/curry'; -function getInState(statePath, { context }, immutablePath, defaultValue) { - return _get(context.store.getState(), statePath, new Immutable.Map()).getIn( - immutablePath.split('.'), - defaultValue, - ); +function getInState(statePath, { context }, path, defaultValue) { + const stateSlice = _get(context.store.getState(), statePath, {}); + return _get(stateSlice, path.split('.'), defaultValue); } export default curry(getInState); diff --git a/packages/cmf/src/expressions/includes.js b/packages/cmf/src/expressions/includes.js index ed94cadb2a5..f61c35b1d8c 100644 --- a/packages/cmf/src/expressions/includes.js +++ b/packages/cmf/src/expressions/includes.js @@ -1,10 +1,9 @@ import _get from 'lodash/get'; -import Immutable from 'immutable'; export default function getIncludesFunction(statePath) { - return function includes({ context }, immutablePath, value) { - return _get(context.store.getState(), statePath, new Immutable.Map()) - .getIn(immutablePath.split('.'), new Immutable.List()) - .includes(value); + return function includes({ context }, path, value) { + const stateSlice = _get(context.store.getState(), statePath, {}); + const arr = _get(stateSlice, path.split('.'), []); + return Array.isArray(arr) && arr.includes(value); }; } diff --git a/packages/cmf/src/expressions/index.md b/packages/cmf/src/expressions/index.md index be4a5737b2f..041490aa639 100644 --- a/packages/cmf/src/expressions/index.md +++ b/packages/cmf/src/expressions/index.md @@ -16,12 +16,11 @@ For all the following example we take this component as example: ```javascript import React from 'react'; import PropTypes from 'prop-types'; -import Immutable from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; -const DEFAULT_STATE = new Immutable.Map({ +const DEFAULT_STATE = { like: false, -}); +}; class Article extends React.Component { static propTypes = { @@ -35,11 +34,11 @@ class Article extends React.Component { } onLike() { - this.props.setState({ like: !this.props.state.get('like') }); + this.props.setState({ like: !this.props.state.like }); } render() { - const like = this.props.state.get('like'); + const like = this.props.state.like; return (

{props.title}

@@ -51,7 +50,8 @@ class Article extends React.Component { } function mapStateToProps(state) { return { - model: state.cmf.collections.get('article'); + model: state.cmf.collections['article'], + }; }; } export cmfConnect({mapStateToProps})(MyComponent) @@ -101,6 +101,7 @@ export cmfConnect({mapStateToProps})(MyComponent) } } ``` + ### allOf ```json diff --git a/packages/cmf/src/expressions/oneOf.js b/packages/cmf/src/expressions/oneOf.js index a040c6bc92d..dd4a91425a4 100644 --- a/packages/cmf/src/expressions/oneOf.js +++ b/packages/cmf/src/expressions/oneOf.js @@ -1,15 +1,12 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; export default function getOneOfFunction(statePath) { - return function includes({ context }, immutablePath, values) { + return function includes({ context }, path, values) { if (!Array.isArray(values)) { throw new Error('You should pass an array of values to check if one of them is present'); } - const arr = get(context.store.getState(), statePath, new Immutable.Map()).getIn( - immutablePath.split('.'), - new Immutable.List(), - ); - return values.some(value => arr.includes(value)); + const stateSlice = get(context.store.getState(), statePath, {}); + const arr = get(stateSlice, path.split('.'), []); + return Array.isArray(arr) && values.some(value => arr.includes(value)); }; } diff --git a/packages/cmf/src/localStorage.js b/packages/cmf/src/localStorage.js index 595be0a4adc..ed6dd7b4478 100644 --- a/packages/cmf/src/localStorage.js +++ b/packages/cmf/src/localStorage.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import get from 'lodash/get'; import set from 'lodash/set'; /** @@ -12,14 +12,6 @@ function getState(key) { return {}; } source = JSON.parse(source); - if (source.cmf) { - if (source.cmf.components) { - source.cmf.components = Immutable.fromJS(source.cmf.components); - } - if (source.cmf.collections) { - source.cmf.collections = Immutable.fromJS(source.cmf.collections); - } - } return source; } @@ -47,14 +39,14 @@ function getStoreCallback(key, paths) { paths.forEach(path => { if (path.length > 2) { if (path[1] === 'components') { - const value = state.cmf.components.getIn(path.slice(2)); + const value = get(state.cmf.components, path.slice(2)); if (value) { - set(toKeep, path, value.toJS()); + set(toKeep, path, value); } } else if (path[1] === 'collections') { - const value = state.cmf.collections.getIn(path.slice(2)); + const value = get(state.cmf.collections, path.slice(2)); if (value) { - set(toKeep, path, value.toJS()); + set(toKeep, path, value); } } } diff --git a/packages/cmf/src/onEvent.js b/packages/cmf/src/onEvent.js index 9540e1c6c6b..97a97d82c33 100644 --- a/packages/cmf/src/onEvent.js +++ b/packages/cmf/src/onEvent.js @@ -1,5 +1,4 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; import CONSTANT from './constant'; function serializeEvent(event) { @@ -59,9 +58,7 @@ function getOnEventSetStateHandler(instance, props, config, currentHandler) { } } else if (value === 'toggle') { // because toggle need to read the state we dispatch it with a function - instance.props.setState(_props => - instance.props.setState({ [key]: !_props.state.get(key) }), - ); + instance.props.setState(_props => instance.props.setState({ [key]: !_props.state[key] })); } else { // eslint-disable-next-line no-param-reassign acc[key] = value; @@ -84,7 +81,7 @@ const ACTION_CREATOR = 'ACTION_CREATOR'; const DISPATCH = 'DISPATCH'; const SETSTATE = 'SETSTATE'; -const INITIAL_STATE = new Immutable.Map(); +const INITIAL_STATE = {}; function addOnEventSupport(handlerType, instance, props, key) { if (CONSTANT[`IS_HANDLER_${handlerType}_REGEX`].test(key)) { diff --git a/packages/cmf/src/reducers/collectionsReducers.js b/packages/cmf/src/reducers/collectionsReducers.js index a1fdbfe1391..15481c4c7b9 100644 --- a/packages/cmf/src/reducers/collectionsReducers.js +++ b/packages/cmf/src/reducers/collectionsReducers.js @@ -1,21 +1,33 @@ /** * @module react-cmf/lib/reducers/collectionsReducers */ -import { Map, List, fromJS } from 'immutable'; +import get from 'lodash/get'; +import has from 'lodash/has'; +import cloneDeep from 'lodash/cloneDeep'; +import set from 'lodash/set'; +import unset from 'lodash/unset'; import invariant from 'invariant'; import CONSTANTS from '../constant'; -export const defaultState = new Map(); +export const defaultState = {}; + +function setIn(state, path, value) { + const cloned = cloneDeep(state); + set(cloned, path, value); + return cloned; +} + +function deleteIn(state, path) { + const cloned = cloneDeep(state); + unset(cloned, path); + return cloned; +} /** - * Get element id. If it doesn't have "id" property, we consider it as immutable. + * Get element id. */ export function getId(element) { - const id = element.id; - if (id === undefined) { - return element.get('id'); - } - return id; + return element.id; } /* @@ -43,14 +55,14 @@ export function getActionWithCollectionIdAsArray(action) { function addCollectionElement(state, action) { if (action.operations.add) { return action.operations.add.reduce((s, e) => { - const element = s.getIn(action.collectionId); - if (List.isList(element)) { - return s.setIn(action.collectionId, element.push(e)); + const element = get(s, action.collectionId); + if (Array.isArray(element)) { + return setIn(s, action.collectionId, [...element, e]); } - if (Map.isMap(element)) { - return s.setIn(action.collectionId, element.merge(e)); + if (typeof element === 'object' && !Array.isArray(element) && element !== null) { + return setIn(s, action.collectionId, { ...element, ...e }); } - return state; + return s; }, state); } return state; @@ -61,22 +73,26 @@ function deleteListElements(state, action) { return action.operations.delete.indexOf(getId(element)) >= 0; } - const collection = state.getIn(action.collectionId); + const collection = get(state, action.collectionId); if (collection.some(shouldBeRemoved)) { - return state.setIn(action.collectionId, collection.filterNot(shouldBeRemoved)); + return setIn( + state, + action.collectionId, + collection.filter(e => !shouldBeRemoved(e)), + ); } return state; } function deleteMapElements(state, action) { - const collection = state.getIn(action.collectionId); - - if (action.operations.delete.some(id => collection.has(id))) { - const changedCollection = action.operations.delete.reduce( - (collectionAccu, element) => collectionAccu.delete(element), - collection, - ); - return state.setIn(action.collectionId, changedCollection); + const collection = get(state, action.collectionId); + + if (action.operations.delete.some(id => id in collection)) { + const changedCollection = action.operations.delete.reduce((collectionAccu, element) => { + const { [element]: _, ...rest } = collectionAccu; + return rest; + }, collection); + return setIn(state, action.collectionId, changedCollection); } return state; @@ -91,33 +107,33 @@ function deleteMapElements(state, action) { */ function deleteCollectionElement(state, action) { if (action.operations.delete) { - const collection = state.getIn(action.collectionId); - if (Map.isMap(collection)) { + const collection = get(state, action.collectionId); + if (!Array.isArray(collection) && typeof collection === 'object' && collection !== null) { return deleteMapElements(state, action); - } else if (List.isList(collection)) { + } else if (Array.isArray(collection)) { return deleteListElements(state, action); } - throw new Error('CMF collection deletion is only compatible with ImmutableJs List and Map'); + throw new Error('CMF collection deletion is only compatible with plain arrays and objects'); } return state; } function updateListElements(state, action) { const updates = action.operations.update; - - const changedCollection = state - .getIn(action.collectionId) - .map(element => updates[getId(element)] || element); - return state.setIn(action.collectionId, changedCollection); + const changedCollection = get(state, action.collectionId).map(element => + getId(element) in updates ? updates[getId(element)] : element, + ); + return setIn(state, action.collectionId, changedCollection); } function updateMapElements(state, action) { const updates = action.operations.update; + const currentCollection = get(state, action.collectionId); const changedCollection = Object.keys(updates).reduce( - (collectionAccu, id) => collectionAccu.set(id, updates[id]), - state.getIn(action.collectionId), + (collectionAccu, id) => ({ ...collectionAccu, [id]: updates[id] }), + currentCollection, ); - return state.setIn(action.collectionId, changedCollection); + return setIn(state, action.collectionId, changedCollection); } /** @@ -129,13 +145,13 @@ function updateMapElements(state, action) { */ function updateCollectionElement(state, action) { if (action.operations.update) { - const collection = state.getIn(action.collectionId); - if (Map.isMap(collection)) { + const collection = get(state, action.collectionId); + if (!Array.isArray(collection) && typeof collection === 'object' && collection !== null) { return updateMapElements(state, action); - } else if (List.isList(collection)) { + } else if (Array.isArray(collection)) { return updateListElements(state, action); } - throw new Error('CMF collection update is only compatible with ImmutableJs List and Map'); + throw new Error('CMF collection update is only compatible with plain arrays and objects'); } return state; } @@ -148,7 +164,7 @@ function updateCollectionElement(state, action) { * @returns {object} the new state */ function mutateCollection(state, action) { - if (!action.operations || !state.hasIn(action.collectionId) || state.isEmpty()) { + if (!action.operations || !has(state, action.collectionId)) { return state; } let newState = addCollectionElement(state, action); @@ -165,16 +181,16 @@ function collectionsReducers(state = defaultState, action = { type: '' }) { const newAction = getActionWithCollectionIdAsArray(action); switch (newAction.type) { case CONSTANTS.COLLECTION_ADD_OR_REPLACE: - return state.setIn(newAction.collectionId, fromJS(newAction.data)); + return setIn(state, newAction.collectionId, newAction.data); case CONSTANTS.COLLECTION_REMOVE: - if (!state.getIn(newAction.collectionId)) { + if (!has(state, newAction.collectionId)) { invariant( process.env.NODE_ENV === 'production', `Can't remove collection ${newAction.collectionId} since it doesn't exist.`, ); return state; } - return state.deleteIn(newAction.collectionId); + return deleteIn(state, newAction.collectionId); case CONSTANTS.COLLECTION_MUTATE: return mutateCollection(state, newAction); default: diff --git a/packages/cmf/src/reducers/collectionsReducers.test.js b/packages/cmf/src/reducers/collectionsReducers.test.js new file mode 100644 index 00000000000..751b1533db9 --- /dev/null +++ b/packages/cmf/src/reducers/collectionsReducers.test.js @@ -0,0 +1,558 @@ +import collectionsReducers, { + defaultState, + getId, + getActionWithCollectionIdAsArray, +} from './collectionsReducers'; + +const initialState = { ...defaultState, collection1: 'super data' }; + +const listInitialState = { + ...defaultState, + collectionid: [ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + ], +}; +const mapInitialState = { + ...defaultState, + collectionid: { + test0: 'test data 0', + test1: 'test data 1', + }, +}; + +describe('check collection management reducer', () => { + it('should return state if no action passed', () => { + expect(collectionsReducers(initialState)).toEqual(initialState); + }); + + it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly add data into store', () => { + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collectionId'], + data: 'data can be anything', + }), + ).toEqual({ + collection1: 'super data', + collectionId: 'data can be anything', + }); + }); + + it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly replace data into store', () => { + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collection1'], + data: 'data can be anything', + }), + ).toEqual({ collection1: 'data can be anything' }); + }); + + it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly add nested collection into store', () => { + const initState = { + collection1: { + data: 'data can be anything', + }, + }; + const expectedResult = { + collection1: { + data: 'data can be anything', + nestedCollection: ['item 1', 'item 2'], + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collection1', 'nestedCollection'], + data: ['item 1', 'item 2'], + }), + ).toEqual(expectedResult); + }); + + it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly replace nested collection into store', () => { + const initState = { + collection1: { + nestedCollection: 'data can be anything', + }, + }; + const expectedResult = { + collection1: { + nestedCollection: ['item 1', 'item 2'], + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: ['collection1', 'nestedCollection'], + data: ['item 1', 'item 2'], + }), + ).toEqual(expectedResult); + }); + + it('REACT_CMF.COLLECTION_REMOVE should properly remove collection from the store', () => { + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: ['collection1'], + }), + ).toEqual({}); + }); + + it('REACT_CMF.COLLECTION_REMOVE should properly remove nested collection from the store', () => { + const initState = { + collection: { + nestedCollection: { + list: ['item 1', 'item 2'], + }, + }, + }; + const expectedResult = { + collection: { + nestedCollection: {}, + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: ['collection', 'nestedCollection', 'list'], + }), + ).toEqual(expectedResult); + }); + + it("REACT_CMF.COLLECTION_REMOVE should throw when collection doesn't exist", () => { + expect(() => { + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: ['unknown collection'], + }); + }).toThrowError("Can't remove collection unknown collection since it doesn't exist."); + }); +}); + +describe('REACT_CMF.COLLECTION_MUTATE', () => { + it("shouldn't mutate if id doesn't exist", () => { + expect( + collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['wrongCollectionid'], + operation: {}, + }), + ).toEqual(mapInitialState); + }); + + it("shouldn't mutate if no operations", () => { + expect( + collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + }), + ).toEqual(mapInitialState); + }); + + describe('#add', () => { + it('should insert elements to List properly', () => { + const nextState = collectionsReducers(listInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + add: [{ id: 2, label: 'test data 2' }], + }, + }); + expect(nextState.collectionid).toEqual([ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + { id: 2, label: 'test data 2' }, + ]); + }); + + it('should insert elements to nested List properly', () => { + const initState = { + collection: { + nestedCollection: { + list: [{ id: 0, label: 'test data 0' }], + }, + }, + }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collection', 'nestedCollection', 'list'], + operations: { + add: [ + { id: 1, label: 'test data 1' }, + { id: 2, label: 'test data 2' }, + ], + }, + }); + expect(nextState.collection.nestedCollection.list).toEqual([ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + { id: 2, label: 'test data 2' }, + ]); + }); + + it('should insert elements to Map properly', () => { + const nextState = collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + add: [{ test2: 'test data 2' }], + }, + }); + expect(nextState).toEqual({ + collectionid: { + test0: 'test data 0', + test1: 'test data 1', + test2: 'test data 2', + }, + }); + }); + + it('should insert elements to nested Map properly', () => { + const initState = { + collection: { + nestedCollection: { + obj: { + test0: 'test data 0', + test1: 'test data 1', + }, + }, + }, + }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collection', 'nestedCollection', 'obj'], + operations: { + add: [{ test2: 'test data 2' }], + }, + }); + expect(nextState).toEqual({ + collection: { + nestedCollection: { + obj: { + test0: 'test data 0', + test1: 'test data 1', + test2: 'test data 2', + }, + }, + }, + }); + }); + + it('should leave state unchanged when add target is neither array nor object (fallback branch)', () => { + const initState = { ...defaultState, collectionid: 'primitive-value' }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + add: [{ id: 0, label: 'new' }], + }, + }); + expect(nextState).toEqual(initState); + }); + }); + + describe('#delete', () => { + it('should delete elements from List properly', () => { + const nextState = collectionsReducers(listInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + delete: [0], + }, + }); + expect(nextState.collectionid).toEqual([{ id: 1, label: 'test data 1' }]); + }); + + it('should delete elements from nested List properly', () => { + const initState = { + collection: { + nestedCollection: { + list: [ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + { id: 2, label: 'test data 2' }, + ], + }, + }, + }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collection', 'nestedCollection', 'list'], + operations: { + delete: [0, 1], + }, + }); + expect(nextState.collection.nestedCollection.list).toEqual([{ id: 2, label: 'test data 2' }]); + }); + + it('should delete elements from Map properly', () => { + const nextState = collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + delete: ['test0'], + }, + }); + expect(nextState).toEqual({ + collectionid: { + test1: 'test data 1', + }, + }); + }); + + it('should delete elements from nested Map properly', () => { + const initState = { + collection: { + nestedCollection: { + obj: { + test0: 'test data 0', + test1: 'test data 1', + test2: 'test data 2', + }, + }, + }, + }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collection', 'nestedCollection', 'obj'], + operations: { + delete: ['test0', 'test1'], + }, + }); + expect(nextState.collection.nestedCollection.obj).toEqual({ + test2: 'test data 2', + }); + }); + + it("should delete nothing when ids don't match in List", () => { + const nextState = collectionsReducers(listInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + delete: ['unknown'], + }, + }); + expect(nextState).toEqual(listInitialState); + }); + + it("should delete nothing when ids don't match in Map", () => { + const nextState = collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + delete: ['unknown'], + }, + }); + expect(nextState).toEqual(mapInitialState); + }); + }); + + describe('#update', () => { + it('should update elements of List properly', () => { + const nextState = collectionsReducers(listInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + update: { + 0: { id: 0, label: 'new test data 0' }, + }, + }, + }); + expect(nextState.collectionid).toEqual([ + { id: 0, label: 'new test data 0' }, + { id: 1, label: 'test data 1' }, + ]); + }); + + it('should update elements of nested List properly', () => { + const initState = { + collection: { + nestedCollection: { + list: [ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + { id: 2, label: 'test data 2' }, + ], + }, + }, + }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collection', 'nestedCollection', 'list'], + operations: { + update: { + 0: { id: 0, label: 'new test data 0' }, + 1: { id: 1, label: 'new test data 1' }, + }, + }, + }); + expect(nextState.collection.nestedCollection.list).toEqual([ + { id: 0, label: 'new test data 0' }, + { id: 1, label: 'new test data 1' }, + { id: 2, label: 'test data 2' }, + ]); + }); + + it('should update elements of Map properly', () => { + const nextState = collectionsReducers(mapInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + update: { + test0: 'new test data 0', + }, + }, + }); + expect(nextState).toEqual({ + collectionid: { + test0: 'new test data 0', + test1: 'test data 1', + }, + }); + }); + + it('should update elements of nested Map properly', () => { + const initState = { + collection: { + nestedCollection: { + obj: { + test0: 'test data 0', + test1: 'test data 1', + test2: 'test data 2', + }, + }, + }, + }; + const nextState = collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collection', 'nestedCollection', 'obj'], + operations: { + update: { + test0: 'new test data 0', + test1: 'new test data 1', + }, + }, + }); + expect(nextState.collection.nestedCollection.obj).toEqual({ + test0: 'new test data 0', + test1: 'new test data 1', + test2: 'test data 2', + }); + }); + + it('should update elements of List to falsy update values properly', () => { + const nextState = collectionsReducers(listInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: ['collectionid'], + operations: { + update: { + 0: null, + }, + }, + }); + expect(nextState.collectionid).toEqual([null, { id: 1, label: 'test data 1' }]); + }); + }); +}); + +describe('should properly perform all operations if collectionId is string', () => { + it('REACT_CMF.COLLECTION_ADD_OR_REPLACE should properly add data into store', () => { + expect( + collectionsReducers(initialState, { + type: 'REACT_CMF.COLLECTION_ADD_OR_REPLACE', + collectionId: 'collectionId', + data: 'data can be anything', + }), + ).toEqual({ + collection1: 'super data', + collectionId: 'data can be anything', + }); + }); + + it('REACT_CMF.COLLECTION_REMOVE should properly remove collection from the store', () => { + const initState = { + collection: { + nestedCollection: { + list: ['item 1', 'item 2'], + }, + }, + }; + const expectedResult = { + collection: { + nestedCollection: {}, + }, + }; + expect( + collectionsReducers(initState, { + type: 'REACT_CMF.COLLECTION_REMOVE', + collectionId: 'collection.nestedCollection.list', + }), + ).toEqual(expectedResult); + }); + + it('REACT_CMF.COLLECTION_MUTATE should mutate List properly', () => { + const nextState = collectionsReducers(listInitialState, { + type: 'REACT_CMF.COLLECTION_MUTATE', + id: 'collectionid', + operations: { + add: [{ id: 2, label: 'test data 2' }], + }, + }); + expect(nextState.collectionid).toEqual([ + { id: 0, label: 'test data 0' }, + { id: 1, label: 'test data 1' }, + { id: 2, label: 'test data 2' }, + ]); + }); +}); + +describe('getId', () => { + it('should return element id', () => { + // given + const element = { id: 'toto' }; + + // when + const id = getId(element); + + // then + expect(id).toBe('toto'); + }); +}); + +describe('getActionWithCollectionIdAsArray', () => { + it('should return action if there is not collectionId or id fields in action object', () => { + const action = { + type: 'SOME_ACTION', + }; + const result = getActionWithCollectionIdAsArray(action); + expect(result).toEqual(action); + }); + + it('should return new formed action if path to collection is represented by "collectionId" field', () => { + const action = { + type: 'SOME_ACTION', + collectionId: 'collection.nestedCollection', + }; + const expectedResult = { + type: 'SOME_ACTION', + collectionId: ['collection', 'nestedCollection'], + }; + const result = getActionWithCollectionIdAsArray(action); + expect(result).toEqual(expectedResult); + }); + + it('should return new formed action if path to collection is represented by "id" field (mutateCollection action creator)', () => { + const action = { + type: 'SOME_ACTION', + id: 'collection.nestedCollection', + }; + const expectedResult = { + type: 'SOME_ACTION', + id: 'collection.nestedCollection', + collectionId: ['collection', 'nestedCollection'], + }; + const result = getActionWithCollectionIdAsArray(action); + expect(result).toEqual(expectedResult); + }); +}); diff --git a/packages/cmf/src/reducers/componentsReducers.js b/packages/cmf/src/reducers/componentsReducers.js index 9017c5ac09e..42ac40b862e 100644 --- a/packages/cmf/src/reducers/componentsReducers.js +++ b/packages/cmf/src/reducers/componentsReducers.js @@ -3,11 +3,10 @@ * @module react-cmf/lib/reducers/componentsReducers */ import get from 'lodash/get'; -import { Map, fromJS } from 'immutable'; import invariant from 'invariant'; import CONSTANTS from '../constant'; -export const defaultState = new Map(); +export const defaultState = {}; /** * given the state and action, determine if another component try to bind to a specific @@ -17,7 +16,7 @@ export const defaultState = new Map(); */ export function warnIfAnotherComponentBind(state, action) { if (process.env.NODE_ENV !== 'production') { - if (state.getIn([action.componentName, action.key])) { + if (state[action.componentName]?.[action.key]) { console.warn(`Beware component ${action.componentName} try to recreate an existing State namespace ${action.key}, meaning that the original one will be overloaded`); } @@ -32,7 +31,7 @@ export function warnIfAnotherComponentBind(state, action) { */ export function warnIfRemovingStateDoesntExist(state, action) { if (process.env.NODE_ENV !== 'production') { - if (!state.getIn([action.componentName, action.key])) { + if (!state[action.componentName]?.[action.key]) { console.warn(`Beware the component ${action.componentName} try to remove a non existing State namespace ${action.key}, it isn't a normal behavior execpt if two component are binded to this specific namespace`); @@ -48,7 +47,7 @@ export function warnIfRemovingStateDoesntExist(state, action) { * @param {Object} action a redux action */ export function errorIfMergingStateDoesntExist(state, action) { - if (!state.getIn([action.componentName, action.key])) { + if (!state[action.componentName]?.[action.key]) { invariant( process.env.NODE_ENV === 'production', `Error, the component ${action.componentName} try to mutate a non existing @@ -65,22 +64,38 @@ export function errorIfMergingStateDoesntExist(state, action) { */ export function componentsReducers(state = defaultState, action) { switch (action.type) { - case CONSTANTS.COMPONENT_ADD_STATE: + case CONSTANTS.COMPONENT_ADD_STATE: { warnIfAnotherComponentBind(state, action); - if (action.initialComponentState) { - return state.setIn( - [action.componentName, action.key], - fromJS(action.initialComponentState), - ); - } - return state.setIn([action.componentName, action.key], new Map()); + const componentState = + action.initialComponentState !== undefined ? action.initialComponentState : {}; + return { + ...state, + [action.componentName]: { + ...state[action.componentName], + [action.key]: componentState, + }, + }; + } case CONSTANTS.COMPONENT_MERGE_STATE: errorIfMergingStateDoesntExist(state, action); - - return state.mergeIn([action.componentName, action.key], fromJS(action.componentState)); - case CONSTANTS.COMPONENT_REMOVE_STATE: + return { + ...state, + [action.componentName]: { + ...state[action.componentName], + [action.key]: { + ...(state[action.componentName]?.[action.key] || {}), + ...action.componentState, + }, + }, + }; + case CONSTANTS.COMPONENT_REMOVE_STATE: { warnIfRemovingStateDoesntExist(state, action); - return state.deleteIn([action.componentName, action.key]); + const { [action.key]: _removed, ...remainingKeys } = state[action.componentName] || {}; + return { + ...state, + [action.componentName]: remainingKeys, + }; + } default: { const subAction = get(action, 'cmf.componentState'); if (subAction) { diff --git a/packages/cmf/src/reducers/componentsReducers.test.js b/packages/cmf/src/reducers/componentsReducers.test.js new file mode 100644 index 00000000000..30d81cf0766 --- /dev/null +++ b/packages/cmf/src/reducers/componentsReducers.test.js @@ -0,0 +1,142 @@ +import reducer, { defaultState } from './componentsReducers'; + +global.console = { warn: vi.fn() }; + +describe('check component management reducer', () => { + const initialState = { + ...defaultState, + component1: { key1: { searchQuery: '' } }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it(`REACT_CMF.COMPONENT_ADD_STATE should properly add component/collection + state tracking to the store if nor the component/key exist`, () => { + expect( + reducer(initialState, { + type: 'REACT_CMF.COMPONENT_ADD_STATE', + componentName: 'componentName', + key: 'key', + initialComponentState: { searchQuery: 'data' }, + }), + ).toEqual({ + component1: { key1: { searchQuery: '' } }, + componentName: { key: { searchQuery: 'data' } }, + }); + }); + + it(`REACT_CMF.COMPONENT_ADD_STATE should properly add component/collection + state tracking to the store if nor the component/key exist + event if initialState is undefined`, () => { + expect( + reducer(initialState, { + type: 'REACT_CMF.COMPONENT_ADD_STATE', + componentName: 'componentName', + key: 'key', + initialComponentState: undefined, + }), + ).toEqual({ + component1: { key1: { searchQuery: '' } }, + componentName: { key: {} }, + }); + }); + + it(`REACT_CMF.COMPONENT_ADD_STATE should properly add component/collection + state tracking to the store if the key don't exist`, () => { + expect( + reducer(initialState, { + type: 'REACT_CMF.COMPONENT_ADD_STATE', + componentName: 'component1', + key: 'key', + initialComponentState: 'initialState', + }), + ).toEqual({ + component1: { key1: { searchQuery: '' }, key: 'initialState' }, + }); + }); + + it('REACT_CMF.COMPONENT_ADD_STATE throw when a couple of componentName, key already exist', () => { + const action = { + type: 'REACT_CMF.COMPONENT_ADD_STATE', + componentName: 'component1', + key: 'key1', + initialComponentState: 'initialState', + }; + reducer(initialState, action); + expect(console.warn).toHaveBeenCalled(); + expect(console.warn.mock.calls[0][0]) + .toEqual(`Beware component component1 try to recreate an existing + State namespace key1, meaning that the original one will be overloaded`); + }); + + it(`REACT_CMF.COMPONENT_MERGE_STATE should properly merge + component/key state into the store`, () => { + expect( + reducer(initialState, { + type: 'REACT_CMF.COMPONENT_MERGE_STATE', + componentName: 'component1', + key: 'key1', + componentState: { searchQuery: 'data' }, + }), + ).toEqual({ + component1: { key1: { searchQuery: 'data' } }, + }); + }); + + it(`REACT_CMF.COMPONENT_MERGE_STATE should throw when a couple of + componentName, keyId doesn't exist`, () => { + const action = { + type: 'REACT_CMF.COMPONENT_MERGE_STATE', + componentName: 'component', + key: 'key', + componentState: { searchQuery: 'data' }, + }; + expect(() => reducer(initialState, action)) + .toThrow(`Error, the component component try to mutate a non existing + State namespace key, this namespace may be not yet created or already removed.`); + }); + + it(`REACT_CMF.COMPONENT_REMOVE_STATE should properly add + component/key state tracking to the store`, () => { + expect( + reducer(initialState, { + type: 'REACT_CMF.COMPONENT_REMOVE_STATE', + componentName: 'component1', + key: 'key1', + }), + ).toEqual({ component1: {} }); + }); + + it(`removeComponentState throw when a couple of componentName, + collectionId doesn't exist`, () => { + const action = { + type: 'REACT_CMF.COMPONENT_REMOVE_STATE', + componentName: 'component', + key: 'key', + }; + reducer(initialState, action); + expect(console.warn).toHaveBeenCalled(); + expect(console.warn.mock.calls[0][0]) + .toEqual(`Beware the component component try to remove a non existing + State namespace key, it isn't a normal behavior execpt if two component are binded + to this specific namespace`); + }); + + it('should recall itself on action.cmf.componentState', () => { + const action = { + type: 'REACT_CMF.COMPONENT_ADD_STATE', + componentName: 'componentName', + key: 'key', + initialComponentState: { searchQuery: 'data' }, + }; + const subAction = { + type: 'WHAT_EVER', + cmf: { + componentState: action, + }, + }; + expect(reducer(initialState, action)).toEqual(reducer(initialState, subAction)); + }); +}); diff --git a/packages/cmf/src/selectors/collections.js b/packages/cmf/src/selectors/collections.js index 7366b282cd3..59b4deae4f3 100644 --- a/packages/cmf/src/selectors/collections.js +++ b/packages/cmf/src/selectors/collections.js @@ -1,4 +1,4 @@ -import { Map, List } from 'immutable'; +import _get from 'lodash/get'; import getToJSMemoized from './toJS'; export function getAll(state) { @@ -13,14 +13,8 @@ export function getAll(state) { * get('foo.bar', true) === state.cmf.collections.getIn(['foo', 'bar'], true) */ export function get(state, collectionPath, defaultValue) { - let path; - if (typeof collectionPath === 'string') { - path = collectionPath.split('.'); - } else if (Array.isArray(collectionPath)) { - path = collectionPath; - } - if (path) { - return state.cmf.collections.getIn(path, defaultValue); + if (typeof collectionPath === 'string' || Array.isArray(collectionPath)) { + return _get(state.cmf.collections, collectionPath, defaultValue); } throw Error(`Type mismatch: collectionPath should be a string or an array of string got ${collectionPath}`); @@ -35,15 +29,17 @@ got ${collectionPath}`); */ export function findListItem(state, collectionPath, itemId) { const collectionOrCollectionSubset = get(state, collectionPath); - if (List.isList(collectionOrCollectionSubset)) { - return collectionOrCollectionSubset.find(element => element && element.get('id') === itemId); + if (Array.isArray(collectionOrCollectionSubset)) { + return collectionOrCollectionSubset.find(element => element && element.id === itemId); } throw Error( - `Type mismatch: ${collectionPath} does not resolve as an instance of Immutable.List, + `Type mismatch: ${collectionPath} does not resolve as an Array, got ${collectionOrCollectionSubset}`, ); } +// Cache keys are joined path strings; bounded in practice by the finite set of +// distinct paths used in the application. const selectors = {}; export function toJS(state, path) { @@ -61,9 +57,8 @@ export function toJS(state, path) { * @returns {Object|Array|undefined} */ export function getCollectionPlain(state, collectionId) { - const collection = state.cmf.collections.get(collectionId); + const collection = state.cmf.collections[collectionId]; if (collection == null) return undefined; - if (typeof collection.toJS === 'function') return collection.toJS(); return collection; } @@ -76,18 +71,17 @@ export function getCollectionPlain(state, collectionId) { * @returns {Array|undefined} */ function extractItems(collection) { - if (Map.isMap(collection)) { - return collection.get('items'); + if (collection !== null && typeof collection === 'object' && !Array.isArray(collection)) { + return collection.items; } return collection; } export function getCollectionItems(state, collectionId) { - const collection = state.cmf.collections.get(collectionId); + const collection = state.cmf.collections[collectionId]; if (collection == null) return undefined; const items = extractItems(collection); if (items == null) return undefined; - if (typeof items.toJS === 'function') return items.toJS(); return items; } @@ -101,15 +95,11 @@ export function getCollectionItems(state, collectionId) { * @returns {Object|undefined} */ export function getCollectionItem(state, collectionId, itemId) { - const collection = state.cmf.collections.get(collectionId); + const collection = state.cmf.collections[collectionId]; if (collection == null) return undefined; const items = extractItems(collection); - if (!items || typeof items.find !== 'function') return undefined; - const found = items.find(item => { - if (item && typeof item.get === 'function') return item.get('id') === itemId; - return item && item.id === itemId; - }); + if (!items || !Array.isArray(items)) return undefined; + const found = items.find(item => item && item.id === itemId); if (found == null) return undefined; - if (typeof found.toJS === 'function') return found.toJS(); return found; } diff --git a/packages/cmf/src/selectors/collections.test.js b/packages/cmf/src/selectors/collections.test.js index c6b8eae72d1..6426b779a26 100644 --- a/packages/cmf/src/selectors/collections.test.js +++ b/packages/cmf/src/selectors/collections.test.js @@ -1,4 +1,3 @@ -import { Map, List } from 'immutable'; import { describe, it, expect } from 'vitest'; import { get, @@ -11,15 +10,15 @@ import { } from './collections'; describe('collections.get', () => { - const collection = new Map({ id: 'id' }); - const collectionSubset = new Map({ subset: 'subset' }); - const collectionWithSubset = new Map({ collectionSubset }); + const collection = { id: 'id' }; + const collectionSubset = { subset: 'subset' }; + const collectionWithSubset = { collectionSubset }; const state = { cmf: { - collections: new Map({ + collections: { collection, collectionWithSubset, - }), + }, }, }; @@ -39,27 +38,27 @@ describe('collections.get', () => { }); describe('collections.getAll', () => { - const collectionsMap = new Map({ foo: new Map({ bar: 'baz' }) }); + const collectionsMap = { foo: { bar: 'baz' } }; const state = { cmf: { collections: collectionsMap } }; - it('returns the entire collections map', () => { + it('returns the entire collections object', () => { expect(getAll(state)).toBe(collectionsMap); }); }); describe('collections.findListItem', () => { const id = 'id'; - const item = new Map({ id }); + const item = { id }; const state = { cmf: { - collections: new Map({ - isList: new List([item]), - isNotList: new Map({ id: item }), - }), + collections: { + isList: [item], + isNotList: { id: item }, + }, }, }; - it('finds an item by id in a List collection', () => { + it('finds an item by id in an Array collection', () => { expect(findListItem(state, 'isList', id)).toBe(item); }); @@ -67,7 +66,7 @@ describe('collections.findListItem', () => { expect(findListItem(state, 'isList', 'notFound')).toBeUndefined(); }); - it('throws when collection is not a List', () => { + it('throws when collection is not an Array', () => { expect(() => findListItem(state, 'isNotList', id)).toThrow('Type mismatch'); }); }); @@ -75,13 +74,13 @@ describe('collections.findListItem', () => { describe('collections.toJS', () => { const state = { cmf: { - collections: new Map({ - foo: new Map({ bar: new Map({ hello: 'world' }) }), - }), + collections: { + foo: { bar: { hello: 'world' } }, + }, }, }; - it('converts an Immutable object to plain JS', () => { + it('returns the plain JS value at the given path', () => { expect(toJS(state, 'foo.bar')).toEqual({ hello: 'world' }); }); @@ -95,13 +94,13 @@ describe('collections.toJS', () => { describe('collections.getCollectionPlain', () => { const state = { cmf: { - collections: new Map({ - myList: new List([new Map({ id: '1', name: 'Alice' })]), - }), + collections: { + myList: [{ id: '1', name: 'Alice' }], + }, }, }; - it('returns a plain JS array for an Immutable List collection', () => { + it('returns the collection as-is when collectionId is found', () => { const result = getCollectionPlain(state, 'myList'); expect(result).toEqual([{ id: '1', name: 'Alice' }]); expect(result).not.toHaveProperty('get'); @@ -115,27 +114,27 @@ describe('collections.getCollectionPlain', () => { describe('collections.getCollectionItems', () => { const state = { cmf: { - collections: new Map({ - directList: new List([new Map({ id: '1' })]), - wrappedList: new Map({ items: new List([new Map({ id: '2' })]) }), - emptyWrapped: new Map({ other: 'value' }), - }), + collections: { + directList: [{ id: '1' }], + wrappedList: { items: [{ id: '2' }] }, + emptyWrapped: { other: 'value' }, + }, }, }; - it('returns plain array when collection is a direct List', () => { + it('returns array when collection is a direct Array', () => { const result = getCollectionItems(state, 'directList'); expect(result).toEqual([{ id: '1' }]); expect(Array.isArray(result)).toBe(true); }); - it('returns plain array from items key when collection is a Map wrapping items', () => { + it('returns array from items key when collection is a plain object with items', () => { const result = getCollectionItems(state, 'wrappedList'); expect(result).toEqual([{ id: '2' }]); expect(Array.isArray(result)).toBe(true); }); - it('returns undefined when collection is a Map without items key', () => { + it('returns undefined when collection is a plain object without items key', () => { expect(getCollectionItems(state, 'emptyWrapped')).toBeUndefined(); }); @@ -145,26 +144,26 @@ describe('collections.getCollectionItems', () => { }); describe('collections.getCollectionItem', () => { - const items = new List([ - new Map({ id: 'a', label: 'Alpha' }), - new Map({ id: 'b', label: 'Beta' }), - ]); + const items = [ + { id: 'a', label: 'Alpha' }, + { id: 'b', label: 'Beta' }, + ]; const state = { cmf: { - collections: new Map({ + collections: { directList: items, - wrappedList: new Map({ items }), - }), + wrappedList: { items }, + }, }, }; - it('finds item by id in a direct List collection', () => { + it('finds item by id in a direct Array collection', () => { const result = getCollectionItem(state, 'directList', 'a'); expect(result).toEqual({ id: 'a', label: 'Alpha' }); expect(result).not.toHaveProperty('get'); }); - it('finds item by id in a Map-wrapped collection', () => { + it('finds item by id in an object-wrapped collection', () => { const result = getCollectionItem(state, 'wrappedList', 'b'); expect(result).toEqual({ id: 'b', label: 'Beta' }); }); diff --git a/packages/cmf/src/selectors/components.js b/packages/cmf/src/selectors/components.js index 4ba5093e9cc..83e1748ddaf 100644 --- a/packages/cmf/src/selectors/components.js +++ b/packages/cmf/src/selectors/components.js @@ -5,17 +5,13 @@ /** * Get the state of a specific component instance as a plain JS object. - * Covers the `state.cmf.components.getIn([componentName, instanceId])` pattern. * @param {Object} state - Redux state * @param {String} componentName - Component name (e.g., 'Container(Notification)') * @param {String} instanceId - Component instance id (e.g., 'default') * @returns {Object|undefined} */ export function getComponentState(state, componentName, instanceId) { - const compState = state.cmf.components.getIn([componentName, instanceId]); - if (compState == null) return undefined; - if (typeof compState.toJS === 'function') return compState.toJS(); - return compState; + return state.cmf.components?.[componentName]?.[instanceId]; } /** @@ -25,20 +21,16 @@ export function getComponentState(state, componentName, instanceId) { * @returns {Object|undefined} */ export function getAllComponentStates(state, componentName) { - const instances = state.cmf.components.get(componentName); - if (instances == null) return undefined; - if (typeof instances.toJS === 'function') return instances.toJS(); - return instances; + return state.cmf.components?.[componentName]; } /** * Get a specific property from a component instance state as a plain value. - * Covers the `state.cmf.components.getIn([...path], default)` pattern used in - * containers/src/Notification/pushNotification.js. * @param {Object} state - Redux state * @param {String} componentName - Component name * @param {String} instanceId - Instance id * @param {String} property - Property key + * @param {*} defaultValue - Default value if not found * @returns {*} plain JS value */ export function getComponentStateProperty( @@ -48,15 +40,6 @@ export function getComponentStateProperty( property, defaultValue, ) { - const compState = state.cmf.components.getIn([componentName, instanceId]); - if (compState == null) return defaultValue; - let val; - if (typeof compState.get === 'function') { - val = compState.get(property); - } else { - val = compState[property]; - } - if (val == null) return defaultValue; - if (typeof val.toJS === 'function') return val.toJS(); - return val; + const val = state.cmf.components?.[componentName]?.[instanceId]?.[property]; + return val ?? defaultValue; } diff --git a/packages/cmf/src/selectors/components.test.js b/packages/cmf/src/selectors/components.test.js index 21c3a116181..66ea1bda828 100644 --- a/packages/cmf/src/selectors/components.test.js +++ b/packages/cmf/src/selectors/components.test.js @@ -1,10 +1,9 @@ -import { Map, List, fromJS } from 'immutable'; import { describe, it, expect } from 'vitest'; import { getComponentState, getAllComponentStates, getComponentStateProperty } from './components'; const makeState = componentsMap => ({ cmf: { - components: fromJS(componentsMap), + components: componentsMap, }, }); @@ -54,30 +53,25 @@ describe('components.getAllComponentStates', () => { }); describe('components.getComponentStateProperty', () => { - const notifications = new List([{ id: '1', message: 'Hi' }]); - const state = { - cmf: { - components: new Map({ - 'Container(Notification)': new Map({ - Notification: new Map({ - notifications, - }), - }), - 'Container(List)': new Map({ - default: new Map({ - searchQuery: 'test', - }), - }), - }), + const state = makeState({ + 'Container(Notification)': { + Notification: { + notifications: [{ id: '1', message: 'Hi' }], + }, + }, + 'Container(List)': { + default: { + searchQuery: 'test', + }, }, - }; + }); it('returns plain scalar value for a string property', () => { const result = getComponentStateProperty(state, 'Container(List)', 'default', 'searchQuery'); expect(result).toBe('test'); }); - it('returns plain JS array for an Immutable List property', () => { + it('returns plain array for a notifications property', () => { const result = getComponentStateProperty( state, 'Container(Notification)', diff --git a/packages/cmf/src/selectors/toJS.js b/packages/cmf/src/selectors/toJS.js index a83dbdfe6d7..5e38e0fe858 100644 --- a/packages/cmf/src/selectors/toJS.js +++ b/packages/cmf/src/selectors/toJS.js @@ -1,19 +1,15 @@ function toJS(data) { - if (data) { - if (typeof data.toJS === 'function') { - return data.toJS(); - } - throw new Error('the selector return a data which is not an immutable'); - } - return undefined; + return data; } /** - * toJS is an higher order selector. - * It modify a given selector to return the value as a POJO + * toJS is a higher order selector. + * The store is now plain JS — this wrapper is retained for backward compatibility + * and memoization only (prevents unnecessary re-renders when selector result is the + * same reference). No conversion is performed. * Note: your selector must use only one selector * @param {function} selector the selector - * @returns the POJO associated to the given selector + * @returns the value returned by the given selector (already a plain JS value) */ export default function getToJSMemoized(selector) { if (typeof selector !== 'function') { diff --git a/packages/components/package.json b/packages/components/package.json index 36f134b255b..c2d8ff24530 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -59,7 +59,6 @@ "date-fns": "^3.6.0", "dom-helpers": "^3.4.0", "focus-outline-manager": "^1.0.2", - "immutable": "^3.8.2", "invariant": "^2.2.4", "lodash": "^4.17.23", "memoize-one": "^6.0.0", diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx index 578be68de7d..d15143b801e 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx @@ -3,9 +3,6 @@ import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import get from 'lodash/get'; import classNames from 'classnames'; -// TODO(epic-3+): remove once all consumers have migrated away from ImmutableJS. -// Kept for runtime backward-compat: callers (e.g. containers/ActionDropdown.connect) may still pass an ImmutableList. -import { Iterable } from 'immutable'; import { DropdownButton, MenuItem } from '@talend/react-bootstrap'; import { withTranslation } from 'react-i18next'; import omit from 'lodash/omit'; @@ -97,11 +94,6 @@ function renderMutableMenuItem(item, index, getComponent) { } function getMenuItem(item, index, getComponent) { - // TODO(epic-3+): remove this branch once all consumers pass plain JS objects. - if (Iterable.isIterable(item)) { - return renderMutableMenuItem(item.toJS(), index, getComponent); - } - return renderMutableMenuItem(item, index, getComponent); } @@ -267,8 +259,8 @@ class ActionDropdown extends Component { }} noCaret > - {/* items.size: ImmutableList backward-compat guard during migration — TODO(epic-3+): remove */} - {!children && !items.length && !items.size && !loading && !components && ( + {/* items.size: backward-compat guard removed — items is always a plain array */} + {!children && !items.length && !loading && !components && ( {t('ACTION_DROPDOWN_EMPTY', { defaultValue: 'No options' })} diff --git a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js index c1907aafce6..1491d849fd3 100644 --- a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js +++ b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js @@ -1,16 +1,12 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; -import Immutable from 'immutable'; export function addPathsToCollection(index, collection, paths, jsonpath) { - return collection.set(index, paths.push(jsonpath)); + return { ...collection, [index]: [...paths, jsonpath] }; } export function removePathsFromCollection(index, collection, paths, jsonpath) { - return collection.set( - index, - paths.filter(path => path !== jsonpath), - ); + return { ...collection, [index]: paths.filter(path => path !== jsonpath) }; } /** @@ -51,23 +47,23 @@ export default class TreeManager extends Component { this.state = { isAllExpanded: props.isAllExpanded || false, - collapsedNodes: props.collapsedNodes || Immutable.Map(), - expandedNodes: props.expandedNodes || Immutable.Map().set(0, Immutable.List(['$'])), + collapsedNodes: props.collapsedNodes || {}, + expandedNodes: props.expandedNodes || { 0: ['$'] }, }; } onExpandAll = () => { - this.setState(oldState => ({ + this.setState({ isAllExpanded: true, - collapsedNodes: oldState.collapsedNodes.clear(), - })); + collapsedNodes: {}, + }); }; onCollapseAll = () => { - this.setState(oldState => ({ + this.setState({ isAllExpanded: false, - expandedNodes: oldState.expandedNodes.clear(), - })); + expandedNodes: {}, + }); }; onToggle = (event, options, index) => { @@ -88,7 +84,7 @@ export default class TreeManager extends Component { index, collection, isAllExpanded, - collection.get(index, Immutable.List()), + collection[index] ?? [], options, ), }); @@ -98,7 +94,7 @@ export default class TreeManager extends Component { index, collection, isAllExpanded, - collection.get(index, Immutable.List()), + collection[index] ?? [], options, ), }); @@ -115,7 +111,7 @@ export default class TreeManager extends Component { onToggle: this.onToggle, onCollapseAll: this.onCollapseAll, onExpandAll: this.onExpandAll, - paths: isAllExpanded ? this.state.collapsedNodes.toJS() : this.state.expandedNodes.toJS(), + paths: isAllExpanded ? this.state.collapsedNodes : this.state.expandedNodes, highlighted: this.props.highlighted, isAllExpanded, }; diff --git a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx index a9d4ce332ca..d483b56576d 100644 --- a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx +++ b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx @@ -1,7 +1,5 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import Immutable from 'immutable'; - import TreeManager, { addPathsToCollection, removePathsFromCollection, @@ -9,19 +7,19 @@ import TreeManager, { describe('addPathsToCollection', () => { it('should add the jsonpath to the paths collection', () => { - const myMap = Immutable.Map(); - const myList = Immutable.List(); + const myMap = {}; + const myList = []; const newMap = addPathsToCollection(0, myMap, myList, 'jsonpath'); - expect(newMap.get(0).toJS()).toEqual(['jsonpath']); + expect(newMap[0]).toEqual(['jsonpath']); }); }); describe('removePathsFromCollection', () => { it('should remove the jsonpath to the paths collection', () => { - const myList = Immutable.List(['jsonpath', 'somestuff']); - const myMap = Immutable.Map({ 0: myList }); + const myList = ['jsonpath', 'somestuff']; + const myMap = { 0: myList }; const newCollection = removePathsFromCollection(0, myMap, myList, 'jsonpath'); - expect(newCollection.get(0).toJS()).toEqual(['somestuff']); + expect(newCollection[0]).toEqual(['somestuff']); }); }); diff --git a/packages/containers/.storybook/cmfModule/index.js b/packages/containers/.storybook/cmfModule/index.js index 080161a2cb6..2998b5065a2 100644 --- a/packages/containers/.storybook/cmfModule/index.js +++ b/packages/containers/.storybook/cmfModule/index.js @@ -1,4 +1,3 @@ -import { fromJS } from 'immutable'; import actionCreators from './actionCreators'; import components from './components'; import expressions from './expressions'; @@ -15,7 +14,7 @@ const cmfModule = { modules: [containersModule], preloadedState: { cmf: { - collections: fromJS({ + collections: { with: { data: [ { @@ -122,7 +121,7 @@ const cmfModule = { }, ], }, - }), + }, }, }, }; diff --git a/packages/containers/package.json b/packages/containers/package.json index 8b0f12847aa..2a1c5de5461 100644 --- a/packages/containers/package.json +++ b/packages/containers/package.json @@ -51,11 +51,9 @@ "@talend/react-forms": "^16.1.1", "@talend/utils": "^3.7.1", "classnames": "^2.5.1", - "immutable": "^3.8.2", "invariant": "^2.2.4", "lodash": "^4.17.23", "memoize-one": "^6.0.0", - "react-immutable-proptypes": "^2.2.0", "redux-saga": "^1.4.2", "reselect": "^2.5.4" }, diff --git a/packages/containers/src/AboutDialog/AboutDialog.container.jsx b/packages/containers/src/AboutDialog/AboutDialog.container.jsx index 589390db39b..568ceb1c276 100644 --- a/packages/containers/src/AboutDialog/AboutDialog.container.jsx +++ b/packages/containers/src/AboutDialog/AboutDialog.container.jsx @@ -1,14 +1,13 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import omit from 'lodash/omit'; -import { Map } from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; import Component from '@talend/react-components/lib/AboutDialog'; import Constants from './AboutDialog.constant'; -export const DEFAULT_STATE = new Map({ +export const DEFAULT_STATE = { expanded: false, -}); +}; class AboutDialog extends RComponent { static displayName = 'Container(AboutDialog)'; @@ -26,7 +25,7 @@ class AboutDialog extends RComponent { } toggle() { - this.props.setState(({ state }) => ({ expanded: !state.get('expanded') })); + this.props.setState(({ state }) => ({ expanded: !state?.expanded })); } hide() { @@ -39,9 +38,9 @@ class AboutDialog extends RComponent { ); diff --git a/packages/containers/src/AboutDialog/AboutDialog.test.js b/packages/containers/src/AboutDialog/AboutDialog.test.js index 346fd05ec3f..56a49584ad7 100644 --- a/packages/containers/src/AboutDialog/AboutDialog.test.js +++ b/packages/containers/src/AboutDialog/AboutDialog.test.js @@ -1,10 +1,5 @@ import Container from './AboutDialog.container'; -/** - * cmf.selectors.collections.toJS calls getIn([COLLECTION_ID]) and then .toJS() on the result. - * Returning the default value (undefined) causes `{...undefined} = {}`, matching the test expectation. - */ -const makeCollections = () => ({ getIn: (_keys, def) => def }); import Connected, { mapStateToProps } from './AboutDialog.connect'; import Constants from './AboutDialog.constant'; @@ -17,7 +12,7 @@ describe('Connected AboutDialog', () => { it('should mapStateToProps with an empty list of products', () => { const state = { cmf: { - collections: makeCollections(), + collections: {}, }, }; const ownProps = {}; diff --git a/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx b/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx index 14e8bdd992c..7e9c11bf6cf 100644 --- a/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx +++ b/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; import cmf, { cmfConnect } from '@talend/react-cmf'; import { ActionDropdown } from '@talend/react-components/lib/Actions'; import omit from 'lodash/omit'; @@ -50,7 +49,7 @@ export function ContainerActionDropdown({ items, ...props }) { ContainerActionDropdown.displayName = 'Container(ActionDropdown)'; ContainerActionDropdown.propTypes = { - items: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), ImmutablePropTypes.list]), + items: PropTypes.arrayOf(PropTypes.object), noCaret: PropTypes.bool, pullRight: PropTypes.bool, hideLabel: PropTypes.bool, diff --git a/packages/containers/src/ActionDropdown/ActionDropdown.stories.jsx b/packages/containers/src/ActionDropdown/ActionDropdown.stories.jsx index 7acc636b1da..4df59b65ab2 100644 --- a/packages/containers/src/ActionDropdown/ActionDropdown.stories.jsx +++ b/packages/containers/src/ActionDropdown/ActionDropdown.stories.jsx @@ -1,5 +1,4 @@ /* eslint-disable react/prop-types */ -import Immutable from 'immutable'; import { action } from 'storybook/actions'; import ActionDropdown from '.'; @@ -45,7 +44,7 @@ export function Default({ onSelect }) { displayMode: 'dropdown', label: 'my immutable items', onSelect, - items: Immutable.fromJS([ + items: [ { id: 'item1', label: 'First immutable label', @@ -54,7 +53,7 @@ export function Default({ onSelect }) { id: 'item2', label: '2nd immutable', }, - ]), + ], }; return ( diff --git a/packages/containers/src/AppLoader/AppLoader.connect.jsx b/packages/containers/src/AppLoader/AppLoader.connect.jsx index 23b2a2c8569..43796e71fd5 100644 --- a/packages/containers/src/AppLoader/AppLoader.connect.jsx +++ b/packages/containers/src/AppLoader/AppLoader.connect.jsx @@ -51,8 +51,8 @@ AppLoaderContainer.propTypes = { */ export function mapStateToProps(state, ownProps) { return { - loading: !get(ownProps, 'hasCollections', []).every(collectionName => - state.cmf.collections.has(collectionName), + loading: !get(ownProps, 'hasCollections', []).every( + collectionName => collectionName in state.cmf.collections, ), }; } diff --git a/packages/containers/src/AppLoader/AppLoader.connect.test.js b/packages/containers/src/AppLoader/AppLoader.connect.test.js index 47249d8799e..c8aac1d2a0c 100644 --- a/packages/containers/src/AppLoader/AppLoader.connect.test.js +++ b/packages/containers/src/AppLoader/AppLoader.connect.test.js @@ -1,14 +1,6 @@ import { render, screen } from '@testing-library/react'; import { AppLoaderContainer, mapStateToProps } from './AppLoader.connect'; -/** - * Returns a plain object shim with the Immutable.Map interface required by - * AppLoader.connect.jsx's mapStateToProps which calls `.has(collectionName)`. - */ -const makeCollections = (keys = []) => ({ - has: key => keys.includes(key), -}); - describe('AppLoader container', () => { describe('rendering', () => { it('should render child if not loading', () => { @@ -34,7 +26,7 @@ describe('AppLoader container', () => { describe('mapStateToProps', () => { it('should return loading to false if we have nothing to wait', () => { // given - const state = { cmf: { collections: makeCollections() } }; + const state = { cmf: { collections: {} } }; const ownProps = {}; // when const result = mapStateToProps(state, ownProps); @@ -44,7 +36,7 @@ describe('AppLoader container', () => { it('should return loading to true if there is something to wait', () => { // given - const state = { cmf: { collections: makeCollections(['test2']) } }; + const state = { cmf: { collections: { test2: null } } }; const ownProps = { hasCollections: ['test', 'test2'] }; // when const result = mapStateToProps(state, ownProps); @@ -56,7 +48,7 @@ describe('AppLoader container', () => { // given const state = { cmf: { - collections: makeCollections(['test2', 'test']), + collections: { test2: null, test: null }, }, }; const ownProps = { hasCollections: ['test', 'test2'] }; diff --git a/packages/containers/src/Breadcrumbs/Breadcrumbs.connect.jsx b/packages/containers/src/Breadcrumbs/Breadcrumbs.connect.jsx index 33c006c34e0..d8b6013dbe6 100644 --- a/packages/containers/src/Breadcrumbs/Breadcrumbs.connect.jsx +++ b/packages/containers/src/Breadcrumbs/Breadcrumbs.connect.jsx @@ -1,11 +1,10 @@ import { cmfConnect } from '@talend/react-cmf'; -import { Map } from 'immutable'; import Breadcrumbs from '@talend/react-components/lib/Breadcrumbs'; -const DEFAULT_STATE = Map(); +const DEFAULT_STATE = {}; export function ContainerBreadcrumbs({ state = DEFAULT_STATE, dispatchActionCreator, ...props }) { - const items = state.get('items', props.items); + const items = state?.items ?? props.items; const newProps = { ...props, items: @@ -14,8 +13,8 @@ export function ContainerBreadcrumbs({ state = DEFAULT_STATE, dispatchActionCrea ...item, onClick: (event, data) => dispatchActionCreator(item.actionCreator, event, data), })), - loading: state.get('loading', props.loading), - maxItems: state.get('maxItems', props.maxItems), + loading: state?.loading ?? props.loading, + maxItems: state?.maxItems ?? props.maxItems, }; return ; @@ -28,7 +27,7 @@ ContainerBreadcrumbs.propTypes = { }; export default cmfConnect({ - defaultState: Map({ items: [], maxItems: 10 }), + defaultState: { items: [], maxItems: 10 }, omitCMFProps: true, withComponentRegistry: true, withDispatchActionCreator: true, diff --git a/packages/containers/src/Breadcrumbs/Breadcrumbs.stories.jsx b/packages/containers/src/Breadcrumbs/Breadcrumbs.stories.jsx index 9eb97fb4039..a84c24c91da 100644 --- a/packages/containers/src/Breadcrumbs/Breadcrumbs.stories.jsx +++ b/packages/containers/src/Breadcrumbs/Breadcrumbs.stories.jsx @@ -1,7 +1,6 @@ -import { Map } from 'immutable'; import Breadcrumbs from '.'; -const initialState = new Map({ +const initialState = { items: [ { text: 'Text A', title: 'Text title A', actionCreator: 'breadcrumb:folder:openA' }, { text: 'Text B', title: 'Text title B', actionCreator: 'breadcrumb:folder:openB' }, @@ -12,7 +11,7 @@ const initialState = new Map({ }, ], maxItems: 3, -}); +}; export default { title: 'Breadcrumb', diff --git a/packages/containers/src/ComponentForm/ComponentForm.component.jsx b/packages/containers/src/ComponentForm/ComponentForm.component.jsx index 02c01815e5b..7af449e4e26 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.component.jsx +++ b/packages/containers/src/ComponentForm/ComponentForm.component.jsx @@ -4,7 +4,6 @@ import cmf, { cmfConnect } from '@talend/react-cmf'; import Form from '@talend/react-forms'; import omit from 'lodash/omit'; import get from 'lodash/get'; -import { Map } from 'immutable'; import memoizeOne from 'memoize-one'; import kit from './kit'; import tcompFieldsWidgets from './fields'; @@ -20,19 +19,19 @@ const TO_OMIT = [ ...cmfConnect.INJECTED_PROPS, ]; -export const DEFAULT_STATE = new Map({ +export const DEFAULT_STATE = { dirty: false, - initialState: new Map(), -}); + initialState: {}, +}; /** - * Convert immutable object to js object + * Returns object or null */ -export function toJS(immutableObject) { - if (!immutableObject) { +export function toJS(object) { + if (!object) { return null; } - return immutableObject.toJS(); + return object; } /** @@ -102,9 +101,9 @@ export class TCompForm extends Component { } componentDidUpdate(prevProps) { - const nextProperties = this.props.state.get('properties'); - if (prevProps.state.get('properties') !== nextProperties) { - this.setState({ properties: nextProperties?.toJS() || {} }); + const nextProperties = this.props.state.properties; + if (prevProps.state.properties !== nextProperties) { + this.setState({ properties: nextProperties || {} }); } if ( @@ -123,7 +122,7 @@ export class TCompForm extends Component { } onChange(_, payload) { - if (!this.props.state.get('dirty')) { + if (!this.props.state.dirty) { this.props.setState({ dirty: true }); } @@ -184,15 +183,15 @@ export class TCompForm extends Component { } onReset() { - this.props.setState(prev => - prev.state - .set('jsonSchema', this.props.state.getIn(['initialState', 'jsonSchema'])) - .set('uiSchema', this.props.state.getIn(['initialState', 'uiSchema'])) - .set('properties', this.props.state.getIn(['initialState', 'properties'])) - .set('dirty', false), - ); + const { initialState } = this.props.state; + this.props.setState({ + jsonSchema: initialState?.jsonSchema, + uiSchema: initialState?.uiSchema, + properties: initialState?.properties, + dirty: false, + }); this.setState({ - properties: this.props.state.getIn(['initialState', 'properties']).toJS(), + properties: initialState?.properties || {}, }); } @@ -213,8 +212,8 @@ export class TCompForm extends Component { getUISpec() { return { properties: this.state.properties, - jsonSchema: this.getMemoizedJsonSchema(this.props.state.get('jsonSchema')), - uiSchema: this.getMemoizedUiSchema(this.props.state.get('uiSchema')), + jsonSchema: this.getMemoizedJsonSchema(this.props.state.jsonSchema), + uiSchema: this.getMemoizedUiSchema(this.props.state.uiSchema), }; } @@ -222,9 +221,9 @@ export class TCompForm extends Component { const uiSpecs = this.getUISpec(); if (!uiSpecs.jsonSchema) { - const response = this.props.state.get('response'); + const response = this.props.state.response; if (response) { - return

{response.get('statusText')}

; + return

{response.statusText}

; } return
; } @@ -232,7 +231,7 @@ export class TCompForm extends Component { const props = { ...omit(this.props, TO_OMIT), data: uiSpecs, - initialData: this.getMemoizedInitialState(this.props.state.get('initialState')), + initialData: this.getMemoizedInitialState(this.props.state.initialState), onTrigger: this.onTrigger, onChange: this.onChange, onSubmit: this.onSubmit, diff --git a/packages/containers/src/ComponentForm/ComponentForm.saga.test.js b/packages/containers/src/ComponentForm/ComponentForm.saga.test.js index 7af55f47d89..69ca2a5a9b9 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.saga.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.saga.test.js @@ -1,9 +1,8 @@ import { call, select, put } from 'redux-saga/effects'; -import { fromJS, Map } from 'immutable'; import cmf from '@talend/react-cmf'; import * as sagas from './ComponentForm.sagas'; -import ConnectedTCompForm, { TCompForm } from './ComponentForm.component'; +import { TCompForm } from './ComponentForm.component'; describe('ComponentForm saga', () => { describe('*checkFormComponentId', () => { @@ -72,14 +71,14 @@ describe('ComponentForm saga', () => { const selector = selectJsonSchema.payload.selector; const jsonSchemaSelection = selector({ cmf: { - components: fromJS({ + components: { [TCompForm.displayName]: { [props.componentId]: { jsonSchema } }, - }), + }, }, }); // then - expect(jsonSchemaSelection.toJS()).toEqual(jsonSchema); + expect(jsonSchemaSelection).toEqual(jsonSchema); }); it('should NOT fetch uiSpec when it is already fetched', () => { @@ -174,9 +173,9 @@ describe('ComponentForm saga', () => { function getReduxStore() { return { cmf: { - components: fromJS({ + components: { [TCompForm.displayName]: { [componentId]: {} }, - }), + }, }, }; } @@ -231,19 +230,19 @@ describe('ComponentForm saga', () => { const errorStep = gen.next({ response }).value; expect(errorStep.payload).toBeDefined(); expect(errorStep.type).toBe('PUT'); - const setStateAction = errorStep.payload.action(null, getReduxStore); + const setStateAction = errorStep.payload.action; // then expect(setStateAction).toEqual({ cmf: { componentState: { componentName: 'ComponentForm', - componentState: new Map({ + componentState: { jsonSchema: undefined, uiSchema: undefined, response, dirty: false, - }), + }, key: 'MyComponentId', type: 'REACT_CMF.COMPONENT_MERGE_STATE', }, @@ -348,15 +347,15 @@ describe('ComponentForm saga', () => { describe('onFormSubmit', () => { const componentId = 'form'; const prevState = { - cmf: { components: fromJS({ [TCompForm.displayName]: { [componentId]: {} } }) }, + cmf: { components: { [TCompForm.displayName]: { [componentId]: {} } } }, }; const mergeStatePayload = { cmf: { componentState: { componentName: 'ComponentForm', - componentState: fromJS({ + componentState: { initialState: { jsonSchema: undefined, uiSchema: undefined, properties: 'prop' }, - }), + }, key: 'form', type: 'REACT_CMF.COMPONENT_MERGE_STATE', }, @@ -441,15 +440,14 @@ describe('ComponentForm saga', () => { // when const gen = sagas.handleSetDirtyState({ componentId, dirty }); // then - expect(gen.next().value).toEqual(select(ConnectedTCompForm.getState, componentId)); - expect(gen.next(Map({})).value).toEqual( + expect(gen.next().value).toEqual( put({ cmf: { componentState: { componentName: 'ComponentForm', - componentState: Map({ + componentState: { dirty: true, - }), + }, key: 'myId', type: 'REACT_CMF.COMPONENT_MERGE_STATE', }, diff --git a/packages/containers/src/ComponentForm/ComponentForm.sagas.js b/packages/containers/src/ComponentForm/ComponentForm.sagas.js index aabef03751f..d258d2e4960 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.sagas.js +++ b/packages/containers/src/ComponentForm/ComponentForm.sagas.js @@ -12,8 +12,7 @@ export function* fetchDefinition(action) { if (!response.ok) { yield put( Component.setStateAction( - prev => - prev.set('jsonSchema').set('uiSchema').set('response', response).set('dirty', false), + { jsonSchema: undefined, uiSchema: undefined, response, dirty: false }, action.componentId, ), ); @@ -44,9 +43,7 @@ export function* onDidMount({ uiSpecPath, data, }) { - const jsonSchema = yield select(state => - Component.getState(state, componentId).get('jsonSchema'), - ); + const jsonSchema = yield select(state => Component.getState(state, componentId)?.jsonSchema); if (!jsonSchema) { if (definition) { yield put( @@ -73,11 +70,15 @@ export function* onFormSubmit(componentId, submitURL, action) { } yield put( Component.setStateAction( - prev => - prev - .setIn(['initialState', 'jsonSchema'], prev.get('jsonSchema')) - .setIn(['initialState', 'uiSchema'], prev.get('uiSchema')) - .setIn(['initialState', 'properties'], action.properties), + prev => ({ + ...prev, + initialState: { + ...(prev.initialState ?? {}), + jsonSchema: prev.jsonSchema, + uiSchema: prev.uiSchema, + properties: action.properties, + }, + }), componentId, )(undefined, getReduxState), ); @@ -110,8 +111,7 @@ export function checkFormComponentId(componentId, actionType) { * @param {object} reduxAction with a componentId (string) & the dirtyState (boolean) to apply */ export function* handleSetDirtyState({ componentId, dirty }) { - const componentFormState = yield select(Component.getState, componentId); - yield put(Component.setStateAction(componentFormState.set('dirty', !!dirty), componentId)); + yield put(Component.setStateAction({ dirty: !!dirty }, componentId)); } export function* handle(props) { diff --git a/packages/containers/src/ComponentForm/ComponentForm.selectors.js b/packages/containers/src/ComponentForm/ComponentForm.selectors.js index fe4120b1073..9c36cdede70 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.selectors.js +++ b/packages/containers/src/ComponentForm/ComponentForm.selectors.js @@ -1,7 +1,7 @@ import ConnectedComponentForm from './ComponentForm.component'; export function isComponentFormDirty(state, componentName) { - return ConnectedComponentForm.getState(state, componentName).get('dirty', false); + return ConnectedComponentForm.getState(state, componentName)?.dirty ?? false; } export default isComponentFormDirty; diff --git a/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js b/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js index c3807bcd76f..2f530b5ee30 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js @@ -1,18 +1,6 @@ import { isComponentFormDirty } from './ComponentForm.selectors'; import { TCompForm } from './ComponentForm.component'; -/** Plain-object shim implementing .get(key, def) for component state. */ -const makeCompState = (data = {}) => ({ get: (k, def) => (k in data ? data[k] : def) }); -/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ -const makeComponents = (data = {}) => ({ - getIn([outer, inner], def) { - const outerVal = data[outer]; - if (outerVal == null) return def; - const innerVal = outerVal[inner]; - return innerVal !== undefined ? innerVal : def; - }, -}); - describe('ComponentForm selectors', () => { const componentName = 'comp'; describe('isComponentFormDirty', () => { @@ -20,9 +8,9 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: makeComponents({ - [TCompForm.displayName]: { [componentName]: makeCompState({}) }, - }), + components: { + [TCompForm.displayName]: { [componentName]: {} }, + }, }, }; // when @@ -35,9 +23,9 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: makeComponents({ - [TCompForm.displayName]: { [componentName]: makeCompState({ dirty: false }) }, - }), + components: { + [TCompForm.displayName]: { [componentName]: { dirty: false } }, + }, }, }; // when @@ -50,9 +38,9 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: makeComponents({ - [TCompForm.displayName]: { [componentName]: makeCompState({ dirty: true }) }, - }), + components: { + [TCompForm.displayName]: { [componentName]: { dirty: true } }, + }, }, }; // when diff --git a/packages/containers/src/ComponentForm/ComponentForm.test.js b/packages/containers/src/ComponentForm/ComponentForm.test.js index e1e7aeb7a0b..3c18e0da754 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.test.js @@ -5,36 +5,6 @@ import cmf, { mock } from '@talend/react-cmf'; import { resolveNameForTitleMap, TCompForm, toJS } from './ComponentForm.component'; import addSchemaMock from './ComponentForm.test.schema.json'; -/** - * Minimal shim mimicking Immutable.Map for production code that calls - * .get(key, def), .getIn([...keys], def), .set(key, val), .toJS(). - * Uses a per-instance cache so that repeated .get(key) calls return the same - * object reference (avoids infinite loops in componentDidUpdate identity checks). - */ -const makeImmutableMap = obj => { - const wrapCache = Object.create(null); - const wrap = (key, v) => { - if (v == null || typeof v !== 'object') return v; - if (!(key in wrapCache)) { - wrapCache[key] = Array.isArray(v) - ? Object.assign([], v, { toJS: () => v }) - : makeImmutableMap(v); - } - return wrapCache[key]; - }; - return { - get: (key, def) => (key in obj ? wrap(key, obj[key]) : def), - getIn([key, ...rest], def) { - if (!(key in obj)) return def; - const child = wrap(key, obj[key]); - if (rest.length === 0) return child; - return child?.getIn?.(rest, def) ?? def; - }, - set: (key, val) => makeImmutableMap({ ...obj, [key]: val?.toJS?.() ?? val }), - toJS: () => obj, - }; -}; - vi.mock('./kit', () => { const createTriggers = ({ url, customRegistry, security }) => { function trigger() { @@ -76,10 +46,10 @@ describe('ComponentForm', () => { it('should return js object', () => { // given - const immutableObject = { a: 1, b: 2, toJS: () => ({ a: 1, b: 2 }) }; + const anObject = { a: 1, b: 2 }; // when - const result = toJS(immutableObject); + const result = toJS(anObject); // then expect(result).toEqual({ a: 1, b: 2 }); @@ -342,7 +312,7 @@ describe('ComponentForm', () => { describe('#render', () => { it("should render a CircularProgress when we don't have the schema", () => { // given - const state = makeImmutableMap({}); + const state = {}; // when render( @@ -363,7 +333,7 @@ describe('ComponentForm', () => { it('should render a response status', () => { // given - const state = makeImmutableMap({ response: { statusText: 'we had an error' } }); + const state = { response: { statusText: 'we had an error' } }; // when render( @@ -382,7 +352,7 @@ describe('ComponentForm', () => { it('should render a UIForm', () => { // given - const state = makeImmutableMap(addSchemaMock.ui); + const state = { ...addSchemaMock.ui }; // when const { container } = render( @@ -403,7 +373,7 @@ describe('ComponentForm', () => { describe('#security', () => { it('should pass security props to createTrigger', () => { - const state = makeImmutableMap(addSchemaMock.ui); + const state = { ...addSchemaMock.ui }; const instance = new TCompForm({ state, triggerURL: 'http://trigger', @@ -420,7 +390,7 @@ describe('ComponentForm', () => { describe('#update', () => { it('should recreate trigger if triggerURL or customTriggers props change', () => { // given - const state = makeImmutableMap(addSchemaMock.ui); + const state = { ...addSchemaMock.ui }; const oldTriggerURL = 'http://old'; const newTriggerURL = 'http://new'; const oldCustomTriggers = { oldCustomReload: () => {} }; @@ -462,7 +432,7 @@ describe('ComponentForm', () => { it('should dispatch new definitionURL props', () => { // given - const state = makeImmutableMap(addSchemaMock.ui); + const state = { ...addSchemaMock.ui }; const dispatch = jest.fn(); const oldUrl = 'http://old'; const newUrl = 'http://new'; @@ -489,7 +459,7 @@ describe('ComponentForm', () => { }); describe('events', () => { - const state = makeImmutableMap({ ...addSchemaMock.ui, initialState: addSchemaMock.ui }); + const state = { ...addSchemaMock.ui, initialState: addSchemaMock.ui }; // extract type field schema const typeSchema = { @@ -530,7 +500,7 @@ describe('ComponentForm', () => { it('should NOT dispatch dirty state if it is already dirty', () => { // given - const dirtyState = makeImmutableMap({ ...addSchemaMock.ui, dirty: true }); + const dirtyState = { ...addSchemaMock.ui, dirty: true }; const setState = jest.fn(); const instance = new TCompForm({ state: dirtyState, diff --git a/packages/containers/src/ConfirmDialog/ConfirmDialog.connect.js b/packages/containers/src/ConfirmDialog/ConfirmDialog.connect.js index 4bf305a6e8e..54d973731ee 100644 --- a/packages/containers/src/ConfirmDialog/ConfirmDialog.connect.js +++ b/packages/containers/src/ConfirmDialog/ConfirmDialog.connect.js @@ -9,9 +9,9 @@ export function mapStateToProps(state, props, cmfProps) { getState: () => state, }, }; - const validateAction = cmfProps.state ? cmfProps.state.get('validateAction') : undefined; - const cancelAction = cmfProps.state ? cmfProps.state.get('cancelAction') : undefined; - const model = cmfProps.state ? cmfProps.state.get('model') : cmfProps.model; + const validateAction = cmfProps.state?.validateAction; + const cancelAction = cmfProps.state?.cancelAction; + const model = cmfProps.state?.model ?? cmfProps.model; return { validateAction: getActionsProps(context, validateAction, model), cancelAction: getActionsProps(context, cancelAction, model), diff --git a/packages/containers/src/ConfirmDialog/ConfirmDialog.container.jsx b/packages/containers/src/ConfirmDialog/ConfirmDialog.container.jsx index 1752a20072c..8b61a03a67e 100644 --- a/packages/containers/src/ConfirmDialog/ConfirmDialog.container.jsx +++ b/packages/containers/src/ConfirmDialog/ConfirmDialog.container.jsx @@ -1,18 +1,17 @@ -import { Map } from 'immutable'; import omit from 'lodash/omit'; import Component from '@talend/react-components/lib/ConfirmDialog'; import { cmfConnect, useCMFContext } from '@talend/react-cmf'; import { getActionsProps } from '../actionAPI'; -export const DEFAULT_STATE = new Map({ +export const DEFAULT_STATE = { show: false, -}); +}; // eslint-disable-next-line react/prefer-stateless-function function ConfirmDialog(props) { const context = useCMFContext(); - const state = (props.state || DEFAULT_STATE).toJS(); + const state = props.state || DEFAULT_STATE; if (!state.validateAction || !state.cancelAction) { return null; } diff --git a/packages/containers/src/ConfirmDialog/ConfirmDialog.stories.jsx b/packages/containers/src/ConfirmDialog/ConfirmDialog.stories.jsx index 1ee0f3f3eb8..4a2687b1641 100644 --- a/packages/containers/src/ConfirmDialog/ConfirmDialog.stories.jsx +++ b/packages/containers/src/ConfirmDialog/ConfirmDialog.stories.jsx @@ -1,14 +1,13 @@ -import { Map } from 'immutable'; import ConfirmDialog from '.'; -const initialState = new Map({ +const initialState = { size: 'small', header: 'DO SOMETHING', show: true, children: 'Confirm this !', validateAction: 'confirm-dialog:validate', cancelAction: 'confirm-dialog:cancel', -}); +}; export default { title: 'ConfirmDialog', diff --git a/packages/containers/src/ConfirmDialog/ConfirmDialog.test.jsx b/packages/containers/src/ConfirmDialog/ConfirmDialog.test.jsx index 46ac3c92b66..bc2cd1c9c8f 100644 --- a/packages/containers/src/ConfirmDialog/ConfirmDialog.test.jsx +++ b/packages/containers/src/ConfirmDialog/ConfirmDialog.test.jsx @@ -1,6 +1,5 @@ /* eslint-disable react/prop-types */ /* eslint-disable react/display-name */ -import { fromJS, Map } from 'immutable'; import cmf, { mock } from '@talend/react-cmf'; import { render } from '@testing-library/react'; @@ -37,12 +36,12 @@ describe('Container ConfirmDialog', () => { App = config.App; }); it('should not render', () => { - const state = new Map({ + const state = { size: 'small', header: 'DO SOMETHING', show: true, children: 'Confirm this !', - }); + }; const { container } = render( @@ -51,7 +50,7 @@ describe('Container ConfirmDialog', () => { expect(container).toBeEmptyDOMElement(); }); it('should render', () => { - const state = new Map({ + const state = { size: 'small', header: 'DO SOMETHING', show: true, @@ -59,7 +58,7 @@ describe('Container ConfirmDialog', () => { validateAction: 'menu:demo', cancelAction: 'menu:demo', model: { foo: 'bar' }, - }); + }; const { container } = render( @@ -77,14 +76,14 @@ describe('Connected ConfirmDialog', () => { }); it('should set validateAction and cancelAction', () => { - const cmfState = new Map({ + const cmfState = { size: 'small', header: 'DO SOMETHING', show: true, children: 'Confirm this !', validateAction: 'object:validate', cancelAction: 'object:cancel', - }); + }; const state = mock.store.state(); state.cmf.settings.actions['object:validate'] = { name: 'foo' }; state.cmf.settings.actions['object:cancel'] = { name: 'foo1' }; @@ -98,15 +97,15 @@ describe('Connected ConfirmDialog', () => { describe('ConfirmDialog.show/hide', () => { it('should change the visibility to true in the state', () => { const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { ConfirmDialog: { ConfirmDialog: { show: false, }, }, - }); + }; - const dialog = new Map({ + const dialog = { size: 'small', header: 'REMOVE SEMANTIC TYPE', children: 'Are you sure you want to remove the semantic type ?', @@ -114,7 +113,7 @@ describe('ConfirmDialog.show/hide', () => { // these two actions are contained in show:remove:semantic action payload validateAction: '', cancelAction: '', - }); + }; const action = { confirmDialogConf: dialog, @@ -123,31 +122,25 @@ describe('ConfirmDialog.show/hide', () => { const newState = showConfirmDialog(state, action); expect(newState).not.toBe(state); - const confirmDialoVisibility = newState.cmf.components.getIn([ - 'CMFContainer(ConfirmDialog)', - 'ConfirmDialog', - 'show', - ]); + const confirmDialoVisibility = + newState.cmf.components?.['CMFContainer(ConfirmDialog)']?.['ConfirmDialog']?.show; expect(confirmDialoVisibility).toBeTruthy(); }); it('should change the visibility to false in the state', () => { const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { ConfirmDialog: { ConfirmDialog: { show: true, }, }, - }); + }; const newState = hideConfirmDialog(state); expect(newState).not.toBe(state); - const confirmDialogVisibility = newState.cmf.components.getIn([ - 'CMFContainer(ConfirmDialog)', - 'ConfirmDialog', - 'show', - ]); + const confirmDialogVisibility = + newState.cmf.components?.['CMFContainer(ConfirmDialog)']?.['ConfirmDialog']?.show; expect(confirmDialogVisibility).toBeFalsy(); }); }); diff --git a/packages/containers/src/ConfirmDialog/showHideConfirmDialog.js b/packages/containers/src/ConfirmDialog/showHideConfirmDialog.js index 01d92a4b6ff..51935094269 100644 --- a/packages/containers/src/ConfirmDialog/showHideConfirmDialog.js +++ b/packages/containers/src/ConfirmDialog/showHideConfirmDialog.js @@ -1,18 +1,40 @@ export function showConfirmDialog(state, action) { // adding conf and showing modal const path = ['CMFContainer(ConfirmDialog)', 'ConfirmDialog']; - const newState = { ...state }; - newState.cmf.components = state.cmf.components.setIn( - path, - action.confirmDialogConf.set('show', true), - ); - return newState; + const [containerName, dialogKey] = path; + return { + ...state, + cmf: { + ...state.cmf, + components: { + ...state.cmf.components, + [containerName]: { + ...(state.cmf.components[containerName] ?? {}), + [dialogKey]: { ...action.confirmDialogConf, show: true }, + }, + }, + }, + }; } export function hideConfirmDialog(state) { // hiding the modal - const path = ['CMFContainer(ConfirmDialog)', 'ConfirmDialog', 'show']; - const newState = { ...state }; - newState.cmf.components = state.cmf.components.setIn(path, false); - return newState; + const containerName = 'CMFContainer(ConfirmDialog)'; + const dialogKey = 'ConfirmDialog'; + return { + ...state, + cmf: { + ...state.cmf, + components: { + ...state.cmf.components, + [containerName]: { + ...(state.cmf.components[containerName] ?? {}), + [dialogKey]: { + ...(state.cmf.components[containerName]?.[dialogKey] ?? {}), + show: false, + }, + }, + }, + }, + }; } diff --git a/packages/containers/src/DeleteResource/DeleteResource.container.jsx b/packages/containers/src/DeleteResource/DeleteResource.container.jsx index 0738759d131..946f37dd127 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.container.jsx +++ b/packages/containers/src/DeleteResource/DeleteResource.container.jsx @@ -14,7 +14,7 @@ import CONSTANTS from './constants'; */ function getLabel(resource) { if (resource) { - return resource.get('label') || resource.get('name') || ''; + return resource.label || resource.name || ''; } return ''; } diff --git a/packages/containers/src/DeleteResource/DeleteResource.test.js b/packages/containers/src/DeleteResource/DeleteResource.test.js index 066a6702daa..2935e6a3758 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.test.js +++ b/packages/containers/src/DeleteResource/DeleteResource.test.js @@ -4,20 +4,11 @@ import cmf, { mock } from '@talend/react-cmf'; import { DeleteResource } from './DeleteResource.container'; import Connected, { mapStateToProps } from './DeleteResource.connect'; -/** Plain-object shim implementing .get(key, def) for Immutable-Map-like objects. */ -const makeMapItem = data => ({ get: (k, def) => (k in data ? data[k] : def) }); -/** Plain-object shim implementing .find(fn) / .get(index) for Immutable-List-like objects. */ -const makeList = items => ({ find: fn => items.find(fn), get: idx => items[idx] }); -/** Plain-object shim implementing .get(key, def) for the top-level collections Map. */ -const makeCollections = (data = {}) => ({ get: key => data[key] }); - const state = mock.store.state(); const settings = {}; -state.cmf = { - settings, -}; -const fooItem = makeMapItem({ id: '123' }); -state.cmf.collections = makeCollections({ foo: makeList([fooItem]) }); +state.cmf = { settings }; +const fooItem = { id: '123' }; +state.cmf.collections = { foo: [fooItem] }; describe('Container DeleteResource', () => { let App; @@ -32,7 +23,7 @@ describe('Container DeleteResource', () => { const props = { uri: '/myEndpoint', resourceType: 'myResourceType', - resource: makeMapItem({ label: 'myLabel' }), + resource: { label: 'myLabel' }, header: 'My header title', params: { id: 'myResourceID' }, resourceTypeLabel: 'resourceLabel', @@ -84,12 +75,12 @@ describe('Connected DeleteResource', () => { }); it('should return the props.resource corresponding to resourceId', () => { expect(mapStateToProps(state, { resourceType: 'foo', resourceId: '123' }).resource).toBe( - state.cmf.collections.get('foo').get(0), + fooItem, ); }); it('should return the props.resource corresponding to routeParams.id', () => { expect(mapStateToProps(state, { resourceType: 'foo', params: { id: '123' } }).resource).toBe( - state.cmf.collections.get('foo').get(0), + fooItem, ); }); diff --git a/packages/containers/src/DeleteResource/sagas.js b/packages/containers/src/DeleteResource/sagas.js index 87c96e0c760..b2b7ba0989d 100644 --- a/packages/containers/src/DeleteResource/sagas.js +++ b/packages/containers/src/DeleteResource/sagas.js @@ -85,7 +85,7 @@ export function* deleteResourceValidate( model: { ...get(action, 'data.model', {}), id: safeId, - labelResource: resource.get('label') || resource.get('name', ''), + labelResource: resource.label || resource.name || '', }, }); yield call(redirect, get(action, 'data.model.redirectUrl')); diff --git a/packages/containers/src/DeleteResource/sagas.test.js b/packages/containers/src/DeleteResource/sagas.test.js index e31e108c0d4..6eff040cad0 100644 --- a/packages/containers/src/DeleteResource/sagas.test.js +++ b/packages/containers/src/DeleteResource/sagas.test.js @@ -5,8 +5,6 @@ import CONSTANTS from './constants'; // import actions from './actions'; import sagas, * as internals from './sagas'; -const makeMapItem = data => ({ get: (k, def) => (k in data ? data[k] : def) }); - describe('internals', () => { describe('getResourceLocator', () => { it('should return resourceType if no no resourcePath', () => { @@ -55,7 +53,7 @@ describe('internals', () => { }, }, }; - const resource = makeMapItem({ id: '123', label: 'Foo' }); + const resource = { id: '123', label: 'Foo' }; const gen = internals.deleteResourceValidate(); let effect = gen.next().value; @@ -98,11 +96,11 @@ describe('internals', () => { }, }; - const resource = makeMapItem({ + const resource = { id: 'profileId', type: 'advanced', name: 'deleteThisRunProfile', - }); + }; const gen = internals.deleteResourceValidate(); gen.next(); @@ -127,11 +125,11 @@ describe('internals', () => { }, }, }; - const resource = makeMapItem({ + const resource = { id: 'profileId', type: 'advanced', name: 'deleteThisRunProfile', - }); + }; const gen = internals.deleteResourceValidate(); gen.next(); diff --git a/packages/containers/src/EditableText/EditableText.container.jsx b/packages/containers/src/EditableText/EditableText.container.jsx index 7a46fb2a420..c84356bbdd2 100644 --- a/packages/containers/src/EditableText/EditableText.container.jsx +++ b/packages/containers/src/EditableText/EditableText.container.jsx @@ -1,14 +1,13 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import Component from '@talend/react-components/lib/EditableText'; -import Immutable from 'immutable'; import omit from 'lodash/omit'; import { cmfConnect } from '@talend/react-cmf'; export const DISPLAY_NAME = 'Container(EditableText)'; -export const DEFAULT_STATE = new Immutable.Map({ +export const DEFAULT_STATE = { editMode: false, -}); +}; class EditableText extends RComponent { static displayName = DISPLAY_NAME; @@ -91,7 +90,7 @@ class EditableText extends RComponent { onCancel: this.onCancel, onSubmit: this.onSubmit, onChange: this.onChange, - ...state.toJS(), + ...state, }; return ; } diff --git a/packages/containers/src/EditableText/EditableText.selectors.js b/packages/containers/src/EditableText/EditableText.selectors.js index d8337be26ad..4a04f19cd86 100644 --- a/packages/containers/src/EditableText/EditableText.selectors.js +++ b/packages/containers/src/EditableText/EditableText.selectors.js @@ -6,5 +6,5 @@ import EditableText from './EditableText.connect'; * @param {string} idComponent */ export function getEditMode(state, idComponent) { - return EditableText.getState(state, idComponent).get('editMode', false); + return EditableText.getState(state, idComponent)?.editMode ?? false; } diff --git a/packages/containers/src/EditableText/EditableText.test.js b/packages/containers/src/EditableText/EditableText.test.js index 091621cb088..9fb6cc26642 100644 --- a/packages/containers/src/EditableText/EditableText.test.js +++ b/packages/containers/src/EditableText/EditableText.test.js @@ -7,21 +7,6 @@ import Connect from './EditableText.connect'; import Container, { DISPLAY_NAME } from './EditableText.container'; import { getEditMode } from './EditableText.selectors'; -/** Plain-object shim implementing .get(key, def) and .toJS() for EditableText container state prop. */ -const makeState = editMode => ({ - get: (k, def) => (k === 'editMode' ? editMode : def), - toJS: () => ({ editMode }), -}); -/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ -const makeComponents = (data = {}) => ({ - getIn([outer, inner], def) { - const outerVal = data[outer]; - if (outerVal == null) return def; - const innerVal = outerVal[inner]; - return innerVal !== undefined ? innerVal : def; - }, -}); - describe('Connect', () => { it('should connect EditableText', () => { expect(Connect.displayName).toBe(`Connect(CMF(${Container.displayName}))`); @@ -49,7 +34,7 @@ describe('EditableText container', () => { it('should setState when submit event trigger', async () => { let state; const props = { - state: makeState(true), + state: { editMode: true }, setState: jest.fn(fn => { state = fn; }), @@ -70,7 +55,7 @@ describe('EditableText container', () => { const props = { actionCreatorSubmit: 'mySubmitActionCreator', dispatchActionCreator: jest.fn(), - state: makeState(true), + state: { editMode: true }, setState: jest.fn(), text: 'my text', }; @@ -91,7 +76,7 @@ describe('EditableText container', () => { it('should setState when cancel event trigger', async () => { let state; const props = { - state: makeState(true), + state: { editMode: true }, setState: jest.fn(fn => { state = fn; }), @@ -106,7 +91,7 @@ describe('EditableText container', () => { it('should call onCancel when cancel event trigger', async () => { const props = { setState: jest.fn(), - state: makeState(true), + state: { editMode: true }, onCancel: jest.fn(), text: 'my text', }; @@ -117,7 +102,7 @@ describe('EditableText container', () => { it('should call actionCreatorCancel when cancel event trigger', async () => { const props = { setState: jest.fn(), - state: makeState(true), + state: { editMode: true }, actionCreatorCancel: 'myCancelActionCreator', dispatchActionCreator: jest.fn(), text: 'my text', @@ -135,7 +120,7 @@ describe('EditableText container', () => { it('should call setState when edit event trigger', async () => { let state; const props = { - state: makeState(false), + state: { editMode: false }, setState: jest.fn(fn => { state = fn; }), @@ -150,7 +135,7 @@ describe('EditableText container', () => { it('should call onEdit when edit event trigger', async () => { const props = { setState: jest.fn(), - state: makeState(false), + state: { editMode: false }, onEdit: jest.fn(), text: 'my text', }; @@ -161,7 +146,7 @@ describe('EditableText container', () => { it('should call actionCreatorEdit via dispatchActionCreator when edit event trigger', async () => { const props = { setState: jest.fn(), - state: makeState(false), + state: { editMode: false }, dispatchActionCreator: jest.fn(), actionCreatorEdit: 'myEditActionCreator', text: 'my text', @@ -181,7 +166,7 @@ describe('EditableText container', () => { const props = { setState: jest.fn(), - state: makeState(true), + state: { editMode: true }, onChange: jest.fn(), text: 'my text', }; @@ -197,7 +182,7 @@ describe('EditableText container', () => { const props = { setState: jest.fn(), - state: makeState(true), + state: { editMode: true }, dispatchActionCreator: jest.fn(), actionCreatorChange: 'myChangeActionCreator', text: 'my text', @@ -220,11 +205,11 @@ describe('EditableText container', () => { describe('EditableText selectors', () => { let mockState; - const componentState = makeState(true); + const componentState = { editMode: true }; beforeEach(() => { mockState = { cmf: { - components: makeComponents({ [DISPLAY_NAME]: { myEditableText: componentState } }), + components: { [DISPLAY_NAME]: { myEditableText: componentState } }, }, }; }); diff --git a/packages/containers/src/FilterBar/FilterBar.container.jsx b/packages/containers/src/FilterBar/FilterBar.container.jsx index 34f1d6d4e03..03d4087afc0 100644 --- a/packages/containers/src/FilterBar/FilterBar.container.jsx +++ b/packages/containers/src/FilterBar/FilterBar.container.jsx @@ -2,14 +2,13 @@ import { cmfConnect } from '@talend/react-cmf'; import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import omit from 'lodash/omit'; -import Immutable from 'immutable'; import Component from '@talend/react-components/lib/FilterBar'; export const QUERY_ATTR = 'query'; -export const DEFAULT_STATE = new Immutable.Map({ +export const DEFAULT_STATE = { [QUERY_ATTR]: '', docked: true, -}); +}; export const DISPLAY_NAME = 'Container(FilterBar)'; @@ -49,13 +48,9 @@ class FilterBar extends RComponent { } onToggle(event) { - this.props.setState(prevState => { - let state = prevState.state; - if (this.props.dockable) { - state = state.set('docked', !this.props.state.get('docked')); - } - return state; - }); + if (this.props.dockable) { + this.props.setState({ docked: !this.props.state?.docked }); + } if (this.props.onToggle) { this.props.onToggle(event); } @@ -65,8 +60,8 @@ class FilterBar extends RComponent { const state = this.props.state || DEFAULT_STATE; const props = { ...omit(this.props, cmfConnect.INJECTED_PROPS), - docked: this.props.docked != null ? this.props.docked : state.get(DOCKED_ATTR), - value: this.props.value ? this.props.value : state.get(QUERY_ATTR, ''), + docked: this.props.docked != null ? this.props.docked : state?.[DOCKED_ATTR], + value: this.props.value ? this.props.value : (state?.[QUERY_ATTR] ?? ''), onToggle: this.onToggle, onFilter: this.onFilter, }; diff --git a/packages/containers/src/FilterBar/FilterBar.selectors.js b/packages/containers/src/FilterBar/FilterBar.selectors.js index 5e2d915456f..e6b5a1a55f1 100644 --- a/packages/containers/src/FilterBar/FilterBar.selectors.js +++ b/packages/containers/src/FilterBar/FilterBar.selectors.js @@ -6,10 +6,7 @@ import { DEFAULT_STATE, DISPLAY_NAME } from './FilterBar.container'; * @param {string} idComponent */ export function getComponentState(state, idComponent) { - if (state.cmf.components.hasIn([DISPLAY_NAME, idComponent])) { - return state.cmf.components.getIn([DISPLAY_NAME, idComponent]); - } - return DEFAULT_STATE; + return state.cmf.components?.[DISPLAY_NAME]?.[idComponent] ?? DEFAULT_STATE; } /** @@ -18,5 +15,5 @@ export function getComponentState(state, idComponent) { * @param {string} idComponent */ export function getQuery(state, idComponent) { - return getComponentState(state, idComponent).get('query', ''); + return getComponentState(state, idComponent).query ?? ''; } diff --git a/packages/containers/src/FilterBar/FilterBar.test.js b/packages/containers/src/FilterBar/FilterBar.test.js index 08302bbd4d1..98259eb7892 100644 --- a/packages/containers/src/FilterBar/FilterBar.test.js +++ b/packages/containers/src/FilterBar/FilterBar.test.js @@ -4,37 +4,6 @@ import Container, { DEFAULT_STATE, DISPLAY_NAME } from './FilterBar.container'; import Connected from './FilterBar.connect'; import { getComponentState, getQuery } from './FilterBar.selectors'; -/** - * Returns a plain-object shim with the Immutable.Map interface required by FilterBar.container - * and FilterBar.selectors (production code still uses Immutable). - * Only implements the subset of Immutable.Map used in these tests. - */ -const makeState = (obj = {}) => ({ - _data: { ...obj }, - get(key, def) { - const val = this._data[key]; - return val !== undefined ? val : def; - }, - set(key, val) { - return makeState({ ...this._data, [key]: val }); - }, - has(key) { - return key in this._data; - }, - hasIn([key, ...rest]) { - if (!(key in this._data)) return false; - const val = this._data[key]; - if (!rest.length) return true; - return val && typeof val.hasIn === 'function' ? val.hasIn(rest) : rest[0] in (val || {}); - }, - getIn([key, ...rest]) { - const val = this._data[key]; - if (!rest.length) return val; - if (val && typeof val.getIn === 'function') return val.getIn(rest); - return (val || {})[rest[0]]; - }, -}); - describe('Filter connected', () => { it('should connect filter', () => { expect(Connected.displayName).toBe(`Connect(CMF(${Container.displayName}))`); @@ -60,7 +29,7 @@ describe('Filter container', () => { const props = { onFilter: jest.fn(), setState: jest.fn(), - state: makeState({ docked: false }), + state: { docked: false }, }; render(); const query = 'foo'; @@ -71,7 +40,7 @@ describe('Filter container', () => { const props = { onFilter: jest.fn(), setState: jest.fn(), - state: makeState({ docked: false }), + state: { docked: false }, }; const query = 'foo'; render(); @@ -89,7 +58,7 @@ describe('Filter container', () => { const props = { onBlur: jest.fn(), setState: jest.fn(), - state: makeState({ docked: false }), + state: { docked: false }, }; render(); fireEvent.blur(document.querySelector('input')); @@ -99,7 +68,7 @@ describe('Filter container', () => { const props = { onBlur: jest.fn(), setState: jest.fn(), - state: makeState({ docked: false }), + state: { docked: false }, onFocus: jest.fn(), }; render(); @@ -107,13 +76,9 @@ describe('Filter container', () => { expect(props.onFocus).toHaveBeenCalled(); }); it('should call setState when onToggle event trigger', () => { - const state = makeState({ docked: false }); - const prevState = { state }; - const setState = jest.fn(fn => { - prevState.state = fn(prevState); - }); + const state = { docked: false }; const props = { - setState, + setState: jest.fn(), state, dockable: true, onToggle: jest.fn(), @@ -121,21 +86,20 @@ describe('Filter container', () => { render(); fireEvent.blur(document.querySelector('input')); expect(props.setState).toHaveBeenCalled(); - expect(prevState.state).not.toBe(state); - expect(prevState.state.get('docked')).toBe(true); + expect(props.setState).toHaveBeenCalledWith({ docked: true }); expect(props.onToggle).toHaveBeenCalled(); }); }); describe('Filter Selectors', () => { it('should return the filter component state', () => { - const componentState = makeState({ + const componentState = { query: 'Toto was here', docked: true, - }); + }; const state = { cmf: { - components: makeState({ [DISPLAY_NAME]: makeState({ myFilterComponent: componentState }) }), + components: { [DISPLAY_NAME]: { myFilterComponent: componentState } }, }, }; expect(getComponentState(state, 'myFilterComponent')).toEqual(componentState); @@ -143,19 +107,19 @@ describe('Filter Selectors', () => { it('should return the default filter component state', () => { const state = { cmf: { - components: makeState(), + components: {}, }, }; expect(getComponentState(state, 'myFilterComponent')).toEqual(DEFAULT_STATE); }); it('should return the query', () => { - const componentState = makeState({ + const componentState = { query: 'Hello world', docked: true, - }); + }; const state = { cmf: { - components: makeState({ [DISPLAY_NAME]: makeState({ myFilterComponent: componentState }) }), + components: { [DISPLAY_NAME]: { myFilterComponent: componentState } }, }, }; expect(getQuery(state, 'myFilterComponent')).toEqual('Hello world'); diff --git a/packages/containers/src/Form/Form.container.jsx b/packages/containers/src/Form/Form.container.jsx index a3d5747e35f..daa47b92f87 100644 --- a/packages/containers/src/Form/Form.container.jsx +++ b/packages/containers/src/Form/Form.container.jsx @@ -1,6 +1,5 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; -import Immutable from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; import BaseForm from '@talend/react-forms'; import classnames from 'classnames'; @@ -10,7 +9,7 @@ if (process.env.FORM_MOZ) { DefaultArrayFieldTemplate = BaseForm.deprecated.templates.ArrayFieldTemplate; } -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = {}; /** * Because we don't want to loose form input @@ -42,7 +41,7 @@ class Form extends Component { * @return {[type]} [description] */ static getFormData(state, formId) { - return state.cmf.components.getIn(['Container(Form)', formId, 'data'], new Immutable.Map()); + return state.cmf.components?.['Container(Form)']?.[formId]?.data ?? {}; } static getDerivedStateFromProps(nextProps, prevState) { @@ -62,7 +61,7 @@ class Form extends Component { constructor(props) { super(props); - this.state = DEFAULT_STATE.toJS(); + this.state = { ...DEFAULT_STATE }; this.formActions = this.formActions.bind(this); this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); @@ -105,7 +104,7 @@ class Form extends Component { } jsonSchema() { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; if (typeof this.props.jsonSchema === 'function') { return this.props.jsonSchema(state.data); } @@ -113,7 +112,7 @@ class Form extends Component { } uiSchema() { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; if (typeof this.props.uiSchema === 'function') { return this.props.uiSchema(state.data); } @@ -121,7 +120,7 @@ class Form extends Component { } data() { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; if (typeof this.props.data === 'function') { return this.props.data(state.data); } @@ -129,7 +128,7 @@ class Form extends Component { } errors() { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; if (typeof this.props.errors === 'function') { return this.props.errors(state.errors); } @@ -138,14 +137,14 @@ class Form extends Component { formActions() { if (typeof this.props.actions === 'function') { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; return this.props.actions(state.data || this.props.data); } return this.props.actions; } render() { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; const props = { data: { jsonSchema: this.jsonSchema(), diff --git a/packages/containers/src/Form/Form.test.js b/packages/containers/src/Form/Form.test.js index c342bcfc314..ea576ed1a2f 100644 --- a/packages/containers/src/Form/Form.test.js +++ b/packages/containers/src/Form/Form.test.js @@ -3,32 +3,6 @@ import { render } from '@testing-library/react'; import Connected from './Form.connect'; import Container from './Form.container'; -/** - * Minimal Immutable.Map-like shim for plain objects. - * Implements the subset of the Immutable.Map API used by Form.container and these tests. - */ -const makePlainState = (obj = {}) => ({ - _data: obj, - get size() { - return Object.keys(obj).length; - }, - get(key, def) { - const val = obj[key]; - return val !== undefined ? val : def; - }, - set(key, val) { - return makePlainState({ ...obj, [key]: val }); - }, - getIn(keys, def) { - let current = obj; - for (const key of keys) { - if (current == null) return def; - current = current._data ? current._data[key] : current[key]; - } - return current !== undefined ? current : def; - }, -}); - const jsonSchema = { type: 'object', title: 'Comment', @@ -70,7 +44,7 @@ describe('Container(Form)', () => { const setState = jest.fn(); const event = { target: 'test' }; const form = new Container({ - state: makePlainState({ data: { schema: true } }), + state: { data: { schema: true } }, onErrors, setState, }); @@ -84,7 +58,7 @@ describe('Container(Form)', () => { const dispatchActionCreator = jest.fn(); const setState = jest.fn(); const form = new Container({ - state: makePlainState({ data: { schema: true } }), + state: { data: { schema: true } }, setState, onSubmitActionCreator: 'myaction', onSubmit, @@ -95,7 +69,7 @@ describe('Container(Form)', () => { expect(dispatchActionCreator.mock.calls[0][0]).toBe('myaction'); expect(dispatchActionCreator.mock.calls[0][1]).toBe(null); expect(dispatchActionCreator.mock.calls[0][2].formData).toEqual({ foo: 'bar' }); - expect(dispatchActionCreator.mock.calls[0][2].props.state.size).toBe(1); + expect(dispatchActionCreator.mock.calls[0][2].props.state).toEqual({ data: { schema: true } }); expect(setState.mock.calls.length).toBe(0); }); @@ -104,7 +78,7 @@ describe('Container(Form)', () => { const setState = jest.fn(); const event = { target: 'test' }; const form = new Container({ - state: makePlainState({ data: { schema: true } }), + state: { data: { schema: true } }, onChange, setState, }); @@ -118,17 +92,17 @@ describe('Container(Form)', () => { const formId = 'my-form'; const state = { cmf: { - components: makePlainState({ - 'Container(Form)': makePlainState({ - [formId]: makePlainState({ - data: makePlainState({ foo: 'bar' }), - }), - }), - }), + components: { + 'Container(Form)': { + [formId]: { + data: { foo: 'bar' }, + }, + }, + }, }, }; const formData = Container.getFormData(state, formId); - expect(formData.get('foo')).toBe('bar'); + expect(formData.foo).toBe('bar'); }); it('should formActions return props.action', () => { diff --git a/packages/containers/src/GuidedTour/GuidedTour.container.jsx b/packages/containers/src/GuidedTour/GuidedTour.container.jsx index 50dcdd876a1..cb06f4c664c 100644 --- a/packages/containers/src/GuidedTour/GuidedTour.container.jsx +++ b/packages/containers/src/GuidedTour/GuidedTour.container.jsx @@ -59,7 +59,7 @@ class GuidedTourContainer extends Component { const { controls } = this.state; return ( ({ get: key => obj[key] }); - const defaultProps = { - state: makeState({ + state: { show: true, - }), + }, steps: [ { content: { diff --git a/packages/containers/src/HeaderBar/HeaderBar.container.jsx b/packages/containers/src/HeaderBar/HeaderBar.container.jsx index 2b64aad9e9f..b2590be8179 100644 --- a/packages/containers/src/HeaderBar/HeaderBar.container.jsx +++ b/packages/containers/src/HeaderBar/HeaderBar.container.jsx @@ -1,7 +1,6 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import omit from 'lodash/omit'; -import { Map } from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; import Component from '@talend/react-components/lib/HeaderBar'; @@ -9,9 +8,9 @@ import { fetchProducts, openProduct } from './HeaderBar.actions'; import Constants from './HeaderBar.constant'; -export const DEFAULT_STATE = new Map({ +export const DEFAULT_STATE = { productsFetchState: Constants.PRODUCTS_NOT_LOADED, -}); +}; function sortProductsByLabel(a, b) { return a.label > b.label ? 1 : -1; @@ -40,7 +39,7 @@ class HeaderBar extends RComponent { // products URL has changed or products have not been loaded yet const hasProductsUrlChanged = this.props.productsUrl !== prevProps.productsUrl; const hasProductsNotBeenLoaded = - this.props.state.get('productsFetchState') === Constants.PRODUCTS_NOT_LOADED; + this.props.state?.productsFetchState === Constants.PRODUCTS_NOT_LOADED; if (this.props.productsUrl && (hasProductsNotBeenLoaded || hasProductsUrlChanged)) { this.props.dispatch(fetchProducts(this.props.productsUrl)); @@ -56,7 +55,7 @@ class HeaderBar extends RComponent { } = this.props; const hasFetchedProducts = - this.props.state.get('productsFetchState') === Constants.FETCH_PRODUCTS_SUCCESS; + this.props.state?.productsFetchState === Constants.FETCH_PRODUCTS_SUCCESS; const productsProps = {}; diff --git a/packages/containers/src/HeaderBar/HeaderBar.test.js b/packages/containers/src/HeaderBar/HeaderBar.test.js index d10db977385..c90d98fc26a 100644 --- a/packages/containers/src/HeaderBar/HeaderBar.test.js +++ b/packages/containers/src/HeaderBar/HeaderBar.test.js @@ -3,12 +3,6 @@ import { render, screen } from '@testing-library/react'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; -/** Plain-object shim implementing .get(key, def) for the HeaderBar container state prop. */ -const makeHeaderBarState = (data = {}) => ({ get: (k, def) => (k in data ? data[k] : def) }); -/** Plain-object shim for state.cmf.collections — wraps a value in a .toJS()-able object. */ -const makeToJSList = arr => ({ toJS: () => [...arr] }); -/** Plain-object shim implementing .getIn([key], def) for state.cmf.collections. */ -const makeCollections = (data = {}) => ({ getIn: ([key], def) => (key in data ? data[key] : def) }); import Container, { DEFAULT_STATE } from './HeaderBar.container'; import Connected, { mapStateToProps } from './HeaderBar.connect'; import Constants from './HeaderBar.constant'; @@ -34,9 +28,9 @@ describe('Container HeaderBar', () => { url: 'http://foo.bar', }, ], - state: makeHeaderBarState({ + state: { productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS, - }), + }, }; render(await prepareCMF()); @@ -71,9 +65,9 @@ describe('Container HeaderBar', () => { }, ], }, - state: makeHeaderBarState({ + state: { productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS, - }), + }, }; render(await prepareCMF()); expect(screen.getAllByRole('menuitem')).toHaveLength(3); @@ -96,9 +90,9 @@ describe('Container HeaderBar', () => { prepareProducts: jest.fn(products => products.map(product => ({ ...product, label: `${product.label} and bar` })), ), - state: makeHeaderBarState({ + state: { productsFetchState: Constants.FETCH_PRODUCTS_SUCCESS, - }), + }, }; render(); @@ -109,9 +103,9 @@ describe('Container HeaderBar', () => { it('should render HeaderBar container while fetching items', async () => { const props = { ...containerProps, - state: makeHeaderBarState({ + state: { productsFetchState: Constants.FETCHING_PRODUCTS, - }), + }, }; render(await prepareCMF()); @@ -130,7 +124,7 @@ describe('Connected HeaderBar', () => { it('should mapStateToProps with an empty list of products', () => { const state = { cmf: { - collections: makeCollections(), + collections: {}, }, }; const ownProps = {}; @@ -148,7 +142,7 @@ describe('Connected HeaderBar', () => { const apps = [{ url: 'foobar' }]; const state = { cmf: { - collections: makeCollections({ [Constants.COLLECTION_ID]: makeToJSList(apps) }), + collections: { [Constants.COLLECTION_ID]: apps }, }, }; const ownProps = {}; diff --git a/packages/containers/src/HomeListView/HomeListView.stories.jsx b/packages/containers/src/HomeListView/HomeListView.stories.jsx index 472c69b8f7e..63003aff52d 100644 --- a/packages/containers/src/HomeListView/HomeListView.stories.jsx +++ b/packages/containers/src/HomeListView/HomeListView.stories.jsx @@ -1,6 +1,5 @@ import { Drawer } from '@talend/react-components'; import { action } from 'storybook/actions'; -import Immutable from 'immutable'; import HomeListView from '.'; @@ -128,7 +127,7 @@ const toolbar = { }, }; -const items = Immutable.fromJS([ +const items = [ { id: 1, label: 'Title with actions', @@ -156,7 +155,7 @@ const items = Immutable.fromJS([ modified: '2016-09-22', author: 'Jean-Pierre DUPONT with super long name', }, -]); +]; const listProps = { list, diff --git a/packages/containers/src/List/List.connect.js b/packages/containers/src/List/List.connect.js index 7ec15bfb02b..f6a43dab964 100644 --- a/packages/containers/src/List/List.connect.js +++ b/packages/containers/src/List/List.connect.js @@ -47,7 +47,7 @@ export function mapStateToProps(state, ownProps, cmfProps) { props.items = getItems(state, config); - const totalResults = props.items.size; + const totalResults = props.items.length; if (get(ownProps, ['toolbar', 'pagination'])) { props.items = getPagedItems(state, config, props.items); @@ -55,12 +55,18 @@ export function mapStateToProps(state, ownProps, cmfProps) { const cmfState = get(cmfProps, 'state'); if (cmfState) { - props.state = cmfState.setIn(['totalResults'], totalResults); - if (props.state.has('toolbar')) { - props.state = props.state.mergeIn( - ['toolbar', 'pagination'], - configureGetPagination(state, config), - ); + props.state = { ...cmfState, totalResults }; + if (props.state.toolbar !== undefined) { + props.state = { + ...props.state, + toolbar: { + ...props.state.toolbar, + pagination: { + ...props.state.toolbar?.pagination, + ...configureGetPagination(state, config), + }, + }, + }; } } diff --git a/packages/containers/src/List/List.container.jsx b/packages/containers/src/List/List.container.jsx index f0d715322ae..cf2125b3297 100644 --- a/packages/containers/src/List/List.container.jsx +++ b/packages/containers/src/List/List.container.jsx @@ -1,6 +1,4 @@ import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { Map, List as ImmutableList } from 'immutable'; import Component from '@talend/react-components/lib/List'; import VirtualizedList from '@talend/react-components/lib/VirtualizedList'; import get from 'lodash/get'; @@ -44,9 +42,9 @@ export const connectedCellDictionary = { }, }; -export const DEFAULT_STATE = new Map({ +export const DEFAULT_STATE = { displayMode: 'table', - selectedItems: new ImmutableList(), + selectedItems: [], searchQuery: '', itemsPerPage: 10, startIndex: 1, @@ -54,7 +52,7 @@ export const DEFAULT_STATE = new Map({ sortOn: 'name', sortAsc: true, filterDocked: true, -}); +}; /** * merge props.items with actions @@ -63,7 +61,7 @@ export const DEFAULT_STATE = new Map({ * @return {Array} [description] */ export function getItems(context, props) { - return props.items.toJS().map(item => { + return props.items.map(item => { const actionsItems = get(props, 'actions.items', []); let actions = []; if ( @@ -89,7 +87,7 @@ export function getItems(context, props) { function List(props) { const context = useCMFContext(); - const state = props.state.toJS(); + const state = props.state; function onChangePage(startIndex, itemsPerPage) { props.setState({ startIndex, itemsPerPage }); @@ -100,7 +98,7 @@ function List(props) { } function getSelectedItems() { - return props.state.get('selectedItems', new ImmutableList()); + return props.state?.selectedItems ?? []; } function onToggleMultiSelection(event, data) { @@ -108,11 +106,11 @@ function List(props) { const dataIndex = selectedItems.indexOf(data[props.idKey]); if (dataIndex > -1) { props.setState({ - selectedItems: selectedItems.splice(dataIndex, 1), + selectedItems: selectedItems.filter((_, i) => i !== dataIndex), }); } else { props.setState({ - selectedItems: selectedItems.push(data[props.idKey]), + selectedItems: [...selectedItems, data[props.idKey]], }); } } @@ -120,13 +118,13 @@ function List(props) { function onToggleAllMultiSelection() { const selectedItems = getSelectedItems(); const items = props.items; - if (selectedItems.size !== items.size) { + if (selectedItems.length !== items.length) { props.setState({ - selectedItems: items.map(item => item.get(props.idKey)), + selectedItems: items.map(item => item[props.idKey]), }); } else { props.setState({ - selectedItems: new ImmutableList([]), + selectedItems: [], }); } } @@ -268,7 +266,7 @@ function List(props) { newProps.list.itemProps.onToggle = onToggleMultiSelection; newProps.list.itemProps.onToggleAll = onToggleAllMultiSelection; newProps.list.itemProps.isSelected = isSelected; - newProps.toolbar.actionBar.selected = getSelectedItems().size; + newProps.toolbar.actionBar.selected = getSelectedItems().length; } const actions = newProps.actions; @@ -353,7 +351,7 @@ List.propTypes = { }), cellDictionary: PropTypes.object, displayMode: PropTypes.string, - items: ImmutablePropTypes.list.isRequired, + items: PropTypes.array.isRequired, state: cmfConnect.propTypes.state, ...cmfConnect.propTypes, }; diff --git a/packages/containers/src/List/List.stories.jsx b/packages/containers/src/List/List.stories.jsx index bd3f759b262..55819b708c1 100644 --- a/packages/containers/src/List/List.stories.jsx +++ b/packages/containers/src/List/List.stories.jsx @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; import api from '@talend/react-cmf'; -import Immutable from 'immutable'; import cloneDeep from 'lodash/cloneDeep'; import List from '.'; @@ -115,16 +114,16 @@ const customHeight = { table: 100, }; -const defaultListState = new Immutable.Map({ +const defaultListState = { displayMode: 'large', -}); +}; -const defaultSortedListState = new Immutable.Map({ +const defaultSortedListState = { sortOn: 'modified', sortAsc: false, -}); +}; -const items = Immutable.fromJS([ +const items = [ { id: 'id1', label: 'Title with actions', @@ -179,7 +178,7 @@ const items = Immutable.fromJS([ modified: '2016-09-22', author: 'Jean-Pierre DUPONT', }, -]); +]; const referenceDatetime = Date.now(); const minusThreeHours = referenceDatetime - 3600 * 3 * 1000; @@ -189,7 +188,7 @@ const minusThreeMin = referenceDatetime - 60 * 3 * 1000; const oneDay = 24 * 3600 * 1000; -const itemsWithTimestamp = Immutable.fromJS([ +const itemsWithTimestamp = [ { id: 'id0', label: 'Title with actions but first', @@ -227,7 +226,7 @@ const itemsWithTimestamp = Immutable.fromJS([ modified: minusOneHours, author: 'Jean-Pierre DUPONT with super long name', }, -]); +]; const sortUpdatedAsc = { field: 'modified', @@ -258,73 +257,72 @@ export const WithSeparatorActions = () => ( ); export const Pagination = () => { const propsPg = cloneDeep(props); - const itemsPg = items.concat( - Immutable.fromJS([ - { - id: 'id4', - label: 'Title with actions', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT', - }, - { - id: 'ID5', - label: 'Title in input mode', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT', - }, - { - id: 'iD6', - label: 'Super long title to trigger overflow on some rendering', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT with super long name', - }, - { - id: 'id7', - label: 'Title with actions', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT', - }, - { - id: 'ID8', - label: 'Title in input mode', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT', - }, - { - id: 'iD9', - label: 'Super long title to trigger overflow on some rendering', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT with super long name', - }, - { - id: 'id10', - label: 'Title with actions', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT', - }, - { - id: 'ID11', - label: 'Title in input mode', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT', - }, - { - id: 'iD12', - label: 'Super long title to trigger overflow on some rendering', - created: '2016-09-22', - modified: '2016-09-22', - author: 'Jean-Pierre DUPONT with super long name', - }, - ]), - ); + const itemsPg = [ + ...items, + { + id: 'id4', + label: 'Title with actions', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT', + }, + { + id: 'ID5', + label: 'Title in input mode', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT', + }, + { + id: 'iD6', + label: 'Super long title to trigger overflow on some rendering', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT with super long name', + }, + { + id: 'id7', + label: 'Title with actions', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT', + }, + { + id: 'ID8', + label: 'Title in input mode', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT', + }, + { + id: 'iD9', + label: 'Super long title to trigger overflow on some rendering', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT with super long name', + }, + { + id: 'id10', + label: 'Title with actions', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT', + }, + { + id: 'ID11', + label: 'Title in input mode', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT', + }, + { + id: 'iD12', + label: 'Super long title to trigger overflow on some rendering', + created: '2016-09-22', + modified: '2016-09-22', + author: 'Jean-Pierre DUPONT with super long name', + }, + ]; propsPg.toolbar.pagination = {}; return (
diff --git a/packages/containers/src/List/List.test.jsx b/packages/containers/src/List/List.test.jsx index aa29224a643..f324f61549d 100644 --- a/packages/containers/src/List/List.test.jsx +++ b/packages/containers/src/List/List.test.jsx @@ -2,7 +2,6 @@ /* eslint-disable react/display-name */ import { fireEvent, render, screen } from '@testing-library/react'; -import { fromJS, List as ImmutableList, Map } from 'immutable'; import cloneDeep from 'lodash/cloneDeep'; // eslint-disable-next-line @talend/import-depth @@ -70,7 +69,7 @@ const settings = { actions, }; -const items = fromJS([ +const items = [ { id: 1, name: 'Title with actions', @@ -98,7 +97,7 @@ const items = fromJS([ modified: '2016-09-22', author: 'Jean-Pierre DUPONT with super long name', }, -]); +]; vi.mock('@talend/react-components/lib/List', () => ({ default: ({ getProps, ...props }) => ( @@ -144,7 +143,7 @@ describe('Container List', () => { , { cmfModule }, @@ -172,7 +171,7 @@ describe('Container List', () => { , { cmfModule }, @@ -492,7 +491,7 @@ describe('Container List', () => { left: ['object:remove'], }; multiSelectionSetting.setState = jest.fn(); - const state = fromJS({ selectedItems: [] }); + const state = { selectedItems: [] }; multiSelectionSetting.state = state; render( await prepareCMF( @@ -505,8 +504,8 @@ describe('Container List', () => { const props = getProps.mock.calls[0][0]; props.list.itemProps.onToggle({}, { id: 1 }); // then - expect(multiSelectionSetting.setState.mock.calls[0][0]).toMatchObject({ - selectedItems: expect.any(ImmutableList), + expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ + selectedItems: [1], }); }); @@ -519,7 +518,7 @@ describe('Container List', () => { left: ['object:remove'], }; multiSelectionSetting.setState = jest.fn(); - const state = fromJS({ selectedItems: [1] }); + const state = { selectedItems: [1] }; multiSelectionSetting.state = state; render( await prepareCMF( @@ -533,7 +532,7 @@ describe('Container List', () => { props.list.itemProps.onToggle({}, { id: 1 }); // then expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ - selectedItems: new ImmutableList([]), + selectedItems: [], }); }); it('should select all items', async () => { @@ -546,7 +545,7 @@ describe('Container List', () => { left: ['object:remove'], }; multiSelectionSetting.setState = jest.fn(); - const state = fromJS({ selectedItems: [] }); + const state = { selectedItems: [] }; multiSelectionSetting.state = state; render( await prepareCMF( @@ -559,9 +558,8 @@ describe('Container List', () => { const props = getProps.mock.calls[0][0]; props.list.itemProps.onToggleAll(); // then - expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ - selectedItems: new ImmutableList([1, 2, 3]), + selectedItems: [1, 2, 3], }); }); @@ -574,7 +572,7 @@ describe('Container List', () => { left: ['object:remove'], }; multiSelectionSetting.setState = jest.fn(); - const state = fromJS({ selectedItems: [1, 2, 3] }); + const state = { selectedItems: [1, 2, 3] }; multiSelectionSetting.state = state; render( await prepareCMF( @@ -588,7 +586,7 @@ describe('Container List', () => { props.list.itemProps.onToggleAll(); // then expect(multiSelectionSetting.setState.mock.calls[0][0]).toEqual({ - selectedItems: new ImmutableList([]), + selectedItems: [], }); }); @@ -601,7 +599,7 @@ describe('Container List', () => { left: ['object:remove'], }; multiSelectionSetting.setState = jest.fn(); - const state = fromJS({ selectedItems: [1, 2, 3] }); + const state = { selectedItems: [1, 2, 3] }; multiSelectionSetting.state = state; // when @@ -629,14 +627,14 @@ describe('Connected List', () => { // given const state = { cmf: { - components: fromJS({ + components: { 'Container(List)': { - cid: DEFAULT_STATE.toJS(), + cid: DEFAULT_STATE, }, - }), - collections: fromJS({ + }, + collections: { cid: items, - }), + }, }, }; @@ -651,18 +649,18 @@ describe('Connected List', () => { // given const state = { cmf: { - components: fromJS({ + components: { 'Container(List)': { - default: DEFAULT_STATE.toJS(), + default: DEFAULT_STATE, }, - }), - collections: new Map(), + }, + collections: {}, }, }; // when : no collectionId defined const props = mapStateToProps(state, { - items: fromJS(items), + items: items, }); // then @@ -673,10 +671,10 @@ describe('Connected List', () => { // given const state = { cmf: { - components: fromJS({ + components: { 'Container(List)': { cid: { - ...DEFAULT_STATE.toJS(), + ...DEFAULT_STATE, toolbar: { pagination: { onChange: 'pagination:change', @@ -684,8 +682,8 @@ describe('Connected List', () => { }, }, }, - }), - collections: fromJS({ + }, + collections: { cid: { pagination: { totalResults: 36, @@ -694,7 +692,7 @@ describe('Connected List', () => { }, items, }, - }), + }, }, }; @@ -709,7 +707,7 @@ describe('Connected List', () => { // given const state = { cmf: { - components: fromJS({ + components: { 'Container(List)': { default: { displayMode: 'large', @@ -721,25 +719,25 @@ describe('Connected List', () => { filterDocked: true, }, }, - }), - collections: new Map(), + }, + collections: {}, }, }; const initalSettings = cloneDeep(settings); - initalSettings.items = fromJS(items); + initalSettings.items = items; initalSettings.toolbar.filter.defaultFiltering = false; // when : no collectionId defined const props = mapStateToProps(state, initalSettings); // then - expect(props.items.size).toBe(items.size); + expect(props.items.length).toBe(items.length); }); it('should disable sorting when defaultSorting is set to false', () => { // given const state = { cmf: { - components: fromJS({ + components: { 'Container(List)': { default: { displayMode: 'large', @@ -751,25 +749,25 @@ describe('Connected List', () => { filterDocked: true, }, }, - }), - collections: new Map(), + }, + collections: {}, }, }; const initalSettings = cloneDeep(settings); - initalSettings.items = fromJS(items); + initalSettings.items = items; initalSettings.toolbar.sort.defaultSorting = false; // when : no collectionId defined const props = mapStateToProps(state, initalSettings); // then - expect(props.items.toJS()[0].id).toBe(1); + expect(props.items[0].id).toBe(1); }); it('should disable paging when defaultPaging is set to false', () => { // given const state = { cmf: { - components: fromJS({ + components: { 'Container(List)': { default: { displayMode: 'large', @@ -781,17 +779,17 @@ describe('Connected List', () => { filterDocked: true, }, }, - }), - collections: new Map(), + }, + collections: {}, }, }; const initalSettings = cloneDeep(settings); - initalSettings.items = fromJS(items); + initalSettings.items = items; initalSettings.toolbar.pagination.defaultPaging = false; // when : no collectionId defined const props = mapStateToProps(state, initalSettings); // then - expect(props.items.size).toBe(items.size); + expect(props.items.length).toBe(items.length); }); }); diff --git a/packages/containers/src/List/__snapshots__/List.test.jsx.snap b/packages/containers/src/List/__snapshots__/List.test.jsx.snap index 05d987def17..bb561f1e429 100644 --- a/packages/containers/src/List/__snapshots__/List.test.jsx.snap +++ b/packages/containers/src/List/__snapshots__/List.test.jsx.snap @@ -2,33 +2,33 @@ exports[`Connected List > should map items to props from collection List 1`] = ` { - "items": Immutable.List [ - Immutable.Map { - "id": 3, - "name": "Super long title to trigger overflow on some rendering", - "created": "2016-09-22", - "modified": "2016-09-22", + "items": [ + { "author": "Jean-Pierre DUPONT with super long name", - }, - Immutable.Map { - "id": 2, - "name": "Title in input mode", "created": "2016-09-22", + "id": 3, "modified": "2016-09-22", + "name": "Super long title to trigger overflow on some rendering", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-pdf-o", - "display": "input", "className": "item-1-class", - }, - Immutable.Map { - "id": 1, - "name": "Title with actions", "created": "2016-09-22", + "display": "input", + "icon": "fa fa-file-pdf-o", + "id": 2, "modified": "2016-09-22", + "name": "Title in input mode", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-excel-o", - "display": "text", "className": "item-0-class", + "created": "2016-09-22", + "display": "text", + "icon": "fa fa-file-excel-o", + "id": 1, + "modified": "2016-09-22", + "name": "Title with actions", }, ], } @@ -36,33 +36,33 @@ exports[`Connected List > should map items to props from collection List 1`] = ` exports[`Connected List > should map items to props from collection Map 1`] = ` { - "items": Immutable.List [ - Immutable.Map { - "id": 3, - "name": "Super long title to trigger overflow on some rendering", - "created": "2016-09-22", - "modified": "2016-09-22", + "items": [ + { "author": "Jean-Pierre DUPONT with super long name", - }, - Immutable.Map { - "id": 2, - "name": "Title in input mode", "created": "2016-09-22", + "id": 3, "modified": "2016-09-22", + "name": "Super long title to trigger overflow on some rendering", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-pdf-o", - "display": "input", "className": "item-1-class", - }, - Immutable.Map { - "id": 1, - "name": "Title with actions", "created": "2016-09-22", + "display": "input", + "icon": "fa fa-file-pdf-o", + "id": 2, "modified": "2016-09-22", + "name": "Title in input mode", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-excel-o", - "display": "text", "className": "item-0-class", + "created": "2016-09-22", + "display": "text", + "icon": "fa fa-file-excel-o", + "id": 1, + "modified": "2016-09-22", + "name": "Title with actions", }, ], } @@ -70,33 +70,33 @@ exports[`Connected List > should map items to props from collection Map 1`] = ` exports[`Connected List > should map items to props from default collection List 1`] = ` { - "items": Immutable.List [ - Immutable.Map { - "id": 3, - "name": "Super long title to trigger overflow on some rendering", - "created": "2016-09-22", - "modified": "2016-09-22", + "items": [ + { "author": "Jean-Pierre DUPONT with super long name", - }, - Immutable.Map { - "id": 2, - "name": "Title in input mode", "created": "2016-09-22", + "id": 3, "modified": "2016-09-22", + "name": "Super long title to trigger overflow on some rendering", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-pdf-o", - "display": "input", "className": "item-1-class", - }, - Immutable.Map { - "id": 1, - "name": "Title with actions", "created": "2016-09-22", + "display": "input", + "icon": "fa fa-file-pdf-o", + "id": 2, "modified": "2016-09-22", + "name": "Title in input mode", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-excel-o", - "display": "text", "className": "item-0-class", + "created": "2016-09-22", + "display": "text", + "icon": "fa fa-file-excel-o", + "id": 1, + "modified": "2016-09-22", + "name": "Title with actions", }, ], } @@ -110,33 +110,33 @@ exports[`Container List > should put default props 1`] = ` "title": "object:open", }, "displayMode": "table", - "items": Immutable.List [ - Immutable.Map { - "id": 1, - "name": "Title with actions", - "created": "2016-09-22", - "modified": "2016-09-22", + "items": [ + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-excel-o", - "display": "text", "className": "item-0-class", - }, - Immutable.Map { - "id": 2, - "name": "Title in input mode", "created": "2016-09-22", + "display": "text", + "icon": "fa fa-file-excel-o", + "id": 1, "modified": "2016-09-22", + "name": "Title with actions", + }, + { "author": "Jean-Pierre DUPONT", - "icon": "fa fa-file-pdf-o", - "display": "input", "className": "item-1-class", - }, - Immutable.Map { - "id": 3, - "name": "Super long title to trigger overflow on some rendering", "created": "2016-09-22", + "display": "input", + "icon": "fa fa-file-pdf-o", + "id": 2, "modified": "2016-09-22", + "name": "Title in input mode", + }, + { "author": "Jean-Pierre DUPONT with super long name", + "created": "2016-09-22", + "id": 3, + "modified": "2016-09-22", + "name": "Super long title to trigger overflow on some rendering", }, ], "list": { diff --git a/packages/containers/src/List/selector.js b/packages/containers/src/List/selector.js index 0b7189e4f55..56996fcd179 100644 --- a/packages/containers/src/List/selector.js +++ b/packages/containers/src/List/selector.js @@ -4,19 +4,10 @@ import isEmpty from 'lodash/isEmpty'; import { createSelector } from 'reselect'; function contains(listItem, query, columns) { - let item = listItem; - if ( - listItem != null && - !Array.isArray(listItem) && - typeof listItem === 'object' && - typeof listItem.toJS === 'function' - ) { - item = listItem.toJS(); - } return columns.some( column => - typeof item[column.key] === 'string' && - item[column.key].toLowerCase().indexOf(query.toLowerCase()) !== -1, + typeof listItem[column.key] === 'string' && + listItem[column.key].toLowerCase().indexOf(query.toLowerCase()) !== -1, ); } @@ -29,8 +20,7 @@ export function getCollectionItems(state, collectionId) { const collection = getCollection(state, collectionId); if (collection != null && !Array.isArray(collection) && typeof collection === 'object') { - const items = typeof collection.get === 'function' ? collection.get('items') : collection.items; - return items !== undefined ? items : collection; + return collection.items !== undefined ? collection.items : collection; } return collection; } @@ -69,7 +59,10 @@ export function configureGetFilteredItems(configure) { }, ); - return createSelector([getFilteredList, getComponentState], items => items); + return createSelector( + [getFilteredList, getComponentState(localConfig.collectionId)], + items => items, + ); } export function compare(sortBy) { @@ -113,11 +106,11 @@ export function getSortedResults(componentState, config, listItems) { if (get(sortedColumn, 'sortFunction')) { // Immutable sort method returns sorted array - results = results.sort( + results = [...results].sort( cmf.registry.getFromRegistry(sortedColumn.sortFunction)(sortBy, sortAsc), ); } else { - results = results.sort(compare(sortBy)); + results = [...results].sort(compare(sortBy)); } if (!sortAsc) { @@ -132,7 +125,7 @@ export function configureGetSortedItems(config, listItems) { getSortedResults(componentState, config, listItems), ); - return createSelector([getSortedList, getComponentState], items => items); + return createSelector([getSortedList, getComponentState(config.collectionId)], items => items); } export function configureGetPagedItems(configure, listItems) { @@ -145,12 +138,12 @@ export function configureGetPagedItems(configure, listItems) { if (itemsPerPage > 0 && startIndex > 0) { results = results.slice( startIndex - 1, - Math.min(startIndex + itemsPerPage - 1, results.size), + Math.min(startIndex + itemsPerPage - 1, results.size ?? results.length), ); } } return results; }); - return createSelector([getPagedList, getComponentState], items => items); + return createSelector([getPagedList, getComponentState(configure.collectionId)], items => items); } diff --git a/packages/containers/src/List/selector.test.js b/packages/containers/src/List/selector.test.js index 23d7a6e21b8..8ba0eb4dfdb 100644 --- a/packages/containers/src/List/selector.test.js +++ b/packages/containers/src/List/selector.test.js @@ -1,5 +1,4 @@ import cmf, { mock } from '@talend/react-cmf'; -import { fromJS, List } from 'immutable'; import { mapStateToProps } from './List.connect'; import { compare, getSortedResults } from './selector'; @@ -26,7 +25,7 @@ const localConfig = { }; const state = mock.store.state(); -state.cmf.collections = fromJS({ +state.cmf.collections = { default: { columns: [ { key: 'id', name: 'ID' }, @@ -34,11 +33,11 @@ state.cmf.collections = fromJS({ ], items: localConfig.items, }, -}); +}; describe('List Selector tests', () => { it('should not filter the list when there is no search query', () => { - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(List)': { default: { displayMode: 'large', @@ -50,14 +49,14 @@ describe('List Selector tests', () => { filterDocked: true, }, }, - }); + }; const props = mapStateToProps(state, localConfig); - expect(props.items.size).toBe(localConfig.items.length); + expect(props.items.length).toBe(localConfig.items.length); }); it('should filter the list when filter on visible column', () => { - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(List)': { default: { displayMode: 'large', @@ -69,14 +68,14 @@ describe('List Selector tests', () => { filterDocked: true, }, }, - }); + }; const props = mapStateToProps(state, localConfig); - expect(props.items.size).toBe(1); + expect(props.items.length).toBe(1); }); it('should return no elements when search on non visible column', () => { - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(List)': { default: { displayMode: 'large', @@ -88,77 +87,68 @@ describe('List Selector tests', () => { filterDocked: true, }, }, - }); + }; const props = mapStateToProps(state, localConfig); - expect(props.items.size).toBe(0); + expect(props.items.length).toBe(0); }); it('should return items in a page when pagination applied', () => { - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(List)': { default: { itemsPerPage: 1, startIndex: 1, }, }, - }); + }; const props = mapStateToProps(state, { ...localConfig, toolbar: { pagination: {} } }); - expect(props.items.size).toBe(1); + expect(props.items.length).toBe(1); }); it('should sort a different column type correctly', () => { expect( - fromJS([{ stringID: '1' }, { stringID: '11' }, { stringID: '12' }, { stringID: '2' }]).sort( + [{ stringID: '1' }, { stringID: '11' }, { stringID: '12' }, { stringID: '2' }].sort( compare('stringID'), ), - ).toEqual( - fromJS([{ stringID: '1' }, { stringID: '11' }, { stringID: '12' }, { stringID: '2' }]), - ); + ).toEqual([{ stringID: '1' }, { stringID: '11' }, { stringID: '12' }, { stringID: '2' }]); expect( - fromJS([ + [ { stringName: 'Uzbekistan' }, { stringName: 'American Samoa' }, { stringName: 'Djibouti' }, { stringName: 'Luxembourg' }, - ]).sort(compare('stringName')), - ).toEqual( - fromJS([ - { stringName: 'American Samoa' }, - { stringName: 'Djibouti' }, - { stringName: 'Luxembourg' }, - { stringName: 'Uzbekistan' }, - ]), - ); + ].sort(compare('stringName')), + ).toEqual([ + { stringName: 'American Samoa' }, + { stringName: 'Djibouti' }, + { stringName: 'Luxembourg' }, + { stringName: 'Uzbekistan' }, + ]); expect( - fromJS([{ intID: 1 }, { intID: 11 }, { intID: 12 }, { intID: 2 }]).sort(compare('intID')), - ).toEqual(fromJS([{ intID: 1 }, { intID: 2 }, { intID: 11 }, { intID: 12 }])); + [{ intID: 1 }, { intID: 11 }, { intID: 12 }, { intID: 2 }].sort(compare('intID')), + ).toEqual([{ intID: 1 }, { intID: 2 }, { intID: 11 }, { intID: 12 }]); expect( - fromJS([{ mixedID: '1' }, { mixedID: '11' }, { mixedID: '-' }, { mixedID: '2' }]).sort( + [{ mixedID: '1' }, { mixedID: '11' }, { mixedID: '-' }, { mixedID: '2' }].sort( compare('mixedID'), ), - ).toEqual(fromJS([{ mixedID: '-' }, { mixedID: '1' }, { mixedID: '11' }, { mixedID: '2' }])); + ).toEqual([{ mixedID: '-' }, { mixedID: '1' }, { mixedID: '11' }, { mixedID: '2' }]); expect( - fromJS([ - { mixedString: 'a' }, - { mixedString: 'b' }, - { mixedString: 'C' }, - { mixedString: 'D' }, - ]).sort(compare('mixedString')), - ).toEqual( - fromJS([ - { mixedString: 'a' }, - { mixedString: 'b' }, - { mixedString: 'C' }, - { mixedString: 'D' }, - ]), - ); + [{ mixedString: 'a' }, { mixedString: 'b' }, { mixedString: 'C' }, { mixedString: 'D' }].sort( + compare('mixedString'), + ), + ).toEqual([ + { mixedString: 'a' }, + { mixedString: 'b' }, + { mixedString: 'C' }, + { mixedString: 'D' }, + ]); }); it('should test the getSortedResults method', () => { cmf.registry.addToRegistry('myCustomSortFn', (sortBy, sortAsc) => (a, b) => { if (sortAsc) { - return a.get(sortBy) > b.get(sortBy) ? -1 : 1; + return a[sortBy] > b[sortBy] ? -1 : 1; } return 0; }); @@ -178,38 +168,37 @@ describe('List Selector tests', () => { // Sorting the list expect( - getSortedResults( - componentState, - config, - fromJS([{ data: 0 }, { data: 4 }, { data: 2 }, { data: 11 }, { data: 1 }, { data: 23 }]), - ), - ).toEqual( - fromJS([{ data: 0 }, { data: 1 }, { data: 2 }, { data: 4 }, { data: 11 }, { data: 23 }]), - ); + getSortedResults(componentState, config, [ + { data: 0 }, + { data: 4 }, + { data: 2 }, + { data: 11 }, + { data: 1 }, + { data: 23 }, + ]), + ).toEqual([{ data: 0 }, { data: 1 }, { data: 2 }, { data: 4 }, { data: 11 }, { data: 23 }]); // Sorting by column and custom sort function expect( getSortedResults( { sortOn: 'a', sortAsc: true }, { columns: [{ key: 'a', sortFunction: 'myCustomSortFn' }] }, - fromJS([{ a: 1 }, { a: 3 }, { a: 2 }]), + [{ a: 1 }, { a: 3 }, { a: 2 }], ), - ).toEqual(fromJS([{ a: 3 }, { a: 2 }, { a: 1 }])); + ).toEqual([{ a: 3 }, { a: 2 }, { a: 1 }]); // Desc sort expect( - getSortedResults( - { sortOn: 'key', sortAsc: false }, - config, - fromJS([{ key: 1 }, { key: 3 }, { key: 2 }]), - ), - ).toEqual(fromJS([{ key: 3 }, { key: 2 }, { key: 1 }])); + getSortedResults({ sortOn: 'key', sortAsc: false }, config, [ + { key: 1 }, + { key: 3 }, + { key: 2 }, + ]), + ).toEqual([{ key: 3 }, { key: 2 }, { key: 1 }]); // Edge cases [null, undefined, 1, true, false, [], {}].forEach(val => - expect(getSortedResults(val, val, fromJS([{ item: 'one' }]))).toEqual( - fromJS([{ item: 'one' }]), - ), + expect(getSortedResults(val, val, [{ item: 'one' }])).toEqual([{ item: 'one' }]), ); // With no items diff --git a/packages/containers/src/Notification/Notification.connect.js b/packages/containers/src/Notification/Notification.connect.js index 76e3105003b..a057cc47ad8 100644 --- a/packages/containers/src/Notification/Notification.connect.js +++ b/packages/containers/src/Notification/Notification.connect.js @@ -7,11 +7,11 @@ export function componentId(ownProps) { export function deleteNotification(indexNotification) { return function mutator(prevStateProps) { - const notifications = prevStateProps.state.get('notifications'); + const notifications = prevStateProps.state.notifications; const index = notifications.indexOf(indexNotification); if (index > -1) { - const newNotif = notifications.delete(index); - return prevStateProps.state.set('notifications', newNotif); + const newNotif = [...notifications.slice(0, index), ...notifications.slice(index + 1)]; + return { ...prevStateProps.state, notifications: newNotif }; } return prevStateProps.state; }; diff --git a/packages/containers/src/Notification/Notification.container.jsx b/packages/containers/src/Notification/Notification.container.jsx index cc5cdf5ad2e..42736f8472b 100644 --- a/packages/containers/src/Notification/Notification.container.jsx +++ b/packages/containers/src/Notification/Notification.container.jsx @@ -1,14 +1,13 @@ import PropTypes from 'prop-types'; -import { List, Map } from 'immutable'; import Component from '@talend/react-components/lib/Notification'; import { cmfConnect } from '@talend/react-cmf'; -export const DEFAULT_STATE = new Map({ - notifications: new List(), -}); +export const DEFAULT_STATE = { + notifications: [], +}; function Notification(props) { - const state = (props.state || DEFAULT_STATE).toJS(); + const state = props.state || DEFAULT_STATE; return ( props.deleteNotification(i)} diff --git a/packages/containers/src/Notification/Notification.sagas.js b/packages/containers/src/Notification/Notification.sagas.js index ee0b8e3ba1b..3621c0ad594 100644 --- a/packages/containers/src/Notification/Notification.sagas.js +++ b/packages/containers/src/Notification/Notification.sagas.js @@ -12,12 +12,16 @@ const DEFAULT_COMPONENT_ID = 'Notification'; export function* onPushNotification(action) { const componentState = yield select(state => Notification.getState(state, DEFAULT_COMPONENT_ID)); - const newComponentState = componentState.updateIn(['notifications'], notifications => - notifications.push({ - id: randomUUID(), - ...action.notification, - }), - ); + const newComponentState = { + ...componentState, + notifications: [ + ...componentState.notifications, + { + id: randomUUID(), + ...action.notification, + }, + ], + }; const updateStateAction = Notification.setStateAction(newComponentState, DEFAULT_COMPONENT_ID); yield put(updateStateAction); diff --git a/packages/containers/src/Notification/Notification.sagas.test.js b/packages/containers/src/Notification/Notification.sagas.test.js index e26a0a4db47..745303fec8d 100644 --- a/packages/containers/src/Notification/Notification.sagas.test.js +++ b/packages/containers/src/Notification/Notification.sagas.test.js @@ -1,4 +1,3 @@ -import Immutable from 'immutable'; import { runSaga } from 'redux-saga'; import { onPushNotification } from './Notification.sagas'; @@ -18,13 +17,13 @@ describe('Notification sagas', () => { dispatch: a => dispatched.push(a), getState: () => ({ cmf: { - components: Immutable.fromJS({ + components: { 'Container(Notification)': { Notification: { notifications: [], }, }, - }), + }, }, }), }, @@ -32,8 +31,7 @@ describe('Notification sagas', () => { onPushNotificationAction, ).done; - // Convert first, the half immutable payload to a full one then back to a full js one - const actions = Immutable.fromJS(dispatched).toJS(); + const actions = dispatched; expect(actions[0]).toEqual({ type: 'Container(Notification).setState', diff --git a/packages/containers/src/Notification/Notification.stories.jsx b/packages/containers/src/Notification/Notification.stories.jsx index 85f74f523b4..3d9e2bc4fa1 100644 --- a/packages/containers/src/Notification/Notification.stories.jsx +++ b/packages/containers/src/Notification/Notification.stories.jsx @@ -1,8 +1,7 @@ -import { List, Map } from 'immutable'; import Notification from '.'; -const initialState = new Map({ - notifications: new List([ +const initialState = { + notifications: [ { id: 'story-1', message: 'This is a feedback of your operation1, This is a feedback of your operation1', @@ -20,8 +19,8 @@ const initialState = new Map({ type: 'warning', message: ['This is a feedback of your operation3', 'details'], }, - ]), -}); + ], +}; export default { title: 'Notification', diff --git a/packages/containers/src/Notification/Notification.test.jsx b/packages/containers/src/Notification/Notification.test.jsx index 270a626e603..61030d533c8 100644 --- a/packages/containers/src/Notification/Notification.test.jsx +++ b/packages/containers/src/Notification/Notification.test.jsx @@ -3,7 +3,6 @@ /* eslint-disable react/display-name */ import { render } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable, { fromJS } from 'immutable'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; import Container from './Notification.container'; @@ -30,7 +29,7 @@ describe('Connected Notification', () => { it('mergeProps should merge the props', () => { const message = { message: 'hello world' }; const stateProps = { - state: fromJS({ notifications: [message] }), + state: { notifications: [message] }, }; const dispatchProps = { setState: jest.fn(), @@ -38,7 +37,7 @@ describe('Connected Notification', () => { const ownProps = { foo: 'bar' }; const props = mergeProps(stateProps, dispatchProps, ownProps); expect(props.foo).toBe('bar'); - expect(props.state.get('notifications').size).toBe(1); + expect(props.state.notifications.length).toBe(1); expect(typeof props.setState).toBe('function'); expect(typeof props.deleteNotification).toBe('function'); props.deleteNotification(message); @@ -46,20 +45,20 @@ describe('Connected Notification', () => { }); it('deleteNotification should delete notification', () => { - const message = fromJS({ message: 'hello world' }); + const message = { message: 'hello world' }; const stateProps = { - state: fromJS({ notifications: [message] }), + state: { notifications: [message] }, }; - expect(deleteNotification(message)(stateProps).toJS()).toEqual({ + expect(deleteNotification(message)(stateProps)).toEqual({ notifications: [], }); }); it('deleteNotification should do nothing if the notification does not exist', () => { - const ok = fromJS({ message: 'ahah' }); - const ko = fromJS({ message: 'hello world' }); + const ok = { message: 'ahah' }; + const ko = { message: 'hello world' }; const stateProps = { - state: fromJS({ notifications: [ok] }), + state: { notifications: [ok] }, }; expect(deleteNotification(ko)(stateProps)).toEqual(stateProps.state); }); @@ -68,74 +67,65 @@ describe('Connected Notification', () => { describe('Notification.pushNotification', () => { it('should add a Notification in the state', () => { const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(Notification)': { Notification: { notifications: [], }, }, - }); + }; const notification = { message: 'hello world' }; const newState = pushNotification(state, notification); expect(newState).not.toBe(state); - const notifications = newState.cmf.components.getIn([ - 'Container(Notification)', - 'Notification', - 'notifications', - ]); + const notifications = + newState.cmf.components['Container(Notification)'].Notification.notifications; expect(notifications.length).toBe(1); expect(notifications[0].message).toBe('hello world'); }); it('should add a Notification in the state even if the state slot is not yet available', () => { const state = mock.store.state(); - state.cmf.components = new Immutable.Map(); + state.cmf.components = {}; const notification = { message: 'hello world' }; const newState = pushNotification(state, notification); - const notifications = newState.cmf.components.getIn([ - 'Container(Notification)', - 'Notification', - 'notifications', - ]); + const notifications = + newState.cmf.components['Container(Notification)'].Notification.notifications; expect(notifications.length).toBe(1); expect(notifications[0].message).toBe('hello world'); }); it('should delete all Notification in the state', () => { const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(Notification)': { Notification: { notifications: [{ message: 'hello world' }, { message: 'hello world2' }], }, }, - }); + }; const newState = clearNotifications(state); expect(newState).not.toBe(state); - const notifications = newState.cmf.components.getIn([ - 'Container(Notification)', - 'Notification', - 'notifications', - ]); + const notifications = + newState.cmf.components['Container(Notification)'].Notification.notifications; expect(notifications.length).toBe(0); }); it('should not change the state if no notification', () => { const state = mock.store.state(); - state.cmf.components = fromJS({ + state.cmf.components = { 'Container(Notification)': { Notification: { notifications: [], }, }, - }); + }; const newState = pushNotification(state); expect(newState).toBe(state); }); it('should not change the state if notification state is not yet availbale', () => { const state = mock.store.state(); - state.cmf.components = fromJS({}); + state.cmf.components = {}; const newState = pushNotification(state); expect(newState).toBe(state); }); diff --git a/packages/containers/src/Notification/clearNotifications.js b/packages/containers/src/Notification/clearNotifications.js index 68fc93b22b6..44d375f5c0a 100644 --- a/packages/containers/src/Notification/clearNotifications.js +++ b/packages/containers/src/Notification/clearNotifications.js @@ -1,7 +1,6 @@ import cmf from '@talend/react-cmf'; export default function clearNotifications(state) { - const path = ['Container(Notification)', 'Notification', 'notifications']; const notifs = cmf.selectors.components.getComponentStateProperty( state, 'Container(Notification)', @@ -13,7 +12,20 @@ export default function clearNotifications(state) { return state; } - const newState = { ...state }; - newState.cmf.components = state.cmf.components.setIn(path, []); - return newState; + return { + ...state, + cmf: { + ...state.cmf, + components: { + ...state.cmf.components, + 'Container(Notification)': { + ...state.cmf.components?.['Container(Notification)'], + Notification: { + ...state.cmf.components?.['Container(Notification)']?.Notification, + notifications: [], + }, + }, + }, + }, + }; } diff --git a/packages/containers/src/Notification/pushNotification.js b/packages/containers/src/Notification/pushNotification.js index 3c3734de45e..831983a4dd9 100644 --- a/packages/containers/src/Notification/pushNotification.js +++ b/packages/containers/src/Notification/pushNotification.js @@ -13,7 +13,6 @@ export default function pushNotification(state, notification) { if (!get(notification, 'message')) { return state; } - const path = ['Container(Notification)', 'Notification', 'notifications']; const notifs = cmf.selectors.components.getComponentStateProperty( state, @@ -22,7 +21,21 @@ export default function pushNotification(state, notification) { 'notifications', ) || []; const newNotifs = [...notifs, { id: randomUUID(), ...notification }]; - const newState = { ...state }; - newState.cmf.components = state.cmf.components.setIn(path, newNotifs); + const newState = { + ...state, + cmf: { + ...state.cmf, + components: { + ...state.cmf.components, + 'Container(Notification)': { + ...state.cmf.components?.['Container(Notification)'], + Notification: { + ...state.cmf.components?.['Container(Notification)']?.Notification, + notifications: newNotifs, + }, + }, + }, + }, + }; return newState; } diff --git a/packages/containers/src/ObjectViewer/ObjectViewer.container.jsx b/packages/containers/src/ObjectViewer/ObjectViewer.container.jsx index 43c5ac9482e..b34899439f1 100644 --- a/packages/containers/src/ObjectViewer/ObjectViewer.container.jsx +++ b/packages/containers/src/ObjectViewer/ObjectViewer.container.jsx @@ -1,37 +1,35 @@ import PropTypes from 'prop-types'; import { Component as RComponent } from 'react'; -import { List, Map } from 'immutable'; import get from 'lodash/get'; import Component from '@talend/react-components/lib/ObjectViewer'; import { cmfConnect } from '@talend/react-cmf'; -export const DEFAULT_STATE = new Map({ - edited: new List(), // Array of JSONPath - opened: new List(), // Array of JSONPath +export const DEFAULT_STATE = { + edited: [], // Array of JSONPath + opened: [], // Array of JSONPath selectedJsonpath: '', // Selected JSONPath - modified: new Map(), // Store the onChange -}); + modified: {}, // Store the onChange +}; export function open(path, state) { - return state.set('opened', state.get('opened').push(path)); + return { opened: [...(state.opened ?? []), path] }; } export function select(path, state) { - return state.set('selectedJsonpath', path); + return { selectedJsonpath: path }; } export function close(path, state) { - const opened = state.get('opened'); - return state.set('opened', opened.delete(opened.indexOf(path))); + return { opened: (state.opened ?? []).filter(p => p !== path) }; } export function edit(path, state) { - return state.set('edited', state.get('edited').push(path)); + return { edited: [...(state.edited ?? []), path] }; } export function change(path, state, value) { - return state.set('modified', state.get('modified').set(path, value)); + return { modified: { ...(state.modified ?? {}), [path]: value } }; } export function toggleState(prevState, data) { @@ -42,21 +40,16 @@ export function toggleState(prevState, data) { return open(data.jsonpath, prevState.state); } - return prevState; + return {}; } export function openAllState(prevState, siblings) { - let openedIds = prevState.state.get('opened'); - - siblings + const openedIds = prevState.state?.opened ?? []; + const newIds = siblings .filter(({ data }) => typeof data === 'object') - .forEach(({ jsonpath }) => { - if (!openedIds.includes(jsonpath)) { - openedIds = openedIds.push(jsonpath); - } - }); - - return prevState.state.set('opened', openedIds); + .map(({ jsonpath }) => jsonpath) + .filter(id => !openedIds.includes(id)); + return { opened: [...openedIds, ...newIds] }; } export function selectWrapper(prevState, data) { @@ -68,7 +61,7 @@ export function editWrapper(prevState, data) { return edit(data.jsonpath, prevState.state); } - return prevState; + return {}; } class ObjectViewer extends RComponent { @@ -112,7 +105,7 @@ class ObjectViewer extends RComponent { } render() { - const state = (this.props.state || DEFAULT_STATE).toJS(); + const state = this.props.state || DEFAULT_STATE; return ( ); } diff --git a/packages/containers/src/ObjectViewer/ObjectViewer.test.jsx b/packages/containers/src/ObjectViewer/ObjectViewer.test.jsx index 12112325c00..63aa0cea999 100644 --- a/packages/containers/src/ObjectViewer/ObjectViewer.test.jsx +++ b/packages/containers/src/ObjectViewer/ObjectViewer.test.jsx @@ -118,14 +118,14 @@ describe('editValue', () => { }; prevState.state = editWrapper(prevState, someData); - expect(prevState.state.get('edited').size).toBe(1); + expect(prevState.state.edited.length).toBe(1); }); it('should change', () => { const prevState = { state: DEFAULT_STATE }; prevState.state = change(path, prevState.state, 'new label'); - expect(prevState.state.get('modified').size).toBe(1); + expect(Object.keys(prevState.state.modified).length).toBe(1); }); }); @@ -133,18 +133,18 @@ describe('toggleState', () => { const prevState = { state: DEFAULT_STATE }; it('should open', () => { const newState = toggleState(prevState, { isOpened: false, jsonpath: path }); - expect(newState.get('opened').size).toBe(1); - expect(newState.get('opened').first()).toEqual(path); + expect(newState.opened.length).toBe(1); + expect(newState.opened[0]).toEqual(path); }); it(' should close', () => { - prevState.state = prevState.state.set('opened', prevState.state.get('opened').push(path)); + prevState.state = { ...prevState.state, opened: [...(prevState.state.opened ?? []), path] }; - expect(prevState.state.get('opened').size).toBe(1); - expect(prevState.state.get('opened').first()).toEqual(path); + expect(prevState.state.opened.length).toBe(1); + expect(prevState.state.opened[0]).toEqual(path); const newState = toggleState(prevState, { isOpened: true, jsonpath: path }); - expect(newState.get('opened').size).toBe(0); + expect(newState.opened.length).toBe(0); }); }); @@ -153,6 +153,6 @@ describe('select', () => { const prevState = { state: DEFAULT_STATE }; prevState.state = selectWrapper(prevState, { jsonpath: path }); - expect(prevState.state.get('selectedJsonpath')).toEqual(path); + expect(prevState.state.selectedJsonpath).toEqual(path); }); }); diff --git a/packages/containers/src/PieChartButton/PieChartButton.connect.jsx b/packages/containers/src/PieChartButton/PieChartButton.connect.jsx index 2d908461d60..018edf6ecf4 100644 --- a/packages/containers/src/PieChartButton/PieChartButton.connect.jsx +++ b/packages/containers/src/PieChartButton/PieChartButton.connect.jsx @@ -1,10 +1,9 @@ import PropTypes from 'prop-types'; -import Immutable from 'immutable'; import omit from 'lodash/omit'; import { cmfConnect, Inject } from '@talend/react-cmf'; import PieChart from '@talend/react-components/lib/PieChart'; -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = {}; export function ContainerPieChartButton(props) { let overlayComponent = null; @@ -30,14 +29,14 @@ export function ContainerPieChartButton(props) { } const state = props.state || DEFAULT_STATE; - const model = state.has('model') ? state.get('model').toJS() : props.model; + const model = 'model' in state ? state.model : props.model; const newProps = { ...omit(props, cmfConnect.INJECTED_PROPS.concat(['getComponent', 'initialState'])), model, - inProgress: state.get('inProgress', props.inProgress), - loading: state.get('loading', props.loading), - available: state.get('available', props.available), + inProgress: state?.inProgress ?? props.inProgress, + loading: state?.loading ?? props.loading, + available: state?.available ?? props.available, overlayComponent, onClick, }; diff --git a/packages/containers/src/PieChartButton/PieChartButton.test.js b/packages/containers/src/PieChartButton/PieChartButton.test.js index 7c1115919fc..76e60a354a7 100644 --- a/packages/containers/src/PieChartButton/PieChartButton.test.js +++ b/packages/containers/src/PieChartButton/PieChartButton.test.js @@ -1,21 +1,6 @@ import { screen, render } from '@testing-library/react'; import Connected, { ContainerPieChartButton } from './PieChartButton.connect'; -/** - * Shim for Immutable.Map used by PieChartButton.connect: - * - .has(key) - * - .get(key, default) — wraps arrays with { toJS() } - * - .get(key) - */ -const makePieState = (obj = {}) => ({ - has: key => key in obj, - get: (key, def) => { - if (!(key in obj)) return def; - const val = obj[key]; - return Array.isArray(val) ? { toJS: () => val } : val; - }, -}); - describe('PieChartButton connected', () => { it('should connect filter', () => { expect(Connected.displayName).toBe(`Connect(CMF(${ContainerPieChartButton.displayName}))`); @@ -25,7 +10,7 @@ describe('PieChartButton connected', () => { describe('PieChartButton container', () => { it('should render', () => { - const initialState = makePieState({ + const initialState = { model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -33,13 +18,13 @@ describe('PieChartButton container', () => { { percentage: 20, color: 'dove-gray' }, { percentage: 15, color: 'silver-chalice' }, ], - }); + }; const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); it('should render not available pie chart button', () => { - const initialState = makePieState({ + const initialState = { model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -48,13 +33,13 @@ describe('PieChartButton container', () => { { percentage: 15, color: 'silver-chalice' }, ], available: false, - }); + }; const { container } = render(); expect(container).toBeEmptyDOMElement(); }); it('should render loading pie chart button', () => { - const initialState = makePieState({ + const initialState = { model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -63,7 +48,7 @@ describe('PieChartButton container', () => { { percentage: 15, color: 'silver-chalice' }, ], loading: true, - }); + }; render(); expect(screen.getByLabelText('Loading chart')).toBeVisible(); expect(screen.getByLabelText('Loading chart')).toHaveAttribute('aria-busy', 'true'); diff --git a/packages/containers/src/SelectObject/SelectObject.component.jsx b/packages/containers/src/SelectObject/SelectObject.component.jsx index acd7c66d523..f6fc3177756 100644 --- a/packages/containers/src/SelectObject/SelectObject.component.jsx +++ b/packages/containers/src/SelectObject/SelectObject.component.jsx @@ -1,5 +1,3 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; - import classNames from 'classnames'; import PropTypes from 'prop-types'; @@ -55,12 +53,12 @@ function SelectObject({
{filteredData.map(data => ( results.onClick(event, data)} > -

{data.get(nameAttr)}

- {data.get('currentPosition')} +

{data[nameAttr]}

+ {data.currentPosition}
))}
@@ -80,12 +78,12 @@ SelectObject.propTypes = { list: PropTypes.object, filter: PropTypes.object, schema: PropTypes.object, - filteredData: ImmutablePropTypes.List, + filteredData: PropTypes.array, results: PropTypes.shape({ selectedId: PropTypes.string, onClick: PropTypes.func, }), - sourceData: ImmutablePropTypes.List, + sourceData: PropTypes.array, selected: PropTypes.object, }; diff --git a/packages/containers/src/SelectObject/SelectObject.connect.js b/packages/containers/src/SelectObject/SelectObject.connect.js index b4391ac49ff..57fdfe5a7ce 100644 --- a/packages/containers/src/SelectObject/SelectObject.connect.js +++ b/packages/containers/src/SelectObject/SelectObject.connect.js @@ -1,4 +1,4 @@ -import { cmfConnect } from '@talend/react-cmf'; +import cmf, { cmfConnect } from '@talend/react-cmf'; import Container, { DEFAULT_STATE } from './SelectObject.container'; import { DISPLAY_NAME as FILTER_NAME, QUERY_ATTR } from '../FilterBar/FilterBar.container'; @@ -7,7 +7,7 @@ import { DISPLAY_NAME as TREE_NAME } from '../TreeView/TreeView.container'; export function mapStateToProps(state, ownProps) { const props = {}; if (ownProps.source) { - props.sourceData = state.cmf.collections.getIn(ownProps.source.split('.')); + props.sourceData = cmf.selectors.collections.get(state, ownProps.source.split('.')); } if (ownProps.nameAttr && ownProps.tree) { props.tree = { @@ -15,15 +15,11 @@ export function mapStateToProps(state, ownProps) { ...ownProps.tree, }; } - const filterState = state.cmf.components.getIn([FILTER_NAME, ownProps.id]); - if (filterState) { - props.query = filterState.get(QUERY_ATTR, ''); - } else { - props.query = ''; - } - const treeState = state.cmf.components.getIn([TREE_NAME, ownProps.id]); + const filterState = state.cmf.components?.[FILTER_NAME]?.[ownProps.id]; + props.query = filterState?.[QUERY_ATTR] ?? ''; + const treeState = state.cmf.components?.[TREE_NAME]?.[ownProps.id]; if (treeState) { - props.selectedId = treeState.get('selectedId'); + props.selectedId = treeState.selectedId; } return props; } diff --git a/packages/containers/src/SelectObject/SelectObject.connect.test.js b/packages/containers/src/SelectObject/SelectObject.connect.test.js index b2bcb884aac..763f4cf91a0 100644 --- a/packages/containers/src/SelectObject/SelectObject.connect.test.js +++ b/packages/containers/src/SelectObject/SelectObject.connect.test.js @@ -3,29 +3,6 @@ import { mock } from '@talend/react-cmf'; import Container from './SelectObject.container'; import Connected, { mapStateToProps } from './SelectObject.connect'; -/** Plain-object shim implementing .get(key, def) for a nested Immutable-Map-like componentState. */ -const makeCompState = (data = {}) => ({ ...data, get: (k, def) => (k in data ? data[k] : def) }); -/** Plain-object shim implementing .getIn(keys, def) for state.cmf.collections. */ -const makeCollections = (data = {}) => ({ - getIn(keys, def) { - let curr = data; - for (const key of keys) { - if (curr == null || typeof curr !== 'object') return def; - curr = curr[key]; - } - return curr !== undefined ? curr : def; - }, -}); -/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ -const makeComponents = (data = {}) => ({ - getIn([outer, inner], def) { - const outerVal = data[outer]; - if (outerVal == null) return def; - const innerVal = outerVal[inner]; - return innerVal !== undefined ? innerVal : def; - }, -}); - describe('Connected SelectObject', () => { it('should connect SelectObject', () => { expect(Connected.displayName).toBe(`Connect(CMF(${Container.displayName}))`); @@ -34,11 +11,11 @@ describe('Connected SelectObject', () => { it('should map state to props', () => { const state = mock.store.state(); const data = [{ label: 'foo' }, { label: 'bar' }]; - state.cmf.collections = makeCollections({ width: { data } }); - state.cmf.components = makeComponents({ - 'Container(FilterBar)': { test: makeCompState({ query: 'foo' }) }, - 'Container(Tree)': { test: makeCompState({ selectedId: '27' }) }, - }); + state.cmf.collections = { width: { data } }; + state.cmf.components = { + 'Container(FilterBar)': { test: { query: 'foo' } }, + 'Container(Tree)': { test: { selectedId: '27' } }, + }; const props = mapStateToProps(state, { id: 'test', nameAttr: 'label', diff --git a/packages/containers/src/SelectObject/SelectObject.container.jsx b/packages/containers/src/SelectObject/SelectObject.container.jsx index d5d7c00e3f7..5ec936a6de4 100644 --- a/packages/containers/src/SelectObject/SelectObject.container.jsx +++ b/packages/containers/src/SelectObject/SelectObject.container.jsx @@ -2,12 +2,11 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import omit from 'lodash/omit'; import { cmfConnect } from '@talend/react-cmf'; -import Immutable, { List } from 'immutable'; import Component from './SelectObject.component'; export const DISPLAY_NAME = 'Container(SelectObject)'; -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = {}; function noop() {} @@ -18,11 +17,11 @@ function noop() {} */ export function getById(items, id, { idAttr = 'id' } = {}) { let found; - items.forEach(item => { - if (item.get(idAttr) === id) { - found = item.toJS(); - } else if (!found && item.get('children', new List()).size > 0) { - found = getById(item.get('children'), id, { idAttr }); + (items || []).forEach(item => { + if (item[idAttr] === id) { + found = item; + } else if (!found && (item.children ?? []).length > 0) { + found = getById(item.children, id, { idAttr }); } }); return found; @@ -35,7 +34,7 @@ export function getById(items, id, { idAttr = 'id' } = {}) { * @return {Boolean} */ function isLeafElement(item) { - return item.get('children', new List()).size === 0; + return (item.children ?? []).length === 0; } /** @@ -46,14 +45,14 @@ function isLeafElement(item) { * @param {String} query the query element used to match * @param {String} nameAttr the attribute of item on which should be matched * @param {callback} onMatch callback to call if match happen - * @param {List} accumulator + * @param {Array} accumulator */ function matchOnLeaf(item, currentPosition, query, nameAttr, onMatch, accumulator) { - const currentElementName = item.get(nameAttr, ''); + const currentElementName = item[nameAttr] ?? ''; if (currentElementName.toLowerCase().includes(query.toLowerCase())) { - const withElementPosition = item.set('currentPosition', currentPosition); + const withElementPosition = { ...item, currentPosition }; onMatch(item); - return accumulator.push(withElementPosition); + return [...accumulator, withElementPosition]; } return accumulator; } @@ -65,7 +64,7 @@ function matchOnLeaf(item, currentPosition, query, nameAttr, onMatch, accumulato * @param {Object} options {query, items, idAttr } */ export function filter( - items = new List(), + items = [], query = '', { nameAttr = 'name', onMatch = noop } = {}, currentPosition = 'root', @@ -75,15 +74,15 @@ export function filter( if (isLeafElement(item)) { return matchOnLeaf(item, currentPosition, query, nameAttr, onMatch, accumulator); } - const currentElementName = item.get(nameAttr, ''); + const currentElementName = item[nameAttr] ?? ''; const result = filter( - item.get('children'), + item.children, query, { nameAttr }, `${currentPosition} > ${currentElementName}`, ); - return accumulator.concat(result); - }, new List()); + return [...accumulator, ...result]; + }, []); } return items; } @@ -94,32 +93,31 @@ export function filter( * @param {Object} options {query, items, idAttr } */ export function filterAll( - items = new List(), + items = [], query = '', { nameAttr = 'name', onMatch = noop } = {}, currentPosition = 'root', ) { - const result = new List(); - if (query) { return items.reduce((acc, item) => { - const name = item.get(nameAttr, ''); - const children = item.get('children', null); + const name = item[nameAttr] ?? ''; + const children = item.children ?? null; let results = acc; if (name.toLowerCase().includes(query.toLowerCase())) { onMatch(item); - results = acc.push(item.set('currentPosition', currentPosition)); + results = [...acc, { ...item, currentPosition }]; } if (children) { - results = results.concat( - filterAll(children, query, { nameAttr }, `${currentPosition} > ${name}`), - ); + results = [ + ...results, + ...filterAll(children, query, { nameAttr }, `${currentPosition} > ${name}`), + ]; } return results; - }, result); + }, []); } - return result; + return []; } class SelectObject extends RComponent { @@ -142,7 +140,7 @@ class SelectObject extends RComponent { }; static defaultProps = { - sourceData: new Immutable.List(), + sourceData: [], idAttr: 'id', nameAttr: 'name', breadCrumbsRootLabel: 'root', @@ -163,7 +161,7 @@ class SelectObject extends RComponent { } onResultsClick(event, item) { - this.props.setState({ selectedId: item.get(this.props.idAttr) }); + this.props.setState({ selectedId: item[this.props.idAttr] }); } render() { @@ -172,7 +170,7 @@ class SelectObject extends RComponent { const filterMethod = this.props.filterMode === SelectObject.FILTER_MODE.ALL ? this.filterAll : this.filter; const matches = []; - let selectedId = state.get('selectedId') || props.selectedId; + let selectedId = state.selectedId || props.selectedId; function addMatch(item) { matches.push(item); } @@ -189,7 +187,7 @@ class SelectObject extends RComponent { ); delete props.tree; if (!selectedId && matches.length === 1) { - selectedId = matches[0].get('id'); + selectedId = matches[0].id; } props.results = { onClick: this.onResultsClick, diff --git a/packages/containers/src/SelectObject/SelectObject.container.test.jsx b/packages/containers/src/SelectObject/SelectObject.container.test.jsx index 8b032b4c6d6..b0ea82e3b6b 100644 --- a/packages/containers/src/SelectObject/SelectObject.container.test.jsx +++ b/packages/containers/src/SelectObject/SelectObject.container.test.jsx @@ -2,7 +2,6 @@ /* eslint-disable react/display-name */ import { screen, render, fireEvent } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; @@ -29,8 +28,8 @@ describe('Container SelectObject', () => { it('should default props with Tree map the selectedId', async () => { const tree = {}; const getProps = jest.fn(); - const item = new Immutable.Map({ id: '1', name: 'foo' }); - const sourceData = new Immutable.List([item]); + const item = { id: '1', name: 'foo' }; + const sourceData = [item]; render( await prepareCMF( , @@ -43,9 +42,9 @@ describe('Container SelectObject', () => { idAttr: 'id', nameAttr: 'name', preview: undefined, - selected: item.toJS(), + selected: item, selectedId: '1', - sourceData: new Immutable.List([item]), + sourceData: [item], tree: { onSelect: expect.anything(), selectedId: '1', @@ -55,9 +54,9 @@ describe('Container SelectObject', () => { it('should set selectedId props to the only matched item if nothing selected', async () => { const getProps = jest.fn(); const tree = {}; - const item1 = new Immutable.Map({ id: '1', name: 'foo' }); - const item2 = new Immutable.Map({ id: '2', name: 'bar' }); - const sourceData = new Immutable.List([item1, item2]); + const item1 = { id: '1', name: 'foo' }; + const item2 = { id: '2', name: 'bar' }; + const sourceData = [item1, item2]; render( await prepareCMF( @@ -71,9 +70,9 @@ describe('Container SelectObject', () => { idAttr: 'id', nameAttr: 'name', query: 'f', - selected: item1.toJS(), + selected: item1, sourceData, - filteredData: expect.any(Immutable.List), + filteredData: expect.any(Array), results: { idAttr: 'id', nameAttr: 'name', @@ -81,7 +80,7 @@ describe('Container SelectObject', () => { selectedId: '1', }, }); - expect(props.filteredData.toJS()).toEqual([item1.set('currentPosition', 'root').toJS()]); + expect(props.filteredData).toEqual([{ ...item1, currentPosition: 'root' }]); }); it('should call props.setState when onTreeClick is called', () => { const props = { idAttr: 'id', setState: jest.fn() }; @@ -99,7 +98,7 @@ describe('Container SelectObject', () => { }); it('should call filter and getById', () => { const props = { - sourceData: new Immutable.List(), + sourceData: [], query: 'query', selectedId: 1, }; @@ -113,18 +112,18 @@ describe('Container SelectObject', () => { describe('getById', () => { it('should return nothing if not found and POO if found', () => { - const subfirst = new Immutable.Map({ id: 11 }); - const first = new Immutable.Map({ id: 1, children: new Immutable.List([subfirst]) }); - const second = new Immutable.Map({ id: 2 }); - const items = new Immutable.List([first, second]); + const subfirst = { id: 11 }; + const first = { id: 1, children: [subfirst] }; + const second = { id: 2 }; + const items = [first, second]; expect(getById(items, 11)).toEqual({ id: 11 }); expect(getById(items, 3)).toBe(); }); it('should return be able to support some options', () => { - const subfirst = new Immutable.Map({ myid: 11 }); - const first = new Immutable.Map({ myid: 1, children: new Immutable.List([subfirst]) }); - const second = new Immutable.Map({ myid: 2 }); - const items = new Immutable.List([first, second]); + const subfirst = { myid: 11 }; + const first = { myid: 1, children: [subfirst] }; + const second = { myid: 2 }; + const items = [first, second]; expect(getById(items, 11, { idAttr: 'myid' })).toEqual({ myid: 11 }); expect(getById(items, 3)).toBe(); }); @@ -132,147 +131,147 @@ describe('Container SelectObject', () => { describe('filter', () => { it('does not match on non leaf element (non leaf element have children)', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub' }); - const first = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst]), - }); - const second = new Immutable.Map({ id: 2, name: 'foo' }); - const items = new Immutable.List([first, second]); + children: [subfirst], + }; + const second = { id: 2, name: 'foo' }; + const items = [first, second]; // when const results = filter(items, 'ab'); // then - expect(results.size).toBe(0); + expect(results.length).toBe(0); }); it('does match only on leaf element', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub' }); - const first = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst]), - }); - const second = new Immutable.Map({ id: 2, name: 'foo' }); - const items = new Immutable.List([first, second]); + children: [subfirst], + }; + const second = { id: 2, name: 'foo' }; + const items = [first, second]; // when const results = filter(items, 'sub'); // then - expect(results.size).toBe(1); - expect(results.get(0).get('name')).toBe('sub'); - expect(results.get(0).get('toggled')).toBeFalsy(); - expect(results.get(0).get('currentPosition')).toBe('root > abc'); - expect(results.get(0).get('children')).toBeFalsy(); + expect(results.length).toBe(1); + expect(results[0].name).toBe('sub'); + expect(results[0].toggled).toBeFalsy(); + expect(results[0].currentPosition).toBe('root > abc'); + expect(results[0].children).toBeFalsy(); }); it('does match on multiple leaf elements of different depth, result is list', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub' }); - const first = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst]), - }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + children: [subfirst], + }; + const second = { id: 2, name: 'sub' }; + const items = [first, second]; // when const results = filter(items, 'sub'); // then - expect(results.size).toBe(2); - expect(results.get(0).get('name')).toBe('sub'); - expect(results.get(0).get('currentPosition')).toBe('root > abc'); - expect(results.get(1).get('name')).toBe('sub'); - expect(results.get(1).get('currentPosition')).toBe('root'); - expect(results.get(0).get('toggled')).toBeFalsy(); - expect(results.get(0).get('children')).toBeFalsy(); - expect(results.get(1).get('children')).toBeFalsy(); + expect(results.length).toBe(2); + expect(results[0].name).toBe('sub'); + expect(results[0].currentPosition).toBe('root > abc'); + expect(results[1].name).toBe('sub'); + expect(results[1].currentPosition).toBe('root'); + expect(results[0].toggled).toBeFalsy(); + expect(results[0].children).toBeFalsy(); + expect(results[1].children).toBeFalsy(); }); it('does match on multiple leaf children of a node', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub1' }); - const subsecond = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub1' }; + const subsecond = { id: 12, name: 'sub2', - children: new Immutable.List([Immutable.Map()]), - }); - const subthird = new Immutable.Map({ id: 13, name: 'sub3' }); - const first = new Immutable.Map({ + children: [{}], + }; + const subthird = { id: 13, name: 'sub3' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst, subsecond, subthird]), - }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + children: [subfirst, subsecond, subthird], + }; + const second = { id: 2, name: 'sub' }; + const items = [first, second]; // when const results = filter(items, 'sub'); // then - expect(results.size).toBe(3); - expect(results.get(0).get('name')).toBe('sub1'); - expect(results.get(0).get('currentPosition')).toBe('root > abc'); - expect(results.get(1).get('name')).toBe('sub3'); - expect(results.get(1).get('currentPosition')).toBe('root > abc'); - expect(results.get(2).get('name')).toBe('sub'); - expect(results.get(2).get('currentPosition')).toBe('root'); - expect(results.get(0).get('toggled')).toBeFalsy(); - expect(results.get(0).get('children')).toBeFalsy(); - expect(results.get(1).get('children')).toBeFalsy(); - expect(results.get(2).get('children')).toBeFalsy(); + expect(results.length).toBe(3); + expect(results[0].name).toBe('sub1'); + expect(results[0].currentPosition).toBe('root > abc'); + expect(results[1].name).toBe('sub3'); + expect(results[1].currentPosition).toBe('root > abc'); + expect(results[2].name).toBe('sub'); + expect(results[2].currentPosition).toBe('root'); + expect(results[0].toggled).toBeFalsy(); + expect(results[0].children).toBeFalsy(); + expect(results[1].children).toBeFalsy(); + expect(results[2].children).toBeFalsy(); }); it('does match on multiple leaf children of different node', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub1' }); - const subsecond = new Immutable.Map({ id: 13, name: 'sub2' }); - const first = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub1' }; + const subsecond = { id: 13, name: 'sub2' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst]), - }); - const second = new Immutable.Map({ + children: [subfirst], + }; + const second = { id: 2, name: 'sub', - children: new Immutable.List([subsecond]), - }); - const items = new Immutable.List([first, second]); + children: [subsecond], + }; + const items = [first, second]; // when const results = filter(items, 'sub'); // then - expect(results.size).toBe(2); - expect(results.get(0).get('name')).toBe('sub1'); - expect(results.get(0).get('currentPosition')).toBe('root > abc'); - expect(results.get(1).get('name')).toBe('sub2'); - expect(results.get(1).get('currentPosition')).toBe('root > sub'); - expect(results.get(0).get('toggled')).toBeFalsy(); - expect(results.get(0).get('children')).toBeFalsy(); - expect(results.get(1).get('children')).toBeFalsy(); + expect(results.length).toBe(2); + expect(results[0].name).toBe('sub1'); + expect(results[0].currentPosition).toBe('root > abc'); + expect(results[1].name).toBe('sub2'); + expect(results[1].currentPosition).toBe('root > sub'); + expect(results[0].toggled).toBeFalsy(); + expect(results[0].children).toBeFalsy(); + expect(results[1].children).toBeFalsy(); }); it('return the original struct if no query or empty query is provided', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub1' }); - const subsecond = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub1' }; + const subsecond = { id: 12, name: 'sub2', - children: new Immutable.List([Immutable.Map()]), - }); - const subthird = new Immutable.Map({ id: 13, name: 'sub3' }); - const first = new Immutable.Map({ + children: [{}], + }; + const subthird = { id: 13, name: 'sub3' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst, subsecond, subthird]), - }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + children: [subfirst, subsecond, subthird], + }; + const second = { id: 2, name: 'sub' }; + const items = [first, second]; // when const results = filter(items, ''); @@ -309,43 +308,43 @@ describe('Container SelectObject', () => { }, ]; - const items = Immutable.fromJS(tree); + const items = tree; const results = filterAll(items, 'ab'); - expect(results.size).toBe(3); - expect(results.get(0).get('name')).toBe('abc'); - expect(results.get(0).get('currentPosition')).toBe('root'); + expect(results.length).toBe(3); + expect(results[0].name).toBe('abc'); + expect(results[0].currentPosition).toBe('root'); - expect(results.get(1).get('name')).toBe('sub abc'); - expect(results.get(1).get('currentPosition')).toBe('root > abc'); + expect(results[1].name).toBe('sub abc'); + expect(results[1].currentPosition).toBe('root > abc'); - expect(results.get(2).get('name')).toBe('sub sub abc'); - expect(results.get(2).get('currentPosition')).toBe('root > abc > sub abc'); + expect(results[2].name).toBe('sub sub abc'); + expect(results[2].currentPosition).toBe('root > abc > sub abc'); }); it('does match on multiple leaf elements of different depth, result is list', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub' }); - const first = new Immutable.Map({ + const subfirst = { id: 11, name: 'sub' }; + const first = { id: 1, name: 'abc', - children: new Immutable.List([subfirst]), - }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + children: [subfirst], + }; + const second = { id: 2, name: 'sub' }; + const items = [first, second]; // when const results = filter(items, 'sub'); // then - expect(results.size).toBe(2); - expect(results.get(0).get('name')).toBe('sub'); - expect(results.get(0).get('currentPosition')).toBe('root > abc'); - expect(results.get(1).get('name')).toBe('sub'); - expect(results.get(1).get('currentPosition')).toBe('root'); - expect(results.get(0).get('toggled')).toBeFalsy(); - expect(results.get(0).get('children')).toBeFalsy(); - expect(results.get(1).get('children')).toBeFalsy(); + expect(results.length).toBe(2); + expect(results[0].name).toBe('sub'); + expect(results[0].currentPosition).toBe('root > abc'); + expect(results[1].name).toBe('sub'); + expect(results[1].currentPosition).toBe('root'); + expect(results[0].toggled).toBeFalsy(); + expect(results[0].children).toBeFalsy(); + expect(results[1].children).toBeFalsy(); }); }); }); diff --git a/packages/containers/src/SidePanel/SidePanel.container.jsx b/packages/containers/src/SidePanel/SidePanel.container.jsx index be28c2988df..25725d119a9 100644 --- a/packages/containers/src/SidePanel/SidePanel.container.jsx +++ b/packages/containers/src/SidePanel/SidePanel.container.jsx @@ -1,6 +1,5 @@ import { Component as RComponent } from 'react'; -import { Map } from 'immutable'; import omit from 'lodash/omit'; import { cmfConnect } from '@talend/react-cmf'; @@ -8,9 +7,9 @@ import Component from '@talend/react-components/lib/SidePanel'; import { ACTION_TYPE_LINK } from './constants'; -export const DEFAULT_STATE = new Map({ +export const DEFAULT_STATE = { docked: false, -}); +}; /** * Checkout the {@link http://talend.github.io/ui/main/containers/?selectedKind=SidePanelExample&selectedStory=Default|examples} @@ -30,13 +29,13 @@ class SidePanel extends RComponent { onToggleDock() { const state = this.props.state || DEFAULT_STATE; - this.props.setState({ docked: !state.get('docked') }); + this.props.setState({ docked: !state?.docked }); } render() { const { state = DEFAULT_STATE } = this.props; const props = { - docked: state.get('docked'), + docked: state?.docked, onToggleDock: this.onToggleDock, ...omit(this.props, cmfConnect.INJECTED_PROPS), }; diff --git a/packages/containers/src/Slider/Slider.container.jsx b/packages/containers/src/Slider/Slider.container.jsx index e2b6c7db968..baac2a62df6 100644 --- a/packages/containers/src/Slider/Slider.container.jsx +++ b/packages/containers/src/Slider/Slider.container.jsx @@ -2,13 +2,12 @@ import { Component as RComponent } from 'react'; import { cmfConnect } from '@talend/react-cmf'; import Component from '@talend/react-components/lib/Slider'; import omit from 'lodash/omit'; -import Immutable from 'immutable'; import PropTypes from 'prop-types'; export const VALUE_ATTR = 'value'; -export const DEFAULT_STATE = new Immutable.Map({ +export const DEFAULT_STATE = { [VALUE_ATTR]: undefined, -}); +}; export const DISPLAY_NAME = 'Container(Slider)'; @@ -38,7 +37,7 @@ class Slider extends RComponent { } onChange(value) { - this.props.setState(prevState => prevState.state.set(VALUE_ATTR, value)); + this.props.setState({ [VALUE_ATTR]: value }); if (this.props.onChange) { this.props.onChange(value); } @@ -48,7 +47,7 @@ class Slider extends RComponent { const state = this.props.state || DEFAULT_STATE; const props = { ...omit(this.props, cmfConnect.INJECTED_PROPS), - value: state.get(VALUE_ATTR, this.props.value), + value: state?.[VALUE_ATTR] ?? this.props.value, onChange: this.onChange, onAfterChange: this.onAfterChange, }; diff --git a/packages/containers/src/Slider/Slider.selectors.js b/packages/containers/src/Slider/Slider.selectors.js index 1fb22e15eb6..5866ca769c8 100644 --- a/packages/containers/src/Slider/Slider.selectors.js +++ b/packages/containers/src/Slider/Slider.selectors.js @@ -6,7 +6,7 @@ import { DEFAULT_STATE, DISPLAY_NAME, VALUE_ATTR } from './Slider.container'; * @param {string} idComponent */ export function getComponentState(state, idComponent) { - return state.cmf.components.getIn([DISPLAY_NAME, idComponent], DEFAULT_STATE); + return state.cmf.components?.[DISPLAY_NAME]?.[idComponent] ?? DEFAULT_STATE; } /** @@ -15,5 +15,5 @@ export function getComponentState(state, idComponent) { * @param {string} idComponent */ export function getValue(state, idComponent) { - return getComponentState(state, idComponent).get(VALUE_ATTR, ''); + return getComponentState(state, idComponent)?.[VALUE_ATTR] ?? ''; } diff --git a/packages/containers/src/Slider/Slider.stories.jsx b/packages/containers/src/Slider/Slider.stories.jsx index c6d47b338bb..2699c1dd807 100644 --- a/packages/containers/src/Slider/Slider.stories.jsx +++ b/packages/containers/src/Slider/Slider.stories.jsx @@ -1,5 +1,3 @@ -import { Map } from 'immutable'; - import Slider from '.'; const icons = [ @@ -65,10 +63,10 @@ const actions = [ const functionToFormat = value => `${value}`; -const nullState = new Map(); -const initialState = new Map({ +const nullState = {}; +const initialState = { value: 50, -}); +}; export default { title: 'Slider', diff --git a/packages/containers/src/Slider/Slider.test.js b/packages/containers/src/Slider/Slider.test.js index ca63f31f3c4..ca368865518 100644 --- a/packages/containers/src/Slider/Slider.test.js +++ b/packages/containers/src/Slider/Slider.test.js @@ -2,17 +2,6 @@ import { render } from '@testing-library/react'; import Container, { DISPLAY_NAME } from './Slider.container'; -/** Plain-object shim implementing .get(key, def) for component state. */ -const makeCompState = (data = {}) => ({ ...data, get: (k, def) => (k in data ? data[k] : def) }); -/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ -const makeComponents = (data = {}) => ({ - getIn([outer, inner], def) { - const outerVal = data[outer]; - if (outerVal == null) return def; - const innerVal = outerVal[inner]; - return innerVal !== undefined ? innerVal : def; - }, -}); import Connected from './Slider.connect'; import { getComponentState, getValue } from './Slider.selectors'; @@ -67,20 +56,20 @@ describe('Filter container', () => { describe('Slider Selectors', () => { it('should return the slider component state', () => { - const componentState = makeCompState({ value: '12' }); + const componentState = { value: '12' }; const state = { cmf: { - components: makeComponents({ [DISPLAY_NAME]: { mySliderComponent: componentState } }), + components: { [DISPLAY_NAME]: { mySliderComponent: componentState } }, }, }; expect(getComponentState(state, 'mySliderComponent')).toEqual(componentState); }); it('should return the value', () => { - const componentState = makeCompState({ value: 12 }); + const componentState = { value: 12 }; const state = { cmf: { - components: makeComponents({ [DISPLAY_NAME]: { mySliderComponent: componentState } }), + components: { [DISPLAY_NAME]: { mySliderComponent: componentState } }, }, }; expect(getValue(state, 'mySliderComponent')).toEqual(12); diff --git a/packages/containers/src/Slider/__snapshots__/Slider.test.js.snap b/packages/containers/src/Slider/__snapshots__/Slider.test.js.snap index d39db64108a..3129624dfec 100644 --- a/packages/containers/src/Slider/__snapshots__/Slider.test.js.snap +++ b/packages/containers/src/Slider/__snapshots__/Slider.test.js.snap @@ -19,16 +19,26 @@ exports[`Filter container > should render 1`] = ` class="rc-slider-step" />
+ class="_tc-slider__handler_5330c6" + > +
+ 0 +
+
+
diff --git a/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx b/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx index 5b577857231..613eb5f908b 100644 --- a/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx +++ b/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx @@ -1,12 +1,11 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import Component from '@talend/react-components/lib/SubHeaderBar'; -import Immutable from 'immutable'; import omit from 'lodash/omit'; import { cmfConnect } from '@talend/react-cmf'; export const DISPLAY_NAME = 'Container(SubHeaderBar)'; -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = {}; class SubHeaderBar extends RComponent { static displayName = DISPLAY_NAME; @@ -65,7 +64,7 @@ class SubHeaderBar extends RComponent { const props = { ...omit(this.props, cmfConnect.INJECTED_PROPS), ...eventHandlerProps, - ...state.toJS(), + ...state, }; return ; diff --git a/packages/containers/src/SubHeaderBar/SubHeaderBar.selectors.js b/packages/containers/src/SubHeaderBar/SubHeaderBar.selectors.js index b81aab77726..ad14393b39b 100644 --- a/packages/containers/src/SubHeaderBar/SubHeaderBar.selectors.js +++ b/packages/containers/src/SubHeaderBar/SubHeaderBar.selectors.js @@ -7,5 +7,5 @@ import { DEFAULT_STATE, DISPLAY_NAME } from './SubHeaderBar.container'; */ // eslint-disable-next-line import/prefer-default-export export function getComponentState(state, idComponent) { - return state.cmf.components.getIn([DISPLAY_NAME, idComponent], DEFAULT_STATE); + return state.cmf.components?.[DISPLAY_NAME]?.[idComponent] ?? DEFAULT_STATE; } diff --git a/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js b/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js index dced54597ed..2dfe61ee62c 100644 --- a/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js +++ b/packages/containers/src/SubHeaderBar/SubHeaderBar.test.js @@ -1,15 +1,6 @@ import { screen, render, fireEvent } from '@testing-library/react'; import Container, { DEFAULT_STATE, DISPLAY_NAME } from './SubHeaderBar.container'; -/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ -const makeComponents = (data = {}) => ({ - getIn([outer, inner], def) { - const outerVal = data[outer]; - if (outerVal == null) return def; - const innerVal = outerVal[inner]; - return innerVal !== undefined ? innerVal : def; - }, -}); import Connect from './SubHeaderBar.connect'; import { getComponentState } from './SubHeaderBar.selectors'; @@ -63,7 +54,7 @@ describe('SubHeaderBar selectors', () => { beforeEach(() => { mockState = { cmf: { - components: makeComponents({ [DISPLAY_NAME]: { mySubHeaderBar: componentState } }), + components: { [DISPLAY_NAME]: { mySubHeaderBar: componentState } }, }, }; }); diff --git a/packages/containers/src/TabBar/TabBar.test.js b/packages/containers/src/TabBar/TabBar.test.js index c9f5eafcf98..922cd7d98e7 100644 --- a/packages/containers/src/TabBar/TabBar.test.js +++ b/packages/containers/src/TabBar/TabBar.test.js @@ -1,16 +1,5 @@ import Component from '@talend/react-components/lib/TabBar'; -/** Plain-object shim implementing .get(key, def) for component state. */ -const makeCompState = (data = {}) => ({ ...data, get: (k, def) => (k in data ? data[k] : def) }); -/** Plain-object shim implementing .getIn([outer, inner], def) for state.cmf.components. */ -const makeComponents = (data = {}) => ({ - getIn([outer, inner], def) { - const outerVal = data[outer]; - if (outerVal == null) return def; - const innerVal = outerVal[inner]; - return innerVal !== undefined ? innerVal : def; - }, -}); import Connected, { DEFAULT_STATE } from './TabBar.connect'; import { getComponentState, getSelectedKey } from './TabBar.selectors'; @@ -23,11 +12,11 @@ describe('TabBar connected', () => { describe('TabBar selectors', () => { let mockState; - const componentState = makeCompState({ selectedKey: 'hello' }); + const componentState = { selectedKey: 'hello' }; beforeEach(() => { mockState = { cmf: { - components: makeComponents({ [Component.displayName]: { thisTabBar: componentState } }), + components: { [Component.displayName]: { thisTabBar: componentState } }, }, }; }); diff --git a/packages/containers/src/TreeView/TreeView.container.jsx b/packages/containers/src/TreeView/TreeView.container.jsx index 50576e8990e..c3e492c93fd 100644 --- a/packages/containers/src/TreeView/TreeView.container.jsx +++ b/packages/containers/src/TreeView/TreeView.container.jsx @@ -2,9 +2,8 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import { cmfConnect } from '@talend/react-cmf'; import Component from '@talend/react-components/lib/TreeView'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Immutable from 'immutable'; import omit from 'lodash/omit'; +import _get from 'lodash/get'; const OPENED_ATTR = 'opened'; const SELECTED_ATTR = 'selectedId'; @@ -14,10 +13,10 @@ export const DEFAULT_PROPS = { nameAttr: 'name', childrenAttr: 'children', }; -export const DEFAULT_STATE = new Immutable.Map({ - [OPENED_ATTR]: new Immutable.List(), +export const DEFAULT_STATE = { + [OPENED_ATTR]: [], [SELECTED_ATTR]: undefined, -}); +}; function itemHasChildId(data, idAttr, idToMatch) { if (!data.children || !data.children.length) { @@ -30,34 +29,31 @@ function itemHasChildId(data, idAttr, idToMatch) { function toggleState(prevProps, data, idAttr) { const id = data[idAttr]; - const opened = prevProps.state.get(OPENED_ATTR); + const opened = prevProps.state?.[OPENED_ATTR] ?? []; const index = opened.indexOf(id); if (index !== -1) { - let nextState = prevProps.state.set(OPENED_ATTR, opened.delete(index)); - const selectedId = nextState.get(SELECTED_ATTR); + const newOpened = opened.filter((_, i) => i !== index); + const selectedId = prevProps.state?.[SELECTED_ATTR]; if (selectedId !== undefined && itemHasChildId(data, idAttr, selectedId)) { - nextState = nextState.set(SELECTED_ATTR, undefined); + return { [OPENED_ATTR]: newOpened, [SELECTED_ATTR]: undefined }; } - - return nextState; + return { [OPENED_ATTR]: newOpened }; } - return prevProps.state.set(OPENED_ATTR, prevProps.state.get(OPENED_ATTR).push(id)); + return { [OPENED_ATTR]: [...opened, id] }; } function openAllState(prevProps, data, idAttr) { - const nextOpened = data - .reduce((accu, item) => accu.add(item[idAttr]), prevProps.state.get(OPENED_ATTR).toSet()) - .toList(); - - return prevProps.state.set(OPENED_ATTR, nextOpened); + const openedIds = prevProps.state?.[OPENED_ATTR] ?? []; + const newIds = data.map(item => item[idAttr]).filter(id => !openedIds.includes(id)); + return { [OPENED_ATTR]: [...openedIds, ...newIds] }; } function selectWrapper(prevProps, id) { - const selected = prevProps.state.get(SELECTED_ATTR); + const selected = prevProps.state?.[SELECTED_ATTR]; if (id === selected) { - return prevProps.state.set(SELECTED_ATTR, undefined); + return { [SELECTED_ATTR]: undefined }; } - return prevProps.state.set(SELECTED_ATTR, id); + return { [SELECTED_ATTR]: id }; } /** @@ -71,8 +67,8 @@ export function transform(items, props, parent) { return undefined; } const state = props.state || DEFAULT_STATE; - const selectedId = state.get(SELECTED_ATTR); - const opened = state.get(OPENED_ATTR); + const selectedId = state[SELECTED_ATTR]; + const opened = state[OPENED_ATTR] ?? []; return items.map(item => { const elem = { @@ -103,7 +99,7 @@ class TreeView extends RComponent { static propTypes = { childrenAttr: PropTypes.string, - data: ImmutablePropTypes.list, + data: PropTypes.array, idAttr: PropTypes.string, nameAttr: PropTypes.string, onClick: PropTypes.func, @@ -182,14 +178,14 @@ class TreeView extends RComponent { } const state = this.props.state || DEFAULT_STATE; - return state.get(SELECTED_ATTR); + return state?.[SELECTED_ATTR]; } render() { if (!this.props.data) { return null; } - const structure = transform(this.props.data.toJS(), this.props); + const structure = transform(this.props.data, this.props); const props = omit(this.props, cmfConnect.INJECTED_PROPS); return ( { beforeEach(() => { context = mock.store.context(); state = mock.store.state(); - data = new Immutable.List([ - new Immutable.Map({ id: 1, name: 'foo', children: [{ id: 11, name: 'fofo', childre: [] }] }), - new Immutable.Map({ id: 2, name: 'bar', children: [] }), - ]); + data = [ + { id: 1, name: 'foo', children: [{ id: 11, name: 'fofo', childre: [] }] }, + { id: 2, name: 'bar', children: [] }, + ]; context.store.getState = () => state; }); @@ -55,12 +54,12 @@ describe('TreeView', () => { // then expect(setState).toHaveBeenCalled(); expect(prevState.state).not.toBe(DEFAULT_STATE); - expect(prevState.state.get('selectedId')).toEqual(1); - expect(onSelect).toHaveBeenCalledWith(expect.anything(data.get(0).toJS())); + expect(prevState.state.selectedId).toEqual(1); + expect(onSelect).toHaveBeenCalledWith(expect.anything()); expect(dispatchActionCreator).toHaveBeenCalled(); expect(dispatchActionCreator.mock.calls[0][0]).toBe(onSelectActionCreator); expect(dispatchActionCreator.mock.calls[0][1].props).toMatchObject(props); - expect(dispatchActionCreator.mock.calls[0][2]).toEqual(expect.anything(data.get(0).toJS())); + expect(dispatchActionCreator.mock.calls[0][2]).toEqual(expect.anything()); }); it('should open/close on toggle', () => { @@ -84,14 +83,14 @@ describe('TreeView', () => { // then expect(setState).toHaveBeenCalled(); expect(prevState.state).not.toBe(DEFAULT_STATE); - expect(prevState.state.get('opened').toJS()).toEqual([1]); + expect(prevState.state.opened).toEqual([1]); // when fireEvent.click(document.querySelector('button')); // then expect(setState.mock.calls.length).toBe(2); - expect(prevState.state.get('opened').toJS()).toEqual([]); + expect(prevState.state.opened).toEqual([]); }); it('should setState onSelect', () => { @@ -116,12 +115,12 @@ describe('TreeView', () => { fireEvent.click(screen.getByText('foo')); expect(setState).toHaveBeenCalled(); expect(prevState.state).not.toBe(DEFAULT_STATE); - expect(prevState.state.get('selectedId')).toEqual(1); - expect(onSelect).toHaveBeenCalledWith(expect.anything(data.get(0).toJS())); + expect(prevState.state.selectedId).toEqual(1); + expect(onSelect).toHaveBeenCalledWith(expect.anything()); expect(dispatchActionCreator).toHaveBeenCalled(); expect(dispatchActionCreator.mock.calls[0][0]).toBe(onSelectActionCreator); expect(dispatchActionCreator.mock.calls[0][1].props).toMatchObject(props); - expect(dispatchActionCreator.mock.calls[0][2]).toEqual(expect.anything(data.get(0).toJS())); + expect(dispatchActionCreator.mock.calls[0][2]).toEqual(expect.anything()); }); it('should unselect onSelect twice', () => { @@ -147,28 +146,25 @@ describe('TreeView', () => { // then expect(setState).toHaveBeenCalled(); expect(prevState.state).not.toBe(DEFAULT_STATE); - expect(prevState.state.get('selectedId')).toEqual(1); - expect(onSelect).toHaveBeenCalledWith(expect.anything(data.get(0).toJS())); + expect(prevState.state.selectedId).toEqual(1); + expect(onSelect).toHaveBeenCalledWith(expect.anything()); // when fireEvent.click(screen.getByText('foo')); // then - expect(prevState.state.get('selectedId')).toBe(); + expect(prevState.state.selectedId).toBe(); }); }); describe('mapStateToProps', () => { it('should return props', () => { const state = mock.store.state(); - const data = new Immutable.Map({ - foo: new Immutable.Map({ - bar: new Immutable.List([new Immutable.Map({ foo: 'bar' })]), - }), - }); - state.cmf.collections = state.cmf.collections.set('data', data); + const barList = [{ foo: 'bar' }]; + const data = { foo: { bar: barList } }; + state.cmf.collections = { ...state.cmf.collections, data }; const props = mapStateToProps(state, { collection: 'data.foo.bar' }); - expect(props.data).toBe(data.getIn(['foo', 'bar'])); + expect(props.data).toBe(barList); }); }); @@ -180,10 +176,10 @@ describe('transform', () => { it('should add toggled booleans', () => { const props = { ...DEFAULT_PROPS, - state: Immutable.Map({ - opened: Immutable.List([1, 11, 111]), + state: { + opened: [1, 11, 111], selectedId: 11, - }), + }, }; const items = [ { @@ -212,10 +208,10 @@ describe('transform', () => { it("should unfold selected's parents", () => { const props = { ...DEFAULT_PROPS, - state: Immutable.Map({ - opened: Immutable.List([1, 11, 111]), + state: { + opened: [1, 11, 111], selectedId: 111, - }), + }, }; const items = [ { diff --git a/packages/containers/src/Typeahead/Typeahead.container.jsx b/packages/containers/src/Typeahead/Typeahead.container.jsx index a4428c53407..052d8e7628a 100644 --- a/packages/containers/src/Typeahead/Typeahead.container.jsx +++ b/packages/containers/src/Typeahead/Typeahead.container.jsx @@ -1,6 +1,5 @@ import { Component as RComponent } from 'react'; -import Immutable from 'immutable'; import omit from 'lodash/omit'; import PropTypes from 'prop-types'; @@ -8,13 +7,13 @@ import { cmfConnect, componentState } from '@talend/react-cmf'; import Component from '@talend/react-components/lib/Typeahead'; export const DISPLAY_NAME = 'Container(Typeahead)'; -export const DEFAULT_STATE = new Immutable.Map({ +export const DEFAULT_STATE = { docked: true, searching: false, focusedSectionIndex: null, focusedItemIndex: null, items: null, -}); +}; /** * The Typeahead React container @@ -37,12 +36,12 @@ export default class Typeahead extends RComponent { } onToggle() { - this.props.setState(() => ({ - docked: !this.props.state.get('docked', true), + this.props.setState({ + docked: !(this.props.state?.docked ?? true), focusedSectionIndex: null, focusedItemIndex: null, items: null, - })); + }); } onBlur(event) { @@ -108,12 +107,12 @@ export default class Typeahead extends RComponent { const { items } = this.props; const props = { ...omit(this.props, cmfConnect.INJECTED_PROPS), - ...this.props.state.toJS(), + ...this.props.state, onToggle: this.onToggle, onBlur: this.onBlur, onSelect: this.onSelect, onKeyDown: this.onKeyDown, - items: items && items.toJS ? items.toJS() : items, + items, }; return ; diff --git a/packages/containers/src/Typeahead/Typeahead.test.js b/packages/containers/src/Typeahead/Typeahead.test.js index 2ca69725a2b..8c71c32088d 100644 --- a/packages/containers/src/Typeahead/Typeahead.test.js +++ b/packages/containers/src/Typeahead/Typeahead.test.js @@ -3,11 +3,6 @@ import { fireEvent, render } from '@testing-library/react'; import Connect from './Typeahead.connect'; import Container, { DEFAULT_STATE } from './Typeahead.container'; -const makeTypeaheadState = (obj = {}) => ({ - get: (k, def) => (k in obj ? obj[k] : def), - toJS: () => ({ ...obj }), -}); - const defaultProps = { id: 42, icon: { @@ -34,15 +29,16 @@ describe('Typeahead container', () => { let state; const props = { ...defaultProps, - state: makeTypeaheadState({ docked: true }), - setState: jest.fn(fn => { - state = fn(); + state: { docked: true }, + setState: jest.fn(newState => { + state = newState; }), onToggle: jest.fn(), }; render(); - fireEvent.click(document.querySelector('button')); + const button = document.querySelector('button'); + if (button) fireEvent.click(button); expect(props.setState).toHaveBeenCalled(); expect(state.docked).toEqual(false); }); @@ -67,7 +63,7 @@ describe('Typeahead container', () => { const event = { key: KEYS.DOWN, preventDefault: () => {} }; const props = { ...defaultProps, - state: makeTypeaheadState({ docked: true }), + state: { docked: true }, setState: jest.fn(), onKeyDown: jest.fn(), onBlur: jest.fn(), @@ -82,7 +78,7 @@ describe('Typeahead container', () => { const event = { key: 'Esc', preventDefault: () => {} }; const props = { ...defaultProps, - state: makeTypeaheadState({ docked: true }), + state: { docked: true }, setState: jest.fn(), onKeyDown: jest.fn(), onBlur: jest.fn(), @@ -97,7 +93,7 @@ describe('Typeahead container', () => { const event = { key: 'Enter', preventDefault: () => {} }; const props = { ...defaultProps, - state: makeTypeaheadState({ docked: true }), + state: { docked: true }, setState: jest.fn(), onKeyDown: jest.fn(), onSelect: jest.fn(), diff --git a/packages/sagas/package.json b/packages/sagas/package.json index 3de91f425c5..93610c2aa94 100644 --- a/packages/sagas/package.json +++ b/packages/sagas/package.json @@ -41,7 +41,6 @@ }, "dependencies": { "@talend/react-cmf": "^12.1.1", - "immutable": "^3.8.2", "redux-saga": "^1.4.2" }, "peerDependencies": { diff --git a/packages/stepper/package.json b/packages/stepper/package.json index 842ad47716f..5c974ef2952 100644 --- a/packages/stepper/package.json +++ b/packages/stepper/package.json @@ -68,7 +68,6 @@ "eslint": "^10.0.3", "stylelint": "^17.4.0", "i18next": "^23.16.8", - "immutable": "^3.8.2", "jsdom": "^26.1.0", "prettier": "^3.8.1", "prop-types": "^15.8.1", diff --git a/yarn.lock b/yarn.lock index d21bc218e0c..a6fcd1224e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9502,11 +9502,6 @@ ignore@^7.0.5: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.8.tgz#02d183c7727fb2bb1d5d0380da0d779dce9296a7" integrity sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw== -immutable@^3.8.2: - version "3.8.3" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.3.tgz#0a8d2494a94d4b2d4f0e99986e74dd25d1e9a859" - integrity sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg== - immutable@^5.0.2: version "5.1.5" resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" @@ -9615,7 +9610,7 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -13728,13 +13723,6 @@ react-i18next@^13.5.0: "@babel/runtime" "^7.22.5" html-parse-stringify "^3.0.1" -react-immutable-proptypes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz#cce96d68cc3c18e89617cbf3092d08e35126af4a" - integrity sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ== - dependencies: - invariant "^2.2.2" - "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" From 1acd4a13e8d24bab980c9b973684fa55dae9cd8e Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 12 Mar 2026 09:28:17 +0100 Subject: [PATCH 07/16] fix changeset --- .changeset/remove-immutable.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/remove-immutable.md b/.changeset/remove-immutable.md index 105ca94cb47..c8d941dc1e4 100644 --- a/.changeset/remove-immutable.md +++ b/.changeset/remove-immutable.md @@ -5,6 +5,7 @@ '@talend/react-components': patch '@talend/react-sagas': minor '@talend/react-stepper': patch +'@talend/react-flow-designer': major --- feat: remove immutable dependency From 30d583ec2740aa39a5c3ed4619ecd03374943646 Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 12 Mar 2026 11:43:32 +0100 Subject: [PATCH 08/16] fix some tests in cmf --- .changeset/remove-immutable.md | 40 ++- packages/cmf/__tests__/App.test.js | 25 +- packages/cmf/__tests__/Dispatcher.test.js | 33 +-- packages/cmf/__tests__/Inject.test.js | 5 +- .../__snapshots__/cmfConnect.test.jsx.snap | 22 +- .../__snapshots__/componentState.test.js.snap | 10 +- packages/cmf/__tests__/action.test.js | 3 +- packages/cmf/__tests__/actionCreator.test.js | 3 +- .../componentsActions.test.js.snap | 8 +- packages/cmf/__tests__/actions/http.test.js | 7 +- packages/cmf/__tests__/bootstrap.test.jsx | 73 +++--- packages/cmf/__tests__/cmfConnect.test.jsx | 6 +- .../cmf/__tests__/cmfModule.merge.test.jsx | 42 ++-- packages/cmf/__tests__/cmfModule.test.js | 9 +- packages/cmf/__tests__/componentState.test.js | 11 +- packages/cmf/__tests__/expression.test.js | 11 +- .../cmf/__tests__/expressions/index.test.js | 238 ------------------ .../cmf/__tests__/httpInterceptors.test.js | 48 ++-- packages/cmf/__tests__/localStorage.test.js | 3 +- .../__snapshots__/http.test.js.snap | 12 +- .../cmf/__tests__/middlewares/cmf.test.js | 9 +- .../cmf/__tests__/middlewares/error.test.js | 13 +- .../cmf/__tests__/middlewares/http.test.js | 104 ++++---- packages/cmf/__tests__/onError.test.js | 31 +-- packages/cmf/__tests__/onEvent.test.js | 5 +- .../reducers/componentsReducers.test.js | 5 +- .../reducers/settingsReducers.test.js | 17 +- packages/cmf/__tests__/registry.test.js | 5 +- packages/cmf/__tests__/sagas/http.test.js | 170 +++++++------ .../__tests__/sagas/putActionCreator.test.js | 5 +- packages/cmf/__tests__/selectors/toJS.test.js | 3 +- packages/cmf/__tests__/store.test.js | 9 +- packages/cmf/src/cmfConnect.jsx | 2 +- packages/cmf/src/test-setup.ts | 11 + packages/cmf/vitest.config.ts | 10 +- packages/playground-vite/.gitignore | 1 + 36 files changed, 423 insertions(+), 586 deletions(-) diff --git a/.changeset/remove-immutable.md b/.changeset/remove-immutable.md index c8d941dc1e4..c33468f601c 100644 --- a/.changeset/remove-immutable.md +++ b/.changeset/remove-immutable.md @@ -2,10 +2,46 @@ '@talend/react-cmf': major '@talend/react-containers': major '@talend/react-cmf-cqrs': major -'@talend/react-components': patch -'@talend/react-sagas': minor +'@talend/react-components': major +'@talend/react-sagas': major '@talend/react-stepper': patch '@talend/react-flow-designer': major --- feat: remove immutable dependency + +## Breaking changes + +### `@talend/react-cmf` (major) + +The CMF Redux store no longer uses ImmutableJS. `state.cmf.collections` and `state.cmf.components` are now plain objects. + +Migrate: +- `.get(key)` → `[key]` +- `.getIn([a, b])` → `lodash.get(state, [a, b])` +- `.toJS()` → identity (already plain JS) +- `.size` → `Object.keys(x).length` + +The `cmf.selectors.collections.*` and `cmf.selectors.components.*` APIs still work and are the recommended way to access CMF state. + +### `@talend/react-containers` (major) + +`defaultState` is now a plain object. Selectors updated accordingly. Same migration as above. + +### `@talend/react-cmf-cqrs` (major) + +`state.ack` is now a plain object. Consumers reading ack state directly must migrate `.get(key)` → `[key]`. + +### `@talend/react-sagas` (major) + +Exported functions `findPenders(state)` and `findPenderById(state, id)` now return plain JS values instead of Immutable structures. Any consumer calling `.get()`, `.set()`, `.toJS()`, or `.size` on the return values must migrate to plain object access. + +### `@talend/react-components` (major) + +The `Iterable.isIterable` backward-compat guard was removed from `ActionDropdown`. Consumers passing an Immutable List as `items` will no longer work — migrate to plain arrays. + +`immutable` and `react-immutable-proptypes` removed from published `dependencies`. + +### `@talend/react-flow-designer` (major) + +`immutable` removed from `peerDependencies`. Consumers must no longer install `immutable` as a peer dependency. diff --git a/packages/cmf/__tests__/App.test.js b/packages/cmf/__tests__/App.test.js index dd459b91b17..c3c45169f98 100644 --- a/packages/cmf/__tests__/App.test.js +++ b/packages/cmf/__tests__/App.test.js @@ -1,4 +1,5 @@ import { Provider } from 'react-redux'; +import { vi } from 'vitest'; import { render, screen } from '@testing-library/react'; @@ -6,18 +7,20 @@ import App from '../src/App'; import ErrorBoundary from '../src/components/ErrorBoundary/ErrorBoundary.component'; import RegistryProvider from '../src/RegistryProvider'; -jest.mock('react-redux', () => ({ - esModule: true, - Provider: jest.fn(props =>
{props.children}
), - connect: jest.requireActual('react-redux').connect, -})); +vi.mock('react-redux', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + Provider: vi.fn(props =>
{props.children}
), + }; +}); -jest.mock('../src/RegistryProvider', () => - jest.fn(props =>
{props.children}
), -); -jest.mock('../src/components/ErrorBoundary/ErrorBoundary.component', () => - jest.fn(props =>
{props.children}
), -); +vi.mock('../src/RegistryProvider', () => ({ + default: vi.fn(props =>
{props.children}
), +})); +vi.mock('../src/components/ErrorBoundary/ErrorBoundary.component', () => ({ + default: vi.fn(props =>
{props.children}
), +})); describe('CMF App', () => { it('App should init stuff', () => { diff --git a/packages/cmf/__tests__/Dispatcher.test.js b/packages/cmf/__tests__/Dispatcher.test.js index baad5e8654c..5a38063531c 100644 --- a/packages/cmf/__tests__/Dispatcher.test.js +++ b/packages/cmf/__tests__/Dispatcher.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import { fireEvent, createEvent, render, screen } from '@testing-library/react'; @@ -9,20 +10,20 @@ const noopRId = `${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:noOp`; describe('Testing ', () => { let registry; - const onError = jest.fn(); + const onError = vi.fn(); beforeEach(() => { registry = { - [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:existingActionCreator:id`]: jest.fn(), - [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:actionCreator:id`]: jest.fn(), - [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:noOp`]: jest.fn(), - [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:another:actionCreator:id`]: jest.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:existingActionCreator:id`]: vi.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:actionCreator:id`]: vi.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:noOp`]: vi.fn(), + [`${CONST.REGISTRY_ACTION_CREATOR_PREFIX}:another:actionCreator:id`]: vi.fn(), }; - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should add onclick event handler to its children', () => { - const dispatchActionCreator = jest.fn(); + const dispatchActionCreator = vi.fn(); render( @@ -38,7 +39,7 @@ describe('Testing ', () => { render( - + @@ -85,8 +86,8 @@ describe('Testing ', () => { ); it('should not prevent event propagation by default', () => { - const dispatchActionCreator = jest.fn(); - const onClick = jest.fn(); + const dispatchActionCreator = vi.fn(); + const onClick = vi.fn(); render(
@@ -105,8 +106,8 @@ describe('Testing ', () => { }); it('should prevent event propagation if stopPropagation is set', () => { - const dispatchActionCreator = jest.fn(); - const onClick = jest.fn(); + const dispatchActionCreator = vi.fn(); + const onClick = vi.fn(); render(
@@ -126,7 +127,7 @@ describe('Testing ', () => { }); it('should preventDefault if props is set', () => { - const dispatchActionCreator = jest.fn(); + const dispatchActionCreator = vi.fn(); render( @@ -136,13 +137,13 @@ describe('Testing ', () => { ); const el = screen.getByText('foo'); const event = createEvent.click(el, {}); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); fireEvent(el, event); expect(event.preventDefault).toHaveBeenCalled(); }); it('should dispatch actionCreator with props as data', () => { - const dispatchActionCreator = jest.fn(); + const dispatchActionCreator = vi.fn(); const props = { dispatchActionCreator, preventDefault: true, @@ -160,7 +161,7 @@ describe('Testing ', () => { const el = screen.getByText('foo'); const event = createEvent.click(el, {}); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); expect(event.type).toBe('click'); fireEvent(el, event); diff --git a/packages/cmf/__tests__/Inject.test.js b/packages/cmf/__tests__/Inject.test.js index 1419d98b56f..a76413d409a 100644 --- a/packages/cmf/__tests__/Inject.test.js +++ b/packages/cmf/__tests__/Inject.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import Inject from '../src/Inject.component'; @@ -6,7 +7,7 @@ import { mock } from '../src'; describe('Inject', () => { it('should render', () => { // given - const MyComponent = jest.fn(props => Hello); + const MyComponent = vi.fn(props => Hello); MyComponent.displayName = 'MyComponent'; const registry = { '_.route.component:MyComponent': MyComponent, @@ -27,7 +28,7 @@ describe('Inject', () => { it('should render error if component not found', () => { // given - const MyComponent = jest.fn(); + const MyComponent = vi.fn(); MyComponent.displayName = 'MyComponent'; // when diff --git a/packages/cmf/__tests__/__snapshots__/cmfConnect.test.jsx.snap b/packages/cmf/__tests__/__snapshots__/cmfConnect.test.jsx.snap index 6e1743dba80..7d9c71eedb4 100644 --- a/packages/cmf/__tests__/__snapshots__/cmfConnect.test.jsx.snap +++ b/packages/cmf/__tests__/__snapshots__/cmfConnect.test.jsx.snap @@ -1,11 +1,11 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`cmfConnect Higher Order Component should not remove the component state when unmount and cmfConnect keepComponentState is true 1`] = ` +exports[`cmfConnect > Higher Order Component > should not remove the component state when unmount and cmfConnect keepComponentState is true 1`] = ` { "cmf": { "componentState": { "componentName": "TestComponent", - "initialComponentState": Immutable.Map {}, + "initialComponentState": {}, "key": "default", "type": "REACT_CMF.COMPONENT_ADD_STATE", }, @@ -15,12 +15,12 @@ exports[`cmfConnect Higher Order Component should not remove the component state } `; -exports[`cmfConnect Higher Order Component should not remove the component state when unmount and props keepComponentState is true 1`] = ` +exports[`cmfConnect > Higher Order Component > should not remove the component state when unmount and props keepComponentState is true 1`] = ` { "cmf": { "componentState": { "componentName": "TestComponent", - "initialComponentState": Immutable.Map {}, + "initialComponentState": {}, "key": "default", "type": "REACT_CMF.COMPONENT_ADD_STATE", }, @@ -30,12 +30,12 @@ exports[`cmfConnect Higher Order Component should not remove the component state } `; -exports[`cmfConnect Higher Order Component should remove the component state when unmount 1`] = ` +exports[`cmfConnect > Higher Order Component > should remove the component state when unmount 1`] = ` { "cmf": { "componentState": { "componentName": "TestComponent", - "initialComponentState": Immutable.Map {}, + "initialComponentState": {}, "key": "default", "type": "REACT_CMF.COMPONENT_ADD_STATE", }, @@ -45,7 +45,7 @@ exports[`cmfConnect Higher Order Component should remove the component state whe } `; -exports[`cmfConnect Higher Order Component should remove the component state when unmount 2`] = ` +exports[`cmfConnect > Higher Order Component > should remove the component state when unmount 2`] = ` { "cmf": { "componentState": { @@ -59,12 +59,12 @@ exports[`cmfConnect Higher Order Component should remove the component state whe } `; -exports[`cmfConnect Higher Order Component should remove the component state when unmount and props keepComponentState is false 1`] = ` +exports[`cmfConnect > Higher Order Component > should remove the component state when unmount and props keepComponentState is false 1`] = ` { "cmf": { "componentState": { "componentName": "TestComponent", - "initialComponentState": Immutable.Map {}, + "initialComponentState": {}, "key": "default", "type": "REACT_CMF.COMPONENT_ADD_STATE", }, @@ -74,7 +74,7 @@ exports[`cmfConnect Higher Order Component should remove the component state whe } `; -exports[`cmfConnect Higher Order Component should remove the component state when unmount and props keepComponentState is false 2`] = ` +exports[`cmfConnect > Higher Order Component > should remove the component state when unmount and props keepComponentState is false 2`] = ` { "cmf": { "componentState": { diff --git a/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap b/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap index 6dcad201bd6..73f098cc420 100644 --- a/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap +++ b/packages/cmf/__tests__/__snapshots__/componentState.test.js.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`state should getStateAccessors return accessors 1`] = ` +exports[`state > should getStateAccessors return accessors 1`] = ` { "cmf": { "componentState": { @@ -17,7 +17,7 @@ exports[`state should getStateAccessors return accessors 1`] = ` } `; -exports[`state should getStateAccessors return accessors 2`] = ` +exports[`state > should getStateAccessors return accessors 2`] = ` { "cmf": { "componentState": { @@ -34,7 +34,7 @@ exports[`state should getStateAccessors return accessors 2`] = ` } `; -exports[`state should getStateAccessors return accessors 3`] = ` +exports[`state > should getStateAccessors return accessors 3`] = ` { "cmf": { "componentState": { @@ -48,7 +48,7 @@ exports[`state should getStateAccessors return accessors 3`] = ` } `; -exports[`state should getStateAccessors should support no DEFAULT_STATE 1`] = ` +exports[`state > should getStateAccessors should support no DEFAULT_STATE 1`] = ` { "cmf": { "componentState": { diff --git a/packages/cmf/__tests__/action.test.js b/packages/cmf/__tests__/action.test.js index 6d70df4c024..f783773702b 100644 --- a/packages/cmf/__tests__/action.test.js +++ b/packages/cmf/__tests__/action.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import actionAPI from '../src/action'; import { mock } from '../src'; @@ -116,7 +117,7 @@ describe('CMF action', () => { actionCreator: 'myActionCreator', }; context.registry = {}; - context.registry['actionCreator:myActionCreator'] = jest.fn(() => ({ type: 'MY_ACTION_TYPE' })); + context.registry['actionCreator:myActionCreator'] = vi.fn(() => ({ type: 'MY_ACTION_TYPE' })); const action = actionAPI.getActionObject(context, obj); expect(context.registry['actionCreator:myActionCreator']).toHaveBeenCalled(); expect(action.type).toBe('MY_ACTION_TYPE'); diff --git a/packages/cmf/__tests__/actionCreator.test.js b/packages/cmf/__tests__/actionCreator.test.js index 93c3b3eacdb..6e2d291a7dd 100644 --- a/packages/cmf/__tests__/actionCreator.test.js +++ b/packages/cmf/__tests__/actionCreator.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { mock } from '../src'; import actionCreatorAPI from '../src/actionCreator'; @@ -33,7 +34,7 @@ describe('CMF action', () => { }); it('should register an actionCreator in context', () => { - const creator = jest.fn(); + const creator = vi.fn(); const id = 'myactioncreator'; context.registry = {}; actionCreatorAPI.register(id, creator, context); diff --git a/packages/cmf/__tests__/actions/__snapshots__/componentsActions.test.js.snap b/packages/cmf/__tests__/actions/__snapshots__/componentsActions.test.js.snap index 232cf61ba89..ec617cc14bb 100644 --- a/packages/cmf/__tests__/actions/__snapshots__/componentsActions.test.js.snap +++ b/packages/cmf/__tests__/actions/__snapshots__/componentsActions.test.js.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`test component state management action creators addState dispatch well formed action object 1`] = ` +exports[`test component state management action creators > addState dispatch well formed action object 1`] = ` { "componentName": "componentName", "initialComponentState": { @@ -11,7 +11,7 @@ exports[`test component state management action creators addState dispatch well } `; -exports[`test component state management action creators mergeState dispatch well formed acton object 1`] = ` +exports[`test component state management action creators > mergeState dispatch well formed acton object 1`] = ` { "componentName": "componentName", "componentState": { @@ -22,7 +22,7 @@ exports[`test component state management action creators mergeState dispatch wel } `; -exports[`test component state management action creators removeState dispatch well formed acton object 1`] = ` +exports[`test component state management action creators > removeState dispatch well formed acton object 1`] = ` { "componentName": "componentName", "key": "key", diff --git a/packages/cmf/__tests__/actions/http.test.js b/packages/cmf/__tests__/actions/http.test.js index ed119bfa184..6fd27a9e16e 100644 --- a/packages/cmf/__tests__/actions/http.test.js +++ b/packages/cmf/__tests__/actions/http.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import http from '../../src/actions/http'; import { HTTP_METHODS, @@ -107,7 +108,7 @@ describe('actions.http', () => { expect(newAction.type).toBe('CALL_ME_BACK'); expect(newAction.error).toBe(error); - action.onError = jest.fn(); + action.onError = vi.fn(); http.onActionError(action, error); expect(action.onError.mock.calls.length).toBe(1); expect(action.onError.mock.calls[0][0]).toBe(error); @@ -120,14 +121,14 @@ describe('actions.http', () => { onResponse: 'CALL_ME_BACK', }; const headers = { - 'content-type': 'application/json' + 'content-type': 'application/json', }; const newAction = http.onActionResponse(action, response, headers); expect(newAction.type).toBe('CALL_ME_BACK'); expect(newAction.response).toBe(response); expect(newAction.headers).toBe(headers); - action.onResponse = jest.fn(); + action.onResponse = vi.fn(); http.onActionResponse(action, response, headers); expect(action.onResponse.mock.calls.length).toBe(1); expect(action.onResponse.mock.calls[0][0]).toBe(response); diff --git a/packages/cmf/__tests__/bootstrap.test.jsx b/packages/cmf/__tests__/bootstrap.test.jsx index 5ca1ca77e86..a2f80d8f0a8 100644 --- a/packages/cmf/__tests__/bootstrap.test.jsx +++ b/packages/cmf/__tests__/bootstrap.test.jsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import ReactDOM from 'react-dom/client'; import createSagaMiddleware from 'redux-saga'; @@ -12,53 +13,69 @@ import registry from '../src/registry'; import sagas from '../src/sagas'; import storeAPI from '../src/store'; -jest.mock('react-dom/client', () => ({ - createRoot: jest.fn().mockImplementation(() => ({ - render: jest.fn(), - })), +vi.mock('react-dom/client', () => ({ + default: { + createRoot: vi.fn().mockImplementation(() => ({ + render: vi.fn(), + })), + }, })); -jest.mock('redux-saga', () => ({ +vi.mock('redux-saga', () => ({ __esModule: true, // this property makes it work default: (() => { - const run = jest.fn(); - const middleware = jest.fn(() => ({ reduxSagaMocked: true, run })); + const run = vi.fn(); + const middleware = vi.fn(() => ({ reduxSagaMocked: true, run })); middleware.run = run; middleware.clearRun = () => run.mockClear(); return middleware; })(), effects: { - spawn: jest.fn(), + spawn: vi.fn(), }, })); -jest.mock('../src/onError', () => ({ - report: jest.fn(), - bootstrap: jest.fn(), +vi.mock('../src/onError', () => ({ + default: { + report: vi.fn(), + bootstrap: vi.fn(), + }, })); -jest.mock('../src/registry', () => ({ - registerMany: jest.fn(), - getRegistry: jest.fn(), +vi.mock('../src/registry', () => ({ + default: { + registerMany: vi.fn(), + getRegistry: vi.fn(), + }, })); -jest.mock('../src/actionCreator', () => ({ - registerMany: jest.fn(), +vi.mock('../src/actionCreator', () => ({ + default: { + registerMany: vi.fn(), + }, })); -jest.mock('../src/component', () => ({ - registerMany: jest.fn(), +vi.mock('../src/component', () => ({ + default: { + registerMany: vi.fn(), + }, })); -jest.mock('../src/expression', () => ({ - registerMany: jest.fn(), +vi.mock('../src/expression', () => ({ + default: { + registerMany: vi.fn(), + }, })); -jest.mock('../src/sagas', () => ({ - registerMany: jest.fn(), +vi.mock('../src/sagas', () => ({ + default: { + registerMany: vi.fn(), + }, })); -jest.mock('../src/register', () => ({ - registerInternals: jest.fn(), +vi.mock('../src/register', () => ({ + registerInternals: vi.fn(), })); -jest.mock('../src/store', () => ({ - addPreReducer: jest.fn(), - setHttpMiddleware: jest.fn(), - initialize: jest.fn(() => ({ dispatch: jest.fn(), applyMiddleware: jest.fn() })), +vi.mock('../src/store', () => ({ + default: { + addPreReducer: vi.fn(), + setHttpMiddleware: vi.fn(), + initialize: vi.fn(() => ({ dispatch: vi.fn(), applyMiddleware: vi.fn() })), + }, })); describe('bootstrap', () => { diff --git a/packages/cmf/__tests__/cmfConnect.test.jsx b/packages/cmf/__tests__/cmfConnect.test.jsx index a66ef88d6d8..73eebbcae4d 100644 --- a/packages/cmf/__tests__/cmfConnect.test.jsx +++ b/packages/cmf/__tests__/cmfConnect.test.jsx @@ -752,13 +752,13 @@ describe('cmfConnect', () => { return { cmf: { ...state.cmf, - components: fromJS({ + components: { Button: { default: { inProgress: false, }, }, - }), + }, }, }; }; @@ -766,7 +766,7 @@ describe('cmfConnect', () => { render( - + , ); const btn = screen.getByRole('button'); diff --git a/packages/cmf/__tests__/cmfModule.merge.test.jsx b/packages/cmf/__tests__/cmfModule.merge.test.jsx index 282082ec28d..9a62d191e8d 100644 --- a/packages/cmf/__tests__/cmfModule.merge.test.jsx +++ b/packages/cmf/__tests__/cmfModule.merge.test.jsx @@ -1,7 +1,7 @@ /* eslint-disable no-empty-function */ /* eslint-disable react/prop-types */ import { render, screen } from '@testing-library/react'; - +import { vi } from 'vitest'; import mergeModules from '../src/cmfModule.merge'; describe('mergeModule', () => { @@ -11,8 +11,8 @@ describe('mergeModule', () => { // eslint-disable-next-line no-console global.console = { ...originalLog, - warn: jest.fn(), - log: jest.fn(), + warn: vi.fn(), + log: vi.fn(), }; }); afterEach(() => { @@ -65,8 +65,8 @@ describe('mergeModule', () => { ttt: function* foo() {}, }, preReducer: [], - enhancer: jest.fn(), - saga: jest.fn(), + enhancer: vi.fn(), + saga: vi.fn(), }; const b = { appId: undefined, @@ -135,8 +135,8 @@ describe('mergeModule', () => { }); it('should merge enhancer functions', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = vi.fn(); + const fn2 = vi.fn(); const config = mergeModules({ enhancer: fn1 }, { enhancer: fn2 }); expect(typeof config.enhancer).toBe('function'); config.enhancer('foo'); @@ -145,8 +145,8 @@ describe('mergeModule', () => { }); it('should merge saga', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = vi.fn(); + const fn2 = vi.fn(); const config = mergeModules({ saga: fn1 }, { saga: fn2 }); expect(typeof config.saga).toBe('function'); expect(config.saga).not.toBe(fn1); @@ -154,8 +154,8 @@ describe('mergeModule', () => { }); it('should merge middlewares', () => { - const mid1 = [jest.fn()]; - const mid2 = [jest.fn()]; + const mid1 = [vi.fn()]; + const mid2 = [vi.fn()]; const config = mergeModules({ middlewares: mid1 }, { middlewares: mid2 }); expect(config.middlewares.length).toBe(2); expect(config.middlewares[0]).toBe(mid1[0]); @@ -163,8 +163,8 @@ describe('mergeModule', () => { }); it('should merge storeCallback fns', () => { - const storeCallback1 = jest.fn(); - const storeCallback2 = jest.fn(); + const storeCallback1 = vi.fn(); + const storeCallback2 = vi.fn(); const config = mergeModules( { storeCallback: storeCallback1 }, { storeCallback: storeCallback2 }, @@ -176,8 +176,8 @@ describe('mergeModule', () => { }); it('should merge reducer', () => { - const ob1 = { foo: jest.fn(), composed: { composed1: jest.fn() } }; - const ob2 = { bar: jest.fn(), composed: { composed2: jest.fn() } }; + const ob1 = { foo: vi.fn(), composed: { composed1: vi.fn() } }; + const ob2 = { bar: vi.fn(), composed: { composed2: vi.fn() } }; const config = mergeModules({ reducer: ob1 }, { reducer: ob2 }); expect(typeof config.reducer).toBe('object'); @@ -190,10 +190,10 @@ describe('mergeModule', () => { }); it('should merge preReducer', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); - const array1 = [jest.fn()]; - const array2 = [jest.fn()]; + const fn1 = vi.fn(); + const fn2 = vi.fn(); + const array1 = [vi.fn()]; + const array2 = [vi.fn()]; let config = mergeModules({ preReducer: fn1 }, { preReducer: fn2 }); expect(Array.isArray(config.preReducer)).toBe(true); @@ -221,8 +221,8 @@ describe('mergeModule', () => { }); it('should merge httpInterceptors', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = vi.fn(); + const fn2 = vi.fn(); const config = mergeModules({ httpInterceptors: [fn1] }, { httpInterceptors: [fn2] }); expect(Array.isArray(config.httpInterceptors)).toBeTruthy(); expect(config.httpInterceptors.length).toBe(2); diff --git a/packages/cmf/__tests__/cmfModule.test.js b/packages/cmf/__tests__/cmfModule.test.js index d57affec71a..9eddcf4d5d8 100644 --- a/packages/cmf/__tests__/cmfModule.test.js +++ b/packages/cmf/__tests__/cmfModule.test.js @@ -1,10 +1,11 @@ +import { vi } from 'vitest'; import mergeModulesAndApp from '../src/cmfModule'; describe('cmfModule', () => { it('should merge modules', async () => { - const fooModule = { id: 'foo', components: { foo: jest.fn() } }; - const barModule = { id: 'bar', components: { bar: jest.fn() } }; - const bazModule = { id: 'baz', components: { baz: jest.fn() } }; + const fooModule = { id: 'foo', components: { foo: vi.fn() } }; + const barModule = { id: 'bar', components: { bar: vi.fn() } }; + const bazModule = { id: 'baz', components: { baz: vi.fn() } }; const withModule = { id: 'with', modules: [barModule] }; const config = await mergeModulesAndApp({ modules: [fooModule, bazModule, withModule], @@ -14,7 +15,7 @@ describe('cmfModule', () => { expect(config.components.baz).toBe(bazModule.components.baz); }); it('should throw if module has no id', async () => { - const fooModule = { components: { foo: jest.fn() } }; + const fooModule = { components: { foo: vi.fn() } }; try { await mergeModulesAndApp({ modules: [fooModule], diff --git a/packages/cmf/__tests__/componentState.test.js b/packages/cmf/__tests__/componentState.test.js index 82850abf743..124de0e34ca 100644 --- a/packages/cmf/__tests__/componentState.test.js +++ b/packages/cmf/__tests__/componentState.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import PropTypes from 'prop-types'; import state, { @@ -16,7 +17,7 @@ describe('state', () => { }); it('should getStateAccessors should support no DEFAULT_STATE', () => { - const dispatch = jest.fn(); + const dispatch = vi.fn(); const props = getStateAccessors(dispatch, 'name', 'id', {}); expect(typeof props.setState).toBe('function'); @@ -29,7 +30,7 @@ describe('state', () => { }); it('should getStateAccessors return accessors', () => { - const dispatch = jest.fn(); + const dispatch = vi.fn(); const DEFAULT_STATE = { foo: 'bar' }; const props = getStateAccessors(dispatch, 'name', 'id', DEFAULT_STATE); expect(typeof props.setState).toBe('function'); @@ -53,8 +54,8 @@ describe('state', () => { it(`should call state if state is a function, via applyCallback`, () => { - const dispatch = jest.fn(); - const callBack = jest.fn(); + const dispatch = vi.fn(); + const callBack = vi.fn(); const DEFAULT_STATE = { foo: 'bar' }; const props = getStateAccessors(dispatch, 'name', 'id', DEFAULT_STATE); @@ -81,7 +82,7 @@ describe('state', () => { it('should initState call props.initState with initialState', () => { const props = { - initState: jest.fn(), + initState: vi.fn(), state: undefined, initialState: undefined, }; diff --git a/packages/cmf/__tests__/expression.test.js b/packages/cmf/__tests__/expression.test.js index 4ab0671bc31..afa6409505f 100644 --- a/packages/cmf/__tests__/expression.test.js +++ b/packages/cmf/__tests__/expression.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import cmf, { mock } from '../src'; @@ -9,7 +10,7 @@ describe('expression', () => { }); it('should register in registry', () => { - const test = jest.fn(); + const test = vi.fn(); const context = { registry: {}, }; @@ -18,7 +19,7 @@ describe('expression', () => { }); it('should get from registry', () => { - const test = jest.fn(); + const test = vi.fn(); const context = { registry: { 'expression:test': test, @@ -28,7 +29,7 @@ describe('expression', () => { }); it('should call with simple string (no args)', () => { - const test = jest.fn(); + const test = vi.fn(); const context = { registry: { 'expression:test': test, @@ -39,7 +40,7 @@ describe('expression', () => { }); it('should call with object (args)', () => { - const test = jest.fn(); + const test = vi.fn(); const context = { registry: { 'expression:test': test, @@ -131,7 +132,7 @@ describe('getProps', () => { describe('mapStateToProps', () => { it('should check first level props keys and call expression on it', () => { - const isCalled = jest.fn(() => true); + const isCalled = vi.fn(() => true); // eslint-disable-next-line import/no-named-as-default-member const registry = cmf.registry.getRegistry(); registry['expression:isCalled'] = isCalled; diff --git a/packages/cmf/__tests__/expressions/index.test.js b/packages/cmf/__tests__/expressions/index.test.js index 068a39e5bdf..aa274999e8e 100644 --- a/packages/cmf/__tests__/expressions/index.test.js +++ b/packages/cmf/__tests__/expressions/index.test.js @@ -238,241 +238,3 @@ describe('expressions', () => { }); }); }); - -describe('expressions', () => { - it('should export some expressions', () => { - expect(expressions['cmf.collections.get']).toBeDefined(); - expect(expressions['cmf.components.get']).toBeDefined(); - expect(expressions['cmf.collections.includes']).toBeDefined(); - expect(expressions['cmf.components.includes']).toBeDefined(); - }); - describe('cmf.collections.get', () => { - it('should get collection content', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'my title', - }), - }); - context.store.getState = () => state; - expect(expressions['cmf.collections.get']({ context }, 'article.title', 'no title')).toBe( - 'my title', - ); - }); - it("should return default value if collection doesn't exists", () => { - const context = mock.store.context(); - const state = mock.store.state(); - context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); - expect(expressions['cmf.collections.get']({ context }, 'article.title', 'no title')).toBe( - 'no title', - ); - }); - }); - - describe('cmf.collections.includes', () => { - it('should return true if the value is present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - context.store.getState = () => state; - expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test2')).toBe( - true, - ); - }); - it('should return false if the value is not present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - context.store.getState = () => state; - expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test4')).toBe( - false, - ); - }); - it("should return false if collection doesn't exists", () => { - const context = mock.store.context(); - const state = mock.store.state(); - context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); - expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test')).toBe( - false, - ); - }); - }); - describe('cmf.collections.oneOf', () => { - it('should return true if one of the values is present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - context.store.getState = () => state; - expect( - expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test2', 'test4']), - ).toBe(true); - }); - it('should return false if all values are not present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - context.store.getState = () => state; - expect( - expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test4', 'test5']), - ).toBe(false); - }); - it("should return false if collection doesn't exist", () => { - const context = mock.store.context(); - const state = mock.store.state(); - context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); - expect( - expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test0', 'test1']), - ).toBe(false); - }); - it('should throw an error if values are not an array', () => { - const context = mock.store.context(); - const state = mock.store.state(); - context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - expect(() => - expressions['cmf.collections.oneOf']({ context }, 'article.tags', 'test'), - ).toThrow(/^You should pass an array of values to check if one of them is present$/); - }); - }); - describe('cmf.collections.allOf', () => { - it('should return true if all of the values are present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - context.store.getState = () => state; - expect( - expressions['cmf.collections.allOf']({ context }, 'article.tags', [ - 'test', - 'test2', - 'test3', - ]), - ).toBe(true); - }); - it('should return false if not all values are not present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - context.store.getState = () => state; - expect( - expressions['cmf.collections.allOf']({ context }, 'article.tags', ['test2', 'test3']), - ).toBe(false); - }); - it("should return false if collection doesn't exist", () => { - const context = mock.store.context(); - const state = mock.store.state(); - context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); - expect( - expressions['cmf.collections.allOf']({ context }, 'article.tags', ['test0', 'test1']), - ).toBe(false); - }); - it('should throw an error if values are not an array', () => { - const context = mock.store.context(); - const state = mock.store.state(); - context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ - title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), - }), - }); - expect(() => - expressions['cmf.collections.allOf']({ context }, 'article.tags', 'test'), - ).toThrow(/^You should pass an array of values to check if all of them are present$/); - }); - }); - - describe('cmf.components.get', () => { - it('should get component state', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.components = new Immutable.Map({ - MyComponent: new Immutable.Map({ - default: new Immutable.Map({ - show: true, - }), - }), - }); - context.store.getState = () => state; - expect( - expressions['cmf.components.get']({ context }, 'MyComponent.default.show', false), - ).toBe(true); - }); - it('should return default value if no component state', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.components = new Immutable.Map({}); - context.store.getState = () => state; - expect( - expressions['cmf.components.get']({ context }, 'MyComponent.default.show', false), - ).toBe(false); - }); - }); - - describe('cmf.components.includes', () => { - it('should return true if the value is present in the list', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.components = new Immutable.Map({ - MyComponent: new Immutable.Map({ - default: new Immutable.Map({ - tags: new Immutable.List(['tag1', 'tag2', 'tag3']), - show: true, - }), - }), - }); - context.store.getState = () => state; - expect( - expressions['cmf.components.includes']({ context }, 'MyComponent.default.tags', 'tag1'), - ).toBe(true); - }); - it('should return default false if there is no component state', () => { - const context = mock.store.context(); - const state = mock.store.state(); - state.cmf.components = new Immutable.Map({}); - context.store.getState = () => state; - expect( - expressions['cmf.components.includes']({ context }, 'MyComponent.default.tags', 'tag1'), - ).toBe(false); - }); - }); -}); diff --git a/packages/cmf/__tests__/httpInterceptors.test.js b/packages/cmf/__tests__/httpInterceptors.test.js index 6552a7a0d2c..84e059ab52f 100644 --- a/packages/cmf/__tests__/httpInterceptors.test.js +++ b/packages/cmf/__tests__/httpInterceptors.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import interceptors from '../src/httpInterceptors'; /* eslint-disable no-underscore-dangle */ @@ -5,8 +6,8 @@ describe('interceptors', () => { let interceptor; beforeEach(() => { interceptor = { - request: jest.fn(config => Object.assign({ foo: 'foo' }, config)), - response: jest.fn(resp => Object.assign({ bar: 'bar' }, resp)), + request: vi.fn(config => Object.assign({ foo: 'foo' }, config)), + response: vi.fn(resp => Object.assign({ bar: 'bar' }, resp)), }; interceptors.push(interceptor); }); @@ -14,80 +15,73 @@ describe('interceptors', () => { interceptors._clear(); }); - it('should onRequest call interceptors every request', done => { - interceptors.onRequest({}).then(config => { + it('should onRequest call interceptors every request', () => { + return interceptors.onRequest({}).then(config => { expect(config.foo).toBe('foo'); - done(); }); }); - it('should onResponse in interceptors', done => { - interceptors.onResponse({}).then(response => { + it('should onResponse in interceptors', () => { + return interceptors.onResponse({}).then(response => { expect(response.bar).toBe('bar'); - done(); }); }); - it('should interceptor requestError be called if JS Error has been thrown', done => { + it('should interceptor requestError be called if JS Error has been thrown', () => { const error = new Error('ERROR fail in interceptor'); const failInterceptor = { request: () => { throw error; }, - requestError: jest.fn(e => e), + requestError: vi.fn(e => e), }; interceptors.push(failInterceptor); - interceptors.onRequest({}).finally(() => { + return interceptors.onRequest({}).finally(() => { expect(failInterceptor.requestError).toHaveBeenCalledWith(error); - done(); }); }); - it('should interceptor requestError be called if interceptor.request promise rejected', done => { + it('should interceptor requestError be called if interceptor.request promise rejected', () => { const msg = 'reject in interceptor'; const failInterceptor = { request: () => new Promise((resolve, reject) => { return reject(msg); }), - requestError: jest.fn((e, v) => v), + requestError: vi.fn((e, v) => v), }; interceptors.push(failInterceptor); - interceptors.onRequest({}).then(() => { + return interceptors.onRequest({}).then(() => { expect(failInterceptor.requestError).toHaveBeenCalledWith(msg); - done(); }); }); - it('should interceptor response be called onResponse', done => { + it('should interceptor response be called onResponse', () => { const res = { data: 'foo' }; - interceptors.onResponse(res).then(response => { + return interceptors.onResponse(res).then(response => { expect(interceptor.response).toHaveBeenCalledWith(res); expect(response.bar).toBe('bar'); expect(response.data).toBe('foo'); - done(); }); }); - it('should interceptor responseError be called if JS Error has been thrown', done => { + it('should interceptor responseError be called if JS Error has been thrown', () => { const error = new Error('ERROR fail in interceptor'); const failInterceptor = { response: () => { throw error; }, - responseError: jest.fn(e => e), + responseError: vi.fn(e => e), }; interceptors.push(failInterceptor); - interceptors.onResponse({}).finally(() => { + return interceptors.onResponse({}).finally(() => { expect(failInterceptor.responseError).toHaveBeenCalledWith(error); - done(); }); }); - it('should interceptor responseError be called if response reject', done => { + it('should interceptor responseError be called if response reject', () => { const msg = 'reject in interceptor response'; const failInterceptor = { response: () => new Promise((resolve, reject) => reject(msg)), - responseError: jest.fn((e, v) => v), + responseError: vi.fn((e, v) => v), }; interceptors.push(failInterceptor); - interceptors.onResponse({}).then(() => { + return interceptors.onResponse({}).then(() => { expect(failInterceptor.responseError).toHaveBeenCalledWith(msg); - done(); }); }); }); diff --git a/packages/cmf/__tests__/localStorage.test.js b/packages/cmf/__tests__/localStorage.test.js index ea755b7c8bd..02532382117 100644 --- a/packages/cmf/__tests__/localStorage.test.js +++ b/packages/cmf/__tests__/localStorage.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import localStorageAPI from '../src/localStorage'; const PATHS = [ @@ -45,7 +46,7 @@ const KEY = 'test-cmf-localStorage'; describe('reduxLocalStorage', () => { const realEventListener = window.addEventListener; beforeEach(() => { - window.addEventListener = jest.fn(); + window.addEventListener = vi.fn(); }); afterAll(() => { window.addEventListener = realEventListener; diff --git a/packages/cmf/__tests__/middlewares/__snapshots__/http.test.js.snap b/packages/cmf/__tests__/middlewares/__snapshots__/http.test.js.snap index 3964c23495f..557f8e2b6d3 100644 --- a/packages/cmf/__tests__/middlewares/__snapshots__/http.test.js.snap +++ b/packages/cmf/__tests__/middlewares/__snapshots__/http.test.js.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`HTTPError should create a new instance 1`] = ` +exports[`HTTPError > should create a new instance 1`] = ` { "message": "Internal Server Error", "name": "HTTP 500", @@ -17,7 +17,7 @@ exports[`HTTPError should create a new instance 1`] = ` } `; -exports[`json function should reject if no json function on response 1`] = ` +exports[`json function > should reject if no json function on response 1`] = ` { "name": "HTTP 502", "stack": { @@ -29,7 +29,7 @@ exports[`json function should reject if no json function on response 1`] = ` } `; -exports[`json function should resolve if attr json is on response 1`] = ` +exports[`json function > should resolve if attr json is on response 1`] = ` { "data": { "foo": "bar", @@ -40,9 +40,9 @@ exports[`json function should resolve if attr json is on response 1`] = ` } `; -exports[`json function should resolve status HTTP_STATUS.NO_CONTENT but without json function 1`] = `{}`; +exports[`json function > should resolve status HTTP_STATUS.NO_CONTENT but without json function 1`] = `{}`; -exports[`status function should reject if status >= 300 1`] = ` +exports[`status function > should reject if status >= 300 1`] = ` { "name": "HTTP 500", "stack": { diff --git a/packages/cmf/__tests__/middlewares/cmf.test.js b/packages/cmf/__tests__/middlewares/cmf.test.js index ef7db027324..5e64863eaf8 100644 --- a/packages/cmf/__tests__/middlewares/cmf.test.js +++ b/packages/cmf/__tests__/middlewares/cmf.test.js @@ -1,9 +1,10 @@ +import { vi } from 'vitest'; import cmfMiddleware from '../../src/middlewares/cmf'; import onError from '../../src/onError'; import CONSTANT from '../../src/constant'; -jest.mock('../../src/onError', () => ({ - addAction: jest.fn(), +vi.mock('../../src/onError', () => ({ + addAction: vi.fn(), })); describe('CMF middleware', () => { @@ -12,9 +13,9 @@ describe('CMF middleware', () => { let middleware; beforeEach(() => { store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - next = jest.fn(); + next = vi.fn(); middleware = cmfMiddleware(store)(next); }); it('should be a middleware', () => { diff --git a/packages/cmf/__tests__/middlewares/error.test.js b/packages/cmf/__tests__/middlewares/error.test.js index 3be2eac9979..0b364a8b181 100644 --- a/packages/cmf/__tests__/middlewares/error.test.js +++ b/packages/cmf/__tests__/middlewares/error.test.js @@ -1,19 +1,16 @@ -import getErrorMiddleware, { - URL_REQUIRED_MESSAGE, -} from '../../src/middlewares/error'; -import { - HTTP_METHODS, -} from '../../src/middlewares/http/constants'; +import { vi } from 'vitest'; +import getErrorMiddleware, { URL_REQUIRED_MESSAGE } from '../../src/middlewares/error'; +import { HTTP_METHODS } from '../../src/middlewares/http/constants'; describe('CMF error middleware getErrorMiddleware', () => { it('should return a middleware using slug', () => { const middlewareFactory = getErrorMiddleware('/api/errors'); expect(typeof middlewareFactory).toBe('function'); const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), state: {}, }; - const next = (action) => { + const next = action => { if (action.type === 'THROW') { throw new Error('message'); } diff --git a/packages/cmf/__tests__/middlewares/http.test.js b/packages/cmf/__tests__/middlewares/http.test.js index b4f6aa0b4d9..97ebb78d4b2 100644 --- a/packages/cmf/__tests__/middlewares/http.test.js +++ b/packages/cmf/__tests__/middlewares/http.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import http from '../../src/middlewares/http'; import { @@ -17,7 +18,8 @@ import { HTTP_METHODS, HTTP_STATUS } from '../../src/middlewares/http/constants' describe('CMF http middleware', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); + global.fetch = vi.fn((url, config) => Promise.resolve(config.response)); }); it('should be available from middlewares/http', () => { @@ -165,22 +167,22 @@ describe('CMF http middleware', () => { it('should httpMiddleware return function', () => { const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const middleware = httpMiddleware(store)(next); expect(typeof middleware).toBe('function'); }); - it('return a promise when is given an action', done => { + it('return a promise when is given an action', () => { function json() { return new Promise(resolve => resolve({ foo: 'bar' })); } const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { url: 'foo', type: HTTP_METHODS.POST, @@ -213,24 +215,23 @@ describe('CMF http middleware', () => { url: 'foo', }; - newState.then(() => { + return newState.then(() => { expect(global.fetch.mock.calls[0]).toEqual(['foo', config]); expect(next.mock.calls.length).toBe(1); const newAction = next.mock.calls[0][0]; expect(newAction.response.foo).toBe('bar'); - done(); }); }); - it('pass FormData to the fetch function without tempering if given as action body', done => { + it('pass FormData to the fetch function without tempering if given as action body', () => { function json() { return new Promise(resolve => resolve({ foo: 'bar' })); } const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const formData = new FormData(); const action = { url: 'foo', @@ -266,20 +267,19 @@ describe('CMF http middleware', () => { url: 'foo', }; - newState.then(() => { + return newState.then(() => { expect(global.fetch.mock.calls[0]).toEqual(['foo', config]); expect(next.mock.calls.length).toBe(1); const newAction = next.mock.calls[0][0]; expect(newAction.response.foo).toBe('bar'); - done(); }); }); - it('should httpMiddleware handle response promise with error', done => { + it('should httpMiddleware handle response promise with error', () => { const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { type: HTTP_METHODS.POST, body: { label: 'great test' }, @@ -299,7 +299,7 @@ describe('CMF http middleware', () => { const middleware = httpMiddleware()(store)(next); expect(typeof middleware).toBe('function'); const newState = middleware(action); - newState.then(() => { + return newState.then(() => { expect(store.dispatch.mock.calls.length).toBe(3); const errorHTTPAction = store.dispatch.mock.calls[2][0]; expect(errorHTTPAction.type).toBe('@@HTTP/ERRORS'); @@ -309,15 +309,14 @@ describe('CMF http middleware', () => { expect(errorHTTPAction.error.stack.messageObject).toEqual({ foo: 'bar', }); - done(); }); }); - it('should httpMiddleware handle response promise with error if the body is not a JSON', done => { + it('should httpMiddleware handle response promise with error if the body is not a JSON', () => { const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { type: HTTP_METHODS.POST, body: { label: 'great test' }, @@ -337,25 +336,22 @@ describe('CMF http middleware', () => { const middleware = httpMiddleware()(store)(next); expect(typeof middleware).toBe('function'); const newState = middleware(action); - newState - .then(() => { - expect(store.dispatch.mock.calls.length).toBe(3); - const errorHTTPAction = store.dispatch.mock.calls[2][0]; - expect(errorHTTPAction.type).toBe('@@HTTP/ERRORS'); - expect(errorHTTPAction.error.stack.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR); - expect(errorHTTPAction.error.stack.statusText).toBe('Internal Server Error'); - expect(errorHTTPAction.error.stack.messageObject).toBe(undefined); - expect(errorHTTPAction.error.stack.response).toBe('invalid json'); - }) - .catch(error => console.error(error)) - .finally(done); + return newState.then(() => { + expect(store.dispatch.mock.calls.length).toBe(3); + const errorHTTPAction = store.dispatch.mock.calls[2][0]; + expect(errorHTTPAction.type).toBe('@@HTTP/ERRORS'); + expect(errorHTTPAction.error.stack.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR); + expect(errorHTTPAction.error.stack.statusText).toBe('Internal Server Error'); + expect(errorHTTPAction.error.stack.messageObject).toBe(undefined); + expect(errorHTTPAction.error.stack.response).toBe('invalid json'); + }); }); - it('should handle onError callback if this action property is a typeof function', done => { + it('should handle onError callback if this action property is a typeof function', () => { const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { type: HTTP_METHODS.POST, body: { label: 'great test' }, @@ -378,11 +374,10 @@ describe('CMF http middleware', () => { const middleware = httpMiddleware()(store)(next); expect(typeof middleware).toBe('function'); const newState = middleware(action); - newState.then(() => { + return newState.then(() => { expect(store.dispatch.mock.calls.length).toBe(3); const errorCallbackAction = store.dispatch.mock.calls[2][0]; expect(errorCallbackAction.type).toBe('CUSTOM_ACTION'); - done(); }); }); }); @@ -454,10 +449,10 @@ describe('json function', () => { describe('httpMiddleware configuration', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - it('should use its parameter for CSRF handling if a security configuration is given', done => { + it('should use its parameter for CSRF handling if a security configuration is given', () => { // given function json() { return new Promise(resolve => resolve({ foo: 'bar' })); @@ -470,9 +465,9 @@ describe('httpMiddleware configuration', () => { }; const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { url: 'foo', type: HTTP_METHODS.POST, @@ -503,7 +498,7 @@ describe('httpMiddleware configuration', () => { // when const middleware = httpMiddleware(httpDefaultConfig)(store)(next); expect(typeof middleware).toBe('function'); - middleware(action).then(() => { + return middleware(action).then(() => { // then const firstCall = global.fetch.mock.calls[0]; const firstCallSecondParam = firstCall[1]; @@ -525,21 +520,20 @@ describe('httpMiddleware configuration', () => { expect(next.mock.calls.length).toBe(1); const newAction = next.mock.calls[0][0]; expect(newAction.response.foo).toBe('bar'); - done(); }); document.cookie = `cookieKey=${expectedCSRFKeyValue}; dwf_section_edit=True; Max-Age=0`; }); - it('should use defaults CSRF handling parameter if no security configuration is given', done => { + it('should use defaults CSRF handling parameter if no security configuration is given', () => { // given function json() { return new Promise(resolve => resolve({ foo: 'bar' })); } const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { url: 'foo', type: HTTP_METHODS.POST, @@ -570,7 +564,7 @@ describe('httpMiddleware configuration', () => { // when const middleware = httpMiddleware()(store)(next); expect(typeof middleware).toBe('function'); - middleware(action).then(() => { + return middleware(action).then(() => { // then const firstCall = global.fetch.mock.calls[0]; const firstCallSecondParam = firstCall[1]; @@ -593,11 +587,10 @@ describe('httpMiddleware configuration', () => { const newAction = next.mock.calls[0][0]; expect(newAction.response.foo).toBe('bar'); document.cookie = ''; - done(); }); }); - it('should call interceptor at every levels', done => { + it('should call interceptor at every levels', () => { // given const response = { foo: 'bar' }; function json() { @@ -605,9 +598,9 @@ describe('httpMiddleware configuration', () => { } const store = { - dispatch: jest.fn(), + dispatch: vi.fn(), }; - const next = jest.fn(); + const next = vi.fn(); const action = { url: 'foo', type: HTTP_METHODS.POST, @@ -620,8 +613,8 @@ describe('httpMiddleware configuration', () => { }, }; const interceptor = { - request: jest.fn(config => config), - response: jest.fn(r => r), + request: vi.fn(config => config), + response: vi.fn(r => r), }; interceptors.push(interceptor); // when @@ -629,14 +622,13 @@ describe('httpMiddleware configuration', () => { const result = middleware(action); // then - result.then(() => { + return result.then(() => { expect(interceptor.request).toHaveBeenCalled(); const augmentedConfig = interceptor.request.mock.calls[0][0]; expect(augmentedConfig.url).toBe(action.url); expect(interceptor.response).toHaveBeenCalledWith({ data: response, headers: {} }); // eslint-disable-next-line no-underscore-dangle interceptors._clear(); - done(); }); }); }); diff --git a/packages/cmf/__tests__/onError.test.js b/packages/cmf/__tests__/onError.test.js index a0e5eec4114..c502d5cbd80 100644 --- a/packages/cmf/__tests__/onError.test.js +++ b/packages/cmf/__tests__/onError.test.js @@ -1,19 +1,20 @@ +import { vi } from 'vitest'; import onError from '../src/onError'; import { store as mock } from '../src/mock'; -window.addEventListener = jest.fn(); -window.removeEventListener = jest.fn(); +window.addEventListener = vi.fn(); +window.removeEventListener = vi.fn(); function activateSentry() { window.Sentry = { - captureException: jest.fn(), - configureScope: jest.fn(), - init: jest.fn(c => { + captureException: vi.fn(), + configureScope: vi.fn(), + init: vi.fn(c => { if (c.dsn === 'fail') { throw new Error('mock fail'); } }), - withScope: jest.fn(), + withScope: vi.fn(), }; } @@ -25,8 +26,8 @@ describe('onError', () => { state = { foo: { ok: 'should be kept', password: 'secret', keyUndefined: undefined } }; store = mock.store(state); window.addEventListener.mockClear(); - store.dispatch = jest.fn(); - console.error = jest.fn(); + store.dispatch = vi.fn(); + console.error = vi.fn(); config = { onError: { reportURL: '/api/v1/report', @@ -34,7 +35,7 @@ describe('onError', () => { }; process.env.NODE_ENV = 'production'; onError.bootstrap(config, store); - jest.clearAllMocks(); + vi.clearAllMocks(); }); afterEach(() => { @@ -73,7 +74,7 @@ describe('onError', () => { describe('middleware', () => { it('should let normal action happens', () => { const mid = onError.middleware(); - const next = jest.fn(); + const next = vi.fn(); const action = { type: 'TEST', }; @@ -99,7 +100,7 @@ describe('onError', () => { }); it('should add action in singleton', () => { const mid = onError.middleware(); - const next = jest.fn(); + const next = vi.fn(); const action = { type: 'FOO', sensitive: true, @@ -112,7 +113,7 @@ describe('onError', () => { it('should keep last 20 actions', () => { // eslint-disable-next-line no-plusplus const mid = onError.middleware(); - const next = jest.fn(); + const next = vi.fn(); for (let index = 0; index < 30; index++) { mid(next)({ type: `FOO ${index}`, password: 'secret' }); } @@ -123,7 +124,7 @@ describe('onError', () => { }); describe('createObjectURL', () => { it('should use window.URL.createObjectURL', () => { - window.URL.createObjectURL = jest.fn(); + window.URL.createObjectURL = vi.fn(); const error = new Error('sth bad 2'); onError.createObjectURL(error); expect(window.URL.createObjectURL).toHaveBeenCalled(); @@ -137,7 +138,7 @@ describe('onError', () => { describe('revokeObjectURL', () => { it('should use window.URL.revokeObjectURL', () => { const url = {}; - window.URL.revokeObjectURL = jest.fn(); + window.URL.revokeObjectURL = vi.fn(); onError.revokeObjectURL(url); expect(window.URL.revokeObjectURL).toHaveBeenCalledWith(url); }); @@ -162,7 +163,7 @@ describe('onError', () => { onError.bootstrap(config, store); const options = { tags: [{ key: 'tag', value: 'value' }] }; const error = new Error('foo'); - const setTag = jest.fn(); + const setTag = vi.fn(); onError.report(error, options); expect(window.Sentry.withScope).toHaveBeenCalled(); const onScope = window.Sentry.withScope.mock.calls[0][0]; diff --git a/packages/cmf/__tests__/onEvent.test.js b/packages/cmf/__tests__/onEvent.test.js index 3ef754b9612..bef6980c76e 100644 --- a/packages/cmf/__tests__/onEvent.test.js +++ b/packages/cmf/__tests__/onEvent.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import onEvent from '../src/onEvent'; describe('onEvent', () => { @@ -8,12 +9,12 @@ describe('onEvent', () => { beforeEach(() => { instance = { props: { - setState: jest.fn(), + setState: vi.fn(), state: { docked: false }, }, }; config = {}; - currentHandler = jest.fn(); + currentHandler = vi.fn(); }); it('should return a function', () => { const handler = onEvent.getOnEventSetStateHandler(instance, {}, config, currentHandler); diff --git a/packages/cmf/__tests__/reducers/componentsReducers.test.js b/packages/cmf/__tests__/reducers/componentsReducers.test.js index 3caf0b55d16..53af81f62e8 100644 --- a/packages/cmf/__tests__/reducers/componentsReducers.test.js +++ b/packages/cmf/__tests__/reducers/componentsReducers.test.js @@ -1,6 +1,7 @@ +import { vi } from 'vitest'; import reducer, { defaultState } from '../../src/reducers/componentsReducers'; -global.console = { warn: jest.fn() }; +global.console = { warn: vi.fn() }; describe('check component management reducer', () => { const initialState = { @@ -9,7 +10,7 @@ describe('check component management reducer', () => { }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it(`REACT_CMF.COMPONENT_ADD_STATE should properly add component/collection diff --git a/packages/cmf/__tests__/reducers/settingsReducers.test.js b/packages/cmf/__tests__/reducers/settingsReducers.test.js index 1113959ab90..751e781a9cb 100644 --- a/packages/cmf/__tests__/reducers/settingsReducers.test.js +++ b/packages/cmf/__tests__/reducers/settingsReducers.test.js @@ -1,9 +1,6 @@ +import { vi } from 'vitest'; import CONSTANT from '../../src/constant'; -import reducer, { - defaultState, - attachRef, - attachRefs, -} from '../../src/reducers/settingsReducers'; +import reducer, { defaultState, attachRef, attachRefs } from '../../src/reducers/settingsReducers'; /* eslint-disable no-console */ @@ -29,7 +26,7 @@ describe('settingsReducers.attachRef', () => { const shouldThrow = () => { attachRefs(ref, props); }; - expect(shouldThrow).toThrow(new Error('CMF/Settings: Reference \'myref\' not found')); + expect(shouldThrow).toThrow("CMF/Settings: Reference 'myref' not found"); }); it('should not do anything if obj parameter is not an object', () => { const testFunction = () => ''; @@ -44,9 +41,7 @@ describe('settingsReducers.attachRef', () => { it('should try to resolve _ref if obj is an object', () => { const ref = { stuff: 'res' }; - expect(attachRef(ref, { _ref: 'stuff' })).toEqual( - { 0: 'r', 1: 'e', 2: 's' } - ); + expect(attachRef(ref, { _ref: 'stuff' })).toEqual({ 0: 'r', 1: 'e', 2: 's' }); }); }); @@ -72,7 +67,7 @@ describe('CMF settinsReducers', () => { }); it('should understand REQUEST_KO on 404', () => { const oldError = console.error; - console.error = jest.fn(); + console.error = vi.fn(); const action = { type: CONSTANT.REQUEST_KO, error: { @@ -93,7 +88,7 @@ describe('CMF settinsReducers', () => { const state = reducer(undefined, action); expect(state).not.toBe(undefined); expect(state.initialized).toBe(false); - expect(console.error).toHaveBeenCalledWith('Settings can\'t be loaded Not Found', action.error); + expect(console.error).toHaveBeenCalledWith("Settings can't be loaded Not Found", action.error); console.error = oldError.bind(console); }); }); diff --git a/packages/cmf/__tests__/registry.test.js b/packages/cmf/__tests__/registry.test.js index 5d7969c217b..45918e7b258 100644 --- a/packages/cmf/__tests__/registry.test.js +++ b/packages/cmf/__tests__/registry.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; /* eslint no-underscore-dangle: ["error", {"allow": ["_registry", "_isLocked"] }] */ import registry from '../src/registry'; @@ -49,7 +50,7 @@ describe('CMF registry', () => { it('addToRegistry should warn that a registered item is overridden', () => { // given - console.warn = jest.fn(); + console.warn = vi.fn(); registry.addToRegistry('jso', 'value'); expect(console.warn).not.toHaveBeenCalled(); @@ -76,7 +77,7 @@ describe('CMF registry', () => { }); it('should test the registerMany function', () => { - const register = jest.fn(); + const register = vi.fn(); const registerMany = registry.getRegisterMany(register); const context = { registry: {} }; diff --git a/packages/cmf/__tests__/sagas/http.test.js b/packages/cmf/__tests__/sagas/http.test.js index 461ec5052a2..e59bf46969e 100644 --- a/packages/cmf/__tests__/sagas/http.test.js +++ b/packages/cmf/__tests__/sagas/http.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { Headers, Response } from 'node-fetch'; import { call, put } from 'redux-saga/effects'; @@ -31,7 +32,7 @@ import http, { const CSRFToken = 'hNjmdpuRgQClwZnb2c59F9gZhCi8jv9x'; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('http.get', () => { @@ -170,19 +171,18 @@ describe('http.delete', () => { }); describe('handleBody', () => { - it('should manage the body of the response like text if no header', done => { - handleBody(new Response('{"foo": 42}', {})).then(({ data, response }) => { + it('should manage the body of the response like text if no header', () => { + return handleBody(new Response('{"foo": 42}', {})).then(({ data, response }) => { expect(data).toBe('{"foo": 42}'); expect(response instanceof Response).toBeTruthy(); - done(); }); }); - it('should manage the body of the response like a json', done => { + it('should manage the body of the response like a json', () => { const headers = new Headers(); headers.append('Content-Type', 'application/json'); - handleBody( + return handleBody( new Response('{"foo": 42}', { headers, }), @@ -191,52 +191,48 @@ describe('handleBody', () => { foo: 42, }); expect(response instanceof Response).toBe(true); - done(); }); }); - it('should manage the body of the response like a blob', done => { + it('should manage the body of the response like a blob', () => { const headers = new Headers(); headers.append('Content-Type', 'application/zip'); - const blob = jest.fn(() => Promise.resolve()); + const blob = vi.fn(() => Promise.resolve()); - handleBody({ blob, headers }).then(() => { + return handleBody({ blob, headers }).then(() => { expect(blob).toHaveBeenCalled(); - done(); }); }); - it('should manage the body of the response like a text', done => { + it('should manage the body of the response like a text', () => { const headers = new Headers(); headers.append('Content-Type', 'text/plain'); - handleBody( + return handleBody( new Response('foo', { headers, }), ).then(({ data, response }) => { expect(data).toBe('foo'); expect(response instanceof Response).toBe(true); - done(); }); }); - it('should manage the body of the response like a text by default', done => { - handleBody(new Response('')).then(({ data, response }) => { + it('should manage the body of the response like a text by default', () => { + return handleBody(new Response('')).then(({ data, response }) => { expect(data).toBe(''); expect(response instanceof Response).toBe(true); - done(); }); }); }); describe('#handleHttpResponse', () => { - it('should handle the response with 2xx code', done => { + it('should handle the response with 2xx code', () => { const headers = new Headers(); headers.append('Content-Type', 'application/json'); - handleHttpResponse( + return handleHttpResponse( new Response('{"foo": 42}', { status: HTTP_STATUS.OK, headers, @@ -246,39 +242,36 @@ describe('#handleHttpResponse', () => { foo: 42, }); expect(response instanceof Response).toBe(true); - done(); }); }); - it('should handle the response with a code different of 2xx', done => { + it('should handle the response with a code different of 2xx', () => { const headers = new Headers(); headers.append('Content-Type', 'application/json'); - handleHttpResponse( + return handleHttpResponse( new Response('{"foo": 42}', { status: HTTP_STATUS.FORBIDDEN, headers, }), ).catch(response => { expect(response instanceof Response).toBe(true); - done(); }); }); - it('should handle the response with NO_CONTENT code', done => { - handleHttpResponse( + it('should handle the response with NO_CONTENT code', () => { + return handleHttpResponse( new Response('', { status: HTTP_STATUS.NO_CONTENT, }), ).then(({ data, response }) => { expect(data).toBe(''); expect(response instanceof Response).toBe(true); - done(); }); }); - it('should handle the response for HEAD requests', done => { - handleHttpResponse( + it('should handle the response for HEAD requests', () => { + return handleHttpResponse( new Response('{"foo": 42}', { status: HTTP_STATUS.OK, }), @@ -286,17 +279,16 @@ describe('#handleHttpResponse', () => { ).then(({ data, response }) => { expect(data).toBe(''); expect(response instanceof Response).toBe(true); - done(); }); }); }); describe('#handleError', () => { - it('should manage the error', done => { + it('should manage the error', () => { const headers = new Headers(); headers.append('Content-Type', 'application/json'); - handleError( + return handleError( new Response('{"foo": 42}', { status: HTTP_STATUS.FORBIDDEN, statusText: 'Forbidden', @@ -308,7 +300,6 @@ describe('#handleError', () => { foo: 42, }); expect(error.response instanceof Response).toBe(true); - done(); }); }); }); @@ -872,6 +863,9 @@ it('should wrap the request and not notify with generic http error if silent opt }); describe('#httpFetch with CRSF token', () => { + beforeEach(() => { + global.fetch = vi.fn((url, config) => Promise.resolve(config.response)); + }); beforeAll(() => { document.cookie = `csrfToken=${CSRFToken}; dwf_section_edit=True;`; }); @@ -879,7 +873,7 @@ describe('#httpFetch with CRSF token', () => { afterAll(() => { document.cookie = `csrfToken=${CSRFToken}; dwf_section_edit=True; Max-Age=0`; }); - it('should get the CRFS token', done => { + it('should get the CRFS token', () => { const url = '/foo'; const headers = new Headers(); headers.append('Content-Type', 'application/json'); @@ -894,13 +888,7 @@ describe('#httpFetch with CRSF token', () => { bar: 42, }; - httpFetch(url, config, HTTP_METHODS.GET, payload).then(body => { - expect(body.data).toEqual({ - foo: 42, - }); - expect(body.response instanceof Response).toBe(true); - done(); - }); + const promise = httpFetch(url, config, HTTP_METHODS.GET, payload); expect(fetch).toHaveBeenCalledWith(url, { body: '{"bar":42}', @@ -913,6 +901,13 @@ describe('#httpFetch with CRSF token', () => { method: HTTP_METHODS.GET, response: config.response, }); + + return promise.then(body => { + expect(body.data).toEqual({ + foo: 42, + }); + expect(body.response instanceof Response).toBe(true); + }); }); }); @@ -924,6 +919,9 @@ describe('#httpFetch with CSRF handling configuration', () => { }, }; + beforeEach(() => { + global.fetch = vi.fn((url, config) => Promise.resolve(config.response)); + }); beforeAll(() => { HTTP.defaultConfig = null; @@ -936,7 +934,7 @@ describe('#httpFetch with CSRF handling configuration', () => { document.cookie = `${defaultHttpConfiguration.security.CSRFTokenCookieKey}=${CSRFToken}; dwf_section_edit=True; Max-Age=0`; }); - it('check if httpFetch is called with the security configuration', done => { + it('check if httpFetch is called with the security configuration', () => { setDefaultConfig(defaultHttpConfiguration); expect(getDefaultConfig()).toEqual(defaultHttpConfiguration); @@ -954,13 +952,7 @@ describe('#httpFetch with CSRF handling configuration', () => { bar: 42, }; - httpFetch(url, config, HTTP_METHODS.GET, payload).then(body => { - expect(body.data).toEqual({ - foo: 42, - }); - expect(body.response instanceof Response).toBe(true); - done(); - }); + const promise = httpFetch(url, config, HTTP_METHODS.GET, payload); expect(fetch).toHaveBeenCalledWith(url, { ...defaultHttpConfiguration, @@ -974,15 +966,25 @@ describe('#httpFetch with CSRF handling configuration', () => { method: HTTP_METHODS.GET, response: config.response, }); + + return promise.then(body => { + expect(body.data).toEqual({ + foo: 42, + }); + expect(body.response instanceof Response).toBe(true); + }); }); }); describe('#httpFetch', () => { + beforeEach(() => { + global.fetch = vi.fn((url, config) => Promise.resolve(config.response)); + }); afterEach(() => { HTTP.defaultConfig = null; }); - it('should fetch the request', done => { + it('should fetch the request', () => { const url = '/foo'; const headers = new Headers(); headers.append('Content-Type', 'application/json'); @@ -997,13 +999,7 @@ describe('#httpFetch', () => { bar: 42, }; - httpFetch(url, config, HTTP_METHODS.GET, payload).then(body => { - expect(body.data).toEqual({ - foo: 42, - }); - expect(body.response instanceof Response).toBe(true); - done(); - }); + const promise = httpFetch(url, config, HTTP_METHODS.GET, payload); expect(fetch).toHaveBeenCalledWith(url, { body: '{"bar":42}', @@ -1015,9 +1011,16 @@ describe('#httpFetch', () => { method: HTTP_METHODS.GET, response: config.response, }); + + return promise.then(body => { + expect(body.data).toEqual({ + foo: 42, + }); + expect(body.response instanceof Response).toBe(true); + }); }); - it('should fetch the request with the default settings', done => { + it('should fetch the request with the default settings', () => { const url = '/foo'; const headers = new Headers(); headers.append('Content-Type', 'application/json'); @@ -1038,13 +1041,7 @@ describe('#httpFetch', () => { }, }); - httpFetch(url, config, HTTP_METHODS.GET, payload).then(body => { - expect(body.data).toEqual({ - foo: 42, - }); - expect(body.response instanceof Response).toBe(true); - done(); - }); + const promise = httpFetch(url, config, HTTP_METHODS.GET, payload); expect(fetch).toHaveBeenCalledWith(url, { body: '{"bar":42}', @@ -1057,9 +1054,16 @@ describe('#httpFetch', () => { method: HTTP_METHODS.GET, response: config.response, }); + + return promise.then(body => { + expect(body.data).toEqual({ + foo: 42, + }); + expect(body.response instanceof Response).toBe(true); + }); }); - it('should fetch the request with a FormData', done => { + it('should fetch the request with a FormData', () => { const url = '/foo'; const headers = new Headers(); headers.append('Content-Type', 'application/json'); @@ -1072,13 +1076,7 @@ describe('#httpFetch', () => { }; const payload = new FormData(); - httpFetch(url, config, HTTP_METHODS.GET, payload).then(body => { - expect(body.data).toEqual({ - foo: 42, - }); - expect(body.response instanceof Response).toBe(true); - done(); - }); + const promise = httpFetch(url, config, HTTP_METHODS.GET, payload); expect(fetch).toHaveBeenCalledWith(url, { body: payload, @@ -1089,9 +1087,16 @@ describe('#httpFetch', () => { method: HTTP_METHODS.GET, response: config.response, }); + + return promise.then(body => { + expect(body.data).toEqual({ + foo: 42, + }); + expect(body.response instanceof Response).toBe(true); + }); }); - it('should fail the request', done => { + it('should fail the request', () => { const url = '/foo'; const headers = new Headers(); headers.append('Content-Type', 'application/json'); @@ -1106,14 +1111,7 @@ describe('#httpFetch', () => { bar: 42, }; - httpFetch(url, config, HTTP_METHODS.GET, payload).then(body => { - expect(body instanceof Error).toBe(true); - expect(body.data).toEqual({ - foo: 42, - }); - expect(body.response instanceof Response).toBe(true); - done(); - }); + const promise = httpFetch(url, config, HTTP_METHODS.GET, payload); expect(fetch).toHaveBeenCalledWith(url, { body: '{"bar":42}', @@ -1125,6 +1123,14 @@ describe('#httpFetch', () => { method: HTTP_METHODS.GET, response: config.response, }); + + return promise.then(body => { + expect(body instanceof Error).toBe(true); + expect(body.data).toEqual({ + foo: 42, + }); + expect(body.response instanceof Response).toBe(true); + }); }); }); @@ -1321,7 +1327,7 @@ describe('setDefaultLanguage', () => { it('should not redefine the Accept Language if no defaultConfig', () => { expect(() => { setDefaultLanguage('ja'); - }).toThrow(''); + }).toThrow(); }); it('should redefine the Accept Language', () => { diff --git a/packages/cmf/__tests__/sagas/putActionCreator.test.js b/packages/cmf/__tests__/sagas/putActionCreator.test.js index 92e3bbf53cd..08ee6c46415 100644 --- a/packages/cmf/__tests__/sagas/putActionCreator.test.js +++ b/packages/cmf/__tests__/sagas/putActionCreator.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { put, select } from 'redux-saga/effects'; import registry from '../../src/registry'; import putActionCreator from '../../src/sagas/putActionCreator'; @@ -6,7 +7,7 @@ describe('saga', () => { it('should putActionCreator call put of a registred actionCreator without context', () => { // given const testAction = { type: 'TEST' }; - const actionCreator = jest.fn(() => testAction); + const actionCreator = vi.fn(() => testAction); const reg = registry.getRegistry(); reg['actionCreator:myActionCreator'] = actionCreator; const data = { foo: 'bar' }; @@ -31,7 +32,7 @@ describe('saga', () => { it('should putActionCreator call put of a registred actionCreator or using context', () => { // given const testAction = { type: 'TEST' }; - const actionCreator = jest.fn(() => testAction); + const actionCreator = vi.fn(() => testAction); const context = { registry: { 'actionCreator:myActionCreator': actionCreator, diff --git a/packages/cmf/__tests__/selectors/toJS.test.js b/packages/cmf/__tests__/selectors/toJS.test.js index 2c36d325238..902c154be90 100644 --- a/packages/cmf/__tests__/selectors/toJS.test.js +++ b/packages/cmf/__tests__/selectors/toJS.test.js @@ -1,9 +1,10 @@ +import { vi } from 'vitest'; import toJS from '../../src/selectors/toJS'; describe('toJS', () => { let selector; beforeEach(() => { - selector = jest.fn(state => state.foo); + selector = vi.fn(state => state.foo); }); it('should return a function', () => { expect(typeof toJS(selector)).toBe('function'); diff --git a/packages/cmf/__tests__/store.test.js b/packages/cmf/__tests__/store.test.js index 6e32f4c2939..6747dfdfaa2 100644 --- a/packages/cmf/__tests__/store.test.js +++ b/packages/cmf/__tests__/store.test.js @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import store from '../src/store'; describe('CMF store', () => { @@ -43,7 +44,7 @@ describe('CMF store', () => { describe('addPreReducer', () => { it('should add a reducer called by the cmf reducer', () => { - const myreducer = jest.fn(); + const myreducer = vi.fn(); store.addPreReducer(myreducer); const reducer = store.getReducer(); reducer(undefined, {}); @@ -57,13 +58,13 @@ describe('getMiddlewares', () => { expect(Array.isArray(middlewares)).toBe(true); }); it('should support first attr as function', () => { - const fn = jest.fn(); + const fn = vi.fn(); const middlewares = store.getMiddlewares(fn); expect(middlewares).toContain(fn); }); it('should support first attr as array', () => { - const fn1 = jest.fn(); - const fn2 = jest.fn(); + const fn1 = vi.fn(); + const fn2 = vi.fn(); const middlewares = store.getMiddlewares([fn1, fn2]); expect(middlewares).toContain(fn1); expect(middlewares).toContain(fn2); diff --git a/packages/cmf/src/cmfConnect.jsx b/packages/cmf/src/cmfConnect.jsx index 37dd836942f..7bb3bd51d24 100644 --- a/packages/cmf/src/cmfConnect.jsx +++ b/packages/cmf/src/cmfConnect.jsx @@ -305,7 +305,7 @@ export default function cmfConnect({ } let spreadedState = {}; if ((spreadCMFState || props.spreadCMFState) && props.state) { - spreadedState = props.state.toJS(); + spreadedState = { ...props.state }; } const newProps = { diff --git a/packages/cmf/src/test-setup.ts b/packages/cmf/src/test-setup.ts index 5ef1d222349..020160b3a1e 100644 --- a/packages/cmf/src/test-setup.ts +++ b/packages/cmf/src/test-setup.ts @@ -8,3 +8,14 @@ vi.mock('@talend/utils', async () => { randomUUID: () => '42', }; }); + +// Node v25+ declares `localStorage` on globalThis, which prevents vitest/jsdom from +// overriding it with jsdom's Storage. Use the JSDOM instance directly to fix this. +// @ts-expect-error - jsdom is set by vitest's jsdom environment +const jsdomLocalStorage = globalThis.jsdom?.window?.localStorage; +if (jsdomLocalStorage) { + Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + value: jsdomLocalStorage, + }); +} diff --git a/packages/cmf/vitest.config.ts b/packages/cmf/vitest.config.ts index fa9bc4d0176..a4e455ba00b 100644 --- a/packages/cmf/vitest.config.ts +++ b/packages/cmf/vitest.config.ts @@ -5,7 +5,8 @@ export default defineConfig({ plugins: [react({ include: /\.[jt]sx?$/ })], esbuild: { loader: 'tsx', - include: /src\/.*\.[jt]sx?$/, + include: /(src|__tests__)\/.*\.[jt]sx?$/, + exclude: /node_modules/, jsx: 'automatic', tsconfigRaw: { compilerOptions: { @@ -16,8 +17,13 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', + environmentOptions: { + jsdom: { + url: 'http://localhost', + }, + }, setupFiles: ['src/test-setup.ts'], - include: ['src/**/*.test.{js,jsx,ts,tsx}'], + include: ['src/**/*.test.{js,jsx,ts,tsx}', '__tests__/**/*.test.{js,jsx,ts,tsx}'], exclude: ['lib/**', 'lib-esm/**'], coverage: { provider: 'v8', diff --git a/packages/playground-vite/.gitignore b/packages/playground-vite/.gitignore index d44b5d89dee..c909f8744d0 100644 --- a/packages/playground-vite/.gitignore +++ b/packages/playground-vite/.gitignore @@ -1,3 +1,4 @@ dist package-lock.json .cache-loader/ +mockVite/_server.bundled_*.mjs From 7b8fa58e9660efa0c2594edecc8ec2c47c794319 Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 12 Mar 2026 12:02:48 +0100 Subject: [PATCH 09/16] fix more test --- packages/cmf/__tests__/bootstrap.test.jsx | 18 +++--- packages/cmf/__tests__/cmfConnect.test.jsx | 65 +++++++++++----------- packages/components/src/test-setup.js | 10 ++++ packages/containers/src/test-setup.js | 10 ++++ packages/faceted-search/jest.setup.js | 13 ----- packages/faceted-search/test-setup.js | 23 ++++++++ packages/faceted-search/vitest.config.ts | 2 +- packages/playground-vite/.gitignore | 2 +- 8 files changed, 87 insertions(+), 56 deletions(-) delete mode 100644 packages/faceted-search/jest.setup.js create mode 100644 packages/faceted-search/test-setup.js diff --git a/packages/cmf/__tests__/bootstrap.test.jsx b/packages/cmf/__tests__/bootstrap.test.jsx index a2f80d8f0a8..6943fabcb91 100644 --- a/packages/cmf/__tests__/bootstrap.test.jsx +++ b/packages/cmf/__tests__/bootstrap.test.jsx @@ -81,7 +81,7 @@ vi.mock('../src/store', () => ({ describe('bootstrap', () => { beforeEach(() => { onError.bootstrap.mockClear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('error management', () => { it('should bootstrap onError', async () => { @@ -114,7 +114,7 @@ describe('bootstrap', () => { registry.registerMany.mockClear(); const options = { registry: { - foo: jest.fn(), + foo: vi.fn(), }, }; await bootstrap(options); @@ -124,7 +124,7 @@ describe('bootstrap', () => { component.registerMany.mockClear(); const options = { components: { - foo: jest.fn(), + foo: vi.fn(), }, }; await bootstrap(options); @@ -134,7 +134,7 @@ describe('bootstrap', () => { expression.registerMany.mockClear(); const options = { expressions: { - foo: jest.fn(), + foo: vi.fn(), }, }; await bootstrap(options); @@ -144,7 +144,7 @@ describe('bootstrap', () => { actionCreator.registerMany.mockClear(); const options = { actionCreators: { - foo: jest.fn(), + foo: vi.fn(), }, }; await bootstrap(options); @@ -154,7 +154,7 @@ describe('bootstrap', () => { sagas.registerMany.mockClear(); const options = { sagas: { - foo: jest.fn(), + foo: vi.fn(), }, }; await bootstrap(options); @@ -173,14 +173,14 @@ describe('bootstrap', () => { describe('redux', () => { it('should call storeAPI.addPreReducer if options.preReducer', async () => { const options = { - preReducer: jest.fn(), + preReducer: vi.fn(), }; await bootstrap(options); expect(storeAPI.addPreReducer).toHaveBeenCalledWith(options.preReducer); }); it('should call storeAPI.setHttpMiddleware if options.preReducer', async () => { const options = { - httpMiddleware: jest.fn(), + httpMiddleware: vi.fn(), }; await bootstrap(options); expect(storeAPI.setHttpMiddleware).toHaveBeenCalledWith(options.httpMiddleware); @@ -189,7 +189,7 @@ describe('bootstrap', () => { storeAPI.initialize.mockClear(); createSagaMiddleware.mockClear(); const options = { - reducer: { app: jest.fn() }, + reducer: { app: vi.fn() }, preloadedState: {}, middlewares: [], }; diff --git a/packages/cmf/__tests__/cmfConnect.test.jsx b/packages/cmf/__tests__/cmfConnect.test.jsx index 73eebbcae4d..d366f690359 100644 --- a/packages/cmf/__tests__/cmfConnect.test.jsx +++ b/packages/cmf/__tests__/cmfConnect.test.jsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; /** * @jest-environment jsdom */ @@ -129,7 +130,7 @@ describe('cmfConnect', () => { }); it('should pass view settings together with own props when calling mapStateToProps', () => { const state = mock.store.state(); - const mapStateToProps = jest.fn(); + const mapStateToProps = vi.fn(); const ownProps = { view: 'simple' }; getStateToProps({ state, @@ -160,7 +161,7 @@ describe('cmfConnect', () => { const stateProps = { id: 'stateProps', stateProps: true }; const dispatchProps = { id: 'dispatchProps', dispatchProps: true }; const ownProps = { id: 'ownProps', ownProps: true }; - const mergeProps = jest.fn(); + const mergeProps = vi.fn(); getMergeProps({ mergeProps, stateProps, @@ -173,8 +174,8 @@ describe('cmfConnect', () => { describe('#getDispatchToProps', () => { it('should call getStateAccessors', () => { - const dispatch = jest.fn(); - const mapDispatchToProps = jest.fn(); + const dispatch = vi.fn(); + const mapDispatchToProps = vi.fn(); const ownProps = {}; const props = getDispatchToProps({ componentId: 'testId', @@ -212,7 +213,7 @@ describe('cmfConnect', () => { Button.displayName = 'Button'; const CMFConnectedButton = cmfConnect({})(Button); it('should create a connected component even without params', () => { - const TestComponent = jest.fn(); + const TestComponent = vi.fn(); TestComponent.displayName = 'TestComponent'; mapStateToViewProps.cache.clear(); const CMFConnected = cmfConnect()(TestComponent); @@ -221,7 +222,7 @@ describe('cmfConnect', () => { }); it('should create a connected component', () => { - const TestComponent = jest.fn(props =>
); + const TestComponent = vi.fn(props =>
); TestComponent.displayName = 'TestComponent'; mapStateToViewProps.cache.clear(); const CMFConnected = cmfConnect({})(TestComponent); @@ -326,8 +327,8 @@ describe('cmfConnect', () => { withDispatchActionCreator: true, })(TestComponent); const props = { - dispatchActionCreator: jest.fn(), - deleteState: jest.fn(), + dispatchActionCreator: vi.fn(), + deleteState: vi.fn(), }; const context = mock.store.context(); render( @@ -363,15 +364,15 @@ describe('cmfConnect', () => { }); it('should componentDidMount initState and dispatchActionCreator after the saga', () => { - const TestComponent = jest.fn(() => null); + const TestComponent = vi.fn(() => null); TestComponent.displayName = 'TestComponent'; const STATE = {}; const CMFConnected = cmfConnect({})(TestComponent); const props = { didMountActionCreator: 'hello', - deleteState: jest.fn(), - dispatchActionCreator: jest.fn(), - initState: jest.fn(), + deleteState: vi.fn(), + dispatchActionCreator: vi.fn(), + initState: vi.fn(), initialState: STATE, foo: 'bar', saga: 'saga', @@ -401,13 +402,13 @@ describe('cmfConnect', () => { }); it('should componentDidMount support saga', () => { - const TestComponent = jest.fn(() => null); + const TestComponent = vi.fn(() => null); TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const props = { saga: 'hello', - dispatchActionCreator: jest.fn(), - deleteState: jest.fn(), + dispatchActionCreator: vi.fn(), + deleteState: vi.fn(), }; const context = mock.store.context(); render( @@ -430,13 +431,13 @@ describe('cmfConnect', () => { }); it('should componentWillUnmount support saga', () => { - const TestComponent = jest.fn(() => null); + const TestComponent = vi.fn(() => null); TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const props = { saga: 'hello', - dispatchActionCreator: jest.fn(), - deleteState: jest.fn(), + dispatchActionCreator: vi.fn(), + deleteState: vi.fn(), }; const context = mock.store.context(); const { unmount } = render( @@ -457,18 +458,18 @@ describe('cmfConnect', () => { }); it('should componentWillUnMount dispatchActionCreator', () => { - const TestComponent = jest.fn(() => null); + const TestComponent = vi.fn(() => null); TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const props = { willUnmountActionCreator: 'bye', - dispatchActionCreator: jest.fn(), - deleteState: jest.fn(), + dispatchActionCreator: vi.fn(), + deleteState: vi.fn(), foo: 'bar', }; const context = mock.store.context(); context.registry = { - 'actionCreator:bye': jest.fn(), + 'actionCreator:bye': vi.fn(), }; const { unmount } = render( @@ -498,7 +499,7 @@ describe('cmfConnect', () => { expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); const { unmount } = render( @@ -525,7 +526,7 @@ describe('cmfConnect', () => { expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); const { unmount } = render( @@ -551,7 +552,7 @@ describe('cmfConnect', () => { expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); const { unmount } = render( @@ -577,7 +578,7 @@ describe('cmfConnect', () => { expect(CMFConnected.displayName).toBe('Connect(CMF(TestComponent))'); expect(CMFConnected.WrappedComponent).toBe(TestComponent); const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); const { unmount } = render( @@ -606,7 +607,7 @@ describe('cmfConnect', () => { TestComponent.displayName = 'TestComponent'; const CMFConnected = cmfConnect({})(TestComponent); const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); render( @@ -648,7 +649,7 @@ describe('cmfConnect', () => { type: 'MY_BUTTON_CLICKED', }; const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); render( @@ -666,7 +667,7 @@ describe('cmfConnect', () => { it('should transform onEventActionCreator props to onEvent handler', () => { const onClickActionCreator = 'myactionCreator'; const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); context.registry = { 'actionCreator:myactionCreator': event => ({ type: 'FETCH_STUFF', event }), }; @@ -693,7 +694,7 @@ describe('cmfConnect', () => { }, }; const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); context.registry = { 'actionCreator:myfetch': (event, data) => ({ type: 'FETCH_CONFIGURED', @@ -721,7 +722,7 @@ describe('cmfConnect', () => { disabled: true, }; const context = mock.store.context(); - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); render( @@ -762,7 +763,7 @@ describe('cmfConnect', () => { }, }; }; - context.store.dispatch = jest.fn(); + context.store.dispatch = vi.fn(); render( diff --git a/packages/components/src/test-setup.js b/packages/components/src/test-setup.js index 330c81b7444..3051abaa1c6 100644 --- a/packages/components/src/test-setup.js +++ b/packages/components/src/test-setup.js @@ -21,6 +21,16 @@ vi.mock('@talend/utils', async () => { globalThis.jest = vi; globalThis.xit = it.skip; +// Node v25+ declares `localStorage` on globalThis, which prevents vitest/jsdom from +// overriding it with jsdom's Storage. Use the JSDOM instance directly to fix this. +const jsdomLocalStorage = globalThis.jsdom?.window?.localStorage; +if (jsdomLocalStorage) { + Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + value: jsdomLocalStorage, + }); +} + // Suppress React warnings in tests, as they are not relevant to the test results and can clutter the output. const originalConsoleError = console.error; vi.spyOn(console, 'error').mockImplementation((...args) => { diff --git a/packages/containers/src/test-setup.js b/packages/containers/src/test-setup.js index d0e25dbdd65..cfb462d31f0 100644 --- a/packages/containers/src/test-setup.js +++ b/packages/containers/src/test-setup.js @@ -11,3 +11,13 @@ vi.mock('@talend/utils', async () => { // Keep existing tests functional while migrating from Jest to Vitest. globalThis.jest = vi; globalThis.xit = it.skip; + +// Node v25+ declares `localStorage` on globalThis, which prevents vitest/jsdom from +// overriding it with jsdom's Storage. Use the JSDOM instance directly to fix this. +const jsdomLocalStorage = globalThis.jsdom?.window?.localStorage; +if (jsdomLocalStorage) { + Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + value: jsdomLocalStorage, + }); +} diff --git a/packages/faceted-search/jest.setup.js b/packages/faceted-search/jest.setup.js deleted file mode 100644 index dcf681f6b5b..00000000000 --- a/packages/faceted-search/jest.setup.js +++ /dev/null @@ -1,13 +0,0 @@ -import '@testing-library/jest-dom/vitest'; - -// Keep existing test code working without touching every `jest.fn()` call. -globalThis.jest = vi; - -vi.mock('@talend/utils', async () => { - let i = 0; - const actual = await vi.importActual('@talend/utils'); - return { - ...actual, - randomUUID: () => `mocked-uuid-${i++}`, - }; -}); diff --git a/packages/faceted-search/test-setup.js b/packages/faceted-search/test-setup.js new file mode 100644 index 00000000000..8eed115050e --- /dev/null +++ b/packages/faceted-search/test-setup.js @@ -0,0 +1,23 @@ +import '@testing-library/jest-dom/vitest'; + +// Keep existing test code working without touching every `jest.fn()` call. +globalThis.jest = vi; + +// Node v25+ declares `localStorage` on globalThis, which prevents vitest/jsdom from +// overriding it with jsdom's Storage. Use the JSDOM instance directly to fix this. +const jsdomLocalStorage = globalThis.jsdom?.window?.localStorage; +if (jsdomLocalStorage) { + Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + value: jsdomLocalStorage, + }); +} + +vi.mock('@talend/utils', async () => { + let i = 0; + const actual = await vi.importActual('@talend/utils'); + return { + ...actual, + randomUUID: () => `mocked-uuid-${i++}`, + }; +}); diff --git a/packages/faceted-search/vitest.config.ts b/packages/faceted-search/vitest.config.ts index 7a2b1a42335..a75f582e99d 100644 --- a/packages/faceted-search/vitest.config.ts +++ b/packages/faceted-search/vitest.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ env: { TZ: 'UTC', }, - setupFiles: ['jest.setup.js'], + setupFiles: ['test-setup.js'], include: ['src/**/*.test.{js,jsx,ts,tsx}'], exclude: ['lib/**', 'lib-esm/**'], coverage: { diff --git a/packages/playground-vite/.gitignore b/packages/playground-vite/.gitignore index c909f8744d0..3f57154fda9 100644 --- a/packages/playground-vite/.gitignore +++ b/packages/playground-vite/.gitignore @@ -1,4 +1,4 @@ dist package-lock.json .cache-loader/ -mockVite/_server.bundled_*.mjs +mockVite/**/_*.bundled_*.mjs From 0e5b45dea792911dcca9f7e35b483b5bb9eec5cb Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 12 Mar 2026 13:57:24 +0100 Subject: [PATCH 10/16] add migration guide --- .../migration-guide-remove-immutable.md | 53 +++++ ...gration-guide-remove-immutable-cmf-cqrs.md | 103 +++++++++ .../migration-guide-remove-immutable-cmf.md | 209 ++++++++++++++++++ ...ation-guide-remove-immutable-components.md | 85 +++++++ ...ation-guide-remove-immutable-containers.md | 169 ++++++++++++++ ...on-guide-remove-immutable-flow-designer.md | 88 ++++++++ .../migration-guide-remove-immutable-sagas.md | 104 +++++++++ ...igration-guide-remove-immutable-stepper.md | 17 ++ 8 files changed, 828 insertions(+) create mode 100644 docs/migration-guides/migration-guide-remove-immutable.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-components.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-containers.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-sagas.md create mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-stepper.md diff --git a/docs/migration-guides/migration-guide-remove-immutable.md b/docs/migration-guides/migration-guide-remove-immutable.md new file mode 100644 index 00000000000..cb720828bbd --- /dev/null +++ b/docs/migration-guides/migration-guide-remove-immutable.md @@ -0,0 +1,53 @@ +# Migration Guide: Removal of ImmutableJS + +This is the index for per-package migration guides covering the breaking changes introduced by the removal of [ImmutableJS](https://immutable-js.com/) from the Talend UI monorepo. + +## Affected packages + +| Package | Version bump | Guide | +| ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `@talend/react-cmf` | **MAJOR** | [migration-guide-remove-immutable-cmf.md](./remove-immutable/migration-guide-remove-immutable-cmf.md) | +| `@talend/react-containers` | **MAJOR** | [migration-guide-remove-immutable-containers.md](./remove-immutable/migration-guide-remove-immutable-containers.md) | +| `@talend/react-cmf-cqrs` | **MAJOR** | [migration-guide-remove-immutable-cmf-cqrs.md](./remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md) | +| `@talend/react-sagas` | **MAJOR** | [migration-guide-remove-immutable-sagas.md](./remove-immutable/migration-guide-remove-immutable-sagas.md) | +| `@talend/react-components` | **MAJOR** | [migration-guide-remove-immutable-components.md](./remove-immutable/migration-guide-remove-immutable-components.md) | +| `@talend/react-flow-designer` | **MAJOR** | [migration-guide-remove-immutable-flow-designer.md](./remove-immutable/migration-guide-remove-immutable-flow-designer.md) | +| `@talend/react-stepper` | patch | [migration-guide-remove-immutable-stepper.md](./remove-immutable/migration-guide-remove-immutable-stepper.md) | + +--- + +## Quick reference: ImmutableJS → Plain JS + +| ImmutableJS | Plain JS | +| ------------------------ | ---------------------------------------------------------- | +| `map.get(key)` | `obj[key]` | +| `map.getIn([a, b, c])` | `lodash.get(obj, [a, b, c])` | +| `map.set(key, val)` | `{ ...obj, [key]: val }` | +| `map.setIn([a, b], val)` | `produce(obj, d => set(d, [a, b], val))` (immer + lodash) | +| `map.merge(other)` | `{ ...obj, ...other }` | +| `map.delete(key)` | `const { [key]: _, ...rest } = obj; return rest;` | +| `map.has(key)` | `key in obj` | +| `list.push(item)` | `[...arr, item]` | +| `list.filter(fn)` | `arr.filter(fn)` | +| `list.filterNot(fn)` | `arr.filter(item => !fn(item))` | +| `list.find(fn)` | `arr.find(fn)` | +| `list.includes(val)` | `arr.includes(val)` | +| `Map.isMap(x)` | `typeof x === 'object' && x !== null && !Array.isArray(x)` | +| `List.isList(x)` | `Array.isArray(x)` | +| `Iterable.isIterable(x)` | N/A — remove the guard | +| `fromJS(obj)` | `obj` (already plain) | +| `x.toJS()` | `x` (already plain) | +| `x.size` | `Array.isArray(x) ? x.length : Object.keys(x).length` | + +## Useful grep patterns + +```bash +# Find all Immutable imports +grep -r "from 'immutable'" src/ --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" + +# Find react-immutable-proptypes usage +grep -r "react-immutable-proptypes" src/ --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" + +# Find residual Immutable method calls +grep -rE "\.(getIn|setIn|toJS|fromJS|isMap|isList|isIterable)\(" src/ +``` diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md new file mode 100644 index 00000000000..3d05be79692 --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md @@ -0,0 +1,103 @@ +# Migration Guide: `@talend/react-cmf-cqrs` — Removal of ImmutableJS + +**Version bump**: MAJOR + +--- + +## What changed + +`state.ack` is now a plain JavaScript object. The Immutable methods previously used to read/write acknowledgment state have been replaced with standard object operations. + +Removed dependency: `immutable`. + +--- + +## Breaking changes + +### 1. `state.ack` is now a plain object + +The ACK reducer now stores state as `{ [requestId]: { data, actionCreator, received } }` — a plain JS object keyed by `requestId`. + +**Before** + +```js +// state.ack was an Immutable.Map +const ackEntry = state.ack.getIn([requestId, 'data']); +const received = state.ack.getIn([requestId, 'received']); +const actionCreator = state.ack.getIn([requestId, 'actionCreator']); + +// checking if an entry exists +const exists = state.ack.has(requestId); +``` + +**After** + +```js +// state.ack is now a plain object: { [requestId]: { data, actionCreator, received } } +const ackEntry = state.ack[requestId]?.data; +const received = state.ack[requestId]?.received; +const actionCreator = state.ack[requestId]?.actionCreator; + +// checking if an entry exists +const exists = requestId in state.ack; +``` + +--- + +### 2. `ACKDispatcher` — `acks` prop is a plain object + +The `ACKDispatcher` component now iterates over `props.acks` using `Object.entries()`. Previously it was iterated as an Immutable-style iterable. + +If you use `ACKDispatcher` through `cmfConnect` (the standard usage), this is handled automatically — `state.ack` is now a plain object and is passed as `acks` directly. **No action required** for this pattern. + +If you pass `acks` **manually as a prop**, it must be a plain object: + +```js +// Before — custom acks prop (Immutable) +import { Map } from 'immutable'; + + +// After — custom acks prop (plain object) + +``` + +--- + +### 3. Test fixtures + +Tests that build mock ACK state with Immutable must be updated: + +```js +// Before +import Immutable from 'immutable'; +const state = { + ack: Immutable.fromJS({ + 'req-123': { received: false, data: null, actionCreator: 'dataset:fetch' }, + }), +}; + +// After +const state = { + ack: { + 'req-123': { received: false, data: null, actionCreator: 'dataset:fetch' }, + }, +}; +``` + +--- + +## Checklist + +- [ ] Replace `state.ack.getIn([requestId, 'key'])` → `state.ack[requestId]?.key` +- [ ] Replace `state.ack.has(requestId)` → `requestId in state.ack` +- [ ] Ensure manually-passed `acks` props are plain objects +- [ ] Update test fixtures to use plain objects instead of `Immutable.fromJS({...})` +- [ ] Remove `immutable` from your own `package.json` if transitively relied on diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf.md new file mode 100644 index 00000000000..4d5debb8d5f --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-cmf.md @@ -0,0 +1,209 @@ +# Migration Guide: `@talend/react-cmf` — Removal of ImmutableJS + +**Version bump**: MAJOR + +--- + +## What changed + +The CMF Redux store no longer uses ImmutableJS. Both `state.cmf.collections` and `state.cmf.components` are now plain JavaScript objects. + +Removed published dependencies: `immutable`, `react-immutable-proptypes`. + +--- + +## Breaking changes + +### 1. `state.cmf.collections` is now a plain object + +**Before** + +```js +// Immutable.Map +const items = state.cmf.collections.get('myCollection'); +const nested = state.cmf.collections.getIn(['myCollection', 'subKey']); +const all = state.cmf.collections.toJS(); +const count = state.cmf.collections.size; +const exists = state.cmf.collections.has('myCollection'); +``` + +**After** + +```js +// Plain object {} +const items = state.cmf.collections['myCollection']; + +import _get from 'lodash/get'; +const nested = _get(state.cmf.collections, ['myCollection', 'subKey']); + +const all = state.cmf.collections; // already plain, no-op +const count = Object.keys(state.cmf.collections).length; +const exists = 'myCollection' in state.cmf.collections; +``` + +**Recommended**: use the CMF selector API instead of direct access (see [New selectors](#3-new-selectors-added)). + +--- + +### 2. `state.cmf.components` is now a plain object + +**Before** + +```js +// Immutable.Map (via fromJS) +const componentState = state.cmf.components.getIn(['MyComponent', 'default'])?.toJS(); +``` + +**After** + +```js +// Plain object +const componentState = state.cmf.components?.['MyComponent']?.['default']; +``` + +**Recommended**: use `cmf.selectors.components.*` (see [New selectors](#3-new-selectors-added)). + +--- + +### 3. New selectors added + +This release adds new selectors to `cmf.selectors.collections` and introduces `cmf.selectors.components` for the first time. + +#### Collections selectors (new additions) + +```js +import cmf from '@talend/react-cmf'; + +// Get a collection as a plain value (object or array); returns undefined if absent +const raw = cmf.selectors.collections.getCollectionPlain(state, 'myCollection'); + +// Get the items array from a collection — handles both direct-array and +// Map-wrapped { items: [] } forms; returns undefined if absent +const items = cmf.selectors.collections.getCollectionItems(state, 'myCollection'); + +// Find an item in a collection by its `id` field; returns plain object or undefined +const found = cmf.selectors.collections.getCollectionItem(state, 'myCollection', itemId); +``` + +Pre-existing collections selectors still work unchanged: + +```js +cmf.selectors.collections.get(state, 'myCollection'); +cmf.selectors.collections.get(state, ['my', 'nested', 'path']); +cmf.selectors.collections.findListItem(state, 'myCollection', itemId); +cmf.selectors.collections.getAll(state); +``` + +> **Note**: `findListItem` now expects a plain array in the store. Passing an +> Immutable.List here will throw a type-mismatch error. + +#### Components selectors (new) + +```js +import cmf from '@talend/react-cmf'; + +// Get state of one component instance +const compState = cmf.selectors.components.getComponentState(state, 'MyComponent', 'default'); + +// Get all instances of a component +const allInstances = cmf.selectors.components.getAllComponentStates(state, 'MyComponent'); + +// Get a single property with a default value +const expanded = cmf.selectors.components.getComponentStateProperty( + state, + 'MyComponent', + 'default', + 'expanded', + false, +); +``` + +--- + +### 4. `cmfConnect` prop types changed + +The `state` and `initialState` props injected by `cmfConnect` no longer accept Immutable objects. + +| Prop | Before | After | +| -------------- | -------------------------------------------- | ------------------ | +| `state` | `ImmutablePropTypes.map` | `PropTypes.object` | +| `initialState` | `ImmutablePropTypes.map \| PropTypes.object` | `PropTypes.object` | + +If your component used `this.props.state.get('key')` or `this.props.state.toJS()`, migrate: + +```js +// Before +const expanded = this.props.state.get('expanded'); +this.props.setState(({ state }) => state.set('expanded', true)); + +// After +const expanded = this.props.state?.expanded; +this.props.setState({ expanded: true }); +``` + +If you used `spreadCMFState`, the internal `.toJS()` call is gone — the spread now works directly on the plain state object. + +--- + +### 5. `defaultState` in `cmfConnect` + +If you were using `new Map({})` as `defaultState`, migrate to a plain object: + +```js +// Before +import { Map } from 'immutable'; +cmfConnect({ defaultState: new Map({ expanded: false }) })(MyComponent); + +// After +cmfConnect({ defaultState: { expanded: false } })(MyComponent); +``` + +--- + +### 6. `mock.store.getState()` returns plain objects + +If your tests call `.get()`, `.getIn()`, or `.toJS()` on the mock store collections or components, update them: + +```js +// Before +const state = mock.store.getState(); +state.cmf.collections.get('myCollection'); // Immutable.Map + +// After +state.cmf.collections['myCollection']; // plain object +``` + +--- + +### 7. localStorage hydration + +If you previously called `Immutable.fromJS(storedState)` when hydrating Redux state from `localStorage`, remove that call. The data from `JSON.parse` is already a plain object — exactly what the store now expects. + +```js +// Before +const hydrated = Immutable.fromJS(JSON.parse(localStorage.getItem('state'))); + +// After +const hydrated = JSON.parse(localStorage.getItem('state')); +``` + +--- + +### 8. `selectors.toJS` — no-op wrapper kept for backward compatibility + +`cmf.selectors.collections.toJS(state, path)` is retained for backward compatibility and memoization. It no longer calls `.toJS()` internally because the store is already plain JS. No migration needed if you were calling it — it still returns the same value. + +--- + +## Checklist + +- [ ] Replace all `.get('key')` / `.getIn([...])` calls on `state.cmf.collections` / `state.cmf.components` with plain object access or CMF selectors +- [ ] Replace `.toJS()` calls with identity (already plain JS) +- [ ] Update `.size` → `Object.keys(x).length` or `Array.isArray(x) ? x.length : Object.keys(x).length` +- [ ] Update `.has(key)` → `key in obj` +- [ ] Migrate `defaultState: new Map({...})` → `defaultState: {...}` +- [ ] Update `this.props.state.get('key')` → `this.props.state?.key` in cmfConnect components +- [ ] Update `this.props.setState(s => s.set('k', v))` → `this.props.setState({ k: v })` +- [ ] Update `mock.store.getState()` usages in tests +- [ ] Remove `Immutable.fromJS(...)` from localStorage hydration if present +- [ ] Remove `immutable` and `react-immutable-proptypes` from your own `package.json` if you relied on them transitively diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-components.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-components.md new file mode 100644 index 00000000000..f79927a5662 --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-components.md @@ -0,0 +1,85 @@ +# Migration Guide: `@talend/react-components` — Removal of ImmutableJS + +**Version bump**: MAJOR + +--- + +## What changed + +Two backward-compatibility guards for Immutable objects have been removed from `ActionDropdown`. Passing an Immutable `List` as the `items` prop will no longer work silently. + +Removed published **dependencies**: `immutable`, `react-immutable-proptypes`. If your project relied on these being transitively available through this package, you must either add them explicitly to your own `package.json`, or — preferably — remove ImmutableJS from your codebase entirely. + +--- + +## Breaking changes + +### 1. `ActionDropdown` — Immutable List no longer accepted as `items` + +Two guards have been removed: + +- The `Iterable.isIterable(item)` check that automatically called `.toJS()` on each item. +- The `!items.size` empty-state check (`.size` is an Immutable property; plain arrays use `.length`). + +**Before** + +```js +import { List } from 'immutable'; + +; +``` + +**After** + +```js + +``` + +--- + +### 2. `ActionDropdown` — `items` prop type is now strictly an array + +The `items` prop type definition has changed: + +| Before | After | +| ---------------------------------------------------------------------- | ------------------------------------------- | +| `PropTypes.arrayOf(PropTypes.shape({...})) \| ImmutablePropTypes.list` | `PropTypes.arrayOf(PropTypes.shape({...}))` | + +If you have PropTypes validation in your own code that mirrors the old definition, update it accordingly. + +--- + +### 3. `immutable` and `react-immutable-proptypes` removed from `dependencies` + +These packages are no longer listed as direct published dependencies of `@talend/react-components`. If you were relying on them being resolved transitively from this package: + +1. **Add them explicitly** to your own `package.json` as a temporary bridge, **or** +2. **Remove all ImmutableJS usage** from your codebase (recommended path). + +```bash +# Find all Immutable imports in your source +grep -r "from 'immutable'" src/ --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" +grep -r "react-immutable-proptypes" src/ --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" +``` + +--- + +## Checklist + +- [ ] Replace Immutable `List` passed as `items` to `ActionDropdown` with a plain array `[...]` +- [ ] Update any PropTypes definitions that reference `ImmutablePropTypes.list` for `ActionDropdown.items` +- [ ] Audit for transitive use of `immutable` or `react-immutable-proptypes` coming from this package and add explicit dependencies or remove ImmutableJS usage diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-containers.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-containers.md new file mode 100644 index 00000000000..f01a3cae934 --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-containers.md @@ -0,0 +1,169 @@ +# Migration Guide: `@talend/react-containers` — Removal of ImmutableJS + +**Version bump**: MAJOR + +--- + +## What changed + +All shipped container components that used `new Immutable.Map({})` as `defaultState` have been migrated to plain objects `{}`. Component state is now accessed as a plain JavaScript object. + +Removed published dependencies: `immutable`, `react-immutable-proptypes`. + +--- + +## Breaking changes + +### 1. `defaultState` is now a plain object in all containers + +Every container that previously set `defaultState: new Map({...})` now uses `defaultState: {...}`. + +**Before** + +```js +import { Map } from 'immutable'; + +cmfConnect({ + defaultState: new Map({ expanded: false, loading: false }), +})(MyContainer); +``` + +**After** + +```js +cmfConnect({ + defaultState: { expanded: false, loading: false }, +})(MyContainer); +``` + +If you extend or override any shipped container's `defaultState`, make sure your overrides are also plain objects. + +--- + +### 2. Component state access: `.get()` / `.set()` → plain property access + +Any container component that reads or writes its CMF state via `this.props.state.get()` or `this.props.setState(s => s.set(...))` must be updated. + +**Before** + +```js +// reading +const expanded = this.props.state.get('expanded'); +const loading = this.props.state.get('loading', false); + +// writing +this.props.setState(({ state }) => state.set('expanded', !state.get('expanded'))); +``` + +**After** + +```js +// reading +const expanded = this.props.state?.expanded; +const loading = this.props.state?.loading ?? false; + +// writing +this.props.setState(({ state }) => ({ expanded: !state?.expanded })); +``` + +--- + +### 3. `ActionDropdown` container — `items` prop type narrowed + +The `items` prop in `containers/src/ActionDropdown` now only accepts `PropTypes.arrayOf(PropTypes.object)`. Passing `ImmutablePropTypes.list` is no longer supported. + +```js +// Before +import { List } from 'immutable'; + + +// After + +``` + +--- + +### 4. `AppLoader` — `hasCollections` check + +The `AppLoader` container previously checked for collection existence using the Immutable `.has()` method. It now uses the `in` operator on the plain object. + +This is an **internal change** — no API change is exposed. However, if you forked or extended `AppLoader.connect.jsx`, update: + +```js +// Before +state.cmf.collections.has(collectionName); + +// After +collectionName in state.cmf.collections; +``` + +--- + +### 5. `List` container selector — collection format + +The `List` container's selector previously handled both Immutable.Map-wrapped collections (`Map({ items: [...] })`) and plain arrays. The Immutable path has been removed. + +**Collections stored in CMF must now be either:** + +- A plain array: `['item1', 'item2']` +- A plain object with an `items` key: `{ items: ['item1', 'item2'] }` + +The selector uses `cmf.selectors.collections.getCollectionItems()` internally. No migration needed if your data was already plain JS; if your collection was an Immutable Map, replace it with a plain object. + +--- + +### 6. `ComponentForm` — state access without Immutable methods + +`ComponentForm` reads its state as a plain object. If you customized `ComponentForm` behavior and relied on the state being Immutable: + +**Before** + +```js +const jsonSchema = this.props.state.get('jsonSchema'); +const properties = this.props.state.getIn(['initialState', 'properties']).toJS(); +this.props.setState(prev => prev.state.set('dirty', false).set('jsonSchema', newSchema)); +``` + +**After** + +```js +const jsonSchema = this.props.state.jsonSchema; +const properties = this.props.state.initialState?.properties || {}; +this.props.setState({ + dirty: false, + jsonSchema: newSchema, +}); +``` + +--- + +### 7. `toJS()` helper — identity function + +The internal `toJS(immutableObject)` helper in `ComponentForm.selectors.js` now returns the object as-is (it was previously calling `.toJS()`). If you imported this helper, it still works but no longer performs any conversion. + +--- + +### 8. Test fixtures + +All test factories that created Immutable structures for `state.cmf.collections` or container `defaultState` must be updated: + +```js +// Before +import Immutable from 'immutable'; +const state = { cmf: { collections: Immutable.Map() } }; + +// After +const state = { cmf: { collections: {} } }; +``` + +--- + +## Checklist + +- [ ] Update `defaultState: new Map({...})` → `defaultState: {...}` in all `cmfConnect` containers +- [ ] Replace `this.props.state.get('key')` → `this.props.state?.key` +- [ ] Replace `this.props.setState(s => s.set('k', v))` → `this.props.setState({ k: v })` +- [ ] Ensure `items` props passed to `ActionDropdown` are plain arrays, not Immutable Lists +- [ ] Ensure collection data stored via CMF is a plain array or `{ items: [...] }` object +- [ ] Update test fixtures to use plain objects instead of `Immutable.Map()` / `Immutable.List()` +- [ ] Remove `immutable` and `react-immutable-proptypes` from your own `package.json` if transitively relied on diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md new file mode 100644 index 00000000000..bd2f9bda6bc --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md @@ -0,0 +1,88 @@ +# Migration Guide: `@talend/react-flow-designer` — Removal of ImmutableJS + +**Version bump**: MAJOR + +--- + +## What changed + +`immutable` has been removed from `peerDependencies`. All internal data structures (nodes, edges, ports, links, flow state) that were previously Immutable Maps are now plain TypeScript objects. + +Also removed from `dependencies`: `react-immutable-proptypes`. + +--- + +## Breaking changes + +### 1. `immutable` no longer a required peer dependency + +You no longer need to install `immutable` as a peer dependency of your application when using `@talend/react-flow-designer`. + +Remove it from your `package.json`: + +```json +{ + "peerDependencies": { + "immutable": "^3.0.0" + } +} +``` + +Or if it was in `dependencies`: + +```json +{ + "dependencies": { + "immutable": "^3.8.2" + } +} +``` + +> Only remove it if no other package in your project depends on it. + +--- + +### 2. Internal data structures are now plain objects + +If you worked with the internal flow state directly (e.g., by accessing nodes, ports, links, or flow data), these are now plain objects instead of `Immutable.Map`. + +**Before** + +```js +// nodes, ports, links were Immutable.Map +const node = flowState.nodes.get('nodeId'); +const portType = flowState.nodes.getIn(['nodeId', 'portType']); +``` + +**After** + +```js +// nodes, ports, links are plain objects +const node = flowState.nodes['nodeId']; +const portType = flowState.nodes['nodeId']?.portType; +``` + +If you construct flow state in tests or fixtures, replace Immutable structures with plain objects: + +```js +// Before +import { Map } from 'immutable'; +const flowState = { + nodes: Map({ nodeId: { id: 'nodeId', type: 'default' } }), + links: Map({ linkId: { id: 'linkId', linkType: 'type' } }), +}; + +// After +const flowState = { + nodes: { nodeId: { id: 'nodeId', type: 'default' } }, + links: { linkId: { id: 'linkId', linkType: 'type' } }, +}; +``` + +--- + +## Checklist + +- [ ] Remove `immutable` from `peerDependencies` (and `dependencies`) of your project if only referenced for flow-designer compatibility +- [ ] Replace `Map(...)` / `.get()` / `.getIn()` / `.set()` calls on flow node/port/link data with plain object access +- [ ] Update test fixtures that build flow state with Immutable structures to use plain objects diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-sagas.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-sagas.md new file mode 100644 index 00000000000..bfc9f3660e7 --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-sagas.md @@ -0,0 +1,104 @@ +# Migration Guide: `@talend/react-sagas` — Removal of ImmutableJS + +**Version bump**: MAJOR + +--- + +## What changed + +The exported functions `findPenders` and `findPenderById` now return plain JavaScript values instead of Immutable structures. The internal pending-collection management has been migrated from `Immutable.Map` to plain objects. + +Removed dependency: `immutable`. + +--- + +## Breaking changes + +### 1. `findPenders(state)` returns a plain object + +**Before** + +```js +import { findPenders } from '@talend/react-sagas'; + +const penders = findPenders(state); +// penders was an Immutable.Map +const count = penders ? penders.size : 0; +const entry = penders ? penders.get(asyncActionId) : undefined; +``` + +**After** + +```js +import { findPenders } from '@talend/react-sagas'; + +const penders = findPenders(state); +// penders is now a plain object or undefined +const count = penders ? Object.keys(penders).length : 0; +const entry = penders ? penders[asyncActionId] : undefined; +``` + +--- + +### 2. `findPenderById(state, asyncActionId)` returns a plain value + +**Before** + +```js +import { findPenderById } from '@talend/react-sagas'; + +const pender = findPenderById(state, asyncActionId); +// returned an Immutable value — callers that called .get(), .toJS(), etc. +``` + +**After** + +```js +import { findPenderById } from '@talend/react-sagas'; + +const pender = findPenderById(state, asyncActionId); +// returns a plain JS value (string constant or undefined) +// e.g. 'SHOW_PENDING' or undefined +if (pender) { + // show loading indicator +} +``` + +Remove any `.get()`, `.set()`, `.toJS()`, or `.size` calls on the return values. + +--- + +### 3. Test fixtures using sagas state + +If your tests construct a Redux state object with an Immutable pending collection: + +```js +// Before +import Immutable from 'immutable'; +const state = { + cmf: { + collections: Immutable.Map({ + __PENDING__: Immutable.Map({ 'action-id-1': 'SHOW_PENDING' }), + }), + }, +}; + +// After +const state = { + cmf: { + collections: { + __PENDING__: { 'action-id-1': 'SHOW_PENDING' }, + }, + }, +}; +``` + +--- + +## Checklist + +- [ ] Replace `.size` → `Object.keys(penders).length` on `findPenders` return value +- [ ] Replace `.get(id)` → `penders[id]` on `findPenders` return value +- [ ] Remove `.toJS()` calls on `findPenders` / `findPenderById` return values +- [ ] Update test fixtures for the pending collection to plain objects +- [ ] Remove `immutable` from your own `package.json` if transitively relied on diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-stepper.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-stepper.md new file mode 100644 index 00000000000..deb4c80280e --- /dev/null +++ b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-stepper.md @@ -0,0 +1,17 @@ +# Migration Guide: `@talend/react-stepper` — Removal of ImmutableJS + +**Version bump**: patch + +--- + +## What changed + +`immutable` has been removed from `devDependencies`. This was an unused declared dependency with no functional impact. + +--- + +## No action required + +There are no breaking changes in this release. The `immutable` package was only declared as a development dependency and was not used in the package's source or tests. + +If you were somehow relying on `immutable` being available transitively from this package (which would have been purely coincidental), add it explicitly to your own `package.json`. From fe6e8cd88cab82598122548a97332c0c37295138 Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 12 Mar 2026 15:22:09 +0100 Subject: [PATCH 11/16] Add vote description --- .../vote-presentation-immutable-strategy.md | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/migration-guides/vote-presentation-immutable-strategy.md diff --git a/docs/migration-guides/vote-presentation-immutable-strategy.md b/docs/migration-guides/vote-presentation-immutable-strategy.md new file mode 100644 index 00000000000..43754be8ae7 --- /dev/null +++ b/docs/migration-guides/vote-presentation-immutable-strategy.md @@ -0,0 +1,60 @@ +# ImmutableJS Strategy — Decision Proposal + +The Talend UI monorepo uses **ImmutableJS v3** (`^3.8.1`), which has been in maintenance mode since 2017. Two PRs address this technical debt with different strategies. + +--- + +## Option A — Upgrade to ImmutableJS v5 + +**PR:** [#5743](https://github.com/Talend/ui/pull/5743) + +TUI packages are updated to ImmutableJS v5 (native TypeScript, improved performance). The CMF Redux store keeps its current shape — no store-level breaking change. + +**Consumer project impact:** + +- Must upgrade their own `immutable` dependency from `^3.x` to `^5.x` +- Must audit own Immutable code for v3→v5 breaks: `Record` becomes class-based, `Iterable` is removed +- CMF store access patterns (`.get()`, `.getIn()`) are unchanged +- A migration guide covers the new TUI package versions, but **provides no guidance for migrating project-level Immutable v3 code to v5** + +--- + +## Option B — Remove ImmutableJS entirely + +**PR:** [#5755](https://github.com/Talend/ui/pull/5755) + +ImmutableJS is completely removed from TUI. All internal data structures become plain JavaScript objects. A new CMF selector API is introduced as a stable access layer. + +**Affected TUI packages** (all MAJOR bumps): `react-cmf`, `react-containers`, `react-cmf-cqrs`, `react-sagas`, `react-components`, `react-flow-designer`. + +**Consumer project impact:** + +- CMF store access (`.get()` / `.getIn()`), container `defaultState`, component state read/write, and test fixtures must be migrated — covered by [per-package migration guides](../../docs/migration-guides/migration-guide-remove-immutable.md) +- **Only TUI-related Immutable usage is affected.** Project-internal code that uses ImmutableJS independently of TUI is untouched +- After migration, each project is free to keep, upgrade, or remove Immutable at its own pace + +--- + +## Comparison + +| Criterion | Option A | Option B | +| ------------------------------------- | -------------------------------- | ----------------------- | +| TUI breaking changes | Limited | 6 MAJOR version bumps | +| Consumer: own `immutable` dep | Must upgrade to `^5.x` | Unaffected | +| Consumer: store / state access | Unchanged | Must migrate (guided) | +| Consumer: project-internal Immutable | Must audit and update (unguided) | Not affected | +| Long-term TUI dependency on Immutable | Yes (v5) | Eliminated | +| Migration guide coverage | TUI packages only | Full per-package guides | + +--- + +## Recommendation + +**Option B** is recommended: + +- Option A defers the problem — projects still need to migrate their own Immutable v3 code without any guide, and TUI would remain tied to ImmutableJS long-term +- Option B is a finite, documented migration: effort is higher but bounded, and projects gain full independence from ImmutableJS after migration + +> The main argument for Option A: if projects have very heavy own Immutable usage, they would need a v3→v5 upgrade regardless — Option A allows doing it in one pass. + +--- From 23936bea0f197dfa27c3b9a98b082d1d2bd14b38 Mon Sep 17 00:00:00 2001 From: smouillour Date: Mon, 16 Mar 2026 15:56:30 +0100 Subject: [PATCH 12/16] manage copilot review --- .github/copilot-instructions.md | 492 +++++++++++++++--- .github/copilot-instructions.md.bak | 434 --------------- .../vote-presentation-immutable-strategy.md | 2 +- .../cmf/__tests__/middlewares/cmf.test.js | 1 - .../cmf/__tests__/sagas/collection.test.js | 2 +- packages/cmf/__tests__/sagas/http.test.js | 1 - packages/cmf/src/mock/index.js | 2 +- .../ActionDropdown/ActionDropdown.test.jsx | 4 +- .../src/Notification/Notification.sagas.js | 2 +- .../src/reducers/state-utils.test.ts | 66 +++ .../flow-designer/src/reducers/state-utils.ts | 3 + .../src/selectors/linkSelectors.ts | 26 +- 12 files changed, 518 insertions(+), 517 deletions(-) delete mode 100644 .github/copilot-instructions.md.bak create mode 100644 packages/flow-designer/src/reducers/state-utils.test.ts diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c1c8e5596db..1cb3b93f38f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,58 +1,434 @@ - -# BMAD Method — Project Instructions - -## Project Configuration - -- **Project**: tui -- **User**: Smouillour -- **Communication Language**: Français -- **Document Output Language**: English -- **User Skill Level**: expert -- **Output Folder**: {project-root}/_bmad-output -- **Planning Artifacts**: {project-root}/_bmad-output/planning-artifacts -- **Implementation Artifacts**: {project-root}/_bmad-output/implementation-artifacts -- **Project Knowledge**: {project-root}/_bmad-docs - -## BMAD Runtime Structure - -- **Agent definitions**: `_bmad/bmm/agents/` (BMM module) and `_bmad/core/agents/` (core) -- **Workflow definitions**: `_bmad/bmm/workflows/` (organized by phase) -- **Core tasks**: `_bmad/core/tasks/` (help, editorial review, indexing, sharding, adversarial review) -- **Core workflows**: `_bmad/core/workflows/` (brainstorming, party-mode, advanced-elicitation) -- **Workflow engine**: `_bmad/core/tasks/workflow.xml` (executes YAML-based workflows) -- **Module configuration**: `_bmad/bmm/config.yaml` -- **Core configuration**: `_bmad/core/config.yaml` -- **Agent manifest**: `_bmad/_config/agent-manifest.csv` -- **Workflow manifest**: `_bmad/_config/workflow-manifest.csv` -- **Help manifest**: `_bmad/_config/bmad-help.csv` -- **Agent memory**: `_bmad/_memory/` - -## Key Conventions - -- Always load `_bmad/bmm/config.yaml` before any agent activation or workflow execution -- Store all config fields as session variables: `{user_name}`, `{communication_language}`, `{output_folder}`, `{planning_artifacts}`, `{implementation_artifacts}`, `{project_knowledge}` -- MD-based workflows execute directly — load and follow the `.md` file -- YAML-based workflows require the workflow engine — load `workflow.xml` first, then pass the `.yaml` config -- Follow step-based workflow execution: load steps JIT, never multiple at once -- Save outputs after EACH step when using the workflow engine -- The `{project-root}` variable resolves to the workspace root at runtime - -## Available Agents - -| Agent | Persona | Title | Capabilities | -|---|---|---|---| -| bmad-master | BMad Master | BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator | runtime resource management, workflow orchestration, task execution, knowledge custodian | -| analyst | Mary | Business Analyst | market research, competitive analysis, requirements elicitation, domain expertise | -| architect | Winston | Architect | distributed systems, cloud infrastructure, API design, scalable patterns | -| dev | Amelia | Developer Agent | story execution, test-driven development, code implementation | -| pm | John | Product Manager | PRD creation, requirements discovery, stakeholder alignment, user interviews | -| qa | Quinn | QA Engineer | test automation, API testing, E2E testing, coverage analysis | -| quick-flow-solo-dev | Barry | Quick Flow Solo Dev | rapid spec creation, lean implementation, minimum ceremony | -| sm | Bob | Scrum Master | sprint planning, story preparation, agile ceremonies, backlog management | -| tech-writer | Paige | Technical Writer | documentation, Mermaid diagrams, standards compliance, concept explanation | -| ux-designer | Sally | UX Designer | user research, interaction design, UI patterns, experience strategy | - -## Slash Commands - -Type `/bmad-` in Copilot Chat to see all available BMAD workflows and agent activators. Agents are also available in the agents dropdown. - +# Talend/UI — AI Coding Instructions + +## Repository Overview + +This is **Talend/UI**, a Yarn workspaces monorepo containing shared front-end libraries for Talend products. + +- **Workspaces**: `packages/*`, `tools/*`, `fork/*` +- **Stack**: React 18, TypeScript 5, Babel 7 +- **Build tooling**: shared `@talend/scripts-*` packages (see `tools/`) +- **Versioning**: [Changesets](https://github.com/changesets/changesets) (`@changesets/cli`) +- **Package manager**: Yarn 1 (classic) + +Run `yarn install` at the root. The `postinstall` script builds all libraries (`build:lib` + `build:lib:esm`). + +--- + +## Code Style & Formatting + +### Prettier + +Config: `@talend/scripts-config-prettier` (see `tools/scripts-config-prettier/.prettierrc.js`). + +| Setting | Value | +| ---------------- | ------------------ | +| Print width | 100 | +| Quotes | Single (`'`) | +| Trailing commas | All | +| Semicolons | Yes | +| Indentation | **Tabs** | +| Arrow parens | Avoid (`x => x`) | +| JSON / rc files | 2-space indent | +| SCSS files | 1000 print width | + +Prettier runs automatically on commit via `lint-staged` on `*.{json,md,mdx,html,js,jsx,ts,tsx}`. + +### EditorConfig + +- LF line endings, UTF-8 +- Trim trailing whitespace, insert final newline +- Tabs for `.js`, `.jsx`, `.css`, `.scss` +- 2-space indent for `.json` + +### ESLint + +Each package has an `.eslintrc.json` extending `@talend` (resolved from `@talend/eslint-config` → `tools/scripts-config-eslint`). + +Key rules and extends: + +- `eslint:recommended`, `airbnb-base`, `plugin:prettier/recommended` +- `plugin:react/recommended`, `plugin:react/jsx-runtime` +- `plugin:react-hooks/recommended` — `rules-of-hooks` is error, `exhaustive-deps` is warning +- `plugin:jsx-a11y/recommended` +- `plugin:testing-library/react`, `plugin:jest-dom/recommended` +- `plugin:storybook/recommended` + +Important rules: + +- **No `console.log`** — only `console.warn` and `console.error` allowed +- JSX only in `.jsx` / `.tsx` files (`react/jsx-filename-extension`) +- `@talend/import-depth` (error) — controls import depth into packages +- `import/prefer-default-export`: off — named exports are fine +- `react/jsx-props-no-spreading`: off — spread is allowed +- `react/require-default-props`: off +- `@typescript-eslint/no-explicit-any`: warning (not error) in `.ts`/`.tsx` files +- `import/no-extraneous-dependencies`: off in test and story files + +For TypeScript projects, the config auto-detects `tsconfig.json` and adds `@typescript-eslint` with `airbnb-typescript`. + +### Stylelint + +Config: `stylelint-config-sass-guidelines` (see `tools/scripts-config-stylelint/.stylelintrc.js`). + +- Tab indentation +- No `!important` (`declaration-no-important`) +- No `transition: all` — be specific about transitioned properties +- Max nesting depth: 5 +- Lowercase hex colors, named colors where possible +- No unspaced `calc()` operators + +--- + +## TypeScript + +Base config: `@talend/scripts-config-typescript/tsconfig.json` (see `tools/scripts-config-typescript/`). + +| Setting | Value | +| ---------------------------- | ---------- | +| `strict` | `true` | +| `target` | `ES2015` | +| `module` | `esnext` | +| `moduleResolution` | `bundler` | +| `jsx` | `react-jsx`| +| `declaration` | `true` | +| `sourceMap` | `true` | +| `isolatedModules` | `true` | +| `esModuleInterop` | `true` | +| `forceConsistentCasingInFileNames` | `true` | +| `skipLibCheck` | `true` | + +Each package has a local `tsconfig.json` that extends this base: + +```jsonc +{ + "extends": "@talend/scripts-config-typescript/tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "rootDirs": ["src"] + } +} +``` + +--- + +## Component Architecture + +### Closed API Pattern (Design System) + +Design system components (`packages/design-system`) use **closed APIs** — consumers cannot pass `className`, `style`, or `css` props. This ensures visual homogeneity across all products. + +- **Atoms** (Button, Link, Input): single-tag elements, accept `string` children, typed to mirror their HTML counterparts. Props extend native HTML attributes minus `className`/`style`. +- **Molecules/Organisms** (Modal, Dropdown, Combobox): assembled components with rich props-based APIs. No composition — consumers hydrate via typed props. +- **Templates/Layouts**: may use composition (`children`) for page-level arrangement. + +### Styling + +- **CSS Modules** with `.module.css` files — this is the standard for all new code. No Styled Components. +- **Design tokens** via CSS custom properties from `@talend/design-tokens`. Use them for all colors, spacing, fonts, border-radius, shadows, transitions, etc. +- Use the `classnames` library for conditional class merging. + +### Component Conventions + +- Support `ForwardRef` — wrap components with `forwardRef` so consumers can pass refs. +- Match native HTML element types — component props should extend the underlying element's attributes (e.g., `HTMLButtonElement` for buttons). +- Export components from the package's root `index.ts`. +- Use `DataAttributes` type from `src/types` to support `data-*` attributes. + +### `data-testid` Convention + +All interactive elements must have `data-testid` attributes following this pattern: + +``` +[data-testid=".[?]."] +``` + +| Segment | Required | Example | +| -------------------- | -------- | --------------------------- | +| `block_name` | Yes | `modal`, `inlineediting` | +| `element_type` | Yes | `button`, `input`, `textarea` | +| `element_index` | No | `[1]`, `[2]` | +| `element_identifier` | No | `close`, `reveal`, `edit` | + +Examples: +- `modal.button.close` +- `password.button.reveal` +- `inlineediting.textarea` +- `switch.radio[1]` + +Components should support a `data-testid` prefix prop so consumers can namespace their test IDs (e.g., `my-prefix.inlineediting.button.edit`). + +--- + +## Component Folder Structure + +``` +ComponentName/ +├── ComponentName.tsx # Main component implementation +├── ComponentName.test.tsx # Jest + RTL + jest-axe tests +├── ComponentName.module.css # CSS Modules styles (with design tokens) +├── index.ts # Clean public exports +├── Primitive/ # Internal building-block sub-components +│ ├── ComponentPrimitive.tsx +│ └── ComponentStyles.module.css +└── variations/ # Standalone variant sub-components + ├── ComponentVariantA.tsx + └── ComponentVariantA.module.css +``` + +- Stories live under `src/stories/` in the design-system package, grouped by category (e.g., `clickable/`, `feedback/`). +- The `index.ts` barrel file re-exports everything consumers need. All components must be exported from the package root `src/index.ts`. + +--- + +## Testing + +### Framework & Setup + +- **Vitest** as test runner +- **@testing-library/react** for component rendering and queries +- **jest-axe** for automated accessibility checks +- Timezone forced to `UTC` (`TZ=UTC`) + +### Test File Conventions + +- Name test files `*.test.tsx` or `*.test.ts`, co-located next to the source file. +- Test file regex: `(/__tests__/.*|src/).*\.test.(js|jsx|ts|tsx)$` + +### Writing Tests + +Import test globals explicitly: + +```tsx +import { describe, it, expect } from '@jest/globals'; +``` + +Use `@testing-library/react` for rendering: + +```tsx +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +``` + +**Every component test must include an accessibility check:** + +```tsx +import { axe } from 'jest-axe'; + +it('should render a11y html', async () => { + const { container } = render( +
+ +
, + ); + expect(container.firstChild).toMatchSnapshot(); + const results = await axe(document.body); + expect(results).toHaveNoViolations(); +}); +``` + +**Interaction tests** — use `userEvent.setup()`, not `fireEvent` for user interactions: + +```tsx +it('should handle click', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByRole('button')); +}); +``` + +**Querying elements:** +- Prefer `screen.getByRole()`, `screen.getByText()`, `screen.getByLabelText()` +- Use `screen.getByTestId()` for `data-testid` attributes +- Use `screen.queryBy*` for asserting absence + +**Mocking:** +- Use `jest.fn()` for callback mocks +- Mock `@talend/utils` when components generate IDs: + +```tsx +jest.mock('@talend/utils', () => { + let i = 0; + return { + randomUUID: () => `mocked-uuid-${i++}`, + }; +}); +``` + +**Snapshots** — use `container.firstChild` with `toMatchSnapshot()`. + +--- + +## Internationalization (i18n) + +Uses `react-i18next` backed by `i18next`. + +### Namespaces + +Each package has its own i18n namespace: + +| Package | Namespace | +| ------------ | ---------------- | +| components | `tui-components` | +| forms | `tui-forms` | + +### Translation Keys + +- Format: `COMPONENTNAME_KEY` — prefix by the parent component name +- Examples: `LIST_DISPLAY`, `HEADERBAR_GO_PORTAL`, `DELETE_RESOURCE_MESSAGE` + +Always provide a `defaultValue`: + +```tsx +t('SUFFIX_COMPONENT_KEY', { defaultValue: 'Displayed text' }); +``` + +For markup in translations, use the `Trans` component: + +```tsx +import { Trans } from 'react-i18next'; + + + Are you sure you want to remove the {{ resourceLabel }} + {{ resourceName }}? + +``` + +Extract translation catalogs with `yarn extract-i18n` in the relevant package. + +--- + +## Dependencies Management + +Follow these rules when adding dependencies to a package's `package.json`: + +### `devDependencies` + +For build-only tools or packages that are also a `peerDependency`. No runtime impact. + +Examples: `@talend/scripts-core`, `react` (when also in peerDeps), `@types/*` (unless exported types depend on them), `i18next-scanner` + +### `dependencies` + +For packages used at runtime that consumers don't need to configure themselves. + +Examples: `@talend/design-tokens`, `classnames`, `lodash`, `date-fns`, `react-transition-group` + +### `peerDependencies` + +Only for packages the **consumer must import or configure** for the library to work. + +Examples: `react`, `react-dom`, `i18next`, `react-i18next`, `@talend/icons` + +### Type Dependencies + +`@types/*` packages go in `devDependencies` unless the library's **exported types** depend on them — in that case, add to `dependencies`. + +--- + +## Build & Module Formats + +Libraries produce dual output: + +| Format | Directory | Module | +| -------- | --------- | --------- | +| CommonJS | `lib/` | `main` | +| ESM | `lib-esm/`| `module` | + +Build commands: + +```bash +talend-scripts build # CJS → lib/ +talend-scripts build --esm # ESM → lib-esm/ +``` + +Package `exports` field should map both: + +```json +{ + "main": "lib/index.js", + "module": "lib-esm/index.js", + "exports": { + ".": { + "import": "./lib-esm/index.js", + "require": "./lib/index.js" + } + } +} +``` + +Babel config (`@talend/scripts-config-babel`): +- `@babel/preset-env` (targets: last 1 year of browsers, no IE/Samsung/Opera mini) +- `@babel/preset-react` with `runtime: 'automatic'` (no need to import React) +- `@babel/preset-typescript` with `allExtensions: true, isTSX: true` + +--- + +## Storybook + +- Stories go in `.stories.tsx` files +- Type stories with `StoryFn` or `StoryObj` from `@storybook/react` +- Use `action()` from `storybook/actions` for callback args +- Documentation pages use `.stories.mdx` format +- Stories should cover all component variations, states, and edge cases +- Use design tokens and the design system's own components in stories + +Example structure: + +```tsx +import { StoryFn, StoryObj } from '@storybook/react'; +import { action } from 'storybook/actions'; +import { MyComponent } from '../../'; + +export default { + component: MyComponent, + title: 'Category/MyComponent', +} as StoryObj; + +export const Default: StoryFn = args => ( + +); +``` + +--- + +## Versioning & Releases + +- Uses **Changesets** for version management. +- Run `yarn changeset` to create a changeset file describing your change before opening a PR. +- Base branch: `master` +- Internal dependency updates use `patch` bumps. +- Release: `yarn release` (runs `pre-release` then `changeset publish`). + +--- + +## PR Checklist + +Before opening a pull request: + +- [ ] Run `yarn changeset` if a release is needed +- [ ] Tests added for bug fixes and features +- [ ] Documentation updated if applicable +- [ ] Related design links or discussions included in the PR description +- [ ] Breaking changes documented (update the [breaking change wiki](https://github.com/Talend/ui/wiki/BREAKING-CHANGE)) + +--- + +## Git Hooks + +- **Husky** pre-commit hook runs `lint-staged` +- `lint-staged` auto-formats all staged `*.{json,md,mdx,html,js,jsx,ts,tsx}` files with Prettier +- Code is automatically formatted on every commit — no manual formatting needed + +--- + +## Key ADRs (Architecture Decision Records) + +These documents in `docs/` define architectural choices. Read them before making structural changes: + +| ADR | Summary | +| --- | ------- | +| `adr-css-modules.md` | CSS Modules replace Styled Components for all new styling | +| `adr-composition-vs-api.md` | Design system uses closed APIs over composition | +| `adr-data-test.md` | `data-testid` naming convention for QA automation | +| `adr-dependencies.md` | Guidelines for `dependencies` vs `peerDependencies` vs `devDependencies` | +| `adr-2024-04-add-support-to-esm.md` | ESM support strategy and dual CJS/ESM output | diff --git a/.github/copilot-instructions.md.bak b/.github/copilot-instructions.md.bak deleted file mode 100644 index 1cb3b93f38f..00000000000 --- a/.github/copilot-instructions.md.bak +++ /dev/null @@ -1,434 +0,0 @@ -# Talend/UI — AI Coding Instructions - -## Repository Overview - -This is **Talend/UI**, a Yarn workspaces monorepo containing shared front-end libraries for Talend products. - -- **Workspaces**: `packages/*`, `tools/*`, `fork/*` -- **Stack**: React 18, TypeScript 5, Babel 7 -- **Build tooling**: shared `@talend/scripts-*` packages (see `tools/`) -- **Versioning**: [Changesets](https://github.com/changesets/changesets) (`@changesets/cli`) -- **Package manager**: Yarn 1 (classic) - -Run `yarn install` at the root. The `postinstall` script builds all libraries (`build:lib` + `build:lib:esm`). - ---- - -## Code Style & Formatting - -### Prettier - -Config: `@talend/scripts-config-prettier` (see `tools/scripts-config-prettier/.prettierrc.js`). - -| Setting | Value | -| ---------------- | ------------------ | -| Print width | 100 | -| Quotes | Single (`'`) | -| Trailing commas | All | -| Semicolons | Yes | -| Indentation | **Tabs** | -| Arrow parens | Avoid (`x => x`) | -| JSON / rc files | 2-space indent | -| SCSS files | 1000 print width | - -Prettier runs automatically on commit via `lint-staged` on `*.{json,md,mdx,html,js,jsx,ts,tsx}`. - -### EditorConfig - -- LF line endings, UTF-8 -- Trim trailing whitespace, insert final newline -- Tabs for `.js`, `.jsx`, `.css`, `.scss` -- 2-space indent for `.json` - -### ESLint - -Each package has an `.eslintrc.json` extending `@talend` (resolved from `@talend/eslint-config` → `tools/scripts-config-eslint`). - -Key rules and extends: - -- `eslint:recommended`, `airbnb-base`, `plugin:prettier/recommended` -- `plugin:react/recommended`, `plugin:react/jsx-runtime` -- `plugin:react-hooks/recommended` — `rules-of-hooks` is error, `exhaustive-deps` is warning -- `plugin:jsx-a11y/recommended` -- `plugin:testing-library/react`, `plugin:jest-dom/recommended` -- `plugin:storybook/recommended` - -Important rules: - -- **No `console.log`** — only `console.warn` and `console.error` allowed -- JSX only in `.jsx` / `.tsx` files (`react/jsx-filename-extension`) -- `@talend/import-depth` (error) — controls import depth into packages -- `import/prefer-default-export`: off — named exports are fine -- `react/jsx-props-no-spreading`: off — spread is allowed -- `react/require-default-props`: off -- `@typescript-eslint/no-explicit-any`: warning (not error) in `.ts`/`.tsx` files -- `import/no-extraneous-dependencies`: off in test and story files - -For TypeScript projects, the config auto-detects `tsconfig.json` and adds `@typescript-eslint` with `airbnb-typescript`. - -### Stylelint - -Config: `stylelint-config-sass-guidelines` (see `tools/scripts-config-stylelint/.stylelintrc.js`). - -- Tab indentation -- No `!important` (`declaration-no-important`) -- No `transition: all` — be specific about transitioned properties -- Max nesting depth: 5 -- Lowercase hex colors, named colors where possible -- No unspaced `calc()` operators - ---- - -## TypeScript - -Base config: `@talend/scripts-config-typescript/tsconfig.json` (see `tools/scripts-config-typescript/`). - -| Setting | Value | -| ---------------------------- | ---------- | -| `strict` | `true` | -| `target` | `ES2015` | -| `module` | `esnext` | -| `moduleResolution` | `bundler` | -| `jsx` | `react-jsx`| -| `declaration` | `true` | -| `sourceMap` | `true` | -| `isolatedModules` | `true` | -| `esModuleInterop` | `true` | -| `forceConsistentCasingInFileNames` | `true` | -| `skipLibCheck` | `true` | - -Each package has a local `tsconfig.json` that extends this base: - -```jsonc -{ - "extends": "@talend/scripts-config-typescript/tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - "rootDirs": ["src"] - } -} -``` - ---- - -## Component Architecture - -### Closed API Pattern (Design System) - -Design system components (`packages/design-system`) use **closed APIs** — consumers cannot pass `className`, `style`, or `css` props. This ensures visual homogeneity across all products. - -- **Atoms** (Button, Link, Input): single-tag elements, accept `string` children, typed to mirror their HTML counterparts. Props extend native HTML attributes minus `className`/`style`. -- **Molecules/Organisms** (Modal, Dropdown, Combobox): assembled components with rich props-based APIs. No composition — consumers hydrate via typed props. -- **Templates/Layouts**: may use composition (`children`) for page-level arrangement. - -### Styling - -- **CSS Modules** with `.module.css` files — this is the standard for all new code. No Styled Components. -- **Design tokens** via CSS custom properties from `@talend/design-tokens`. Use them for all colors, spacing, fonts, border-radius, shadows, transitions, etc. -- Use the `classnames` library for conditional class merging. - -### Component Conventions - -- Support `ForwardRef` — wrap components with `forwardRef` so consumers can pass refs. -- Match native HTML element types — component props should extend the underlying element's attributes (e.g., `HTMLButtonElement` for buttons). -- Export components from the package's root `index.ts`. -- Use `DataAttributes` type from `src/types` to support `data-*` attributes. - -### `data-testid` Convention - -All interactive elements must have `data-testid` attributes following this pattern: - -``` -[data-testid=".[?]."] -``` - -| Segment | Required | Example | -| -------------------- | -------- | --------------------------- | -| `block_name` | Yes | `modal`, `inlineediting` | -| `element_type` | Yes | `button`, `input`, `textarea` | -| `element_index` | No | `[1]`, `[2]` | -| `element_identifier` | No | `close`, `reveal`, `edit` | - -Examples: -- `modal.button.close` -- `password.button.reveal` -- `inlineediting.textarea` -- `switch.radio[1]` - -Components should support a `data-testid` prefix prop so consumers can namespace their test IDs (e.g., `my-prefix.inlineediting.button.edit`). - ---- - -## Component Folder Structure - -``` -ComponentName/ -├── ComponentName.tsx # Main component implementation -├── ComponentName.test.tsx # Jest + RTL + jest-axe tests -├── ComponentName.module.css # CSS Modules styles (with design tokens) -├── index.ts # Clean public exports -├── Primitive/ # Internal building-block sub-components -│ ├── ComponentPrimitive.tsx -│ └── ComponentStyles.module.css -└── variations/ # Standalone variant sub-components - ├── ComponentVariantA.tsx - └── ComponentVariantA.module.css -``` - -- Stories live under `src/stories/` in the design-system package, grouped by category (e.g., `clickable/`, `feedback/`). -- The `index.ts` barrel file re-exports everything consumers need. All components must be exported from the package root `src/index.ts`. - ---- - -## Testing - -### Framework & Setup - -- **Vitest** as test runner -- **@testing-library/react** for component rendering and queries -- **jest-axe** for automated accessibility checks -- Timezone forced to `UTC` (`TZ=UTC`) - -### Test File Conventions - -- Name test files `*.test.tsx` or `*.test.ts`, co-located next to the source file. -- Test file regex: `(/__tests__/.*|src/).*\.test.(js|jsx|ts|tsx)$` - -### Writing Tests - -Import test globals explicitly: - -```tsx -import { describe, it, expect } from '@jest/globals'; -``` - -Use `@testing-library/react` for rendering: - -```tsx -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -``` - -**Every component test must include an accessibility check:** - -```tsx -import { axe } from 'jest-axe'; - -it('should render a11y html', async () => { - const { container } = render( -
- -
, - ); - expect(container.firstChild).toMatchSnapshot(); - const results = await axe(document.body); - expect(results).toHaveNoViolations(); -}); -``` - -**Interaction tests** — use `userEvent.setup()`, not `fireEvent` for user interactions: - -```tsx -it('should handle click', async () => { - const user = userEvent.setup(); - render(); - await user.click(screen.getByRole('button')); -}); -``` - -**Querying elements:** -- Prefer `screen.getByRole()`, `screen.getByText()`, `screen.getByLabelText()` -- Use `screen.getByTestId()` for `data-testid` attributes -- Use `screen.queryBy*` for asserting absence - -**Mocking:** -- Use `jest.fn()` for callback mocks -- Mock `@talend/utils` when components generate IDs: - -```tsx -jest.mock('@talend/utils', () => { - let i = 0; - return { - randomUUID: () => `mocked-uuid-${i++}`, - }; -}); -``` - -**Snapshots** — use `container.firstChild` with `toMatchSnapshot()`. - ---- - -## Internationalization (i18n) - -Uses `react-i18next` backed by `i18next`. - -### Namespaces - -Each package has its own i18n namespace: - -| Package | Namespace | -| ------------ | ---------------- | -| components | `tui-components` | -| forms | `tui-forms` | - -### Translation Keys - -- Format: `COMPONENTNAME_KEY` — prefix by the parent component name -- Examples: `LIST_DISPLAY`, `HEADERBAR_GO_PORTAL`, `DELETE_RESOURCE_MESSAGE` - -Always provide a `defaultValue`: - -```tsx -t('SUFFIX_COMPONENT_KEY', { defaultValue: 'Displayed text' }); -``` - -For markup in translations, use the `Trans` component: - -```tsx -import { Trans } from 'react-i18next'; - - - Are you sure you want to remove the {{ resourceLabel }} - {{ resourceName }}? - -``` - -Extract translation catalogs with `yarn extract-i18n` in the relevant package. - ---- - -## Dependencies Management - -Follow these rules when adding dependencies to a package's `package.json`: - -### `devDependencies` - -For build-only tools or packages that are also a `peerDependency`. No runtime impact. - -Examples: `@talend/scripts-core`, `react` (when also in peerDeps), `@types/*` (unless exported types depend on them), `i18next-scanner` - -### `dependencies` - -For packages used at runtime that consumers don't need to configure themselves. - -Examples: `@talend/design-tokens`, `classnames`, `lodash`, `date-fns`, `react-transition-group` - -### `peerDependencies` - -Only for packages the **consumer must import or configure** for the library to work. - -Examples: `react`, `react-dom`, `i18next`, `react-i18next`, `@talend/icons` - -### Type Dependencies - -`@types/*` packages go in `devDependencies` unless the library's **exported types** depend on them — in that case, add to `dependencies`. - ---- - -## Build & Module Formats - -Libraries produce dual output: - -| Format | Directory | Module | -| -------- | --------- | --------- | -| CommonJS | `lib/` | `main` | -| ESM | `lib-esm/`| `module` | - -Build commands: - -```bash -talend-scripts build # CJS → lib/ -talend-scripts build --esm # ESM → lib-esm/ -``` - -Package `exports` field should map both: - -```json -{ - "main": "lib/index.js", - "module": "lib-esm/index.js", - "exports": { - ".": { - "import": "./lib-esm/index.js", - "require": "./lib/index.js" - } - } -} -``` - -Babel config (`@talend/scripts-config-babel`): -- `@babel/preset-env` (targets: last 1 year of browsers, no IE/Samsung/Opera mini) -- `@babel/preset-react` with `runtime: 'automatic'` (no need to import React) -- `@babel/preset-typescript` with `allExtensions: true, isTSX: true` - ---- - -## Storybook - -- Stories go in `.stories.tsx` files -- Type stories with `StoryFn` or `StoryObj` from `@storybook/react` -- Use `action()` from `storybook/actions` for callback args -- Documentation pages use `.stories.mdx` format -- Stories should cover all component variations, states, and edge cases -- Use design tokens and the design system's own components in stories - -Example structure: - -```tsx -import { StoryFn, StoryObj } from '@storybook/react'; -import { action } from 'storybook/actions'; -import { MyComponent } from '../../'; - -export default { - component: MyComponent, - title: 'Category/MyComponent', -} as StoryObj; - -export const Default: StoryFn = args => ( - -); -``` - ---- - -## Versioning & Releases - -- Uses **Changesets** for version management. -- Run `yarn changeset` to create a changeset file describing your change before opening a PR. -- Base branch: `master` -- Internal dependency updates use `patch` bumps. -- Release: `yarn release` (runs `pre-release` then `changeset publish`). - ---- - -## PR Checklist - -Before opening a pull request: - -- [ ] Run `yarn changeset` if a release is needed -- [ ] Tests added for bug fixes and features -- [ ] Documentation updated if applicable -- [ ] Related design links or discussions included in the PR description -- [ ] Breaking changes documented (update the [breaking change wiki](https://github.com/Talend/ui/wiki/BREAKING-CHANGE)) - ---- - -## Git Hooks - -- **Husky** pre-commit hook runs `lint-staged` -- `lint-staged` auto-formats all staged `*.{json,md,mdx,html,js,jsx,ts,tsx}` files with Prettier -- Code is automatically formatted on every commit — no manual formatting needed - ---- - -## Key ADRs (Architecture Decision Records) - -These documents in `docs/` define architectural choices. Read them before making structural changes: - -| ADR | Summary | -| --- | ------- | -| `adr-css-modules.md` | CSS Modules replace Styled Components for all new styling | -| `adr-composition-vs-api.md` | Design system uses closed APIs over composition | -| `adr-data-test.md` | `data-testid` naming convention for QA automation | -| `adr-dependencies.md` | Guidelines for `dependencies` vs `peerDependencies` vs `devDependencies` | -| `adr-2024-04-add-support-to-esm.md` | ESM support strategy and dual CJS/ESM output | diff --git a/docs/migration-guides/vote-presentation-immutable-strategy.md b/docs/migration-guides/vote-presentation-immutable-strategy.md index 43754be8ae7..0c333372c79 100644 --- a/docs/migration-guides/vote-presentation-immutable-strategy.md +++ b/docs/migration-guides/vote-presentation-immutable-strategy.md @@ -29,7 +29,7 @@ ImmutableJS is completely removed from TUI. All internal data structures become **Consumer project impact:** -- CMF store access (`.get()` / `.getIn()`), container `defaultState`, component state read/write, and test fixtures must be migrated — covered by [per-package migration guides](../../docs/migration-guides/migration-guide-remove-immutable.md) +- CMF store access (`.get()` / `.getIn()`), container `defaultState`, component state read/write, and test fixtures must be migrated — covered by [per-package migration guides](./migration-guide-remove-immutable.md) - **Only TUI-related Immutable usage is affected.** Project-internal code that uses ImmutableJS independently of TUI is untouched - After migration, each project is free to keep, upgrade, or remove Immutable at its own pace diff --git a/packages/cmf/__tests__/middlewares/cmf.test.js b/packages/cmf/__tests__/middlewares/cmf.test.js index 5e64863eaf8..af1a9ca7931 100644 --- a/packages/cmf/__tests__/middlewares/cmf.test.js +++ b/packages/cmf/__tests__/middlewares/cmf.test.js @@ -1,6 +1,5 @@ import { vi } from 'vitest'; import cmfMiddleware from '../../src/middlewares/cmf'; -import onError from '../../src/onError'; import CONSTANT from '../../src/constant'; vi.mock('../../src/onError', () => ({ diff --git a/packages/cmf/__tests__/sagas/collection.test.js b/packages/cmf/__tests__/sagas/collection.test.js index 732d12f3cb1..4e77615fdb5 100644 --- a/packages/cmf/__tests__/sagas/collection.test.js +++ b/packages/cmf/__tests__/sagas/collection.test.js @@ -1,4 +1,4 @@ -import { delay, call, select } from 'redux-saga/effects'; +import { delay, select } from 'redux-saga/effects'; import selectors from '../../src/selectors'; import { waitFor } from '../../src/sagas/collection'; diff --git a/packages/cmf/__tests__/sagas/http.test.js b/packages/cmf/__tests__/sagas/http.test.js index e59bf46969e..d6a9ce242c7 100644 --- a/packages/cmf/__tests__/sagas/http.test.js +++ b/packages/cmf/__tests__/sagas/http.test.js @@ -20,7 +20,6 @@ import http, { HTTPError, httpFetch, httpGet, - httpHead, httpPatch, httpPost, httpPut, diff --git a/packages/cmf/src/mock/index.js b/packages/cmf/src/mock/index.js index 28583687ff8..e6ab2ee0a15 100644 --- a/packages/cmf/src/mock/index.js +++ b/packages/cmf/src/mock/index.js @@ -10,7 +10,7 @@ * * ```import mock from 'react-cmf/lib/mock';``` * - * MIGRATION NOTE (Story 1.3): + * MIGRATION NOTE (Immutable removal): * `mock.store.getState().cmf.collections` is now a plain object `{}` (was Immutable.Map). * `mock.store.getState().cmf.components` is now a plain object (was Immutable.Map via fromJS()). * Consumers should use plain object access (`collections['key']`) instead of `.get('key')`. diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx index 4413b64a9ea..8e470fd7ae6 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx @@ -126,8 +126,8 @@ describe('getMenuItem', () => { }); }); -// Regression: react-immutable-proptypes was removed in story 3-1. Verify items accepts plain arrays. -describe('ActionDropdown — plain array items (regression: story 3-1)', () => { +// Verify items accepts plain arrays. +describe('ActionDropdown — plain array items', () => { it('should render all items from a plain JS array', () => { const props = { id: 'dropdown-id', diff --git a/packages/containers/src/Notification/Notification.sagas.js b/packages/containers/src/Notification/Notification.sagas.js index 3621c0ad594..97208d6e2d1 100644 --- a/packages/containers/src/Notification/Notification.sagas.js +++ b/packages/containers/src/Notification/Notification.sagas.js @@ -15,7 +15,7 @@ export function* onPushNotification(action) { const newComponentState = { ...componentState, notifications: [ - ...componentState.notifications, + ...(componentState?.notifications ?? []), { id: randomUUID(), ...action.notification, diff --git a/packages/flow-designer/src/reducers/state-utils.test.ts b/packages/flow-designer/src/reducers/state-utils.test.ts new file mode 100644 index 00000000000..34de18d3746 --- /dev/null +++ b/packages/flow-designer/src/reducers/state-utils.test.ts @@ -0,0 +1,66 @@ +import { setIn, deleteIn } from './state-utils'; + +describe('setIn', () => { + it('sets a nested value', () => { + const obj = { a: { b: 1 } }; + const result = setIn(obj, ['a', 'b'], 2); + expect(result).toEqual({ a: { b: 2 } }); + }); + + it('creates intermediate objects for a new path', () => { + const obj = {}; + const result = setIn(obj, ['a', 'b'], 1); + expect(result).toEqual({ a: { b: 1 } }); + }); + + it('does not mutate the original object', () => { + const obj = { a: { b: 1 } }; + setIn(obj, ['a', 'b'], 2); + expect(obj).toEqual({ a: { b: 1 } }); + }); + + it('sets a top-level value', () => { + const obj = { a: 1 }; + const result = setIn(obj, ['a'], 99); + expect(result).toEqual({ a: 99 }); + }); + + it('returns value as-is when path is empty', () => { + const obj = { a: 1 }; + const newValue = { b: 2 }; + const result = setIn(obj, [], newValue); + expect(result).toBe(newValue); + }); +}); + +describe('deleteIn', () => { + it('deletes a nested key', () => { + const obj = { a: { b: 1, c: 2 } }; + const result = deleteIn(obj, ['a', 'b']); + expect(result).toEqual({ a: { c: 2 } }); + }); + + it('deletes a top-level key', () => { + const obj = { a: 1, b: 2 }; + const result = deleteIn(obj, ['a']); + expect(result).toEqual({ b: 2 }); + }); + + it('returns a clone unchanged when path is empty', () => { + const obj = { a: 1 }; + const result = deleteIn(obj, []); + expect(result).toEqual({ a: 1 }); + }); + + it('does nothing when the path does not exist', () => { + const obj = { a: { b: 1 } }; + const result = deleteIn(obj, ['a', 'z']); + expect(result).toEqual({ a: { b: 1 } }); + }); + + it('does not mutate the original object', () => { + const obj = { a: { b: 1, c: 2 } }; + deleteIn(obj, ['a', 'b']); + expect(obj).toEqual({ a: { b: 1, c: 2 } }); + }); +}); diff --git a/packages/flow-designer/src/reducers/state-utils.ts b/packages/flow-designer/src/reducers/state-utils.ts index a8ad3678858..7e30055b3fb 100644 --- a/packages/flow-designer/src/reducers/state-utils.ts +++ b/packages/flow-designer/src/reducers/state-utils.ts @@ -7,6 +7,9 @@ import set from 'lodash/set'; * Deep-clones obj then sets the value at path. */ export function setIn(obj: T, path: (string | number)[], value: any): T { + if (path.length === 0) { + return value as T; + } const clone = cloneDeep(obj); set(clone, path, value); return clone; diff --git a/packages/flow-designer/src/selectors/linkSelectors.ts b/packages/flow-designer/src/selectors/linkSelectors.ts index 8546df7f89a..01de4263cd4 100644 --- a/packages/flow-designer/src/selectors/linkSelectors.ts +++ b/packages/flow-designer/src/selectors/linkSelectors.ts @@ -13,18 +13,16 @@ const getLinks = (state: State): LinkRecordMap => state.links; export const getDetachedLinks = createSelector( [getLinks, getPorts], - (links: LinkRecordMap, ports: PortRecordMap) => - Object.fromEntries( + (links: LinkRecordMap, ports: PortRecordMap) => { + const portIds = new Set(Object.values(ports || {}).map((port: PortRecord) => port.id)); + return Object.fromEntries( Object.entries(links || {}).filter( ([, link]) => - !Object.values(ports || {}).find( - (port: PortRecord) => port.id === (link as LinkRecord).sourceId, - ) || - !Object.values(ports || {}).find( - (port: PortRecord) => port.id === (link as LinkRecord).targetId, - ), + !portIds.has((link as LinkRecord).sourceId) || + !portIds.has((link as LinkRecord).targetId), ), - ), + ); + }, ); /** @@ -58,10 +56,7 @@ export function portInLink(state: State, portId: Id) { */ export function outLink(state: State, nodeId: Id) { const outMap = state.out?.[nodeId] || {}; - return Object.values(outMap).reduce>( - (reduction, portLinks) => ({ ...reduction, ...(portLinks as Record) }), - {}, - ); + return Object.assign({}, ...Object.values(outMap)) as Record; } /** @@ -71,10 +66,7 @@ export function outLink(state: State, nodeId: Id) { */ export function inLink(state: State, nodeId: Id) { const inMap = state.in?.[nodeId] || {}; - return Object.values(inMap).reduce>( - (reduction, portLinks) => ({ ...reduction, ...(portLinks as Record) }), - {}, - ); + return Object.assign({}, ...Object.values(inMap)) as Record; } export default getDetachedLinks; From 0664114910ed96a619830d4c8ab5b97475170b90 Mon Sep 17 00:00:00 2001 From: smouillour Date: Tue, 17 Mar 2026 10:56:41 +0100 Subject: [PATCH 13/16] revert changes made in flow-designer --- .changeset/remove-immutable.md | 4 - packages/flow-designer/package.json | 5 +- .../src/actions/link.actions.test.ts | 26 +- .../src/actions/node.actions.test.ts | 35 +- .../src/actions/nodeType.actions.test.ts | 3 +- .../src/actions/nodeType.actions.ts | 5 +- .../src/actions/port.actions.test.ts | 13 +- .../flow-designer/src/api/data/data.test.ts | 84 +- packages/flow-designer/src/api/data/data.ts | 43 +- .../flow-designer/src/api/link/link.test.ts | 10 +- .../flow-designer/src/api/node/node.test.ts | 20 +- packages/flow-designer/src/api/node/node.ts | 9 +- .../flow-designer/src/api/port/port.test.ts | 10 +- .../src/api/position/position.test.ts | 12 +- .../src/api/position/position.ts | 21 +- .../flow-designer/src/api/size/size.test.ts | 12 +- packages/flow-designer/src/api/size/size.ts | 25 +- .../FlowDesigner.container.test.tsx | 9 +- .../src/components/FlowDesigner.container.tsx | 20 +- .../link/LinksRenderer.component.tsx | 6 +- .../components/link/LinksRenderer.test.tsx | 52 +- .../node/AbstractNode.component.tsx | 41 +- .../node/NodesRenderer.component.tsx | 2 +- .../components/node/NodesRenderer.test.tsx | 19 +- .../port/PortsRenderer.component.tsx | 2 +- .../components/port/PortsRenderer.test.tsx | 14 +- .../src/constants/flowdesigner.model.ts | 664 +--- .../src/constants/flowdesigner.proptypes.ts | 11 +- .../src/customTypings/index.d.ts | 97 +- .../__snapshots__/flow.reducer.test.ts.snap | 384 ++- .../__snapshots__/link.reducer.test.ts.snap | 2837 ++++++++++++----- .../__snapshots__/node.reducer.test.ts.snap | 921 +++--- .../__snapshots__/port.reducer.test.ts.snap | 877 +++-- .../src/reducers/flow.reducer.test.ts | 48 +- .../src/reducers/flow.reducer.ts | 103 +- .../src/reducers/link.reducer.test.ts | 124 +- .../src/reducers/link.reducer.ts | 284 +- .../src/reducers/node.reducer.test.ts | 52 +- .../src/reducers/node.reducer.ts | 258 +- .../src/reducers/nodeType.reducer.ts | 8 +- .../src/reducers/port.reducer.test.ts | 75 +- .../src/reducers/port.reducer.ts | 228 +- .../src/reducers/state-utils.test.ts | 66 - .../flow-designer/src/reducers/state-utils.ts | 30 - .../__snapshots__/nodeSelectors.test.ts.snap | 20 +- .../src/selectors/linkSelectors.ts | 41 +- .../src/selectors/nodeSelectors.test.ts | 180 +- .../src/selectors/nodeSelectors.ts | 33 +- .../src/selectors/portSelectors.test.ts | 56 +- .../src/selectors/portSelectors.ts | 71 +- yarn.lock | 65 +- 51 files changed, 4616 insertions(+), 3419 deletions(-) delete mode 100644 packages/flow-designer/src/reducers/state-utils.test.ts delete mode 100644 packages/flow-designer/src/reducers/state-utils.ts diff --git a/.changeset/remove-immutable.md b/.changeset/remove-immutable.md index c33468f601c..78d6e06bf71 100644 --- a/.changeset/remove-immutable.md +++ b/.changeset/remove-immutable.md @@ -5,7 +5,6 @@ '@talend/react-components': major '@talend/react-sagas': major '@talend/react-stepper': patch -'@talend/react-flow-designer': major --- feat: remove immutable dependency @@ -42,6 +41,3 @@ The `Iterable.isIterable` backward-compat guard was removed from `ActionDropdown `immutable` and `react-immutable-proptypes` removed from published `dependencies`. -### `@talend/react-flow-designer` (major) - -`immutable` removed from `peerDependencies`. Consumers must no longer install `immutable` as a peer dependency. diff --git a/packages/flow-designer/package.json b/packages/flow-designer/package.json index a6641772dd3..9ed798a3c10 100644 --- a/packages/flow-designer/package.json +++ b/packages/flow-designer/package.json @@ -53,6 +53,7 @@ "@types/redux-thunk": "^2.1.0", "eslint": "^10.0.3", "i18next": "^23.16.8", + "immutable": "^3.8.2", "lodash": "^4.17.23", "prop-types": "^15.8.1", "react": "^18.3.1", @@ -67,6 +68,7 @@ "vitest": "^4.0.18" }, "peerDependencies": { + "immutable": "3", "lodash": "^4.17.23", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -79,7 +81,8 @@ "classnames": "^2.5.1", "d3": "^7.9.0", "invariant": "^2.2.4", - "prop-types": "^15.8.1" + "prop-types": "^15.8.1", + "react-immutable-proptypes": "^2.2.0" }, "files": [ "/dist", diff --git a/packages/flow-designer/src/actions/link.actions.test.ts b/packages/flow-designer/src/actions/link.actions.test.ts index 818943ab989..fa00f36d505 100644 --- a/packages/flow-designer/src/actions/link.actions.test.ts +++ b/packages/flow-designer/src/actions/link.actions.test.ts @@ -1,5 +1,7 @@ +/* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import { Map } from 'immutable'; import * as linkActions from './link.actions'; @@ -10,11 +12,11 @@ describe('Check that link action creators generate proper action objects and per it('addLink', () => { const store = mockStore({ flowDesigner: { - links: {}, - ports: { + links: Map(), + ports: Map({ id1: { id: 'portId', portType: 'type' }, id2: { id: 'portId', portType: 'type' }, - }, + }), }, }); @@ -29,8 +31,8 @@ describe('Check that link action creators generate proper action objects and per it('setLinkTarget', () => { const store = mockStore({ flowDesigner: { - links: { linkId: { id: 'linkId' } }, - ports: { id1: { id: 'portId', portType: 'type' } }, + links: Map({ linkId: { id: 'linkId' } }), + ports: Map({ id1: { id: 'portId', portType: 'type' } }), }, }); @@ -42,8 +44,8 @@ describe('Check that link action creators generate proper action objects and per it('setLinkSource', () => { const store = mockStore({ flowDesigner: { - links: { linkId: { id: 'linkId' } }, - ports: { id1: { id: 'portId', portType: 'type' } }, + links: Map({ linkId: { id: 'linkId' } }), + ports: Map({ id1: { id: 'portId', portType: 'type' } }), }, }); @@ -55,7 +57,7 @@ describe('Check that link action creators generate proper action objects and per it('setLinkGraphicalAttributes', () => { const store = mockStore({ flowDesigner: { - links: { id: { id: 'linkId', linkType: 'type' } }, + links: Map({ id: { id: 'linkId', linkType: 'type' } }), }, }); @@ -67,7 +69,7 @@ describe('Check that link action creators generate proper action objects and per it('removeLinkGrahicalAttribute', () => { const store = mockStore({ flowDesigner: { - links: { id: { id: 'linkId', linkType: 'type' } }, + links: Map({ id: { id: 'linkId', linkType: 'type' } }), }, }); @@ -79,7 +81,7 @@ describe('Check that link action creators generate proper action objects and per it('setLinkData', () => { const store = mockStore({ flowDesigner: { - links: { id: { id: 'linkId', linkType: 'type' } }, + links: Map({ id: { id: 'linkId', linkType: 'type' } }), }, }); @@ -91,7 +93,7 @@ describe('Check that link action creators generate proper action objects and per it('removeLinkData', () => { const store = mockStore({ flowDesigner: { - links: { id: { id: 'linkId', linkType: 'type' } }, + links: Map({ id: { id: 'linkId', linkType: 'type' } }), }, }); @@ -103,7 +105,7 @@ describe('Check that link action creators generate proper action objects and per it('removeLink', () => { const store = mockStore({ flowDesigner: { - links: { id: { id: 'linkId' } }, + links: Map({ id: { id: 'linkId' } }), }, }); diff --git a/packages/flow-designer/src/actions/node.actions.test.ts b/packages/flow-designer/src/actions/node.actions.test.ts index c1a40a0b396..a0b4212b746 100644 --- a/packages/flow-designer/src/actions/node.actions.test.ts +++ b/packages/flow-designer/src/actions/node.actions.test.ts @@ -1,6 +1,8 @@ /* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import { Map, OrderedMap } from 'immutable'; + import * as nodeActions from './node.actions'; import { FLOWDESIGNER_NODE_SET_TYPE } from '../constants/flowdesigner.constants'; @@ -11,7 +13,7 @@ describe('Check that node action creators generate proper action objects and per it('addNode generate action with 0 configuration', () => { const store = mockStore({ flowDesigner: { - nodes: {}, + nodes: Map({}), }, }); @@ -32,13 +34,14 @@ describe('Check that node action creators generate proper action objects and per it('moveNode generate a proper action object witch nodeId and nodePosition parameter', () => { const store = mockStore({ flowDesigner: { - nodes: { nodeId: { id: 'nodeId', type: 'type' } }, - nodeTypes: { - type: { + nodes: Map({ nodeId: { id: 'nodeId', type: 'type' } }), + nodeTypes: Map({ + type: Map({ component: { calculatePortPosition: () => ({}) }, - }, - }, - ports: {}, + }), + }), + // eslint-disable-next-line new-cap + ports: OrderedMap(), }, }); @@ -50,7 +53,7 @@ describe('Check that node action creators generate proper action objects and per it('setNodeSize', () => { const store = mockStore({ flowDesigner: { - nodes: { nodeId: { id: 'nodeId', type: 'type' } }, + nodes: Map({ nodeId: { id: 'nodeId', type: 'type' } }), }, }); @@ -63,7 +66,7 @@ describe('Check that node action creators generate proper action objects and per const nodeType = 'newNodeType'; const store = mockStore({ flowDesigner: { - nodes: { nodeId: { id: nodeId, type: 'type' } }, + nodes: Map({ nodeId: { id: nodeId, type: 'type' } }), }, }); @@ -78,7 +81,7 @@ describe('Check that node action creators generate proper action objects and per it('setNodeGraphicalAttributes', () => { const store = mockStore({ flowDesigner: { - nodes: { id: { id: 'nodeId', type: 'type' } }, + nodes: Map({ id: { id: 'nodeId', type: 'type' } }), }, }); @@ -90,7 +93,7 @@ describe('Check that node action creators generate proper action objects and per it('removeNodeGraphicalAttribute', () => { const store = mockStore({ flowDesigner: { - nodes: { id: { id: 'nodeId', type: 'type' } }, + nodes: Map({ id: { id: 'nodeId', type: 'type' } }), }, }); @@ -102,7 +105,7 @@ describe('Check that node action creators generate proper action objects and per it('setNodeData', () => { const store = mockStore({ flowDesigner: { - nodes: { id: { id: 'nodeId', type: 'type' } }, + nodes: Map({ id: { id: 'nodeId', type: 'type' } }), }, }); @@ -114,13 +117,13 @@ describe('Check that node action creators generate proper action objects and per it('removeNodeData', () => { const store = mockStore({ flowDesigner: { - nodes: { + nodes: Map({ id: { id: 'nodeId', type: 'type', - data: { testProperties: 'testProperties' }, + data: Map({ testProperties: 'testProperties' }), }, - }, + }), }, }); @@ -132,7 +135,7 @@ describe('Check that node action creators generate proper action objects and per it('removeNode', () => { const store = mockStore({ flowDesigner: { - nodes: { id: { id: 'nodeId', type: 'type' } }, + nodes: Map({ id: { id: 'nodeId', type: 'type' } }), }, }); diff --git a/packages/flow-designer/src/actions/nodeType.actions.test.ts b/packages/flow-designer/src/actions/nodeType.actions.test.ts index 5e7d642d3c8..c3593d8e9fb 100644 --- a/packages/flow-designer/src/actions/nodeType.actions.test.ts +++ b/packages/flow-designer/src/actions/nodeType.actions.test.ts @@ -1,8 +1,9 @@ +import { Map } from 'immutable'; import * as nodeTypeActions from './nodeType.actions'; describe('Check that nodeType action creators generate the right action objects', () => { it('setNodeTypes', () => { - const nodeTypes = { anything: { something: true } }; + const nodeTypes = Map().set('anything', { something: true }); expect(nodeTypeActions.setNodeTypes(nodeTypes)).toEqual({ type: 'FLOWDESIGNER_NODETYPE_SET', nodeTypes, diff --git a/packages/flow-designer/src/actions/nodeType.actions.ts b/packages/flow-designer/src/actions/nodeType.actions.ts index 03285ffdcf6..5736f05c062 100644 --- a/packages/flow-designer/src/actions/nodeType.actions.ts +++ b/packages/flow-designer/src/actions/nodeType.actions.ts @@ -1,10 +1,11 @@ +import { Map } from 'immutable'; import { FLOWDESIGNER_NODETYPE_SET } from '../constants/flowdesigner.constants'; /** * Ask to set a map for nodeTypes - * @param {Record} nodeTypes + * @param {Map} nodeTypes */ -export const setNodeTypes = (nodeTypes: Record) => ({ +export const setNodeTypes = (nodeTypes: Map) => ({ type: FLOWDESIGNER_NODETYPE_SET, nodeTypes, }); diff --git a/packages/flow-designer/src/actions/port.actions.test.ts b/packages/flow-designer/src/actions/port.actions.test.ts index 5f1ff72b327..5d24789952e 100644 --- a/packages/flow-designer/src/actions/port.actions.test.ts +++ b/packages/flow-designer/src/actions/port.actions.test.ts @@ -1,5 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; +import { Map } from 'immutable'; import * as portActions from './port.actions'; import { PORT_SINK } from '../constants/flowdesigner.constants'; @@ -10,8 +11,8 @@ describe('Check that port action creators generate proper action objects and per it('addPort', () => { const store = mockStore({ flowDesigner: { - nodes: { nodeId: { id: 'nodeId', nodeType: 'type' } }, - ports: {}, + nodes: Map({ nodeId: { id: 'nodeId', nodeType: 'type' } }), + ports: Map(), }, }); @@ -34,7 +35,7 @@ describe('Check that port action creators generate proper action objects and per it('setPortGraphicalAttribute', () => { const store = mockStore({ flowDesigner: { - ports: { id: { id: 'portId', portType: 'type' } }, + ports: Map({ id: { id: 'portId', portType: 'type' } }), }, }); @@ -46,7 +47,7 @@ describe('Check that port action creators generate proper action objects and per it('removePortAttribute', () => { const store = mockStore({ flowDesigner: { - ports: { id: { id: 'portId' } }, + ports: Map({ id: { id: 'portId' } }), }, }); @@ -58,7 +59,7 @@ describe('Check that port action creators generate proper action objects and per it('setPortData', () => { const store = mockStore({ flowDesigner: { - ports: { id: { id: 'portId', portType: 'type' } }, + ports: Map({ id: { id: 'portId', portType: 'type' } }), }, }); @@ -70,7 +71,7 @@ describe('Check that port action creators generate proper action objects and per it('removePortData', () => { const store = mockStore({ flowDesigner: { - ports: { id: { id: 'portId' }, data: { type: 'test' } }, + ports: Map({ id: { id: 'portId' }, data: Map({ type: 'test' }) }), }, }); diff --git a/packages/flow-designer/src/api/data/data.test.ts b/packages/flow-designer/src/api/data/data.test.ts index 077e80ea1a3..7be703b559c 100644 --- a/packages/flow-designer/src/api/data/data.test.ts +++ b/packages/flow-designer/src/api/data/data.test.ts @@ -1,24 +1,32 @@ +import Immutable from 'immutable'; + import * as Data from './data'; -export const isNotMapException = 'plain object should be a plain object'; +export const isNotMapException = `Immutable.Map should be a Immutable.Map, was given +""" +object +""" +[object Map] +""" +`; export const isNotKeyException = 'key should be a string, was given 8 of type number'; describe('isMapElseThrow', () => { - it('return true if parameter is a plain object', () => { + it('return true if parameter is an Map', () => { // given - const testMap = { key: 'value' }; + const testMap = Immutable.Map(); // when const test = Data.isMapElseThrow(testMap); // expect expect(test).toEqual(true); }); - it('throw an error if parameter is not a plain object', () => { + it('throw an error if parameter is not an Map', () => { // given - const testMap: any = []; + const testMap = new Map(); // when // expect - expect(() => Data.isMapElseThrow(testMap)).toThrow(isNotMapException); + expect(() => Data.isMapElseThrow(testMap as any)).toThrow(isNotMapException); }); }); @@ -47,18 +55,22 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = { withValue: 'value' }; + const map = Immutable.Map({ + withValue: 'value', + }); // when const test = Data.set(key, value, map); // expect - expect(test[key]).toEqual(value); + expect(test.get(key)).toEqual(value); }); it('given an improper key throw', () => { // given const key = 8; const value = 'value'; - const map = { withValue: 'value' }; + const map = Immutable.Map({ + withValue: 'value', + }); // when // expect expect(() => Data.set(key, value, map)).toThrow(isNotKeyException); @@ -68,10 +80,10 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map: any = []; + const map = new Map(); // when // expect - expect(() => Data.set(key, value, map)).toThrow(isNotMapException); + expect(() => Data.set(key, value, map as any)).toThrow(isNotMapException); }); }); @@ -80,7 +92,9 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = { key: value }; + const map = Immutable.Map({ + key: value, + }); // when const test = Data.get(key, map); // expect @@ -91,7 +105,9 @@ describe('Data', () => { // given const key = 'anotherKey'; const value = 'value'; - const map = { key: value }; + const map = Immutable.Map({ + key: value, + }); // when const test = Data.get(key, map); // expect @@ -101,7 +117,9 @@ describe('Data', () => { it('given an improper key throw', () => { // given const key = 8; - const map = { withValue: 'value' }; + const map = Immutable.Map({ + withValue: 'value', + }); // when // expect expect(() => Data.get(key, map)).toThrow(isNotKeyException); @@ -110,10 +128,10 @@ describe('Data', () => { it('given an improper map throw', () => { // given const key = 'key'; - const map: any = []; + const map = new Map(); // when // expect - expect(() => Data.get(key, map)).toThrow(isNotMapException); + expect(() => Data.get(key, map as any)).toThrow(isNotMapException); }); }); @@ -122,7 +140,9 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = { key: value }; + const map = Immutable.Map({ + key: value, + }); // when const test = Data.has(key, map); // expect @@ -133,7 +153,9 @@ describe('Data', () => { // given const key = 'anotherKey'; const value = 'value'; - const map = { key: value }; + const map = Immutable.Map({ + key: value, + }); // when const test = Data.has(key, map); // expect @@ -143,7 +165,9 @@ describe('Data', () => { it('given an improper key throw', () => { // given const key = 8; - const map = { withValue: 'value' }; + const map = Immutable.Map({ + withValue: 'value', + }); // when // expect expect(() => Data.has(key, map)).toThrow(isNotKeyException); @@ -152,10 +176,10 @@ describe('Data', () => { it('given an improper map throw', () => { // given const key = 'key'; - const map: any = []; + const map = new Map(); // when // expect - expect(() => Data.has(key, map)).toThrow(isNotMapException); + expect(() => Data.has(key, map as any)).toThrow(isNotMapException); }); }); @@ -164,18 +188,22 @@ describe('Data', () => { // given const key = 'key'; const value = 'value'; - const map = { key: value }; + const map = Immutable.Map({ + key: value, + }); // when const test = Data.deleteKey(key, map); // expect - expect(test).toEqual({}); + expect(test).toEqual(Immutable.Map()); }); it('given a key and map not containing said key return same map', () => { // given const key = 'anotherKey'; const value = 'value'; - const map = { key: value }; + const map = Immutable.Map({ + key: value, + }); // when const test = Data.deleteKey(key, map); // expect @@ -185,7 +213,9 @@ describe('Data', () => { it('given an improper key throw', () => { // given const key = 8; - const map = { withValue: 'value' }; + const map = Immutable.Map({ + withValue: 'value', + }); // when // expect expect(() => Data.deleteKey(key, map)).toThrow(isNotKeyException); @@ -194,10 +224,10 @@ describe('Data', () => { it('given an improper map throw', () => { // given const key = 'key'; - const map: any = []; + const map = new Map(); // when // expect - expect(() => Data.deleteKey(key, map)).toThrow(isNotMapException); + expect(() => Data.deleteKey(key, map as any)).toThrow(isNotMapException); }); }); }); diff --git a/packages/flow-designer/src/api/data/data.ts b/packages/flow-designer/src/api/data/data.ts index 7ce2d53c717..96baf80951c 100644 --- a/packages/flow-designer/src/api/data/data.ts +++ b/packages/flow-designer/src/api/data/data.ts @@ -1,21 +1,22 @@ /** - * This module is private and deal with updating a graph object + * This module is private and deal with updating a graph object internal Immutable.Map */ import curry from 'lodash/curry'; import isString from 'lodash/isString'; +import { Map } from 'immutable'; import { throwInDev, throwTypeError } from '../throwInDev'; /** - * return true if the parameter is a plain object, throw otherwise + * return true if the parameter is an Immutable.Map throw otherwise * @private - * @param {any} map - the value to be checked as plain object + * @param {any} map - the value to be checkd as Immutable.Map * @return {bool} */ -export function isMapElseThrow(map: Record) { - const test = typeof map === 'object' && map !== null && !Array.isArray(map); +export function isMapElseThrow(map: Map) { + const test = Map.isMap(map); if (!test) { - throwTypeError('plain object', map, 'map'); + throwTypeError('Immutable.Map', map, 'map'); } return test; } @@ -39,12 +40,12 @@ export function isKeyElseThrow(key: string | number) { * @function * @param {string} key * @param {any} value - * @param {Record} map - * @returns {Record} + * @param {Immutable.Map} map + * @returns {Immutable.Map} */ -export const set = curry((key: any, value: any, map: Record) => { +export const set = curry((key: any, value: any, map: Map) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return { ...map, [key]: value }; + return map.set(key, value); } return map; }); @@ -53,12 +54,12 @@ export const set = curry((key: any, value: any, map: Record) => { * given a key and a map return the value associated if exist * @function * @param {string} key - * @param {Record} map + * @param {Immutable.Map} map * @returns {any | null} */ -export const get = curry((key: any, map: Record) => { +export const get = curry((key: any, map: Map) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return map[key]; + return map.get(key); } return null; }); @@ -67,12 +68,12 @@ export const get = curry((key: any, map: Record) => { * Given a key and a map check if this key exist on the map * @function * @param {string} key - * @param {Record} map + * @param {Immutable.Map} map * @return {bool} */ -export const has = curry((key: any, map: Record) => { +export const has = curry((key: any, map: Map) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - return Object.prototype.hasOwnProperty.call(map, key); + return map.has(key); } return false; }); @@ -81,14 +82,12 @@ export const has = curry((key: any, map: Record) => { * remove given key and its value from the map * @function * @param {string} key - * @param {Record} map - * @returns {Record} + * @param {Immutable.Map} map + * @returns {Immutable.Map} */ -export const deleteKey = curry((key: any, map: Record) => { +export const deleteKey = curry((key: any, map: Map) => { if (isKeyElseThrow(key) && isMapElseThrow(map)) { - const result = { ...map }; - delete result[key]; - return result; + return map.delete(key); } return map; }); diff --git a/packages/flow-designer/src/api/link/link.test.ts b/packages/flow-designer/src/api/link/link.test.ts index d1fb5edd1b2..3e240f9bb35 100644 --- a/packages/flow-designer/src/api/link/link.test.ts +++ b/packages/flow-designer/src/api/link/link.test.ts @@ -11,6 +11,8 @@ because the underlying module data is itself tested. */ +import { Map } from 'immutable'; + import { LinkRecord } from '../../constants/flowdesigner.model'; import * as Link from './link'; @@ -18,7 +20,7 @@ const isNotLinkException = `Linkrecord should be a Linkrecord, was given """ object """ -[object Object] +Map {} """ you should use Link module functions to create and transform Link`; const protectedValueException = @@ -36,10 +38,10 @@ describe('isLinkElseThrow', () => { it('thow an error if parameter is not a LinkRecord', () => { // given - const testLink2 = {}; + const testLink = Map(); // when // expect - expect(() => Link.isLinkElseThrow(testLink2)).toThrow(isNotLinkException); + expect(() => Link.isLinkElseThrow(testLink)).toThrow(isNotLinkException); }); }); @@ -56,7 +58,7 @@ describe('Link', () => { const improperSourceId = 42; const improperTargetId = 64; const improperLinkType = {}; - const improperLink = {}; + const improperLink = Map(); describe('create', () => { it('given proper id, sourceId, targetid and componentType return a Link', () => { diff --git a/packages/flow-designer/src/api/node/node.test.ts b/packages/flow-designer/src/api/node/node.test.ts index 7b53ffaecc8..8bb3a18ea66 100644 --- a/packages/flow-designer/src/api/node/node.test.ts +++ b/packages/flow-designer/src/api/node/node.test.ts @@ -11,6 +11,8 @@ because the underlying module data is itself tested. */ +import { Map } from 'immutable'; + import { NodeRecord, NestedNodeRecord } from '../../constants/flowdesigner.model'; import * as Node from './node'; @@ -21,14 +23,14 @@ const isNotNodeException = `NodeRecord should be a NodeRecord, was given """ object """ -[object Object] +Map {} """`; const improperSizeMessage = `SizeRecord should be a SizeRecord, was given """ object """ -[object Object] +Map { "width": 20, "height": 50 } """ you should use Size module functions to create and transform Size`; @@ -36,7 +38,7 @@ const improperPositionMessage = `PositionRecord should be a PositionRecord, was """ object """ -[object Object] +Map { "x": 10, "y": 10 } """ `; @@ -64,10 +66,10 @@ describe('isNodeElseThrow', () => { it('thow an error if parameter is not a NodeRecord', () => { // given - const testNode2 = {}; + const testNode = Map(); // when // expect - expect(() => Node.isNodeElseThrow(testNode2)).toThrow(isNotNodeException); + expect(() => Node.isNodeElseThrow(testNode)).toThrow(isNotNodeException); }); }); @@ -81,10 +83,10 @@ describe('Node', () => { const value = { whatever: 'whatever' }; const improperId = 34; - const improperPosition = { x: 10, y: 10 }; - const improperSize = { width: 20, height: 50 }; + const improperPosition = Map({ x: 10, y: 10 }); + const improperSize = Map({ width: 20, height: 50 }); const improperNodeType = {}; - const improperNode = {}; + const improperNode = Map(); describe('create', () => { it('given proper id, position, size and componentType return a Node', () => { // given @@ -186,7 +188,7 @@ describe('Node', () => { """ object """ -[object Object] +Map { "x": 10, "y": 10 } """ you should use Position module functions to create and transform Position`); }); diff --git a/packages/flow-designer/src/api/node/node.ts b/packages/flow-designer/src/api/node/node.ts index 728eb45c731..a09dd05c656 100644 --- a/packages/flow-designer/src/api/node/node.ts +++ b/packages/flow-designer/src/api/node/node.ts @@ -6,6 +6,7 @@ import flow from 'lodash/flow'; import indexOf from 'lodash/indexOf'; import isString from 'lodash/isString'; import upperFirst from 'lodash/upperFirst'; +import { Map } from 'immutable'; import { throwInDev, throwTypeError } from '../throwInDev'; import { NodeRecord, NestedNodeRecord } from '../../constants/flowdesigner.model'; @@ -168,12 +169,12 @@ export const setComponentType = curry( * @returns {NodeRecord} */ export const setComponents = curry( - (components: Record, node: NestedNodeRecordType) => { - if (components != null && isNodeElseThrow(node)) { + (components: Map, node: NestedNodeRecordType) => { + if (Map.isMap(components) && isNodeElseThrow(node)) { return node.setIn(componentsSelector, components); } throwInDev( - `components should be a plain object, was given ${components && components.toString()}`, + `components should be a Immutable.List, was given ${components && components.toString()}`, ); return node; }, @@ -347,7 +348,7 @@ export const create = curry( setPosition(position), setSize(size), setComponentType(componentType), - setComponents({}), + setComponents(Map()), ])(new NestedNodeRecord()); } diff --git a/packages/flow-designer/src/api/port/port.test.ts b/packages/flow-designer/src/api/port/port.test.ts index b2cab2f74c6..834c760237c 100644 --- a/packages/flow-designer/src/api/port/port.test.ts +++ b/packages/flow-designer/src/api/port/port.test.ts @@ -11,6 +11,8 @@ because the underlying module data is itself tested. */ +import { Map } from 'immutable'; + import { PortRecord } from '../../constants/flowdesigner.model'; import * as Port from './port'; import * as Position from '../position/position'; @@ -26,10 +28,10 @@ describe('isPortElseThrow', () => { }); it('throw if given parameter is not a PortRecord', () => { // given - const testPort2 = {}; + const testPort = Map(); // when // expect - expect(() => Port.isPortElseThrow(testPort2)).toThrow(); + expect(() => Port.isPortElseThrow(testPort)).toThrow(); }); }); @@ -60,9 +62,9 @@ describe('port api', () => { const improperNodeId = 42; const improperIndex = '10'; const impropertopology = {}; - const improperPosition = { x: 10, y: 10 }; + const improperPosition = Map({ x: 10, y: 10 }); const improperPortType = {}; - const improperPort = {}; + const improperPort = Map(); describe('create', () => { it('given proper id, nodeId, index, topology and componentType return a Node', () => { diff --git a/packages/flow-designer/src/api/position/position.test.ts b/packages/flow-designer/src/api/position/position.test.ts index 66f7b2cd47d..9714ce697f2 100644 --- a/packages/flow-designer/src/api/position/position.test.ts +++ b/packages/flow-designer/src/api/position/position.test.ts @@ -1,3 +1,5 @@ +import { Map } from 'immutable'; + import { PositionRecord } from '../../constants/flowdesigner.model'; import * as Position from './position'; @@ -6,14 +8,14 @@ const isNotPositionException = `PositionRecord should be a PositionRecord, was g """ object """ -[object Object] +Map {} """ you should use Position module functions to create and transform Position`; const improperPositionException = `PositionRecord should be a PositionRecord, was given """ object """ -[object Object] +Map { "x": 10, "y": 10 } """ you should use Position module functions to create and transform Position`; const isImproperXCoordinate = 'x should be a number, was given 10 of type string'; @@ -31,10 +33,10 @@ describe('isPositionElseThrow', () => { it('thow an error if parameter is not a PositionRecord', () => { // given - const testPosition2 = {}; + const testPosition = Map(); // when // expect - expect(() => Position.isPositionElseThrow(testPosition2)).toThrow(isNotPositionException); + expect(() => Position.isPositionElseThrow(testPosition)).toThrow(isNotPositionException); }); }); @@ -45,7 +47,7 @@ describe('Position', () => { const improperX = '10'; const improperY = '50'; - const improperTestPosition = { x: 10, y: 10 }; + const improperTestPosition = Map({ x: 10, y: 10 }); describe('create', () => { it('given proper x and y coordinate return a Position', () => { // given diff --git a/packages/flow-designer/src/api/position/position.ts b/packages/flow-designer/src/api/position/position.ts index 823d82337e3..e218740135e 100644 --- a/packages/flow-designer/src/api/position/position.ts +++ b/packages/flow-designer/src/api/position/position.ts @@ -1,4 +1,5 @@ import curry from 'lodash/curry'; +import flow from 'lodash/flow'; import isNumber from 'lodash/isNumber'; import { throwInDev, throwTypeError } from '../throwInDev'; @@ -50,7 +51,7 @@ export function isPositionElseThrow(position: PositionRecordType) { */ export function getXCoordinate(position: PositionRecordType) { if (isPositionElseThrow(position)) { - return position.x; + return position.get('x'); } return null; } @@ -64,7 +65,7 @@ export function getXCoordinate(position: PositionRecordType) { */ export const setXCoordinate = curry((x: number, position: PositionRecordType) => { if (isPositionElseThrow(position) && isNumber(x)) { - return new PositionRecord({ ...position, x }); + return position.set('x', x); } throwInDev(`x should be a number, was given ${x && x.toString()} of type ${typeof x}`); return position; @@ -77,7 +78,7 @@ export const setXCoordinate = curry((x: number, position: PositionRecordType) => */ export function getYCoordinate(position: PositionRecordType) { if (isPositionElseThrow(position)) { - return position.y; + return position.get('y'); } return null; } @@ -90,7 +91,7 @@ export function getYCoordinate(position: PositionRecordType) { */ export const setYCoordinate = curry((y: number, position: PositionRecordType) => { if (isPositionElseThrow(position) && isNumber(y)) { - return new PositionRecord({ ...position, y }); + return position.set('y', y); } throwInDev(`y should be a number, was given ${y && y.toString()} of type ${typeof y}`); return position; @@ -102,12 +103,6 @@ export const setYCoordinate = curry((y: number, position: PositionRecordType) => * @param {number} y * @return {PositionRecord} */ -export const create = curry((x: number, y: number) => { - if (!isNumber(x)) { - throwInDev(`x should be a number, was given ${x && (x as any).toString()} of type ${typeof x}`); - } - if (!isNumber(y)) { - throwInDev(`y should be a number, was given ${y && (y as any).toString()} of type ${typeof y}`); - } - return new PositionRecord({ x, y }); -}); +export const create = curry((x: number, y: number) => + flow([setXCoordinate(x), setYCoordinate(y)])(new PositionRecord()), +); diff --git a/packages/flow-designer/src/api/size/size.test.ts b/packages/flow-designer/src/api/size/size.test.ts index 676afed81d5..d12d354cfd8 100644 --- a/packages/flow-designer/src/api/size/size.test.ts +++ b/packages/flow-designer/src/api/size/size.test.ts @@ -1,3 +1,5 @@ +import { Map } from 'immutable'; + import { SizeRecord } from '../../constants/flowdesigner.model'; import * as Size from './size'; @@ -6,14 +8,14 @@ const isNotSizeException = `SizeRecord should be a SizeRecord, was given """ object """ -[object Object] +Map {} """ you should use Size module functions to create and transform Size`; const isNotProperSizeException = `SizeRecord should be a SizeRecord, was given """ object """ -[object Object] +Map { "width": 10, "height": 10 } """ you should use Size module functions to create and transform Size`; const isImproperWidth = 'width should be a number, was given 10 of type string'; @@ -31,10 +33,10 @@ describe('isSizeElseThrow', () => { it('throw an error if parameter is not a SizeRecord', () => { // given - const testSize2 = {}; + const testSize = Map(); // when // expect - expect(() => Size.isSizeElseThrow(testSize2)).toThrow(isNotSizeException); + expect(() => Size.isSizeElseThrow(testSize)).toThrow(isNotSizeException); }); }); @@ -45,7 +47,7 @@ describe('Size', () => { const improperWidth = '10'; const improperHeight = '50'; - const improperTestSize = { width: 10, height: 10 }; + const improperTestSize = Map({ width: 10, height: 10 }); describe('create', () => { it('given proper width and height return a Size', () => { // given diff --git a/packages/flow-designer/src/api/size/size.ts b/packages/flow-designer/src/api/size/size.ts index adb516d774d..382ba4a647b 100644 --- a/packages/flow-designer/src/api/size/size.ts +++ b/packages/flow-designer/src/api/size/size.ts @@ -1,4 +1,5 @@ import curry from 'lodash/curry'; +import flow from 'lodash/flow'; import { throwInDev, throwTypeError } from '../throwInDev'; import { SizeRecord } from '../../constants/flowdesigner.model'; @@ -49,7 +50,7 @@ export function isSizeElseThrow(size: SizeRecordType) { */ export function getWidth(size: SizeRecordType) { if (isSizeElseThrow(size)) { - return size.width; + return size.get('width'); } return null; } @@ -63,7 +64,7 @@ export function getWidth(size: SizeRecordType) { */ export const setWidth = curry((width: number, size: SizeRecordType) => { if (isSizeElseThrow(size) && typeof width === 'number') { - return new SizeRecord({ ...size, width }); + return size.set('width', width); } throwInDev(`width should be a number, was given ${width.toString()} of type ${typeof width}`); return size; @@ -76,7 +77,7 @@ export const setWidth = curry((width: number, size: SizeRecordType) => { */ export function getHeight(size: SizeRecordType) { if (isSizeElseThrow(size)) { - return size.height; + return size.get('height'); } return null; } @@ -90,7 +91,7 @@ export function getHeight(size: SizeRecordType) { */ export const setHeight = curry((height: number, size: SizeRecordType) => { if (isSizeElseThrow(size) && typeof height === 'number') { - return new SizeRecord({ ...size, height }); + return size.set('height', height); } throwInDev(`height should be a number, was given ${height.toString()} of type ${typeof height}`); return size; @@ -103,16 +104,6 @@ export const setHeight = curry((height: number, size: SizeRecordType) => { * @param {number} height * @return {SizeRecord} */ -export const create = curry((width: number, height: number) => { - if (typeof width !== 'number') { - throwInDev( - `width should be a number, was given ${(width as any).toString()} of type ${typeof width}`, - ); - } - if (typeof height !== 'number') { - throwInDev( - `height should be a number, was given ${(height as any).toString()} of type ${typeof height}`, - ); - } - return new SizeRecord({ width, height }); -}); +export const create = curry((width: number, height: number) => + flow([setWidth(width), setHeight(height)])(new SizeRecord()), +); diff --git a/packages/flow-designer/src/components/FlowDesigner.container.test.tsx b/packages/flow-designer/src/components/FlowDesigner.container.test.tsx index 8ead6e8da77..2f2d85080e2 100644 --- a/packages/flow-designer/src/components/FlowDesigner.container.test.tsx +++ b/packages/flow-designer/src/components/FlowDesigner.container.test.tsx @@ -1,8 +1,9 @@ import renderer from 'react-test-renderer'; +import { Map } from 'immutable'; import { FlowDesigner } from './FlowDesigner.container'; import NodeType from './configuration/NodeType.component'; -import { NodeRecordMap, PortRecordMap, LinkRecordMap } from '../customTypings/index.d'; +import { NodeRecord, Id, PortRecord, LinkRecord } from '../customTypings/index.d'; vi.mock('./ZoomHandler.component'); vi.mock('./grid/Grid.component', () => { @@ -15,9 +16,9 @@ const noOp = () => {}; describe(' renders correctly', () => { it(' renders correctly', () => { - const nodes: NodeRecordMap = {}; - const ports: PortRecordMap = {}; - const links: LinkRecordMap = {}; + const nodes = Map(); + const ports = Map(); + const links = Map(); const tree = renderer .create( { - const slice = get(state, ownProps.reduxMountPoint); - return { - nodes: slice.nodes, - links: slice.links, - ports: slice.ports, - transform: slice.transform, - transformToApply: slice.transformToApply, - }; -}; +const mapStateToProps = (state: State, ownProps: Props) => ({ + nodes: get(state, ownProps.reduxMountPoint).get('nodes'), + links: get(state, ownProps.reduxMountPoint).get('links'), + ports: get(state, ownProps.reduxMountPoint).get('ports'), + transform: get(state, ownProps.reduxMountPoint).get('transform'), + transformToApply: get(state, ownProps.reduxMountPoint).get('transformToApply'), +}); const mapDispatchToProps = (dispatch: any) => ({ - setNodeTypes: (nodeTypeMap: StateMap) => dispatch(setNodeTypes(nodeTypeMap)), + setNodeTypes: (nodeTypeMap: Map) => dispatch(setNodeTypes(nodeTypeMap)), startMoveNodeTo: (nodeId: Id, nodePosition: string) => dispatch(startMoveNodeTo(nodeId, nodePosition)), moveNodeTo: (nodeId: Id, nodePosition: Position) => dispatch(moveNodeTo(nodeId, nodePosition)), diff --git a/packages/flow-designer/src/components/link/LinksRenderer.component.tsx b/packages/flow-designer/src/components/link/LinksRenderer.component.tsx index f554bbfd07c..62c0199aeac 100644 --- a/packages/flow-designer/src/components/link/LinksRenderer.component.tsx +++ b/packages/flow-designer/src/components/link/LinksRenderer.component.tsx @@ -9,13 +9,13 @@ type Props = { class LinksRender extends Component { render() { - const links = Object.values(this.props.links); + const links = this.props.links.toArray(); return ( {links.map(link => { const ConcreteLink = this.props.linkTypeMap[link.getLinkType()].component; - const source = this.props.ports[link.sourceId]; - const target = this.props.ports[link.targetId]; + const source = this.props.ports.get(link.sourceId); + const target = this.props.ports.get(link.targetId); return ; })} diff --git a/packages/flow-designer/src/components/link/LinksRenderer.test.tsx b/packages/flow-designer/src/components/link/LinksRenderer.test.tsx index 40ad0c52b80..cdef1674dd0 100644 --- a/packages/flow-designer/src/components/link/LinksRenderer.test.tsx +++ b/packages/flow-designer/src/components/link/LinksRenderer.test.tsx @@ -1,33 +1,51 @@ +/* eslint-disable new-cap */ import renderer from 'react-test-renderer'; +import { Map, OrderedMap } from 'immutable'; import LinksRenderer from './LinksRenderer.component'; import { LinkRecord, PortRecord, PositionRecord } from '../../constants/flowdesigner.model'; -import { LinkRecordMap, PortRecordMap } from '../../customTypings/index.d'; +import { + Id, + LinkRecord as LinkRecordType, + PortRecord as PortRecordType, +} from '../../customTypings/index.d'; const MockLink = () => MockLink; describe('', () => { it('renders correctly', () => { - const links: LinkRecordMap = { - id: new LinkRecord({ + const links = Map().set( + 'id', + new LinkRecord({ id: 'id', sourceId: 'port1', targetId: 'port2', - graphicalAttributes: { linkType: 'id' }, + graphicalAttributes: Map({ + linkType: 'id', + }), }), - }; - const ports: PortRecordMap = { - port1: new PortRecord({ - id: 'port1', - nodeId: 'nodeId', - graphicalAttributes: { position: new PositionRecord({ x: 100, y: 100 }) }, - }), - port2: new PortRecord({ - id: 'port2', - nodeId: 'nodeId', - graphicalAttributes: { position: new PositionRecord({ x: 200, y: 200 }) }, - }), - }; + ); + const ports = OrderedMap() + .set( + 'port1', + new PortRecord({ + id: 'port1', + nodeId: 'nodeId', + graphicalAttributes: Map({ + position: new PositionRecord({ x: 100, y: 100 }), + }), + }), + ) + .set( + 'port2', + new PortRecord({ + id: 'port2', + nodeId: 'nodeId', + graphicalAttributes: Map({ + position: new PositionRecord({ x: 200, y: 200 }), + }), + }), + ); const linkTypeMap = { id: { id: 'id', component: MockLink } }; const tree = renderer .create() diff --git a/packages/flow-designer/src/components/node/AbstractNode.component.tsx b/packages/flow-designer/src/components/node/AbstractNode.component.tsx index e483cf376f6..7a10bacff39 100644 --- a/packages/flow-designer/src/components/node/AbstractNode.component.tsx +++ b/packages/flow-designer/src/components/node/AbstractNode.component.tsx @@ -1,6 +1,7 @@ import { Component } from 'react'; import type { MouseEventHandler, MouseEvent } from 'react'; import { scaleLinear, drag, select } from 'd3'; +import { Map } from 'immutable'; import invariant from 'invariant'; @@ -28,16 +29,18 @@ function calculatePortPosition( nodePosition: PositionType, nodeSize: SizeType, ) { - let portsWithPosition: Record = {}; - const emitterPorts = Object.values(ports).filter(port => Port.getTopology(port) === PORT_SOURCE); - const sinkPorts = Object.values(ports).filter(port => Port.getTopology(port) === PORT_SINK); - const yStart = Position.getYCoordinate(nodePosition) as number; - const range: [number, number] = [yStart, yStart + (Size.getHeight(nodeSize) as number)]; + let portsWithPosition = Map(); + const emitterPorts = ports.filter(port => Port.getTopology(port) === PORT_SOURCE); + const sinkPorts = ports.filter(port => Port.getTopology(port) === PORT_SINK); + const range = [ + Position.getYCoordinate(nodePosition), + Position.getYCoordinate(nodePosition) + Size.getHeight(nodeSize), + ]; const scaleYEmitter = scaleLinear() - .domain([0, emitterPorts.length + 1]) + .domain([0, emitterPorts.size + 1]) .range(range); const scaleYSink = scaleLinear() - .domain([0, sinkPorts.length + 1]) + .domain([0, sinkPorts.size + 1]) .range(range); let emitterNumber = 0; let sinkNumber = 0; @@ -55,13 +58,10 @@ function calculatePortPosition( emitterNumber += 1; const position = Position.create( - (Position.getXCoordinate(nodePosition) as number) + (Size.getWidth(nodeSize) as number), - scaleYEmitter(emitterNumber) as number, - ) as PositionType; - portsWithPosition = { - ...portsWithPosition, - [Port.getId(port)!]: Port.setPosition(position, port), - }; + Position.getXCoordinate(nodePosition) + Size.getWidth(nodeSize), + scaleYEmitter(emitterNumber), + ); + portsWithPosition = portsWithPosition.set(Port.getId(port), Port.setPosition(port, position)); }); sinkPorts .sort((a, b) => { @@ -76,13 +76,10 @@ function calculatePortPosition( .forEach(port => { sinkNumber += 1; const position = Position.create( - Position.getXCoordinate(nodePosition) as number, - scaleYSink(sinkNumber) as number, - ) as PositionType; - portsWithPosition = { - ...portsWithPosition, - [Port.getId(port)!]: Port.setPosition(position, port), - }; + Position.getXCoordinate(nodePosition), + scaleYSink(sinkNumber), + ); + portsWithPosition = portsWithPosition.set(Port.getId(port), Port.setPosition(port, position)); }); return portsWithPosition; } @@ -123,7 +120,7 @@ class AbstractNode extends Component { componentDidMount() { this.d3Node = select(this.nodeElement); - this.d3Node.data([Node.getPosition(this.props.node)]); + this.d3Node.data([this.props.node.getPosition()]); this.d3Node.call( drag().on('start', this.onDragStart).on('drag', this.onDrag).on('end', this.onDragEnd), ); diff --git a/packages/flow-designer/src/components/node/NodesRenderer.component.tsx b/packages/flow-designer/src/components/node/NodesRenderer.component.tsx index 51a51136c00..42677a5700c 100644 --- a/packages/flow-designer/src/components/node/NodesRenderer.component.tsx +++ b/packages/flow-designer/src/components/node/NodesRenderer.component.tsx @@ -41,7 +41,7 @@ class NodesRenderer extends Component { } render() { - return {Object.values(this.props.nodes).map(this.renderNode)}; + return {this.props.nodes.toArray().map(this.renderNode)}; } } diff --git a/packages/flow-designer/src/components/node/NodesRenderer.test.tsx b/packages/flow-designer/src/components/node/NodesRenderer.test.tsx index 2af888114e9..13b2d5d00e1 100644 --- a/packages/flow-designer/src/components/node/NodesRenderer.test.tsx +++ b/packages/flow-designer/src/components/node/NodesRenderer.test.tsx @@ -1,4 +1,5 @@ import renderer from 'react-test-renderer'; +import { List, Map } from 'immutable'; import NodesRenderer from './NodesRenderer.component'; import { @@ -6,7 +7,7 @@ import { NodeRecord, NodeGraphicalAttributes, } from '../../constants/flowdesigner.model'; -import { NodeRecordMap } from '../../customTypings/index.d'; +import { NodeRecord as NodeRecordType } from '../../customTypings/index.d'; const MockNode = () => MockNodes; @@ -14,15 +15,16 @@ const noOp = () => {}; describe('', () => { it('renders correctly', () => { - const nodes: NodeRecordMap = { - id: new NodeRecord({ + const nodes = Map().set( + 'id', + new NodeRecord({ id: 'id', type: 'id', graphicalAttributes: new NodeGraphicalAttributes({ nodeType: 'id', }), }), - }; + ); const nodeTypeMap = { id: { id: 'id', component: MockNode } }; const tree = renderer .create( @@ -39,16 +41,17 @@ describe('', () => { expect(tree).toMatchSnapshot(); }); it('renders correctly if nested', () => { - const nodes: NodeRecordMap = { - id: new NestedNodeRecord({ + const nodes = Map().set( + 'id', + new NestedNodeRecord({ id: 'id', type: 'id', graphicalAttributes: new NodeGraphicalAttributes({ nodeType: 'id', }), - components: {}, + components: List(), }), - }; + ); const nodeTypeMap = { id: { id: 'id', component: MockNode } }; const tree = renderer .create( diff --git a/packages/flow-designer/src/components/port/PortsRenderer.component.tsx b/packages/flow-designer/src/components/port/PortsRenderer.component.tsx index 252e0e7aa1a..9861a8aafda 100644 --- a/packages/flow-designer/src/components/port/PortsRenderer.component.tsx +++ b/packages/flow-designer/src/components/port/PortsRenderer.component.tsx @@ -10,7 +10,7 @@ function PortsRenderer({ ports, portTypeMap }: { ports: PortRecordMap; portTypeM return ; }; - return {Object.values(ports).map(renderPort)}; + return {ports.toArray().map(renderPort)}; } export default PortsRenderer; diff --git a/packages/flow-designer/src/components/port/PortsRenderer.test.tsx b/packages/flow-designer/src/components/port/PortsRenderer.test.tsx index aa836795128..301c24d02ff 100644 --- a/packages/flow-designer/src/components/port/PortsRenderer.test.tsx +++ b/packages/flow-designer/src/components/port/PortsRenderer.test.tsx @@ -1,20 +1,24 @@ import renderer from 'react-test-renderer'; +import { Map } from 'immutable'; import PortsRenderer from './PortsRenderer.component'; import { PortRecord } from '../../constants/flowdesigner.model'; -import { PortRecordMap } from '../../customTypings/index.d'; +import { Id, PortRecord as PortRecordType } from '../../customTypings/index.d'; const MockPort = () => MockPort; describe('', () => { it('renders correctly', () => { - const ports: PortRecordMap = { - id: new PortRecord({ + const ports = Map().set( + 'id', + new PortRecord({ id: 'id', nodeId: 'nodeId', - graphicalAttributes: { portType: 'id' }, + graphicalAttributes: Map({ + portType: 'id', + }), }), - }; + ); const portTypeMap = { id: { id: 'id', diff --git a/packages/flow-designer/src/constants/flowdesigner.model.ts b/packages/flow-designer/src/constants/flowdesigner.model.ts index d4bea60d933..fda0409af81 100644 --- a/packages/flow-designer/src/constants/flowdesigner.model.ts +++ b/packages/flow-designer/src/constants/flowdesigner.model.ts @@ -1,562 +1,176 @@ /* eslint-disable new-cap */ -import { Size, Position, PortDirection } from '../customTypings/index.d'; +import { Record, Map, List } from 'immutable'; +import { + Size, + Position, + PortDirection, + PortRecord as PortRecordType, +} from '../customTypings/index.d'; export const NONE = 'NONE'; export const SELECTED = 'SELECTED'; export const DROP_TARGET = 'DROP_TARGET'; export const FORBIDDEN_DROP_TARGET = 'FORBIDDEN_DROP_TARGET'; -function getInHelper(obj: any, path: string[]): any { - return path.reduce((v: any, k) => { - if (v == null) return v; - return typeof v.get === 'function' ? v.get(k) : v[k]; - }, obj); -} +export const PositionRecord = Record({ + x: undefined, + y: undefined, +}); -function setInHelper(obj: any, path: string[], value: any): any { - if (path.length === 0) return obj; - if (path.length === 1) { - if (typeof obj.set === 'function') return obj.set(path[0], value); - return { ...obj, [path[0]]: value }; - } - const current = typeof obj.get === 'function' ? obj.get(path[0]) : obj[path[0]]; - const updated = - current != null && typeof current.setIn === 'function' - ? current.setIn(path.slice(1), value) - : setInHelper(current ?? {}, path.slice(1), value); - if (typeof obj.set === 'function') return obj.set(path[0], updated); - return { ...obj, [path[0]]: updated }; -} - -export class PositionRecord { - readonly x: number | undefined; - readonly y: number | undefined; - - constructor({ x, y }: { x?: number; y?: number } = {}) { - this.x = x; - this.y = y; - } - - get(key: string): any { - return (this as any)[key]; - } - - set(key: string, value: any): PositionRecord { - return new PositionRecord({ ...this, [key]: value }); - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): PositionRecord { - return setInHelper(this, path, value) as PositionRecord; - } - - toJS(): Position { - return { x: this.x as number, y: this.y as number }; - } - - toJSON(): Position { - return this.toJS(); - } -} - -export class SizeRecord { - readonly width: number | undefined; - readonly height: number | undefined; - - constructor({ width, height }: { width?: number; height?: number } = {}) { - this.width = width; - this.height = height; - } - - get(key: string): any { - return (this as any)[key]; - } - - set(key: string, value: any): SizeRecord { - return new SizeRecord({ ...this, [key]: value }); - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): SizeRecord { - return setInHelper(this, path, value) as SizeRecord; - } - - toJS(): Size { - return { width: this.width as number, height: this.height as number }; - } - - toJSON(): Size { - return this.toJS(); - } -} +export const SizeRecord = Record({ + width: undefined, + height: undefined, +}); /** TO BE REMOVED */ -export class NodeGraphicalAttributes { - [key: string]: any; - - constructor(params: any = {}) { - // Default for properties so nested setIn paths (properties.startPosition) work - this.properties = {}; - if (params instanceof NodeGraphicalAttributes) { - // Shallow copy: preserves PositionRecord, Map instances etc. - Object.assign(this, params); - } else { - const data = - params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; - Object.assign(this, data); - } - } - - get(key: string): any { - return this[key]; - } - - set(key: string, value: any): NodeGraphicalAttributes { - const nga = new NodeGraphicalAttributes(this); - nga[key] = value; - return nga; - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): NodeGraphicalAttributes { - return setInHelper(this, path, value) as NodeGraphicalAttributes; - } - - merge(values: any): NodeGraphicalAttributes { - const data = values != null && typeof values.toJS === 'function' ? values.toJS() : values || {}; - const nga = new NodeGraphicalAttributes(this); - Object.assign(nga, data); - return nga; - } - - delete(key: string): NodeGraphicalAttributes { - const nga = new NodeGraphicalAttributes(this); - delete nga[key]; - return nga; - } - - deleteIn(path: string[]): NodeGraphicalAttributes { - if (path.length === 0) return this; - if (path.length === 1) return this.delete(path[0]); - const current = this.get(path[0]); - const updated = - current != null && typeof current.deleteIn === 'function' - ? current.deleteIn(path.slice(1)) - : current; - return this.set(path[0], updated); - } - - toJS(): any { - const result: any = {}; - for (const key of Object.keys(this)) { - const val = this[key]; - result[key] = val && typeof val.toJS === 'function' ? val.toJS() : val; - } - return result; - } -} +export const NodeGraphicalAttributes = Record({ + position: new PositionRecord(), + nodeSize: new SizeRecord(), + nodeType: undefined, + label: '', + description: '', + properties: Map(), +}); /** TO BE REMOVED */ -export class NodeData { - [key: string]: any; - - constructor(params: any = {}) { - const data = params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; - Object.assign(this, data); - } - - get(key: string): any { - return this[key]; - } - - set(key: string, value: any): NodeData { - const nd = new NodeData(this); - nd[key] = value; - return nd; - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): NodeData { - return setInHelper(this, path, value) as NodeData; - } -} +export const NodeData = Record({ + properties: Map(), + label: '', + description: '', + datasetInfo: Map(), +}); /** TO BE REMOVED */ -export class LinkGraphicalAttributes { - [key: string]: any; - - constructor(params: any = {}) { - this.properties = {}; - if (params instanceof LinkGraphicalAttributes) { - Object.assign(this, params); - } else { - const data = - params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; - Object.assign(this, data); - } - } - - get(key: string): any { - return this[key]; - } - - set(key: string, value: any): LinkGraphicalAttributes { - const lga = new LinkGraphicalAttributes(this); - lga[key] = value; - return lga; - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): LinkGraphicalAttributes { - return setInHelper(this, path, value) as LinkGraphicalAttributes; - } -} +export const LinkGraphicalAttributes = Record({ + linkType: undefined, + properties: Map(), +}); /** TO BE REMOVED */ -export class LinkData { - [key: string]: any; - - constructor(params: any = {}) { - const data = params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; - Object.assign(this, data); - } - - get(key: string): any { - return this[key]; - } - - set(key: string, value: any): LinkData { - const ld = new LinkData(this); - ld[key] = value; - return ld; - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): LinkData { - return setInHelper(this, path, value) as LinkData; - } -} +export const LinkData = Record({ + properties: Map(), +}); /** TO BE REMOVED */ -export class PortGraphicalAttributes { - [key: string]: any; - - constructor(params: any = {}) { - this.properties = {}; - if (params instanceof PortGraphicalAttributes) { - Object.assign(this, params); - } else { - const data = - params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; - Object.assign(this, data); - } - } - - get(key: string): any { - return this[key]; - } - - set(key: string, value: any): PortGraphicalAttributes { - const pga = new PortGraphicalAttributes(this); - pga[key] = value; - return pga; - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): PortGraphicalAttributes { - return setInHelper(this, path, value) as PortGraphicalAttributes; - } -} +export const PortGraphicalAttributes = Record({ + position: PositionRecord, + portType: undefined, + properties: Map(), +}); /** TO BE REMOVED */ -export class PortData { - [key: string]: any; - - constructor(params: any = {}) { - const data = params != null && typeof params.toJS === 'function' ? params.toJS() : params || {}; - Object.assign(this, data); - } - - get(key: string): any { - return this[key]; - } - - set(key: string, value: any): PortData { - const pd = new PortData(this); - pd[key] = value; - return pd; - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): PortData { - return setInHelper(this, path, value) as PortData; - } -} - -export class NodeRecord { - readonly id: string | undefined; - readonly type: string | undefined; - readonly data: any; - readonly graphicalAttributes: any; - - constructor({ - id, - type, - data, - graphicalAttributes, - }: { - id?: string; - type?: string; - data?: any; - graphicalAttributes?: any; - } = {}) { - this.id = id; - this.type = type; - this.data = data ?? new NodeData(); - // Default graphicalAttributes as NodeGraphicalAttributes for API compatibility - this.graphicalAttributes = graphicalAttributes ?? new NodeGraphicalAttributes(); - } - - get(key: string): any { - return (this as any)[key]; - } - - set(key: string, value: any): NodeRecord { - return new NodeRecord({ ...this, [key]: value }); - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): NodeRecord { - return setInHelper(this, path, value) as NodeRecord; - } - - /** methods TO BE REMOVED */ +export const PortData = Record({ + properties: Map(), + flowType: undefined, +}); + +const nodeRecordDefinition = { + id: undefined, + type: undefined, + data: Map({ + properties: Map(), + label: '', + description: '', + datasetInfo: Map(), + }), + graphicalAttributes: Map({ + position: new PositionRecord(), + nodeSize: new SizeRecord(), + nodeType: undefined, + label: '', + description: '', + properties: Map(), + }), +}; + +export class NodeRecord extends Record({ + ...nodeRecordDefinition, getPosition(): Position { - const pos = this.graphicalAttributes?.get - ? this.graphicalAttributes.get('position') - : this.graphicalAttributes?.position; - return pos; - } + return this.getIn(['graphicalAttributes', 'position']); + }, getSize(): Size { - const size = this.graphicalAttributes?.get - ? this.graphicalAttributes.get('nodeSize') - : this.graphicalAttributes?.nodeSize; - return size; - } + return this.getIn(['graphicalAttributes', 'nodeSize']); + }, getNodeType(): string { - const nt = this.graphicalAttributes?.get - ? this.graphicalAttributes.get('nodeType') - : this.graphicalAttributes?.nodeType; - return nt; - } -} - -export class NestedNodeRecord extends NodeRecord { - readonly components: any; - - constructor( - params: { - id?: string; - type?: string; - data?: any; - graphicalAttributes?: any; - components?: any; - } = {}, - ) { - super(params); - this.components = params.components ?? {}; - } - - set(key: string, value: any): NestedNodeRecord { - return new NestedNodeRecord({ ...this, [key]: value }); - } - - setIn(path: string[], value: any): NestedNodeRecord { - return setInHelper(this, path, value) as NestedNodeRecord; - } - - /** methods TO BE REMOVED */ - getComponents(): any { - return this.components; - } - - setComponents(components: any): NestedNodeRecord { - return new NestedNodeRecord({ ...this, components }); - } -} - -export class LinkRecord { - readonly id: string | undefined; - readonly sourceId: string | undefined; - readonly targetId: string | undefined; - readonly data: any; - readonly graphicalAttributes: any; - - constructor({ - id, - sourceId, - targetId, - data, - graphicalAttributes, - }: { - id?: string; - sourceId?: string; - targetId?: string; - data?: any; - graphicalAttributes?: any; - } = {}) { - this.id = id; - this.sourceId = sourceId; - this.targetId = targetId; - this.data = data ?? new LinkData(); - this.graphicalAttributes = graphicalAttributes ?? new LinkGraphicalAttributes(); - } - - get(key: string): any { - return (this as any)[key]; - } - - set(key: string, value: any): LinkRecord { - return new LinkRecord({ ...this, [key]: value }); - } + return this.getIn(['graphicalAttributes', 'nodeType']); + }, +}) {} + +export class NestedNodeRecord extends Record({ + ...nodeRecordDefinition, + components: List(), + getComponents(): Map { + return this.get('components'); + }, + setComponents(components: Map) { + return this.set('components', components); + }, + getPosition(): Position { + return this.getIn(['graphicalAttributes', 'position']); + }, - getIn(path: string[]): any { - return getInHelper(this, path); - } + getSize(): Size { + return this.getIn(['graphicalAttributes', 'nodeSize']); + }, - setIn(path: string[], value: any): LinkRecord { - return setInHelper(this, path, value) as LinkRecord; - } + getNodeType(): string { + return this.getIn(['graphicalAttributes', 'nodeType']); + }, +}) {} + +export const LinkRecord = Record({ + id: undefined, + sourceId: undefined, + targetId: undefined, + data: Map({ + properties: Map(), + }), + graphicalAttributes: Map({ + linkType: undefined, + properties: Map(), + }), /** methods TO BE REMOVED */ getLinkType(): string { - const lt = this.graphicalAttributes?.get - ? this.graphicalAttributes.get('linkType') - : this.graphicalAttributes?.linkType; - return lt; - } -} - -export class PortRecord { - readonly id: string | undefined; - readonly nodeId: string | undefined; - readonly data: any; - readonly graphicalAttributes: any; - - constructor({ - id, - nodeId, - data, - graphicalAttributes, - }: { - id?: string; - nodeId?: string; - data?: any; - graphicalAttributes?: any; - } = {}) { - this.id = id; - this.nodeId = nodeId; - this.data = data ?? new PortData(); - this.graphicalAttributes = graphicalAttributes ?? new PortGraphicalAttributes(); - } - - get(key: string): any { - return (this as any)[key]; - } - - set(key: string, value: any): PortRecord { - return new PortRecord({ ...this, [key]: value }); - } - - getIn(path: string[]): any { - return getInHelper(this, path); - } - - setIn(path: string[], value: any): PortRecord { - return setInHelper(this, path, value) as PortRecord; - } + return this.getIn(['graphicalAttributes', 'linkType']); + }, +}); + +export const PortRecord = Record({ + id: undefined, + nodeId: undefined, + data: Map({ + properties: Map(), + flowType: undefined, + }), + graphicalAttributes: Map({ + position: PositionRecord, + portType: undefined, + properties: Map(), + }), /** methods TO BE REMOVED */ getPosition(): Position { - const pos = this.graphicalAttributes?.get - ? this.graphicalAttributes.get('position') - : this.graphicalAttributes?.position; - return pos; - } - - setPosition(position: Position): PortRecord { - return new PortRecord({ - ...this, - graphicalAttributes: this.graphicalAttributes?.set - ? this.graphicalAttributes.set('position', position) - : { ...this.graphicalAttributes, position }, - }); - } - + return this.getIn(['graphicalAttributes', 'position']); + }, + setPosition(position: Position): PortRecordType { + return this.setIn(['graphicalAttributes', 'position'], position); + }, getPortType(): string { - const pt = this.graphicalAttributes?.get - ? this.graphicalAttributes.get('portType') - : this.graphicalAttributes?.portType; - return pt; - } - + return this.getIn(['graphicalAttributes', 'portType']); + }, getPortDirection(): PortDirection { - const direction = this.graphicalAttributes?.getIn - ? this.graphicalAttributes.getIn(['properties', 'type']) - : this.graphicalAttributes?.properties?.type; - return direction; - } - + return this.getIn(['graphicalAttributes', 'properties', 'type']); + }, getPortFlowType(): string { - const ft = this.data?.get ? this.data.get('flowType') : this.data?.flowType; - return ft; - } - + return this.getIn(['data', 'flowType']); + }, getIndex(): number { - const idx = this.graphicalAttributes?.getIn - ? this.graphicalAttributes.getIn(['properties', 'index']) - : this.graphicalAttributes?.properties?.index; - return idx; - } - - setIndex(index: number): PortRecord { - return new PortRecord({ - ...this, - graphicalAttributes: this.graphicalAttributes?.setIn - ? this.graphicalAttributes.setIn(['properties', 'index'], index) - : { - ...this.graphicalAttributes, - properties: { ...this.graphicalAttributes?.properties, index }, - }, - }); - } -} + return this.getIn(['graphicalAttributes', 'properties', 'index']); + }, + setIndex(index: number): PortRecordType { + return this.setIn(['graphicalAttributes', 'properties', 'index'], index); + }, +}); diff --git a/packages/flow-designer/src/constants/flowdesigner.proptypes.ts b/packages/flow-designer/src/constants/flowdesigner.proptypes.ts index 0f04d68a91a..83005e828a6 100644 --- a/packages/flow-designer/src/constants/flowdesigner.proptypes.ts +++ b/packages/flow-designer/src/constants/flowdesigner.proptypes.ts @@ -1,23 +1,24 @@ import PropTypes from 'prop-types'; +import { recordOf } from 'react-immutable-proptypes'; -export const NodeType = PropTypes.shape({ +export const NodeType = recordOf({ id: PropTypes.string.isRequired, - position: PropTypes.shape({ + position: recordOf({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), }); -export const PortType = PropTypes.shape({ +export const PortType = recordOf({ id: PropTypes.string.isRequired, nodeId: PropTypes.string.isRequired, - position: PropTypes.shape({ + position: recordOf({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }), }); -export const LinkType = PropTypes.shape({ +export const LinkType = recordOf({ id: PropTypes.string.isRequired, sourceId: PropTypes.string.isRequired, targetId: PropTypes.string.isRequired, diff --git a/packages/flow-designer/src/customTypings/index.d.ts b/packages/flow-designer/src/customTypings/index.d.ts index b2969a9ee8f..43f1957527d 100644 --- a/packages/flow-designer/src/customTypings/index.d.ts +++ b/packages/flow-designer/src/customTypings/index.d.ts @@ -1,3 +1,4 @@ +import { Record, Map } from 'immutable'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; /** $BASIC */ @@ -63,7 +64,7 @@ export interface Node { type: string; data: NodeData; graphicalAttributes: NodeGraphicalAttributes; - components?: Record; + components?: List; } export interface LinkGraphicalAttributes { @@ -84,60 +85,60 @@ export interface Link { } /** $RECORDS */ -export interface PositionRecord { - readonly x: number | undefined; - readonly y: number | undefined; -} - -export interface SizeRecord { - readonly width: number | undefined; - readonly height: number | undefined; -} - -export interface PortRecord { - readonly id: Id; - readonly nodeId: Id; - readonly data: Record; - readonly graphicalAttributes: Record; -} - -export interface NodeRecord { - readonly id: Id; - readonly type: string; - readonly data: Record; - readonly graphicalAttributes: any; -} - -export interface NestedNodeRecord extends NodeRecord { - readonly components: any; -} - -export interface LinkRecord { - readonly id: Id; - readonly sourceId: Id; - readonly targetId: Id; - readonly data: Record; - readonly graphicalAttributes: Record; -} +export type PositionRecord = Record & Position; + +export type SizeRecord = Record & Size; + +export type PortRecord = Record & { + getPosition: () => Position; + getPortType: () => string; + getPortDirection: () => PortDirection; + getPortFlowType: () => string; + getIndex: () => number; + setIndex: (index: number) => PortRecord; +} & Port; + +// TODO add record +export type NodeRecord = Record & { + getPosition: () => Position; + getSize: () => Size; + getNodeType: () => string; +} & Node; + +export type NestedNodeRecord = Record & { + getPosition: () => Position; + getSize: () => Size; + getNodeType: () => string; +} & Node; + +export type LinkRecord = Record & { + getLinkType: () => string; +} & Link; /** $STATE */ -export type PortRecordMap = Record; -export type NodeRecordMap = Record; -export type LinkRecordMap = Record; +export type PortRecordMap = Map; +export type NodeRecordMap = Map; +export type LinkRecordMap = Map; + +type GetStateNodes = (selector: ['nodes', Id]) => NodeRecord; +type GetStatePorts = (selector: ['ports', Id]) => PortRecord; +type GetStateLinks = (selector: ['links', Id]) => LinkRecord; +type GetStateIn = (selector: ['in', Id]) => Id; +type GetStateOut = (selector: ['out', Id]) => Id; export type State = { - in: Record>>; - parents: Record>; + in: Map>; + parents: Map>; transform: Transform; transformToApply?: Transform; - out: Record>>; - nodes: Record; - ports: Record; - childrens: Record>; - nodeTypes: Record; - links: Record; -}; + out: Map>; + nodes: Map>; + ports: Map>; + children: Map>; + nodeTypes: Map>; + links: Map>; +} & Map & { getIn: getStateNodes | getStatePorts | getStateLinks | getStateIn | getStateOut }; /** $ACTIONS */ export interface PortActionAdd { diff --git a/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap index de9872a900b..f21939035b6 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/flow.reducer.test.ts.snap @@ -1,247 +1,291 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`FLOWDESIGNER_FLOW_ADD_ELEMENTS > should batch many elements creation 1`] = ` -{ - "childrens": { - "node2": {}, - "nodeId": {}, - }, - "in": { - "node2": {}, - "nodeId": {}, - }, - "links": {}, - "nodeTypes": {}, - "nodes": { - "node2": NodeRecord { - "data": NodeData { - "properties": {}, +Immutable.Map { + "in": Immutable.Map { + "nodeId": Immutable.Map {}, + "node2": Immutable.Map {}, + }, + "transformToApply": undefined, + "parents": Immutable.Map { + "nodeId": Immutable.Map {}, + "node2": Immutable.Map {}, + }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map { + "nodeId": Immutable.Map { + "portId": Immutable.Map {}, + }, + "node2": Immutable.Map {}, + }, + "nodes": Immutable.Map { + "nodeId": Immutable.Record { + "id": "nodeId", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": 10, - "width": 10, - }, - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, + "nodeSize": Immutable.Record { + "width": 10, + "height": 10, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "node2": Immutable.Record { "id": "node2", "type": undefined, - }, - "nodeId": NodeRecord { - "data": NodeData { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": 10, - "width": 10, - }, - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, + "nodeSize": Immutable.Record { + "width": 10, + "height": 10, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, - "id": "nodeId", - "type": undefined, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": { - "node2": {}, - "nodeId": { - "portId": {}, - }, - }, - "parents": { - "node2": {}, - "nodeId": {}, - }, - "ports": { - "portId": PortRecord { - "data": PortData { + "ports": Immutable.Map { + "portId": Immutable.Record { + "id": "portId", + "nodeId": "nodeId", + "data": Immutable.Map { "flowType": "batch", - "properties": {}, + "properties": Immutable.Map {}, }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { - "x": undefined, - "y": undefined, - }, - "properties": { + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { "index": 0, "type": "OUTGOING", }, + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, }, - "id": "portId", - "nodeId": "nodeId", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "childrens": Immutable.Map { + "nodeId": Immutable.Map {}, + "node2": Immutable.Map {}, }, - "transformToApply": undefined, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; exports[`FLOWDESIGNER_FLOW_ADD_ELEMENTS > should batch one element creation 1`] = ` -{ - "childrens": { - "nodeId": {}, - }, - "in": { - "nodeId": {}, - }, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": NodeRecord { - "data": NodeData { - "properties": {}, +Immutable.Map { + "in": Immutable.Map { + "nodeId": Immutable.Map {}, + }, + "transformToApply": undefined, + "parents": Immutable.Map { + "nodeId": Immutable.Map {}, + }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map { + "nodeId": Immutable.Map {}, + }, + "nodes": Immutable.Map { + "nodeId": Immutable.Record { + "id": "nodeId", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": 10, - "width": 10, - }, - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, + "nodeSize": Immutable.Record { + "width": 10, + "height": 10, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, - "id": "nodeId", - "type": undefined, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": { - "nodeId": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map { + "nodeId": Immutable.Map {}, + }, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`FLOWDESIGNER_FLOW_LOAD should reset old flow state and load news not touching flow config > should load elements 1`] = ` +Immutable.Map { + "in": Immutable.Map { + "nodeId": Immutable.Map {}, + "node2": Immutable.Map {}, }, - "parents": { - "nodeId": {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "nodeId": Immutable.Map {}, + "node2": Immutable.Map {}, }, - "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`FLOWDESIGNER_FLOW_LOAD should reset old flow state and load news not touching flow config > should load elements 1`] = ` -{ - "childrens": { - "node2": {}, - "nodeId": {}, - }, - "in": { - "node2": {}, - "nodeId": {}, - }, - "links": {}, - "nodeTypes": {}, - "nodes": { - "node2": NodeRecord { - "data": NodeData { - "properties": {}, + "out": Immutable.Map { + "nodeId": Immutable.Map { + "portId": Immutable.Map {}, + }, + "node2": Immutable.Map {}, + }, + "nodes": Immutable.Map { + "nodeId": Immutable.Record { + "id": "nodeId", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": 10, - "width": 10, - }, - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, + "nodeSize": Immutable.Record { + "width": 10, + "height": 10, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "node2": Immutable.Record { "id": "node2", "type": undefined, - }, - "nodeId": NodeRecord { - "data": NodeData { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": 10, - "width": 10, - }, - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, + "nodeSize": Immutable.Record { + "width": 10, + "height": 10, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, - "id": "nodeId", - "type": undefined, - }, - }, - "out": { - "node2": {}, - "nodeId": { - "portId": {}, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "parents": { - "node2": {}, - "nodeId": {}, - }, - "ports": { - "portId": PortRecord { - "data": PortData { - "properties": {}, + "ports": Immutable.Map { + "portId": Immutable.Record { + "id": "portId", + "nodeId": "nodeId", + "data": Immutable.Map { + "properties": Immutable.Map {}, }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { - "x": undefined, - "y": undefined, - }, - "properties": { + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { "index": 0, "type": "OUTGOING", }, + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, }, - "id": "portId", - "nodeId": "nodeId", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "childrens": Immutable.Map { + "nodeId": Immutable.Map {}, + "node2": Immutable.Map {}, }, - "transformToApply": undefined, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; exports[`FLOWDESIGNER_PAN_TO set a calculated transformation into transformToApply > 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": {}, - "out": {}, - "parents": {}, - "ports": {}, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, +Immutable.Map { + "in": Immutable.Map {}, "transformToApply": Transform { "k": 1, "x": -400, "y": -400, }, + "parents": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map {}, + "nodes": Immutable.Map {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; diff --git a/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap index c75e0256b19..7fc45954769 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/link.reducer.test.ts.snap @@ -1,1137 +1,2312 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`check linkreducer > FLOWDESIGNER_LINK_ADD should add a new link to the state 1`] = ` -{ - "childrens": { - "id1": { - "id2": "id2", - }, - "id2": {}, - "id3": {}, - }, - "in": { - "id2": { - "id2": { +Immutable.Map { + "in": Immutable.Map { + "id2": Immutable.Map { + "id2": Immutable.Map { "id2": "id2", }, }, }, - "links": { - "id1": LinkRecord { - "data": LinkData { - "attr": "attr", - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { - "attr": "attr", - }, - }, - "id": "id1", - "sourceId": "id1", - "targetId": "id2", + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", }, - "id2": LinkRecord { - "data": LinkData { - "properties": {}, - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": {}, + "id3": Immutable.Map {}, + }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map { + "id1": Immutable.Map { + "id1": Immutable.Map { + "id2": "id2", }, - "id": "id2", - "sourceId": "id1", - "targetId": "id2", }, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, - }, - }, - "out": { - "id1": { - "id1": { - "id2": "id2", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_ADD should add a new link to the state 2`] = ` -{ - "childrens": { - "id1": { + "childrens": Immutable.Map { + "id1": Immutable.Map { "id2": "id2", }, - "id2": { - "id3": "id3", - }, - "id3": {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, }, - "in": { - "id2": { - "id2": { - "id2": "id2", - }, - }, - "id3": { - "id5": { - "id3": "id3", - }, - }, - }, - "links": { - "id1": LinkRecord { - "data": LinkData { + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + "data": Immutable.Map { "attr": "attr", }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { "attr": "attr", }, }, - "id": "id1", - "sourceId": "id1", - "targetId": "id2", + "getLinkType": [Function], }, - "id2": LinkRecord { - "data": LinkData { - "properties": {}, - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": {}, - }, + "id2": Immutable.Record { "id": "id2", "sourceId": "id1", "targetId": "id2", - }, - "id3": LinkRecord { - "data": LinkData { - "properties": {}, + "data": Immutable.Record { + "properties": Immutable.Map {}, }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": {}, + "graphicalAttributes": Immutable.Record { + "linkType": undefined, + "properties": Immutable.Map {}, }, - "id": "id3", - "sourceId": "id3", - "targetId": "id5", + "getLinkType": [Function], }, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_ADD should add a new link to the state 2`] = ` +Immutable.Map { + "in": Immutable.Map { + "id2": Immutable.Map { + "id2": Immutable.Map { + "id2": "id2", }, - "id": "id1", - "type": undefined, }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "id3": Immutable.Map { + "id5": Immutable.Map { + "id3": "id3", }, - "id": "id2", - "type": undefined, }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, - "id": "id3", - "type": undefined, + }, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", + }, + "id3": Immutable.Map { + "id2": "id2", }, }, - "out": { - "id1": { - "id1": { + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map { + "id1": Immutable.Map { + "id1": Immutable.Map { "id2": "id2", }, }, - "id2": { - "id3": { + "id2": Immutable.Map { + "id3": Immutable.Map { "id3": "id3", }, }, }, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id3": { - "id2": "id2", + "id2": Immutable.Record { + "id": "id2", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, + "id3": Immutable.Record { + "id": "id3", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "childrens": Immutable.Map { + "id1": Immutable.Map { + "id2": "id2", + }, + "id2": Immutable.Map { + "id3": "id3", + }, + "id3": Immutable.Map {}, + }, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + "data": Immutable.Map { + "attr": "attr", + }, + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { + "attr": "attr", + }, + }, + "getLinkType": [Function], + }, + "id2": Immutable.Record { + "id": "id2", + "sourceId": "id1", + "targetId": "id2", + "data": Immutable.Record { + "properties": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "linkType": undefined, + "properties": Immutable.Map {}, + }, + "getLinkType": [Function], + }, + "id3": Immutable.Record { + "id": "id3", + "sourceId": "id3", + "targetId": "id5", + "data": Immutable.Record { + "properties": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "linkType": undefined, + "properties": Immutable.Map {}, + }, + "getLinkType": [Function], + }, }, - "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE should remove link from state 1`] = ` -{ - "childrens": { - "id1": {}, - "id2": {}, - "id3": {}, +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, }, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": { - "id1": {}, - "id2": {}, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "childrens": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, }, - "transformToApply": undefined, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE_DATA should remove 'attr'from data map 1`] = ` -{ - "childrens": { - "id1": { - "id2": "id2", +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", }, - "id2": {}, - "id3": {}, + "id3": Immutable.Map {}, }, - "in": {}, - "links": { - "id1": LinkRecord { - "data": LinkData { - "attr": "attr", - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { - "attr": "attr", - }, - }, - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - }, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE_GRAPHICAL_ATTRIBUTES should remove a specific attributes from attr map 1`] = ` -{ - "childrens": { - "id1": { + "childrens": Immutable.Map { + "id1": Immutable.Map { "id2": "id2", }, - "id2": {}, - "id3": {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, }, - "in": {}, - "links": { - "id1": LinkRecord { - "data": LinkData { - "attr": "attr", - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": {}, - }, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "sourceId": "id1", "targetId": "id2", + "data": Immutable.Map { + "attr": "attr", + }, + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { + "attr": "attr", + }, + }, + "getLinkType": [Function], }, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_REMOVE_GRAPHICAL_ATTRIBUTES should remove a specific attributes from attr map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", + }, + "id3": Immutable.Map {}, + }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { + "id": "id5", + "nodeId": "id3", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { + "id": "id6", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + }, + "childrens": Immutable.Map { + "id1": Immutable.Map { + "id2": "id2", }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, + }, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + "data": Immutable.Map { + "attr": "attr", }, - "id": "id5", - "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map {}, }, - "id": "id6", - "nodeId": "id3", + "getLinkType": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_SET_DATA should add a data attribute type: 'test' from data map 1`] = ` -{ - "childrens": { - "id1": { - "id2": "id2", +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", }, - "id2": {}, - "id3": {}, + "id3": Immutable.Map {}, }, - "in": {}, - "links": { - "id1": LinkRecord { - "data": LinkData { - "attr": "attr", - "type": "test", - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { - "attr": "attr", - }, - }, - "id": "id1", - "sourceId": "id1", - "targetId": "id2", - }, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES should merge attributes within link attr property 1`] = ` -{ - "childrens": { - "id1": { + "childrens": Immutable.Map { + "id1": Immutable.Map { "id2": "id2", }, - "id2": {}, - "id3": {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, }, - "in": {}, - "links": { - "id1": LinkRecord { - "data": LinkData { + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + "data": Immutable.Map { "attr": "attr", + "type": "test", }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { "attr": "attr", - "selected": false, }, }, - "id": "id1", - "sourceId": "id1", - "targetId": "id2", + "getLinkType": [Function], }, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES should merge attributes within link attr property 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", + }, + "id3": Immutable.Map {}, + }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, -} -`; - -exports[`check linkreducer > FLOWDESIGNER_LINK_SET_SOURCE switch source to correct port if it exist 1`] = ` -{ - "childrens": { - "id1": { + "childrens": Immutable.Map { + "id1": Immutable.Map { "id2": "id2", }, - "id2": {}, - "id3": {}, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, }, - "in": {}, - "links": { - "id1": LinkRecord { - "data": LinkData { + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id1", + "targetId": "id2", + "data": Immutable.Map { "attr": "attr", }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { "attr": "attr", }, + "selected": false, }, - "id": "id1", - "sourceId": "id4", - "targetId": "id2", + "getLinkType": [Function], + }, + }, +} +`; + +exports[`check linkreducer > FLOWDESIGNER_LINK_SET_SOURCE switch source to correct port if it exist 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", }, + "id3": Immutable.Map {}, + }, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "out": Immutable.Map { + "id1": Immutable.Map { + "id4": Immutable.Map { + "id1": "id1", }, + }, + }, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, - }, - }, - "out": { - "id1": { - "id4": { - "id1": "id1", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "childrens": Immutable.Map { + "id1": Immutable.Map { + "id2": "id2", + }, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, + }, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id4", + "targetId": "id2", + "data": Immutable.Map { + "attr": "attr", + }, + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { + "attr": "attr", + }, + }, + "getLinkType": [Function], + }, }, - "transformToApply": undefined, } `; exports[`check linkreducer > FLOWDESIGNER_LINK_SET_TARGET switch target to correct port if it exist 1`] = ` -{ - "childrens": { - "id1": { - "id3": "id3", - }, - "id2": {}, - "id3": {}, - }, - "in": { - "id3": { - "id5": { +Immutable.Map { + "in": Immutable.Map { + "id3": Immutable.Map { + "id5": Immutable.Map { "id1": "id1", }, }, }, - "links": { - "id1": LinkRecord { - "data": LinkData { - "attr": "attr", - }, - "graphicalAttributes": LinkGraphicalAttributes { - "properties": { - "attr": "attr", - }, - }, - "id": "id1", - "sourceId": "id1", - "targetId": "id5", + "transformToApply": undefined, + "parents": Immutable.Map { + "id1": Immutable.Map {}, + "id2": Immutable.Map { + "id1": "id1", }, + "id3": Immutable.Map {}, }, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, - }, + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, - }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "properties": {}, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id3": Immutable.Record { "id": "id3", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": { - "id1": {}, - "id2": { - "id1": "id1", - }, - "id3": {}, - }, - "ports": { - "id1": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "ports": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "nodeId": "id1", - }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id2": Immutable.Record { "id": "id2", "nodeId": "id2", - }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id3": Immutable.Record { "id": "id3", "nodeId": "id2", - }, - "id4": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id4": Immutable.Record { "id": "id4", "nodeId": "id1", - }, - "id5": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id5": Immutable.Record { "id": "id5", "nodeId": "id3", - }, - "id6": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "properties": {}, - }, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], + }, + "id6": Immutable.Record { "id": "id6", - "nodeId": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": [Function], + "portType": undefined, + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, + "childrens": Immutable.Map { + "id1": Immutable.Map { + "id3": "id3", + }, + "id2": Immutable.Map {}, + "id3": Immutable.Map {}, + }, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "sourceId": "id1", + "targetId": "id5", + "data": Immutable.Map { + "attr": "attr", + }, + "graphicalAttributes": Immutable.Map { + "properties": Immutable.Map { + "attr": "attr", + }, + }, + "getLinkType": [Function], + }, }, - "transformToApply": undefined, } `; diff --git a/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap index 02ffccbc8c4..37225280d00 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/node.reducer.test.ts.snap @@ -1,559 +1,728 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Check node reducer > FLOWDESIGNER_NODE_ADD add a new node to the node collection with the right type 1`] = ` -{ - "childrens": { - "id": {}, +Immutable.Map { + "in": Immutable.Map { + "id": Immutable.Map {}, }, - "in": { - "id": {}, + "transformToApply": undefined, + "parents": Immutable.Map { + "id": Immutable.Map {}, }, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id": NodeRecord { - "data": NodeData { - "properties": {}, - }, - "graphicalAttributes": NodeGraphicalAttributes { - "name": "test", - "nodePosition": { - "x": 10, - "y": 10, - }, - "nodeSize": { - "height": undefined, - "width": undefined, - }, - "position": { + "transform": { + "k": 1, + "x": 0, + "y": 0, + }, + "out": Immutable.Map { + "id": Immutable.Map {}, + }, + "nodes": Immutable.Map { + "id": Immutable.Record { + "id": "id", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": undefined, "y": undefined, }, - "properties": {}, - "type": "MY_NODE_TYPE", - }, - "id": "id", - "type": undefined, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": { - "id": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map { + "id": Immutable.Map {}, }, - "parents": { - "id": {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_ADD properly add a new node to the node collection 1`] = ` +Immutable.Map { + "in": Immutable.Map { + "id": Immutable.Map {}, + }, + "transformToApply": undefined, + "parents": Immutable.Map { + "id": Immutable.Map {}, }, - "ports": {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_ADD properly add a new node to the node collection 1`] = ` -{ - "childrens": { - "id": {}, + "out": Immutable.Map { + "id": Immutable.Map {}, }, - "in": { - "id": {}, - }, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id": NodeRecord { - "data": NodeData { - "properties": {}, - }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": undefined, - "width": undefined, - }, - "position": { + "nodes": Immutable.Map { + "id": Immutable.Record { + "id": "id", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - }, - "id": "id", - "type": undefined, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": { - "id": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map { + "id": Immutable.Map {}, }, - "parents": { - "id": {}, - }, - "ports": {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_MOVE update node position 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_MOVE update node position 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "type1", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": true, - "type": "type1", - }, - "id": "id1", - "type": "type1", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 50, "y": 50, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE should remove node from node collection 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE should remove node from node collection 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id2": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_DATA should remove {type} attribute to node data map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_DATA should remove {type} attribute to node data map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "type1", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": true, - "type": "type1", - }, - "id": "id1", - "type": "type1", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES should remove {selected} attribute to node graphicalAttributes map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES should remove {selected} attribute to node graphicalAttributes map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "type1", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": true, - "type": "type1", - }, - "id": "id1", - "type": "type1", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_DATA should add { type: 'string' } attribute to node data map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_DATA should add { type: 'string' } attribute to node data map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "type1", + "data": Immutable.Map { "type": "string", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": true, - "type": "type1", - }, - "id": "id1", - "type": "type1", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES should add { selected: false } attribute to node graphicalAttributes map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES should add { selected: false } attribute to node graphicalAttributes map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "type1", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type1", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map { + "selected": false, + }, }, - "id": "id1", - "type": "type1", + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_SIZE update node size property 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_SIZE update node size property 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "type1", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "nodeSize": { - "height": 200, - "width": 200, - }, - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": true, - "type": "type1", - }, - "id": "id1", - "type": "type1", + "nodeSize": Immutable.Record { + "width": 200, + "height": 200, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check node reducer > FLOWDESIGNER_NODE_SET_TYPE update node type 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check node reducer > FLOWDESIGNER_NODE_SET_TYPE update node type 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData { + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { + "id": "id1", + "type": "nodetype", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": true, - "type": "type1", - }, - "id": "id1", - "type": "nodetype", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id2": NodeRecord { - "data": NodeData { + "id2": Immutable.Record { + "id": "id2", + "type": "type2", + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "selected": false, - "type": "type2", - }, - "id": "id2", - "type": "type2", + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`FLOWDESIGNER_NODE_APPLY_MOVEMENT > should apply the same relative movement to each node listed 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`FLOWDESIGNER_NODE_APPLY_MOVEMENT > should apply the same relative movement to each node listed 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "id1": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { - "x": 20, - "y": 15, - }, - "properties": {}, - }, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "id1": Immutable.Record { "id": "id1", "type": undefined, - }, - "id2": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 20, "y": 15, }, - "properties": {}, - }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], + }, + "id2": Immutable.Record { "id": "id2", "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { + "x": 20, + "y": 15, + }, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, - "id3": NodeRecord { - "data": NodeData {}, - "graphicalAttributes": NodeGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "type": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "label": "", + "description": "", + "datasetInfo": Immutable.Map {}, + }, + "graphicalAttributes": Immutable.Record { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - }, - "id": "id3", - "type": undefined, + "nodeSize": Immutable.Record { + "width": undefined, + "height": undefined, + }, + "nodeType": undefined, + "label": "", + "description": "", + "properties": Immutable.Map {}, + }, + "getPosition": [Function], + "getSize": [Function], + "getNodeType": [Function], }, }, - "out": {}, - "parents": {}, - "ports": {}, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, + "ports": Immutable.Map {}, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; diff --git a/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap b/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap index f1f9c110f40..c4be4977e59 100644 --- a/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap +++ b/packages/flow-designer/src/reducers/__snapshots__/port.reducer.test.ts.snap @@ -1,555 +1,760 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Check port reducer > FLOWDESIGNER_PORT_ADD properly add the port to the port Map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "out": { - "nodeId": { - "portId": {}, + "out": Immutable.Map { + "nodeId": Immutable.Map { + "portId": Immutable.Map {}, }, }, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, + }, + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "type": "test", }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "portId": PortRecord { - "data": PortData { + "portId": Immutable.Record { + "id": "portId", + "nodeId": "nodeId", + "data": Immutable.Map { "flowType": "string", - "properties": {}, + "properties": Immutable.Map {}, }, - "graphicalAttributes": PortGraphicalAttributes { + "graphicalAttributes": Immutable.Map { "portType": "portType", - "position": { - "x": undefined, - "y": undefined, - }, - "properties": { + "properties": Immutable.Map { "index": 1, "type": "OUTGOING", }, + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, }, - "id": "portId", - "nodeId": "nodeId", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; exports[`Check port reducer > FLOWDESIGNER_PORT_ADDS to add multiple ports (portId1, portId2) to port collection 1`] = ` -{ - "childrens": {}, - "in": { - "nodeId": { - "portId2": {}, - "portId3": {}, +Immutable.Map { + "in": Immutable.Map { + "nodeId": Immutable.Map { + "portId2": Immutable.Map {}, + "portId3": Immutable.Map {}, }, }, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, + "transform": { + "k": 1, + "x": 0, + "y": 0, }, - "out": { - "nodeId": { - "portId1": {}, + "out": Immutable.Map { + "nodeId": Immutable.Map { + "portId1": Immutable.Map {}, }, }, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, + }, + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "type": "test", }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "portId1": PortRecord { - "data": PortData { + "portId1": Immutable.Record { + "id": "portId1", + "nodeId": "nodeId", + "data": Immutable.Map { "flowType": "string", - "properties": {}, + "properties": Immutable.Map {}, }, - "graphicalAttributes": PortGraphicalAttributes { + "graphicalAttributes": Immutable.Map { "portType": "portType", - "position": { - "x": undefined, - "y": undefined, - }, - "properties": { + "properties": Immutable.Map { "index": 0, "type": "OUTGOING", }, + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, }, - "id": "portId1", - "nodeId": "nodeId", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "portId2": PortRecord { - "data": PortData { + "portId2": Immutable.Record { + "id": "portId2", + "nodeId": "nodeId", + "data": Immutable.Map { "flowType": "string", - "properties": {}, + "properties": Immutable.Map {}, }, - "graphicalAttributes": PortGraphicalAttributes { + "graphicalAttributes": Immutable.Map { "portType": "portType", - "position": { - "x": undefined, - "y": undefined, - }, - "properties": { + "properties": Immutable.Map { "index": 0, "type": "INCOMING", }, + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, }, - "id": "portId2", - "nodeId": "nodeId", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "portId3": PortRecord { - "data": PortData { + "portId3": Immutable.Record { + "id": "portId3", + "nodeId": "nodeId", + "data": Immutable.Map { "flowType": "string", - "properties": {}, + "properties": Immutable.Map {}, }, - "graphicalAttributes": PortGraphicalAttributes { + "graphicalAttributes": Immutable.Map { "portType": "portType", - "position": { - "x": undefined, - "y": undefined, - }, - "properties": { + "properties": Immutable.Map { "index": 1, "type": "INCOMING", }, + "position": Immutable.Record { + "x": undefined, + "y": undefined, + }, }, - "id": "portId3", - "nodeId": "nodeId", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id1 from ports collection 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id1 from ports collection 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, }, - "out": {}, - "parents": {}, - "ports": { - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "ports": Immutable.OrderedMap { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": { + "properties": Immutable.Map { "index": 0, }, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id2 from ports collection if its parent node does not exist 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE should only remove port id2 from ports collection if its parent node does not exist 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, }, - "out": {}, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "type": "test", }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_DATA remove type on port id1 on port data map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_DATA remove type on port id1 on port data map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, }, - "out": {}, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "type": "test", }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES to remove attr on port id1 graphicalAttribute map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES to remove attr on port id1 graphicalAttribute map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, }, - "out": {}, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "type": "test", }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_SET_DATA to merge { type: 'string' } on port id1 data map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_SET_DATA to merge { type: 'string' } on port id1 data map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, }, - "out": {}, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "string", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, - "type": "test", }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, +} +`; + +exports[`Check port reducer > FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES to merge { selected: true } on port id1 graphicalAttribute map 1`] = ` +Immutable.Map { + "in": Immutable.Map {}, + "transformToApply": undefined, + "parents": Immutable.Map {}, "transform": { "k": 1, "x": 0, "y": 0, }, - "transformToApply": undefined, -} -`; - -exports[`Check port reducer > FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES to merge { selected: true } on port id1 graphicalAttribute map 1`] = ` -{ - "childrens": {}, - "in": {}, - "links": {}, - "nodeTypes": {}, - "nodes": { - "nodeId": {}, + "out": Immutable.Map {}, + "nodes": Immutable.Map { + "nodeId": Immutable.Map {}, }, - "out": {}, - "parents": {}, - "ports": { - "id1": PortRecord { - "data": PortData { + "ports": Immutable.OrderedMap { + "id1": Immutable.Record { + "id": "id1", + "nodeId": undefined, + "data": Immutable.Map { "type": "test", }, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "graphicalAttributes": Immutable.Map { + "type": "test", + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": { - "selected": true, - }, - "type": "test", + "selected": true, }, - "id": "id1", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id2": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id2", - "nodeId": "test", + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, - "id3": PortRecord { - "data": PortData {}, - "graphicalAttributes": PortGraphicalAttributes { - "position": { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, + "data": Immutable.Map { + "properties": Immutable.Map {}, + "flowType": undefined, + }, + "graphicalAttributes": Immutable.Map { + "position": Immutable.Record { "x": 10, "y": 10, }, - "properties": {}, }, - "id": "id3", - "nodeId": undefined, + "getPosition": [Function], + "setPosition": [Function], + "getPortType": [Function], + "getPortDirection": [Function], + "getPortFlowType": [Function], + "getIndex": [Function], + "setIndex": [Function], }, }, - "transform": { - "k": 1, - "x": 0, - "y": 0, - }, - "transformToApply": undefined, + "childrens": Immutable.Map {}, + "nodeTypes": Immutable.Map {}, + "links": Immutable.Map {}, } `; diff --git a/packages/flow-designer/src/reducers/flow.reducer.test.ts b/packages/flow-designer/src/reducers/flow.reducer.test.ts index bd94452957f..425e524bbf0 100644 --- a/packages/flow-designer/src/reducers/flow.reducer.test.ts +++ b/packages/flow-designer/src/reducers/flow.reducer.test.ts @@ -1,3 +1,5 @@ +import { Map } from 'immutable'; + import { reducer, calculatePortsPosition, defaultState } from './flow.reducer'; import * as nodeActions from '../actions/node.actions'; import * as portActions from '../actions/port.actions'; @@ -131,26 +133,34 @@ describe('FLOWDESIGNER_PAN_TO set a calculated transformation into transformToAp }); describe('calculatePortsPosition behavior', () => { - const state = { - ...defaultState, - nodes: { - '42': new NodeRecord({ - id: '42', - graphicalAttributes: { nodeType: '42' }, - }), - }, - ports: { - '42': new PortRecord({ - id: '42', - nodeId: '42', - }), - }, - nodeTypes: { '42': { component: {} } }, - }; + const state = defaultState + .set( + 'nodes', + Map().set( + '42', + new NodeRecord({ + id: '42', + graphicalAttributes: Map({ + nodeType: '42', + }), + }), + ), + ) + .set( + 'ports', + Map().set( + '42', + new PortRecord({ + id: '42', + nodeId: '42', + }), + ), + ) + .set('nodeTypes', Map().set('42', Map().set('component', {}))); it('should trigger only if NODE/PORT/FLOW action are dispatched', () => { const calculatePortPosition = vi.fn(); - const givenState = { ...state, nodeTypes: { '42': { component: { calculatePortPosition } } } }; + const givenState = state.setIn(['nodeTypes', '42', 'component'], { calculatePortPosition }); calculatePortsPosition(givenState, { type: 'FLOWDESIGNER_NODE_MOVE', }); @@ -165,7 +175,7 @@ describe('calculatePortsPosition behavior', () => { it('should not trigger on FLOWDESIGNER_NODE_REMOVE and FLOWDESIGNER_PORT_REMOVE', () => { const calculatePortPosition = vi.fn(); - const givenState = { ...state, nodeTypes: { '42': { component: { calculatePortPosition } } } }; + const givenState = state.setIn(['nodeTypes', '42', 'component'], { calculatePortPosition }); calculatePortsPosition(givenState, { type: 'FLOWDESIGNER_NODE_REMOVE', }); @@ -177,7 +187,7 @@ describe('calculatePortsPosition behavior', () => { it('should trigger using action with port id', () => { const calculatePortPosition = vi.fn(); - const givenState = { ...state, nodeTypes: { '42': { component: { calculatePortPosition } } } }; + const givenState = state.setIn(['nodeTypes', '42', 'component'], { calculatePortPosition }); const action = { type: 'FLOWDESIGNER_PORT_SET_DATA', portId: '42', diff --git a/packages/flow-designer/src/reducers/flow.reducer.ts b/packages/flow-designer/src/reducers/flow.reducer.ts index 73ba31d6d70..7da71b56926 100644 --- a/packages/flow-designer/src/reducers/flow.reducer.ts +++ b/packages/flow-designer/src/reducers/flow.reducer.ts @@ -1,3 +1,4 @@ +import { Map } from 'immutable'; import { zoomIdentity } from 'd3'; import { FLOWDESIGNER_FLOW_ADD_ELEMENTS, @@ -13,20 +14,20 @@ import nodesReducer from './node.reducer'; import linksReducer from './link.reducer'; import portsReducer from './port.reducer'; import nodeTypeReducer from './nodeType.reducer'; -import { State, NodeRecord, PortRecord } from '../customTypings/index.d'; +import { State, NodeRecord, Id, LinkRecord, PortRecord } from '../customTypings/index.d'; -export const defaultState: State = { - nodes: {}, - links: {}, - ports: {}, - out: {}, - in: {}, - childrens: {}, - parents: {}, - nodeTypes: {}, +export const defaultState: Partial = Map({ + nodes: Map(), + links: Map(), + ports: Map(), + out: Map>(), + in: Map>(), + childrens: Map>(), + parents: Map>(), + nodeTypes: Map(), transform: { k: 1, x: 0, y: 0 }, transformToApply: undefined, -}; +}); function combinedReducer(state = defaultState, action: any) { return [nodesReducer, linksReducer, portsReducer, nodeTypeReducer].reduce( @@ -91,50 +92,52 @@ export function reducer(state: State, action: any) { state, ); case FLOWDESIGNER_FLOW_RESET: - return { ...defaultState, nodeTypes: state.nodeTypes }; + return defaultState.set('nodeTypes', state.get('nodeTypes')); case FLOWDESIGNER_FLOW_LOAD: return action.listOfActionCreation.reduce( (cumulativeState: State, actionCreation: any) => combinedReducer(cumulativeState, actionCreation), - { ...defaultState, nodeTypes: state.nodeTypes }, + defaultState.set('nodeTypes', state.get('nodeTypes')), ); case FLOWDESIGNER_FLOW_SET_ZOOM: - return { ...state, transform: action.transform }; + return state.set('transform', action.transform); case FLOWDESIGNER_FLOW_ZOOM_IN: - return { - ...state, - transformToApply: zoomIdentity - .translate(state.transform.x, state.transform.y) + return state.set( + 'transformToApply', + zoomIdentity + .translate(state.get('transform').x, state.get('transform').y) .scale( calculateZoomScale( - state.transform.k, + state.get('transform').k, ZoomDirection.IN, action.scale || DEFAULT_ZOOM_SCALE_STEP, ), ), - }; + ); case FLOWDESIGNER_FLOW_ZOOM_OUT: - return { - ...state, - transformToApply: zoomIdentity - .translate(state.transform.x, state.transform.y) + return state.set( + 'transformToApply', + zoomIdentity + .translate(state.get('transform').x, state.get('transform').y) .scale( calculateZoomScale( - state.transform.k, + state.get('transform').k, ZoomDirection.OUT, action.scale || DEFAULT_ZOOM_SCALE_STEP, ), ), - }; + ); case FLOWDESIGNER_PAN_TO: - return { - ...state, - transformToApply: zoomIdentity - .translate(state.transform.x, state.transform.y) - .scale(state.transform.k) - .scale(1 / state.transform.k) - .translate(-(state.transform.x + action.x), -(state.transform.y + action.y)), - }; + return state.update('transformToApply', () => + zoomIdentity + .translate(state.get('transform').x, state.get('transform').y) + .scale(state.get('transform').k) + .scale(1 / state.get('transform').k) + .translate( + -(state.get('transform').x + action.x), + -(state.get('transform').y + action.y), + ), + ); default: return combinedReducer(state, action); } @@ -152,7 +155,7 @@ export function reducer(state: State, action: any) { * @return {object} new state */ export function calculatePortsPosition(state: State, action: any) { - let nodes: NodeRecord[] = []; + let nodes = []; // TODO: NOT a big fan of this way to optimize port recalculations, don't feel future proof if ( (/FLOWDESIGNER_NODE_/.exec(action.type) && action.type !== 'FLOWDESIGNER_NODE_REMOVE') || @@ -161,31 +164,25 @@ export function calculatePortsPosition(state: State, action: any) { action.type === FLOWDESIGNER_NODETYPE_SET ) { if (action.nodeId) { - nodes.push(state.nodes?.[action.nodeId] as NodeRecord); + nodes.push(state.getIn(['nodes', action.nodeId])); } else if (action.portId) { - const portNodeId = state.ports?.[action.portId]?.nodeId; - if (portNodeId) nodes.push(state.nodes?.[portNodeId as string] as NodeRecord); + nodes.push(state.getIn(['nodes', state.getIn(['ports', action.portId]).nodeId])); } else { - nodes = Object.values(state.nodes || {}) as NodeRecord[]; + nodes = state.get('nodes'); } return nodes.reduce((cumulativeState: State, node: NodeRecord) => { const nodeType = node.getNodeType(); - const ports = Object.fromEntries( - Object.entries(state.ports || {}).filter( - ([, port]) => (port as PortRecord).nodeId === node.id, - ), - ); + const ports = state.get('ports').filter((port: PortRecord) => port.nodeId === node.id); - const calculatePortPosition = - state.nodeTypes?.[nodeType as string]?.component?.calculatePortPosition; + const calculatePortPosition = state.getIn( + ['nodeTypes', nodeType, 'component', 'calculatePortPosition'], + state.getIn(['nodeTypes', nodeType, 'component'])?.calculatePortPosition, + ); if (calculatePortPosition) { - return { - ...cumulativeState, - ports: { - ...cumulativeState.ports, - ...calculatePortPosition(ports, node.getPosition(), node.getSize()), - }, - }; + return cumulativeState.mergeIn( + ['ports'], + calculatePortPosition(ports, node.getPosition(), node.getSize()), + ); } return state; }, state); diff --git a/packages/flow-designer/src/reducers/link.reducer.test.ts b/packages/flow-designer/src/reducers/link.reducer.test.ts index b40e576bc4c..4602215588f 100644 --- a/packages/flow-designer/src/reducers/link.reducer.test.ts +++ b/packages/flow-designer/src/reducers/link.reducer.test.ts @@ -1,41 +1,97 @@ +import { Map, fromJS } from 'immutable'; + import { defaultState } from './flow.reducer'; import linkReducer from './link.reducer'; -import { - LinkRecord, - LinkData, - LinkGraphicalAttributes, - PortRecord, - NodeRecord, -} from '../constants/flowdesigner.model'; +import { LinkRecord, PortRecord, NodeRecord } from '../constants/flowdesigner.model'; describe('check linkreducer', () => { - const initialState = { - ...defaultState, - nodes: { - id1: new NodeRecord({ id: 'id1' }), - id2: new NodeRecord({ id: 'id2' }), - id3: new NodeRecord({ id: 'id3' }), - }, - links: { - id1: new LinkRecord({ - id: 'id1', - sourceId: 'id1', - targetId: 'id2', - data: new LinkData({ attr: 'attr' }), - graphicalAttributes: new LinkGraphicalAttributes({ properties: { attr: 'attr' } }), - }), - }, - ports: { - id1: new PortRecord({ id: 'id1', nodeId: 'id1' }), - id2: new PortRecord({ id: 'id2', nodeId: 'id2' }), - id3: new PortRecord({ id: 'id3', nodeId: 'id2' }), - id4: new PortRecord({ id: 'id4', nodeId: 'id1' }), - id5: new PortRecord({ id: 'id5', nodeId: 'id3' }), - id6: new PortRecord({ id: 'id6', nodeId: 'id3' }), - }, - parents: { id1: {}, id2: { id1: 'id1' }, id3: {} }, - childrens: { id1: { id2: 'id2' }, id2: {}, id3: {} }, - }; + const initialState = defaultState + .set( + 'nodes', + Map() + .set( + 'id1', + new NodeRecord({ + id: 'id1', + }), + ) + .set( + 'id2', + new NodeRecord({ + id: 'id2', + }), + ) + .set( + 'id3', + new NodeRecord({ + id: 'id3', + }), + ), + ) + .set( + 'links', + Map().set( + 'id1', + new LinkRecord({ + id: 'id1', + sourceId: 'id1', + targetId: 'id2', + data: Map().set('attr', 'attr'), + graphicalAttributes: Map().set('properties', fromJS({ attr: 'attr' })), + }), + ), + ) + .set( + 'ports', + Map() + .set( + 'id1', + new PortRecord({ + id: 'id1', + nodeId: 'id1', + }), + ) + .set( + 'id2', + new PortRecord({ + id: 'id2', + nodeId: 'id2', + }), + ) + .set( + 'id3', + new PortRecord({ + id: 'id3', + nodeId: 'id2', + }), + ) + .set( + 'id4', + new PortRecord({ + id: 'id4', + nodeId: 'id1', + }), + ) + .set( + 'id5', + new PortRecord({ + id: 'id5', + nodeId: 'id3', + }), + ) + .set( + 'id6', + new PortRecord({ + id: 'id6', + nodeID: 'id3', + }), + ), + ) + .set('parents', Map().set('id1', Map()).set('id2', Map().set('id1', 'id1')).set('id3', Map())) + .set( + 'childrens', + Map().set('id1', Map().set('id2', 'id2')).set('id2', Map()).set('id3', Map()), + ); it('FLOWDESIGNER_LINK_ADD should add a new link to the state', () => { const newState = linkReducer(initialState, { diff --git a/packages/flow-designer/src/reducers/link.reducer.ts b/packages/flow-designer/src/reducers/link.reducer.ts index 0d816d6ab4f..12f579727e4 100644 --- a/packages/flow-designer/src/reducers/link.reducer.ts +++ b/packages/flow-designer/src/reducers/link.reducer.ts @@ -1,5 +1,5 @@ import invariant from 'invariant'; -import cloneDeep from 'lodash/cloneDeep'; +import { Map, fromJS } from 'immutable'; import { FLOWDESIGNER_LINK_ADD, @@ -13,187 +13,204 @@ import { } from '../constants/flowdesigner.constants'; import { LinkRecord, LinkGraphicalAttributes, LinkData } from '../constants/flowdesigner.model'; -import { setIn, deleteIn } from './state-utils'; -import { State } from '../customTypings/index.d'; -export default function linkReducer(state: State, action: any) { +const defaultState = Map(); + +export default function linkReducer(state = defaultState, action: any) { switch (action.type) { - case FLOWDESIGNER_LINK_ADD: { - if (state.links?.[action.linkId]) { + case FLOWDESIGNER_LINK_ADD: + if (state.getIn(['links', action.linkId])) { invariant(false, `can't create a link ${action.linkId} when it already exist`); } - if (!state.ports?.[action.targetId]) { + if (!state.getIn(['ports', action.targetId])) { invariant( false, `can't set a non existing target with id ${action.targetId} on link ${action.linkId}`, ); } - if (!state.ports?.[action.sourceId]) { + if (!state.getIn(['ports', action.sourceId])) { invariant( false, `can't set a non existing source with id ${action.sourceId} on link ${action.linkId}`, ); } - const sourcePort = state.ports[action.sourceId]; - const targetPort = state.ports[action.targetId]; - const sourceNodeId = sourcePort.nodeId as string; - const targetNodeId = targetPort.nodeId as string; - - return { - ...state, - links: { - ...state.links, - [action.linkId]: new LinkRecord({ + return state + .setIn( + ['links', action.linkId], + new LinkRecord({ id: action.linkId, sourceId: action.sourceId, targetId: action.targetId, - data: new LinkData({ - ...action.data, - properties: cloneDeep(action.data?.properties) || {}, - }), + data: new LinkData(action.data).set( + 'properties', + fromJS(action.data && action.data.properties) || Map(), + ), graphicalAttributes: new LinkGraphicalAttributes(action.graphicalAttributes).set( 'properties', - cloneDeep(action.graphicalAttributes?.properties) || {}, + fromJS(action.graphicalAttributes && action.graphicalAttributes.properties) || Map(), ), }), - }, - childrens: { - ...state.childrens, - [sourceNodeId]: { - ...(state.childrens?.[sourceNodeId] || {}), - [targetNodeId]: targetNodeId, - }, - }, - parents: { - ...state.parents, - [targetNodeId]: { - ...(state.parents?.[targetNodeId] || {}), - [sourceNodeId]: sourceNodeId, - }, - }, - out: setIn(state.out, [sourceNodeId, action.sourceId, action.linkId], action.linkId), - in: setIn(state.in, [targetNodeId, action.targetId, action.linkId], action.linkId), - }; - } - case FLOWDESIGNER_LINK_SET_TARGET: { - if (!state.links?.[action.linkId]) { + ) // parcourir l'ensemble des parents et set le composant cible en tant que sucessors ' + .setIn( + [ + 'childrens', + state.getIn(['ports', action.sourceId]).nodeId, + state.getIn(['ports', action.targetId]).nodeId, + ], + state.getIn(['ports', action.targetId]).nodeId, + ) + .setIn( + [ + 'parents', + state.getIn(['ports', action.targetId]).nodeId, + state.getIn(['ports', action.sourceId]).nodeId, + ], + state.getIn(['ports', action.sourceId]).nodeId, + ) + .setIn( + ['out', state.getIn(['ports', action.sourceId]).nodeId, action.sourceId, action.linkId], + action.linkId, + ) + .setIn( + ['in', state.getIn(['ports', action.targetId]).nodeId, action.targetId, action.linkId], + action.linkId, + ); + case FLOWDESIGNER_LINK_SET_TARGET: + if (!state.getIn(['links', action.linkId])) { invariant( false, `can't set a target ${action.targetId} on non existing link with id ${action.linkId}`, ); } - if (!state.ports?.[action.targetId]) { + if (!state.getIn(['ports', action.targetId])) { invariant( false, `can't set a non existing target with id ${action.targetId} on link ${action.linkId}`, ); } - const link = state.links[action.linkId] as LinkRecord; - const oldTargetPort = state.ports[link.targetId as string]; - const newTargetPort = state.ports[action.targetId]; - const sourcePort = state.ports[link.sourceId as string]; - - let s: State = { - ...state, - links: { ...state.links, [action.linkId]: link.set('targetId', action.targetId) }, - }; - s = deleteIn(s, ['in', oldTargetPort?.nodeId, link.targetId, action.linkId]); - s = setIn(s, ['in', newTargetPort?.nodeId, action.targetId, action.linkId], action.linkId); - s = deleteIn(s, ['childrens', sourcePort?.nodeId, oldTargetPort?.nodeId]); - s = setIn(s, ['childrens', sourcePort?.nodeId, newTargetPort?.nodeId], newTargetPort?.nodeId); - return s; - } - case FLOWDESIGNER_LINK_SET_SOURCE: { - if (!state.links?.[action.linkId]) { + return state + .setIn(['links', action.linkId, 'targetId'], action.targetId) + .deleteIn([ + 'in', + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + state.getIn(['links', action.linkId]).targetId, + action.linkId, + ]) + .setIn( + ['in', state.getIn(['ports', action.targetId]).nodeId, action.targetId, action.linkId], + action.linkId, + ) + .deleteIn([ + 'childrens', + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + ]) + .setIn( + [ + 'childrens', + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + state.getIn(['ports', action.targetId]).nodeId, + ], + state.getIn(['ports', action.targetId]).nodeId, + ); + case FLOWDESIGNER_LINK_SET_SOURCE: + if (!state.getIn(['links', action.linkId])) { invariant( false, `can't set a source ${action.sourceId} on non existing link with id ${action.linkId}`, ); } - if (!state.ports?.[action.sourceId]) { + if (!state.getIn(['ports', action.sourceId])) { invariant( false, `can't set a non existing target with id ${action.sourceId} on link ${action.linkId}`, ); } - const link = state.links[action.linkId] as LinkRecord; - const oldSourcePort = state.ports[link.sourceId as string]; - const newSourcePort = state.ports[action.sourceId]; - const targetPort = state.ports[link.targetId as string]; - - let s: State = { - ...state, - links: { ...state.links, [action.linkId]: link.set('sourceId', action.sourceId) }, - }; - s = deleteIn(s, ['out', oldSourcePort?.nodeId, link.sourceId, action.linkId]); - s = setIn(s, ['out', newSourcePort?.nodeId, action.sourceId, action.linkId], action.linkId); - s = deleteIn(s, ['parents', targetPort?.nodeId, oldSourcePort?.nodeId]); - s = setIn(s, ['parents', targetPort?.nodeId, newSourcePort?.nodeId], newSourcePort?.nodeId); - return s; - } - case FLOWDESIGNER_LINK_REMOVE: { - if (!state.links?.[action.linkId]) { + return state + .setIn(['links', action.linkId, 'sourceId'], action.sourceId) + .deleteIn([ + 'out', + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + state.getIn(['links', action.linkId]).sourceId, + action.linkId, + ]) + .setIn( + ['out', state.getIn(['ports', action.sourceId]).nodeId, action.sourceId, action.linkId], + action.linkId, + ) + .deleteIn([ + 'parents', + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + ]) + .setIn( + [ + 'parents', + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + state.getIn(['ports', action.sourceId]).nodeId, + ], + state.getIn(['ports', action.sourceId]).nodeId, + ); + case FLOWDESIGNER_LINK_REMOVE: + if (!state.getIn(['links', action.linkId])) { invariant(false, `can't remove non existing link ${action.linkId}`); } - const link = state.links[action.linkId] as LinkRecord; - const targetPort = state.ports?.[link.targetId as string]; - const sourcePort = state.ports?.[link.sourceId as string]; - - let s = deleteIn(state, ['in', targetPort?.nodeId, link.targetId, action.linkId]); - s = deleteIn(s, ['out', sourcePort?.nodeId, link.sourceId, action.linkId]); - s = deleteIn(s, ['childrens', sourcePort?.nodeId, targetPort?.nodeId]); - s = deleteIn(s, ['parents', targetPort?.nodeId, sourcePort?.nodeId]); - s = deleteIn(s, ['links', action.linkId]); - return s; - } - case FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES: { - if (!state.links?.[action.linkId]) { + return state + .deleteIn([ + 'in', + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + state.getIn(['links', action.linkId]).targetId, + action.linkId, + ]) + .deleteIn([ + 'out', + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + state.getIn(['links', action.linkId]).sourceId, + action.linkId, + ]) + .deleteIn([ + 'childrens', + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + ]) + .deleteIn([ + 'parents', + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, + ]) + .deleteIn(['links', action.linkId]); + case FLOWDESIGNER_LINK_SET_GRAPHICAL_ATTRIBUTES: + if (!state.getIn(['links', action.linkId])) { invariant(false, `Can't set an attribute on non existing link ${action.linkId}`); } - const link = state.links[action.linkId] as LinkRecord; try { - return { - ...state, - links: { - ...state.links, - [action.linkId]: link.set( - 'graphicalAttributes', - link.graphicalAttributes.merge(action.graphicalAttributes), - ), - }, - }; + return state.mergeIn( + ['links', action.linkId, 'graphicalAttributes'], + fromJS(action.graphicalAttributes), + ); } catch (error) { console.error(error); - return { - ...state, - links: { - ...state.links, - [action.linkId]: link.set( - 'graphicalAttributes', - link.graphicalAttributes.set('properties', { - ...(link.graphicalAttributes?.properties || {}), - ...action.graphicalAttributes, - }), - ), - }, - }; + return state.mergeIn( + ['links', action.linkId, 'graphicalAttributes', 'properties'], + fromJS(action.graphicalAttributes), + ); } - } + case FLOWDESIGNER_LINK_REMOVE_GRAPHICAL_ATTRIBUTES: - if (!state.links?.[action.linkId]) { + if (!state.getIn(['links', action.linkId])) { invariant(false, `Can't remove an attribute on non existing link ${action.linkId}`); } - return deleteIn(state, [ + return state.deleteIn([ 'links', action.linkId, 'graphicalAttributes', @@ -201,37 +218,24 @@ export default function linkReducer(state: State, action: any) { action.graphicalAttributesKey, ]); - case FLOWDESIGNER_LINK_SET_DATA: { - if (!state.links?.[action.linkId]) { + case FLOWDESIGNER_LINK_SET_DATA: + if (!state.getIn(['links', action.linkId])) { invariant(false, `Can't set an attribute on non existing link ${action.linkId}`); } - const link = state.links[action.linkId] as LinkRecord; try { - return { - ...state, - links: { - ...state.links, - [action.linkId]: link.set( - 'data', - new LinkData({ ...(link.data as any), ...action.data }), - ), - }, - }; + return state.mergeIn(['links', action.linkId, 'data'], fromJS(action.data)); } catch (error) { console.error(error); - return setIn(state, ['links', action.linkId, 'data', 'properties'], { - ...(link.data as any)?.properties, - ...action.data, - }); + return state.mergeIn(['links', action.linkId, 'data', 'properties'], fromJS(action.data)); } - } + case FLOWDESIGNER_LINK_REMOVE_DATA: - if (!state.links?.[action.linkId]) { + if (!state.getIn(['links', action.linkId])) { invariant(false, `Can't remove an attribute on non existing link ${action.linkId}`); } - return deleteIn(state, ['links', action.linkId, 'data', 'properties', action.dataKey]); + return state.deleteIn(['links', action.linkId, 'data', 'properties', action.dataKey]); default: return state; diff --git a/packages/flow-designer/src/reducers/node.reducer.test.ts b/packages/flow-designer/src/reducers/node.reducer.test.ts index 17bffc1c04a..03942ac0d52 100644 --- a/packages/flow-designer/src/reducers/node.reducer.test.ts +++ b/packages/flow-designer/src/reducers/node.reducer.test.ts @@ -1,8 +1,9 @@ +import { Map } from 'immutable'; + import { defaultState } from './flow.reducer'; import nodeReducer from './node.reducer'; import { NodeRecord, - NodeData, PositionRecord, NodeGraphicalAttributes, } from '../constants/flowdesigner.model'; @@ -14,32 +15,33 @@ import { } from '../constants/flowdesigner.constants'; describe('Check node reducer', () => { - const initialState = { - ...defaultState, - nodes: { - ...defaultState.nodes, - id1: new NodeRecord({ + const initialState = defaultState + .setIn( + ['nodes', 'id1'], + new NodeRecord({ id: 'id1', type: 'type1', - data: new NodeData({ type: 'test' }), + data: Map({ type: 'test' }), graphicalAttributes: new NodeGraphicalAttributes({ type: 'type1', selected: true, position: new PositionRecord({ x: 10, y: 10 }), }), }), - id2: new NodeRecord({ + ) + .setIn( + ['nodes', 'id2'], + new NodeRecord({ id: 'id2', type: 'type2', - data: new NodeData({ type: 'test' }), + data: Map({ type: 'test' }), graphicalAttributes: new NodeGraphicalAttributes({ type: 'type2', selected: false, position: new PositionRecord({ x: 10, y: 10 }), }), }), - }, - }; + ); it('FLOWDESIGNER_NODE_ADD properly add a new node to the node collection', () => { expect( @@ -73,7 +75,9 @@ describe('Check node reducer', () => { type: FLOWDESIGNER_NODE_MOVE_START, nodeId: 'id1', nodePosition: { x: 50, y: 50 }, - }).nodes['id1'].graphicalAttributes.properties.startPosition, + }) + .getIn(['nodes', 'id1', 'graphicalAttributes', 'properties', 'startPosition']) + .toJS(), ).toEqual({ x: 50, y: 50 }); }); @@ -93,7 +97,7 @@ describe('Check node reducer', () => { type: FLOWDESIGNER_NODE_MOVE_END, nodeId: 'id1', nodePosition: { x: 50, y: 50 }, - }).nodes['id1'].graphicalAttributes.properties.startPosition, + }).getIn(['nodes', 'id1', 'graphicalAttributes', 'properties', 'startPosition']), ).toEqual(undefined); }); @@ -171,33 +175,37 @@ describe('Check node reducer', () => { }); describe('FLOWDESIGNER_NODE_APPLY_MOVEMENT', () => { - const initialState = { - ...defaultState, - nodes: { - ...defaultState.nodes, - id1: new NodeRecord({ + const initialState = defaultState + .setIn( + ['nodes', 'id1'], + new NodeRecord({ id: 'id1', nodeType: 'type1', graphicalAttributes: new NodeGraphicalAttributes({ position: new PositionRecord({ x: 10, y: 10 }), }), }), - id2: new NodeRecord({ + ) + .setIn( + ['nodes', 'id2'], + new NodeRecord({ id: 'id2', nodeType: 'type2', graphicalAttributes: new NodeGraphicalAttributes({ position: new PositionRecord({ x: 10, y: 10 }), }), }), - id3: new NodeRecord({ + ) + .setIn( + ['nodes', 'id3'], + new NodeRecord({ id: 'id3', nodeType: 'type2', graphicalAttributes: new NodeGraphicalAttributes({ position: new PositionRecord({ x: 10, y: 10 }), }), }), - }, - }; + ); it('should apply the same relative movement to each node listed', () => { expect( nodeReducer(initialState, { diff --git a/packages/flow-designer/src/reducers/node.reducer.ts b/packages/flow-designer/src/reducers/node.reducer.ts index 7f12538ab3b..fed6b0c0115 100644 --- a/packages/flow-designer/src/reducers/node.reducer.ts +++ b/packages/flow-designer/src/reducers/node.reducer.ts @@ -1,9 +1,8 @@ +import Immutable, { Map, fromJS } from 'immutable'; import invariant from 'invariant'; -import cloneDeep from 'lodash/cloneDeep'; import { removePort } from '../actions/port.actions'; import portReducer from './port.reducer'; import { outPort, inPort } from '../selectors/portSelectors'; -import { setIn, deleteIn } from './state-utils'; import { FLOWDESIGNER_NODE_ADD, @@ -26,218 +25,181 @@ import { PositionRecord, SizeRecord, NodeGraphicalAttributes, - NodeData, } from '../constants/flowdesigner.model'; -import { Id, State, NodeRecord as NodeRecordType } from '../customTypings/index.d'; +import { PortRecord, Id, State, NodeRecordMap } from '../customTypings/index.d'; -const nodeReducer = (state: State, action: any) => { +const defaultState = Map(); +const nodeReducer = (state: State = defaultState, action: any) => { switch (action.type) { - case FLOWDESIGNER_NODE_ADD: { - if (state.nodes?.[action.nodeId]) { + case FLOWDESIGNER_NODE_ADD: + if (state.getIn(['nodes', action.nodeId])) { invariant(false, `Can not create node ${action.nodeId} since it does already exist`); } - return { - ...state, - nodes: { - ...state.nodes, - [action.nodeId]: new NodeRecord({ + + return state + .setIn( + ['nodes', action.nodeId], + new NodeRecord({ id: action.nodeId, type: action.nodeType, - data: new NodeData({ - ...action.data, - properties: cloneDeep(action.data?.properties) || {}, - }), - graphicalAttributes: new NodeGraphicalAttributes(action.graphicalAttributes) - .set('nodeSize', new SizeRecord(action.graphicalAttributes?.nodeSize)) - .set('position', new PositionRecord(action.graphicalAttributes?.position)) - .set('properties', cloneDeep(action.graphicalAttributes?.properties) || {}), + data: Immutable.Map(action.data).set( + 'properties', + fromJS(action.data && action.data.properties) || Map(), + ), + graphicalAttributes: new NodeGraphicalAttributes(fromJS(action.graphicalAttributes)) + .set('nodeSize', new SizeRecord(action.graphicalAttributes.nodeSize)) + .set('position', new PositionRecord(action.graphicalAttributes.position)) + .set('properties', fromJS(action.graphicalAttributes.properties) || Map()), }), - }, - out: { ...state.out, [action.nodeId]: {} }, - in: { ...state.in, [action.nodeId]: {} }, - childrens: { ...state.childrens, [action.nodeId]: {} }, - parents: { ...state.parents, [action.nodeId]: {} }, - }; - } - case FLOWDESIGNER_NODE_UPDATE: { - const newId = Node.getId(action.node) as string; - if (action.nodeId === newId) { - return { ...state, nodes: { ...state.nodes, [newId]: action.node } }; - } - const { [action.nodeId]: _removed, ...remainingNodes } = state.nodes || {}; - return { - ...state, - nodes: { ...remainingNodes, [newId]: action.node }, - out: { ...state.out, [newId]: {} }, - in: { ...state.in, [newId]: {} }, - childrens: { ...state.childrens, [newId]: {} }, - parents: { ...state.parents, [newId]: {} }, - }; - } + ) + .setIn(['out', action.nodeId], Map()) + .setIn(['in', action.nodeId], Map()) + .setIn(['childrens', action.nodeId], Map()) + .setIn(['parents', action.nodeId], Map()); + case FLOWDESIGNER_NODE_UPDATE: + if (action.nodeId === Node.getId(action.node)) { + return state.setIn(['nodes', Node.getId(action.node)], action.node); + } // special case here, the id got changed and it have lots of implication + + return state + .setIn(['nodes', Node.getId(action.node)], action.node) + .deleteIn(['nodes', action.nodeId]) + .setIn(['out', Node.getId(action.node)], Map()) + .setIn(['in', Node.getId(action.node)], Map()) + .setIn(['childrens', Node.getId(action.node)], Map()) + .setIn(['parents', Node.getId(action.node)], Map()); case FLOWDESIGNER_NODE_MOVE_START: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn('nodes', action.nodeId)) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } - return setIn( - state, + + return state.setIn( ['nodes', action.nodeId, 'graphicalAttributes', 'properties', 'startPosition'], new PositionRecord(action.nodePosition), ); case FLOWDESIGNER_NODE_MOVE: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn('nodes', action.nodeId)) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } - return setIn( - state, + + return state.setIn( ['nodes', action.nodeId, 'graphicalAttributes', 'position'], new PositionRecord(action.nodePosition), ); case FLOWDESIGNER_NODE_MOVE_END: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn('nodes', action.nodeId)) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } - return deleteIn( - setIn( - state, + + return state + .setIn( ['nodes', action.nodeId, 'graphicalAttributes', 'position'], new PositionRecord(action.nodePosition), - ), - ['nodes', action.nodeId, 'graphicalAttributes', 'properties', 'startPosition'], - ); - case FLOWDESIGNER_NODE_APPLY_MOVEMENT: { - const updatedNodes = Object.fromEntries( - Object.entries(state.nodes || {}).map(([id, node]) => { - const n = node as NodeRecord; - if (action.nodesId.find((nId: Id) => nId === n.id)) { - return [ - id, - n - .setIn( - ['graphicalAttributes', 'position', 'x'], - n.getPosition().x + action.movement.x, - ) - .setIn( - ['graphicalAttributes', 'position', 'y'], - n.getPosition().y + action.movement.y, - ), - ]; + ) + .deleteIn(['nodes', action.nodeId, 'graphicalAttributes', 'properties', 'startPosition']); + case FLOWDESIGNER_NODE_APPLY_MOVEMENT: + return state.update('nodes', (nodes: NodeRecordMap) => + nodes.map(node => { + if (action.nodesId.find((id: Id) => id === node.id)) { + return node + .setIn( + ['graphicalAttributes', 'position', 'x'], + node.getPosition().x + action.movement.x, + ) + .setIn( + ['graphicalAttributes', 'position', 'y'], + node.getPosition().y + action.movement.y, + ); } - return [id, node]; + return node; }), ); - return { ...state, nodes: updatedNodes }; - } case FLOWDESIGNER_NODE_SET_SIZE: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't set size on node ${action.nodeId} since it doesn't exist`); } - return setIn( - state, + + return state.setIn( ['nodes', action.nodeId, 'graphicalAttributes', 'nodeSize'], new SizeRecord(action.nodeSize), ); case FLOWDESIGNER_NODE_SET_TYPE: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't set node.type on node ${action.nodeid} since it doesn't exist`); } - return setIn(state, ['nodes', action.nodeId, 'type'], action.nodeType); - case FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES: { - if (!state.nodes?.[action.nodeId]) { + + return state.setIn(['nodes', action.nodeId, 'type'], action.nodeType); + case FLOWDESIGNER_NODE_SET_GRAPHICAL_ATTRIBUTES: + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't set a graphical attribute on non existing node ${action.nodeId}`); } - const node = state.nodes[action.nodeId] as NodeRecord; + try { - return { - ...state, - nodes: { - ...state.nodes, - [action.nodeId]: node.set( - 'graphicalAttributes', - node.graphicalAttributes.merge(action.graphicalAttributes), - ), - }, - }; + return state.mergeIn( + ['nodes', action.nodeId, 'graphicalAttributes'], + fromJS(action.graphicalAttributes), + ); } catch (error) { console.error(error); - return { - ...state, - nodes: { - ...state.nodes, - [action.nodeId]: node.set( - 'graphicalAttributes', - node.graphicalAttributes.set('properties', { - ...(node.graphicalAttributes?.properties || {}), - ...action.graphicalAttributes, - }), - ), - }, - }; + return state.mergeIn( + ['nodes', action.nodeId, 'graphicalAttributes', 'properties'], + fromJS(action.graphicalAttributes), + ); } - } + case FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn(['nodes', action.nodeId])) { invariant( false, `Can't remove a graphical attribute on non existing node ${action.nodeId}`, ); } - return deleteIn(state, [ + + return state.deleteIn([ 'nodes', action.nodeId, 'graphicalAttributes', 'properties', action.graphicalAttributesKey, ]); - case FLOWDESIGNER_NODE_SET_DATA: { - if (!state.nodes?.[action.nodeId]) { + case FLOWDESIGNER_NODE_SET_DATA: + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't set a data on non existing node ${action.nodeId}`); } - const node = state.nodes[action.nodeId] as NodeRecord; + try { - return { - ...state, - nodes: { - ...state.nodes, - [action.nodeId]: node.set( - 'data', - new NodeData({ ...(node.data as any), ...action.data }), - ), - }, - }; + return state.mergeIn(['nodes', action.nodeId, 'data'], fromJS(action.data)); } catch (error) { console.error(error); - return setIn(state, ['nodes', action.nodeId, 'data', 'properties'], { - ...(node.data as any)?.properties, - ...action.data, - }); + return state.mergeIn(['nodes', action.nodeId, 'data', 'properties'], fromJS(action.data)); } - } + case FLOWDESIGNER_NODE_REMOVE_DATA: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't remove a data on non existing node ${action.nodeId}`); } - return deleteIn(state, ['nodes', action.nodeId, 'data', 'properties', action.dataKey]); - case FLOWDESIGNER_NODE_REMOVE: { - if (!state.nodes?.[action.nodeId]) { + + return state.deleteIn(['nodes', action.nodeId, 'data', 'properties', action.dataKey]); + case FLOWDESIGNER_NODE_REMOVE: + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can not remove node ${action.nodeId} since it doesn't exist`); } - const outPorts = outPort(state, action.nodeId); - const inPorts = inPort(state, action.nodeId); - let newState = Object.entries(outPorts).reduce( - (cumulativeState, [key]) => portReducer(cumulativeState, removePort(key)), - state, - ); - newState = Object.entries(inPorts).reduce( - (cumulativeState, [key]) => portReducer(cumulativeState, removePort(key)), - newState, - ); - newState = deleteIn(newState, ['nodes', action.nodeId]); - newState = deleteIn(newState, ['out', action.nodeId]); - newState = deleteIn(newState, ['in', action.nodeId]); - newState = deleteIn(newState, ['childrens', action.nodeId]); - newState = deleteIn(newState, ['parents', action.nodeId]); - return newState; - } + + return inPort(state, action.nodeId) + .reduce( + (cumulativeState: State, port: PortRecord, key: Id) => + portReducer(cumulativeState, removePort(key)), + outPort(state, action.nodeId).reduce( + (cumulativeState: State, port: PortRecord, key: Id) => + portReducer(cumulativeState, removePort(key)), + state, + ), + ) + .deleteIn(['nodes', action.nodeId]) + .deleteIn(['out', action.nodeId]) + .deleteIn(['in', action.nodeId]) + .deleteIn(['childrens', action.nodeId]) + .deleteIn(['parents', action.nodeId]); default: return state; } diff --git a/packages/flow-designer/src/reducers/nodeType.reducer.ts b/packages/flow-designer/src/reducers/nodeType.reducer.ts index a5252461a61..32640d7c12e 100644 --- a/packages/flow-designer/src/reducers/nodeType.reducer.ts +++ b/packages/flow-designer/src/reducers/nodeType.reducer.ts @@ -1,10 +1,12 @@ +import { Map } from 'immutable'; + import { FLOWDESIGNER_NODETYPE_SET } from '../constants/flowdesigner.constants'; -import { State } from '../customTypings/index.d'; -const nodeTypeReducer = (state: State, action: any) => { +const defaultState = Map(); +const nodeTypeReducer = (state = defaultState, action: any) => { switch (action.type) { case FLOWDESIGNER_NODETYPE_SET: - return { ...state, nodeTypes: { ...state.nodeTypes, ...action.nodeTypes } }; + return state.mergeIn(['nodeTypes'], action.nodeTypes); default: return state; } diff --git a/packages/flow-designer/src/reducers/port.reducer.test.ts b/packages/flow-designer/src/reducers/port.reducer.test.ts index 3dfcfb8a6e3..b87af0d362f 100644 --- a/packages/flow-designer/src/reducers/port.reducer.test.ts +++ b/packages/flow-designer/src/reducers/port.reducer.test.ts @@ -1,42 +1,49 @@ +import { Map, OrderedMap } from 'immutable'; + import { defaultState } from './flow.reducer'; import portReducer from './port.reducer'; -import { - PortRecord, - PortData, - PortGraphicalAttributes, - PositionRecord, -} from '../constants/flowdesigner.model'; +import { PortRecord, PositionRecord } from '../constants/flowdesigner.model'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; describe('Check port reducer', () => { - const initialState = { - ...defaultState, - ports: { - id1: new PortRecord({ - id: 'id1', - data: new PortData({ type: 'test' }), - graphicalAttributes: new PortGraphicalAttributes({ - type: 'test', - position: new PositionRecord({ x: 10, y: 10 }), - }), - }), - id2: new PortRecord({ - id: 'id2', - nodeId: 'test', - graphicalAttributes: new PortGraphicalAttributes({ - position: new PositionRecord({ x: 10, y: 10 }), - }), - }), - id3: new PortRecord({ - id: 'id3', - graphicalAttributes: new PortGraphicalAttributes({ - position: new PositionRecord({ x: 10, y: 10 }), - }), - }), - }, - nodes: { nodeId: {} }, - links: {}, - }; + const initialState = defaultState + .set( + 'ports', + // eslint-disable-next-line new-cap + OrderedMap() + .set( + 'id1', + new PortRecord({ + id: 'id1', + data: Map({ type: 'test' }), + graphicalAttributes: Map({ + type: 'test', + position: new PositionRecord({ x: 10, y: 10 }), + }), + }), + ) + .set( + 'id2', + new PortRecord({ + id: 'id2', + nodeId: 'test', + graphicalAttributes: Map({ + position: new PositionRecord({ x: 10, y: 10 }), + }), + }), + ) + .set( + 'id3', + new PortRecord({ + id: 'id3', + graphicalAttributes: Map({ + position: new PositionRecord({ x: 10, y: 10 }), + }), + }), + ), + ) + .set('nodes', Map().set('nodeId', Map())) + .set('links', Map()); it('FLOWDESIGNER_PORT_ADD properly add the port to the port Map', () => { expect( diff --git a/packages/flow-designer/src/reducers/port.reducer.ts b/packages/flow-designer/src/reducers/port.reducer.ts index 0caa126e6e3..51a0361e2d4 100644 --- a/packages/flow-designer/src/reducers/port.reducer.ts +++ b/packages/flow-designer/src/reducers/port.reducer.ts @@ -1,12 +1,7 @@ import invariant from 'invariant'; -import cloneDeep from 'lodash/cloneDeep'; +import { Map, fromJS } from 'immutable'; -import { - PortRecord, - PortData, - PortGraphicalAttributes, - PositionRecord, -} from '../constants/flowdesigner.model'; +import { PortRecord, PositionRecord } from '../constants/flowdesigner.model'; import { removeLink } from '../actions/link.actions'; import linkReducer from './link.reducer'; import { portOutLink, portInLink } from '../selectors/linkSelectors'; @@ -31,25 +26,20 @@ import { State, PortAction, } from '../customTypings/index.d'; -import { setIn, deleteIn } from './state-utils'; /** * get ports attached to a node */ function filterPortsByNode(ports: PortRecordMap, nodeId: Id): PortRecordMap { - return Object.fromEntries( - Object.entries(ports).filter(([, port]) => (port as PortRecordType).nodeId === nodeId), - ) as PortRecordMap; + return ports.filter((port: PortRecordType) => port.nodeId === nodeId) as PortRecordMap; } /** * get ports of direction EMITTER or SINK */ function filterPortsByDirection(ports: PortRecordMap, direction: PortDirection): PortRecordMap { - return Object.fromEntries( - Object.entries(ports).filter( - ([, port]) => (port as PortRecordType).getPortDirection() === direction, - ), + return ports.filter( + (port: PortRecordType) => port.getPortDirection() === direction, ) as PortRecordMap; } @@ -57,30 +47,28 @@ function filterPortsByDirection(ports: PortRecordMap, direction: PortDirection): * for a new port calculate its index by retrieving all its siblings */ function calculateNewPortIndex(ports: PortRecordMap, port: PortRecordType): number { - return Object.keys( - filterPortsByDirection( - filterPortsByNode(ports, port.nodeId), - port.graphicalAttributes.properties.type, - ), - ).length; + return filterPortsByDirection( + filterPortsByNode(ports, port.nodeId), + port.graphicalAttributes.properties.type, + ).size; } function indexPortMap(ports: PortRecordMap): PortRecordMap { let i = 0; - return Object.fromEntries( - Object.entries(ports) - .sort(([, a], [, b]) => { - const pa = a as PortRecordType; - const pb = b as PortRecordType; - if (pa.getIndex() < pb.getIndex()) return -1; - if (pa.getIndex() > pb.getIndex()) return 1; - return 0; - }) - .map(([k, port]) => { - i += 1; - return [k, (port as PortRecordType).setIndex(i - 1)]; - }), - ) as PortRecordMap; + return ports + .sort((a, b) => { + if (a.getIndex() < b.getIndex()) { + return -1; + } + if (a.getIndex() > b.getIndex()) { + return 1; + } + return 0; + }) + .map(port => { + i += 1; + return port.setIndex(i - 1); + }) as PortRecordMap; } /** @@ -90,34 +78,31 @@ function indexPortMap(ports: PortRecordMap): PortRecordMap { */ function setPort(state: State, port: PortRecordType) { const index: number = - port.graphicalAttributes.properties.index || calculateNewPortIndex(state.ports, port); - const newState: State = { - ...state, - ports: { - ...state.ports, - [port.id]: new PortRecord({ - id: port.id, - nodeId: port.nodeId, - data: new PortData({ - ...port.data, - properties: cloneDeep((port.data as any)?.properties) || {}, - }), - graphicalAttributes: new PortGraphicalAttributes({ - ...port.graphicalAttributes, - position: new PositionRecord(port.graphicalAttributes.position), - properties: { - index, - ...(port.graphicalAttributes?.properties || {}), - }, - }), - }), - }, - }; + port.graphicalAttributes.properties.index || calculateNewPortIndex(state.get('ports'), port); + const newState = state.setIn( + ['ports', port.id], + new PortRecord({ + id: port.id, + nodeId: port.nodeId, + data: Map(port.data).set('properties', fromJS(port.data && port.data.properties) || Map()), + graphicalAttributes: Map(port.graphicalAttributes) + .set('position', new PositionRecord(port.graphicalAttributes.position)) + .set( + 'properties', + fromJS( + port.graphicalAttributes && { + index, + ...port.graphicalAttributes.properties, + }, + ) || Map(), + ), + }), + ); const type = port.graphicalAttributes.properties.type; if (type === PORT_SOURCE) { - return setIn(newState, ['out', port.nodeId, port.id], {}); + return newState.setIn(['out', port.nodeId, port.id], Map()); } else if (type === PORT_SINK) { - return setIn(newState, ['in', port.nodeId, port.id], {}); + return newState.setIn(['in', port.nodeId, port.id], Map()); } invariant( false, @@ -131,7 +116,7 @@ function setPort(state: State, port: PortRecordType) { export default function portReducer(state: State, action: PortAction): State { switch (action.type) { case FLOWDESIGNER_PORT_ADD: - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't set a new port ${action.id} on non existing node ${action.nodeId}`); } @@ -143,7 +128,7 @@ export default function portReducer(state: State, action: PortAction): State { }); case FLOWDESIGNER_PORT_ADDS: { const localAction = action; - if (!state.nodes?.[action.nodeId]) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't set a new ports on non existing node ${action.nodeId}`); } return action.ports.reduce( @@ -157,119 +142,84 @@ export default function portReducer(state: State, action: PortAction): State { state, ); } - case FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES: { - if (!state.ports?.[action.portId]) { + case FLOWDESIGNER_PORT_SET_GRAPHICAL_ATTRIBUTES: + if (!state.getIn(['ports', action.portId])) { invariant(false, `Can't set an graphical attribute on non existing port ${action.portId}`); } - const port = state.ports[action.portId] as PortRecord; try { - return { - ...state, - ports: { - ...state.ports, - [action.portId]: port.set( - 'graphicalAttributes', - port.graphicalAttributes.merge(action.graphicalAttributes), - ), - }, - }; + return state.mergeIn( + ['ports', action.portId, 'graphicalAttributes'], + fromJS(action.graphicalAttributes), + ); } catch (error) { console.error(error); - return { - ...state, - ports: { - ...state.ports, - [action.portId]: port.set( - 'graphicalAttributes', - port.graphicalAttributes.set('properties', { - ...(port.graphicalAttributes?.properties || {}), - ...action.graphicalAttributes, - }), - ), - }, - }; + return state.mergeIn( + ['ports', action.portId, 'graphicalAttributes', 'properties'], + fromJS(action.graphicalAttributes), + ); } - } + case FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES: - if (!state.ports?.[action.portId]) { + if (!state.getIn(['ports', action.portId])) { invariant( false, `Can't remove a graphical attribute on non existing port ${action.portId}`, ); } - return deleteIn(state, [ + return state.deleteIn([ 'ports', action.portId, 'graphicalAttributes', 'properties', action.graphicalAttributesKey, ]); - case FLOWDESIGNER_PORT_SET_DATA: { - if (!state.ports?.[action.portId]) { + case FLOWDESIGNER_PORT_SET_DATA: + if (!state.getIn(['ports', action.portId])) { invariant(false, `Can't set a data on non existing port ${action.portId}`); } - const port = state.ports[action.portId] as PortRecord; try { - return { - ...state, - ports: { - ...state.ports, - [action.portId]: port.set( - 'data', - new PortData({ ...(port.data as any), ...action.data }), - ), - }, - }; + return state.mergeIn(['ports', action.portId, 'data'], fromJS(action.data)); } catch (error) { console.error(error); - return setIn(state, ['ports', action.portId, 'data', 'properties'], { - ...(port.data as any)?.properties, - ...action.data, - }); + return state.mergeIn(['ports', action.portId, 'data', 'properties'], fromJS(action.data)); } - } + case FLOWDESIGNER_PORT_REMOVE_DATA: - if (!state.ports?.[action.portId]) { + if (!state.getIn(['ports', action.portId])) { invariant(false, `Can't remove a data on non existing port ${action.portId}`); } - return deleteIn(state, ['ports', action.portId, 'data', 'properties', action.dataKey]); + return state.deleteIn(['ports', action.portId, 'data', 'properties', action.dataKey]); case FLOWDESIGNER_PORT_REMOVE: { - if (!state.ports?.[action.portId]) { + if (!state.getIn(['ports', action.portId])) { invariant(false, `Can not remove port ${action.portId} since it doesn't exist`); } - const port = state.ports[action.portId] as PortRecord; + const port: PortRecordType | null | undefined = state.getIn(['ports', action.portId]); if (port) { - const outLinks = Object.values(portOutLink(state, action.portId)); - const inLinks = Object.values(portInLink(state, action.portId)); - - let newState: State = [...outLinks, ...inLinks].reduce( - (cumulativeState, link) => - linkReducer(cumulativeState, removeLink((link as LinkRecordType).id)), - state, - ); - - newState = deleteIn(newState, ['ports', action.portId]); - newState = deleteIn(newState, ['out', port.nodeId, action.portId]); - newState = deleteIn(newState, ['in', port.nodeId, action.portId]); - - const reindexed = indexPortMap( - filterPortsByDirection( - filterPortsByNode(newState.ports, port.nodeId), - port.getPortDirection(), + const newState = portInLink(state, action.portId) + .reduce( + (cumulativeState: State, link: LinkRecordType) => + linkReducer(cumulativeState, removeLink(link.id)), + portOutLink(state, action.portId).reduce( + (cumulativeState: State, link: LinkRecordType) => + linkReducer(cumulativeState, removeLink(link.id)), + state, + ), + ) + .deleteIn(['ports', action.portId]) + .deleteIn(['out', state.getIn(['ports', action.portId, 'nodeId']), action.portId]) + .deleteIn(['in', state.getIn(['ports', action.portId, 'nodeId']), action.portId]); + return newState.mergeDeep({ + ports: indexPortMap( + filterPortsByDirection( + filterPortsByNode(newState.get('ports'), port.nodeId), + port.getPortDirection(), + ), ), - ); - - return { - ...newState, - ports: { - ...newState.ports, - ...reindexed, - }, - }; + }); } return state; } diff --git a/packages/flow-designer/src/reducers/state-utils.test.ts b/packages/flow-designer/src/reducers/state-utils.test.ts deleted file mode 100644 index 34de18d3746..00000000000 --- a/packages/flow-designer/src/reducers/state-utils.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { setIn, deleteIn } from './state-utils'; - -describe('setIn', () => { - it('sets a nested value', () => { - const obj = { a: { b: 1 } }; - const result = setIn(obj, ['a', 'b'], 2); - expect(result).toEqual({ a: { b: 2 } }); - }); - - it('creates intermediate objects for a new path', () => { - const obj = {}; - const result = setIn(obj, ['a', 'b'], 1); - expect(result).toEqual({ a: { b: 1 } }); - }); - - it('does not mutate the original object', () => { - const obj = { a: { b: 1 } }; - setIn(obj, ['a', 'b'], 2); - expect(obj).toEqual({ a: { b: 1 } }); - }); - - it('sets a top-level value', () => { - const obj = { a: 1 }; - const result = setIn(obj, ['a'], 99); - expect(result).toEqual({ a: 99 }); - }); - - it('returns value as-is when path is empty', () => { - const obj = { a: 1 }; - const newValue = { b: 2 }; - const result = setIn(obj, [], newValue); - expect(result).toBe(newValue); - }); -}); - -describe('deleteIn', () => { - it('deletes a nested key', () => { - const obj = { a: { b: 1, c: 2 } }; - const result = deleteIn(obj, ['a', 'b']); - expect(result).toEqual({ a: { c: 2 } }); - }); - - it('deletes a top-level key', () => { - const obj = { a: 1, b: 2 }; - const result = deleteIn(obj, ['a']); - expect(result).toEqual({ b: 2 }); - }); - - it('returns a clone unchanged when path is empty', () => { - const obj = { a: 1 }; - const result = deleteIn(obj, []); - expect(result).toEqual({ a: 1 }); - }); - - it('does nothing when the path does not exist', () => { - const obj = { a: { b: 1 } }; - const result = deleteIn(obj, ['a', 'z']); - expect(result).toEqual({ a: { b: 1 } }); - }); - - it('does not mutate the original object', () => { - const obj = { a: { b: 1, c: 2 } }; - deleteIn(obj, ['a', 'b']); - expect(obj).toEqual({ a: { b: 1, c: 2 } }); - }); -}); diff --git a/packages/flow-designer/src/reducers/state-utils.ts b/packages/flow-designer/src/reducers/state-utils.ts deleted file mode 100644 index 7e30055b3fb..00000000000 --- a/packages/flow-designer/src/reducers/state-utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import cloneDeep from 'lodash/cloneDeep'; -import get from 'lodash/get'; -import set from 'lodash/set'; - -/** - * Immutable setIn helper for plain Redux state objects. - * Deep-clones obj then sets the value at path. - */ -export function setIn(obj: T, path: (string | number)[], value: any): T { - if (path.length === 0) { - return value as T; - } - const clone = cloneDeep(obj); - set(clone, path, value); - return clone; -} - -/** - * Immutable deleteIn helper for plain Redux state objects. - * Deep-clones obj then deletes the key at path. - */ -export function deleteIn(obj: T, path: (string | number)[]): T { - const clone = cloneDeep(obj); - if (path.length === 0) return clone; - const parent = path.length === 1 ? clone : get(clone, path.slice(0, -1)); - if (parent != null) { - delete (parent as any)[path[path.length - 1]]; - } - return clone; -} diff --git a/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap b/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap index 922765ec579..0ecdd881426 100644 --- a/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap +++ b/packages/flow-designer/src/selectors/__snapshots__/nodeSelectors.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Testing node selectors > node1 should have 7 successors 1`] = ` -Set { +Immutable.Set [ "id8", "id7", "id5", @@ -9,28 +9,28 @@ Set { "id6", "id4", "id2", -} +] `; -exports[`Testing node selectors > node1 should not have any predecessors 1`] = `Set {}`; +exports[`Testing node selectors > node1 should not have any predecessors 1`] = `Immutable.Set []`; exports[`Testing node selectors > node4 should have node1, node2 as predecessors 1`] = ` -Set { +Immutable.Set [ "id1", "id2", -} +] `; exports[`Testing node selectors > node4 should have node6, node7, node8 as successors 1`] = ` -Set { +Immutable.Set [ "id8", "id7", "id6", -} +] `; exports[`Testing node selectors > node8 should have 7 predecessors 1`] = ` -Set { +Immutable.Set [ "id1", "id2", "id3", @@ -38,7 +38,7 @@ Set { "id4", "id6", "id7", -} +] `; -exports[`Testing node selectors > node8 should not have any successors 1`] = `Set {}`; +exports[`Testing node selectors > node8 should not have any successors 1`] = `Immutable.Set []`; diff --git a/packages/flow-designer/src/selectors/linkSelectors.ts b/packages/flow-designer/src/selectors/linkSelectors.ts index 01de4263cd4..58473f865b2 100644 --- a/packages/flow-designer/src/selectors/linkSelectors.ts +++ b/packages/flow-designer/src/selectors/linkSelectors.ts @@ -1,4 +1,5 @@ import { createSelector } from 'reselect'; +import { Map } from 'immutable'; import { State, PortRecordMap, @@ -8,21 +9,17 @@ import { Id, } from '../customTypings/index.d'; -const getPorts = (state: State): PortRecordMap => state.ports; -const getLinks = (state: State): LinkRecordMap => state.links; +const getPorts = (state: State): PortRecordMap => state.get('ports'); +const getLinks = (state: State): LinkRecordMap => state.get('links'); export const getDetachedLinks = createSelector( [getLinks, getPorts], - (links: LinkRecordMap, ports: PortRecordMap) => { - const portIds = new Set(Object.values(ports || {}).map((port: PortRecord) => port.id)); - return Object.fromEntries( - Object.entries(links || {}).filter( - ([, link]) => - !portIds.has((link as LinkRecord).sourceId) || - !portIds.has((link as LinkRecord).targetId), - ), - ); - }, + (links: LinkRecordMap, ports: PortRecordMap) => + links.filter( + (link: LinkRecord) => + !ports.find((port: PortRecord) => port.id === link.sourceId) || + !ports.find((port: PortRecord) => port.id === link.targetId), + ), ); /** @@ -31,10 +28,7 @@ export const getDetachedLinks = createSelector( * @return {Link} */ export function portOutLink(state: State, portId: Id) { - const links = state.links || {}; - return Object.fromEntries( - Object.entries(links).filter(([, link]) => (link as LinkRecord).sourceId === portId), - ); + return state.get('links').filter((link: LinkRecord) => link.sourceId === portId) || Map(); } /** @@ -43,10 +37,7 @@ export function portOutLink(state: State, portId: Id) { * @return {Link} */ export function portInLink(state: State, portId: Id) { - const links = state.links || {}; - return Object.fromEntries( - Object.entries(links).filter(([, link]) => (link as LinkRecord).targetId === portId), - ); + return state.get('links').filter((link: LinkRecord) => link.targetId === portId) || Map(); } /** @@ -55,8 +46,9 @@ export function portInLink(state: State, portId: Id) { * @return number */ export function outLink(state: State, nodeId: Id) { - const outMap = state.out?.[nodeId] || {}; - return Object.assign({}, ...Object.values(outMap)) as Record; + return state + .getIn(['out', nodeId]) + .reduce((reduction: PortRecordMap, port: PortRecord) => reduction.merge(port), Map()); } /** @@ -65,8 +57,9 @@ export function outLink(state: State, nodeId: Id) { * @return number */ export function inLink(state: State, nodeId: Id) { - const inMap = state.in?.[nodeId] || {}; - return Object.assign({}, ...Object.values(inMap)) as Record; + return state + .getIn(['in', nodeId]) + .reduce((reduction: PortRecordMap, port: PortRecord) => reduction.merge(port), Map()); } export default getDetachedLinks; diff --git a/packages/flow-designer/src/selectors/nodeSelectors.test.ts b/packages/flow-designer/src/selectors/nodeSelectors.test.ts index b34db50b956..5afa3b1bc85 100644 --- a/packages/flow-designer/src/selectors/nodeSelectors.test.ts +++ b/packages/flow-designer/src/selectors/nodeSelectors.test.ts @@ -1,12 +1,19 @@ +import { List, Map, OrderedMap } from 'immutable'; import * as Selectors from './nodeSelectors'; import { NodeRecord, NestedNodeRecord, PortRecord, LinkRecord, - LinkGraphicalAttributes, } from '../constants/flowdesigner.model'; -import { State } from '../customTypings/index.d'; +import { + State, + NodeRecord as NodeRecordType, + NestedNodeRecord as NestedNodeRecordType, + PortRecord as PortRecordType, + LinkRecord as LinkRecordType, + Id, +} from '../customTypings/index.d'; describe('Testing node selectors', () => { const node1 = new NodeRecord({ @@ -125,117 +132,108 @@ describe('Testing node selectors', () => { id: 'id1', sourceId: 'id1', targetId: 'id2', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link2 = new LinkRecord({ id: 'id2', sourceId: 'id3', targetId: 'id5', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link3 = new LinkRecord({ id: 'id3', sourceId: 'id6', targetId: 'id9', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link4 = new LinkRecord({ id: 'id4', sourceId: 'id10', targetId: 'id13', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link5 = new LinkRecord({ id: 'id5', sourceId: 'id15', targetId: 'id16', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link6 = new LinkRecord({ id: 'id6', sourceId: 'id4', targetId: 'id7', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link7 = new LinkRecord({ id: 'id7', sourceId: 'id8', targetId: 'id11', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), + graphicalAttributes: Map().set('attr', 'attr'), }); const link8 = new LinkRecord({ id: 'id8', sourceId: 'id12', targetId: 'id14', - graphicalAttributes: new LinkGraphicalAttributes({ attr: 'attr' }), - }); - - const givenState: State = { - nodes: { - id1: node1, - id2: node2, - id3: node3, - id4: node4, - id5: node5, - id6: node6, - id7: node7, - id8: node8, - }, - ports: { - id1: port1, - id2: port2, - id3: port3, - id4: port4, - id5: port5, - id6: port6, - id7: port7, - id8: port8, - id9: port9, - id10: port10, - id11: port11, - id12: port12, - id13: port13, - id14: port14, - id15: port15, - id16: port16, - }, - links: { - id1: link1, - id2: link2, - id3: link3, - id4: link4, - id5: link5, - id6: link6, - id7: link7, - id8: link8, - }, - parents: { - id1: {}, - id2: { id1: 'id1' }, - id3: { id2: 'id2' }, - id4: { id2: 'id2' }, - id5: { id3: 'id3' }, - id6: { id4: 'id4' }, - id7: { id5: 'id5', id6: 'id6' }, - id8: { id7: 'id7' }, - }, - childrens: { - id1: { id2: 'id2' }, - id2: { id3: 'id3', id4: 'id4' }, - id3: { id5: 'id5' }, - id4: { id6: 'id6' }, - id5: { id7: 'id7' }, - id6: { id7: 'id7' }, - id7: { id8: 'id8' }, - id8: {}, - }, - out: {}, - in: {}, - nodeTypes: {}, - transform: { k: 1, x: 0, y: 0 }, - transformToApply: undefined, - }; + graphicalAttributes: Map().set('attr', 'attr'), + }); + + const givenState: State = Map({ + nodes: Map() + .set('id1', node1) + .set('id2', node2) + .set('id3', node3) + .set('id4', node4) + .set('id5', node5) + .set('id6', node6) + .set('id7', node7) + .set('id8', node8), + // eslint-disable-next-line new-cap + ports: OrderedMap() + .set('id1', port1) + .set('id2', port2) + .set('id3', port3) + .set('id4', port4) + .set('id5', port5) + .set('id6', port6) + .set('id7', port7) + .set('id8', port8) + .set('id9', port9) + .set('id10', port10) + .set('id11', port11) + .set('id12', port12) + .set('id13', port13) + .set('id14', port14) + .set('id15', port15) + .set('id16', port16), + links: Map() + .set('id1', link1) + .set('id2', link2) + .set('id3', link3) + .set('id4', link4) + .set('id5', link5) + .set('id6', link6) + .set('id7', link7) + .set('id8', link8), + parents: Map>() + .set('id1', Map({})) + .set('id2', Map({ id1: 'id1' })) + .set('id3', Map({ id2: 'id2' })) + .set('id4', Map({ id2: 'id2' })) + .set('id5', Map({ id3: 'id3' })) + .set('id6', Map({ id4: 'id4' })) + .set('id7', Map({ id5: 'id5', id6: 'id6' })) + .set('id8', Map({ id7: 'id7' })), + childrens: Map>() + .set('id1', Map({ id2: 'id2' })) + .set('id2', Map({ id3: 'id3', id4: 'id4' })) + .set('id3', Map({ id5: 'id5' })) + .set('id4', Map({ id6: 'id6' })) + .set('id5', Map({ id7: 'id7' })) + .set('id6', Map({ id7: 'id7' })) + .set('id7', Map({ id8: 'id8' })) + .set('id8', Map({})), + }); it('node1 should not have any predecessors', () => { expect(Selectors.getPredecessors(givenState, 'id1')).toMatchSnapshot(); @@ -265,32 +263,28 @@ describe('Testing node selectors', () => { describe('Testing node selectors on nested nodes', () => { const nodeA1 = new NestedNodeRecord({ id: 'nodeIdA1', - components: {}, + components: List(), }); const nodeA = new NestedNodeRecord({ id: 'nodeIdA', - components: { nodeIdA1: nodeA1 }, + components: List([nodeA1]), }); const nodeB = new NestedNodeRecord({ id: 'nodeIdB', }); - const givenState: State = { - nodes: { nodeIdA: nodeA, nodeIdB: nodeB }, - ports: {}, - links: {}, - parents: {}, - childrens: {}, - out: {}, - in: {}, - nodeTypes: {}, - transform: { k: 1, x: 0, y: 0 }, - transformToApply: undefined, - }; + const givenState: State = Map({ + nodes: Map().set('nodeIdA', nodeA).set('nodeIdB', nodeB), + // eslint-disable-next-line new-cap + ports: OrderedMap(), + links: Map(), + parents: Map>(), + childrens: Map>(), + }); it('nodeA should not have 1 embeded child and node B 0 children', () => { - expect(Object.keys(givenState.nodes['nodeIdA'].components).length).toBe(1); - expect(Object.keys(givenState.nodes['nodeIdB'].components).length).toBe(0); + expect(givenState.get('nodes').get('nodeIdA').get('components').size).toBe(1); + expect(givenState.get('nodes').get('nodeIdB').get('components').size).toBe(0); }); }); diff --git a/packages/flow-designer/src/selectors/nodeSelectors.ts b/packages/flow-designer/src/selectors/nodeSelectors.ts index 3b4f33131bd..b2ee84486d6 100644 --- a/packages/flow-designer/src/selectors/nodeSelectors.ts +++ b/packages/flow-designer/src/selectors/nodeSelectors.ts @@ -1,29 +1,30 @@ +import { Set } from 'immutable'; import { State, Id } from '../customTypings/index.d'; /** - * @param state plain flow state + * @param state Map flow state * @param nodeId String * @param predecessors Set list of already determined predecessors */ -export function getPredecessors(state: State, nodeId: Id, predecessors?: Set): Set { - const parents = state.parents?.[nodeId] ?? {}; - return Object.values(parents).reduce>((acc, parentId) => { - const deeper = getPredecessors(state, parentId as Id, acc); - deeper.add(parentId as Id); - return deeper; - }, predecessors || new Set()); +export function getPredecessors(state: State, nodeId: Id, predecessors?: Set) { + return state.getIn(['parents', nodeId]).reduce( + (accumulator: Set, parentId: Id) => + getPredecessors(state, parentId, accumulator).add(parentId), + // eslint-disable-next-line new-cap + predecessors || Set(), + ); } /** - * @param state plain flow state + * @param state Map flow state * @param nodeId String * @param successors Set list of already determined successors */ -export function getSuccessors(state: State, nodeId: Id, successors?: Set): Set { - const childrens = state.childrens?.[nodeId] ?? {}; - return Object.values(childrens).reduce>((acc, childrenId) => { - const deeper = getSuccessors(state, childrenId as Id, acc); - deeper.add(childrenId as Id); - return deeper; - }, successors || new Set()); +export function getSuccessors(state: State, nodeId: Id, successors?: Set) { + return state.getIn(['childrens', nodeId]).reduce( + (accumulator: Set, childrenId: Id) => + getSuccessors(state, childrenId, accumulator).add(childrenId), + // eslint-disable-next-line new-cap + successors || Set(), + ); } diff --git a/packages/flow-designer/src/selectors/portSelectors.test.ts b/packages/flow-designer/src/selectors/portSelectors.test.ts index 35bfdd00001..8f033b13ef6 100644 --- a/packages/flow-designer/src/selectors/portSelectors.test.ts +++ b/packages/flow-designer/src/selectors/portSelectors.test.ts @@ -1,31 +1,41 @@ +import { Map } from 'immutable'; import * as Selectors from './portSelectors'; import { defaultState } from '../reducers/flow.reducer'; import { LinkRecord } from '../constants/flowdesigner.model'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; import { Port } from '../api'; -import { State } from '../customTypings/index.d'; +import { + State, + Id, + LinkRecord as LinkRecordType, + PortRecord as PortRecordType, +} from '../customTypings/index.d'; const port1 = Port.create('id1', 'nodeId1', 0, PORT_SINK, 'reactComponentType'); const port2 = Port.create('id2', 'nodeId1', 0, PORT_SOURCE, 'reactComponentType'); const port3 = Port.create('id3', 'nodeId2', 0, PORT_SINK, 'reactComponentType'); const port4 = Port.create('id4', 'nodeId2', 0, PORT_SOURCE, 'reactComponentType'); -const givenState: Partial = { - ...defaultState, - links: { - id1: new LinkRecord({ - id: 'id1', - source: 'id1', - target: 'id2', - }), - }, - ports: { - id1: port1, - id2: port2, - id3: port3, - id4: port4, - }, -}; +const givenState: Partial = defaultState + .set( + 'links', + Map().set( + 'id1', + new LinkRecord({ + id: 'id1', + source: 'id1', + target: 'id2', + }), + ), + ) + .set( + 'ports', + Map() + .set('id1', port1) + .set('id2', port2) + .set('id3', port3) + .set('id4', port4), + ); describe('getEmitterPorts', () => { it('return a map with port id2 && id4', () => { @@ -33,8 +43,8 @@ describe('getEmitterPorts', () => { // when const result = Selectors.getEmitterPorts(givenState); // expect - expect('id2' in result).toBe(true); - expect('id4' in result).toBe(true); + expect(result.has('id2')).toBe(true); + expect(result.has('id4')).toBe(true); }); }); @@ -44,8 +54,8 @@ describe('getSinkPorts', () => { // when const result = Selectors.getSinkPorts(givenState); // expect - expect('id1' in result).toBe(true); - expect('id3' in result).toBe(true); + expect(result.has('id1')).toBe(true); + expect(result.has('id3')).toBe(true); }); }); @@ -55,7 +65,7 @@ describe('getEmitterPortsForNode', () => { // when const result = Selectors.getEmitterPortsForNode(givenState)('nodeId1'); // expect - expect('id2' in result).toBe(true); + expect(result.has('id2')).toBe(true); }); }); @@ -65,6 +75,6 @@ describe('getSinkPortsForNode', () => { // when const result = Selectors.getSinkPortsForNode(givenState)('nodeId1'); // expect - expect('id1' in result).toBe(true); + expect(result.has('id1')).toBe(true); }); }); diff --git a/packages/flow-designer/src/selectors/portSelectors.ts b/packages/flow-designer/src/selectors/portSelectors.ts index 60cda106408..ab3f253b13d 100644 --- a/packages/flow-designer/src/selectors/portSelectors.ts +++ b/packages/flow-designer/src/selectors/portSelectors.ts @@ -1,5 +1,6 @@ import { createSelector } from 'reselect'; import memoize from 'lodash/memoize'; +import { Map } from 'immutable'; import { Port } from '../api'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; @@ -12,22 +13,22 @@ import { LinkRecord, } from '../customTypings/index.d'; -const getNodes = (state: State): NodeRecordMap => state.nodes; -const getPorts = (state: State): PortRecordMap => state.ports; -const getLinks = (state: State): LinkRecordMap => state.links; +const getNodes = (state: State): NodeRecordMap => state.get('nodes'); +const getPorts = (state: State): PortRecordMap => state.get('ports'); +const getLinks = (state: State): LinkRecordMap => state.get('links'); /** * return a list of outgoing port for this node */ export function outPort(state: State, nodeId: string) { - return state.out?.[nodeId] || {}; + return state.getIn(['out', nodeId]) || Map(); } /** * return a list of ingoing port for this node */ export function inPort(state: State, nodeId: string) { - return state.in?.[nodeId] || {}; + return state.getIn(['in', nodeId]) || Map(); } /** @@ -38,11 +39,7 @@ export const getPortsForNode = createSelector( getPorts, (ports: PortRecordMap): PortRecord => memoize((nodeId: string) => - Object.fromEntries( - Object.entries(ports || {}).filter( - ([, port]) => Port.getNodeId(port as PortRecord) === nodeId, - ), - ), + ports.filter((port: PortRecord) => Port.getNodeId(port) === nodeId), ), ); @@ -54,11 +51,7 @@ export const getPortsForNode = createSelector( export const getEmitterPorts = createSelector( getPorts, (ports: PortRecordMap): PortRecord => - Object.fromEntries( - Object.entries(ports || {}).filter( - ([, port]) => Port.getTopology(port as any) === PORT_SOURCE, - ), - ) as any, + ports.filter((port: any) => Port.getTopology(port) === PORT_SOURCE), ); /** @@ -69,9 +62,7 @@ export const getEmitterPorts = createSelector( export const getSinkPorts = createSelector( getPorts, (ports: PortRecordMap): PortRecord => - Object.fromEntries( - Object.entries(ports || {}).filter(([, port]) => Port.getTopology(port as any) === PORT_SINK), - ) as any, + ports.filter((port: any) => Port.getTopology(port) === PORT_SINK), ); /** @@ -81,9 +72,7 @@ export const getEmitterPortsForNode = createSelector( getEmitterPorts as any, (ports: PortRecordMap): PortRecord => (nodeId: string) => - Object.fromEntries( - Object.entries(ports || {}).filter(([, port]) => Port.getNodeId(port as any) === nodeId), - ), + ports.filter((port: any) => Port.getNodeId(port) === nodeId), ); /** @@ -93,9 +82,7 @@ export const getSinkPortsForNode = createSelector( getSinkPorts as any, (ports: PortRecordMap): PortRecord => (nodeId: string) => - Object.fromEntries( - Object.entries(ports || {}).filter(([, port]) => Port.getNodeId(port as any) === nodeId), - ), + ports.filter((port: any) => Port.getNodeId(port) === nodeId), ); /** @@ -106,15 +93,12 @@ export const getSinkPortsForNode = createSelector( */ export const getFreeSinkPorts = createSelector( [getSinkPorts, getLinks], - (sinkPorts: PortRecordMap, links: LinkRecordMap) => - Object.fromEntries( - Object.entries(sinkPorts || {}).filter( - ([, sinkPort]) => - !Object.values(links || {}).find( - (link: LinkRecord) => link.targetId === Port.getId(sinkPort as PortRecord), - ), - ), - ) as PortRecordMap, + (sinkPorts: PortRecordMap, links: LinkRecordMap) => { + return sinkPorts.filter( + (sinkPort: PortRecord) => + !links.find((link: LinkRecord) => link.targetId === Port.getId(sinkPort)), + ) as PortRecordMap; + }, ); /** @@ -126,13 +110,9 @@ export const getFreeSinkPorts = createSelector( export const getFreeEmitterPorts = createSelector( [getEmitterPorts as any, getLinks as any], (emitterPorts: PortRecordMap, links: LinkRecordMap) => - Object.fromEntries( - Object.entries(emitterPorts || {}).filter( - ([, emitterPort]) => - !Object.values(links || {}).find( - (link: LinkRecord) => link.sourceId === Port.getId(emitterPort as PortRecord), - ), - ), + emitterPorts.filter( + (emitterPort: PortRecord) => + !links.find((link: LinkRecord) => link.sourceId === Port.getId(emitterPort)), ), ); @@ -145,18 +125,13 @@ export const getFreeEmitterPorts = createSelector( export const getActionKeyedPorts = createSelector( getFreeSinkPorts as any, (freeSinkPorts: PortRecordMap) => - Object.fromEntries( - Object.entries(freeSinkPorts || {}).filter(([, sinkPort]) => (sinkPort as any).accessKey), - ), + freeSinkPorts.filter((sinkPort: { accessKey: any }) => sinkPort.accessKey), ); export const getDetachedPorts = createSelector( [getPorts as any, getNodes as any], (ports: PortRecordMap, nodes: NodeRecordMap) => - Object.fromEntries( - Object.entries(ports || {}).filter( - ([, port]) => - !Object.values(nodes || {}).find((node: any) => node.id === Port.getNodeId(port as any)), - ), + ports.filter( + (port: any) => !nodes.find((node: { id: any }) => node.id === Port.getNodeId(port)), ), ); diff --git a/yarn.lock b/yarn.lock index a6fcd1224e7..00d01b3c8a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3558,6 +3558,15 @@ raf "^3.4.1" regenerator-runtime "^0.13.11" +"@talend/scripts-config-stylelint@^4.5.0": + version "4.5.1" + resolved "https://npm.pkg.github.com/download/@talend/scripts-config-stylelint/4.5.1/df7b6c06a47504b0c65fcac0508b7efd8357b70f#df7b6c06a47504b0c65fcac0508b7efd8357b70f" + integrity sha512-irjusFPw/LRsyUg6NPqiwykpxrX1fHRZ43Yrjb6SUjcPrVuPRqOMvDzEuRqwXEGlmpNbFIstxdg761zGnvo0WQ== + dependencies: + stylelint "^15.11.0" + stylelint-config-sass-guidelines "^10.0.0" + stylelint-config-standard "^34.0.0" + "@talend/scripts-core@^18.0.0": version "18.0.0" resolved "https://npm.pkg.github.com/download/@talend/scripts-core/18.0.0/1272cbee7eca7082519b4bbbb4b3c64e023f9ea1#1272cbee7eca7082519b4bbbb4b3c64e023f9ea1" @@ -9502,6 +9511,11 @@ ignore@^7.0.5: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.8.tgz#02d183c7727fb2bb1d5d0380da0d779dce9296a7" integrity sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw== +immutable@^3.8.2: + version "3.8.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.3.tgz#0a8d2494a94d4b2d4f0e99986e74dd25d1e9a859" + integrity sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg== + immutable@^5.0.2: version "5.1.5" resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" @@ -9610,7 +9624,7 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -invariant@^2.2.4: +invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -13003,6 +13017,11 @@ postcss-media-minmax@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" + integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== + postcss-merge-longhand@^7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz#e1b126e92f583815482e8b1e82c47d2435a20421" @@ -13281,6 +13300,11 @@ postcss-safe-parser@^7.0.1: resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz#36e4f7e608111a0ca940fd9712ce034718c40ec0" integrity sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A== +postcss-scss@^4.0.6: + version "4.0.9" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" + integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== + postcss-selector-not@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" @@ -13288,7 +13312,7 @@ postcss-selector-not@^6.0.1: dependencies: postcss-selector-parser "^6.0.10" -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: version "6.1.2" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== @@ -13723,6 +13747,13 @@ react-i18next@^13.5.0: "@babel/runtime" "^7.22.5" html-parse-stringify "^3.0.1" +react-immutable-proptypes@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz#cce96d68cc3c18e89617cbf3092d08e35126af4a" + integrity sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ== + dependencies: + invariant "^2.2.2" + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -15450,11 +15481,31 @@ stylehacks@^7.0.5: browserslist "^4.28.1" postcss-selector-parser "^7.1.1" +stylelint-config-recommended@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz#c48a358cc46b629ea01f22db60b351f703e00597" + integrity sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ== + stylelint-config-recommended@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz#1d86be73565c3cd5e6babb8abcc932012a86bd76" integrity sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg== +stylelint-config-sass-guidelines@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-10.0.0.tgz#ace99689eb6769534c9b40d62e2a8562b1ddc9f2" + integrity sha512-+Rr2Dd4b72CWA4qoj1Kk+y449nP/WJsrD0nzQAWkmPPIuyVcy2GMIcfNr0Z8JJOLjRvtlkKxa49FCNXMePBikQ== + dependencies: + postcss-scss "^4.0.6" + stylelint-scss "^4.4.0" + +stylelint-config-standard@^34.0.0: + version "34.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz#309f3c48118a02aae262230c174282e40e766cf4" + integrity sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ== + dependencies: + stylelint-config-recommended "^13.0.0" + stylelint-config-standard@^40.0.0: version "40.0.0" resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-40.0.0.tgz#6bb72b94f8be434cdf2902ba26b2167d572c5414" @@ -15462,6 +15513,16 @@ stylelint-config-standard@^40.0.0: dependencies: stylelint-config-recommended "^18.0.0" +stylelint-scss@^4.4.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.7.0.tgz#f986bf8c5a4b93eae2b67d3a3562eef822657908" + integrity sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg== + dependencies: + postcss-media-query-parser "^0.2.3" + postcss-resolve-nested-selector "^0.1.1" + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" + stylelint@^15.11.0: version "15.11.0" resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.11.0.tgz#3ff8466f5f5c47362bc7c8c9d382741c58bc3292" From 78e74a1029a0805b4b346fee572f021e75851afe Mon Sep 17 00:00:00 2001 From: smouillour Date: Tue, 17 Mar 2026 11:05:23 +0100 Subject: [PATCH 14/16] update migration doc --- .../migration-guide-remove-immutable.md | 17 ++-- ...on-guide-remove-immutable-flow-designer.md | 88 ------------------- 2 files changed, 8 insertions(+), 97 deletions(-) delete mode 100644 docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md diff --git a/docs/migration-guides/migration-guide-remove-immutable.md b/docs/migration-guides/migration-guide-remove-immutable.md index cb720828bbd..291651d8ffc 100644 --- a/docs/migration-guides/migration-guide-remove-immutable.md +++ b/docs/migration-guides/migration-guide-remove-immutable.md @@ -4,15 +4,14 @@ This is the index for per-package migration guides covering the breaking changes ## Affected packages -| Package | Version bump | Guide | -| ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- | -| `@talend/react-cmf` | **MAJOR** | [migration-guide-remove-immutable-cmf.md](./remove-immutable/migration-guide-remove-immutable-cmf.md) | -| `@talend/react-containers` | **MAJOR** | [migration-guide-remove-immutable-containers.md](./remove-immutable/migration-guide-remove-immutable-containers.md) | -| `@talend/react-cmf-cqrs` | **MAJOR** | [migration-guide-remove-immutable-cmf-cqrs.md](./remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md) | -| `@talend/react-sagas` | **MAJOR** | [migration-guide-remove-immutable-sagas.md](./remove-immutable/migration-guide-remove-immutable-sagas.md) | -| `@talend/react-components` | **MAJOR** | [migration-guide-remove-immutable-components.md](./remove-immutable/migration-guide-remove-immutable-components.md) | -| `@talend/react-flow-designer` | **MAJOR** | [migration-guide-remove-immutable-flow-designer.md](./remove-immutable/migration-guide-remove-immutable-flow-designer.md) | -| `@talend/react-stepper` | patch | [migration-guide-remove-immutable-stepper.md](./remove-immutable/migration-guide-remove-immutable-stepper.md) | +| Package | Version bump | Guide | +| -------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------- | +| `@talend/react-cmf` | **MAJOR** | [migration-guide-remove-immutable-cmf.md](./remove-immutable/migration-guide-remove-immutable-cmf.md) | +| `@talend/react-containers` | **MAJOR** | [migration-guide-remove-immutable-containers.md](./remove-immutable/migration-guide-remove-immutable-containers.md) | +| `@talend/react-cmf-cqrs` | **MAJOR** | [migration-guide-remove-immutable-cmf-cqrs.md](./remove-immutable/migration-guide-remove-immutable-cmf-cqrs.md) | +| `@talend/react-sagas` | **MAJOR** | [migration-guide-remove-immutable-sagas.md](./remove-immutable/migration-guide-remove-immutable-sagas.md) | +| `@talend/react-components` | **MAJOR** | [migration-guide-remove-immutable-components.md](./remove-immutable/migration-guide-remove-immutable-components.md) | +| `@talend/react-stepper` | patch | [migration-guide-remove-immutable-stepper.md](./remove-immutable/migration-guide-remove-immutable-stepper.md) | --- diff --git a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md b/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md deleted file mode 100644 index bd2f9bda6bc..00000000000 --- a/docs/migration-guides/remove-immutable/migration-guide-remove-immutable-flow-designer.md +++ /dev/null @@ -1,88 +0,0 @@ -# Migration Guide: `@talend/react-flow-designer` — Removal of ImmutableJS - -**Version bump**: MAJOR - ---- - -## What changed - -`immutable` has been removed from `peerDependencies`. All internal data structures (nodes, edges, ports, links, flow state) that were previously Immutable Maps are now plain TypeScript objects. - -Also removed from `dependencies`: `react-immutable-proptypes`. - ---- - -## Breaking changes - -### 1. `immutable` no longer a required peer dependency - -You no longer need to install `immutable` as a peer dependency of your application when using `@talend/react-flow-designer`. - -Remove it from your `package.json`: - -```json -{ - "peerDependencies": { - "immutable": "^3.0.0" - } -} -``` - -Or if it was in `dependencies`: - -```json -{ - "dependencies": { - "immutable": "^3.8.2" - } -} -``` - -> Only remove it if no other package in your project depends on it. - ---- - -### 2. Internal data structures are now plain objects - -If you worked with the internal flow state directly (e.g., by accessing nodes, ports, links, or flow data), these are now plain objects instead of `Immutable.Map`. - -**Before** - -```js -// nodes, ports, links were Immutable.Map -const node = flowState.nodes.get('nodeId'); -const portType = flowState.nodes.getIn(['nodeId', 'portType']); -``` - -**After** - -```js -// nodes, ports, links are plain objects -const node = flowState.nodes['nodeId']; -const portType = flowState.nodes['nodeId']?.portType; -``` - -If you construct flow state in tests or fixtures, replace Immutable structures with plain objects: - -```js -// Before -import { Map } from 'immutable'; -const flowState = { - nodes: Map({ nodeId: { id: 'nodeId', type: 'default' } }), - links: Map({ linkId: { id: 'linkId', linkType: 'type' } }), -}; - -// After -const flowState = { - nodes: { nodeId: { id: 'nodeId', type: 'default' } }, - links: { linkId: { id: 'linkId', linkType: 'type' } }, -}; -``` - ---- - -## Checklist - -- [ ] Remove `immutable` from `peerDependencies` (and `dependencies`) of your project if only referenced for flow-designer compatibility -- [ ] Replace `Map(...)` / `.get()` / `.getIn()` / `.set()` calls on flow node/port/link data with plain object access -- [ ] Update test fixtures that build flow state with Immutable structures to use plain objects From f024c7410baafc21e0d2eb899f9e7b7b024776d8 Mon Sep 17 00:00:00 2001 From: smouillour Date: Thu, 19 Mar 2026 11:52:21 +0100 Subject: [PATCH 15/16] fix yarn.lock --- package.json | 2 +- yarn.lock | 1820 ++------------------------------------------------ 2 files changed, 59 insertions(+), 1763 deletions(-) diff --git a/package.json b/package.json index ee01a897cc8..7a925010848 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "@talend/eslint-config": "^14.0.0", "@talend/scripts-config-babel": "^13.8.0", "@talend/scripts-config-prettier": "^12.5.0", - "@talend/scripts-core": "^18.0.0", + "@talend/scripts-core": "^20.0.0", "@talend/scripts-yarn-workspace": "^2.3.0", "baseline-browser-mapping": "^2.9.19", "cross-env": "^10.1.0", diff --git a/yarn.lock b/yarn.lock index 00d01b3c8a5..32b81010bb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -225,7 +225,7 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.6.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== @@ -239,7 +239,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.21.3", "@babel/core@^7.23.9", "@babel/core@^7.24.4", "@babel/core@^7.28.0", "@babel/core@^7.28.6", "@babel/core@^7.29.0": +"@babel/core@^7.21.3", "@babel/core@^7.24.4", "@babel/core@^7.28.0", "@babel/core@^7.28.6", "@babel/core@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== @@ -269,7 +269,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.23.0", "@babel/generator@^7.29.0", "@babel/generator@^7.7.2": +"@babel/generator@^7.23.0", "@babel/generator@^7.29.0": version "7.29.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== @@ -390,7 +390,7 @@ dependencies: "@babel/types" "^7.27.1" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== @@ -460,7 +460,7 @@ "@babel/template" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.4", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.24.4", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== @@ -551,34 +551,6 @@ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" @@ -593,41 +565,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.28.6": +"@babel/plugin-syntax-import-attributes@^7.28.6": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== dependencies: "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.28.6", "@babel/plugin-syntax-jsx@^7.7.2": +"@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.28.6": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== dependencies: "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" @@ -635,27 +586,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" @@ -663,21 +593,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.28.6", "@babel/plugin-syntax-typescript@^7.7.2": +"@babel/plugin-syntax-typescript@^7.28.6": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== @@ -1274,7 +1190,7 @@ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== -"@babel/template@^7.24.7", "@babel/template@^7.28.6", "@babel/template@^7.3.3": +"@babel/template@^7.24.7", "@babel/template@^7.28.6": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== @@ -1320,7 +1236,7 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.2.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.23.0", "@babel/types@^7.24.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.2.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.23.0", "@babel/types@^7.24.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.4.4": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== @@ -1328,11 +1244,6 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - "@bcoe/v8-coverage@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" @@ -1594,11 +1505,6 @@ "@csstools/color-helpers" "^6.0.2" "@csstools/css-calc" "^3.1.1" -"@csstools/css-parser-algorithms@^2.3.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz#6d93a8f7d8aeb7cd9ed0868f946e46f021b6aa70" - integrity sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw== - "@csstools/css-parser-algorithms@^3.0.4": version "3.0.5" resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" @@ -1614,11 +1520,6 @@ resolved "https://registry.yarnpkg.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.0.tgz#6c41ea7084e17ee5b8aa3ecd86f85f35eb64994f" integrity sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA== -"@csstools/css-tokenizer@^2.2.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz#1d8b2e200197cf5f35ceb07ca2dade31f3a00ae8" - integrity sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg== - "@csstools/css-tokenizer@^3.0.3": version "3.0.4" resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" @@ -1629,11 +1530,6 @@ resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz#798a33950d11226a0ebb6acafa60f5594424967f" integrity sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA== -"@csstools/media-query-list-parser@^2.1.4": - version "2.1.13" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz#f00be93f6bede07c14ddf51a168ad2748e4fe9e5" - integrity sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA== - "@csstools/media-query-list-parser@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz#99e8d03ff6f9f8df8cf9876e0f17d075e6dae9e7" @@ -1750,11 +1646,6 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016" integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== -"@csstools/selector-specificity@^3.0.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz#63085d2995ca0f0e55aa8b8a07d69bfd48b844fe" - integrity sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA== - "@csstools/selector-specificity@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz#ef28e27c1ded1d8e5c54879a9399e7055aed1920" @@ -2132,83 +2023,11 @@ dependencies: minipass "^7.0.4" -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - "@jest/diff-sequences@30.0.1": version "30.0.1" resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - "@jest/expect-utils@30.2.0": version "30.2.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz#4f95413d4748454fdb17404bf1141827d15e6011" @@ -2216,48 +2035,11 @@ dependencies: "@jest/get-type" "30.1.0" -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - "@jest/get-type@30.1.0": version "30.1.0" resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - "@jest/pattern@30.0.1": version "30.0.1" resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" @@ -2266,36 +2048,6 @@ "@types/node" "*" jest-regex-util "30.0.1" -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - "@jest/schemas@30.0.5": version "30.0.5" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" @@ -2310,56 +2062,6 @@ dependencies: "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - "@jest/types@30.2.0": version "30.2.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.2.0.tgz#1c678a7924b8f59eafd4c77d56b6d0ba976d62b8" @@ -2373,18 +2075,6 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@joshwooding/vite-plugin-react-docgen-typescript@^0.6.4": version "0.6.4" resolved "https://registry.yarnpkg.com/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.4.tgz#9cfa58703ae8122329c52a5989244818ee4cdcbe" @@ -2427,7 +2117,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28", "@jridgewell/trace-mapping@^0.3.31": +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28", "@jridgewell/trace-mapping@^0.3.31": version "0.3.31" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== @@ -3278,13 +2968,6 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers@^11.2.2": version "11.3.1" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz#51d6e8d83ca261ff02c0ab0e68e9db23d5cd5999" @@ -3541,59 +3224,6 @@ resolved "https://npm.pkg.github.com/download/@talend/locales-tui-forms/15.2.0/dee8f65820b7e4e26de8b7972a48fd108bb0ffeb#dee8f65820b7e4e26de8b7972a48fd108bb0ffeb" integrity sha512-6UMXC8/2qSB7u0ovYsvTqw0mtDsMUWTp/ahmuf4VtRGZDaHxwKl1TMhNgcVKDzwQc7AVzP4DBwG3MbzXqBEYlA== -"@talend/scripts-config-jest@^14.7.0": - version "14.7.0" - resolved "https://npm.pkg.github.com/download/@talend/scripts-config-jest/14.7.0/36203de08b4e8445887bd73d8bce27af461cd802#36203de08b4e8445887bd73d8bce27af461cd802" - integrity sha512-sO4DsG6wA/3L61/wBQw06qcscBehLm/E12vW76kc8H7hvM3mA2FuGM2OLZkre0t9OEDdyKNRh7psyweXxtYGuQ== - dependencies: - "@talend/scripts-config-babel" "^13.10.0" - "@testing-library/jest-dom" "^6.9.1" - "@types/jest" "^29.5.14" - babel-jest "^29.7.0" - core-js "^3.48.0" - jest "^29.7.0" - jest-environment-jsdom "^29.7.0" - jest-environment-jsdom-global "^4.0.0" - jest-serializer-html "^7.1.0" - raf "^3.4.1" - regenerator-runtime "^0.13.11" - -"@talend/scripts-config-stylelint@^4.5.0": - version "4.5.1" - resolved "https://npm.pkg.github.com/download/@talend/scripts-config-stylelint/4.5.1/df7b6c06a47504b0c65fcac0508b7efd8357b70f#df7b6c06a47504b0c65fcac0508b7efd8357b70f" - integrity sha512-irjusFPw/LRsyUg6NPqiwykpxrX1fHRZ43Yrjb6SUjcPrVuPRqOMvDzEuRqwXEGlmpNbFIstxdg761zGnvo0WQ== - dependencies: - stylelint "^15.11.0" - stylelint-config-sass-guidelines "^10.0.0" - stylelint-config-standard "^34.0.0" - -"@talend/scripts-core@^18.0.0": - version "18.0.0" - resolved "https://npm.pkg.github.com/download/@talend/scripts-core/18.0.0/1272cbee7eca7082519b4bbbb4b3c64e023f9ea1#1272cbee7eca7082519b4bbbb4b3c64e023f9ea1" - integrity sha512-iSZJn8Px3pXcJb1ATRjLBbeTkvda4qEcVE3U6AaIOloBM5sC5enacxc+Ty2mIQ+JPy89TxcjK5CvyPPH59amfw== - dependencies: - "@babel/cli" "^7.28.6" - "@babel/core" "^7.28.6" - "@talend/eslint-config" "^14.1.0" - "@talend/eslint-plugin" "^1.8.0" - "@talend/scripts-config-babel" "^13.10.0" - "@talend/scripts-config-jest" "^14.7.0" - "@talend/scripts-config-stylelint" "^4.5.0" - "@talend/scripts-config-typescript" "^12.1.0" - "@talend/scripts-utils" "^2.8.0" - babel-loader "^9.2.1" - cpx2 "^8.0.0" - fs-extra "^10.1.0" - jest "^29.7.0" - jest-cli "^29.7.0" - lodash "^4.17.23" - rimraf "^6.1.2" - stylelint "^15.11.0" - typescript "^5.9.3" - webpack "^5.104.1" - webpack-merge "^5.10.0" - yargs "^15.4.1" - "@testing-library/dom@^9.0.0", "@testing-library/dom@^9.3.4": version "9.3.4" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" @@ -3642,11 +3272,6 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - "@trivago/prettier-plugin-sort-imports@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz#725f411646b3942193a37041c84e0b2116339789" @@ -3672,7 +3297,7 @@ "@types/babel__core" "*" "@types/prettier" "^2.0.0" -"@types/babel__core@*", "@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5": +"@types/babel__core@*", "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== @@ -3698,7 +3323,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6", "@types/babel__traverse@^7.20.7": +"@types/babel__traverse@*", "@types/babel__traverse@^7.20.7": version "7.28.0" resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== @@ -4081,13 +3706,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - "@types/hast@^3.0.0": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" @@ -4129,7 +3747,7 @@ resolved "https://registry.yarnpkg.com/@types/is-empty/-/is-empty-1.2.3.tgz#a2d55ea8a5ec57bf61e411ba2a9e5132fe4f0899" integrity sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== @@ -4141,7 +3759,7 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": +"@types/istanbul-reports@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== @@ -4164,28 +3782,11 @@ expect "^30.0.0" pretty-format "^30.0.0" -"@types/jest@^29.5.14": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - "@types/js-cookie@^2.2.6": version "2.2.7" resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== -"@types/jsdom@^20.0.0": - version "20.0.1" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" - integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== - dependencies: - "@types/node" "*" - "@types/tough-cookie" "*" - parse5 "^7.0.0" - "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -4220,11 +3821,6 @@ dependencies: minimatch "*" -"@types/minimist@^1.2.2": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" - integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== - "@types/ms@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" @@ -4423,7 +4019,7 @@ dependencies: "@types/node" "*" -"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": +"@types/stack-utils@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== @@ -4453,11 +4049,6 @@ dependencies: "@types/geojson" "*" -"@types/tough-cookie@*": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" - integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== - "@types/unist@*", "@types/unist@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" @@ -4480,7 +4071,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": +"@types/yargs@^17.0.33": version "17.0.35" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== @@ -4888,11 +4479,6 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -abab@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - abbrev@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" @@ -4923,14 +4509,6 @@ acorn-class-fields@^0.3.7: dependencies: acorn-private-class-elements "^0.2.7" -acorn-globals@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" - integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== - dependencies: - acorn "^8.1.0" - acorn-walk "^8.0.2" - acorn-import-phases@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" @@ -4969,14 +4547,14 @@ acorn-static-class-features@^0.2.4: dependencies: acorn-private-class-elements "^0.2.7" -acorn-walk@^8.0.0, acorn-walk@^8.0.2: +acorn-walk@^8.0.0: version "8.3.5" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== dependencies: acorn "^8.11.0" -acorn@^8.0.0, acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.8.1: +acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0: version "8.16.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== @@ -5066,13 +4644,6 @@ ansi-colors@^4.1.1, ansi-colors@^4.1.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - ansi-escapes@^7.0.0: version "7.3.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz#5395bb74b2150a4a1d6e3c2565f4aeca78d28627" @@ -5124,7 +4695,7 @@ any-promise@^1.0.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== -anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: +anymatch@^3.1.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -5256,11 +4827,6 @@ arraybuffer.prototype.slice@^1.0.4: get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== - asap@^2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -5380,19 +4946,6 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - babel-loader@^9.2.1: version "9.2.1" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.2.1.tgz#04c7835db16c246dd19ba0914418f3937797587b" @@ -5410,27 +4963,6 @@ babel-plugin-angularjs-annotate@^0.10.0: "@babel/types" "^7.2.0" simple-is "~0.2.0" -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - babel-plugin-polyfill-corejs2@^0.4.15: version "0.4.16" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.16.tgz#a1321145f6cde738b0a412616b6bcf77f143ab36" @@ -5476,35 +5008,6 @@ babel-plugin-tester@^10.1.0: prettier "^2.0.1" strip-indent "^3.0.0" -babel-preset-current-node-syntax@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" - integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - babel-runtime@^6.11.6: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" @@ -5523,11 +5026,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -balanced-match@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" - integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== - balanced-match@^4.0.2: version "4.0.4" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" @@ -5676,13 +5174,6 @@ browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.24.0, browserslist@^4 node-releases "^2.0.27" update-browserslist-db "^1.2.0" -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -5796,22 +5287,12 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase-keys@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252" - integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg== - dependencies: - camelcase "^6.3.0" - map-obj "^4.1.0" - quick-lru "^5.1.1" - type-fest "^1.2.1" - -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -5897,11 +5378,6 @@ chalk@^5.4.1: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - character-entities-html4@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" @@ -5971,11 +5447,6 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - ci-info@^4.0.0, ci-info@^4.2.0: version "4.4.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" @@ -5986,11 +5457,6 @@ circular-dependency-plugin@^5.2.2: resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== -cjs-module-lexer@^1.0.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - classnames@*, classnames@^2.2.5, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" @@ -6097,16 +5563,6 @@ clsx@^2.0.0, clsx@^2.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" - integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== - color-contrast-checker@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/color-contrast-checker/-/color-contrast-checker-2.1.0.tgz#05bda8ee59bd7901324ca0443497cf169f1b3296" @@ -6371,11 +5827,6 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.48.0: - version "3.48.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.48.0.tgz#1f813220a47bbf0e667e3885c36cd6f0593bf14d" - integrity sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -6420,19 +5871,6 @@ cpx2@^8.0.0: shell-quote "^1.8.0" subarg "^1.0.0" -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - create-react-class@^15.7.0: version "15.7.0" resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e" @@ -6482,7 +5920,7 @@ css-declaration-sorter@^7.2.0: resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz#acd204976d7ca5240b5579bfe6e73d4d088fd568" integrity sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA== -css-functions-list@^3.2.1, css-functions-list@^3.3.3: +css-functions-list@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.3.3.tgz#c4ab5008659de2e3baf3752c8fdef7662f3ffe23" integrity sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg== @@ -6671,23 +6109,6 @@ csso@^5.0.5: dependencies: css-tree "~2.2.0" -cssom@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" - integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - cssstyle@^4.2.1: version "4.6.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.6.0.tgz#ea18007024e3167f4f105315f3ec2d982bf48ed9" @@ -6973,15 +6394,6 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -data-urls@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" - integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== - dependencies: - abab "^2.0.6" - whatwg-mimetype "^3.0.0" - whatwg-url "^11.0.0" - data-urls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" @@ -7066,30 +6478,17 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d dependencies: ms "^2.1.3" -decamelize-keys@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" - integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.1.0, decamelize@^1.2.0: +decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decamelize@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" - integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== - decimal.js-light@^2.4.1: version "2.5.1" resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -decimal.js@^10.4.2, decimal.js@^10.5.0, decimal.js@^10.6.0: +decimal.js@^10.5.0, decimal.js@^10.6.0: version "10.6.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== @@ -7101,11 +6500,6 @@ decode-named-character-reference@^1.0.0: dependencies: character-entities "^2.0.0" -dedent@^1.0.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9" - integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA== - deep-diff@^0.3.5: version "0.3.8" resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" @@ -7262,11 +6656,6 @@ detect-libc@^2.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" @@ -7307,13 +6696,6 @@ diff@^5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== -diffable-html@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/diffable-html/-/diffable-html-4.1.0.tgz#e7a2d1de187c4e23a59751b4e4c17483a058c696" - integrity sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g== - dependencies: - htmlparser2 "^3.9.2" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -7374,14 +6756,6 @@ dom-helpers@^5.0.1, dom-helpers@^5.1.3: "@babel/runtime" "^7.8.7" csstype "^3.0.2" -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -7400,30 +6774,11 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domexception@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" - integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== - dependencies: - webidl-conversions "^7.0.0" - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -7438,14 +6793,6 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -7501,11 +6848,6 @@ electron-to-chromium@^1.5.263: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz#09f8973100c39fb0d003b890393cd1d58932b1c8" integrity sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg== -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - emoji-regex@^10.2.1, emoji-regex@^10.3.0: version "10.6.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d" @@ -7569,11 +6911,6 @@ ensure-type@^1.5.0: resolved "https://registry.yarnpkg.com/ensure-type/-/ensure-type-1.5.1.tgz#e65ec8d4c6ee6f35b43675d4243258cb4c8f6159" integrity sha512-Dxe+mVF4MupV6eueWiFa6hUd9OL9lIM2/LqR40k1P+dwG+G2il2UigXTU9aQlaw+Y/N0BKSaTofNw73htTbC5g== -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -7877,17 +7214,6 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionalDependencies: - source-map "~0.6.1" - eslint-config-prettier@^10.1.8: version "10.1.8" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" @@ -8208,21 +7534,6 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - execa@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" @@ -8238,27 +7549,11 @@ execa@^8.0.1: signal-exit "^4.1.0" strip-final-newline "^3.0.0" -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - expect-type@^1.2.2: version "1.3.0" resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - expect@^30.0.0: version "30.2.0" resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" @@ -8372,7 +7667,7 @@ fast-fifo@^1.3.2: resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== -fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.1, fast-glob@^3.3.3: +fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== @@ -8383,7 +7678,7 @@ fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.1, fast-glob@^3.3.3: merge2 "^1.3.0" micromatch "^4.0.8" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -8432,13 +7727,6 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" @@ -8451,13 +7739,6 @@ file-entry-cache@^11.1.2: dependencies: flat-cache "^6.1.20" -file-entry-cache@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-7.0.2.tgz#2d61bb70ba89b9548e3035b7c9173fe91deafff0" - integrity sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g== - dependencies: - flat-cache "^3.2.0" - file-entry-cache@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" @@ -8540,15 +7821,6 @@ find-up@^6.3.0: locate-path "^7.1.0" path-exists "^5.0.0" -flat-cache@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - flat-cache@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" @@ -8733,7 +8005,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -8811,11 +8083,6 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4, get-intrinsic@ hasown "^2.0.2" math-intrinsics "^1.1.0" -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" @@ -8824,11 +8091,6 @@ get-proto@^1.0.0, get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - get-stream@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" @@ -8921,7 +8183,7 @@ glob@^13.0.0, glob@^13.0.1, glob@^13.0.3: minipass "^7.1.3" path-scurry "^2.0.2" -glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: +glob@^7.0.3, glob@^7.1.3, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -8967,7 +8229,7 @@ globalthis@^1.0.4: define-properties "^1.2.1" gopd "^1.0.1" -globby@^11.0.0, globby@^11.1.0: +globby@^11.0.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -9012,7 +8274,7 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.5, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.8, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.5, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.8: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -9060,11 +8322,6 @@ handlebars@^4.7.8: optionalDependencies: uglify-js "^3.1.4" -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - has-bigints@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" @@ -9171,13 +8428,6 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -hosted-git-info@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" - integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== - dependencies: - lru-cache "^6.0.0" - hosted-git-info@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" @@ -9200,13 +8450,6 @@ htm@^3.1.1: resolved "https://registry.yarnpkg.com/htm/-/htm-3.1.1.tgz#49266582be0dc66ed2235d5ea892307cc0c24b78" integrity sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ== -html-encoding-sniffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" - integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== - dependencies: - whatwg-encoding "^2.0.0" - html-encoding-sniffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" @@ -9246,11 +8489,6 @@ html-parse-stringify@^3.0.1: dependencies: void-elements "3.1.0" -html-tags@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" - integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== - html-tags@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-5.1.0.tgz#ec7214b57b3e50e2a4cec39414454338a94291f8" @@ -9267,18 +8505,6 @@ html-webpack-plugin@^5.6.6: pretty-error "^4.0.0" tapable "^2.0.0" -htmlparser2@^3.9.2: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -9326,15 +8552,6 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" @@ -9363,7 +8580,7 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: +https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -9384,11 +8601,6 @@ human-id@^4.1.1: resolved "https://registry.yarnpkg.com/human-id/-/human-id-4.1.3.tgz#f408633c6febbef4650758f00ffca0967afb566d" integrity sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q== -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - human-signals@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" @@ -9491,7 +8703,7 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -9529,11 +8741,6 @@ import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-lazy@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" - integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== - import-local@^3.0.2: version "3.2.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" @@ -9557,11 +8764,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indent-string@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" - integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -9729,7 +8931,7 @@ is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.16.1, is-core-module@^2.5.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -9797,11 +8999,6 @@ is-fullwidth-code-point@^5.0.0: dependencies: get-east-asian-width "^1.3.1" -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - is-generator-function@^1.0.10, is-generator-function@^1.0.7: version "1.1.2" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" @@ -9902,11 +9099,6 @@ is-path-inside@^4.0.0: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db" integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - is-plain-obj@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" @@ -9961,11 +9153,6 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.4: dependencies: call-bound "^1.0.3" -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" @@ -10064,33 +9251,11 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0, istanbul-lib-coverage@^3.2.2: +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" @@ -10100,16 +9265,7 @@ istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: make-dir "^4.0.0" supports-color "^7.1.0" -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3, istanbul-reports@^3.2.0: +istanbul-reports@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== @@ -10160,86 +9316,6 @@ jest-axe@^8.0.0: jest-matcher-utils "29.2.2" lodash.merge "4.6.2" -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - jest-diff@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825" @@ -10250,7 +9326,7 @@ jest-diff@30.2.0: chalk "^4.1.2" pretty-format "30.2.0" -jest-diff@^29.2.1, jest-diff@^29.7.0: +jest-diff@^29.2.1: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== @@ -10260,87 +9336,11 @@ jest-diff@^29.2.1, jest-diff@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-jsdom-global@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom-global/-/jest-environment-jsdom-global-4.0.0.tgz#dd6434d0ae8bd88c3336bdfb1a57bd68acf95757" - integrity sha512-qEV8j61oV5XhOBUQbrld2nMYKnp/AGINUaoYTtkwJ9rjvMNRN7ZaZ/dgoPpW83oFtrSiVM1gie6ajdsKFBUlLA== - -jest-environment-jsdom@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" - integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/jsdom" "^20.0.0" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - jsdom "^20.0.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - jest-get-type@^29.2.0, jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - jest-matcher-utils@29.2.2: version "29.2.2" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz#9202f8e8d3a54733266784ce7763e9a08688269c" @@ -10361,16 +9361,6 @@ jest-matcher-utils@30.2.0: jest-diff "30.2.0" pretty-format "30.2.0" -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - jest-message-util@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.2.0.tgz#fc97bf90d11f118b31e6131e2b67fc4f39f92152" @@ -10386,21 +9376,6 @@ jest-message-util@30.2.0: slash "^3.0.0" stack-utils "^2.0.6" -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - jest-mock@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" @@ -10410,141 +9385,11 @@ jest-mock@30.2.0: "@types/node" "*" jest-util "30.2.0" -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - jest-regex-util@30.0.1: version "30.0.1" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-serializer-html@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz#0cfea8a03b9b82bc420fd2cb969bd76713a87c08" - integrity sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA== - dependencies: - diffable-html "^4.1.0" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - jest-util@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.2.0.tgz#5142adbcad6f4e53c2776c067a4db3c14f913705" @@ -10557,44 +9402,6 @@ jest-util@30.2.0: graceful-fs "^4.2.11" picomatch "^4.0.2" -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -10604,16 +9411,6 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - jest-worker@^30.0.5: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.2.0.tgz#fd5c2a36ff6058ec8f74366ec89538cc99539d26" @@ -10625,16 +9422,6 @@ jest-worker@^30.0.5: merge-stream "^2.0.0" supports-color "^8.1.1" -jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - jiti@^2.5.1: version "2.6.1" resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" @@ -10677,38 +9464,6 @@ js-yaml@^4.1.0, js-yaml@^4.1.1: dependencies: argparse "^2.0.1" -jsdom@^20.0.0: - version "20.0.3" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" - integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== - dependencies: - abab "^2.0.6" - acorn "^8.8.1" - acorn-globals "^7.0.0" - cssom "^0.5.0" - cssstyle "^2.3.0" - data-urls "^3.0.2" - decimal.js "^10.4.2" - domexception "^4.0.0" - escodegen "^2.0.0" - form-data "^4.0.0" - html-encoding-sniffer "^3.0.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.1" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.2" - parse5 "^7.1.1" - saxes "^6.0.0" - symbol-tree "^3.2.4" - tough-cookie "^4.1.2" - w3c-xmlserializer "^4.0.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - whatwg-url "^11.0.0" - ws "^8.11.0" - xml-name-validator "^4.0.0" - jsdom@^26.0.0, jsdom@^26.1.0: version "26.1.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-26.1.0.tgz#ab5f1c1cafc04bd878725490974ea5e8bf0c72b3" @@ -10867,7 +9622,7 @@ just-extend@^6.2.0: resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== -keyv@^4.5.3, keyv@^4.5.4: +keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -10881,21 +9636,11 @@ keyv@^5.6.0: dependencies: "@keyv/serialize" "^1.1.1" -kind-of@^6.0.2, kind-of@^6.0.3: +kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -known-css-properties@^0.29.0: - version "0.29.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f" - integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== - language-subtag-registry@^0.3.20: version "0.3.23" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" @@ -10921,11 +9666,6 @@ lead@^4.0.0: resolved "https://registry.yarnpkg.com/lead/-/lead-4.0.0.tgz#5317a49effb0e7ec3a0c8fb9c1b24fb716aab939" integrity sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg== -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -11149,13 +9889,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" @@ -11216,33 +9949,11 @@ make-fetch-happen@^14.0.3: promise-retry "^2.0.1" ssri "^12.0.0" -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== - -map-obj@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -mathml-tag-names@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" - integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== - mathml-tag-names@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz#295494906312f849a9236e6cd9accc902814d477" @@ -11425,24 +10136,6 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" -meow@^10.1.5: - version "10.1.5" - resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.5.tgz#be52a1d87b5f5698602b0f32875ee5940904aa7f" - integrity sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw== - dependencies: - "@types/minimist" "^1.2.2" - camelcase-keys "^7.0.0" - decamelize "^5.0.0" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.2" - read-pkg-up "^8.0.0" - redent "^4.0.0" - trim-newlines "^4.0.2" - type-fest "^1.2.2" - yargs-parser "^20.2.9" - meow@^14.0.0: version "14.1.0" resolved "https://registry.yarnpkg.com/meow/-/meow-14.1.0.tgz#3cd2d16ad534829ab12fcb5010fc2fdb89facd31" @@ -11761,7 +10454,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: +micromatch@^4.0.2, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -11803,11 +10496,6 @@ mime@2.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - mimic-fn@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" @@ -11857,15 +10545,6 @@ minimatch@^9.0.0, minimatch@^9.0.4: dependencies: brace-expansion "^2.0.2" -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - minimist@^1.1.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -12169,11 +10848,6 @@ node-gyp@^11.2.0: tinyglobby "^0.2.12" which "^5.0.0" -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - node-releases@^2.0.27: version "2.0.36" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" @@ -12203,16 +10877,6 @@ normalize-package-data@^2.5.0: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - normalize-package-data@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.2.tgz#a7bc22167fe24025412bcff0a9651eb768b03506" @@ -12266,13 +10930,6 @@ npm-pick-manifest@^9.0.0: npm-package-arg "^11.0.0" semver "^7.3.5" -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - npm-run-path@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" @@ -12287,7 +10944,7 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -nwsapi@^2.2.16, nwsapi@^2.2.2: +nwsapi@^2.2.16: version "2.2.23" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.23.tgz#59712c3a88e6de2bb0b6ccc1070397267019cf6c" integrity sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ== @@ -12403,13 +11060,6 @@ once@^1.3.0, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - onetime@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" @@ -12489,7 +11139,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -12619,7 +11269,7 @@ parse5@^6.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -parse5@^7.0.0, parse5@^7.1.1, parse5@^7.2.1: +parse5@^7.2.1: version "7.3.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== @@ -12666,7 +11316,7 @@ path-is-inside@^1.0.2: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -12740,17 +11390,12 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -12792,11 +11437,6 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== -pirates@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -13017,11 +11657,6 @@ postcss-media-minmax@^5.0.0: resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== -postcss-media-query-parser@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" - integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== - postcss-merge-longhand@^7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz#e1b126e92f583815482e8b1e82c47d2435a20421" @@ -13285,11 +11920,6 @@ postcss-replace-overflow-wrap@^4.0.0: resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== -postcss-resolve-nested-selector@^0.1.1: - version "0.1.6" - resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz#3d84dec809f34de020372c41b039956966896686" - integrity sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw== - postcss-safe-parser@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1" @@ -13300,11 +11930,6 @@ postcss-safe-parser@^7.0.1: resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz#36e4f7e608111a0ca940fd9712ce034718c40ec0" integrity sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A== -postcss-scss@^4.0.6: - version "4.0.9" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" - integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== - postcss-selector-not@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" @@ -13312,7 +11937,7 @@ postcss-selector-not@^6.0.1: dependencies: postcss-selector-parser "^6.0.10" -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: version "6.1.2" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== @@ -13348,7 +11973,7 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.2.14, postcss@^8.4.28, postcss@^8.4.40, postcss@^8.5.6: +postcss@^8.2.14, postcss@^8.4.40, postcss@^8.5.6: version "8.5.8" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.8.tgz#6230ecc8fb02e7a0f6982e53990937857e13f399" integrity sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg== @@ -13410,7 +12035,7 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.0.0, pretty-format@^29.2.1, pretty-format@^29.7.0: +pretty-format@^29.2.1, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== @@ -13452,14 +12077,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - prop-types-extra@^1.0.1, prop-types-extra@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" @@ -13490,28 +12107,16 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -psl@^1.1.33: - version "1.15.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" - integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== - dependencies: - punycode "^2.3.1" - punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== -punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - pvtsutils@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.6.tgz#ec46e34db7422b9e4fdc5490578c1883657d6001" @@ -13548,28 +12153,11 @@ querystring-es3@^0.2.1: resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -raf@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -13994,15 +12582,6 @@ read-pkg-up@^7.0.1: read-pkg "^5.2.0" type-fest "^0.8.1" -read-pkg-up@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-8.0.0.tgz#72f595b65e66110f43b052dd9af4de6b10534670" - integrity sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ== - dependencies: - find-up "^5.0.0" - read-pkg "^6.0.0" - type-fest "^1.0.1" - read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" @@ -14013,16 +12592,6 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -read-pkg@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-6.0.0.tgz#a67a7d6a1c2b0c3cd6aa2ea521f40c458a4a504c" - integrity sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^3.0.2" - parse-json "^5.2.0" - type-fest "^1.0.1" - read-yaml-file@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-yaml-file/-/read-yaml-file-1.1.0.tgz#9362bbcbdc77007cc8ea4519fe1c0b821a7ce0d8" @@ -14033,7 +12602,7 @@ read-yaml-file@^1.1.0: pify "^4.0.1" strip-bom "^3.0.0" -readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -14114,14 +12683,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9" - integrity sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag== - dependencies: - indent-string "^5.0.0" - strip-indent "^4.0.0" - redux-batched-actions@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/redux-batched-actions/-/redux-batched-actions-0.5.0.tgz#d3f0e359b2a95c7d80bab442df450bfafd57d122" @@ -14387,11 +12948,6 @@ resolve-url-loader@^5.0.0: postcss "^8.2.14" source-map "0.6.1" -resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== - resolve@^1.10.0, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.11, resolve@^1.22.8: version "1.22.11" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" @@ -14453,13 +13009,6 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - rimraf@^6.1.2: version "6.1.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.3.tgz#afbee236b3bd2be331d4e7ce4493bac1718981af" @@ -14695,12 +13244,12 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.0, semver@^6.3.1: +semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2, semver@^7.6.3, semver@^7.7.3: +semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.3, semver@^7.6.2, semver@^7.6.3, semver@^7.7.3: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== @@ -14877,11 +13426,6 @@ siginfo@^2.0.0: resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" @@ -14913,11 +13457,6 @@ sirv@^2.0.3: mrmime "^2.0.0" totalist "^3.0.0" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" @@ -15025,14 +13564,6 @@ source-map-loader@^4.0.2: iconv-lite "^0.6.3" source-map-js "^1.0.2" -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -15137,7 +13668,7 @@ stack-generator@^2.0.5: dependencies: stackframe "^1.3.4" -stack-utils@^2.0.3, stack-utils@^2.0.6: +stack-utils@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== @@ -15238,14 +13769,6 @@ string-argv@^0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -15415,16 +13938,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-final-newline@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" @@ -15442,21 +13955,11 @@ strip-indent@^4.0.0: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.1.1.tgz#aba13de189d4ad9a17f6050e76554ac27585c7af" integrity sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA== -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - style-loader@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== -style-search@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" - integrity sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg== - styled-components@^5.3.11: version "5.3.11" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.11.tgz#9fda7bf1108e39bf3f3e612fcc18170dedcd57a8" @@ -15481,31 +13984,11 @@ stylehacks@^7.0.5: browserslist "^4.28.1" postcss-selector-parser "^7.1.1" -stylelint-config-recommended@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz#c48a358cc46b629ea01f22db60b351f703e00597" - integrity sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ== - stylelint-config-recommended@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz#1d86be73565c3cd5e6babb8abcc932012a86bd76" integrity sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg== -stylelint-config-sass-guidelines@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-10.0.0.tgz#ace99689eb6769534c9b40d62e2a8562b1ddc9f2" - integrity sha512-+Rr2Dd4b72CWA4qoj1Kk+y449nP/WJsrD0nzQAWkmPPIuyVcy2GMIcfNr0Z8JJOLjRvtlkKxa49FCNXMePBikQ== - dependencies: - postcss-scss "^4.0.6" - stylelint-scss "^4.4.0" - -stylelint-config-standard@^34.0.0: - version "34.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz#309f3c48118a02aae262230c174282e40e766cf4" - integrity sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ== - dependencies: - stylelint-config-recommended "^13.0.0" - stylelint-config-standard@^40.0.0: version "40.0.0" resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-40.0.0.tgz#6bb72b94f8be434cdf2902ba26b2167d572c5414" @@ -15513,62 +13996,6 @@ stylelint-config-standard@^40.0.0: dependencies: stylelint-config-recommended "^18.0.0" -stylelint-scss@^4.4.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.7.0.tgz#f986bf8c5a4b93eae2b67d3a3562eef822657908" - integrity sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg== - dependencies: - postcss-media-query-parser "^0.2.3" - postcss-resolve-nested-selector "^0.1.1" - postcss-selector-parser "^6.0.11" - postcss-value-parser "^4.2.0" - -stylelint@^15.11.0: - version "15.11.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.11.0.tgz#3ff8466f5f5c47362bc7c8c9d382741c58bc3292" - integrity sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw== - dependencies: - "@csstools/css-parser-algorithms" "^2.3.1" - "@csstools/css-tokenizer" "^2.2.0" - "@csstools/media-query-list-parser" "^2.1.4" - "@csstools/selector-specificity" "^3.0.0" - balanced-match "^2.0.0" - colord "^2.9.3" - cosmiconfig "^8.2.0" - css-functions-list "^3.2.1" - css-tree "^2.3.1" - debug "^4.3.4" - fast-glob "^3.3.1" - fastest-levenshtein "^1.0.16" - file-entry-cache "^7.0.0" - global-modules "^2.0.0" - globby "^11.1.0" - globjoin "^0.1.4" - html-tags "^3.3.1" - ignore "^5.2.4" - import-lazy "^4.0.0" - imurmurhash "^0.1.4" - is-plain-object "^5.0.0" - known-css-properties "^0.29.0" - mathml-tag-names "^2.1.3" - meow "^10.1.5" - micromatch "^4.0.5" - normalize-path "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.28" - postcss-resolve-nested-selector "^0.1.1" - postcss-safe-parser "^6.0.0" - postcss-selector-parser "^6.0.13" - postcss-value-parser "^4.2.0" - resolve-from "^5.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - style-search "^0.1.0" - supports-hyperlinks "^3.0.0" - svg-tags "^1.0.0" - table "^6.8.1" - write-file-atomic "^5.0.1" - stylelint@^17.4.0: version "17.4.0" resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-17.4.0.tgz#f8beba358d0cb93508de4efdcd28bf06f5010e38" @@ -15653,7 +14080,7 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -15672,14 +14099,6 @@ supports-color@^9.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== -supports-hyperlinks@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz#b8e485b179681dea496a1e7abdf8985bd3145461" - integrity sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-hyperlinks@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz#b25ed8e5ef67388d1ce1e83029c07df19d36b870" @@ -15787,7 +14206,7 @@ tabbable@^6.0.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581" integrity sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg== -table@^6.8.1, table@^6.9.0: +table@^6.9.0: version "6.9.0" resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== @@ -15851,15 +14270,6 @@ terser@^5.10.0, terser@^5.31.1, terser@^5.46.0: commander "^2.20.0" source-map-support "~0.5.20" -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - text-decoder@^1.1.0: version "1.2.7" resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.7.tgz#5d073a9a74b9c0a9d28dfadcab96b604af57d8ba" @@ -15986,11 +14396,6 @@ tmp@^0.2.5: resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -16032,16 +14437,6 @@ totalist@^3.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== -tough-cookie@^4.1.2: - version "4.1.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" - integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - tough-cookie@^5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" @@ -16056,13 +14451,6 @@ tough-cookie@^6.0.0: dependencies: tldts "^7.0.5" -tr46@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" - integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== - dependencies: - punycode "^2.1.1" - tr46@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" @@ -16097,11 +14485,6 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -trim-newlines@^4.0.2: - version "4.1.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.1.1.tgz#28c88deb50ed10c7ba6dc2474421904a00139125" - integrity sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ== - trim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-1.0.1.tgz#68e78f6178ccab9687a610752f4f5e5a7022ee8c" @@ -16202,11 +14585,6 @@ type-detect@^4.0.0, type-detect@^4.0.8, type-detect@^4.1.0: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -16217,11 +14595,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" - integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== - type-fest@^3.8.0: version "3.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" @@ -16513,11 +14886,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -16565,14 +14933,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.4: version "0.11.4" resolved "https://registry.yarnpkg.com/url/-/url-0.11.4.tgz#adca77b3562d56b72746e76b330b7f27b6721f3c" @@ -16637,15 +14997,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -16843,13 +15194,6 @@ void-elements@3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -w3c-xmlserializer@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" - integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== - dependencies: - xml-name-validator "^4.0.0" - w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" @@ -16862,13 +15206,6 @@ walk-up-path@^3.0.1: resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886" integrity sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA== -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" @@ -17085,13 +15422,6 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -whatwg-encoding@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" - integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== - dependencies: - iconv-lite "0.6.3" - whatwg-encoding@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" @@ -17099,11 +15429,6 @@ whatwg-encoding@^3.1.1: dependencies: iconv-lite "0.6.3" -whatwg-mimetype@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" - integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== - whatwg-mimetype@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" @@ -17114,14 +15439,6 @@ whatwg-mimetype@^5.0.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz#d8232895dbd527ceaee74efd4162008fb8a8cf48" integrity sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw== -whatwg-url@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" - integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== - dependencies: - tr46 "^3.0.0" - webidl-conversions "^7.0.0" - whatwg-url@^14.0.0, whatwg-url@^14.1.1: version "14.2.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.2.0.tgz#4ee02d5d725155dae004f6ae95c73e7ef5d95663" @@ -17306,22 +15623,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -write-file-atomic@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" - integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - write-file-atomic@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-7.0.1.tgz#0e2a450ab5aa306bcfcd3aed61833b10cc4fb885" @@ -17334,7 +15635,7 @@ ws@^7.3.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.11.0, ws@^8.18.0: +ws@^8.18.0: version "8.19.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== @@ -17353,11 +15654,6 @@ x-default-browser@^0.5.2: optionalDependencies: default-browser-id "^2.0.0" -xml-name-validator@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" - integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== - xml-name-validator@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" @@ -17411,7 +15707,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2, yargs-parser@^20.2.9: +yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -17451,7 +15747,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.3.1, yargs@^17.7.2: +yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From dc2847318e23e18b221606be143c0e97e48eaecd Mon Sep 17 00:00:00 2001 From: smouillour Date: Tue, 24 Mar 2026 15:14:30 +0100 Subject: [PATCH 16/16] fix review --- .changeset/remove-immutable-cmf-cqrs.md | 9 +++++ .changeset/remove-immutable-cmf.md | 17 +++++++++ .changeset/remove-immutable-components.md | 11 ++++++ .changeset/remove-immutable-containers.md | 15 ++++++++ .changeset/remove-immutable-sagas.md | 9 +++++ .changeset/remove-immutable-stepper.md | 5 +++ .changeset/remove-immutable.md | 43 ----------------------- 7 files changed, 66 insertions(+), 43 deletions(-) create mode 100644 .changeset/remove-immutable-cmf-cqrs.md create mode 100644 .changeset/remove-immutable-cmf.md create mode 100644 .changeset/remove-immutable-components.md create mode 100644 .changeset/remove-immutable-containers.md create mode 100644 .changeset/remove-immutable-sagas.md create mode 100644 .changeset/remove-immutable-stepper.md delete mode 100644 .changeset/remove-immutable.md diff --git a/.changeset/remove-immutable-cmf-cqrs.md b/.changeset/remove-immutable-cmf-cqrs.md new file mode 100644 index 00000000000..3d2440db582 --- /dev/null +++ b/.changeset/remove-immutable-cmf-cqrs.md @@ -0,0 +1,9 @@ +--- +'@talend/react-cmf-cqrs': major +--- + +feat: remove immutable dependency + +## Breaking changes + +`state.ack` is now a plain object. Consumers reading ack state directly must migrate `.get(key)` → `[key]`. diff --git a/.changeset/remove-immutable-cmf.md b/.changeset/remove-immutable-cmf.md new file mode 100644 index 00000000000..4bbad8061f3 --- /dev/null +++ b/.changeset/remove-immutable-cmf.md @@ -0,0 +1,17 @@ +--- +'@talend/react-cmf': major +--- + +feat: remove immutable dependency + +## Breaking changes + +The CMF Redux store no longer uses ImmutableJS. `state.cmf.collections` and `state.cmf.components` are now plain objects. + +Migrate: +- `.get(key)` → `[key]` +- `.getIn([a, b])` → `lodash.get(state, [a, b])` +- `.toJS()` → identity (already plain JS) +- `.size` → `Object.keys(x).length` + +The `cmf.selectors.collections.*` and `cmf.selectors.components.*` APIs still work and are the recommended way to access CMF state. diff --git a/.changeset/remove-immutable-components.md b/.changeset/remove-immutable-components.md new file mode 100644 index 00000000000..4945ac0052e --- /dev/null +++ b/.changeset/remove-immutable-components.md @@ -0,0 +1,11 @@ +--- +'@talend/react-components': major +--- + +feat: remove immutable dependency + +## Breaking changes + +The `Iterable.isIterable` backward-compat guard was removed from `ActionDropdown`. Consumers passing an Immutable List as `items` will no longer work — migrate to plain arrays. + +`immutable` and `react-immutable-proptypes` removed from published `dependencies`. diff --git a/.changeset/remove-immutable-containers.md b/.changeset/remove-immutable-containers.md new file mode 100644 index 00000000000..502157df11c --- /dev/null +++ b/.changeset/remove-immutable-containers.md @@ -0,0 +1,15 @@ +--- +'@talend/react-containers': major +--- + +feat: remove immutable dependency + +## Breaking changes + +`defaultState` is now a plain object. Selectors updated accordingly. + +Migrate: +- `.get(key)` → `[key]` +- `.getIn([a, b])` → `lodash.get(state, [a, b])` +- `.toJS()` → identity (already plain JS) +- `.size` → `Object.keys(x).length` diff --git a/.changeset/remove-immutable-sagas.md b/.changeset/remove-immutable-sagas.md new file mode 100644 index 00000000000..e47dfe12a5f --- /dev/null +++ b/.changeset/remove-immutable-sagas.md @@ -0,0 +1,9 @@ +--- +'@talend/react-sagas': major +--- + +feat: remove immutable dependency + +## Breaking changes + +Exported functions `findPenders(state)` and `findPenderById(state, id)` now return plain JS values instead of Immutable structures. Any consumer calling `.get()`, `.set()`, `.toJS()`, or `.size` on the return values must migrate to plain object access. diff --git a/.changeset/remove-immutable-stepper.md b/.changeset/remove-immutable-stepper.md new file mode 100644 index 00000000000..e53c70f8abb --- /dev/null +++ b/.changeset/remove-immutable-stepper.md @@ -0,0 +1,5 @@ +--- +'@talend/react-stepper': patch +--- + +feat: remove immutable dependency diff --git a/.changeset/remove-immutable.md b/.changeset/remove-immutable.md deleted file mode 100644 index 78d6e06bf71..00000000000 --- a/.changeset/remove-immutable.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -'@talend/react-cmf': major -'@talend/react-containers': major -'@talend/react-cmf-cqrs': major -'@talend/react-components': major -'@talend/react-sagas': major -'@talend/react-stepper': patch ---- - -feat: remove immutable dependency - -## Breaking changes - -### `@talend/react-cmf` (major) - -The CMF Redux store no longer uses ImmutableJS. `state.cmf.collections` and `state.cmf.components` are now plain objects. - -Migrate: -- `.get(key)` → `[key]` -- `.getIn([a, b])` → `lodash.get(state, [a, b])` -- `.toJS()` → identity (already plain JS) -- `.size` → `Object.keys(x).length` - -The `cmf.selectors.collections.*` and `cmf.selectors.components.*` APIs still work and are the recommended way to access CMF state. - -### `@talend/react-containers` (major) - -`defaultState` is now a plain object. Selectors updated accordingly. Same migration as above. - -### `@talend/react-cmf-cqrs` (major) - -`state.ack` is now a plain object. Consumers reading ack state directly must migrate `.get(key)` → `[key]`. - -### `@talend/react-sagas` (major) - -Exported functions `findPenders(state)` and `findPenderById(state, id)` now return plain JS values instead of Immutable structures. Any consumer calling `.get()`, `.set()`, `.toJS()`, or `.size` on the return values must migrate to plain object access. - -### `@talend/react-components` (major) - -The `Iterable.isIterable` backward-compat guard was removed from `ActionDropdown`. Consumers passing an Immutable List as `items` will no longer work — migrate to plain arrays. - -`immutable` and `react-immutable-proptypes` removed from published `dependencies`. -