diff --git a/packages/core/src/Modifiers.ts b/packages/core/src/Modifiers.ts index b6d4df11a..2e21178a6 100644 --- a/packages/core/src/Modifiers.ts +++ b/packages/core/src/Modifiers.ts @@ -3,7 +3,7 @@ import { Constructor, isConstructor } from '../../utils/src/utils'; import { EventMixin } from '../../utils/src/EventMixin'; import { VersionableArray } from './Memory/VersionableArray'; -export class Modifiers extends EventMixin { +export class Modifiers extends EventMixin implements Iterable { private _contents: Modifier[]; constructor(...modifiers: Array>) { super(); @@ -247,6 +247,20 @@ export class Modifiers extends EventMixin { toggle(modifier: Modifier | Constructor): void { this.remove(modifier) || this.append(modifier); } + /** + * Check that all `otherModifiers` ar contained within this ones. + * + * @param otherModifiers + */ + contains(otherModifiers: Modifiers): boolean { + for (const otherModifier of otherModifiers) { + const foundModifier = this.find(m => m.isSameAs(otherModifier)); + if (!foundModifier && !otherModifier.isSameAs(undefined)) { + return false; + } + } + return true; + } /** * Return true if the modifiers in this array are the same as the modifiers * in the given array (as defined by the `isSameAs` methods of the @@ -255,17 +269,7 @@ export class Modifiers extends EventMixin { * @param otherModifiers */ areSameAs(otherModifiers: Modifiers): boolean { - const modifiersMap = new Map( - this._contents?.map(a => [a, otherModifiers.find(b => a.isSameAs(b))]) || [], - ); - const aModifiers = Array.from(modifiersMap.keys()); - const bModifiers = Array.from(modifiersMap.values()); - - const allAinB = aModifiers.every(a => a.isSameAs(modifiersMap.get(a))); - const allBinA = otherModifiers.every( - b => bModifiers.includes(b) || b.isSameAs(this.find(b)), - ); - return allAinB && allBinA; + return this.contains(otherModifiers) && otherModifiers.contains(this); } /** * Remove all modifiers. @@ -306,6 +310,20 @@ export class Modifiers extends EventMixin { map(callbackfn: (value: Modifier, index: number, array: Modifier[]) => T): T[] { return this._contents?.map(callbackfn) || []; } + /** + * Iterate through all modifiers. + */ + [Symbol.iterator](): Iterator { + let index = -1; + const data = this._contents || []; + + return { + next: (): IteratorResult => ({ + value: data[++index], + done: !(index in data), + }), + }; + } /** * @override */ diff --git a/packages/core/test/Modifiers.test.ts b/packages/core/test/Modifiers.test.ts index 55be65f24..3502c6899 100644 --- a/packages/core/test/Modifiers.test.ts +++ b/packages/core/test/Modifiers.test.ts @@ -18,6 +18,11 @@ class ExtendedModifier extends Modifier { return otherModifier instanceof ExtendedModifier && this.value === otherModifier.value; } } +class SameUndefinedModifier extends Modifier { + isSameAs(otherModifier: Modifier): boolean { + return otherModifier instanceof Modifier || typeof otherModifier === 'undefined'; + } +} describe('core', () => { describe('Modifiers', () => { describe('constructor()', () => { @@ -505,6 +510,56 @@ describe('core', () => { expect(modifiersMap[1] instanceof ExtendedModifier).to.be.true; }); }); + describe('contains()', () => { + it('should contain itself favorably', () => { + const m1 = new Modifier(); + const m2 = new Modifier(); + const modifiers1 = new Modifiers(m1, m2); + expect(modifiers1.contains(modifiers1)).to.be.true; + }); + it('should contain a modifier that has the same favorably', () => { + const m1 = new ExtendedModifier(1); + const m2 = new ExtendedModifier(2); + const modifiers1 = new Modifiers(m1, m2); + const m1bis = new ExtendedModifier(1); + const m2bis = new ExtendedModifier(2); + const modifiers2 = new Modifiers(m1bis); + const modifiers3 = new Modifiers(m2bis); + const modifiers4 = new Modifiers(m1bis, m2bis); + expect(modifiers1.contains(modifiers2)).to.be.true; + expect(modifiers1.contains(modifiers3)).to.be.true; + expect(modifiers1.contains(modifiers4)).to.be.true; + }); + it('should contain a modifier that has the same favorably even if their order is different', () => { + const m1 = new ExtendedModifier(1); + const m2 = new ExtendedModifier(2); + const modifiers1 = new Modifiers(m1, m2); + const modifiers2 = new Modifiers(m2, m1); + expect(modifiers1.contains(modifiers2)).to.be.true; + }); + it('should contain a modifier that has the same favorably even if their order and instances are different', () => { + const m1 = new ExtendedModifier(1); + const m2 = new ExtendedModifier(2); + const modifiers1 = new Modifiers(m1, m2); + const m1bis = new ExtendedModifier(1); + const m2bis = new ExtendedModifier(2); + const modifiers2 = new Modifiers(m2bis, m1bis); + expect(modifiers1.contains(modifiers2)).to.be.true; + }); + it('should match with modifiers that are the same with undefined', () => { + const m1 = new SameUndefinedModifier(); + const modifiers1 = new Modifiers(); + const modifiers2 = new Modifiers(m1); + expect(modifiers1.contains(modifiers2)).to.be.true; + }); + it('should not contain the other modifiers', () => { + const m1 = new ExtendedModifier(1); + const modifiers1 = new Modifiers(m1); + const m2 = new ExtendedModifier(0); + const modifiers2 = new Modifiers(m2); + expect(modifiers1.contains(modifiers2)).to.be.false; + }); + }); describe('areSameAs()', () => { it('should know that an instance of Modifiers is the same as itself', () => { const m1 = new Modifier();