Skip to content

Commit 1cb1f4d

Browse files
committed
Update package version to 0.1.23, enhance enableMapSet function to update global equality check for Map and Set support, and add comprehensive tests for immer-utils and reaction handling with Maps and Sets.
1 parent 1abd6ba commit 1cb1f4d

4 files changed

Lines changed: 260 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@flexsurfer/reflex",
3-
"version": "0.1.22",
3+
"version": "0.1.23",
44
"license": "MIT",
55
"repository": {
66
"type": "git",

src/immer-utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
*/
55

66
import { isDraft, original as immerOriginal, current as immerCurrent, enableMapSet as immerEnableMapSet } from 'immer';
7+
import { setGlobalEqualityCheck, getGlobalEqualityCheck } from './settings';
8+
import isEqual from 'fast-deep-equal';
9+
import isEqualEs6 from 'fast-deep-equal/es6';
710

811
/**
912
* Safe version of immer's original function
@@ -26,7 +29,14 @@ export function current<T>(value: T): T {
2629
/**
2730
* Enable Map and Set support in Immer
2831
* This allows Immer to handle Map and Set objects properly in drafts
32+
* Also updates the global equality check to use fast-deep-equal/es6 for proper Map/Set comparison,
33+
* but only if the current equality check is still the default isEqual
2934
*/
3035
export function enableMapSet(): void {
3136
immerEnableMapSet();
37+
// Only update the global equality check if it's still the default isEqual
38+
// to avoid overriding user-set custom equality checks
39+
if (getGlobalEqualityCheck() === isEqual) {
40+
setGlobalEqualityCheck(isEqualEs6);
41+
}
3242
}

src/tests/immer-utils.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { enableMapSet, original, current } from '../immer-utils';
2+
import { getGlobalEqualityCheck, setGlobalEqualityCheck } from '../settings';
3+
import isEqual from 'fast-deep-equal';
4+
import isEqualEs6 from 'fast-deep-equal/es6';
5+
6+
describe('immer-utils', () => {
7+
describe('enableMapSet', () => {
8+
beforeEach(() => {
9+
// Reset to default equality check before each test
10+
setGlobalEqualityCheck(isEqual);
11+
});
12+
13+
it('should update global equality check to isEqualEs6 when current check is default isEqual', () => {
14+
// Initially should be the default isEqual
15+
expect(getGlobalEqualityCheck()).toBe(isEqual);
16+
17+
enableMapSet();
18+
19+
// Should now be isEqualEs6
20+
expect(getGlobalEqualityCheck()).toBe(isEqualEs6);
21+
});
22+
23+
it('should NOT override custom equality check when user has set one', () => {
24+
// Set a custom equality check
25+
const customEquality = () => true;
26+
setGlobalEqualityCheck(customEquality);
27+
28+
// Verify it's set
29+
expect(getGlobalEqualityCheck()).toBe(customEquality);
30+
31+
enableMapSet();
32+
33+
// Should still be the custom equality check, not isEqualEs6
34+
expect(getGlobalEqualityCheck()).toBe(customEquality);
35+
expect(getGlobalEqualityCheck()).not.toBe(isEqualEs6);
36+
});
37+
38+
it('should handle Map and Set equality correctly after enableMapSet is called', () => {
39+
const map1 = new Map([['key1', 'value1'], ['key2', 'value2']]);
40+
const map2 = new Map([['key1', 'value1'], ['key2', 'value2']]);
41+
const set1 = new Set(['a', 'b', 'c']);
42+
const set2 = new Set(['a', 'b', 'c']);
43+
const map3 = new Map([['key1', 'value1'], ['key2', 'different']]);
44+
const set3 = new Set(['a', 'b', 'd']);
45+
46+
// Before enableMapSet, default isEqual might not handle Map/Set properly
47+
let equalityCheck = getGlobalEqualityCheck();
48+
49+
expect(equalityCheck(map1, map2)).toBe(true);
50+
expect(equalityCheck(set1, set2)).toBe(true);
51+
expect(equalityCheck(map1, map3)).toBe(true);
52+
expect(equalityCheck(set1, set3)).toBe(true);
53+
54+
enableMapSet();
55+
56+
// After enableMapSet, isEqualEs6 should handle Map/Set properly
57+
equalityCheck = getGlobalEqualityCheck();
58+
59+
expect(equalityCheck(map1, map2)).toBe(true);
60+
expect(equalityCheck(set1, set2)).toBe(true);
61+
expect(equalityCheck(map1, map3)).toBe(false);
62+
expect(equalityCheck(set1, set3)).toBe(false);
63+
});
64+
});
65+
66+
describe('original', () => {
67+
it('should return original value for non-draft values', () => {
68+
const obj = { a: 1, b: 2 };
69+
expect(original(obj)).toBe(obj);
70+
});
71+
72+
it('should return original value for primitive values', () => {
73+
expect(original(42)).toBe(42);
74+
expect(original('hello')).toBe('hello');
75+
expect(original(null)).toBe(null);
76+
expect(original(undefined)).toBe(undefined);
77+
});
78+
});
79+
80+
describe('current', () => {
81+
it('should return current value for non-draft values', () => {
82+
const obj = { a: 1, b: 2 };
83+
expect(current(obj)).toBe(obj);
84+
});
85+
86+
it('should return current value for primitive values', () => {
87+
expect(current(42)).toBe(42);
88+
expect(current('hello')).toBe('hello');
89+
expect(current(null)).toBe(null);
90+
expect(current(undefined)).toBe(undefined);
91+
});
92+
});
93+
});

src/tests/reaction.test.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,162 @@ describe('Complex data structures and memoization', () => {
10571057
});
10581058
});
10591059

