import { useState } from "./state" import { State, LitLikeElement, InjectableState } from "./types" import { asUpdateableLitElement } from "./decorator" class StateExample { constructor(public value = "true") {} } describe("State get/set", () => { let state: State const litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn() } as unknown as LitLikeElement const initialState = { value: "bla", other: "blub" } beforeEach(() => { jest.resetAllMocks() state = useState(litElement, initialState) }) it("sets up the default state", () => { expect(state.get()).toEqual(initialState) expect(state.get()).not.toBe(initialState) }) it("sets the default state up correctly for different types", () => { expect(useState(litElement, "bla").get()).toBe("bla") expect(useState(litElement, 3).get()).toBe(3) }) describe("When the state is published without change", () => { const subscriber = jest.fn() let currentState: StateExample beforeEach(() => { currentState = state.get() state.subscribe(subscriber) state.set(currentState) }) it("does not update the state", () => { expect(state.get()).toBe(currentState) }) it("does not notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(0) }) it("does not refresh the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(0) }) }) describe("When state is injected", () => { const subscriber = jest.fn() let newState: StateExample = {value: "changed"} beforeEach(() => { state.subscribe(subscriber); (state as InjectableState).inject(newState) }) it("should change the state", () => { expect(state.get()).toBe(newState) }) it("should change it without notification", () => { expect(subscriber).not.toBeCalled() }) }) describe("When the state is changed", () => { const subscriber = jest.fn() beforeEach(() => { state.subscribe(subscriber) state.set({ value: "new" }) }) it("updates the state", () => { expect(state.get()).toEqual({ value: "new"}) }) it("notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(1) expect(subscriber).toBeCalledWith({ value: "new"}) }) it("refreshes the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(1) }) }) }) describe("State value", () => { let state: State const litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn() } as unknown as LitLikeElement const initialState = { value: "bla", other: "blub" } beforeEach(() => { jest.resetAllMocks() state = useState(litElement, initialState) }) it("sets up the default state", () => { expect(state.value).toEqual(initialState) expect(state.value).not.toBe(initialState) }) it("sets the default state up correctly for different types", () => { expect(useState(litElement, "bla").value).toBe("bla") expect(useState(litElement, 3).value).toBe(3) }) describe("When the state is published without change", () => { const subscriber = jest.fn() let currentState: StateExample beforeEach(() => { currentState = state.get() state.subscribe(subscriber) state.value = currentState }) it("does not update the state", () => { expect(state.value).toBe(currentState) }) it("does not notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(0) }) it("does not refresh the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(0) }) }) describe("When state is injected", () => { const subscriber = jest.fn() let newState: StateExample = {value: "changed"} beforeEach(() => { state.subscribe(subscriber); (state as InjectableState).inject(newState) }) it("should change the state", () => { expect(state.value).toBe(newState) }) it("should change it without notification", () => { expect(subscriber).not.toBeCalled() }) }) describe("When the state is changed", () => { const subscriber = jest.fn() beforeEach(() => { state.subscribe(subscriber) state.value = { value: "new" } }) it("updates the state", () => { expect(state.value).toEqual({ value: "new"}) }) it("notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(1) expect(subscriber).toBeCalledWith({ value: "new"}) }) it("refreshes the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(1) }) }) }) describe("State legacy retrieving", () => { let state: State const litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn() } as unknown as LitLikeElement const initialState = { value: "bla", other: "blub" } beforeEach(() => { jest.resetAllMocks() state = useState(litElement, initialState) }) it("sets up the default state", () => { expect(state.getState()).toEqual(initialState) expect(state.getState()).not.toBe(initialState) }) it("sets the default state up correctly for different types", () => { expect(useState(litElement, "bla").getState()).toBe("bla") expect(useState(litElement, 3).getState()).toBe(3) }) describe("When the state is published without change", () => { const subscriber = jest.fn() let currentState: StateExample beforeEach(() => { currentState = state.get() state.subscribe(subscriber) state.value = currentState }) it("does not update the state", () => { expect(state.getState()).toBe(currentState) }) it("does not notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(0) }) it("does not refresh the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(0) }) }) describe("When state is injected", () => { const subscriber = jest.fn() let newState: StateExample = {value: "changed"} beforeEach(() => { state.subscribe(subscriber); (state as InjectableState).inject(newState) }) it("should change the state", () => { expect(state.getState()).toBe(newState) }) it("should change it without notification", () => { expect(subscriber).not.toBeCalled() }) }) describe("When the state is changed", () => { const subscriber = jest.fn() beforeEach(() => { state.subscribe(subscriber) state.publish({ value: "new" }) }) it("updates the state", () => { expect(state.getState()).toEqual({ value: "new"}) }) it("notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(1) expect(subscriber).toBeCalledWith({ value: "new"}) }) it("refreshes the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(1) }) }) }) describe("State mixed between value and get/set", () => { let state: State const litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn() } as unknown as LitLikeElement const initialState = { value: "bla", other: "blub" } beforeEach(() => { jest.resetAllMocks() state = useState(litElement, initialState) }) it("sets up the default state", () => { expect(state.value).toBe(state.get()) }) describe("When the state is changed", () => { const subscriber = jest.fn() describe("on value", () => { beforeEach(() => { state.subscribe(subscriber) state.value = { value: "new" } }) it("updates the state on the get", () => { expect(state.get()).toEqual({ value: "new"}) }) }) describe("on set", () => { beforeEach(() => { state.subscribe(subscriber) state.set({ value: "new" }) }) it("updates the state on the value", () => { expect(state.value).toEqual({ value: "new"}) }) }) }) }) describe("state - state registration", () => { let state: State let litElement: LitLikeElement const initialState = { value: "bla", other: "blub" } beforeEach(() => { jest.resetAllMocks() litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn(), updated: jest.fn() } as unknown as LitLikeElement state = useState(litElement, initialState) }) it("should retrieve the same state after an update", () => { asUpdateableLitElement(litElement).updated(); const state1 = useState(litElement, initialState) asUpdateableLitElement(litElement).updated(); const state2 = useState(litElement, initialState) expect(state1).toBe(state2) expect(state).toBe(state2) }) it("should add multiple states between updateds", () => { const state1 = useState(litElement, "initialState") const state2 = useState(litElement, 42) asUpdateableLitElement(litElement).updated(); const retrieved0 = useState(litElement, initialState) const retrieved1 = useState(litElement, "otherState") const retrieved2 = useState(litElement, 0) asUpdateableLitElement(litElement).updated(); expect(retrieved0).toBe(state) expect(state1).toBe(retrieved1) expect(state2).toBe(retrieved2) expect(retrieved1.get()).toBe("initialState") expect(retrieved2.get()).toBe(42) }) }) describe("State - string type", () => { let state: State const litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn() } as unknown as LitLikeElement const subscriber = jest.fn() const initialState = "" beforeEach(() => { jest.resetAllMocks() state = useState(litElement, initialState) state.subscribe(subscriber) state.set("new") }) it("notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(1) expect(subscriber).toBeCalledWith("new") }) it("refreshes the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(1) }) describe("When the state is changed and then the default is published again", () => { beforeEach(() => { state.subscribe(subscriber) state.set(initialState) }) it("resets to the default state", () => { expect(state.get()).toEqual(initialState) }) it("notifies any subscriber", () => { expect(subscriber).toBeCalledTimes(2) expect(subscriber).toBeCalledWith("") }) it("refreshes the owning component", () => { expect(litElement.requestUpdate).toBeCalledTimes(2) }) });}) describe("Bug https://github.com/MatthiasKainer/pure-lit/issues/27 - subscribing to array triggers subscribers multiple times", () => { let state: State const litElement = { dispatchEvent: jest.fn(), requestUpdate: jest.fn() } as unknown as LitLikeElement const subscriber = jest.fn() beforeEach(() => { jest.resetAllMocks() state = useState(litElement, []) state.subscribe(subscriber) }) it("notifies any subscriber", () => { state.set(["new"]) expect(subscriber).toBeCalledTimes(1) expect(subscriber).toBeCalledWith(["new"]) }) it("notifies the subscriber only once, even if called multiple times", () => { state = useState(litElement, []) state.subscribe(subscriber) state.set(["new"]) expect(subscriber).toBeCalledTimes(1) expect(subscriber).toBeCalledWith(["new"]) state = useState(litElement, []) state.subscribe(subscriber) state.set(["new"]) expect(subscriber).toBeCalledTimes(2) expect(subscriber).toBeCalledWith(["new"]) }) })