UNPKG

@furystack/shades

Version:

Google Authentication Provider for FuryStack

329 lines 16 kB
import { Injector } from '@furystack/inject'; import { sleepAsync, usingAsync } from '@furystack/utils'; import { TextDecoder, TextEncoder } from 'util'; global.TextEncoder = TextEncoder; global.TextDecoder = TextDecoder; import { serializeToQueryString } from '@furystack/rest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { initializeShadeRoot } from './initialize.js'; import { createComponent } from './shade-component.js'; import { Shade } from './shade.js'; describe('Shades integration tests', () => { beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; }); afterEach(() => { document.body.innerHTML = ''; }); it('Should mount a custom component to a Shade root', () => { const injector = new Injector(); const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ render: () => createComponent("div", null, "Hello"), shadowDomName: 'shades-example' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-example><div>Hello</div></shades-example></div>'); }); it('Should mount a custom component with a string render result', () => { const injector = new Injector(); const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ render: () => 'Hello', shadowDomName: 'shades-string-render-result' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-string-render-result>Hello</shades-string-render-result></div>'); }); it('Should mount a custom component with null render result', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ render: () => null, shadowDomName: 'shades-null-render-result' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-null-render-result></shades-null-render-result></div>'); }); }); it('Should mount a custom component with a document fragment render result', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ render: () => (createComponent(createComponent, null, createComponent("p", null, "1"), createComponent("p", null, "2"))), shadowDomName: 'shades-fragment-render-result', }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-fragment-render-result><p>1</p><p>2</p></shades-fragment-render-result></div>'); }); }); it('Should mount a custom component with a nested document fragment render result', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ render: () => (createComponent("p", null, createComponent(createComponent, null, createComponent("p", null, "1"), createComponent("p", null, "2")))), shadowDomName: 'shades-fragment-render-result-nested', }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-fragment-render-result-nested><p><p>1</p><p>2</p></p></shades-fragment-render-result-nested></div>'); }); }); it('Should mount a custom component with a document fragment that contains custom components', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const CustomComponent = Shade({ shadowDomName: 'shades-fragment-test-custom-component', render: () => createComponent("p", null, "Hello"), }); const ExampleComponent = Shade({ render: () => (createComponent(createComponent, null, createComponent(CustomComponent, null), createComponent(CustomComponent, null))), shadowDomName: 'shades-fragment-render-result-2', }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-fragment-render-result-2><shades-fragment-test-custom-component><p>Hello</p></shades-fragment-test-custom-component><shades-fragment-test-custom-component><p>Hello</p></shades-fragment-test-custom-component></shades-fragment-render-result-2></div>'); }); }); it('Should mount nested Shades components', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ render: ({ children }) => createComponent("div", null, children), shadowDomName: 'shades-example-2', }); const ExampleSubs = Shade({ render: ({ props }) => createComponent("div", null, props.no), shadowDomName: 'shades-example-sub', }); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(ExampleComponent, null, createComponent(ExampleSubs, { no: 1 }), createComponent(ExampleSubs, { no: 2 }), createComponent(ExampleSubs, { no: 3 }))), }); expect(document.body.innerHTML).toBe('<div id="root"><shades-example-2><div><shades-example-sub><div>1</div></shades-example-sub><shades-example-sub><div>2</div></shades-example-sub><shades-example-sub><div>3</div></shades-example-sub></div></shades-example-2></div>'); }); }); it("Should execute the constructed and constructed's cleanup callback", async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const cleanup = vi.fn(); const constructed = vi.fn(() => cleanup); const ExampleComponent = Shade({ constructed, shadowDomName: 'example-component-1', render: () => createComponent("div", null, "Hello"), }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(constructed).toBeCalled(); expect(cleanup).not.toBeCalled(); document.body.innerHTML = ''; await sleepAsync(10); // Dispose can be async expect(cleanup).toBeCalled(); }); }); it('Should execute the onAttach and onDetach callbacks', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const onAttach = vi.fn(); const onDetach = vi.fn(); const ExampleComponent = Shade({ onAttach, onDetach, shadowDomName: 'example-component-2', render: () => createComponent("div", null, "Hello"), }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); expect(onAttach).toBeCalled(); expect(onDetach).not.toBeCalled(); document.body.innerHTML = ''; expect(onDetach).toBeCalled(); }); }); it('Should update state', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ shadowDomName: 'example-component-3', render: ({ useState }) => { const [count, setCount] = useState('count', 0); return (createComponent("div", null, "Count is ", count, createComponent("button", { id: "plus", onclick: () => setCount(count + 1) }, "+"), createComponent("button", { id: "minus", onclick: () => setCount(count - 1) }, "-"))); }, }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); const plus = () => document.getElementById('plus')?.click(); const minus = () => document.getElementById('minus')?.click(); const expectCount = (count) => expect(document.body.innerHTML).toContain(`Count is ${count}`); expectCount(0); plus(); expectCount(1); plus(); expectCount(2); minus(); minus(); expectCount(0); }); }); it('Should update the stored state', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ shadowDomName: 'example-component-3-stored-state', render: ({ useStoredState }) => { const [count, setCount] = useStoredState('count', 0); return (createComponent("div", null, "Count is ", count, createComponent("button", { id: "plus", onclick: () => setCount(count + 1) }, "+"), createComponent("button", { id: "minus", onclick: () => setCount(count - 1) }, "-"))); }, }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); const plus = () => document.getElementById('plus')?.click(); const minus = () => document.getElementById('minus')?.click(); const expectCount = (count) => expect(document.body.innerHTML).toContain(`Count is ${count}`); expectCount(0); await sleepAsync(100); plus(); expectCount(1); expect(localStorage.getItem('count')).toBe('1'); plus(); expectCount(2); expect(localStorage.getItem('count')).toBe('2'); minus(); minus(); expectCount(0); expect(localStorage.getItem('count')).toBe('0'); }); }); it('Should update the search state', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const ExampleComponent = Shade({ shadowDomName: 'example-component-3-search-state', render: ({ useSearchState }) => { const [count, setCount] = useSearchState('count', 0); return (createComponent("div", null, "Count is ", count, createComponent("button", { id: "plus", onclick: () => setCount(count + 1) }, "+"), createComponent("button", { id: "minus", onclick: () => setCount(count - 1) }, "-"))); }, }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(ExampleComponent, null), }); const plus = () => document.getElementById('plus')?.click(); const minus = () => document.getElementById('minus')?.click(); const expectCount = (count) => expect(document.body.innerHTML).toContain(`Count is ${count}`); expectCount(0); await sleepAsync(100); plus(); expectCount(1); expect(location.search).toBe(`?${serializeToQueryString({ count: 1 })}`); plus(); expectCount(2); expect(location.search).toBe(`?${serializeToQueryString({ count: 2 })}`); minus(); minus(); expectCount(0); expect(location.search).toBe(`?${serializeToQueryString({ count: 0 })}`); }); }); it('Should allow children update after unmount and remount', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root'); const Parent = Shade({ shadowDomName: 'shade-remount-parent', render: ({ children, useState }) => { const [areChildrenVisible, setAreChildrenVisible] = useState('areChildrenVisible', true); return (createComponent("div", null, createComponent("button", { id: "showHideChildren", onclick: () => { setAreChildrenVisible(!areChildrenVisible); } }, "Toggle"), areChildrenVisible ? children : null)); }, }); const Child = Shade({ shadowDomName: 'example-remount-child', render: ({ useState }) => { const [count, setCount] = useState('count', 0); return (createComponent("div", null, "Count is ", `${count}`, createComponent("button", { id: "plus", onclick: () => setCount(count + 1) }, "+"), createComponent("button", { id: "minus", onclick: () => setCount(count - 1) }, "-"))); }, }); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(Parent, null, createComponent(Child, null))), }); const plus = () => document.getElementById('plus')?.click(); const minus = () => document.getElementById('minus')?.click(); const expectCount = (count) => expect(document.body.innerHTML).toContain(`Count is ${count}`); const toggleChildren = () => document.getElementById('showHideChildren')?.click(); expectCount(0); plus(); expectCount(1); toggleChildren(); expect(document.getElementById('plus')).toBeNull(); await sleepAsync(10); // Dispose can be async toggleChildren(); expect(document.getElementById('plus')).toBeDefined(); expectCount(0); plus(); expectCount(1); minus(); expectCount(0); }); }); }); //# sourceMappingURL=shades.integration.spec.js.map