Skip to content

Commit

Permalink
feat(ts): Support typescript 3.4
Browse files Browse the repository at this point in the history
BREAKING CHANGE: typescript@3.4.0 is now the minimum required version
  • Loading branch information
aleclarson committed Apr 17, 2019
1 parent 2c27133 commit 2868065
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 55 deletions.
10 changes: 5 additions & 5 deletions __tests__/immutable.ts
Expand Up @@ -14,19 +14,19 @@ declare const exactType: <Actual, Expected>(
// array in tuple
{
let val = {} as Immutable<[string[], 1]>
exactType(val, {} as [ReadonlyArray<string>, 1])
exactType(val, {} as readonly [ReadonlyArray<string>, 1])
}

// tuple in array
{
let val = {} as Immutable<[string, 1][]>
exactType(val, {} as ReadonlyArray<[string, 1]>)
exactType(val, {} as ReadonlyArray<readonly [string, 1]>)
}

// tuple in tuple
{
let val = {} as Immutable<[[string, 1], 1]>
exactType(val, {} as [[string, 1], 1])
exactType(val, {} as readonly [readonly [string, 1], 1])
}

// array in array
Expand All @@ -38,13 +38,13 @@ declare const exactType: <Actual, Expected>(
// tuple in object
{
let val = {} as Immutable<{a: [string, 1]}>
exactType(val, {} as {readonly a: [string, 1]})
exactType(val, {} as {readonly a: readonly [string, 1]})
}

// object in tuple
{
let val = {} as Immutable<[{a: string}, 1]>
exactType(val, {} as [{readonly a: string}, 1])
exactType(val, {} as readonly [{readonly a: string}, 1])
}

// array in object
Expand Down
95 changes: 61 additions & 34 deletions __tests__/produce.ts
Expand Up @@ -2,7 +2,8 @@ import produce, {
produce as produce2,
applyPatches,
Patch,
nothing
nothing,
Draft
} from "../dist/immer.js"

// prettier-ignore
Expand Down Expand Up @@ -68,23 +69,24 @@ it("can update readonly state via standard api", () => {

// NOTE: only when the function type is inferred
it("can infer state type from default state", () => {
type Producer = (base: number | undefined) => number
let foo = produce(_ => {}, 1)
exactType(foo, {} as Producer)
exactType(foo(2), 0 as number)
type State = {readonly a: number} | boolean
type Recipe = (base?: State | undefined) => State

let foo = produce(_ => {}, {} as State)
exactType(foo, {} as Recipe)
})

it("can infer state type from recipe function", () => {
type T = string | number
type Producer = (base: T | undefined, _2: number) => T
type State = {readonly a: string} | {readonly b: string}
type Recipe = (base: State | undefined, arg: number) => State

let foo = produce((_: string | number, _2: number) => {}, 1)
exactType(foo, {} as Producer)
exactType(foo("", 0), {} as string | number)
let foo = produce((draft: Draft<State>, arg: number) => {}, {} as any)
exactType(foo, {} as Recipe)
})

it("cannot infer state type when the function type and default state are missing", () => {
exactType(produce(_ => {}), {} as (base: any) => any)
const res = produce(_ => {})
exactType(res, {} as (base: any) => any)
})

it("can update readonly state via curried api", () => {
Expand Down Expand Up @@ -136,25 +138,53 @@ it("can apply patches", () => {

describe("curried producer", () => {
it("supports rest parameters", () => {
type Foo = (base: {}, _2: number, _3: number) => {}
let foo = produce((_1: {}, _2: number, _3: number) => {})
exactType(foo, {} as Foo)
foo({}, 1, 2)
type State = {readonly a: 1}

// No initial state:
let foo = produce<State, number[]>(() => {})
exactType(foo, {} as (base: State, ...args: number[]) => State)
foo({} as State, 1, 2)

// TODO: Using argument parameters
// let woo = produce((state: Draft<State>, ...args: number[]) => {})
// exactType(woo, {} as (base: State, ...args: number[]) => State)
// woo({} as State, 1, 2)

// With initial state:
type Bar = (base: {} | undefined, _2: number, _3: number) => {}
let bar = produce((_1: {}, _2: number, _3: number) => {}, {})
exactType(bar, {} as Bar)
bar(undefined, 1, 2)
let bar = produce(
(state: Draft<State>, ...args: number[]) => {},
{} as State
)
exactType(bar, {} as (base?: State, ...args: number[]) => State)
bar({} as State | undefined, 1, 2)
bar()

// When args is a tuple:
let tup = produce(
(state: Draft<State>, ...args: [string, ...number[]]) => {},
{} as State
)
exactType(tup, {} as (
base: State | undefined,
arg1: string,
...args: number[]
) => State)
tup({a: 1}, "", 2)
tup(undefined, "", 2)
})

it("can be passed a readonly array", () => {
let foo = produce((_: any[]) => {})
// No initial state:
let foo = produce<ReadonlyArray<any>>(() => {})
exactType(foo, {} as (base: readonly any[]) => readonly any[])
foo([] as ReadonlyArray<any>)

// With initial state:
let bar = produce((_: any[]) => {}, [])
let bar = produce(() => {}, [] as ReadonlyArray<any>)
exactType(bar, {} as (base?: readonly any[]) => readonly any[])
bar([] as ReadonlyArray<any>)
bar(undefined)
bar()
})
})

Expand Down Expand Up @@ -219,23 +249,20 @@ it("can return the draft itself", () => {
})

it("can return a promise", () => {
let base = {} as {readonly a: number}
type Base = {readonly a: number}
let base = {} as Base

// Return a promise only.
exactType(
produce(base, draft => {
return Promise.resolve(draft.a > 0 ? null : undefined)
}),
{} as Promise<{readonly a: number} | null>
)
let res1 = produce(base, draft => {
return Promise.resolve(draft.a > 0 ? null : undefined)
})
exactType(res1, {} as Promise<Base | null>)

// Return a promise or undefined.
exactType(
produce(base, draft => {
if (draft.a > 0) return Promise.resolve()
}),
{} as (Promise<{readonly a: number}> | {readonly a: number})
)
let res2 = produce(base, draft => {
if (draft.a > 0) return Promise.resolve()
})
exactType(res2, {} as Base | Promise<Base>)
})

it("works with `void` hack", () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -67,7 +67,7 @@
"regenerator-runtime": "^0.11.1",
"rimraf": "^2.6.2",
"seamless-immutable": "^7.1.3",
"typescript": "3.1.1",
"typescript": "3.4.3",
"yarn-or-npm": "^2.0.4"
},
"jest": {
Expand Down
32 changes: 21 additions & 11 deletions src/immer.d.ts
Expand Up @@ -92,22 +92,32 @@ export interface IProduce {
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
<T = any, Return = void, D = Draft<T>>(
base: T,
recipe: (this: D, draft: D) => Return,
<Base = any, Return = void>(
base: Base extends Function ? never : Base,
recipe: (this: Draft<Base>, draft: Draft<Base>) => Return,
listener?: PatchListener
): Produced<T, Return>
): Produced<Base, Return>

/** Curried producer with a default value */
<T = any, Rest extends any[] = [], Return = void, D = Draft<T>>(
recipe: (this: D, draft: D, ...rest: Rest) => Return,
defaultBase: T
): (base: Immutable<D> | undefined, ...rest: Rest) => Produced<D, Return>
<Base = any, Rest extends any[] = [], Return = void>(
recipe: (this: Base, draft: Base, ...rest: Rest) => Return,
defaultBase: Immutable<Base>
): Rest[number][] extends Rest | never[]
? (
// The `base` argument is optional when `Rest` is optional.
base?: Immutable<Base>,
...rest: Rest
) => Produced<Immutable<Base>, Return>
: (
// The `base` argument is required when `Rest` is required.
base: Immutable<Base> | undefined,
...rest: Rest
) => Produced<Immutable<Base>, Return>

/** Curried producer with no default value */
<T = any, Rest extends any[] = [], Return = void>(
recipe: (this: Draft<T>, draft: Draft<T>, ...rest: Rest) => Return
): (base: Immutable<T>, ...rest: Rest) => Produced<T, Return>
<Base = any, Rest extends any[] = [], Return = void>(
recipe: (this: Draft<Base>, draft: Draft<Base>, ...rest: Rest) => Return
): (base: Immutable<Base>, ...rest: Rest) => Produced<Base, Return>
}

export const produce: IProduce
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Expand Up @@ -6049,10 +6049,10 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"

typescript@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.1.tgz#3362ba9dd1e482ebb2355b02dfe8bcd19a2c7c96"
integrity sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==
typescript@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==

uglify-js@^3.1.4:
version "3.5.3"
Expand Down

0 comments on commit 2868065

Please sign in to comment.