Skip to content

Commit

Permalink
fix(current()): add getCurrent() and castMutable() APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
unadlib committed Sep 19, 2024
1 parent c1f6e80 commit f5f1192
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 7 deletions.
33 changes: 29 additions & 4 deletions src/current.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DraftType, ProxyDraft } from './interface';
import { DraftType, type Draft, type ProxyDraft } from './interface';
import {
forEach,
get,
Expand Down Expand Up @@ -59,7 +59,7 @@ export function handleReturnValue<T extends object>(options: {
}
}

function getCurrent(target: any) {
function getCurrentValue(target: any) {
const proxyDraft = getProxyDraft(target);
if (!isDraftable(target, proxyDraft?.options)) return target;
const type = getType(target);
Expand Down Expand Up @@ -90,7 +90,7 @@ function getCurrent(target: any) {

forEach(currentValue, (key, value) => {
if (proxyDraft && isEqual(get(proxyDraft.original, key), value)) return;
const newValue = getCurrent(value);
const newValue = getCurrentValue(value);
if (newValue !== value) {
if (currentValue === target) ensureShallowCopy();
set(currentValue, key, newValue);
Expand Down Expand Up @@ -121,5 +121,30 @@ export function current<T extends object>(target: T): T {
if (!isDraft(target)) {
throw new Error(`current() is only used for Draft, parameter: ${target}`);
}
return getCurrent(target);
return getCurrentValue(target);
}

/**
* `getCurrent(draft)` to get current state in the draft mutation function.
*
* ## Example
*
* ```ts
* import { create, getCurrent } from '../index';
*
* const baseState = { foo: { bar: 'str' }, arr: [] };
* const state = create(
* baseState,
* (draft) => {
* draft.foo.bar = 'str2';
* expect(getCurrent(draft.foo)).toEqual({ bar: 'str2' });
* },
* );
* ```
*/
export function getCurrent<T extends object>(target: Draft<T>): T {
if (!isDraft(target)) {
throw new Error(`current() is only used for Draft, parameter: ${target}`);
}
return getCurrentValue(target);
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ export { makeCreator } from './makeCreator';
export { create } from './create';
export { apply } from './apply';
export { original } from './original';
export { current } from './current';
export { current, getCurrent } from './current';
export { unsafe } from './unsafe';
export { rawReturn } from './rawReturn';
export { isDraft } from './utils/draft';
export { isDraftable } from './utils/draft';
export { markSimpleObject } from './utils/marker';

export { castDraft, castImmutable } from './utils/cast';
export { castDraft, castImmutable, castMutable } from './utils/cast';
export type {
Immutable,
Draft,
Expand Down
7 changes: 7 additions & 0 deletions src/utils/cast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ export function castDraft<T>(value: T): Draft<T> {
export function castImmutable<T>(value: T): Immutable<T> {
return value as any;
}

/**
* Cast a value to an Mutable type value.
*/
export function castMutable<T>(draft: Draft<T>): T {
return draft as any;
}
30 changes: 29 additions & 1 deletion test/immer-non-support.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-inner-declarations */
/* eslint-disable symbol-description */
/* eslint-disable no-unused-expressions */
Expand All @@ -15,8 +16,10 @@ import {
applyPatches,
setUseStrictShallowCopy,
current as immerCurrent,
createDraft,
finishDraft,
} from 'immer';
import { create, apply, current } from '../src';
import { create, apply, current, getCurrent } from '../src';

enableMapSet();

Expand Down Expand Up @@ -589,3 +592,28 @@ test('#47 Avoid deep copies', () => {
});
}
});

test('#61 - type issue: current of Draft<T> type should return T type', () => {
{
function test<T extends { x: { y: ReadonlySet<string> } }>(base: T): T {
const draft = createDraft(base);
// @ts-ignore
const currentValue: T = immerCurrent(draft); // !!! Type Draft<T> is not assignable to type T
// @ts-expect-error
return finishDraft(draft);
}
expect(test({ x: { y: new Set(['a', 'b']) } })).toEqual({
x: { y: new Set(['a', 'b']) },
});
}
{
function test<T extends { x: { y: ReadonlySet<string> } }>(base: T): T {
const [draft, f] = create(base);
const currentValue: T = getCurrent(draft);
return f();
}
expect(test({ x: { y: new Set(['a', 'b']) } })).toEqual({
x: { y: new Set(['a', 'b']) },
});
}
});
21 changes: 21 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable prefer-template */
/* eslint-disable no-unused-expressions */
/* eslint-disable arrow-body-style */
Expand All @@ -14,6 +15,8 @@ import {
markSimpleObject,
rawReturn,
makeCreator,
getCurrent,
castMutable,
} from '../src';
import { PROXY_DRAFT } from '../src/constant';

Expand Down Expand Up @@ -4083,3 +4086,21 @@ test('#59 - Failure to apply inverse patchset(Map)', () => {
const reverted2 = apply(myObj, patchset);
expect(reverted2).toEqual(newState);
});

test('#61 - type issue: current of Draft<T> type should return T type', () => {
function test<T extends { x: { y: ReadonlySet<string> } }>(base: T): T {
const [draft, f] = create(base);
const mutableValue: T = castMutable(draft);
const currentValue: T = getCurrent(draft);
expect(() => {
// @ts-expect-error
const value = getCurrent({ x: { y: new Set(['a', 'b']) } } as T);
}).toThrowErrorMatchingInlineSnapshot(
`"current() is only used for Draft, parameter: [object Object]"`
);
return f();
}
expect(test({ x: { y: new Set(['a', 'b']) } })).toEqual({
x: { y: new Set(['a', 'b']) },
});
});

0 comments on commit f5f1192

Please sign in to comment.