Skip to content

Commit

Permalink
test: hooks (onAssign, onDelete, onCopy)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Dec 15, 2018
1 parent 7f50364 commit ab05744
Show file tree
Hide file tree
Showing 2 changed files with 371 additions and 0 deletions.
146 changes: 146 additions & 0 deletions __tests__/__snapshots__/hooks.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`hooks - onAssign() when draft is an array assign 1`] = `
Array [
Array [
0,
0,
],
]
`;

exports[`hooks - onAssign() when draft is an array push 1`] = `
Array [
Array [
0,
4,
],
]
`;

exports[`hooks - onAssign() when draft is an array splice (length += 0) 1`] = `
Array [
Array [
1,
0,
],
]
`;

exports[`hooks - onAssign() when draft is an array splice (length += 1) 1`] = `
Array [
Array [
1,
0,
],
Array [
2,
0,
],
Array [
3,
3,
],
]
`;

exports[`hooks - onAssign() when draft is an array splice (length -= 1) 1`] = `
Array [
Array [
0,
6,
],
Array [
1,
3,
],
]
`;

exports[`hooks - onAssign() when draft is an array unshift 1`] = `
Array [
Array [
0,
0,
],
Array [
1,
1,
],
]
`;

exports[`hooks - onAssign() when draft is an object assign 1`] = `
Array [
Array [
"a",
1,
],
Array [
"c",
1,
],
]
`;

exports[`hooks - onAssign() when draft is an object nested assignments 1`] = `
Array [
Array [
"c",
2,
],
Array [
"b",
Object {
"c": 2,
"e": 1,
},
],
Array [
"a",
Object {
"b": Object {
"c": 2,
"e": 1,
},
},
],
]
`;

exports[`hooks - onDelete() when draft is an array - length = 0 1`] = `Array []`;

exports[`hooks - onDelete() when draft is an array - pop 1`] = `
Array [
Array [
"0",
],
]
`;

exports[`hooks - onDelete() when draft is an array - splice (length -= 1) 1`] = `
Array [
Array [
"2",
],
]
`;

exports[`hooks - onDelete() when draft is an object - delete 1`] = `
Array [
Array [
"a",
],
Array [
"c",
],
]
`;

exports[`hooks - onDelete() when draft is an object - nested deletions 1`] = `
Array [
Array [
"c",
],
]
`;
225 changes: 225 additions & 0 deletions __tests__/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
"use strict"
import {Immer} from "../src/index"
import matchers from "expect/build/matchers"