1060+
describe('Map and Set support with enableMapSet', () => {
1061+
it('should work correctly with Map objects after enableMapSet is called', async () => {
1062+
const { enableMapSet } = await import('../../src/immer-utils');
1063+
const { setGlobalEqualityCheck } = await import('../../src/settings');
1064+
const isEqual = (await import('fast-deep-equal')).default;
1065+
1066+
let mapValue = new Map([['key1', 'value1'], ['key2', 'value2']]);
1067+
const root = Reaction.create(() => mapValue);
1068+
const computed = Reaction.create((map) => ({
1069+
size: map.size,
1070+
keys: Array.from(map.keys()).sort(),
1071+
values: Array.from(map.values()).sort()
1072+
}), [root]);
1073+
1074+
const callback = jest.fn();
1075+
computed.watch(callback);
1076+
1077+
// Initial computation
1078+
const result1 = computed.computeValue();
1079+
expect(result1.size).toBe(2);
1080+
expect(result1.keys).toEqual(['key1', 'key2']);
1081+
expect(result1.values).toEqual(['value1', 'value2']);
1082+
callback.mockClear();
1083+
1084+
// Enable MapSet support
1085+
enableMapSet();
1086+
1087+
// Update map with same content - should not trigger change due to equality
1088+
mapValue = new Map([['key1', 'value1'], ['key2', 'value2']]);
1089+
root.markDirty();
1090+
1091+
await waitForMicrotasks();
1092+
expect(callback).not.toHaveBeenCalled(); // Should not trigger due to deep equality
1093+
1094+
// Update map with different content - should trigger change
1095+
mapValue = new Map([['key1', 'value1'], ['key2', 'updated']]);
1096+
root.markDirty();
1097+
1098+
await waitForMicrotasks();
1099+
expect(callback).toHaveBeenCalledWith({
1100+
size: 2,
1101+
keys: ['key1', 'key2'],
1102+
values: ['updated', 'value1']
1103+
});
1104+
1105+
computed.unwatch(callback);
1106+
1107+
// Reset to default equality check
1108+
setGlobalEqualityCheck(isEqual);
1109+
});
1110+
1111+
it('should work correctly with Set objects after enableMapSet is called', async () => {
1112+
const { enableMapSet } = await import('../../src/immer-utils');
1113+
const { setGlobalEqualityCheck, getGlobalEqualityCheck } = await import('../../src/settings');
1114+
const isEqual = (await import('fast-deep-equal')).default;
1115+
1116+
let setValue = new Set(['a', 'b', 'c']);
1117+
const root = Reaction.create(() => setValue);
1118+
const computed = Reaction.create((set) => ({
1119+
size: set.size,
1120+
values: Array.from(set).sort()
1121+
}), [root]);
1122+
1123+
const callback = jest.fn();
1124+
computed.watch(callback);
1125+
1126+
// Initial computation
1127+
const result1 = computed.computeValue();
1128+
expect(result1.size).toBe(3);
1129+
expect(result1.values).toEqual(['a', 'b', 'c']);
1130+
callback.mockClear();
1131+
1132+
// Enable MapSet support
1133+
enableMapSet();
1134+
1135+
// Update set with same content - should not trigger change
1136+
setValue = new Set(['a', 'b', 'c']);
1137+
root.markDirty();
1138+
1139+
await waitForMicrotasks();
1140+
expect(callback).not.toHaveBeenCalled(); // Should not trigger due to deep equality
1141+
1142+
// Update set with different content - should trigger change
1143+
setValue = new Set(['a', 'b', 'd']);
1144+
root.markDirty();
1145+
1146+
await waitForMicrotasks();
1147+
expect(callback).toHaveBeenCalledWith({
1148+
size: 3,
1149+
values: ['a', 'b', 'd']
1150+
});
1151+
1152+
computed.unwatch(callback);
1153+
1154+
// Reset to default equality check
1155+
setGlobalEqualityCheck(isEqual);
1156+
});
1157+
1158+
it('should handle complex nested structures with Maps and Sets', async () => {
1159+
const { enableMapSet } = await import('../../src/immer-utils');
1160+
const { setGlobalEqualityCheck, getGlobalEqualityCheck } = await import('../../src/settings');
1161+
const isEqual = (await import('fast-deep-equal')).default;
1162+
1163+
let complexValue = {
1164+
map: new Map([['users', new Set(['alice', 'bob'])]]),
1165+
config: { enabled: true }
1166+
};
1167+
1168+
const root = Reaction.create(() => complexValue);
1169+
const computed = Reaction.create((obj) => ({
1170+
userCount: obj.map.get('users')?.size || 0,
1171+
enabled: obj.config.enabled
1172+
}), [root]);
1173+
1174+
const callback = jest.fn();
1175+
computed.watch(callback);
1176+
1177+
// Initial computation
1178+
const result1 = computed.computeValue();
1179+
expect(result1.userCount).toBe(2);
1180+
expect(result1.enabled).toBe(true);
1181+
callback.mockClear();
1182+
1183+
// Enable MapSet support
1184+
enableMapSet();
1185+
1186+
// Update with structurally identical object - should not trigger change
1187+
complexValue = {
1188+
map: new Map([['users', new Set(['alice', 'bob'])]]),
1189+
config: { enabled: true }
1190+
};
1191+
root.markDirty();
1192+
1193+
await waitForMicrotasks();
1194+
expect(callback).not.toHaveBeenCalled();
1195+
1196+
// Update with different Map content - should trigger change
1197+
complexValue = {
1198+
map: new Map([['users', new Set(['alice', 'bob', 'charlie'])]]),
1199+
config: { enabled: true }
1200+
};
1201+
root.markDirty();
1202+
1203+
await waitForMicrotasks();
1204+
expect(callback).toHaveBeenCalledWith({
1205+
userCount: 3,
1206+
enabled: true
1207+
});
1208+
1209+
computed.unwatch(callback);
1210+
1211+
// Reset to default equality check
1212+
setGlobalEqualityCheck(isEqual);
1213+
});
1214+
});
1215+
10601216
describe('Custom equality check configuration', () => {
10611217
// Note: Equality checks are only used for COMPUTED reactions (those with dependencies).
10621218
// Root reactions always mark changed=true and don't use equality checks.

0 commit comments

Comments
 (0)