Skip to content

Commit

Permalink
refactor(current): refactor current() API (#50)
Browse files Browse the repository at this point in the history
* refactor(current): refactor current() API

* fix(current): update

* fix(copy): fix shallowCopy check
  • Loading branch information
unadlib authored Jul 24, 2024
1 parent 31602fc commit dff26cc
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 25 deletions.
29 changes: 7 additions & 22 deletions src/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,38 +64,23 @@ function getCurrent(target: any) {
if (!isDraftable(target, proxyDraft?.options)) return target;
const type = getType(target);
if (proxyDraft && !proxyDraft.operated) return proxyDraft.original;
let currentValue: any;
function ensureShallowCopy() {
if (!currentValue || currentValue === target)
if (proxyDraft) proxyDraft.finalized = true;
let currentValue = target;
if (proxyDraft) {
try {
currentValue =
type === DraftType.Map
? new Map(target)
: type === DraftType.Set
? Array.from(proxyDraft!.setMap!.values()!)
: shallowCopy(target, proxyDraft?.options);
}

if (proxyDraft) {
// It's a proxy draft, let's create a shallow copy eagerly
proxyDraft.finalized = true;
try {
ensureShallowCopy();
? Array.from(proxyDraft.setMap!.values()!)
: shallowCopy(target, proxyDraft.options);
} finally {
proxyDraft.finalized = false;
}
} else {
// It's not a proxy draft, let's use the target directly and let's see
// lazily if we need to create a shallow copy
currentValue = target;
}

forEach(currentValue, (key, value) => {
if (proxyDraft && isEqual(get(proxyDraft.original, key), value)) return;
const newValue = getCurrent(value);
if (newValue !== value) {
ensureShallowCopy();
set(currentValue, key, newValue);
}
set(currentValue, key, getCurrent(value));
});
return type === DraftType.Set ? new Set(currentValue) : currentValue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/utils/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function strictCopy(target: any) {

const propIsEnum = Object.prototype.propertyIsEnumerable;

export function shallowCopy(original: any, options?: Options<any, any>) {
export function shallowCopy(original: any, options: Options<any, any>) {
let markResult: any;
if (Array.isArray(original)) {
return Array.prototype.concat.call(original);
Expand All @@ -38,7 +38,7 @@ export function shallowCopy(original: any, options?: Options<any, any>) {
} else if (original instanceof Map) {
return new Map(original);
} else if (
options?.mark &&
options.mark &&
((markResult = options.mark(original, dataTypes)),
markResult !== undefined) &&
markResult !== dataTypes.mutable
Expand Down
80 changes: 80 additions & 0 deletions test/current.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,83 @@ test('nested draft', () => {
expect(current(draft.j) === current(draft.j)).toBeTruthy();
});
});

test('#47 current creates new copies of the objects where unnecessary', () => {
const obj = { k: 42 };
const original = { x: { y: { z: [obj] } } };
const yReplace = { z: [obj] };

// with create
const withCreate = create(original, (draft) => {
draft.x.y = yReplace;
});
expect(withCreate.x.y === yReplace).toBe(true);
expect(withCreate.x.y.z[0] === obj).toBe(true);
// with draft + current
const [draft] = create(original);
draft.x.y = yReplace;
const withDraft = current(draft);
expect(withDraft.x.y === yReplace).toBe(true);
expect(withDraft.x.y.z[0] === obj).toBe(true);
});

test('current() for changed Set/Map draft', () => {
const obj = { k: 42 };
const base = { x: { y: { z: obj } }, a: new Set([1]), b: new Map([[1, 2]]) };
create(base, (draft) => {
draft.a.add(2);
draft.b.delete(1);
draft.x.y.z = { k: 43 };
const c = current(draft);
expect(c.a.has(2)).toBeTruthy();
expect(c.a.has(1)).toBeTruthy();
expect(c.b.has(1)).toBeFalsy();
expect(c.x.y.z.k).toBe(43);
expect(JSON.stringify(c)).toMatchInlineSnapshot(
`"{"x":{"y":{"z":{"k":43}}},"a":{},"b":{}}"`
);
});
});

test('Avoid deep copies', () => {
const obj = { k: 42 };
const base = { x: { y: { z: obj } }, a: { c: 1 } };
create(base, (draft) => {
// @ts-ignore
const a = draft.a;
a.c = 2;
// @ts-ignore
delete draft.a;
// @ts-ignore
draft.x1 = { y1: { z1: obj }, a };
const c = current(draft);
// @ts-ignore
expect(c.x1.y1.z1).toBe(obj);
expect(JSON.stringify(c)).toMatchInlineSnapshot(
`"{"x":{"y":{"z":{"k":42}}},"x1":{"y1":{"z1":{"k":42}},"a":{"c":2}}}"`
);
});
});

test('nested create() - Avoid deep copies', () => {
const obj = { k: 42 };
const base = { x: { y: { z: obj } }, a: { c: 1 } };
const base0 = { x: { y: { z: obj } }, a: { c: 1 } };
create(base0, (draft0) => {
// @ts-ignore
const a = draft0.a;
a.c = 2;
// @ts-ignore
delete draft0.a;
create(base, (draft) => {
// @ts-ignore
draft.x1 = { y1: { z1: obj }, a };
const c = current(draft);
// @ts-ignore
expect(c.x1.y1.z1).toBe(obj);
expect(JSON.stringify(c)).toMatchInlineSnapshot(
`"{"x":{"y":{"z":{"k":42}}},"a":{"c":1},"x1":{"y1":{"z1":{"k":42}},"a":{"c":2}}}"`
);
});
});
});
25 changes: 24 additions & 1 deletion test/immer-non-support.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
enablePatches,
applyPatches,
setUseStrictShallowCopy,
current as immerCurrent,
} from 'immer';
import { create, apply } from '../src';
import { create, apply, current } from '../src';

enableMapSet();

Expand Down Expand Up @@ -566,3 +567,25 @@ test(`Object values of a Map are not frozen anymore #1119`, () => {
expect(Object.isFrozen(product)).toBeTruthy();
}
});

test('#47 Avoid deep copies', () => {
{
const obj = { k: 42 };
const base = { x: { y: { z: obj } } };
produce(base, (draft) => {
draft.x = { y: { z: obj } };
const c = immerCurrent(draft);
// ! it should be equal
expect(c.x.y.z).not.toBe(obj);
});
}
{
const obj = { k: 42 };
const base = { x: { y: { z: obj } } };
create(base, (draft) => {
draft.x = { y: { z: obj } };
const c = current(draft);
expect(c.x.y.z).toBe(obj);
});
}
});

0 comments on commit dff26cc

Please sign in to comment.