|
1 | 1 | // @vitest-environment node |
2 | 2 |
|
3 | | -import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' |
4 | | -import { experimental_AstroContainer as AstroContainer } from 'astro/container' |
5 | | -import CheckboxFixture from '@components/Consent/Checkbox/client/__tests__/checkbox.fixture.astro' |
6 | | -import type { ConsentCheckboxElement } from '@components/Consent/Checkbox/client' |
7 | | -import type { WebComponentModule } from '@components/scripts/@types/webComponentModule' |
8 | | -import { executeRender, withJsdomEnvironment } from '@test/unit/helpers/litRuntime' |
9 | | -import { ClientScriptError } from '@components/scripts/errors' |
| 3 | +import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 4 | +import { renderConsentCheckbox } from '@components/Consent/Checkbox/client/__tests__/testUtils' |
| 5 | +import { withJsdomEnvironment } from '@test/unit/helpers/litRuntime' |
10 | 6 | import * as consentStore from '@components/scripts/store' |
11 | 7 |
|
12 | 8 | vi.mock('@components/scripts/store', () => { |
@@ -50,77 +46,12 @@ const consentStoreMock = consentStore as ConsentStoreMock |
50 | 46 | const updateConsentMock = vi.mocked(consentStore.updateConsent) |
51 | 47 | const subscribeToFunctionalConsentMock = vi.mocked(consentStore.subscribeToFunctionalConsent) |
52 | 48 |
|
53 | | -type ConsentClientModule = typeof import('@components/Consent/Checkbox/client') |
54 | | -let consentClientModule: ConsentClientModule | null = null |
55 | | - |
56 | | -const getConsentClientModule = (): ConsentClientModule => { |
57 | | - if (!consentClientModule) { |
58 | | - throw new Error('Consent checkbox client module not initialized') |
59 | | - } |
60 | | - |
61 | | - return consentClientModule |
62 | | -} |
63 | | - |
64 | | -type ConsentCheckboxModule = WebComponentModule<ConsentCheckboxElement> |
65 | | - |
66 | | -const defaultProps = { |
67 | | - id: 'gdpr-consent', |
68 | | - formId: 'contact-form', |
69 | | - purpose: 'Responding to your inquiry', |
70 | | -} |
71 | | - |
72 | | -beforeAll(async () => { |
73 | | - await withJsdomEnvironment(async () => { |
74 | | - consentClientModule = await import('@components/Consent/Checkbox/client') |
75 | | - }) |
76 | | -}) |
77 | | - |
78 | | -const CONSENT_READY_TIMEOUT_MS = 2_000 |
79 | | - |
80 | 49 | beforeEach(() => { |
81 | 50 | consentStoreMock.__test.resetConsent() |
82 | 51 | updateConsentMock.mockClear() |
83 | 52 | subscribeToFunctionalConsentMock.mockClear() |
84 | 53 | }) |
85 | 54 |
|
86 | | -const waitForConsentReady = async (element: ConsentCheckboxElement) => { |
87 | | - await new Promise<void>((resolve, reject) => { |
88 | | - if (element.isInitialized) { |
89 | | - resolve() |
90 | | - return |
91 | | - } |
92 | | - |
93 | | - const timeoutId = setTimeout(() => { |
94 | | - element.removeEventListener('consent-checkbox:ready', onReady) |
95 | | - reject(new Error('Consent checkbox never finished initializing')) |
96 | | - }, CONSENT_READY_TIMEOUT_MS) |
97 | | - |
98 | | - function onReady() { |
99 | | - clearTimeout(timeoutId) |
100 | | - resolve() |
101 | | - } |
102 | | - |
103 | | - element.addEventListener('consent-checkbox:ready', onReady, { once: true }) |
104 | | - }) |
105 | | -} |
106 | | - |
107 | | -const renderConsentCheckbox = async ( |
108 | | - assertion: (_context: { element: ConsentCheckboxElement; window: any }) => Promise<void> | void, |
109 | | - props: Record<string, unknown> = {}, |
110 | | -) => { |
111 | | - const container = await AstroContainer.create() |
112 | | - |
113 | | - await executeRender<ConsentCheckboxModule>({ |
114 | | - container, |
115 | | - component: CheckboxFixture, |
116 | | - moduleSpecifier: '@components/Consent/Checkbox/client/index', |
117 | | - args: { props: { ...defaultProps, ...props } }, |
118 | | - selector: 'consent-checkbox', |
119 | | - waitForReady: waitForConsentReady, |
120 | | - assert: async ({ element, window }) => assertion({ element, window }), |
121 | | - }) |
122 | | -} |
123 | | - |
124 | 55 | describe('ConsentCheckboxElement', () => { |
125 | 56 | it('requests consent updates when the checkbox is checked', async () => { |
126 | 57 | await renderConsentCheckbox(({ element, window }) => { |
@@ -219,74 +150,52 @@ describe('ConsentCheckboxElement', () => { |
219 | 150 | expect(focusSpy).toHaveBeenCalled() |
220 | 151 | }) |
221 | 152 | }) |
222 | | -}) |
223 | 153 |
|
224 | | -describe('Validation helpers', () => { |
225 | | - it('validateConsent shows and clears error messages', async () => { |
226 | | - const { validateConsent } = getConsentClientModule() |
227 | | - |
228 | | - await renderConsentCheckbox(({ element }) => { |
| 154 | + it('continues to operate when the checkbox is not inside a form', async () => { |
| 155 | + await renderConsentCheckbox(({ element, window }) => { |
| 156 | + const form = element.closest('form') |
229 | 157 | const checkbox = element.querySelector<HTMLInputElement>('input[type="checkbox"]') |
230 | | - const errorElement = element.querySelector<HTMLDivElement>('#gdpr-consent-error') |
231 | 158 |
|
| 159 | + expect(form).toBeNull() |
232 | 160 | expect(checkbox).toBeTruthy() |
233 | | - expect(errorElement).toBeTruthy() |
234 | | - |
235 | | - checkbox!.checked = false |
236 | | - expect(validateConsent(checkbox!, errorElement!)).toBe(false) |
237 | | - expect(errorElement!.textContent).toContain('You must consent to data processing') |
238 | 161 |
|
239 | 162 | checkbox!.checked = true |
240 | | - expect(validateConsent(checkbox!, errorElement!)).toBe(true) |
241 | | - expect(errorElement!.textContent).toBe('') |
242 | | - }) |
243 | | - }) |
244 | | - |
245 | | - it('isConsentValid reflects the checkbox state', async () => { |
246 | | - const { isConsentValid } = getConsentClientModule() |
247 | | - |
248 | | - await renderConsentCheckbox(({ element }) => { |
249 | | - const checkbox = element.querySelector<HTMLInputElement>('input[type="checkbox"]') |
250 | | - expect(checkbox).toBeTruthy() |
251 | | - |
252 | | - checkbox!.checked = false |
253 | | - expect(isConsentValid('gdpr-consent')).toBe(false) |
| 163 | + checkbox!.dispatchEvent(new window.Event('change', { bubbles: true })) |
254 | 164 |
|
255 | | - checkbox!.checked = true |
256 | | - expect(isConsentValid('gdpr-consent')).toBe(true) |
257 | | - }) |
| 165 | + expect(updateConsentMock).toHaveBeenCalledWith('functional', true) |
| 166 | + }, { wrapInForm: false }) |
258 | 167 | }) |
259 | 168 | }) |
260 | 169 |
|
261 | | -describe('initGDPRConsent', () => { |
262 | | - it('throws ClientScriptError when elements are missing', async () => { |
263 | | - await withJsdomEnvironment(async ({ window }) => { |
264 | | - window.document.body.innerHTML = '<div></div>' |
265 | | - |
266 | | - const { initGDPRConsent } = getConsentClientModule() |
267 | | - expect(() => initGDPRConsent('missing', 'contact')).toThrow(ClientScriptError) |
| 170 | +describe('ConsentCheckbox web component module contract', () => { |
| 171 | + it('exposes metadata for registration', async () => { |
| 172 | + await withJsdomEnvironment(async () => { |
| 173 | + const { webComponentModule, ConsentCheckboxElement } = await import('@components/Consent/Checkbox/client') |
| 174 | + expect(webComponentModule.registeredName).toBe('consent-checkbox') |
| 175 | + expect(webComponentModule.componentCtor).toBe(ConsentCheckboxElement) |
268 | 176 | }) |
269 | 177 | }) |
270 | 178 |
|
271 | | - it('handles checkboxes that are not inside a form', async () => { |
| 179 | + it('registers the custom element when window is available', async () => { |
272 | 180 | await withJsdomEnvironment(async ({ window }) => { |
273 | | - window.document.body.innerHTML = ` |
274 | | - <div id="gdpr-consent-container"> |
275 | | - <input type="checkbox" id="gdpr-consent" /> |
276 | | - <div id="gdpr-consent-error"></div> |
277 | | - </div> |
278 | | - ` |
| 181 | + const { registerConsentCheckboxWebComponent, ConsentCheckboxElement } = await import('@components/Consent/Checkbox/client') |
| 182 | + const uniqueTag = `consent-checkbox-${Math.random().toString(36).slice(2)}` |
279 | 183 |
|
280 | | - const { initGDPRConsent } = getConsentClientModule() |
281 | | - const cleanup = initGDPRConsent('gdpr-consent', 'contact') |
| 184 | + const originalGet = window.customElements.get.bind(window.customElements) |
| 185 | + const getSpy = vi.spyOn(window.customElements, 'get').mockImplementation((tagName: string) => { |
| 186 | + if (tagName === uniqueTag) { |
| 187 | + return undefined |
| 188 | + } |
| 189 | + return originalGet(tagName) |
| 190 | + }) |
| 191 | + const defineSpy = vi.spyOn(window.customElements, 'define').mockImplementation(() => undefined) |
282 | 192 |
|
283 | | - const checkbox = window.document.getElementById('gdpr-consent') as HTMLInputElement |
284 | | - checkbox.checked = true |
285 | | - checkbox.dispatchEvent(new window.Event('change')) |
| 193 | + registerConsentCheckboxWebComponent(uniqueTag) |
286 | 194 |
|
287 | | - expect(updateConsentMock).toHaveBeenCalledWith('functional', true) |
| 195 | + expect(defineSpy).toHaveBeenCalledWith(uniqueTag, ConsentCheckboxElement) |
288 | 196 |
|
289 | | - cleanup() |
| 197 | + getSpy.mockRestore() |
| 198 | + defineSpy.mockRestore() |
290 | 199 | }) |
291 | 200 | }) |
292 | 201 | }) |
0 commit comments