fix set iterator (destructuring etc. form)

main
Laurin Weger 3 weeks ago
parent 400ba719b5
commit a04162b724
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 31
      src/deepSignal.ts
  2. 21
      src/test/watchPatches.test.ts

@ -371,7 +371,11 @@ const createProxy = (
};
// Set-specific access & structural patch emission.
function getFromSet(raw: Set<any>, key: string, receiver: object): any {
function getFromSet(
raw: Set<any>,
key: string | symbol,
receiver: object
): any {
const meta = proxyMeta.get(receiver);
// Helper to proxy a single entry (object) & assign synthetic id if needed.
const ensureEntryProxy = (entry: any) => {
@ -516,8 +520,27 @@ function getFromSet(raw: Set<any>, key: string, receiver: object): any {
});
};
}
// Properly handle native iteration (for..of, Array.from, spread) by binding to the raw Set.
if (key === Symbol.iterator) {
// Return a function whose `this` is the raw Set (avoids brand check failure on the proxy).
return function (this: any) {
// Use raw.values() so we can still ensure child entries are proxied lazily.
const iterable = raw.values();
return {
[Symbol.iterator]() {
return this;
},
next() {
const n = iterable.next();
if (n.done) return n;
const entry = ensureEntryProxy(n.value);
return { value: entry, done: false };
},
} as Iterator<any>;
};
}
if (key === Symbol.iterator.toString()) {
// string form access of iterator symbol; pass through
// string form access of iterator symbol; pass through (rare path)
}
const val = (raw as any)[key];
if (typeof val === "function") return val.bind(raw);
@ -601,8 +624,8 @@ const get =
(target: object, fullKey: string, receiver: object): unknown => {
if (peeking) return Reflect.get(target, fullKey, receiver);
// Set handling delegated completely.
if (target instanceof Set && typeof fullKey === "string") {
return getFromSet(target as Set<any>, fullKey, receiver);
if (target instanceof Set) {
return getFromSet(target as Set<any>, fullKey as any, receiver);
}
const norm = normalizeKey(target, fullKey, isArrayMeta, receiver);
if ((norm as any).shortCircuit) return (norm as any).shortCircuit; // returned meta proxy

@ -298,6 +298,27 @@ describe("watch (patch mode)", () => {
expect(new Set(keys).size).toBe(2);
stop();
});
it("allows Array.from() and spread on Set without brand errors and tracks nested mutation", async () => {
const st = deepSignal({
s: new Set<any>([{ id: "eIter", inner: { v: 1 } }]),
});
// Regression: previously 'values method called on incompatible Proxy' was thrown here.
const arr = Array.from(st.s);
expect(arr.length).toBe(1);
expect(arr[0].inner.v).toBe(1);
const spread = [...st.s];
expect(spread[0].inner.v).toBe(1);
const batches: DeepPatch[][] = [];
const { stopListening: stop } = watch(st, ({ patches }) =>
batches.push(patches)
);
spread[0].inner.v = 2; // mutate nested field of iterated (proxied) entry
await Promise.resolve();
const flat = batches.flat().map((p) => p.path.join("."));
expect(flat.some((p) => p.endsWith("eIter.inner.v"))).toBe(true);
stop();
});
});
describe("Arrays & mixed batch", () => {

Loading…
Cancel
Save