From 4af759183cf5445c29ec939aa556f0947e6b7f9a Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 11 Apr 2019 15:20:10 -0500 Subject: [PATCH] wip --- packages/state-tunnel/src/declarations.ts | 2 +- .../src/utils/__test__/state-tunnel.spec.ts | 126 ------------------ .../src/utils/__test__/state-tunnel.spec.tsx | 118 ++++++++++++++++ .../state-tunnel/src/utils/state-tunnel.tsx | 40 +++--- packages/state-tunnel/tsconfig.json | 1 - 5 files changed, 137 insertions(+), 150 deletions(-) delete mode 100644 packages/state-tunnel/src/utils/__test__/state-tunnel.spec.ts create mode 100644 packages/state-tunnel/src/utils/__test__/state-tunnel.spec.tsx diff --git a/packages/state-tunnel/src/declarations.ts b/packages/state-tunnel/src/declarations.ts index 06a3f44..a45c7b5 100644 --- a/packages/state-tunnel/src/declarations.ts +++ b/packages/state-tunnel/src/declarations.ts @@ -1,3 +1,3 @@ export type PropList = Extract[] | string; -export type SubscribeCallback = (el: any, props: PropList) => () => void; +export type SubscribeCallback = (el: any, props: PropList) => void; export type ConsumerRenderer = (subscribe: SubscribeCallback, renderer: Function) => any; diff --git a/packages/state-tunnel/src/utils/__test__/state-tunnel.spec.ts b/packages/state-tunnel/src/utils/__test__/state-tunnel.spec.ts deleted file mode 100644 index f727135..0000000 --- a/packages/state-tunnel/src/utils/__test__/state-tunnel.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { createProviderConsumer } from '../state-tunnel'; - -describe('createProviderConsumer', () => { - let Tunnel: any; - let createTunnelCallback: any; - let stencilElement: HTMLElement; - let subscribe: any; - - interface State { - messageLog: string[], - creatingMessage: boolean, - setCreatingMessage: () => void - } - - beforeEach(() => { - stencilElement = document.createElement('div'); - (stencilElement as any).forceUpdate = jest.fn(); - - createTunnelCallback = jest.fn((subscribeFn) => { - subscribe = subscribeFn; - }); - Tunnel = createProviderConsumer({ - messageLog: [], - creatingMessage: false, - setCreatingMessage: () => {} - }, createTunnelCallback); - }); - - it('should have a provider and consumer', () => { - expect(Tunnel.Provider).toBeDefined(); - expect(Tunnel.Consumer).toBeDefined(); - }); - - it('should call Tunnel consumerRender when Consumer created', () => { - const props = {}; - const childAsFunction = jest.fn(); - Tunnel.Consumer(props, [childAsFunction]); - subscribe(stencilElement, 'all'); - - expect(createTunnelCallback.mock.calls.length).toBe(1); - expect(createTunnelCallback.mock.calls[0][1]).toBe(childAsFunction); - expect((stencilElement as any).forceUpdate.mock.calls.length).toBe(1); - }); - - it('should set element prop as copy of state when only a string is passed', () => { - Tunnel.Consumer({}, [jest.fn()]); - subscribe(stencilElement, 'all'); - - const newState = { - messageLog: ['a'], - creatingMessage: false, - setCreatingMessage: () => {} - } - - Tunnel.Provider({ state: newState }); - expect((stencilElement as any).forceUpdate.mock.calls.length).toBe(2); - expect((stencilElement as any).all).toEqual(newState); - }); - - it('should set element props array of strings', () => { - Tunnel.Consumer({}, [jest.fn()]); - subscribe(stencilElement, ['messageLog', 'creatingMessage']); - - const newState = { - messageLog: ['a'], - creatingMessage: false, - setCreatingMessage: () => {} - } - - Tunnel.Provider({ state: newState }); - expect((stencilElement as any).forceUpdate.mock.calls.length).toBe(2); - expect((stencilElement as any).messageLog).toEqual(['a']); - expect((stencilElement as any).creatingMessage).toEqual(false); - expect((stencilElement as any).setCreatingMessage).toBeUndefined(); - }); - - - it('should set element props array of strings', () => { - const ElementClass = { - properties: { - messageLog: {}, - createingMessage: {}, - item: { - elementRef: true - } - }, - prototype: {} - }; - Tunnel.injectProps(ElementClass, ['messageLog', 'creatingMessage']); - - expect((ElementClass.prototype as any).componentWillLoad).toBeDefined(); - expect((ElementClass.prototype as any).componentDidUnload).toBeDefined(); - (ElementClass.prototype as any).componentWillLoad.call({ - item: stencilElement - }); - - let newState = { - messageLog: ['a'], - creatingMessage: false, - setCreatingMessage: () => {} - } - - Tunnel.Provider({ state: newState }); - expect((stencilElement as any).forceUpdate.mock.calls.length).toBe(2); - expect((stencilElement as any).messageLog).toEqual(['a']); - expect((stencilElement as any).creatingMessage).toEqual(false); - expect((stencilElement as any).setCreatingMessage).toBeUndefined(); - - // Unsubscribing removes from all further updates - (ElementClass.prototype as any).componentDidUnload.call({ - item: stencilElement - }); - - newState = { - messageLog: ['b'], - creatingMessage: true, - setCreatingMessage: () => {} - } - - Tunnel.Provider({ state: newState }); - expect((stencilElement as any).forceUpdate.mock.calls.length).toBe(2); - expect((stencilElement as any).messageLog).toEqual(['a']); - expect((stencilElement as any).creatingMessage).toEqual(false); - expect((stencilElement as any).setCreatingMessage).toBeUndefined(); - }); -}); diff --git a/packages/state-tunnel/src/utils/__test__/state-tunnel.spec.tsx b/packages/state-tunnel/src/utils/__test__/state-tunnel.spec.tsx new file mode 100644 index 0000000..7feac26 --- /dev/null +++ b/packages/state-tunnel/src/utils/__test__/state-tunnel.spec.tsx @@ -0,0 +1,118 @@ +import { Component, Prop } from '@stencil/core'; +import { newSpecPage, mockDocument } from '@stencil/core/testing'; +import { createProviderConsumer } from '../state-tunnel'; + +describe('createProviderConsumer', () => { + let Tunnel: any; + let createTunnelCallback: any; + let subscribe: any; + let elm: any; + let doc = mockDocument(); + + interface State { + messageLog: string[], + creatingMessage: boolean, + setCreatingMessage: () => void + } + + beforeEach(() => { + elm = doc.createElement('stencil-element'); + + createTunnelCallback = jest.fn((subscribeFn) => { + subscribe = subscribeFn; + }); + Tunnel = createProviderConsumer({ + messageLog: [], + creatingMessage: false, + setCreatingMessage: () => {} + }, createTunnelCallback); + }); + + it('should have a provider and consumer', () => { + expect(Tunnel.Provider).toBeDefined(); + expect(Tunnel.Consumer).toBeDefined(); + }); + + it('should call Tunnel consumerRender when Consumer created', () => { + const props = {}; + const childAsFunction = jest.fn(); + Tunnel.Consumer(props, [childAsFunction]); + subscribe(elm, 'all'); + + expect(createTunnelCallback.mock.calls.length).toBe(1); + expect(createTunnelCallback.mock.calls[0][1]).toBe(childAsFunction); + }); + + it('should set element prop as copy of state when only a string is passed', () => { + Tunnel.Consumer({}, [jest.fn()]); + subscribe(elm, 'all'); + + const newState = { + messageLog: ['a'], + creatingMessage: false, + setCreatingMessage: () => {} + } + + Tunnel.Provider({ state: newState }); + expect(elm.all).toEqual(newState); + }); + + it('should set element props array of strings', () => { + Tunnel.Consumer({}, [jest.fn()]); + subscribe(elm, ['messageLog', 'creatingMessage']); + + const newState = { + messageLog: ['a'], + creatingMessage: false, + setCreatingMessage: () => {} + } + + Tunnel.Provider({ state: newState }); + expect(elm.messageLog).toEqual(['a']); + expect(elm.creatingMessage).toEqual(false); + expect(elm.setCreatingMessage).toBeUndefined(); + }); + + fit('should set element props array of strings', async () => { + @Component({ tag: 'cmp-a' }) + class CmpA { + @Prop() messageLog: string[]; + @Prop() creatingMessage: boolean; + } + + Tunnel.injectProps(CmpA, ['messageLog', 'creatingMessage']); + + const { root } = await newSpecPage({ + components: [CmpA], + html: `` + }); + + expect(root.messageLog).toEqual([]); + expect(root.creatingMessage).toEqual(false); + + let newState = { + messageLog: ['a'], + creatingMessage: true, + setCreatingMessage: () => {} + } + + Tunnel.Provider({ state: newState }); + + expect(root.messageLog).toEqual(['a']); + expect(root.creatingMessage).toEqual(true); + expect(root.setCreatingMessage).toBeUndefined(); + + newState = { + messageLog: ['b'], + creatingMessage: false, + setCreatingMessage: () => {} + } + + Tunnel.Provider({ state: newState }); + + expect(root.messageLog).toEqual(['b']); + expect(root.creatingMessage).toEqual(false); + expect(root.setCreatingMessage).toBeUndefined(); + }); + +}); diff --git a/packages/state-tunnel/src/utils/state-tunnel.tsx b/packages/state-tunnel/src/utils/state-tunnel.tsx index 8669bd5..9a40024 100644 --- a/packages/state-tunnel/src/utils/state-tunnel.tsx +++ b/packages/state-tunnel/src/utils/state-tunnel.tsx @@ -1,6 +1,7 @@ import { ComponentInterface, FunctionalComponent } from '@stencil/core'; import { SubscribeCallback, ConsumerRenderer, PropList } from '../declarations'; + export const createProviderConsumer = (defaultState: T, consumerRender: ConsumerRenderer) => { let listeners: Map> = new Map(); @@ -20,14 +21,10 @@ export const createProviderConsumer = (defaultSt } const subscribe: SubscribeCallback = (instance: ComponentInterface, propList: PropList) => { - if (listeners.has(instance)) { - return noop; + if (!listeners.has(instance)) { + listeners.set(instance, propList); + updateListener(propList, instance); } - - listeners.set(instance, propList); - updateListener(propList, instance); - - return () => listeners.delete(instance); } const Provider: FunctionalComponent<{state: T}> = ({ state }, children) => { @@ -42,24 +39,25 @@ export const createProviderConsumer = (defaultSt return consumerRender(subscribe, children[0] as any); } - const injectProps = (childComponent: any, fieldList: PropList) => { - let unsubscribe: any = null; + const injectProps = (Cstr: any, fieldList: PropList) => { + const CstrPrototype = Cstr.prototype; + const cstrComponentWillLoad = CstrPrototype.componentWillLoad; + const cstrComponentDidUnload = CstrPrototype.componentDidUnload; - const prevComponentWillLoad = (childComponent.prototype as ComponentInterface).componentWillLoad; - (childComponent.prototype as ComponentInterface).componentWillLoad = function() { - unsubscribe = subscribe(this, fieldList); - if (prevComponentWillLoad) { - return prevComponentWillLoad.bind(this)(); + CstrPrototype.componentWillLoad = function() { + subscribe(this, fieldList); + + if (cstrComponentWillLoad) { + return cstrComponentWillLoad.call(this); } } - const prevComponentDidUnload = childComponent.prototype.componentDidUnload; - childComponent.prototype.componentDidUnload = function() { - unsubscribe(); - if (prevComponentDidUnload) { - return prevComponentDidUnload.bind(this)(); + CstrPrototype.componentDidUnload = function() { + listeners.delete(this); + if (cstrComponentDidUnload) { + cstrComponentDidUnload.call(this); } - } + }; } return { @@ -68,5 +66,3 @@ export const createProviderConsumer = (defaultSt injectProps } } - -const noop = () => {}; diff --git a/packages/state-tunnel/tsconfig.json b/packages/state-tunnel/tsconfig.json index 0438813..e41dcd1 100644 --- a/packages/state-tunnel/tsconfig.json +++ b/packages/state-tunnel/tsconfig.json @@ -2,7 +2,6 @@ "compilerOptions": { "allowSyntheticDefaultImports": true, "allowUnreachableCode": false, - "strict": true, "declaration": false, "experimentalDecorators": true, "lib": [