UNPKG

primitate

Version:

Managing your app state by two methods

483 lines (389 loc) 15.8 kB
/// <reference path="../node_modules/@types/jasmine/index.d.ts" /> const { Primitate } = require("../lib/Primitate"); function spyOnAll(obj) { Object.keys(obj).forEach( key => spyOn(obj, key).and.callThrough() ); } const Err_NullUndefined = "Primitate cannot include null or undefined" const Err_TypeofObject = "Primitate cannot include typeof 'object' except [object Object] and [object Array]"; function createErrMsgTypeDiff(Value, InitialValue) { return ["Primitate not allow changing the state structure." ,`Your value '${Value}' must be typeof '${typeof InitialValue}'`].join(" "); } function identity(x) { return x; } function increment(x) { return x + 1; } function add(x, y) { return x + y; } describe("InitialState", () => { function expectNotThrow(InitialState) { expect( () => Primitate(InitialState)).not.toThrow(); } function expectThrowError(InitialState, ErrMsg) { expect( () => Primitate(InitialState)).toThrowError(ErrMsg); } it("is a primitive value", () => expectNotThrow(0) ); it("is a [object Array]", () => { expectNotThrow([]); expectNotThrow([1, 2]); expectNotThrow([[1, 2, 3], [1, 2, 3]]); expectNotThrow([[[1, 2, 3], [1, 2, 3]], [[1, 2, 3, 4], [1, 3, 4, 5]]]); }); it("is a [object Object]", () => { expectNotThrow({}); expectNotThrow({ count: 0 }); }); it("is a complex [object Object]", () => { const InitialState = { counter: [{count: 0}, {count: 0}] , foo: { foo1: "", foo2: { foo3: ["Hello", "Hello"] } } , bar: { bar1: 0 } } expectNotThrow(InitialState); }); it("cannot contains typeof object except [object Object] and [object Array]", () => { // error typeof object function eto(InitialState) { expectThrowError(InitialState, Err_TypeofObject); } eto(new Date()); eto([1, 2, new Date()]); eto({ date: new Date()}); eto({ foo: { bar: [new Date(), new Date()]} }); }); it("cannot contains null or undefined", () => { // error null or undefined function enu(InitialState) { expectThrowError(InitialState, Err_NullUndefined); } enu(); enu([1, 2, undefined]); enu([[1, 2, 3], [1, 2, undefined]]); enu({ foo: { bar: [1, undefined] } }); enu({ foo: { bar: { yoo: undefined } }, foo2: {} }) enu({ foo: { bar: [{ count: 0 }, { count: undefined }] } }); }); it("cannot contains an array contains different type", () => { expectThrowError([1, "Hello"], createErrMsgTypeDiff("Hello", 0)); expectThrowError({ foo: "Hello", bar: [1, 1, "Hello"]}, createErrMsgTypeDiff("Hello", 0)); }); }); describe("Action", () => { describe("passes", () => { it("previous state", () => { const Counter = Primitate(0); const results = [0, 1, 2, 3]; const increment$ = Counter.createAction( count => { expect(count).toBe(results.shift()); return increment(count); }); increment$(); increment$(); increment$(); increment$(); expect(results.length).toBe(0); }); it("the pick returns value", () => { const Counter = Primitate({ counter: { count: 0 } }); const increment$ = Counter.createAction(increment, s => s.counter.count); expect(increment$()).toBe(1); expect(increment$()).toBe(2); }); it("next value", () => { const Counter = Primitate(0); const add$ = Counter.createAction(add); expect(add$(10)).toBe(10); expect(add$(5)).toBe(15); }); it("initial state", () => { const Counter = Primitate(0); const InitialStates = [0, 0, 0]; const incremenet$ = Counter.createAction( (count, next, initialState) => { expect(InitialStates.shift()).toBe(initialState); return increment(count); }); incremenet$(); incremenet$(); incremenet$(); expect(InitialStates.length).toBe(0); }); it("state tree", () => { const Counter = Primitate({ counter: { count: 0 } }); const states = [ { counter: { count: 0 } }, { counter: { count: 1 } }, { counter: { count: 2 } } ]; const increment$ = Counter.createAction( (count, next, ini, stateTree) => { expect(states.shift()).toEqual(stateTree); return increment(count); }, s => s.counter.count ); increment$(); increment$(); increment$(); expect(states.length).toBe(0); }); it("state as state tree if the state is primitive value or [object Array]", () => { const Source_Funcs = { increment(count, next, ini, stateTree) { expect(count).toBe(stateTree); return increment(count); } , twice(nums, next, ini, stateTree) { expect(nums).toBe(stateTree); return nums.map( num => num * 2 ); } } spyOnAll(Source_Funcs); const Counter = Primitate(0); const incremenet$ = Counter.createAction(Source_Funcs.increment); incremenet$(); incremenet$(); expect(Source_Funcs.increment).toHaveBeenCalledTimes(2); const Nums = Primitate([1, 2, 3]); const twice$ = Nums.createAction(Source_Funcs.twice); twice$(); twice$(); expect(Source_Funcs.twice).toHaveBeenCalledTimes(2); }); }); describe("returns", () => { it("the same value as source function returns", () => { const SourceResult = increment(0); const Counter = Primitate(0); const incremnt$ = Counter.createAction(increment); const Result = incremnt$(); expect(Result).toBe(SourceResult); }); it("previous state if the state did not changed", () => { const Counter = Primitate({ counter: { count: 0 } }); const add$ = Counter.createAction( (count, next) => { return { count: next }; }, s => s.counter ); expect(Object.isFrozen(add$(0))).toBe(true); }); }); describe("changes the state that type of is", () => { it("number", () => { const Counter = Primitate(0); const incremenet$ = Counter.createAction(increment); expect(incremenet$()).toBe(1); expect(incremenet$()).toBe(2); }); it("string", () => { const Memo = Primitate(""); const memo$ = Memo.createAction( (p, n) => n ); expect(memo$("Hello")).toBe("Hello"); expect(memo$("See you")).toBe("See you"); }); it("empty array", () => { const Users = Primitate([]); const addUser$ = Users.createAction( (p, n) => p.concat(n) ); const user1 = { name: "Oda", email: "xxx" }; const user2 = { name: "Uesugi", email: "xxx" }; expect(addUser$(user1)).toEqual([user1]); expect(addUser$(user2)).toEqual([user1, user2]); expect(Users.getCurrentState()).toEqual([user1, user2]); }); }); describe("is safety to manage the state because", () => { it("passes deep freezed value", () => { const Source_Funcs = { act(prevState, next, ini, stateTree) { expect(Object.isFrozen(stateTree)).toBe(true); expect(Object.isFrozen(prevState.foo2)).toBe(true); expect(Object.isFrozen(prevState.foo3)).toBe(true); expect(Object.isFrozen(prevState.foo3.foo6)).toBe(true); expect(Object.isFrozen(stateTree.bar1.bar2)).toBe(true); return { foo2: { foo4: increment(prevState.foo2.foo4) } , foo3: { foo5: increment(prevState.foo3.foo5) , foo6: { foo7: increment(prevState.foo3.foo6.foo7)} } }; } } spyOn(Source_Funcs, "act").and.callThrough(); const P = Primitate({ foo1: { foo2: { foo4: 0 }, foo3: { foo5: 0, foo6: { foo7: 0 } } } , bar1: { bar2: { bar3: 0 } } }); const act$ = P.createAction(Source_Funcs.act, s => s.foo1); act$(); act$(); act$(); expect(Source_Funcs.act).toHaveBeenCalledTimes(3); }); it("returns deep cloned value.", () => { function incrementCount(counter) { return { count: counter.count + 1 }; } const Counter = Primitate({ counter: { count: 0 } }); const increment$ = Counter.createAction(incrementCount, s => s.counter ); const result1 = increment$(); result1.count = 10000; const result2 = increment$(); expect(result1).toEqual({ count: 1 }); expect(result2).toEqual({ count: 2 }); }); describe("throw errors when", () => { it("it returns null or undefined value contained", () => { // primitive const Counter = Primitate(0); const incremenet$ = Counter.createAction( () => undefined); expect(() => incremenet$()).toThrowError(Err_NullUndefined); // array const Nums = Primitate([0, 0, 0]); const act$ = Nums.createAction( () => [0, 0, undefined] ); expect(() => act$()).toThrowError(Err_NullUndefined); // object const Counter2 = Primitate({ counter: { count: 0 } }); const incremenet$2 = Counter2.createAction( () => ({ count: undefined }), s => s.counter ); expect(() => incremenet$2()).toThrowError(Err_NullUndefined); }); it("it returns a defferent type of value", () => { // primitive const Counter = Primitate(0); const act$ = Counter.createAction( () => "Hello" ); expect(() => act$()).toThrowError(createErrMsgTypeDiff("Hello", 0)); // array const Nums = Primitate([0, 1, 2]); const act$2 = Nums.createAction( () => [0, 1, "Hello"]); expect(() => act$2()).toThrowError(createErrMsgTypeDiff("Hello", 0)); // object const Counter2 = Primitate({ counter: { count: 0 } }); const act$3 = Counter2.createAction( count => { return { count: "GoodNight" }; }, s => s.counter ); expect(() => act$3()).toThrowError(createErrMsgTypeDiff("GoodNight", 0)); }); it("object has extra key than the initial state.", () => { const Counter = Primitate({ counter: { count: 0 } }); const act$ = Counter.createAction(() => ({ count: 1, msg: "Opps" }), s => s.counter); expect(() => act$()).toThrowError("Cannot change the state structure. You have an extra key 'msg'"); }); it("object lacks key than the initial state.", () => { const Counter = Primitate({ foo: { foo1: 0, foo2: 0 } }); const act$ = Counter.createAction(() => ({ foo1: 1 }), s => s.foo ); expect(() => act$()).toThrowError("Cannot change the state structure. You lack key 'foo2'"); }); }); }); it([ "does not emit itself if" , "previous Action did not change the state" , "and current Action gets same arugument as previous it" ].join(" "), () => { const Source_Funcs = { add(count, next) { return count + next; } , multiple(nums, coefficient) { return nums.map( num => num * coefficient )} , add2(counter, next) { return { count: counter.count + next } } } spyOnAll(Source_Funcs); // primitive const Counter = Primitate(0); const add$ = Counter.createAction(Source_Funcs.add); add$(0); add$(0); add$(0); expect(Source_Funcs.add).toHaveBeenCalledTimes(1); // array const Nums = Primitate([3, 1, 2]); const multiple$ = Nums.createAction(Source_Funcs.multiple); multiple$(1); multiple$(1); multiple$(1); expect(Source_Funcs.multiple).toHaveBeenCalledTimes(1); // object const Counter2 = Primitate({ counter: { count: 0 } }); const add$2 = Counter2.createAction(Source_Funcs.add2, s => s.counter ); add$2(0); add$2(0); add$2(0); expect(Source_Funcs.add2).toHaveBeenCalledTimes(1); }); }); describe("Subscribe", () => { it ("emits the listener when state was changed", done => { const Source_Funcs = { func() {} }; spyOnAll(Source_Funcs); const Counter = Primitate(0); Counter.subscribe(Source_Funcs.func); setTimeout( () => { expect(Source_Funcs.func).toHaveBeenCalledTimes(0); done(); }, 30); }); it("emits the listener in async if default", done => { const Source_Funcs = { func() { expect(Source_Funcs.func).toHaveBeenCalledTimes(1); done(); } }; spyOnAll(Source_Funcs); const Counter = Primitate(0); const incremenet$ = Counter.createAction(increment); Counter.subscribe(Source_Funcs.func); incremenet$(); expect(Source_Funcs.func).toHaveBeenCalledTimes(0); }); it("emits listener in sync if set false for third arguments", () => { const Source_Funcs = { func() {} }; spyOnAll(Source_Funcs); const Counter = Primitate(0); const incremenet$ = Counter.createAction(increment); Counter.subscribe(Source_Funcs.func, undefined, false); incremenet$(); incremenet$(); incremenet$(); expect(Source_Funcs.func).toHaveBeenCalledTimes(3); }); it("passes current state deep freezed", done => { const Counter = Primitate({ counter: { a: { count: 0 }, b: { count: 0 } } }); const incremenet$ = Counter.createAction(increment, s => s.counter.b.count); Counter.subscribe( s => { expect(Object.isFrozen(s.counter.a)).toBe(true); expect(Object.isFrozen(s.counter.b)).toBe(true); done(); }, [s => s.counter]); incremenet$(); }); it("returns a function to unsubscribe", done => { const Source_Funcs = { listener1() {} , listener2() { expect(Source_Funcs.listener1).toHaveBeenCalledTimes(1); expect(Source_Funcs.listener2).toHaveBeenCalledTimes(1); done(); } }; spyOnAll(Source_Funcs); const Counter = Primitate(0); const increment$ = Counter.createAction(increment); const unsubscribe1 = Counter.subscribe(Source_Funcs.listener1); const unsubscribe2 = Counter.subscribe(Source_Funcs.listener2); increment$(); unsubscribe1(); increment$(); }); it("set up for listening depth", done => { const Source_Funcs = { Lis() {}, Lis_Foo1() {}, Lis_Foo2() {} , Lis_Foo3() {}, Lis_Foo4() {}, Lis_Foo5() {} , Lis_Bar() {} }; spyOnAll(Source_Funcs); const Sample = Primitate({ foo: { foo1: 0, foo2: 0, foo3: { foo4: 0, foo5: 0 } }, bar: 0 }); const incFoo1$ = Sample.createAction( increment, s => s.foo.foo1 ); const incFoo2$ = Sample.createAction( increment, s => s.foo.foo2 ); const incFoo3$ = Sample.createAction( () => ({ foo4: 10, foo5: 0 }), s => s.foo.foo3); const incFoo4$ = Sample.createAction( increment, s => s.foo.foo3.foo4 ); const incFoo5$ = Sample.createAction( increment, s => s.foo.foo3.foo5 ); Sample.subscribe( Source_Funcs.Lis, [s => s.foo] ); Sample.subscribe( Source_Funcs.Lis_Foo1, [s => s.foo.foo1]); Sample.subscribe( Source_Funcs.Lis_Foo2, [s => s.foo.foo2]); Sample.subscribe( Source_Funcs.Lis_Foo3, [s => s.foo.foo3]); Sample.subscribe( Source_Funcs.Lis_Foo4, [s => s.foo.foo3.foo4]); Sample.subscribe( Source_Funcs.Lis_Foo5, [s => s.foo.foo3.foo5]); Sample.subscribe( Source_Funcs.Lis_Bar, [s => s.bar]); setTimeout(incFoo1$, 10); // foo foo1 setTimeout(incFoo2$, 20); // foo foo2 setTimeout(incFoo3$, 30); // foo foo3 foo4 foo5 setTimeout(incFoo4$, 40); // foo foo3 foo4 setTimeout(incFoo5$, 50); // foo foo3 foo5 setTimeout(() => { expect(Source_Funcs.Lis).toHaveBeenCalledTimes(5); expect(Source_Funcs.Lis_Foo1).toHaveBeenCalledTimes(1); expect(Source_Funcs.Lis_Foo2).toHaveBeenCalledTimes(1); expect(Source_Funcs.Lis_Foo3).toHaveBeenCalledTimes(3); expect(Source_Funcs.Lis_Foo4).toHaveBeenCalledTimes(2); expect(Source_Funcs.Lis_Foo5).toHaveBeenCalledTimes(2); expect(Source_Funcs.Lis_Bar).toHaveBeenCalledTimes(0); done(); }, 60); }); });