@dhmk/zustand-lens
Version:
Lens support for zustand
700 lines (699 loc) • 26.1 kB
JavaScript
;
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);
});