diff --git a/.changeset/immutable-v5-cmf-cqrs-major.md b/.changeset/immutable-v5-cmf-cqrs-major.md new file mode 100644 index 00000000000..5ded9be2e48 --- /dev/null +++ b/.changeset/immutable-v5-cmf-cqrs-major.md @@ -0,0 +1,13 @@ +--- +'@talend/react-cmf-cqrs': major +--- + +Upgrade Immutable.js to v5 + +## Breaking Changes + +- **Immutable.js upgraded from v3.8.2 to v5.x**: Consumers depending on Immutable v3 or v4 APIs must update accordingly. +- **Default import removed**: `import Immutable from 'immutable'` is no longer supported. Use named imports instead (e.g., `import { Map, List, fromJS, isImmutable } from 'immutable'`). +- **`react-immutable-proptypes` removed**: Custom internal validators replace `ImmutablePropTypes`. No peer-dep on `react-immutable-proptypes` anymore. + +See the [migration guide](../docs/breaking-change-immutable-v5.md) for full details and upgrade instructions. diff --git a/.changeset/immutable-v5-cmf-major.md b/.changeset/immutable-v5-cmf-major.md new file mode 100644 index 00000000000..b1523218f2e --- /dev/null +++ b/.changeset/immutable-v5-cmf-major.md @@ -0,0 +1,15 @@ +--- +'@talend/react-cmf': major +--- + +Upgrade Immutable.js to v5 + +## Breaking Changes + +- **Immutable.js upgraded from v3.8.2 to v5.x**: Consumers depending on Immutable v3 or v4 APIs must update accordingly. +- **Default import removed**: `import Immutable from 'immutable'` is no longer supported. Use named imports instead (e.g., `import { Map, List, fromJS, isImmutable } from 'immutable'`). All cmf source and expressions modules have been updated accordingly. +- **`Iterable` removed**: Replace `Immutable.Iterable` checks with `isImmutable()` from `immutable`. +- **`react-immutable-proptypes` removed**: Custom internal validators replace `ImmutablePropTypes`. No peer-dep on `react-immutable-proptypes` anymore. +- **`OrderedMap` → `Map`**: `Map` in Immutable v5 preserves insertion order, making `OrderedMap` redundant. All internal usage has been updated. + +See the [migration guide](../docs/breaking-change-immutable-v5.md) for full details and upgrade instructions. diff --git a/.changeset/immutable-v5-components-major.md b/.changeset/immutable-v5-components-major.md new file mode 100644 index 00000000000..a2ba3eaae68 --- /dev/null +++ b/.changeset/immutable-v5-components-major.md @@ -0,0 +1,14 @@ +--- +'@talend/react-components': major +--- + +Upgrade Immutable.js to v5 + +## Breaking Changes + +- **Immutable.js upgraded from v3.8.2 to v5.x**: Consumers depending on Immutable v3 or v4 APIs must update accordingly. +- **Default import removed**: `import Immutable from 'immutable'` is no longer supported. Use named imports instead (e.g., `import { Map, List, fromJS, isImmutable } from 'immutable'`). ActionDropdown and other components have been updated accordingly. +- **`Iterable` removed**: Replace `Immutable.Iterable` checks with `isImmutable()` from `immutable`. +- **`react-immutable-proptypes` removed**: Custom internal validators replace `ImmutablePropTypes`. No peer-dep on `react-immutable-proptypes` anymore. + +See the [migration guide](../docs/breaking-change-immutable-v5.md) for full details and upgrade instructions. diff --git a/.changeset/immutable-v5-containers-major.md b/.changeset/immutable-v5-containers-major.md new file mode 100644 index 00000000000..cb09b9468ca --- /dev/null +++ b/.changeset/immutable-v5-containers-major.md @@ -0,0 +1,15 @@ +--- +'@talend/react-containers': major +--- + +Upgrade Immutable.js to v5 + +## Breaking Changes + +- **Immutable.js upgraded from v3.8.2 to v5.x**: Consumers depending on Immutable v3 or v4 APIs must update accordingly. +- **Default import removed**: `import Immutable from 'immutable'` is no longer supported. Use named imports instead (e.g., `import { Map, List, fromJS, isImmutable } from 'immutable'`). +- **`Iterable` removed**: Replace `Immutable.Iterable` checks with `isImmutable()` from `immutable`. +- **`react-immutable-proptypes` removed**: Custom internal validators replace `ImmutablePropTypes`. No peer-dep on `react-immutable-proptypes` anymore. +- **`OrderedMap` → `Map`**: `Map` in Immutable v5 preserves insertion order, making `OrderedMap` redundant. All internal usage has been updated. + +See the [migration guide](../docs/breaking-change-immutable-v5.md) for full details and upgrade instructions. diff --git a/.changeset/immutable-v5-flow-designer-major.md b/.changeset/immutable-v5-flow-designer-major.md new file mode 100644 index 00000000000..dbe868d18f3 --- /dev/null +++ b/.changeset/immutable-v5-flow-designer-major.md @@ -0,0 +1,15 @@ +--- +'@talend/react-flow-designer': major +--- + +Upgrade Immutable.js to v5 + +## Breaking Changes + +- **Immutable.js upgraded from v3.8.2 to v5.x**: Consumers depending on Immutable v3 or v4 APIs must update accordingly. +- **Default import removed**: `import Immutable from 'immutable'` is no longer supported. Use named imports instead (e.g., `import { Map, List, fromJS, isImmutable } from 'immutable'`). Reducers (link, node, port) and renderers (LinksRenderer, NodesRenderer, PortsRenderer) have been updated accordingly. +- **`Iterable` removed**: Replace `Immutable.Iterable` checks with `isImmutable()` from `immutable`. +- **`react-immutable-proptypes` removed**: Custom internal validators replace `ImmutablePropTypes`. No peer-dep on `react-immutable-proptypes` anymore. +- **`OrderedMap` → `Map`**: `Map` in Immutable v5 preserves insertion order, making `OrderedMap` redundant. All internal usage has been updated. + +See the [migration guide](../docs/breaking-change-immutable-v5.md) for full details and upgrade instructions. diff --git a/.changeset/immutable-v5-sagas-major.md b/.changeset/immutable-v5-sagas-major.md new file mode 100644 index 00000000000..ab16708143c --- /dev/null +++ b/.changeset/immutable-v5-sagas-major.md @@ -0,0 +1,13 @@ +--- +'@talend/react-sagas': major +--- + +Upgrade Immutable.js to v5 + +## Breaking Changes + +- **Immutable.js upgraded from v3.8.2 to v5.x**: Consumers depending on Immutable v3 or v4 APIs must update accordingly. +- **Default import removed**: `import Immutable from 'immutable'` is no longer supported. Use named imports instead (e.g., `import { Map, List, fromJS, isImmutable } from 'immutable'`). +- **`react-immutable-proptypes` removed**: Custom internal validators replace `ImmutablePropTypes`. No peer-dep on `react-immutable-proptypes` anymore. + +See the [migration guide](../docs/breaking-change-immutable-v5.md) for full details and upgrade instructions. 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-* diff --git a/.prettierignore b/.prettierignore index 34f6040084a..482dc82ad41 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,10 @@ dist node_modules __fixtures__ tools/scripts-config-storybook-lib/.storybook-templates/main.js + +# TMP +_bmad +_bmad-output +.github/agents/bmad-* +.github/prompts/bmad_* +.github/prompts/bmad-* diff --git a/docs/breaking-change-immutable-v5.md b/docs/breaking-change-immutable-v5.md new file mode 100644 index 00000000000..c850c1d2723 --- /dev/null +++ b/docs/breaking-change-immutable-v5.md @@ -0,0 +1,160 @@ +# Breaking Changes: Immutable.js v5 Migration + +This document covers the breaking changes introduced by the Immutable.js v5 migration +across the following packages: + +- `@talend/react-cmf` +- `@talend/react-components` +- `@talend/react-containers` +- `@talend/react-flow-designer` +- `@talend/react-cmf-cqrs` +- `@talend/react-sagas` + +--- + +## 1. Immutable.js upgraded from v3.8.2 to v5.x + +The `immutable` peer dependency has been bumped to `^5.0.0`. + +Immutable v5 ships significant performance improvements and API clean-ups relative to v3/v4. +Consumers must upgrade their own `immutable` dependency and review any private API usage. + +**Action required:** Update your `package.json`: + +```json +{ + "dependencies": { + "immutable": "^5.0.0" + } +} +``` + +--- + +## 2. Default import removed + +The default export of `immutable` no longer exists in v5. Any code relying on the default +import will fail at runtime. + +**Before:** + +```js +import Immutable from 'immutable'; + +const map = Immutable.Map({ key: 'value' }); +const list = Immutable.List([1, 2, 3]); +``` + +**After:** + +```js +import { Map, List, fromJS, isImmutable } from 'immutable'; + +const map = Map({ key: 'value' }); +const list = List([1, 2, 3]); +``` + +--- + +## 3. `Iterable` replaced by `isImmutable()` + +`Immutable.Iterable` (and the `Iterable.isIterable()` helper) were removed in v5. +Use the top-level `isImmutable()` function to check whether a value is an Immutable +data structure. + +**Before:** + +```js +import Immutable from 'immutable'; + +if (Immutable.Iterable.isIterable(value)) { + // ... +} +``` + +**After:** + +```js +import { isImmutable } from 'immutable'; + +if (isImmutable(value)) { + // ... +} +``` + +--- + +## 4. `react-immutable-proptypes` removed + +The `react-immutable-proptypes` package has been removed from all affected packages. +Custom lightweight validators (provided internally) replace it. + +If your application depends on `ImmutablePropTypes` from `react-immutable-proptypes`, +you must either: + +- Remove the dependency and write plain PropTypes validators, **or** +- Keep `react-immutable-proptypes` as a direct dependency of your own package. + +**Before (in your component):** + +```js +import ImmutablePropTypes from 'react-immutable-proptypes'; + +MyComponent.propTypes = { + items: ImmutablePropTypes.list.isRequired, +}; +``` + +**After (example using plain PropTypes + isImmutable):** + +```js +import PropTypes from 'prop-types'; +import { isImmutable } from 'immutable'; + +MyComponent.propTypes = { + items: (props, propName) => { + if (!isImmutable(props[propName])) { + return new Error(`${propName} must be an Immutable structure`); + } + return null; + }, +}; +``` + +--- + +## 5. `OrderedMap` replaced by `Map` + +Immutable v5's `Map` preserves insertion order by default, making `OrderedMap` +redundant. All internal usages of `OrderedMap` have been replaced with `Map`. + +If your code uses `OrderedMap` directly from these packages' re-exports or expects +`OrderedMap` instances from props/state, switch to `Map`. + +**Before:** + +```js +import Immutable from 'immutable'; + +const ordered = Immutable.OrderedMap({ a: 1, b: 2 }); +``` + +**After:** + +```js +import { Map } from 'immutable'; + +const map = Map({ a: 1, b: 2 }); // insertion order preserved in v5 +``` + +--- + +## Migration Summary + +| Change | v3 / v4 | v5 | +| ---------------- | ----------------------------------- | ------------------------------------------ | +| Package import | `import Immutable from 'immutable'` | `import { Map, List, … } from 'immutable'` | +| Iterable check | `Immutable.Iterable.isIterable(x)` | `isImmutable(x)` | +| Ordered map | `OrderedMap({ … })` | `Map({ … })` | +| PropTypes | `react-immutable-proptypes` | Custom validators / plain PropTypes | +| Peer dep version | `^3.8.2` or `^4.0.0` | `^5.0.0` | diff --git a/packages/cmf-cqrs/package.json b/packages/cmf-cqrs/package.json index 28c5ea1ce18..d316f559706 100644 --- a/packages/cmf-cqrs/package.json +++ b/packages/cmf-cqrs/package.json @@ -43,7 +43,7 @@ "dependencies": { "@talend/react-cmf": "^12.1.0", "@talend/utils": "^3.7.0", - "immutable": "^3.8.2", + "immutable": "^5.1.5", "redux-saga": "^1.4.2" }, "devDependencies": { diff --git a/packages/cmf/__tests__/componentState.test.js b/packages/cmf/__tests__/componentState.test.js index 04609ddee19..4f438e6d133 100644 --- a/packages/cmf/__tests__/componentState.test.js +++ b/packages/cmf/__tests__/componentState.test.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import Immutable, { Map } from 'immutable'; +import { Map, fromJS } from 'immutable'; import state, { getStateAccessors, @@ -67,7 +67,7 @@ describe('state', () => { it('should getStateProps return state', () => { const storeState = { cmf: { - components: Immutable.fromJS({ + components: fromJS({ foo: { bar: { open: true, diff --git a/packages/cmf/__tests__/expressions/index.test.js b/packages/cmf/__tests__/expressions/index.test.js index f13a854920d..0800593cb6a 100644 --- a/packages/cmf/__tests__/expressions/index.test.js +++ b/packages/cmf/__tests__/expressions/index.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { Map, List } from 'immutable'; import expressions from '../../src/expressions'; import { mock } from '../../src'; @@ -13,8 +13,8 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'my title', }), }); @@ -27,7 +27,7 @@ describe('expressions', () => { const context = mock.store.context(); const state = mock.store.state(); context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); + state.cmf.collections = new Map({}); expect(expressions['cmf.collections.get']({ context }, 'article.title', 'no title')).toBe( 'no title', ); @@ -38,10 +38,10 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); context.store.getState = () => state; @@ -52,10 +52,10 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); context.store.getState = () => state; @@ -67,7 +67,7 @@ describe('expressions', () => { const context = mock.store.context(); const state = mock.store.state(); context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); + state.cmf.collections = new Map({}); expect(expressions['cmf.collections.includes']({ context }, 'article.tags', 'test')).toBe( false, ); @@ -77,10 +77,10 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); context.store.getState = () => state; @@ -91,10 +91,10 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); context.store.getState = () => state; @@ -106,7 +106,7 @@ describe('expressions', () => { const context = mock.store.context(); const state = mock.store.state(); context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); + state.cmf.collections = new Map({}); expect( expressions['cmf.collections.oneOf']({ context }, 'article.tags', ['test0', 'test1']), ).toBe(false); @@ -115,10 +115,10 @@ describe('expressions', () => { const context = mock.store.context(); const state = mock.store.state(); context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); expect(() => @@ -130,10 +130,10 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); context.store.getState = () => state; @@ -148,10 +148,10 @@ describe('expressions', () => { 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({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); context.store.getState = () => state; @@ -163,7 +163,7 @@ describe('expressions', () => { const context = mock.store.context(); const state = mock.store.state(); context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({}); + state.cmf.collections = new Map({}); expect( expressions['cmf.collections.allOf']({ context }, 'article.tags', ['test0', 'test1']), ).toBe(false); @@ -172,10 +172,10 @@ describe('expressions', () => { const context = mock.store.context(); const state = mock.store.state(); context.store.getState = () => state; - state.cmf.collections = new Immutable.Map({ - article: new Immutable.Map({ + state.cmf.collections = new Map({ + article: new Map({ title: 'title', - tags: new Immutable.List(['test', 'test2', 'test3']), + tags: new List(['test', 'test2', 'test3']), }), }); expect(() => @@ -188,9 +188,9 @@ describe('expressions', () => { 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({ + state.cmf.components = new Map({ + MyComponent: new Map({ + default: new Map({ show: true, }), }), @@ -203,7 +203,7 @@ describe('expressions', () => { 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({}); + state.cmf.components = new Map({}); context.store.getState = () => state; expect( expressions['cmf.components.get']({ context }, 'MyComponent.default.show', false), @@ -215,10 +215,10 @@ describe('expressions', () => { 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']), + state.cmf.components = new Map({ + MyComponent: new Map({ + default: new Map({ + tags: new List(['tag1', 'tag2', 'tag3']), show: true, }), }), @@ -231,7 +231,7 @@ describe('expressions', () => { 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({}); + state.cmf.components = new Map({}); context.store.getState = () => state; expect( expressions['cmf.components.includes']({ context }, 'MyComponent.default.tags', 'tag1'), diff --git a/packages/cmf/__tests__/localStorage.test.js b/packages/cmf/__tests__/localStorage.test.js index 6c31efa4beb..21c8568e8b4 100644 --- a/packages/cmf/__tests__/localStorage.test.js +++ b/packages/cmf/__tests__/localStorage.test.js @@ -1,4 +1,7 @@ -import Immutable from 'immutable'; +/** + * @jest-environment jest-environment-jsdom-global + */ +import { Map, List } from 'immutable'; import localStorageAPI from '../src/localStorage'; const PATHS = [ @@ -12,33 +15,35 @@ const state = { app: { extra: true, }, - components: new Immutable.Map({ - Foo: new Immutable.Map({ - default: new Immutable.Map({ + components: new Map({ + Foo: new Map({ + default: new Map({ foo: 'foo', }), }), }), - collections: new Immutable.Map({ - data: new Immutable.Map({}), + collections: new Map({ + data: new Map({}), }), }, }; -const serializedState = JSON.stringify(Object.assign({}, state, { - cmf: { - components: { - Foo: { - default: { - foo: 'foo', +const serializedState = JSON.stringify( + Object.assign({}, state, { + cmf: { + components: { + Foo: { + default: { + foo: 'foo', + }, }, }, + collections: { + data: {}, + }, }, - collections: { - data: {}, - }, - }, -})); + }), +); const KEY = 'test-cmf-localStorage'; describe('reduxLocalStorage', () => { @@ -60,6 +65,26 @@ describe('reduxLocalStorage', () => { expect(initialState.cmf.collections.getIn(['data']).toJS()).toEqual({}); localStorage.setItem(KEY, undefined); }); + it('should getState restore arrays as immutable Lists', () => { + const stateWithList = JSON.stringify({ + cmf: { + components: { + Foo: { + default: { + items: ['a', 'b', 'c'], + }, + }, + }, + collections: {}, + }, + }); + localStorage.setItem(KEY, stateWithList); + const initialState = localStorageAPI.getState(KEY); + const items = initialState.cmf.components.getIn(['Foo', 'default', 'items']); + expect(List.isList(items)).toBe(true); + expect(items.toJS()).toEqual(['a', 'b', 'c']); + localStorage.setItem(KEY, undefined); + }); it('should getStoreCallback return a function', () => { const callback = localStorageAPI.getStoreCallback(KEY, PATHS); expect(typeof callback).toBe('function'); diff --git a/packages/cmf/__tests__/onEvent.test.js b/packages/cmf/__tests__/onEvent.test.js index 4d7a0d3d769..bf3125617a7 100644 --- a/packages/cmf/__tests__/onEvent.test.js +++ b/packages/cmf/__tests__/onEvent.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { Map } from 'immutable'; import onEvent from '../src/onEvent'; describe('onEvent', () => { @@ -10,7 +10,7 @@ describe('onEvent', () => { instance = { props: { setState: jest.fn(), - state: new Immutable.Map({ docked: false }), + state: new Map({ docked: false }), }, }; config = {}; diff --git a/packages/cmf/__tests__/sagas/collection.test.js b/packages/cmf/__tests__/sagas/collection.test.js index 3cc85213091..6c3168cfe0f 100644 --- a/packages/cmf/__tests__/sagas/collection.test.js +++ b/packages/cmf/__tests__/sagas/collection.test.js @@ -1,18 +1,16 @@ import { delay, call, select } from 'redux-saga/effects'; -import Immutable from 'immutable'; +import { Map } 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: new Map({}), }, }; - const withCollection = withoutCollection.cmf.collections.set('foo', new Immutable.Map({})); + const withCollection = withoutCollection.cmf.collections.set('foo', new Map({})); 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..8a30fb40def 100644 --- a/packages/cmf/__tests__/selectors/toJS.test.js +++ b/packages/cmf/__tests__/selectors/toJS.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { Map } from 'immutable'; import toJS from '../../src/selectors/toJS'; describe('toJS', () => { @@ -12,7 +12,7 @@ describe('toJS', () => { it('the returned function should call toJS on the results', () => { const myselector = toJS(selector); const state = { - foo: new Immutable.Map({ bar: 'bar' }), + foo: new Map({ bar: 'bar' }), }; const result = myselector(state); expect(result).toEqual({ bar: 'bar' }); @@ -20,7 +20,7 @@ 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: new Map({ bar: 'bar' }), }; const result1 = myselector(state); const result2 = myselector(state); @@ -29,7 +29,7 @@ describe('toJS', () => { it('the returned function should return a different result if store is has been modified', () => { const myselector = toJS(selector); const state = { - foo: new Immutable.Map({ bar: 'bar' }), + foo: new Map({ bar: 'bar' }), }; const result1 = myselector(state); state.foo = state.foo.set('bar', 'baz'); diff --git a/packages/cmf/package.json b/packages/cmf/package.json index 17a8672512b..68648502517 100644 --- a/packages/cmf/package.json +++ b/packages/cmf/package.json @@ -46,13 +46,12 @@ "@talend/utils": "^3.7.0", "commander": "^6.2.1", "hoist-non-react-statics": "^3.3.2", - "immutable": "^3.8.2", + "immutable": "^5.1.5", "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..ad707f2534f 100644 --- a/packages/cmf/src/cmfConnect.jsx +++ b/packages/cmf/src/cmfConnect.jsx @@ -24,8 +24,8 @@ 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 { immutableMapPropType } from './propTypes/immutable'; import { randomUUID } from '@talend/utils'; import actions from './actions'; import actionCreator from './actionCreator'; @@ -392,8 +392,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: immutableMapPropType, + initialState: PropTypes.oneOfType([immutableMapPropType, PropTypes.object]), getComponent: PropTypes.func, setState: PropTypes.func, initState: PropTypes.func, diff --git a/packages/cmf/src/cmfConnect.md b/packages/cmf/src/cmfConnect.md index df36fd10eb6..4859b3572dd 100644 --- a/packages/cmf/src/cmfConnect.md +++ b/packages/cmf/src/cmfConnect.md @@ -35,7 +35,7 @@ This is required. ```javascript // example adapted from https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class -const DEFAULT_STATE = new Immutable.Map({ date: new Date() }); +const DEFAULT_STATE = new Map({ date: new Date() }); class Clock extends React.Component { static displayName = 'Clock'; // required @@ -78,7 +78,7 @@ export default cmfConnect({ // This will create the state in redux at state.cmf.components.getIn(['Clock', 'default']) ``` -First you should use immutable data structure, the `setState` of CMF uses `Immutable.fromJS` to convert its content. +First you should use immutable data structure, the `setState` of CMF uses `fromJS` to convert its content. The main idea behind is to remove the need to write reducer. diff --git a/packages/cmf/src/componentState.js b/packages/cmf/src/componentState.js index d63c1417f81..229690ea334 100644 --- a/packages/cmf/src/componentState.js +++ b/packages/cmf/src/componentState.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import Immutable from 'immutable'; +import { Map, fromJS } from 'immutable'; import actions from './actions'; /** @@ -69,7 +69,7 @@ export function getStateAccessors(dispatch, name, id, DEFAULT_STATE) { if (DEFAULT_STATE) { state = DEFAULT_STATE.merge(initialState); } else if (initialState) { - state = Immutable.Map.isMap(initialState) ? initialState : Immutable.fromJS(initialState); + state = Map.isMap(initialState) ? initialState : fromJS(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..d6634fdd937 100644 --- a/packages/cmf/src/expressions/allOf.js +++ b/packages/cmf/src/expressions/allOf.js @@ -1,14 +1,14 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; +import { Map, List } from 'immutable'; export default function getAllOfFunction(statePath) { return function includes({ context }, immutablePath, 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( + const arr = get(context.store.getState(), statePath, new Map()).getIn( immutablePath.split('.'), - new Immutable.List(), + new List(), ); return arr.size > 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..a876a18804f 100644 --- a/packages/cmf/src/expressions/getInState.js +++ b/packages/cmf/src/expressions/getInState.js @@ -1,9 +1,9 @@ import _get from 'lodash/get'; -import Immutable from 'immutable'; +import { Map } from 'immutable'; import curry from 'lodash/curry'; function getInState(statePath, { context }, immutablePath, defaultValue) { - return _get(context.store.getState(), statePath, new Immutable.Map()).getIn( + return _get(context.store.getState(), statePath, new Map()).getIn( immutablePath.split('.'), defaultValue, ); diff --git a/packages/cmf/src/expressions/includes.js b/packages/cmf/src/expressions/includes.js index ed94cadb2a5..e41a2a3a1c8 100644 --- a/packages/cmf/src/expressions/includes.js +++ b/packages/cmf/src/expressions/includes.js @@ -1,10 +1,10 @@ import _get from 'lodash/get'; -import Immutable from 'immutable'; +import { Map, List } 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()) + return _get(context.store.getState(), statePath, new Map()) + .getIn(immutablePath.split('.'), new List()) .includes(value); }; } diff --git a/packages/cmf/src/expressions/index.md b/packages/cmf/src/expressions/index.md index be4a5737b2f..01ab2c3275b 100644 --- a/packages/cmf/src/expressions/index.md +++ b/packages/cmf/src/expressions/index.md @@ -16,10 +16,10 @@ 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 { Map } from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; -const DEFAULT_STATE = new Immutable.Map({ +const DEFAULT_STATE = new Map({ like: false, }); @@ -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..943f1e0db35 100644 --- a/packages/cmf/src/expressions/oneOf.js +++ b/packages/cmf/src/expressions/oneOf.js @@ -1,14 +1,14 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; +import { Map, List } from 'immutable'; export default function getOneOfFunction(statePath) { return function includes({ context }, immutablePath, 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( + const arr = get(context.store.getState(), statePath, new Map()).getIn( immutablePath.split('.'), - new Immutable.List(), + new List(), ); return values.some(value => arr.includes(value)); }; diff --git a/packages/cmf/src/index.js b/packages/cmf/src/index.js index 147cd642ffa..b4278a8fd82 100644 --- a/packages/cmf/src/index.js +++ b/packages/cmf/src/index.js @@ -19,6 +19,7 @@ import ConnectedDispatcher from './Dispatcher'; import expression from './expression'; import expressions from './expressions'; import Inject from './Inject.component'; +import { immutableListPropType, immutableMapPropType } from './propTypes/immutable'; import localStorage from './localStorage'; import matchPath from './matchPath'; import middlewares from './middlewares'; @@ -63,6 +64,8 @@ export { CmfRegisteredSaga, store, useCMFContext, + immutableMapPropType, + immutableListPropType, }; /** diff --git a/packages/cmf/src/localStorage.js b/packages/cmf/src/localStorage.js index 595be0a4adc..c656e4597f9 100644 --- a/packages/cmf/src/localStorage.js +++ b/packages/cmf/src/localStorage.js @@ -1,8 +1,8 @@ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import set from 'lodash/set'; /** - * getState read localStorage and create a initilState for redux + * getState read localStorage and create a initialState for redux * @param {string} key the localStorage key where to read * @return {Object} initialState for redux */ @@ -14,10 +14,10 @@ function getState(key) { source = JSON.parse(source); if (source.cmf) { if (source.cmf.components) { - source.cmf.components = Immutable.fromJS(source.cmf.components); + source.cmf.components = fromJS(source.cmf.components); } if (source.cmf.collections) { - source.cmf.collections = Immutable.fromJS(source.cmf.collections); + source.cmf.collections = fromJS(source.cmf.collections); } } return source; diff --git a/packages/cmf/src/onEvent.js b/packages/cmf/src/onEvent.js index 9540e1c6c6b..41f495d7d4d 100644 --- a/packages/cmf/src/onEvent.js +++ b/packages/cmf/src/onEvent.js @@ -1,5 +1,5 @@ import get from 'lodash/get'; -import Immutable from 'immutable'; +import { Map } from 'immutable'; import CONSTANT from './constant'; function serializeEvent(event) { @@ -84,7 +84,7 @@ const ACTION_CREATOR = 'ACTION_CREATOR'; const DISPATCH = 'DISPATCH'; const SETSTATE = 'SETSTATE'; -const INITIAL_STATE = new Immutable.Map(); +const INITIAL_STATE = new Map(); function addOnEventSupport(handlerType, instance, props, key) { if (CONSTANT[`IS_HANDLER_${handlerType}_REGEX`].test(key)) { diff --git a/packages/cmf/src/propTypes/immutable.js b/packages/cmf/src/propTypes/immutable.js new file mode 100644 index 00000000000..d84d760f9b5 --- /dev/null +++ b/packages/cmf/src/propTypes/immutable.js @@ -0,0 +1,37 @@ +// TODO: consider moving these validators to @talend/utils to avoid duplication +// with packages/components/src/propTypes/immutable.js. +import { Map, List } from 'immutable'; + +function immutableMapPropType(props, propName, componentName) { + if (props[propName] != null && !Map.isMap(props[propName])) { + return new Error( + `Invalid prop \`${propName}\` supplied to \`${componentName}\`, expected an Immutable.Map.`, + ); + } + return null; +} + +function immutableListPropType(props, propName, componentName) { + if (props[propName] != null && !List.isList(props[propName])) { + return new Error( + `Invalid prop \`${propName}\` supplied to \`${componentName}\`, expected an Immutable.List.`, + ); + } + return null; +} + +function makeRequired(validator) { + return function isRequired(props, propName, componentName) { + if (props[propName] == null) { + return new Error( + `The prop \`${propName}\` is marked as required in \`${componentName}\`, but its value is \`${props[propName]}\`.`, + ); + } + return validator(props, propName, componentName); + }; +} + +immutableMapPropType.isRequired = makeRequired(immutableMapPropType); +immutableListPropType.isRequired = makeRequired(immutableListPropType); + +export { immutableMapPropType, immutableListPropType }; diff --git a/packages/cmf/src/propTypes/immutable.test.js b/packages/cmf/src/propTypes/immutable.test.js new file mode 100644 index 00000000000..65db5966d06 --- /dev/null +++ b/packages/cmf/src/propTypes/immutable.test.js @@ -0,0 +1,128 @@ +import { Map, List } from 'immutable'; +import { immutableMapPropType, immutableListPropType } from './immutable'; + +describe('immutableMapPropType', () => { + it('should return null when prop is an Immutable.Map', () => { + const props = { state: Map() }; + expect(immutableMapPropType(props, 'state', 'TestComponent')).toBeNull(); + }); + + it('should return null when prop is undefined (optional)', () => { + const props = {}; + expect(immutableMapPropType(props, 'state', 'TestComponent')).toBeNull(); + }); + + it('should return null when prop is null (optional)', () => { + const props = { state: null }; + expect(immutableMapPropType(props, 'state', 'TestComponent')).toBeNull(); + }); + + it('should return Error when prop is a plain object', () => { + const props = { state: {} }; + const result = immutableMapPropType(props, 'state', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + expect(result.message).toContain('state'); + expect(result.message).toContain('TestComponent'); + expect(result.message).toContain('Immutable.Map'); + }); + + it('should return Error when prop is an array', () => { + const props = { state: [] }; + const result = immutableMapPropType(props, 'state', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return Error when prop is an Immutable.List', () => { + const props = { state: List() }; + const result = immutableMapPropType(props, 'state', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); +}); + +describe('immutableMapPropType.isRequired', () => { + it('should return Error when prop is undefined', () => { + const props = {}; + const result = immutableMapPropType.isRequired(props, 'state', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return Error when prop is null', () => { + const props = { state: null }; + const result = immutableMapPropType.isRequired(props, 'state', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return null when prop is an Immutable.Map', () => { + const props = { state: Map() }; + expect(immutableMapPropType.isRequired(props, 'state', 'TestComponent')).toBeNull(); + }); + + it('should return Error when prop is not an Immutable.Map', () => { + const props = { state: {} }; + const result = immutableMapPropType.isRequired(props, 'state', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); +}); + +describe('immutableListPropType', () => { + it('should return null when prop is an Immutable.List', () => { + const props = { items: List() }; + expect(immutableListPropType(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return null when prop is undefined (optional)', () => { + const props = {}; + expect(immutableListPropType(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return null when prop is null (optional)', () => { + const props = { items: null }; + expect(immutableListPropType(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return Error when prop is a plain array', () => { + const props = { items: [] }; + const result = immutableListPropType(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + expect(result.message).toContain('items'); + expect(result.message).toContain('TestComponent'); + expect(result.message).toContain('Immutable.List'); + }); + + it('should return Error when prop is a plain object', () => { + const props = { items: {} }; + const result = immutableListPropType(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return Error when prop is an Immutable.Map', () => { + const props = { items: Map() }; + const result = immutableListPropType(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); +}); + +describe('immutableListPropType.isRequired', () => { + it('should return Error when prop is undefined', () => { + const props = {}; + const result = immutableListPropType.isRequired(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return Error when prop is null', () => { + const props = { items: null }; + const result = immutableListPropType.isRequired(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return null when prop is an Immutable.List', () => { + const props = { items: List() }; + expect(immutableListPropType.isRequired(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return Error when prop is not an Immutable.List', () => { + const props = { items: [] }; + const result = immutableListPropType.isRequired(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); +}); diff --git a/packages/components/package.json b/packages/components/package.json index 178ad4e6e17..4fef1fa6e04 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -57,7 +57,7 @@ "date-fns": "^3.6.0", "dom-helpers": "^3.4.0", "focus-outline-manager": "^1.0.2", - "immutable": "^3.8.2", + "immutable": "^5.1.5", "invariant": "^2.2.4", "lodash": "^4.17.23", "memoize-one": "^6.0.0", @@ -66,7 +66,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..2d1b1840c5b 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.component.jsx @@ -1,10 +1,10 @@ import { Component } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; +import { immutableListPropType } from '../../propTypes/immutable'; import get from 'lodash/get'; import classNames from 'classnames'; -import { Iterable } from 'immutable'; +import { isImmutable } from 'immutable'; import { DropdownButton, MenuItem } from '@talend/react-bootstrap'; import { withTranslation } from 'react-i18next'; import omit from 'lodash/omit'; @@ -96,7 +96,7 @@ function renderMutableMenuItem(item, index, getComponent) { } function getMenuItem(item, index, getComponent) { - if (Iterable.isIterable(item)) { + if (isImmutable(item)) { return renderMutableMenuItem(item.toJS(), index, getComponent); } @@ -323,7 +323,7 @@ ActionDropdown.propTypes = { ...MenuItem.propTypes, }), ), - ImmutablePropTypes.list, + immutableListPropType, ]), badge: PropTypes.shape({ className: PropTypes.string, diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx index b0e3ba26459..646a3f15d77 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.snapshot.test.jsx @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import { render, screen, within } from '@testing-library/react'; import ActionDropdown from './ActionDropdown.component'; @@ -13,7 +13,7 @@ const items = [ onClick: jest.fn(), }, ]; -const immutableItems = Immutable.fromJS(items); +const immutableItems = fromJS(items); describe('ActionDropdown', () => { it('should render a button dropdown with its menu', () => { diff --git a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx index 521ba6be240..6615ae19c97 100644 --- a/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx +++ b/packages/components/src/Actions/ActionDropdown/ActionDropdown.test.jsx @@ -1,6 +1,7 @@ /* eslint-disable react/prop-types */ /* eslint-disable react/display-name */ +import { fromJS } from 'immutable'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -124,6 +125,13 @@ describe('getMenuItem', () => { expect(screen.getByRole('menuitem')).toHaveTextContent('Toto'); expect(screen.getByRole('menuitem')).toHaveAttribute('data-feature', 'action.feature'); }); + it('should call toJS() and render correctly when item is an Immutable Map', () => { + const item = fromJS({ label: 'Immutable Item', 'data-feature': 'immutable.feature' }); + render(getMenuItem(item, 0, undefined)); + expect(screen.getByRole('menuitem')).toBeInTheDocument(); + expect(screen.getByRole('menuitem')).toHaveTextContent('Immutable Item'); + expect(screen.getByRole('menuitem')).toHaveAttribute('data-feature', 'immutable.feature'); + }); }); describe('InjectDropdownMenuItem', () => { diff --git a/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx b/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx index e2b9783bcf8..282e328565b 100644 --- a/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx +++ b/packages/components/src/Actions/ActionDropdown/Dropdown.stories.jsx @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import FilterBar from '../../FilterBar'; import Action from '../Action'; @@ -62,7 +62,7 @@ const contentAndLoadingAdditionalContent = { const withImmutable = { id: 'context-dropdown-related-items', label: 'related immutable items', - items: Immutable.fromJS([ + items: fromJS([ { id: 'context-dropdown-item-document-1', icon: 'talend-file-json-o', diff --git a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js index c1907aafce6..dd30c19feba 100644 --- a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js +++ b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.container.js @@ -1,6 +1,6 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; -import Immutable from 'immutable'; +import { List, Map } from 'immutable'; export function addPathsToCollection(index, collection, paths, jsonpath) { return collection.set(index, paths.push(jsonpath)); @@ -16,9 +16,9 @@ export function removePathsFromCollection(index, collection, paths, jsonpath) { /** * Collections expandedNodes / collapsedNodes are switched depending of the expand all value. * @param {number} index - * @param {Immutable.Map} collection + * @param {Map} collection * @param {boolean} expandAll - * @param {Immutable.List} paths + * @param {List} paths */ export function updateCollection(index, collection, expandAll, paths, { opened, jsonpath }) { if (opened) { @@ -51,8 +51,8 @@ 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 || Map(), + expandedNodes: props.expandedNodes || Map().set(0, List(['$'])), }; } @@ -88,7 +88,7 @@ export default class TreeManager extends Component { index, collection, isAllExpanded, - collection.get(index, Immutable.List()), + collection.get(index, List()), options, ), }); @@ -98,7 +98,7 @@ export default class TreeManager extends Component { index, collection, isAllExpanded, - collection.get(index, Immutable.List()), + collection.get(index, List()), options, ), }); diff --git a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx index a9d4ce332ca..e80045ebaa1 100644 --- a/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx +++ b/packages/components/src/DataViewer/Managers/TreeManager/TreeManager.test.jsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import Immutable from 'immutable'; +import { List, Map } from 'immutable'; import TreeManager, { addPathsToCollection, @@ -9,8 +9,8 @@ import TreeManager, { describe('addPathsToCollection', () => { it('should add the jsonpath to the paths collection', () => { - const myMap = Immutable.Map(); - const myList = Immutable.List(); + const myMap = Map(); + const myList = List(); const newMap = addPathsToCollection(0, myMap, myList, 'jsonpath'); expect(newMap.get(0).toJS()).toEqual(['jsonpath']); }); @@ -18,8 +18,8 @@ describe('addPathsToCollection', () => { 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 = List(['jsonpath', 'somestuff']); + const myMap = Map({ 0: myList }); const newCollection = removePathsFromCollection(0, myMap, myList, 'jsonpath'); expect(newCollection.get(0).toJS()).toEqual(['somestuff']); }); diff --git a/packages/components/src/HeaderBar/HeaderBar.stories.jsx b/packages/components/src/HeaderBar/HeaderBar.stories.jsx index a314d23c18f..7b19978d462 100644 --- a/packages/components/src/HeaderBar/HeaderBar.stories.jsx +++ b/packages/components/src/HeaderBar/HeaderBar.stories.jsx @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import assetsApi from '@talend/assets-api'; import tokens from '@talend/design-tokens'; import AppSwitcher from '../AppSwitcher'; @@ -102,7 +102,7 @@ export default meta; export const Default = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); return ; }, parameters: { info: { styles: infoStyle } }, @@ -110,7 +110,7 @@ export const Default = { export const WithFullLogo = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.logo.isFull = true; return ; }, @@ -119,7 +119,7 @@ export const WithFullLogo = { export const WithoutProducts = { render: () => { - const headerProps = Immutable.fromJS({ + const headerProps = fromJS({ ...props, products: null, }).toJS(); @@ -131,7 +131,7 @@ export const WithoutProducts = { export const WithBrandIcon = { render: () => { - const headerProps = Immutable.fromJS({ + const headerProps = fromJS({ ...props, brand: { ...props.brand, @@ -145,7 +145,7 @@ export const WithBrandIcon = { export const WithBrandIconUrl = { render: () => { - const headerProps = Immutable.fromJS({ + const headerProps = fromJS({ ...props, brand: { ...props.brand, @@ -159,7 +159,7 @@ export const WithBrandIconUrl = { export const WithEnvironmentDropdown = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.env = { id: 'header-environment', items: [ @@ -177,7 +177,7 @@ export const WithEnvironmentDropdown = { export const WithUnreadNotifications = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.notification = { hasUnread: true, }; @@ -188,7 +188,7 @@ export const WithUnreadNotifications = { export const WithReadNotifications = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.notification = { hasUnread: false, }; @@ -199,7 +199,7 @@ export const WithReadNotifications = { export const WithHelpSplitDropdown = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.help.items = [ { icon: 'talend-board', @@ -219,7 +219,7 @@ export const WithHelpSplitDropdown = { export const WithCallToAction = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.callToAction = { bsStyle: 'info', className: 'btn-inverse', @@ -234,7 +234,7 @@ export const WithCallToAction = { export const WithGenericAction = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.genericAction = { bsStyle: 'link', id: 'header-generic-action', @@ -249,7 +249,7 @@ export const WithGenericAction = { export const WithoutUserAndWithInformation = { render: () => { - const headerProps = Immutable.fromJS(props).toJS(); + const headerProps = fromJS(props).toJS(); headerProps.user = null; headerProps.information = { id: 'header-info', diff --git a/packages/components/src/propTypes/immutable.js b/packages/components/src/propTypes/immutable.js new file mode 100644 index 00000000000..b1831df30e9 --- /dev/null +++ b/packages/components/src/propTypes/immutable.js @@ -0,0 +1,28 @@ +// Local copy of immutableListPropType — components does not depend on @talend/react-cmf. +// Canonical version: packages/cmf/src/propTypes/immutable.js +// TODO: consider moving these validators to @talend/utils to avoid duplication. +import { List } from 'immutable'; + +function immutableListPropType(props, propName, componentName) { + if (props[propName] != null && !List.isList(props[propName])) { + return new Error( + `Invalid prop \`${propName}\` supplied to \`${componentName}\`, expected an Immutable.List.`, + ); + } + return null; +} + +function makeRequired(validator) { + return function isRequired(props, propName, componentName) { + if (props[propName] == null) { + return new Error( + `The prop \`${propName}\` is marked as required in \`${componentName}\`, but its value is \`${props[propName]}\`.`, + ); + } + return validator(props, propName, componentName); + }; +} + +immutableListPropType.isRequired = makeRequired(immutableListPropType); + +export { immutableListPropType }; diff --git a/packages/components/src/propTypes/immutable.test.js b/packages/components/src/propTypes/immutable.test.js new file mode 100644 index 00000000000..ffdf2efc887 --- /dev/null +++ b/packages/components/src/propTypes/immutable.test.js @@ -0,0 +1,65 @@ +import { List, Map } from 'immutable'; +import { immutableListPropType } from './immutable'; + +describe('immutableListPropType', () => { + it('should return null when prop is an Immutable.List', () => { + const props = { items: List() }; + expect(immutableListPropType(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return null when prop is undefined (optional)', () => { + const props = {}; + expect(immutableListPropType(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return null when prop is null (optional)', () => { + const props = { items: null }; + expect(immutableListPropType(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return Error when prop is a plain array', () => { + const props = { items: [] }; + const result = immutableListPropType(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + expect(result.message).toContain('items'); + expect(result.message).toContain('TestComponent'); + expect(result.message).toContain('Immutable.List'); + }); + + it('should return Error when prop is a plain object', () => { + const props = { items: {} }; + const result = immutableListPropType(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return Error when prop is an Immutable.Map', () => { + const props = { items: Map() }; + const result = immutableListPropType(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); +}); + +describe('immutableListPropType.isRequired', () => { + it('should return Error when prop is undefined', () => { + const props = {}; + const result = immutableListPropType.isRequired(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return Error when prop is null', () => { + const props = { items: null }; + const result = immutableListPropType.isRequired(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); + + it('should return null when prop is an Immutable.List', () => { + const props = { items: List() }; + expect(immutableListPropType.isRequired(props, 'items', 'TestComponent')).toBeNull(); + }); + + it('should return Error when prop is not an Immutable.List', () => { + const props = { items: [] }; + const result = immutableListPropType.isRequired(props, 'items', 'TestComponent'); + expect(result).toBeInstanceOf(Error); + }); +}); diff --git a/packages/components/src/test-setup.js b/packages/components/src/test-setup.js index 080c03fe9d7..32ae9bce96d 100644 --- a/packages/components/src/test-setup.js +++ b/packages/components/src/test-setup.js @@ -3,6 +3,46 @@ import serializer from 'jest-serializer-html'; import i18next from 'i18next'; import { initReactI18next } from 'react-i18next'; +// Node.js v22+ injects a native `localStorage` own data-property on `globalThis` +// that has no working `setItem`/`getItem` when `--localstorage-file` is not a +// valid path. In Vitest's jsdom environment `window === globalThis`, so jsdom's +// own setup cannot override this property. +// +// Replace it with a Proxy-backed in-memory store. The Proxy get trap checks +// `Storage.prototype` at call time so `StorageMock` spies (which patch +// Storage.prototype) are picked up automatically. +if (typeof Storage !== 'undefined') { + const _data = {}; + const store = { + getItem: key => (Object.prototype.hasOwnProperty.call(_data, key) ? _data[key] : null), + setItem: (key, value) => { + _data[key] = String(value); + }, + removeItem: key => { + delete _data[key]; + }, + clear: () => { + for (const k of Object.keys(_data)) delete _data[k]; + }, + get length() { + return Object.keys(_data).length; + }, + key: n => Object.keys(_data)[n] ?? null, + }; + const originals = Object.fromEntries( + ['getItem', 'setItem', 'removeItem', 'clear', 'key'].map(n => [n, Storage.prototype[n]]), + ); + globalThis.localStorage = new Proxy(store, { + get(target, prop) { + if (prop in originals && Storage.prototype[prop] !== originals[prop]) { + return Storage.prototype[prop]; + } + const v = target[prop]; + return typeof v === 'function' ? v.bind(target) : v; + }, + }); +} + void i18next.use(initReactI18next).init({ lng: 'en', fallbackLng: 'en', diff --git a/packages/components/vitest.config.ts b/packages/components/vitest.config.ts index 3fd0ac0e11e..f3d3d772100 100644 --- a/packages/components/vitest.config.ts +++ b/packages/components/vitest.config.ts @@ -28,6 +28,11 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', + environmentOptions: { + jsdom: { + url: 'http://localhost', + }, + }, setupFiles: ['src/test-setup.js'], include: ['src/**/*.test.{js,jsx,ts,tsx}'], exclude: ['lib/**', 'lib-esm/**'], diff --git a/packages/containers/package.json b/packages/containers/package.json index 17c2e549199..30f6ee46080 100644 --- a/packages/containers/package.json +++ b/packages/containers/package.json @@ -49,11 +49,10 @@ "@talend/react-forms": "^16.1.0", "@talend/utils": "^3.7.0", "classnames": "^2.5.1", - "immutable": "^3.8.2", + "immutable": "^5.1.5", "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/ActionDropdown/ActionDropdown.connect.jsx b/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx index 14e8bdd992c..645447192eb 100644 --- a/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx +++ b/packages/containers/src/ActionDropdown/ActionDropdown.connect.jsx @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import cmf, { cmfConnect } from '@talend/react-cmf'; +import cmf, { cmfConnect, immutableListPropType } 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.oneOfType([PropTypes.arrayOf(PropTypes.object), immutableListPropType]), 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..84f05a21bde 100644 --- a/packages/containers/src/ActionDropdown/ActionDropdown.stories.jsx +++ b/packages/containers/src/ActionDropdown/ActionDropdown.stories.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/prop-types */ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import { action } from 'storybook/actions'; import ActionDropdown from '.'; @@ -45,7 +45,7 @@ export function Default({ onSelect }) { displayMode: 'dropdown', label: 'my immutable items', onSelect, - items: Immutable.fromJS([ + items: fromJS([ { id: 'item1', label: 'First immutable label', diff --git a/packages/containers/src/AppLoader/AppLoader.connect.test.js b/packages/containers/src/AppLoader/AppLoader.connect.test.js index f2feea27f5a..c5259d1dae7 100644 --- a/packages/containers/src/AppLoader/AppLoader.connect.test.js +++ b/packages/containers/src/AppLoader/AppLoader.connect.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { Map } from 'immutable'; import { render, screen } from '@testing-library/react'; import { AppLoaderContainer, mapStateToProps } from './AppLoader.connect'; @@ -27,7 +27,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: Map() } }; const ownProps = {}; // when const result = mapStateToProps(state, ownProps); @@ -37,7 +37,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: Map({ test2: Map() }) } }; const ownProps = { hasCollections: ['test', 'test2'] }; // when const result = mapStateToProps(state, ownProps); @@ -49,7 +49,7 @@ describe('AppLoader container', () => { // given const state = { cmf: { - collections: Immutable.Map({ test2: Immutable.Map(), test: Immutable.Map() }), + collections: Map({ test2: Map(), test: Map() }), }, }; 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..2def53b17b2 100644 --- a/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js +++ b/packages/containers/src/ComponentForm/ComponentForm.selectors.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import { isComponentFormDirty } from './ComponentForm.selectors'; import { TCompForm } from './ComponentForm.component'; @@ -9,7 +9,7 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: Immutable.fromJS({ + components: fromJS({ [TCompForm.displayName]: { [componentName]: {}, }, @@ -26,7 +26,7 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: Immutable.fromJS({ + components: fromJS({ [TCompForm.displayName]: { [componentName]: { dirty: false }, }, @@ -43,7 +43,7 @@ describe('ComponentForm selectors', () => { // given const state = { cmf: { - components: Immutable.fromJS({ + components: fromJS({ [TCompForm.displayName]: { [componentName]: { dirty: true }, }, diff --git a/packages/containers/src/DeleteResource/DeleteResource.connect.js b/packages/containers/src/DeleteResource/DeleteResource.connect.js index 6f3970497e1..8c4e24a07cb 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.connect.js +++ b/packages/containers/src/DeleteResource/DeleteResource.connect.js @@ -1,5 +1,5 @@ import { cmfConnect } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { Map } from 'immutable'; import get from 'lodash/get'; import Container from './DeleteResource.container'; @@ -17,7 +17,7 @@ export function mapStateToProps(state, ownProps) { const collectionId = ownProps.collectionId || ownProps.resourceType; if (collectionId) { props.resource = state.cmf.collections - .get(collectionId, new Immutable.Map()) + .get(collectionId, new Map()) .find(currentResource => currentResource.get('id') === resourceId); } } diff --git a/packages/containers/src/DeleteResource/DeleteResource.test.js b/packages/containers/src/DeleteResource/DeleteResource.test.js index e57643bbf4b..bbcc53c5613 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.test.js +++ b/packages/containers/src/DeleteResource/DeleteResource.test.js @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; import cmf, { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { List, Map } from 'immutable'; import { DeleteResource } from './DeleteResource.container'; import Connected, { mapStateToProps } from './DeleteResource.connect'; @@ -10,8 +10,8 @@ const settings = {}; state.cmf = { settings, }; -state.cmf.collections = new Immutable.Map({ - foo: new Immutable.List([new Immutable.Map({ id: '123' })]), +state.cmf.collections = new Map({ + foo: new List([new Map({ id: '123' })]), }); describe('Container DeleteResource', () => { @@ -27,7 +27,7 @@ describe('Container DeleteResource', () => { const props = { uri: '/myEndpoint', resourceType: 'myResourceType', - resource: new Immutable.Map({ label: 'myLabel' }), + resource: new Map({ label: 'myLabel' }), header: 'My header title', params: { id: 'myResourceID' }, resourceTypeLabel: 'resourceLabel', diff --git a/packages/containers/src/EditableText/EditableText.container.jsx b/packages/containers/src/EditableText/EditableText.container.jsx index 7a46fb2a420..ccd69a298d7 100644 --- a/packages/containers/src/EditableText/EditableText.container.jsx +++ b/packages/containers/src/EditableText/EditableText.container.jsx @@ -1,12 +1,12 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import Component from '@talend/react-components/lib/EditableText'; -import Immutable from 'immutable'; +import { Map } 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 = new Map({ editMode: false, }); diff --git a/packages/containers/src/FilterBar/FilterBar.container.jsx b/packages/containers/src/FilterBar/FilterBar.container.jsx index 34f1d6d4e03..ba2214eed2f 100644 --- a/packages/containers/src/FilterBar/FilterBar.container.jsx +++ b/packages/containers/src/FilterBar/FilterBar.container.jsx @@ -2,11 +2,11 @@ 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 { Map } 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 = new Map({ [QUERY_ATTR]: '', docked: true, }); diff --git a/packages/containers/src/Form/Form.container.jsx b/packages/containers/src/Form/Form.container.jsx index a3d5747e35f..0f4e0bc0545 100644 --- a/packages/containers/src/Form/Form.container.jsx +++ b/packages/containers/src/Form/Form.container.jsx @@ -1,6 +1,6 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; -import Immutable from 'immutable'; +import { Map } from 'immutable'; import { cmfConnect } from '@talend/react-cmf'; import BaseForm from '@talend/react-forms'; import classnames from 'classnames'; @@ -10,7 +10,7 @@ if (process.env.FORM_MOZ) { DefaultArrayFieldTemplate = BaseForm.deprecated.templates.ArrayFieldTemplate; } -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = new Map({}); /** * Because we don't want to loose form input @@ -42,7 +42,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.getIn(['Container(Form)', formId, 'data'], new Map()); } static getDerivedStateFromProps(nextProps, prevState) { diff --git a/packages/containers/src/HomeListView/HomeListView.stories.jsx b/packages/containers/src/HomeListView/HomeListView.stories.jsx index 472c69b8f7e..a70a0e4a2da 100644 --- a/packages/containers/src/HomeListView/HomeListView.stories.jsx +++ b/packages/containers/src/HomeListView/HomeListView.stories.jsx @@ -1,6 +1,6 @@ import { Drawer } from '@talend/react-components'; import { action } from 'storybook/actions'; -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import HomeListView from '.'; @@ -128,7 +128,7 @@ const toolbar = { }, }; -const items = Immutable.fromJS([ +const items = fromJS([ { id: 1, label: 'Title with actions', diff --git a/packages/containers/src/List/List.container.jsx b/packages/containers/src/List/List.container.jsx index f0d715322ae..a2058feb011 100644 --- a/packages/containers/src/List/List.container.jsx +++ b/packages/containers/src/List/List.container.jsx @@ -1,12 +1,11 @@ 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'; import omit from 'lodash/omit'; import pick from 'lodash/pick'; -import cmf, { cmfConnect, useCMFContext } from '@talend/react-cmf'; +import cmf, { cmfConnect, useCMFContext, immutableListPropType } from '@talend/react-cmf'; import { getActionsProps } from '../actionAPI'; import Constants from './List.constant'; @@ -353,7 +352,7 @@ List.propTypes = { }), cellDictionary: PropTypes.object, displayMode: PropTypes.string, - items: ImmutablePropTypes.list.isRequired, + items: immutableListPropType.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..917966ee865 100644 --- a/packages/containers/src/List/List.stories.jsx +++ b/packages/containers/src/List/List.stories.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import api from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { Map, fromJS } from 'immutable'; import cloneDeep from 'lodash/cloneDeep'; import List from '.'; @@ -115,16 +115,16 @@ const customHeight = { table: 100, }; -const defaultListState = new Immutable.Map({ +const defaultListState = new Map({ displayMode: 'large', }); -const defaultSortedListState = new Immutable.Map({ +const defaultSortedListState = new Map({ sortOn: 'modified', sortAsc: false, }); -const items = Immutable.fromJS([ +const items = fromJS([ { id: 'id1', label: 'Title with actions', @@ -189,7 +189,7 @@ const minusThreeMin = referenceDatetime - 60 * 3 * 1000; const oneDay = 24 * 3600 * 1000; -const itemsWithTimestamp = Immutable.fromJS([ +const itemsWithTimestamp = fromJS([ { id: 'id0', label: 'Title with actions but first', @@ -259,7 +259,7 @@ export const WithSeparatorActions = () => ( export const Pagination = () => { const propsPg = cloneDeep(props); const itemsPg = items.concat( - Immutable.fromJS([ + fromJS([ { id: 'id4', label: 'Title with actions', diff --git a/packages/containers/src/Notification/Notification.sagas.test.js b/packages/containers/src/Notification/Notification.sagas.test.js index e26a0a4db47..9e35821e155 100644 --- a/packages/containers/src/Notification/Notification.sagas.test.js +++ b/packages/containers/src/Notification/Notification.sagas.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import { runSaga } from 'redux-saga'; import { onPushNotification } from './Notification.sagas'; @@ -18,7 +18,7 @@ describe('Notification sagas', () => { dispatch: a => dispatched.push(a), getState: () => ({ cmf: { - components: Immutable.fromJS({ + components: fromJS({ 'Container(Notification)': { Notification: { notifications: [], @@ -33,7 +33,7 @@ describe('Notification sagas', () => { ).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 = fromJS(dispatched).toJS(); expect(actions[0]).toEqual({ type: 'Container(Notification).setState', diff --git a/packages/containers/src/Notification/Notification.test.jsx b/packages/containers/src/Notification/Notification.test.jsx index c38ad6f2e6b..3e9adde11d3 100644 --- a/packages/containers/src/Notification/Notification.test.jsx +++ b/packages/containers/src/Notification/Notification.test.jsx @@ -3,7 +3,7 @@ /* eslint-disable react/display-name */ import { render } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable, { fromJS } from 'immutable'; +import { Map, fromJS } from 'immutable'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; import Container from './Notification.container'; @@ -89,7 +89,7 @@ describe('Notification.pushNotification', () => { 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 = new Map(); const notification = { message: 'hello world' }; const newState = pushNotification(state, notification); const notifications = newState.cmf.components.getIn([ diff --git a/packages/containers/src/Notification/pushNotification.js b/packages/containers/src/Notification/pushNotification.js index 3e8e51ca99c..b2fe3b710cc 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 { List } from 'immutable'; import { randomUUID } from '@talend/utils'; /** @@ -14,7 +14,7 @@ export default function pushNotification(state, notification) { return state; } const path = ['Container(Notification)', 'Notification', 'notifications']; - let notifs = state.cmf.components.getIn(path, new Immutable.List()); + let notifs = state.cmf.components.getIn(path, new List()); notifs = notifs.push({ id: randomUUID(), ...notification, diff --git a/packages/containers/src/PieChartButton/PieChartButton.connect.jsx b/packages/containers/src/PieChartButton/PieChartButton.connect.jsx index 2d908461d60..af372ac9728 100644 --- a/packages/containers/src/PieChartButton/PieChartButton.connect.jsx +++ b/packages/containers/src/PieChartButton/PieChartButton.connect.jsx @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; -import Immutable from 'immutable'; +import { Map } 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 = new Map({}); export function ContainerPieChartButton(props) { let overlayComponent = null; diff --git a/packages/containers/src/PieChartButton/PieChartButton.test.js b/packages/containers/src/PieChartButton/PieChartButton.test.js index f2e7d72dc0d..54a511c4b83 100644 --- a/packages/containers/src/PieChartButton/PieChartButton.test.js +++ b/packages/containers/src/PieChartButton/PieChartButton.test.js @@ -1,4 +1,4 @@ -import Immutable from 'immutable'; +import { fromJS } from 'immutable'; import { screen, render } from '@testing-library/react'; import Connected, { ContainerPieChartButton } from './PieChartButton.connect'; @@ -11,7 +11,7 @@ describe('PieChartButton connected', () => { describe('PieChartButton container', () => { it('should render', () => { - const initialState = Immutable.fromJS({ + const initialState = fromJS({ model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -25,7 +25,7 @@ describe('PieChartButton container', () => { }); it('should render not available pie chart button', () => { - const initialState = Immutable.fromJS({ + const initialState = fromJS({ model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, @@ -40,7 +40,7 @@ describe('PieChartButton container', () => { }); it('should render loading pie chart button', () => { - const initialState = Immutable.fromJS({ + const initialState = fromJS({ model: [ { percentage: 10, color: 'rio-grande' }, { percentage: 15, color: 'chestnut-rose' }, diff --git a/packages/containers/src/SelectObject/SelectObject.component.jsx b/packages/containers/src/SelectObject/SelectObject.component.jsx index acd7c66d523..b4caec666e9 100644 --- a/packages/containers/src/SelectObject/SelectObject.component.jsx +++ b/packages/containers/src/SelectObject/SelectObject.component.jsx @@ -1,4 +1,4 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; +import { immutableListPropType } from '@talend/react-cmf'; import classNames from 'classnames'; import PropTypes from 'prop-types'; @@ -80,12 +80,12 @@ SelectObject.propTypes = { list: PropTypes.object, filter: PropTypes.object, schema: PropTypes.object, - filteredData: ImmutablePropTypes.List, + filteredData: immutableListPropType, results: PropTypes.shape({ selectedId: PropTypes.string, onClick: PropTypes.func, }), - sourceData: ImmutablePropTypes.List, + sourceData: immutableListPropType, selected: PropTypes.object, }; diff --git a/packages/containers/src/SelectObject/SelectObject.component.test.js b/packages/containers/src/SelectObject/SelectObject.component.test.js index 3a7421577e8..c9007a7ba08 100644 --- a/packages/containers/src/SelectObject/SelectObject.component.test.js +++ b/packages/containers/src/SelectObject/SelectObject.component.test.js @@ -1,13 +1,13 @@ import { render } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { List, Map } 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 = new Map({ id: '1', name: 'foo' }); const props = { id: 'my-tree', schema: { @@ -21,7 +21,7 @@ describe('Component SelectObject', () => { }, }, }, - sourceData: new Immutable.List([item]), + sourceData: new List([item]), 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..bc83a6f4c5f 100644 --- a/packages/containers/src/SelectObject/SelectObject.connect.test.js +++ b/packages/containers/src/SelectObject/SelectObject.connect.test.js @@ -1,5 +1,5 @@ import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { List, Map } from 'immutable'; import Container from './SelectObject.container'; import Connected, { mapStateToProps } from './SelectObject.connect'; @@ -11,19 +11,16 @@ 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 }), + const data = new List([new Map({ label: 'foo' }), new Map({ label: 'bar' })]); + state.cmf.collections = new Map({ + width: new Map({ data }), }); - state.cmf.components = new Immutable.Map({ - 'Container(FilterBar)': new Immutable.Map({ - test: new Immutable.Map({ query: 'foo' }), + state.cmf.components = new Map({ + 'Container(FilterBar)': new Map({ + test: new Map({ query: 'foo' }), }), - 'Container(Tree)': new Immutable.Map({ - test: new Immutable.Map({ selectedId: '27' }), + 'Container(Tree)': new Map({ + test: new Map({ selectedId: '27' }), }), }); const props = mapStateToProps(state, { diff --git a/packages/containers/src/SelectObject/SelectObject.container.jsx b/packages/containers/src/SelectObject/SelectObject.container.jsx index d5d7c00e3f7..779d7db747f 100644 --- a/packages/containers/src/SelectObject/SelectObject.container.jsx +++ b/packages/containers/src/SelectObject/SelectObject.container.jsx @@ -2,12 +2,12 @@ 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 { List, Map } from 'immutable'; import Component from './SelectObject.component'; export const DISPLAY_NAME = 'Container(SelectObject)'; -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = new Map({}); function noop() {} @@ -142,7 +142,7 @@ class SelectObject extends RComponent { }; static defaultProps = { - sourceData: new Immutable.List(), + sourceData: new List(), idAttr: 'id', nameAttr: 'name', breadCrumbsRootLabel: 'root', diff --git a/packages/containers/src/SelectObject/SelectObject.container.test.jsx b/packages/containers/src/SelectObject/SelectObject.container.test.jsx index 8b032b4c6d6..78327fa1450 100644 --- a/packages/containers/src/SelectObject/SelectObject.container.test.jsx +++ b/packages/containers/src/SelectObject/SelectObject.container.test.jsx @@ -2,7 +2,7 @@ /* eslint-disable react/display-name */ import { screen, render, fireEvent } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { List, Map, fromJS } from 'immutable'; // eslint-disable-next-line @talend/import-depth import { prepareCMF } from '@talend/react-cmf/lib/mock/rtl'; @@ -29,8 +29,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 = new Map({ id: '1', name: 'foo' }); + const sourceData = new List([item]); render( await prepareCMF( , @@ -45,7 +45,7 @@ describe('Container SelectObject', () => { preview: undefined, selected: item.toJS(), selectedId: '1', - sourceData: new Immutable.List([item]), + sourceData: new List([item]), tree: { onSelect: expect.anything(), selectedId: '1', @@ -55,9 +55,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 = new Map({ id: '1', name: 'foo' }); + const item2 = new Map({ id: '2', name: 'bar' }); + const sourceData = new List([item1, item2]); render( await prepareCMF( @@ -73,7 +73,7 @@ describe('Container SelectObject', () => { query: 'f', selected: item1.toJS(), sourceData, - filteredData: expect.any(Immutable.List), + filteredData: expect.any(List), results: { idAttr: 'id', nameAttr: 'name', @@ -99,7 +99,7 @@ describe('Container SelectObject', () => { }); it('should call filter and getById', () => { const props = { - sourceData: new Immutable.List(), + sourceData: new List(), query: 'query', selectedId: 1, }; @@ -113,18 +113,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 = new Map({ id: 11 }); + const first = new Map({ id: 1, children: new List([subfirst]) }); + const second = new Map({ id: 2 }); + const items = new List([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 = new Map({ myid: 11 }); + const first = new Map({ myid: 1, children: new List([subfirst]) }); + const second = new Map({ myid: 2 }); + const items = new List([first, second]); expect(getById(items, 11, { idAttr: 'myid' })).toEqual({ myid: 11 }); expect(getById(items, 3)).toBe(); }); @@ -132,14 +132,14 @@ 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 = new Map({ id: 11, name: 'sub' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst]), + children: new List([subfirst]), }); - const second = new Immutable.Map({ id: 2, name: 'foo' }); - const items = new Immutable.List([first, second]); + const second = new Map({ id: 2, name: 'foo' }); + const items = new List([first, second]); // when const results = filter(items, 'ab'); @@ -149,14 +149,14 @@ describe('Container SelectObject', () => { }); it('does match only on leaf element', () => { // given - const subfirst = new Immutable.Map({ id: 11, name: 'sub' }); - const first = new Immutable.Map({ + const subfirst = new Map({ id: 11, name: 'sub' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst]), + children: new List([subfirst]), }); - const second = new Immutable.Map({ id: 2, name: 'foo' }); - const items = new Immutable.List([first, second]); + const second = new Map({ id: 2, name: 'foo' }); + const items = new List([first, second]); // when const results = filter(items, 'sub'); @@ -170,14 +170,14 @@ describe('Container SelectObject', () => { }); 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 = new Map({ id: 11, name: 'sub' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst]), + children: new List([subfirst]), }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + const second = new Map({ id: 2, name: 'sub' }); + const items = new List([first, second]); // when const results = filter(items, 'sub'); @@ -195,20 +195,20 @@ describe('Container SelectObject', () => { 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 = new Map({ id: 11, name: 'sub1' }); + const subsecond = new Map({ id: 12, name: 'sub2', - children: new Immutable.List([Immutable.Map()]), + children: new List([Map()]), }); - const subthird = new Immutable.Map({ id: 13, name: 'sub3' }); - const first = new Immutable.Map({ + const subthird = new Map({ id: 13, name: 'sub3' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst, subsecond, subthird]), + children: new List([subfirst, subsecond, subthird]), }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + const second = new Map({ id: 2, name: 'sub' }); + const items = new List([first, second]); // when const results = filter(items, 'sub'); @@ -229,19 +229,19 @@ describe('Container SelectObject', () => { 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 = new Map({ id: 11, name: 'sub1' }); + const subsecond = new Map({ id: 13, name: 'sub2' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst]), + children: new List([subfirst]), }); - const second = new Immutable.Map({ + const second = new Map({ id: 2, name: 'sub', - children: new Immutable.List([subsecond]), + children: new List([subsecond]), }); - const items = new Immutable.List([first, second]); + const items = new List([first, second]); // when const results = filter(items, 'sub'); @@ -259,20 +259,20 @@ describe('Container SelectObject', () => { 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 = new Map({ id: 11, name: 'sub1' }); + const subsecond = new Map({ id: 12, name: 'sub2', - children: new Immutable.List([Immutable.Map()]), + children: new List([Map()]), }); - const subthird = new Immutable.Map({ id: 13, name: 'sub3' }); - const first = new Immutable.Map({ + const subthird = new Map({ id: 13, name: 'sub3' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst, subsecond, subthird]), + children: new List([subfirst, subsecond, subthird]), }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + const second = new Map({ id: 2, name: 'sub' }); + const items = new List([first, second]); // when const results = filter(items, ''); @@ -309,7 +309,7 @@ describe('Container SelectObject', () => { }, ]; - const items = Immutable.fromJS(tree); + const items = fromJS(tree); const results = filterAll(items, 'ab'); expect(results.size).toBe(3); @@ -325,14 +325,14 @@ describe('Container SelectObject', () => { 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 = new Map({ id: 11, name: 'sub' }); + const first = new Map({ id: 1, name: 'abc', - children: new Immutable.List([subfirst]), + children: new List([subfirst]), }); - const second = new Immutable.Map({ id: 2, name: 'sub' }); - const items = new Immutable.List([first, second]); + const second = new Map({ id: 2, name: 'sub' }); + const items = new List([first, second]); // when const results = filter(items, 'sub'); diff --git a/packages/containers/src/Slider/Slider.container.jsx b/packages/containers/src/Slider/Slider.container.jsx index e2b6c7db968..f4f4bc2a37f 100644 --- a/packages/containers/src/Slider/Slider.container.jsx +++ b/packages/containers/src/Slider/Slider.container.jsx @@ -2,11 +2,11 @@ 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 { Map } from 'immutable'; import PropTypes from 'prop-types'; export const VALUE_ATTR = 'value'; -export const DEFAULT_STATE = new Immutable.Map({ +export const DEFAULT_STATE = new Map({ [VALUE_ATTR]: undefined, }); diff --git a/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx b/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx index 5b577857231..05b9bdd6ad2 100644 --- a/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx +++ b/packages/containers/src/SubHeaderBar/SubHeaderBar.container.jsx @@ -1,12 +1,12 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; import Component from '@talend/react-components/lib/SubHeaderBar'; -import Immutable from 'immutable'; +import { Map } 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 = new Map({}); class SubHeaderBar extends RComponent { static displayName = DISPLAY_NAME; diff --git a/packages/containers/src/TabBar/TabBar.connect.js b/packages/containers/src/TabBar/TabBar.connect.js index 35cfb527c7a..561cb41cd94 100644 --- a/packages/containers/src/TabBar/TabBar.connect.js +++ b/packages/containers/src/TabBar/TabBar.connect.js @@ -1,8 +1,8 @@ import { cmfConnect } from '@talend/react-cmf'; import TabBar from '@talend/react-components/lib/TabBar'; -import Immutable from 'immutable'; +import { Map } from 'immutable'; -export const DEFAULT_STATE = new Immutable.Map({}); +export const DEFAULT_STATE = new Map({}); export default cmfConnect({ componentId: ownProps => ownProps.componentId || ownProps.id, diff --git a/packages/containers/src/TreeView/TreeView.container.jsx b/packages/containers/src/TreeView/TreeView.container.jsx index 50576e8990e..cd1c3188e45 100644 --- a/packages/containers/src/TreeView/TreeView.container.jsx +++ b/packages/containers/src/TreeView/TreeView.container.jsx @@ -1,9 +1,8 @@ import { Component as RComponent } from 'react'; import PropTypes from 'prop-types'; -import { cmfConnect } from '@talend/react-cmf'; +import { cmfConnect, immutableListPropType } from '@talend/react-cmf'; import Component from '@talend/react-components/lib/TreeView'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Immutable from 'immutable'; +import { List, Map } from 'immutable'; import omit from 'lodash/omit'; const OPENED_ATTR = 'opened'; @@ -14,8 +13,8 @@ export const DEFAULT_PROPS = { nameAttr: 'name', childrenAttr: 'children', }; -export const DEFAULT_STATE = new Immutable.Map({ - [OPENED_ATTR]: new Immutable.List(), +export const DEFAULT_STATE = new Map({ + [OPENED_ATTR]: new List(), [SELECTED_ATTR]: undefined, }); @@ -103,7 +102,7 @@ class TreeView extends RComponent { static propTypes = { childrenAttr: PropTypes.string, - data: ImmutablePropTypes.list, + data: immutableListPropType, idAttr: PropTypes.string, nameAttr: PropTypes.string, onClick: PropTypes.func, diff --git a/packages/containers/src/TreeView/TreeView.test.js b/packages/containers/src/TreeView/TreeView.test.js index 009c5bf7807..876d79cdf40 100644 --- a/packages/containers/src/TreeView/TreeView.test.js +++ b/packages/containers/src/TreeView/TreeView.test.js @@ -1,6 +1,6 @@ import { screen, render, fireEvent } from '@testing-library/react'; import { mock } from '@talend/react-cmf'; -import Immutable from 'immutable'; +import { List, Map } from 'immutable'; import TreeView, { DEFAULT_STATE, DEFAULT_PROPS, @@ -16,9 +16,9 @@ describe('TreeView', () => { 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 = new List([ + new Map({ id: 1, name: 'foo', children: [{ id: 11, name: 'fofo', childre: [] }] }), + new Map({ id: 2, name: 'bar', children: [] }), ]); context.store.getState = () => state; }); @@ -161,9 +161,9 @@ describe('TreeView', () => { 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' })]), + const data = new Map({ + foo: new Map({ + bar: new List([new Map({ foo: 'bar' })]), }), }); state.cmf.collections = state.cmf.collections.set('data', data); @@ -180,8 +180,8 @@ describe('transform', () => { it('should add toggled booleans', () => { const props = { ...DEFAULT_PROPS, - state: Immutable.Map({ - opened: Immutable.List([1, 11, 111]), + state: Map({ + opened: List([1, 11, 111]), selectedId: 11, }), }; @@ -212,8 +212,8 @@ describe('transform', () => { it("should unfold selected's parents", () => { const props = { ...DEFAULT_PROPS, - state: Immutable.Map({ - opened: Immutable.List([1, 11, 111]), + state: Map({ + opened: List([1, 11, 111]), selectedId: 111, }), }; diff --git a/packages/containers/src/Typeahead/Typeahead.container.jsx b/packages/containers/src/Typeahead/Typeahead.container.jsx index a4428c53407..865c6374bf4 100644 --- a/packages/containers/src/Typeahead/Typeahead.container.jsx +++ b/packages/containers/src/Typeahead/Typeahead.container.jsx @@ -1,6 +1,6 @@ import { Component as RComponent } from 'react'; -import Immutable from 'immutable'; +import { Map } from 'immutable'; import omit from 'lodash/omit'; import PropTypes from 'prop-types'; @@ -8,7 +8,7 @@ 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 = new Map({ docked: true, searching: false, focusedSectionIndex: null, diff --git a/packages/flow-designer/package.json b/packages/flow-designer/package.json index 318abb1d9c4..47d7713cc5a 100644 --- a/packages/flow-designer/package.json +++ b/packages/flow-designer/package.json @@ -53,7 +53,7 @@ "@types/redux-thunk": "^2.1.0", "eslint": "^9.39.3", "i18next": "^23.16.8", - "immutable": "^3.8.2", + "immutable": "^5.1.5", "lodash": "^4.17.23", "prop-types": "^15.8.1", "react": "^18.3.1", @@ -68,7 +68,7 @@ "vitest": "^4.0.18" }, "peerDependencies": { - "immutable": "3", + "immutable": "^5.0.0", "lodash": "^4.17.23", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -81,8 +81,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/node.actions.test.ts b/packages/flow-designer/src/actions/node.actions.test.ts index a0b4212b746..48b7f44aa22 100644 --- a/packages/flow-designer/src/actions/node.actions.test.ts +++ b/packages/flow-designer/src/actions/node.actions.test.ts @@ -1,7 +1,7 @@ /* eslint-disable import/no-extraneous-dependencies */ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { Map, OrderedMap } from 'immutable'; +import { Map } from 'immutable'; import * as nodeActions from './node.actions'; import { FLOWDESIGNER_NODE_SET_TYPE } from '../constants/flowdesigner.constants'; @@ -41,7 +41,7 @@ describe('Check that node action creators generate proper action objects and per }), }), // eslint-disable-next-line new-cap - ports: OrderedMap(), + ports: Map(), }, }); diff --git a/packages/flow-designer/src/components/link/LinksRenderer.component.tsx b/packages/flow-designer/src/components/link/LinksRenderer.component.tsx index 62c0199aeac..9c44aa768d9 100644 --- a/packages/flow-designer/src/components/link/LinksRenderer.component.tsx +++ b/packages/flow-designer/src/components/link/LinksRenderer.component.tsx @@ -9,7 +9,7 @@ type Props = { class LinksRender extends Component { render() { - const links = this.props.links.toArray(); + const links = this.props.links.valueSeq().toArray(); return ( {links.map(link => { diff --git a/packages/flow-designer/src/components/link/LinksRenderer.test.tsx b/packages/flow-designer/src/components/link/LinksRenderer.test.tsx index cdef1674dd0..d2c1d046782 100644 --- a/packages/flow-designer/src/components/link/LinksRenderer.test.tsx +++ b/packages/flow-designer/src/components/link/LinksRenderer.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable new-cap */ import renderer from 'react-test-renderer'; -import { Map, OrderedMap } from 'immutable'; +import { Map } from 'immutable'; import LinksRenderer from './LinksRenderer.component'; import { LinkRecord, PortRecord, PositionRecord } from '../../constants/flowdesigner.model'; @@ -25,7 +25,7 @@ describe('', () => { }), }), ); - const ports = OrderedMap() + const ports = Map() .set( 'port1', new PortRecord({ diff --git a/packages/flow-designer/src/components/node/NodesRenderer.component.tsx b/packages/flow-designer/src/components/node/NodesRenderer.component.tsx index 42677a5700c..c5b29a5fcd1 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 {this.props.nodes.valueSeq().toArray().map(this.renderNode)}; } } diff --git a/packages/flow-designer/src/components/port/PortsRenderer.component.tsx b/packages/flow-designer/src/components/port/PortsRenderer.component.tsx index 9861a8aafda..8cad7537601 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 {ports.valueSeq().toArray().map(renderPort)}; } export default PortsRenderer; diff --git a/packages/flow-designer/src/constants/flowdesigner.model.ts b/packages/flow-designer/src/constants/flowdesigner.model.ts index fda0409af81..ebba45c04fe 100644 --- a/packages/flow-designer/src/constants/flowdesigner.model.ts +++ b/packages/flow-designer/src/constants/flowdesigner.model.ts @@ -83,17 +83,22 @@ const nodeRecordDefinition = { }), }; +// Note: methods below use `this: any` to work around an Immutable.js v5 breaking change. +// In v5, TypeScript infers `this` inside a Record({...}) shape as the plain object literal +// (not the instantiated Record), so methods like getIn/get/set/setIn are not recognized. +// `this: any` is a TypeScript-only fake parameter that suppresses the type check on `this`. +// All affected methods are marked "TO BE REMOVED" — this is a transitional workaround only. export class NodeRecord extends Record({ ...nodeRecordDefinition, - getPosition(): Position { + getPosition(this: any): Position { return this.getIn(['graphicalAttributes', 'position']); }, - getSize(): Size { + getSize(this: any): Size { return this.getIn(['graphicalAttributes', 'nodeSize']); }, - getNodeType(): string { + getNodeType(this: any): string { return this.getIn(['graphicalAttributes', 'nodeType']); }, }) {} @@ -101,21 +106,21 @@ export class NodeRecord extends Record({ export class NestedNodeRecord extends Record({ ...nodeRecordDefinition, components: List(), - getComponents(): Map { + getComponents(this: any): Map { return this.get('components'); }, - setComponents(components: Map) { + setComponents(this: any, components: Map) { return this.set('components', components); }, - getPosition(): Position { + getPosition(this: any): Position { return this.getIn(['graphicalAttributes', 'position']); }, - getSize(): Size { + getSize(this: any): Size { return this.getIn(['graphicalAttributes', 'nodeSize']); }, - getNodeType(): string { + getNodeType(this: any): string { return this.getIn(['graphicalAttributes', 'nodeType']); }, }) {} @@ -133,7 +138,7 @@ export const LinkRecord = Record({ }), /** methods TO BE REMOVED */ - getLinkType(): string { + getLinkType(this: any): string { return this.getIn(['graphicalAttributes', 'linkType']); }, }); @@ -152,25 +157,25 @@ export const PortRecord = Record({ }), /** methods TO BE REMOVED */ - getPosition(): Position { + getPosition(this: any): Position { return this.getIn(['graphicalAttributes', 'position']); }, - setPosition(position: Position): PortRecordType { + setPosition(this: any, position: Position): PortRecordType { return this.setIn(['graphicalAttributes', 'position'], position); }, - getPortType(): string { + getPortType(this: any): string { return this.getIn(['graphicalAttributes', 'portType']); }, - getPortDirection(): PortDirection { + getPortDirection(this: any): PortDirection { return this.getIn(['graphicalAttributes', 'properties', 'type']); }, - getPortFlowType(): string { + getPortFlowType(this: any): string { return this.getIn(['data', 'flowType']); }, - getIndex(): number { + getIndex(this: any): number { return this.getIn(['graphicalAttributes', 'properties', 'index']); }, - setIndex(index: number): PortRecordType { + setIndex(this: any, 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 83005e828a6..8fa1458fd91 100644 --- a/packages/flow-designer/src/constants/flowdesigner.proptypes.ts +++ b/packages/flow-designer/src/constants/flowdesigner.proptypes.ts @@ -1,25 +1,11 @@ import PropTypes from 'prop-types'; -import { recordOf } from 'react-immutable-proptypes'; -export const NodeType = recordOf({ - id: PropTypes.string.isRequired, - position: recordOf({ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }), -}); +// Runtime shape validation (via recordOf) was removed as part of the +// Immutable PropTypes migration. TypeScript types in +// customTypings/index.d.ts provide compile-time type safety instead. -export const PortType = recordOf({ - id: PropTypes.string.isRequired, - nodeId: PropTypes.string.isRequired, - position: recordOf({ - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - }), -}); +export const NodeType = PropTypes.object; -export const LinkType = recordOf({ - id: PropTypes.string.isRequired, - sourceId: PropTypes.string.isRequired, - targetId: PropTypes.string.isRequired, -}); +export const PortType = PropTypes.object; + +export const LinkType = PropTypes.object; diff --git a/packages/flow-designer/src/customTypings/index.d.ts b/packages/flow-designer/src/customTypings/index.d.ts index 43f1957527d..1f9f41a328a 100644 --- a/packages/flow-designer/src/customTypings/index.d.ts +++ b/packages/flow-designer/src/customTypings/index.d.ts @@ -1,4 +1,4 @@ -import { Record, Map } from 'immutable'; +import { RecordOf, Map } from 'immutable'; import { PORT_SINK, PORT_SOURCE } from '../constants/flowdesigner.constants'; /** $BASIC */ @@ -78,42 +78,42 @@ export interface LinkData { export interface Link { id: Id; - source: Id; - target: Id; + sourceId: Id; + targetId: Id; data: LinkData; graphicalAttributes: LinkGraphicalAttributes; } /** $RECORDS */ -export type PositionRecord = Record & Position; +export type PositionRecord = RecordOf; -export type SizeRecord = Record & Size; +export type SizeRecord = RecordOf; -export type PortRecord = Record & { +export type PortRecord = RecordOf & { getPosition: () => Position; getPortType: () => string; getPortDirection: () => PortDirection; getPortFlowType: () => string; getIndex: () => number; setIndex: (index: number) => PortRecord; -} & Port; +}; // TODO add record -export type NodeRecord = Record & { +export type NodeRecord = RecordOf & { getPosition: () => Position; getSize: () => Size; getNodeType: () => string; -} & Node; +}; -export type NestedNodeRecord = Record & { +export type NestedNodeRecord = RecordOf & { getPosition: () => Position; getSize: () => Size; getNodeType: () => string; -} & Node; +}; -export type LinkRecord = Record & { +export type LinkRecord = RecordOf & { getLinkType: () => string; -} & Link; +}; /** $STATE */ @@ -138,7 +138,7 @@ export type State = { children: Map>; nodeTypes: Map>; links: Map>; -} & Map & { getIn: getStateNodes | getStatePorts | getStateLinks | getStateIn | getStateOut }; +} & Map & { getIn: GetStateNodes | GetStatePorts | GetStateLinks | GetStateIn | GetStateOut }; /** $ACTIONS */ export interface PortActionAdd { 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..845852b53bf 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 @@ -1538,7 +1538,9 @@ Immutable.Map { "targetId": "id2", "data": Immutable.Map { "attr": "attr", - "type": "test", + "properties": Immutable.Map { + "type": "test", + }, }, "graphicalAttributes": Immutable.Map { "properties": Immutable.Map { @@ -1792,8 +1794,8 @@ Immutable.Map { "graphicalAttributes": Immutable.Map { "properties": Immutable.Map { "attr": "attr", + "selected": false, }, - "selected": false, }, "getLinkType": [Function], }, 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..da4d1de524a 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 @@ -18,7 +18,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, @@ -138,7 +138,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, @@ -301,10 +301,10 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { - "id2": Immutable.Record { - "id": "id2", - "nodeId": "test", + "ports": Immutable.Map { + "id3": Immutable.Record { + "id": "id3", + "nodeId": undefined, "data": Immutable.Map { "properties": Immutable.Map {}, "flowType": undefined, @@ -314,6 +314,9 @@ Immutable.Map { "x": 10, "y": 10, }, + "properties": Immutable.Map { + "index": 0, + }, }, "getPosition": [Function], "setPosition": [Function], @@ -323,9 +326,9 @@ Immutable.Map { "getIndex": [Function], "setIndex": [Function], }, - "id3": Immutable.Record { - "id": "id3", - "nodeId": undefined, + "id2": Immutable.Record { + "id": "id2", + "nodeId": "test", "data": Immutable.Map { "properties": Immutable.Map {}, "flowType": undefined, @@ -335,9 +338,6 @@ Immutable.Map { "x": 10, "y": 10, }, - "properties": Immutable.Map { - "index": 0, - }, }, "getPosition": [Function], "setPosition": [Function], @@ -368,7 +368,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, @@ -432,7 +432,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, @@ -517,7 +517,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, @@ -602,7 +602,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, @@ -687,7 +687,7 @@ Immutable.Map { "nodes": Immutable.Map { "nodeId": Immutable.Map {}, }, - "ports": Immutable.OrderedMap { + "ports": Immutable.Map { "id1": Immutable.Record { "id": "id1", "nodeId": undefined, diff --git a/packages/flow-designer/src/reducers/link.reducer.ts b/packages/flow-designer/src/reducers/link.reducer.ts index f206990e8d4..c96671b4a0d 100644 --- a/packages/flow-designer/src/reducers/link.reducer.ts +++ b/packages/flow-designer/src/reducers/link.reducer.ts @@ -48,13 +48,9 @@ export default function linkReducer(state = defaultState, action: any) { 'properties', fromJS(action.data && action.data.properties) || Map(), ), - graphicalAttributes: new LinkGraphicalAttributes( - action.graphicalAttributes, - ).set( + graphicalAttributes: new LinkGraphicalAttributes(action.graphicalAttributes).set( 'properties', - fromJS( - action.graphicalAttributes && action.graphicalAttributes.properties, - ) || Map(), + fromJS(action.graphicalAttributes && action.graphicalAttributes.properties) || Map(), ), }), ) // parcourir l'ensemble des parents et set le composant cible en tant que sucessors ' @@ -75,21 +71,11 @@ export default function linkReducer(state = defaultState, action: any) { state.getIn(['ports', action.sourceId]).nodeId, ) .setIn( - [ - 'out', - state.getIn(['ports', action.sourceId]).nodeId, - action.sourceId, - action.linkId, - ], + ['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, - ], + ['in', state.getIn(['ports', action.targetId]).nodeId, action.targetId, action.linkId], action.linkId, ); case FLOWDESIGNER_LINK_SET_TARGET: @@ -116,12 +102,7 @@ export default function linkReducer(state = defaultState, action: any) { action.linkId, ]) .setIn( - [ - 'in', - state.getIn(['ports', action.targetId]).nodeId, - action.targetId, - action.linkId, - ], + ['in', state.getIn(['ports', action.targetId]).nodeId, action.targetId, action.linkId], action.linkId, ) .deleteIn([ @@ -132,8 +113,7 @@ export default function linkReducer(state = defaultState, action: any) { .setIn( [ 'childrens', - state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]) - .nodeId, + state.getIn(['ports', state.getIn(['links', action.linkId]).sourceId]).nodeId, state.getIn(['ports', action.targetId]).nodeId, ], state.getIn(['ports', action.targetId]).nodeId, @@ -162,12 +142,7 @@ export default function linkReducer(state = defaultState, action: any) { action.linkId, ]) .setIn( - [ - 'out', - state.getIn(['ports', action.sourceId]).nodeId, - action.sourceId, - action.linkId, - ], + ['out', state.getIn(['ports', action.sourceId]).nodeId, action.sourceId, action.linkId], action.linkId, ) .deleteIn([ @@ -178,8 +153,7 @@ export default function linkReducer(state = defaultState, action: any) { .setIn( [ 'parents', - state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]) - .nodeId, + state.getIn(['ports', state.getIn(['links', action.linkId]).targetId]).nodeId, state.getIn(['ports', action.sourceId]).nodeId, ], state.getIn(['ports', action.sourceId]).nodeId, @@ -218,18 +192,10 @@ export default function linkReducer(state = defaultState, action: any) { invariant(false, `Can't set an attribute on non existing link ${action.linkId}`); } - try { - return state.mergeIn( - ['links', action.linkId, 'graphicalAttributes'], - fromJS(action.graphicalAttributes), - ); - } catch (error) { - console.error(error); - return state.mergeIn( - ['links', action.linkId, 'graphicalAttributes', 'properties'], - fromJS(action.graphicalAttributes), - ); - } + return state.mergeIn( + ['links', action.linkId, 'graphicalAttributes', 'properties'], + fromJS(action.graphicalAttributes), + ); case FLOWDESIGNER_LINK_REMOVE_GRAPHICAL_ATTRIBUTES: if (!state.getIn(['links', action.linkId])) { @@ -249,15 +215,7 @@ export default function linkReducer(state = defaultState, action: any) { invariant(false, `Can't set an attribute on non existing link ${action.linkId}`); } - try { - return state.mergeIn(['links', action.linkId, 'data'], fromJS(action.data)); - } catch (error) { - console.error(error); - return state.mergeIn( - ['links', action.linkId, 'data', 'properties'], - fromJS(action.data), - ); - } + return state.mergeIn(['links', action.linkId, 'data', 'properties'], fromJS(action.data)); case FLOWDESIGNER_LINK_REMOVE_DATA: if (!state.getIn(['links', action.linkId])) { diff --git a/packages/flow-designer/src/reducers/node.reducer.ts b/packages/flow-designer/src/reducers/node.reducer.ts index 3bf8c708ec2..d26c1947f51 100644 --- a/packages/flow-designer/src/reducers/node.reducer.ts +++ b/packages/flow-designer/src/reducers/node.reducer.ts @@ -1,4 +1,4 @@ -import Immutable, { Map, fromJS } from 'immutable'; +import { Map, fromJS } from 'immutable'; import invariant from 'invariant'; import { removePort } from '../actions/port.actions'; import portReducer from './port.reducer'; @@ -33,10 +33,7 @@ const nodeReducer = (state: State = defaultState, 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`, - ); + invariant(false, `Can not create node ${action.nodeId} since it does already exist`); } return state @@ -45,22 +42,14 @@ const nodeReducer = (state: State = defaultState, action: any) => { new NodeRecord({ id: action.nodeId, type: action.nodeType, - data: Immutable.Map(action.data).set( + data: Map(action.data).set( 'properties', fromJS(action.data && action.data.properties) || Map(), ), - graphicalAttributes: new NodeGraphicalAttributes( - fromJS(action.graphicalAttributes), - ) + 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(), - ), + .set('position', new PositionRecord(action.graphicalAttributes.position)) + .set('properties', fromJS(action.graphicalAttributes.properties) || Map()), }), ) .setIn(['out', action.nodeId], Map()) @@ -80,7 +69,7 @@ const nodeReducer = (state: State = defaultState, action: any) => { .setIn(['childrens', Node.getId(action.node)], Map()) .setIn(['parents', Node.getId(action.node)], Map()); case FLOWDESIGNER_NODE_MOVE_START: - if (!state.getIn('nodes', action.nodeId)) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } @@ -89,7 +78,7 @@ const nodeReducer = (state: State = defaultState, action: any) => { new PositionRecord(action.nodePosition), ); case FLOWDESIGNER_NODE_MOVE: - if (!state.getIn('nodes', action.nodeId)) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } @@ -98,7 +87,7 @@ const nodeReducer = (state: State = defaultState, action: any) => { new PositionRecord(action.nodePosition), ); case FLOWDESIGNER_NODE_MOVE_END: - if (!state.getIn('nodes', action.nodeId)) { + if (!state.getIn(['nodes', action.nodeId])) { invariant(false, `Can't move node ${action.nodeId} since it doesn't exist`); } @@ -107,13 +96,7 @@ const nodeReducer = (state: State = defaultState, action: any) => { ['nodes', action.nodeId, 'graphicalAttributes', 'position'], new PositionRecord(action.nodePosition), ) - .deleteIn([ - 'nodes', - action.nodeId, - 'graphicalAttributes', - 'properties', - 'startPosition', - ]); + .deleteIn(['nodes', action.nodeId, 'graphicalAttributes', 'properties', 'startPosition']); case FLOWDESIGNER_NODE_APPLY_MOVEMENT: return state.update('nodes', (nodes: NodeRecordMap) => nodes.map(node => { @@ -142,33 +125,19 @@ const nodeReducer = (state: State = defaultState, action: any) => { ); 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`, - ); + 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}`, - ); + invariant(false, `Can't set a graphical attribute on non existing node ${action.nodeId}`); } - try { - return state.mergeIn( - ['nodes', action.nodeId, 'graphicalAttributes'], - fromJS(action.graphicalAttributes), - ); - } catch (error) { - console.error(error); - return state.mergeIn( - ['nodes', action.nodeId, 'graphicalAttributes', 'properties'], - fromJS(action.graphicalAttributes), - ); - } + return state.mergeIn( + ['nodes', action.nodeId, 'graphicalAttributes', 'properties'], + fromJS(action.graphicalAttributes), + ); case FLOWDESIGNER_NODE_REMOVE_GRAPHICAL_ATTRIBUTES: if (!state.getIn(['nodes', action.nodeId])) { @@ -190,15 +159,7 @@ const nodeReducer = (state: State = defaultState, action: any) => { invariant(false, `Can't set a data on non existing node ${action.nodeId}`); } - try { - return state.mergeIn(['nodes', action.nodeId, 'data'], fromJS(action.data)); - } catch (error) { - console.error(error); - return state.mergeIn( - ['nodes', action.nodeId, 'data', 'properties'], - fromJS(action.data), - ); - } + return state.mergeIn(['nodes', action.nodeId, 'data'], fromJS(action.data)); case FLOWDESIGNER_NODE_REMOVE_DATA: if (!state.getIn(['nodes', action.nodeId])) { diff --git a/packages/flow-designer/src/reducers/port.reducer.test.ts b/packages/flow-designer/src/reducers/port.reducer.test.ts index b87af0d362f..b9dd587504a 100644 --- a/packages/flow-designer/src/reducers/port.reducer.test.ts +++ b/packages/flow-designer/src/reducers/port.reducer.test.ts @@ -1,4 +1,4 @@ -import { Map, OrderedMap } from 'immutable'; +import { Map } from 'immutable'; import { defaultState } from './flow.reducer'; import portReducer from './port.reducer'; @@ -10,7 +10,7 @@ describe('Check port reducer', () => { .set( 'ports', // eslint-disable-next-line new-cap - OrderedMap() + Map() .set( 'id1', new PortRecord({ diff --git a/packages/flow-designer/src/reducers/port.reducer.ts b/packages/flow-designer/src/reducers/port.reducer.ts index 63a7da00fb6..1ce76b00e39 100644 --- a/packages/flow-designer/src/reducers/port.reducer.ts +++ b/packages/flow-designer/src/reducers/port.reducer.ts @@ -78,17 +78,13 @@ function indexPortMap(ports: PortRecordMap): PortRecordMap { */ function setPort(state: State, port: PortRecordType) { const index: number = - port.graphicalAttributes.properties.index || - calculateNewPortIndex(state.get('ports'), port); + 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(), - ), + 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( @@ -121,10 +117,7 @@ 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}`, - ); + invariant(false, `Can't set a new port ${action.id} on non existing node ${action.nodeId}`); } return setPort(state, { @@ -151,24 +144,13 @@ export default function portReducer(state: State, action: PortAction): 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}`, - ); + invariant(false, `Can't set an graphical attribute on non existing port ${action.portId}`); } - try { - return state.mergeIn( - ['ports', action.portId, 'graphicalAttributes'], - fromJS(action.graphicalAttributes), - ); - } catch (error) { - console.error(error); - return state.mergeIn( - ['ports', action.portId, 'graphicalAttributes', 'properties'], - fromJS(action.graphicalAttributes), - ); - } + return state.mergeIn( + ['ports', action.portId, 'graphicalAttributes'], + fromJS(action.graphicalAttributes), + ); case FLOWDESIGNER_PORT_REMOVE_GRAPHICAL_ATTRIBUTES: if (!state.getIn(['ports', action.portId])) { @@ -190,15 +172,7 @@ export default function portReducer(state: State, action: PortAction): State { invariant(false, `Can't set a data on non existing port ${action.portId}`); } - try { - return state.mergeIn(['ports', action.portId, 'data'], fromJS(action.data)); - } catch (error) { - console.error(error); - return state.mergeIn( - ['ports', action.portId, 'data', 'properties'], - fromJS(action.data), - ); - } + return state.mergeIn(['ports', action.portId, 'data'], fromJS(action.data)); case FLOWDESIGNER_PORT_REMOVE_DATA: if (!state.getIn(['ports', action.portId])) { @@ -223,16 +197,8 @@ export default function portReducer(state: State, action: PortAction): 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, - ]); + .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( diff --git a/packages/flow-designer/src/selectors/nodeSelectors.test.ts b/packages/flow-designer/src/selectors/nodeSelectors.test.ts index 5afa3b1bc85..3c3493200bf 100644 --- a/packages/flow-designer/src/selectors/nodeSelectors.test.ts +++ b/packages/flow-designer/src/selectors/nodeSelectors.test.ts @@ -1,4 +1,4 @@ -import { List, Map, OrderedMap } from 'immutable'; +import { List, Map } from 'immutable'; import * as Selectors from './nodeSelectors'; import { NodeRecord, @@ -189,7 +189,7 @@ describe('Testing node selectors', () => { .set('id7', node7) .set('id8', node8), // eslint-disable-next-line new-cap - ports: OrderedMap() + ports: Map() .set('id1', port1) .set('id2', port2) .set('id3', port3) @@ -277,7 +277,7 @@ describe('Testing node selectors on nested nodes', () => { const givenState: State = Map({ nodes: Map().set('nodeIdA', nodeA).set('nodeIdB', nodeB), // eslint-disable-next-line new-cap - ports: OrderedMap(), + ports: Map(), links: Map(), parents: Map>(), childrens: Map>(), diff --git a/packages/flow-designer/tsconfig.json b/packages/flow-designer/tsconfig.json index 8eaf460e3d3..0988b9076c3 100644 --- a/packages/flow-designer/tsconfig.json +++ b/packages/flow-designer/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "allowJs": false, "incremental": true, - "module": "CommonJs" + "module": "CommonJs", + "moduleResolution": "node" } } diff --git a/packages/sagas/package.json b/packages/sagas/package.json index 42408f28b7b..e39ead26059 100644 --- a/packages/sagas/package.json +++ b/packages/sagas/package.json @@ -41,7 +41,7 @@ }, "dependencies": { "@talend/react-cmf": "^12.1.0", - "immutable": "^3.8.2", + "immutable": "^5.1.5", "redux-saga": "^1.4.2" }, "peerDependencies": { diff --git a/packages/stepper/package.json b/packages/stepper/package.json index 63a665a3948..4a2ee323e96 100644 --- a/packages/stepper/package.json +++ b/packages/stepper/package.json @@ -64,7 +64,6 @@ "@testing-library/react-hooks": "^8.0.1", "eslint": "^9.39.3", "i18next": "^23.16.8", - "immutable": "^3.8.2", "jsdom": "^26.1.0", "prettier": "^3.8.1", "prop-types": "^15.8.1", diff --git a/tools/scripts-config-cdn/umds.json b/tools/scripts-config-cdn/umds.json index e51522b25fd..457f91e2ce6 100644 --- a/tools/scripts-config-cdn/umds.json +++ b/tools/scripts-config-cdn/umds.json @@ -80,15 +80,6 @@ } } }, - "react-immutable-proptypes": { - "var": "ReactImmutableProptypes", - "versions": { - ">= 2.2.0": { - "development": "/talend-umds/ReactImmutableProptypes.min.js", - "production": "/talend-umds/ReactImmutableProptypes.min.js" - } - } - }, "react-popper": { "var": "ReactPopper", "versions": { diff --git a/yarn.lock b/yarn.lock index 51fed4ccd7e..32e2ef93353 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9476,15 +9476,10 @@ ignore@^7.0.5: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== -immutable@^3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" - integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== - -immutable@^5.0.2: - version "5.1.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.4.tgz#e3f8c1fe7b567d56cf26698f31918c241dae8c1f" - integrity sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA== +immutable@^5.0.2, immutable@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" + integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" @@ -9608,7 +9603,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== @@ -13732,13 +13727,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"