diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index a1c1655..9a914c6 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/@types/index.d.ts b/@types/index.d.ts index 87f626f..d5fabff 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -15,6 +15,7 @@ declare namespace EntitiesSearch { type Entities = Set; type Kind = Set; type Options = Set>; + type Value = string | number; interface QueryArguments extends Partial< diff --git a/package.json b/package.json index 55493ec..1f61589 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "yarn": "^1.22.19" }, "dependencies": { + "@types/lodash": "^4", "@wordpress/api-fetch": "~6.21.0", "@wordpress/components": "~23.1.0", "@wordpress/compose": "^6.23.0", @@ -38,6 +39,7 @@ "@wordpress/hooks": "^3.49.0", "@wordpress/i18n": "~4.24.0", "classnames": "^2.3.2", + "lodash": "^4.17.21", "react": "~18.2.0" }, "scripts": { diff --git a/sources/client/src/components/plural-select-control.tsx b/sources/client/src/components/plural-select-control.tsx index 3ae7029..3eccbf0 100644 --- a/sources/client/src/components/plural-select-control.tsx +++ b/sources/client/src/components/plural-select-control.tsx @@ -6,7 +6,7 @@ import { Set } from '../vo/set'; import { NoOptionsMessage } from './no-options-message'; export function PluralSelectControl( - props: EntitiesSearch.BaseControl & { + props: EntitiesSearch.BaseControl & { className?: string; } ): JSX.Element { @@ -17,6 +17,22 @@ export function PluralSelectControl( 'wz-select-control--plural' ); + const onChange = (event: React.ChangeEvent) => { + if (event.target.selectedOptions.length <= 0) { + props.onChange(new Set()); + } + + const selectedOptions = Array.from(event.target.selectedOptions).map( + (option) => option.value + ); + const selectedValues = props.options + .filter((option) => selectedOptions.includes(String(option.value))) + .map((option) => option.value); + + setSelected(selectedValues); + props.onChange(selectedValues); + }; + if (props.options.length() <= 0) { return ; } @@ -25,19 +41,8 @@ export function PluralSelectControl( props.onChange(event.target.value)} - > + {option.label} diff --git a/sources/client/src/utils/convert-entities-to-control-options.ts b/sources/client/src/utils/convert-entities-to-control-options.ts index 85063ac..a7e95d1 100644 --- a/sources/client/src/utils/convert-entities-to-control-options.ts +++ b/sources/client/src/utils/convert-entities-to-control-options.ts @@ -4,17 +4,18 @@ import { Set } from '../vo/set'; import { makeControlOption } from './make-control-option'; export function convertEntitiesToControlOptions< + V, EntitiesFields extends { [p: string]: any } >( entities: Set, labelKey: string, valueKey: string -): Set> { +): Set> { return entities.map((entity) => { const label = entity[labelKey]; const value = entity[valueKey]; labelKeyIsString(label); - return makeControlOption(label, String(value)); + return makeControlOption(label, value); }); } diff --git a/sources/client/src/vo/set.ts b/sources/client/src/vo/set.ts index 4788c8f..51bf256 100644 --- a/sources/client/src/vo/set.ts +++ b/sources/client/src/vo/set.ts @@ -1,3 +1,5 @@ +import { isEqual as _isEqual } from 'lodash'; + export class Set { readonly #data: ReadonlyArray; @@ -18,19 +20,19 @@ export class Set { return this; } - return new Set(this.#data.filter((item) => item !== value)); + return new Set(this.#data.filter((item) => !this.isEqual(item, value))); } public has(value: T): boolean { - return this.#data.includes(value); + return this.#data.some((current) => this.isEqual(current, value)); } public map(fn: (value: T) => R): Set { return new Set(this.#data.map(fn)); } - public toArray(): Array { - return [...this.#data]; + public toArray(): ReadonlyArray { + return Object.freeze([...this.#data]); } public forEach(fn: (value: T) => void): void { @@ -49,6 +51,10 @@ export class Set { return new Set(this.#data.filter(fn)); } + public find(fn: (value: T) => boolean): T | undefined { + return this.#data.slice(0).find(fn); + } + public first(): T | undefined { return this.#data.slice(0)[0]; } @@ -84,4 +90,8 @@ export class Set { yield value; } } + + private isEqual(a: unknown, b: unknown): boolean { + return _isEqual(a, b); + } } diff --git a/sources/server/src/Modules/E2e/resources/js/post-types-example-block/index.js b/sources/server/src/Modules/E2e/resources/js/post-types-example-block/index.js index db0091e..f92bbcb 100644 --- a/sources/server/src/Modules/E2e/resources/js/post-types-example-block/index.js +++ b/sources/server/src/Modules/E2e/resources/js/post-types-example-block/index.js @@ -11,7 +11,9 @@ document.addEventListener('DOMContentLoaded', () => { const { Set, searchEntities, + SingularSelectControl, PluralSelectControl, + RadioControl, ToggleControl, SearchControl, CompositeEntitiesByKind, diff --git a/tests/client/unit/components/__snapshots__/entities-toggle-control.test.tsx.snap b/tests/client/unit/components/__snapshots__/toggle-control.test.tsx.snap similarity index 100% rename from tests/client/unit/components/__snapshots__/entities-toggle-control.test.tsx.snap rename to tests/client/unit/components/__snapshots__/toggle-control.test.tsx.snap diff --git a/tests/client/unit/components/entities-toggle-control.test.tsx b/tests/client/unit/components/toggle-control.test.tsx similarity index 98% rename from tests/client/unit/components/entities-toggle-control.test.tsx rename to tests/client/unit/components/toggle-control.test.tsx index be8dcb7..ae39a3b 100644 --- a/tests/client/unit/components/entities-toggle-control.test.tsx +++ b/tests/client/unit/components/toggle-control.test.tsx @@ -47,7 +47,7 @@ describe('EntitiesToggleControl', () => { ); diff --git a/tests/client/unit/utils/convert-entities-to-control-options.test.ts b/tests/client/unit/utils/convert-entities-to-control-options.test.ts index 6cd48e6..2b0a720 100644 --- a/tests/client/unit/utils/convert-entities-to-control-options.test.ts +++ b/tests/client/unit/utils/convert-entities-to-control-options.test.ts @@ -30,7 +30,7 @@ describe('Convert Entities To Control Options', () => { 'id' ).map((option) => option.value); for (const entity of entities) { - expect(options.has(`${entity.id}`)).toEqual(true); + expect(options.has(entity.id)).toEqual(true); } }); diff --git a/tests/client/unit/vo/set.test.ts b/tests/client/unit/vo/set.test.ts index 4fbc1a8..55d2f9f 100644 --- a/tests/client/unit/vo/set.test.ts +++ b/tests/client/unit/vo/set.test.ts @@ -44,12 +44,35 @@ describe('Set', () => { expect(set.add(obj).add(obj).length()).toBe(1); }); - it('Should add the same object again if it is a different reference', () => { + it('Should not add the same object again if it is a different reference', () => { const obj = { a: 1 }; const obj2 = { a: 1 }; const set = new Set(); - expect(set.add(obj).add(obj2).length()).toBe(2); - }); + expect(set.add(obj).add(obj2).length()).toBe(1); + }); + + it.each([ + [[1, 2, 3, 4, 5], 3, true], + [[1, 2, 3, 4, 5], '4', false], + [['3', '4', '5'], 4, false], + [[{ a: 1 }, { b: 2 }, { c: 3 }], { b: 2 }, true], + [ + [ + { label: 'Label 1', value: 1 }, + { label: 'Label 2', value: 2 }, + { label: 'Label 3', value: 3 }, + ], + { label: 'Label 2', value: 2 }, + true, + ], + [[{ a: 1 }, { b: 2 }, { c: 3 }], 'b', false], + ])( + 'Should return true if a given value is the same in shape of an existing one', + (collection, given, expected) => { + const set = new Set(collection); + expect(set.has(given)).toBe(expected); + } + ); it('Return the first element', () => { const set = new Set(); diff --git a/yarn.lock b/yarn.lock index 1597b2b..46b0758 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3672,6 +3672,13 @@ __metadata: languageName: node linkType: hard +"@types/lodash@npm:^4": + version: 4.14.202 + resolution: "@types/lodash@npm:4.14.202" + checksum: 6064d43c8f454170841bd67c8266cc9069d9e570a72ca63f06bceb484cb4a3ee60c9c1f305c1b9e3a87826049fd41124b8ef265c4dd08b00f6766609c7fe9973 + languageName: node + linkType: hard + "@types/mime@npm:*": version: 3.0.1 resolution: "@types/mime@npm:3.0.1" @@ -16434,6 +16441,7 @@ __metadata: "@testing-library/user-event": "npm:^14.5.1" "@total-typescript/shoehorn": "npm:^0.1.0" "@trivago/prettier-plugin-sort-imports": "npm:^4.0.0" + "@types/lodash": "npm:^4" "@wordpress/api-fetch": "npm:~6.21.0" "@wordpress/components": "npm:~23.1.0" "@wordpress/compose": "npm:^6.23.0" @@ -16448,6 +16456,7 @@ __metadata: eslint-import-resolver-typescript: "npm:^3.5.5" jest: "npm:^29.4.3" jest-environment-jsdom: "npm:^29.5.0" + lodash: "npm:^4.17.21" prettier: "npm:^2.8.1" react: "npm:~18.2.0" ts-jest: "npm:^29.0.5"