Skip to content

Commit

Permalink
Added forEach implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrei15193 committed Jun 23, 2024
1 parent 858166f commit da7d4ec
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 30 deletions.
4 changes: 2 additions & 2 deletions src/collections/observableSet/IReadOnlyObservableSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ export interface IReadOnlyObservableSet<TItem> extends Iterable<TItem>, ISetLike
* @param callback The callback processing each item.
* @see [Set.forEach](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach)
*/
forEach(callback: (item: TItem, index: number, set: this) => void): void;
forEach(callback: (item: TItem, key: TItem, set: this) => void): void;
/**
* Iterates over the entire collections executing the `callback` for each.
* @template TContext The context type in which the callback is executed.
* @param callback The callback processing each item.
* @param thisArg A value to use as context when processing items.
* @see [Set.forEach](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach)
*/
forEach<TContext>(callback: (this: TContext, item: TItem, index: number, set: this) => void, thisArg: TContext): void;
forEach<TContext>(callback: (this: TContext, item: TItem, key: TItem, set: this) => void, thisArg: TContext): void;

/**
* Converts the observable set to a native JavaScript [Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set).
Expand Down
34 changes: 11 additions & 23 deletions src/collections/observableSet/ReadOnlyObservableSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,25 @@ export class ReadOnlyObservableSet<TItem> extends ViewModel implements IReadOnly
* @param callback The callback processing each item.
* @see [Set.forEach](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach)
*/
public forEach(callback: (item: TItem, index: number, set: this) => void): void;
public forEach(callback: (item: TItem, key: TItem, set: this) => void): void;
/**
* Iterates over the entire collections executing the `callback` for each.
* @template TContext The context type in which the callback is executed.
* @param callback The callback processing each item.
* @param thisArg A value to use as context when processing items.
* @see [Set.forEach](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach)
*/
public forEach<TContext>(callback: (this: TContext, item: TItem, index: number, set: this) => void, thisArg: TContext): void;
public forEach<TContext>(callback: (this: TContext, item: TItem, key: TItem, set: this) => void, thisArg: TContext): void;

public forEach<TContext = void>(callback: (this: TContext, item: TItem, index: number, set: this) => void, thisArg?: TContext): void {
throw new Error('Method not implemented.');
public forEach<TContext = void>(callback: (this: TContext, item: TItem, key: TItem, set: this) => void, thisArg?: TContext): void {
const changeTokenCopy = this._changeToken;

for (const item of this) {
callback.call(thisArg, item, item, this);

if (changeTokenCopy !== this._changeToken)
throw new Error('Set has changed while being iterated.');
}
}

/**
Expand Down Expand Up @@ -380,23 +387,4 @@ class ObservableSetIterator<TItem, TValue = TItem> implements Iterator<TValue, T
value: undefined
};
}
}

function resolveSetLike<TItem>(collection: Set<TItem> | ISetLike<TItem> | Iterable<TItem>): ISetLike<TItem> {
if (collection === null || collection === undefined)
return {
size: 0,
keys() {
return [][Symbol.iterator]();
},
has() {
return false;
}
};
else if (collection instanceof Set)
return collection;
else if (isSetLike(collection))
return collection;
else
return new Set<TItem>(collection);
}
2 changes: 1 addition & 1 deletion src/collections/observableSet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export type { ISetLike } from './ISetLike';
export type { IReadOnlyObservableSet } from './IReadOnlyObservableSet';
export type { IObservableSet } from './IObservableSet';

export { isSetLike } from "./isSetLike";
export { isSetLike } from './isSetLike';
8 changes: 4 additions & 4 deletions src/collections/observableSet/isSetLike.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ISetLike } from "./ISetLike";
import type { ISetLike } from './ISetLike';

export function isSetLike<TItem>(maybeSetLike: any): maybeSetLike is ISetLike<TItem> {
return (
typeof maybeSetLike.size === "number"
&& typeof maybeSetLike.has === "function"
&& typeof maybeSetLike.keys === "function"
typeof maybeSetLike.size === 'number'
&& typeof maybeSetLike.has === 'function'
&& typeof maybeSetLike.keys === 'function'
);
}
101 changes: 101 additions & 0 deletions src/collections/observableSet/tests/ObservableSet.forEach.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ObservableSet } from '../ObservableSet';
import { testBlankMutatingOperation } from './common';

describe('ObserableSet.forEach', (): void => {
it('iterating over an empty set does not invoke the callback', (): void => {
let setInvocationCount = 0;
let observableSetInvocationCount = 0;

testBlankMutatingOperation<number>({
initialState: [],

applyOperation: {
applySetOperation: set => set.forEach(_ => setInvocationCount++),
applyObservableSetOperation: observableSet => observableSet.forEach(_ => observableSetInvocationCount++)
},

expectedResult: undefined
});

expect(setInvocationCount).toBe(0);
expect(observableSetInvocationCount).toBe(0);
});

it('iterating over a set invokes the callback for each item', (): void => {
const setItems: number[] = [];
const observableSetItems: number[] = [];

testBlankMutatingOperation<number>({
initialState: [1, 2, 3],

applyOperation: {
applySetOperation: set => set.forEach(item => setItems.push(item)),
applyObservableSetOperation: observableSet => observableSet.forEach(item => observableSetItems.push(item))
},

expectedResult: undefined
});

expect(observableSetItems).toEqual(setItems);
});

it('calling forEach passes arguments to each parameter accordingly', (): void => {
let invocationCount = 0;
const observableSet = new ObservableSet<number>([1]);
observableSet.forEach((item, key, set) => {
invocationCount++;

expect(item).toBe(1);
expect(key).toBe(item);
expect(set).toStrictEqual(observableSet);

return true;
});

expect(invocationCount).toBe(1);
});

it('calling forEach with context passes it to the callback', (): void => {
let invocationCount = 0;
const context = {};
const observableSet = new ObservableSet<number>([1]);
observableSet.forEach(
function (item, key, set) {
invocationCount++;

expect(this).toStrictEqual(context);
expect(item).toBe(1);
expect(key).toBe(item);
expect(set).toStrictEqual(observableSet);

return true;
},
context
);

expect(invocationCount).toBe(1);
});

it('modifying the set while executing forEach throws exception', (): void => {
expect(
() => {
const observableSet = new ObservableSet<number>([1, 2, 3]);
observableSet.forEach(_ => {
observableSet.clear();
});
})
.toThrow(new Error('Set has changed while being iterated.'));
});

it('calling forEach while iterating will not break iterators', (): void => {
expect(
() => {
const observableSet = new ObservableSet<number>([1, 2, 3]);

for (const _ of observableSet)
observableSet.forEach(_ => {});
})
.not
.toThrow();
});
});

0 comments on commit da7d4ec

Please sign in to comment.