UNPKG

@dhmk/zustand-lens

Version:

Lens support for zustand

700 lines (699 loc) 26.1 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); var vanilla_1 = require("zustand/vanilla"); var middleware_1 = require("zustand/middleware"); var immer_1 = require("zustand/middleware/immer"); var immer_2 = require("immer"); var _1 = require("./"); describe("createLens", function () { it("returns a scoped set/get pair", function () { var store = (0, vanilla_1.createStore)(function (set, get) { var _a = (0, _1.createLens)(set, get, ["deeply", "nested"]), _set = _a[0], _get = _a[1]; return { deeply: { nested: { id: 123, prop: "abc", }, }, getter: _get, partial: function () { return _set({ prop: "def" }); }, partialFn: function () { return _set(function (old) { return ({ prop: old.prop + "-def" }); }); }, replace: function () { return _set({ prop: "qwe" }, true); }, replaceFn: function () { return _set(function (old) { return ({ id: 456, prop: old.prop + "-qwe" }); }, true); }, }; }); expect(store.getState().getter()).toEqual({ id: 123, prop: "abc" }); store.getState().partial(); expect(store.getState().getter()).toEqual({ id: 123, prop: "def" }); store.getState().partialFn(); expect(store.getState().getter()).toEqual({ id: 123, prop: "def-def" }); store.getState().replace(); expect(store.getState().getter()).toEqual({ prop: "qwe" }); store.getState().replaceFn(); expect(store.getState().getter()).toEqual({ id: 456, prop: "qwe-qwe" }); }); it("takes `path` as `string | string[]`", function () { var store = { subA: { id: 123, value: "abc", }, subB: { nested: { id: 456, value: "abc", }, }, }; var set = function (x) { return (store = Object.assign({}, store, x(store))); }; var get = function () { return store; }; var _a = (0, _1.createLens)(set, get, "subA"), aSet = _a[0], aGet = _a[1]; aSet({ value: "def" }); expect(aGet()).toEqual({ id: 123, value: "def" }); var _b = (0, _1.createLens)(set, get, ["subB", "nested"]), bSet = _b[0], bGet = _b[1]; bSet({ value: "def" }); expect(bGet()).toEqual({ id: 456, value: "def" }); }); it("passes rest arguments to parent setter", function () { var store = { subA: { id: 123, value: "abc", }, }; var set = jest.fn(function (partial, replace, arg1, arg2, arg3) { store = Object.assign({}, store, partial(store)); }); var get = function () { return store; }; var aSet = (0, _1.createLens)(set, get, "subA")[0]; aSet({ value: "def" }, true, "arg1", "arg2", "arg3"); expect(set).toBeCalledWith(expect.any(Function), false, "arg1", "arg2", "arg3"); set.mockClear(); aSet(function () { return ({ value: "def" }); }, true, "arg3", "arg4", "arg5"); expect(set).toBeCalledWith(expect.any(Function), false, "arg3", "arg4", "arg5"); }); it("applies `postprocess` function", function () { var _a; var store = { subA: (_a = { id: 123, value: "abc" }, _a[_1.meta] = { postprocess: function (state, prevState) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } expect(args).toEqual(["arg1", "arg2", "arg3"]); return { value: prevState.value + state.value, }; }, }, _a), }; var set = function (partial) { store = Object.assign({}, store, partial(store)); }; var get = function () { return store; }; var _b = (0, _1.createLens)(set, get, "subA"), aSet = _b[0], aGet = _b[1]; aSet({ value: "def" }, false, "arg1", "arg2", "arg3"); expect(aGet()).toMatchObject({ id: 123, value: "abcdef" }); var immerSet = function (partial) { return set(function (state) { return (0, immer_2.produce)(state, partial); }); }; var _c = (0, _1.createLens)(immerSet, get, "subA"), iSet = _c[0], iGet = _c[1]; iSet({ value: "ghi" }, false, "arg1", "arg2", "arg3"); expect(iGet()).toMatchObject({ id: 123, value: "abcdefghi" }); }); }); describe("immer", function () { it("works out-of-the-box", function () { var store = (0, vanilla_1.createStore)()((0, immer_1.immer)((0, _1.withLenses)(function () { return ({ subA: (0, _1.lens)(function (set) { return ({ id: 123, name: "subA", changeName: function () { return set(function (draft) { expect((0, immer_2.isDraft)(draft)).toBeTruthy(); draft.name = "changed"; }); }, }); }), subB: (0, _1.lens)(function (set) { return ({ id: 234, name: "subB", changeName: function () { return set(function (draft) { expect((0, immer_2.isDraft)(draft)).toBeTruthy(); draft.name = "changed"; }); }, }); }), }); }))); expect(store.getState().subA.name).toBe("subA"); var s1 = store.getState(); store.getState().subA.changeName(); expect(store.getState()).not.toBe(s1); expect(store.getState().subA.name).toBe("changed"); expect(store.getState().subB.name).toBe("subB"); var s2 = store.getState(); store.getState().subB.changeName(); expect(store.getState()).not.toBe(s2); expect(store.getState().subB.name).toBe("changed"); }); }); describe("lens", function () { it("calls creator function with (set, get, api, context)", function () { var store = (0, vanilla_1.createStore)((0, _1.withLenses)(function (storeSet, storeGet, storeApi) { return ({ sub: (0, _1.lens)(function (set, get, api, ctx) { expect(set).toEqual(expect.any(Function)); expect(get).toEqual(expect.any(Function)); expect(api).toBe(storeApi); expect(ctx.rootPath).toEqual(["sub"]); expect(ctx.relativePath).toEqual(["sub"]); expect(ctx.set).toBe(set); expect(ctx.get).toBe(get); expect(ctx.api).toBe(api); return { name: "", test: function () { set({ name: "ok-1" }); }, nested: { even: { deeper: (0, _1.lens)(function (set, get, api, ctx) { expect(set).toEqual(expect.any(Function)); expect(get).toEqual(expect.any(Function)); expect(api).toBe(storeApi); expect(ctx.rootPath).toEqual([ "sub", "nested", "even", "deeper", ]); expect(ctx.relativePath).toEqual([ "nested", "even", "deeper", ]); expect(ctx.set).toBe(set); expect(ctx.get).toBe(get); expect(ctx.api).toBe(api); return { name: "", test: function () { set({ name: "ok-2" }); }, }; }), }, }, }; }), }); })); store.getState().sub.test(); expect(store.getState().sub.name).toBe("ok-1"); store.getState().sub.nested.even.deeper.test(); expect(store.getState().sub.nested.even.deeper.name).toBe("ok-2"); }); it("doesn`t throw an error if created outside `withLenses` function", function () { var todosSlice = (0, _1.lens)(function () { return ({ todos: [1], }); }); var usersSlice = (0, _1.lens)(function () { return ({ users: [2], }); }); var slices = { todosSlice: todosSlice, usersSlice: usersSlice, }; var useStore = (0, vanilla_1.createStore)((0, _1.withLenses)(slices)); expect(useStore.getState().todosSlice.todos).toEqual([1]); expect(useStore.getState().usersSlice.users).toEqual([2]); }); }); describe("withLenses", function () { it("also accepts an object config", function () { var store = (0, vanilla_1.createStore)((0, _1.withLenses)({ test: (0, _1.lens)(function (set) { return ({ name: "abc", setName: function () { set({ name: "def" }); }, }); }), })); expect(store.getState()).toEqual({ test: { name: "abc", setName: expect.any(Function) }, }); store.getState().test.setName(); expect(store.getState().test.name).toEqual("def"); }); it("preserves Symbols", function () { var _a; var symbol = Symbol(); var initializer = (_a = { test: (0, _1.lens)(function () { var _a; return (_a = {}, _a[symbol] = true, _a); }) }, _a[symbol] = true, _a); var store = (0, vanilla_1.createStore)()((0, _1.withLenses)(initializer)); expect(store.getState()[symbol]).toEqual(true); expect(store.getState().test[symbol]).toEqual(true); }); it("checks lenses api types", function () { var one = (0, _1.lens)(function () { return ({ id: 1, }); }); var two = (0, _1.lens)(function () { return ({ id: 1, }); }); var store = (0, vanilla_1.createStore)()((0, _1.withLenses)({ one: one, // @ts-expect-error two: two, })); var store2 = (0, vanilla_1.createStore)()( // @ts-expect-error (0, _1.withLenses)(function () { return ({ one: one, two: two, }); })); var store3 = (0, vanilla_1.createStore)((0, _1.withLenses)({ one: one, // @ts-expect-error two: two, })); var store4 = (0, vanilla_1.createStore)( // @ts-expect-error (0, _1.withLenses)(function () { return ({ one: one, two: two, }); })); }); it("applies `postprocess` function", function () { var _a; var sub = (0, _1.lens)(function (set) { return ({ value: 1, test: function () { set({ value: 2 }); }, }); }); var store = (0, vanilla_1.createStore)((0, _1.withLenses)((_a = { sub: sub }, _a[_1.meta] = { postprocess: function (state, prevState) { expect(state.sub.value).toBe(2); expect(prevState.sub.value).toBe(1); }, }, _a))); store.getState().sub.test(); expect.assertions(2); }); it("uses `setter` function", function () { var _a; var cb = jest.fn(); var store = (0, vanilla_1.createStore)((0, _1.withLenses)((_a = { nested: (0, _1.lens)(function () { return ({ deep: { slice: (0, _1.lens)(function (set) { var _a; return (_a = { id: 123, test: function () { set({ id: 456 }); } }, _a[_1.meta] = { setter: function (set, ctx) { cb(1); set(); }, }, _a); }), }, }); }) }, _a[_1.meta] = { setter: function (set, ctx) { cb(2); set(); }, }, _a))); store.getState().nested.deep.slice.test(); expect(cb).toBeCalledTimes(2); expect(cb).nthCalledWith(1, 1); expect(cb).nthCalledWith(2, 2); }); }); it("namedSetter", function () { var spy = jest.fn(); var _ = null; var state = (0, _1.namedSetter)(function (set) { return ({ name: "abc", setName: function () { set({ name: "def" }, "setName"); }, }); })(spy, _, _, _); state.setName(); expect(spy).toBeCalledWith({ name: "def" }, undefined, "setName"); }); describe("atomic", function () { it("makes `setter`s atomic", function () { // no atomic var store1 = (0, vanilla_1.createStore)()((0, _1.withLenses)(function (set, get) { var _a; return (_a = { sub: (0, _1.lens)(function (set) { return ({ id: 123, test: function () { set({ id: 456 }); }, }); }), flag: false, test: function () { set({ flag: true }); } }, _a[_1.meta] = { setter: function (set) { set(); if (!get().flag) get().test(); }, }, _a); })); var cb1 = jest.fn(); store1.subscribe(cb1); store1.getState().sub.test(); expect(cb1).toBeCalledTimes(2); // atomic var store2 = (0, vanilla_1.createStore)()((0, _1.atomic)((0, _1.withLenses)(function (set, get) { var _a; return (_a = { sub: (0, _1.lens)(function (set) { return ({ id: 123, test: function () { set({ id: 456 }); }, }); }), flag: false, test: function () { set({ flag: true }); } }, _a[_1.meta] = { setter: function (set) { set(); if (!get().flag) get().test(); }, }, _a); }))); var cb2 = jest.fn(); store2.subscribe(cb2); store2.getState().sub.test(); expect(cb2).toBeCalledTimes(1); }); it("enables context`s `atomic` function", function () { var store = (0, vanilla_1.createStore)((0, _1.atomic)((0, _1.withLenses)({ sub: (0, _1.lens)(function (set, get, _api, ctx) { return ({ id: 123, name: "abc", nested: (0, _1.lens)(function (set) { return ({ id: 123, setId: function (id) { set({ id: id }); }, }); }), test: function () { ctx.atomic(function () { set({ id: 456 }); get().nested.setId(get().id); set({ name: "def" }); }); }, }); }), }))); var cb = jest.fn(); store.subscribe(cb); store.getState().sub.test(); expect(cb).toBeCalledTimes(1); expect(store.getState().sub).toMatchObject({ id: 456, name: "def", nested: { id: 456, }, }); }); }); it("subscribe", function () { var store = (0, vanilla_1.createStore)(function (set) { return ({ id: 123, name: "abc", test1: function () { set({ id: 456, name: "def", }); }, test2: function () { set({ id: 456, // same value name: "abcdef", }); }, test3: function () { set({ id: 789, }); }, }); }); var cb1 = jest.fn(); var cb2 = jest.fn(); var unsub = (0, _1.subscribe)(store, function (s) { return s.id; }, cb1, { fireImmediately: true }); (0, _1.subscribe)(store, function (s) { return s.name; }, cb2, { equalityFn: function (a, b) { return a.length === b.length; }, }); expect(cb1).toBeCalledTimes(1); expect(cb2).toBeCalledTimes(0); store.getState().test1(); expect(cb1).lastCalledWith(456, 123); expect(cb2).toBeCalledTimes(0); store.getState().test2(); expect(cb1).toBeCalledTimes(2); expect(cb2).lastCalledWith("abcdef", "abc"); unsub(); store.getState().test3(); expect(cb1).toBeCalledTimes(2); expect(cb2).toBeCalledTimes(1); }); it("watch", function () { var cb1 = jest.fn(); var cb2 = jest.fn(); var store = (0, vanilla_1.createStore)((0, _1.withLenses)({ sub: (0, _1.lens)(function (set) { var _a; return (_a = { id: 123, test: function () { set({ id: 456 }); }, nested: (0, _1.lens)(function (set) { var _a; return (_a = { name: "abc", test: function () { set({ name: "def" }); } }, _a[_1.meta] = { setter: (0, _1.watch)(function (s) { return s; }, cb1, { fireImmediately: true }) }, _a); }) }, _a[_1.meta] = { setter: (0, _1.watch)(function (s) { return s.id; }, cb2) }, _a); }), })); expect(cb1).toBeCalledTimes(1); expect(cb2).toBeCalledTimes(0); store.getState().sub.nested.test(); expect(cb1).toBeCalledTimes(2); expect(cb2).toBeCalledTimes(0); store.getState().sub.test(); expect(cb2).toBeCalledTimes(1); store.getState().sub.test(); expect(cb2).toBeCalledTimes(1); }); it("combineWatchers", function () { var cbId = jest.fn(); var cbName = jest.fn(); var store = (0, vanilla_1.createStore)()((0, _1.withLenses)(function (set) { var _a; return (_a = { id: 1, setId: function (x) { set({ id: x }); }, name: "a", setName: function (x) { set({ name: x }); } }, _a[_1.meta] = { setter: (0, _1.combineWatchers)((0, _1.watch)(function (s) { return s.id; }, cbId), (0, _1.watch)(function (s) { return s.name; }, cbName)), }, _a); })); store.getState().setId(2); store.getState().setId(3); store.getState().setId(3); expect(cbId).toBeCalledTimes(2); store.getState().setName("a"); store.getState().setName("b"); store.getState().setName("c"); expect(cbName).toBeCalledTimes(2); }); it("persistOptions", function () { var _storage; var store = (0, vanilla_1.createStore)()((0, middleware_1.persist)(function (set) { return ({ subA: __assign({ id: 1 }, (0, _1.persistOptions)({ load: function (x) { return (__assign(__assign({}, x), { id: x.id * 10 })); }, })), subB: __assign({ name: "a" }, (0, _1.persistOptions)({ save: function (x) { return (__assign(__assign({}, x), { name: x.name.repeat(2) })); }, })), test: function () { set((0, immer_2.produce)(function (s) { s.subA.id = 2; s.subB.name = "b"; })); }, }); }, __assign({ name: "test", storage: { getItem: function () { return _storage; }, setItem: function (_, v) { return (_storage = v); }, removeItem: function () { }, } }, _1.persistOptions))); expect(store.getState()).toMatchObject({ subA: { id: 10, }, subB: { name: "a", }, }); store.getState().test(); expect(store.getState()).toMatchObject({ subA: { id: 2, }, subB: { name: "b", }, }); store.persist.rehydrate(); expect(store.getState()).toMatchObject({ subA: { id: 20, }, subB: { name: "bb", }, }); }); describe("lens meta type tests", function () { it("with explicitly typed store", function () { var _a; // need to explicitly add [meta] to avoid error (0, vanilla_1.createStore)()((0, _1.withLenses)((_a = { id: 123, name: "test", nested: (0, _1.lens)(function (set) { var _a; return (_a = { text: "test", isOk: true, toggle: function () { set({ isOk: true, }); } }, _a[_1.meta] = { postprocess: function (state, prevState) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } }, setter: function (set, ctx) { }, }, _a); }) }, _a[_1.meta] = { postprocess: function (state) { }, setter: function (set, ctx) { }, }, _a))); }); it("with implicitly typed store", function () { var _a; (0, vanilla_1.createStore)((0, _1.withLenses)((_a = { id: 123, name: "test", nested: (0, _1.lens)(function (set) { var _a; return (_a = { text: "test", isOk: true, toggle: function () { set({ isOk: true, }); } }, _a[_1.meta] = { postprocess: function (state, prevState) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } }, setter: function (set, ctx) { }, }, _a); }) }, _a[_1.meta] = { postprocess: function (state) { }, setter: function (set, ctx) { }, }, _a))); }); }); it("immer@10 bug", function () { var store = (0, vanilla_1.createStore)((0, immer_1.immer)((0, _1.withLenses)({ sub: (0, _1.lens)(function (set) { var _a; return (_a = { id: 1, test: function () { set(function (s) { return ({ id: s.id + 1 }); }); } }, _a[_1.meta] = { postprocess: function (state) { }, }, _a); }), }))); store.getState().sub.test(); // 1st call - ok store.getState().sub.test(); // 2nd call - error expect(store.getState().sub.id).toBe(3); });