describe("hooks -", () => {
let produce, onAssign, onDelete, onCopy

const reset = () =>
({produce, onAssign, onDelete, onCopy} = new Immer({
autoFreeze: true,
onAssign: defuseProxies(jest.fn().mockName("onAssign")),
onDelete: defuseProxies(jest.fn().mockName("onDelete")),
onCopy: defuseProxies(jest.fn().mockName("onCopy"))
}))

describe("onAssign()", () => {
beforeEach(reset)
useSharedTests(() => onAssign)
describe("when draft is an object", () => {
test("assign", () => {
produce({a: 0, b: 0, c: 0}, s => {
s.a++
s.c++
})
expectCalls(onAssign)
})
test("assign (no change)", () => {
produce({a: 0}, s => {
s.a = 0
})
expect(onAssign).not.toBeCalled()
})
test("delete", () => {
produce({a: 1}, s => {
delete s.a
})
expect(onAssign).not.toBeCalled()
})
test("nested assignments", () => {
produce({a: {b: {c: 1, d: 1, e: 1}}}, s => {
const {b} = s.a
b.c = 2
delete b.d
b.e = 1 // no-op
})
expectCalls(onAssign)
})
})
describe("when draft is an array", () => {
test("assign", () => {
produce([1], s => {
s[0] = 0
})
expectCalls(onAssign)
})
test("push", () => {
produce([], s => {
s.push(4)
})
expectCalls(onAssign)
})
test("pop", () => {
produce([1], s => {
s.pop()
})
expect(onAssign).not.toBeCalled()
})
test("unshift", () => {
produce([1], s => {
s.unshift(0)
})
expectCalls(onAssign)
})
test("length = 0", () => {
produce([1], s => {
s.length = 0
})
expect(onAssign).not.toBeCalled()
})
test("splice (length += 1)", () => {
produce([1, 2, 3], s => {
s.splice(1, 1, 0, 0)
})
expectCalls(onAssign)
})
test("splice (length += 0)", () => {
produce([1, 2, 3], s => {
s.splice(1, 1, 0)
})
expectCalls(onAssign)
})
test("splice (length -= 1)", () => {
produce([1, 2, 3], s => {
s.splice(0, 2, 6)
})
expectCalls(onAssign)
})
})
describe("when a draft is moved into a new object", () => {
it("is called in the right order", () => {
const calls = []
onAssign.mockImplementation((_, prop) => {
calls.push(prop)
})
produce({a: {b: 1, c: {}}}, s => {
s.a.b = 0
s.a.c.d = 1
s.x = {y: {z: s.a}}
delete s.a
})
// Sibling properties use enumeration order, which means new
// properties come last among their siblings. The deepest
// properties always come first in their ancestor chain.
expect(calls).toEqual(["b", "d", "c", "x"])
})
})
})

describe("onDelete()", () => {
beforeEach(reset)
useSharedTests(() => onDelete)
describe("when draft is an object -", () => {
test("delete", () => {
produce({a: 1, b: 1, c: 1}, s => {
delete s.a
delete s.c
})
expectCalls(onDelete)
})
test("delete (no change)", () => {
produce({}, s => {
delete s.a
})
expect(onDelete).not.toBeCalled()
})
test("nested deletions", () => {
produce({a: {b: {c: 1}}}, s => {
delete s.a.b.c
})
expectCalls(onDelete)
})
})
describe("when draft is an array -", () => {
test("pop", () => {
produce([1], s => {
s.pop()
})
expectCalls(onDelete)
})
test("length = 0", () => {
produce([1], s => {
s.length = 0
})
expectCalls(onDelete)
})
test("splice (length -= 1)", () => {
produce([1, 2, 3], s => {
s.splice(0, 2, 6)
})
expectCalls(onDelete)
})
})
})

describe("onCopy()", () => {
beforeEach(reset)
useSharedTests(() => onCopy)
it("is called in the right order", () => {
const calls = []
onCopy.mockImplementation(s => {
calls.push(s.base)
})
const base = {a: {b: {c: 1}}}
produce(base, s => {
delete s.a.b.c
})
expect(calls).toShallowEqual([base.a.b, base.a, base])
})
})

function useSharedTests(getHook) {
it("is called before the parent is frozen", () => {
const hook = getHook()
hook.mockImplementation(s => {
// Parent object must not be frozen.
expect(Object.isFrozen(s.base)).toBeFalsy()
})
produce({a: {b: {c: 0}}}, s => {
if (hook == onDelete) delete s.a.b.c
else s.a.b.c = 1
})
expect(hook).toHaveBeenCalledTimes(hook == onDelete ? 1 : 3)
})
}
})

// Produce a snapshot of the hook arguments (minus any draft state).
function expectCalls(hook) {
expect(
hook.mock.calls.map(call => {
return call.slice(1)
})
).toMatchSnapshot()
}

// For defusing draft proxies.
function defuseProxies(fn) {
return Object.assign((...args) => {
expect(args[0].finalized).toBeTruthy()
args[0].draft = args[0].drafts = null
fn(...args)
}, fn)
}

expect.extend({
toShallowEqual(received, expected) {
const match = matchers.toBe(received, expected)
return match.pass || !received || typeof received !== "object"
? match
: !Array.isArray(expected) ||
(Array.isArray(received) && received.length === expected.length)
? matchers.toEqual(received, expected)
: match
}
})

0 comments on commit ab05744

Please sign in to comment.