Skip to content

Commit

Permalink
[IMP] reactivity: add missing support for forEach method
Browse files Browse the repository at this point in the history
  • Loading branch information
sdegueldre authored and ged-odoo committed Mar 11, 2022
1 parent 53ab54b commit 0625b58
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 0 deletions.
24 changes: 24 additions & 0 deletions src/reactivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,28 @@ function makeIteratorObserver(
}
};
}
/**
* Creates a forEach function that will delegate to forEach on the underlying
* collection while observing key changes, and keys as they're iterated over,
* and making the passed keys/values reactive.
*
* @param target @see reactive
* @param callback @see reactive
*/
function makeForEachObserver(target: any, callback: Callback) {
return function forEach(forEachCb: (val: any, key: any, target: any) => void, thisArg: any) {
observeTargetKey(target, KEYCHANGES, callback);
target.forEach(function (val: any, key: any, targetObj: any) {
observeTargetKey(target, key, callback);
forEachCb.call(
thisArg,
possiblyReactive(val, callback),
possiblyReactive(key, callback),
possiblyReactive(targetObj, callback)
);
}, thisArg);
};
}
/**
* Creates a function that will delegate to an underlying method, and check if
* that method has modified the presence or value of a key, and notify the
Expand Down Expand Up @@ -370,6 +392,7 @@ const rawTypeToFuncHandlers = {
values: makeIteratorObserver("values", target, callback),
entries: makeIteratorObserver("entries", target, callback),
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
forEach: makeForEachObserver(target, callback),
clear: makeClearNotifier(target),
get size() {
observeTargetKey(target, KEYCHANGES, callback);
Expand All @@ -385,6 +408,7 @@ const rawTypeToFuncHandlers = {
values: makeIteratorObserver("values", target, callback),
entries: makeIteratorObserver("entries", target, callback),
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
forEach: makeForEachObserver(target, callback),
clear: makeClearNotifier(target),
get size() {
observeTargetKey(target, KEYCHANGES, callback);
Expand Down
57 changes: 57 additions & 0 deletions tests/reactivity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,33 @@ describe("Collections", () => {
reactiveObj.a = 1; // setting same value again shouldn't notify
expect(observer).toHaveBeenCalledTimes(1);
});

test("iterating with forEach returns reactives", async () => {
const keyObj = { a: 2 };
const thisArg = {};
const observer = jest.fn();
const state = reactive(new Set([keyObj]), observer);
let reactiveKeyObj: any, reactiveValObj: any, thisObj: any, mapObj: any;
state.forEach(function (this: any, val, key, map) {
[reactiveValObj, reactiveKeyObj, mapObj, thisObj] = [val, key, map, this];
}, thisArg);
expect(reactiveKeyObj).not.toBe(keyObj);
expect(reactiveValObj).not.toBe(keyObj);
expect(mapObj).toBe(state); // third argument should be the reactive
expect(thisObj).toBe(thisArg); // thisArg should not be made reactive
expect(toRaw(reactiveKeyObj as any)).toBe(keyObj);
expect(toRaw(reactiveValObj as any)).toBe(keyObj);
expect(reactiveKeyObj).toBe(reactiveValObj); // reactiveKeyObj and reactiveValObj should be the same object
reactiveKeyObj!.a = 0;
reactiveValObj!.a = 0;
expect(observer).toHaveBeenCalledTimes(0);
reactiveKeyObj!.a; // observe key "a" in key sub-reactive;
reactiveKeyObj!.a = 1;
expect(observer).toHaveBeenCalledTimes(1);
reactiveKeyObj!.a = 1; // setting same value again shouldn't notify
reactiveValObj!.a = 1;
expect(observer).toHaveBeenCalledTimes(1);
});
});

describe("WeakSet", () => {
Expand Down Expand Up @@ -1467,6 +1494,36 @@ describe("Collections", () => {
reactiveValObj.a = 1;
expect(observer).toHaveBeenCalledTimes(2);
});

test("iterating with forEach returns reactives", async () => {
const keyObj = { a: 2 };
const valObj = { a: 2 };
const thisArg = {};
const observer = jest.fn();
const state = reactive(new Map([[keyObj, valObj]]), observer);
let reactiveKeyObj: any, reactiveValObj: any, thisObj: any, mapObj: any;
state.forEach(function (this: any, val, key, map) {
[reactiveValObj, reactiveKeyObj, mapObj, thisObj] = [val, key, map, this];
}, thisArg);
expect(reactiveKeyObj).not.toBe(keyObj);
expect(reactiveValObj).not.toBe(valObj);
expect(mapObj).toBe(state); // third argument should be the reactive
expect(thisObj).toBe(thisArg); // thisArg should not be made reactive
expect(toRaw(reactiveKeyObj as any)).toBe(keyObj);
expect(toRaw(reactiveValObj as any)).toBe(valObj);
reactiveKeyObj!.a = 0;
reactiveValObj!.a = 0;
expect(observer).toHaveBeenCalledTimes(0);
reactiveKeyObj!.a; // observe key "a" in key sub-reactive;
reactiveKeyObj!.a = 1;
expect(observer).toHaveBeenCalledTimes(1);
reactiveValObj!.a; // observe key "a" in val sub-reactive;
reactiveValObj!.a = 1;
expect(observer).toHaveBeenCalledTimes(2);
reactiveKeyObj!.a = 1; // setting same value again shouldn't notify
reactiveValObj!.a = 1;
expect(observer).toHaveBeenCalledTimes(2);
});
});

describe("WeakMap", () => {
Expand Down

0 comments on commit 0625b58

Please sign in to comment.