|
1 | 1 | // @vitest-environment node |
2 | | -import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest' |
| 2 | +import { beforeEach, describe, expect, test } from 'vitest' |
3 | 3 | import { experimental_AstroContainer as AstroContainer } from 'astro/container' |
4 | | -import { GlobalRegistrator } from '@happy-dom/global-registrator' |
| 4 | +import { Window } from 'happy-dom' |
| 5 | +import TestWebComponentAstro from '@components/Test/webComponent.astro' |
5 | 6 | import type { TestWebComponent as TestWebComponentInstance } from '@components/Test/webComponent' |
6 | | -import { withLitRuntime } from '@test/unit/helpers/litRuntime' |
7 | 7 |
|
8 | | -type TestWebComponentModule = typeof import('@components/Test/webComponent') |
9 | | -type TestWebComponentAstroModule = typeof import('@components/Test/webComponent.astro') |
| 8 | +type RenderResult = Awaited<ReturnType<AstroContainer['renderToString']>> |
| 9 | + |
| 10 | +type HappyDomGlobalKey = 'window' | 'document' | 'customElements' | 'HTMLElement' |
| 11 | +type HappyDomGlobals = Partial<Record<HappyDomGlobalKey, unknown>> |
| 12 | + |
| 13 | +const setGlobalValue = (key: HappyDomGlobalKey, value: unknown) => { |
| 14 | + if (value === undefined) { |
| 15 | + delete (globalThis as Record<string, unknown>)[key] |
| 16 | + return |
| 17 | + } |
| 18 | + |
| 19 | + ;(globalThis as Record<string, unknown>)[key] = value |
| 20 | +} |
| 21 | + |
| 22 | +const withHappyDomEnvironment = async <TReturn>( |
| 23 | + callback: (_context: { window: Window }) => Promise<TReturn> | TReturn, |
| 24 | +): Promise<TReturn> => { |
| 25 | + const window = new Window() |
| 26 | + const globalObject = globalThis as Record<string, unknown> |
| 27 | + const previousGlobals: HappyDomGlobals = { |
| 28 | + window: globalObject['window'], |
| 29 | + document: globalObject['document'], |
| 30 | + customElements: globalObject['customElements'], |
| 31 | + HTMLElement: globalObject['HTMLElement'], |
| 32 | + } |
| 33 | + |
| 34 | + setGlobalValue('window', window) |
| 35 | + setGlobalValue('document', window.document) |
| 36 | + setGlobalValue('customElements', window.customElements) |
| 37 | + setGlobalValue('HTMLElement', window.HTMLElement) |
| 38 | + |
| 39 | + try { |
| 40 | + return await callback({ window }) |
| 41 | + } finally { |
| 42 | + setGlobalValue('window', previousGlobals.window) |
| 43 | + setGlobalValue('document', previousGlobals.document) |
| 44 | + setGlobalValue('customElements', previousGlobals.customElements) |
| 45 | + setGlobalValue('HTMLElement', previousGlobals.HTMLElement) |
| 46 | + await window.happyDOM?.whenAsyncComplete?.() |
| 47 | + await window.happyDOM?.close?.() |
| 48 | + } |
| 49 | +} |
10 | 50 |
|
11 | | -let TestWebComponentAstro: TestWebComponentAstroModule['default'] |
12 | | -let TestWebComponentCtor: TestWebComponentModule['TestWebComponent'] |
13 | | -let registerTestWebComponent: TestWebComponentModule['registerTestWebComponent'] |
14 | | - |
15 | | -beforeAll(async () => { |
16 | | - GlobalRegistrator.register() |
17 | | - const [astroComponent, componentModule] = await Promise.all([ |
18 | | - import('@components/Test/webComponent.astro'), |
19 | | - import('@components/Test/webComponent'), |
20 | | - ]) |
21 | | - |
22 | | - TestWebComponentAstro = astroComponent.default |
23 | | - TestWebComponentCtor = componentModule.TestWebComponent |
24 | | - registerTestWebComponent = componentModule.registerTestWebComponent |
25 | | -}) |
26 | | - |
27 | | -afterAll(async () => { |
28 | | - await GlobalRegistrator.unregister() |
29 | | -}) |
30 | | - |
31 | | -describe('Test web component rendering via Astro Container API', () => { |
| 51 | +describe('TestWebComponent class behavior', () => { |
32 | 52 | let container: AstroContainer |
33 | 53 |
|
34 | 54 | beforeEach(async () => { |
35 | 55 | container = await AstroContainer.create() |
36 | 56 | }) |
37 | 57 |
|
38 | | - test('container output references compiled client bundle instead of inlining code', async () => { |
39 | | - const result = await container.renderToString(TestWebComponentAstro, { |
40 | | - props: { |
41 | | - heading: 'Test', |
42 | | - }, |
43 | | - }) |
| 58 | + const renderInHappyDom = async ( |
| 59 | + assertion: (_context: { element: TestWebComponentInstance }) => Promise<void> | void, |
| 60 | + ): Promise<void> => { |
| 61 | + await withHappyDomEnvironment(async ({ window }) => { |
| 62 | + const { TestWebComponent: TestWebComponentCtor, registerTestWebComponent } = await import( |
| 63 | + '@components/Test/webComponent' |
| 64 | + ) |
44 | 65 |
|
45 | | - expect(result).toMatchInlineSnapshot( |
46 | | - `"<test-web-component class=\"test-web-component\" data-testid=\"test-web-component\" data-astro-cid-7wyxbui3=\"true\"> <h2 data-astro-cid-7wyxbui3>Test</h2> </test-web-component> <script type=\"module\" src=\"/home/kevin/Repos/Webstack Builders/Corporate Website/astro.webstackbuilders.com/src/components/Test/webComponent.astro?astro&type=script&index=0&lang.ts\"></script>"`, |
47 | | - ) |
| 66 | + const renderedHtml: RenderResult = await container.renderToString(TestWebComponentAstro, { |
| 67 | + props: { |
| 68 | + heading: 'Integration test', |
| 69 | + }, |
| 70 | + }) |
48 | 71 |
|
49 | | - expect(result).toContain('<test-web-component') |
50 | | - }) |
51 | | -}) |
| 72 | + window.document.body.innerHTML = renderedHtml |
| 73 | + window.document.querySelectorAll('script').forEach((script) => script.remove()) |
52 | 74 |
|
53 | | -describe('TestWebComponent class behavior', () => { |
54 | | - const runtimeTagName = 'test-web-component-spec' |
| 75 | + registerTestWebComponent() |
55 | 76 |
|
56 | | - test('renders default message in light DOM', async () => { |
57 | | - await withLitRuntime(async ({ register, mount }) => { |
58 | | - await register(runtimeTagName, (tagName) => registerTestWebComponent(tagName)) |
59 | | - expect(customElements.get(runtimeTagName)).toBe(TestWebComponentCtor) |
| 77 | + const element = window.document.querySelector('test-web-component') as |
| 78 | + | TestWebComponentInstance |
| 79 | + | null |
| 80 | + |
| 81 | + if (!element) { |
| 82 | + throw new Error('Unable to locate rendered <test-web-component> instance in document') |
| 83 | + } |
60 | 84 |
|
61 | | - const element = await mount<TestWebComponentInstance>(runtimeTagName) |
| 85 | + expect(window.customElements.get('test-web-component')).toBe(TestWebComponentCtor) |
62 | 86 |
|
| 87 | + await element.updateComplete |
| 88 | + await assertion({ element }) |
| 89 | + }) |
| 90 | + } |
| 91 | + |
| 92 | + test('renders default message in light DOM', async () => { |
| 93 | + await renderInHappyDom(async ({ element }) => { |
63 | 94 | expect(element.querySelector('#message')?.textContent).toBe('Hello from Lit') |
64 | 95 | }) |
65 | 96 | }) |
66 | 97 |
|
67 | 98 | test('updates DOM when message changes', async () => { |
68 | | - await withLitRuntime(async ({ register, mount }) => { |
69 | | - await register(runtimeTagName, (tagName) => registerTestWebComponent(tagName)) |
70 | | - expect(customElements.get(runtimeTagName)).toBe(TestWebComponentCtor) |
71 | | - |
72 | | - const element = await mount<TestWebComponentInstance>(runtimeTagName) |
73 | | - |
| 99 | + await renderInHappyDom(async ({ element }) => { |
74 | 100 | element.message = 'Updated message' |
75 | 101 | await element.updateComplete |
76 | 102 |
|
|
0 commit comments