Skip to content

Files

This branch is 30 commits behind immerjs/immer:main.

__tests__

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Apr 5, 2023
Apr 5, 2023
May 9, 2023
Apr 6, 2023
Apr 5, 2023
Mar 23, 2023
Mar 23, 2023
Apr 5, 2023
Apr 5, 2023
Mar 23, 2023
Oct 22, 2022
Mar 24, 2023
Apr 5, 2023
Mar 24, 2023
Mar 23, 2023
Mar 25, 2023
Apr 5, 2023
Apr 5, 2023
Apr 5, 2023
Apr 5, 2023
Mar 23, 2023
Apr 5, 2023
Oct 23, 2019
Mar 20, 2021
"use strict"
import {
	produce,
	applyPatches,
	immerable,
	produceWithPatches,
	enableMapSet,
	enablePatches,
	setAutoFreeze
} from "../src/immer"

enableMapSet()
enablePatches()

describe("readme example", () => {
	it("works", () => {
		const baseState = [
			{
				todo: "Learn typescript",
				done: true
			},
			{
				todo: "Try immer",
				done: false
			}
		]

		const nextState = produce(baseState, draft => {
			draft.push({todo: "Tweet about it"})
			draft[1].done = true
		})

		// the new item is only added to the next state,
		// base state is unmodified
		expect(baseState.length).toBe(2)
		expect(nextState.length).toBe(3)

		// same for the changed 'done' prop
		expect(baseState[1].done).toBe(false)
		expect(nextState[1].done).toBe(true)

		// unchanged data is structurally shared
		expect(nextState[0]).toBe(baseState[0])
		// changed data not (dûh)
		expect(nextState[1]).not.toBe(baseState[1])
	})

	it("patches", () => {
		let state = {
			name: "Micheal",
			age: 32
		}

		// Let's assume the user is in a wizard, and we don't know whether
		// his changes should be updated
		let fork = state
		// all the changes the user made in the wizard
		let changes = []
		// all the inverse patches
		let inverseChanges = []

		fork = produce(
			fork,
			draft => {
				draft.age = 33
			},
			// The third argument to produce is a callback to which the patches will be fed
			(patches, inversePatches) => {
				changes.push(...patches)
				inverseChanges.push(...inversePatches)
			}
		)

		// In the mean time, our original state is updated as well, as changes come in from the server
		state = produce(state, draft => {
			draft.name = "Michel"
		})

		// When the wizard finishes (successfully) we can replay the changes made in the fork onto the *new* state!
		state = applyPatches(state, changes)

		// state now contains the changes from both code paths!
		expect(state).toEqual({
			name: "Michel",
			age: 33
		})

		// Even after finishing the wizard, the user might change his mind...
		state = applyPatches(state, inverseChanges)
		expect(state).toEqual({
			name: "Michel",
			age: 32
		})
	})

	it("can update set", () => {
		const state = {
			title: "hello",
			tokenSet: new Set()
		}

		const nextState = produce(state, draft => {
			draft.title = draft.title.toUpperCase()
			draft.tokenSet.add("c1342")
		})

		expect(state).toEqual({title: "hello", tokenSet: new Set()})
		expect(nextState).toEqual({
			title: "HELLO",
			tokenSet: new Set(["c1342"])
		})
	})

	it("can deep update map", () => {
		const state = {
			users: new Map([["michel", {name: "miche", age: 27}]])
		}

		const nextState = produce(state, draft => {
			draft.users.get("michel").name = "michel"
		})

		expect(state).toEqual({
			users: new Map([["michel", {name: "miche", age: 27}]])
		})
		expect(nextState).toEqual({
			users: new Map([["michel", {name: "michel", age: 27}]])
		})
	})

	it("supports immerable", () => {
		class Clock {
			constructor(hours = 0, minutes = 0) {
				this.hours = hours
				this.minutes = minutes
			}

			increment(hours, minutes = 0) {
				return produce(this, d => {
					d.hours += hours
					d.minutes += minutes
				})
			}

			toString() {
				return `${("" + this.hours).padStart(2, 0)}:${(
					"" + this.minutes
				).padStart(2, 0)}`
			}
		}
		Clock[immerable] = true

		const midnight = new Clock()
		const lunch = midnight.increment(12, 30)

		expect(midnight).not.toBe(lunch)
		expect(lunch).toBeInstanceOf(Clock)
		expect(midnight.toString()).toBe("00:00")
		expect(lunch.toString()).toBe("12:30")

		const diner = lunch.increment(6)

		expect(diner).not.toBe(lunch)
		expect(lunch).toBeInstanceOf(Clock)
		expect(diner.toString()).toBe("18:30")
	})

	test("produceWithPatches", () => {
		const result = produceWithPatches(
			{
				age: 33
			},
			draft => {
				draft.age++
			}
		)
		expect(result).toEqual([
			{
				age: 34
			},
			[
				{
					op: "replace",
					path: ["age"],
					value: 34
				}
			],
			[
				{
					op: "replace",
					path: ["age"],
					value: 33
				}
			]
		])
	})
})

test("Producers can update Maps", () => {
	setAutoFreeze(true)
	const usersById_v1 = new Map()

	const usersById_v2 = produce(usersById_v1, draft => {
		// Modifying a map results in a new map
		draft.set("michel", {name: "Michel Weststrate", country: "NL"})
	})

	const usersById_v3 = produce(usersById_v2, draft => {
		// Making a change deep inside a map, results in a new map as well!
		draft.get("michel").country = "UK"
		debugger
	})

	// We got a new map each time!
	expect(usersById_v2).not.toBe(usersById_v1)
	expect(usersById_v3).not.toBe(usersById_v2)
	// With different content obviously
	expect(usersById_v1).toMatchInlineSnapshot(`Map {}`)
	expect(usersById_v2).toMatchInlineSnapshot(`
		Map {
		  "michel" => {
		    "country": "NL",
		    "name": "Michel Weststrate",
		  },
		}
	`)
	expect(usersById_v3).toMatchInlineSnapshot(`
		Map {
		  "michel" => {
		    "country": "UK",
		    "name": "Michel Weststrate",
		  },
		}
	`)
	// The old one was never modified
	expect(usersById_v1.size).toBe(0)
	// And trying to change a Map outside a producers is going to: NO!
	expect(() => usersById_v3.clear()).toThrowErrorMatchingSnapshot()
})

test("clock class", () => {
	class Clock {
		[immerable] = true

		constructor(hour, minute) {
			this.hour = hour
			this.minute = minute
		}

		get time() {
			return `${this.hour}:${this.minute}`
		}

		tick() {
			return produce(this, draft => {
				draft.minute++
			})
		}
	}

	const clock1 = new Clock(12, 10)
	const clock2 = clock1.tick()
	expect(clock1.time).toEqual("12:10") // 12:10
	expect(clock2.time).toEqual("12:11") // 12:11
	expect(clock2).toBeInstanceOf(Clock)
})