Skip to content

Commit 2be1df2

Browse files
committed
Refactor Download Form tests to new webComponent pattern
1 parent af7f470 commit 2be1df2

6 files changed

Lines changed: 304 additions & 323 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
import DownloadFormComponent from '../../../index.astro'
3+
const props = {
4+
title: 'Test Resource',
5+
fileName: 'test-resource',
6+
fileType: 'PDF',
7+
}
8+
---
9+
10+
<DownloadFormComponent {...props} />
Lines changed: 95 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
11
// @vitest-environment node
2-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
3-
import { GlobalRegistrator } from '@happy-dom/global-registrator'
4-
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
5-
import {
6-
getDownloadButtonWrapper,
7-
getDownloadCompanyNameInput,
8-
getDownloadFirstNameInput,
9-
getDownloadFormElement,
10-
getDownloadJobTitleInput,
11-
getDownloadLastNameInput,
12-
getDownloadStatusDiv,
13-
getDownloadSubmitButton,
14-
getDownloadWorkEmailInput,
15-
} from '@components/Forms/Download/client/selectors'
16-
import DownloadFormComponent from '@components/Forms/Download/index.astro'
17-
import { TestError } from '@test/errors'
18-
import { withLitRuntime } from '@test/unit/helpers/litRuntime'
2+
import { afterEach, describe, expect, it, vi } from 'vitest'
3+
import { renderDownloadForm, type DownloadFormElements } from './testUtils'
194

205
// Mock the logger to suppress error output in tests
216
vi.mock('@lib/logger', () => ({
@@ -26,254 +11,136 @@ vi.mock('@lib/logger', () => ({
2611
debug: vi.fn(),
2712
},
2813
}))
29-
30-
let container: AstroContainer | undefined
31-
let consoleErrorSpy: ReturnType<typeof vi.spyOn> | undefined
32-
const originalFetch = global.fetch
33-
let downloadFormClientPromise: Promise<void> | undefined
34-
const downloadFormTagName = 'download-form'
35-
36-
/**
37-
* Helper function to set up DOM from Container API
38-
*/
39-
async function renderDownloadFormDOM() {
40-
if (!container) {
41-
throw new TestError('Astro container not initialized')
42-
}
43-
44-
const result = await container.renderToString(DownloadFormComponent, {
45-
props: {
46-
title: 'Test Resource',
47-
fileName: 'test-file.pdf',
48-
fileType: 'PDF',
49-
},
50-
})
51-
52-
const template = document.createElement('template')
53-
template.innerHTML = result
54-
const body = document.body
55-
body.replaceChildren()
56-
while (template.content.firstChild) {
57-
body.appendChild(template.content.firstChild)
58-
}
14+
const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0))
15+
16+
const defaultFormValues = {
17+
firstName: 'Jane',
18+
lastName: 'Doe',
19+
workEmail: 'jane@example.com',
20+
jobTitle: 'Engineer',
21+
companyName: 'Acme Corp',
5922
}
6023

61-
async function hydrateDownloadFormElement() {
62-
await customElements.whenDefined(downloadFormTagName)
63-
const element = document.querySelector(downloadFormTagName)
64-
if (!element) {
65-
throw new TestError('download-form element not found in DOM')
66-
}
67-
await Promise.resolve()
68-
return element
24+
const fillDownloadForm = (
25+
elements: DownloadFormElements,
26+
overrides: Partial<typeof defaultFormValues> = {},
27+
) => {
28+
const values = { ...defaultFormValues, ...overrides }
29+
elements.firstName.value = values.firstName
30+
elements.lastName.value = values.lastName
31+
elements.workEmail.value = values.workEmail
32+
elements.jobTitle.value = values.jobTitle
33+
elements.companyName.value = values.companyName
34+
return values
6935
}
7036

71-
const withHydratedDownloadForm = async (assertions: () => Promise<void> | void) => {
72-
await withLitRuntime(async ({ register }) => {
73-
await register(downloadFormTagName, async () => ensureDownloadFormClient())
74-
await renderDownloadFormDOM()
75-
await hydrateDownloadFormElement()
76-
await assertions()
77-
})
37+
const submitForm = (window: Window & typeof globalThis, form: HTMLFormElement) => {
38+
const submitEvent = new window.Event('submit', { bubbles: true, cancelable: true })
39+
form.dispatchEvent(submitEvent)
7840
}
7941

80-
describe('download-form web component', () => {
81-
beforeAll(() => {
82-
GlobalRegistrator.register()
83-
})
84-
85-
afterAll(async () => {
86-
await GlobalRegistrator.unregister()
87-
})
88-
89-
beforeEach(async () => {
90-
container = await AstroContainer.create()
91-
global.fetch = vi.fn().mockResolvedValue({
92-
ok: true,
93-
json: async () => ({ success: true }),
94-
}) as typeof fetch
95-
96-
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
97-
})
42+
const successfulResponse = (): Response =>
43+
({
44+
ok: true,
45+
json: async () => ({ success: true }),
46+
} as Response)
9847

48+
describe('download-form web component', () => {
9949
afterEach(() => {
100-
consoleErrorSpy?.mockRestore()
101-
if (originalFetch) {
102-
global.fetch = originalFetch
103-
} else {
104-
delete (global as typeof global & { fetch?: typeof global.fetch }).fetch
105-
}
106-
container = undefined
50+
vi.restoreAllMocks()
10751
})
10852

109-
test('submits download requests via fetch', async () => {
110-
await withHydratedDownloadForm(async () => {
111-
const {
112-
form,
113-
firstName,
114-
lastName,
115-
workEmail,
116-
jobTitle,
117-
companyName,
118-
} = getDownloadFormElements()
119-
firstName.value = 'John'
120-
lastName.value = 'Doe'
121-
workEmail.value = 'john.doe@example.com'
122-
jobTitle.value = 'Developer'
123-
companyName.value = 'Acme Corp'
53+
it('submits download requests via fetch', async () => {
54+
await renderDownloadForm(async ({ elements, window }) => {
55+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(successfulResponse())
56+
const payload = fillDownloadForm(elements)
12457

125-
await submitFormAndFlush(form)
58+
submitForm(window, elements.form)
59+
await flushPromises()
12660

127-
expect(global.fetch).toHaveBeenCalledWith(
61+
expect(fetchSpy).toHaveBeenCalledWith(
12862
'/api/downloads/submit',
12963
expect.objectContaining({
13064
method: 'POST',
13165
headers: {
13266
'Content-Type': 'application/json',
13367
},
134-
body: JSON.stringify({
135-
firstName: 'John',
136-
lastName: 'Doe',
137-
workEmail: 'john.doe@example.com',
138-
jobTitle: 'Developer',
139-
companyName: 'Acme Corp',
140-
}),
141-
})
68+
body: JSON.stringify(payload),
69+
}),
14270
)
71+
72+
fetchSpy.mockRestore()
14373
})
14474
})
14575

146-
test('shows success message and reveals download button', async () => {
147-
await withHydratedDownloadForm(async () => {
148-
const {
149-
form,
150-
firstName,
151-
lastName,
152-
workEmail,
153-
jobTitle,
154-
companyName,
155-
} = getDownloadFormElements()
156-
firstName.value = 'Jane'
157-
lastName.value = 'Doe'
158-
workEmail.value = 'jane@example.com'
159-
jobTitle.value = 'Engineer'
160-
companyName.value = 'Widgets Inc'
76+
it('shows success message and reveals download button', async () => {
77+
await renderDownloadForm(async ({ elements, window }) => {
78+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(successfulResponse())
79+
fillDownloadForm(elements)
16180

162-
await submitFormAndFlush(form)
81+
submitForm(window, elements.form)
82+
await flushPromises()
16383

164-
const statusDiv = getDownloadStatusDiv()
165-
expect(statusDiv?.classList.contains('hidden')).toBe(false)
166-
expect(statusDiv?.classList.contains('success')).toBe(true)
167-
expect(statusDiv?.textContent).toContain('Thank you')
84+
expect(elements.statusDiv.classList.contains('hidden')).toBe(false)
85+
expect(elements.statusDiv.classList.contains('success')).toBe(true)
86+
expect(elements.statusDiv.textContent).toContain('Thank you')
87+
expect(elements.downloadButtonWrapper.classList.contains('hidden')).toBe(false)
88+
expect(elements.submitButton.classList.contains('hidden')).toBe(true)
89+
expect(elements.firstName.value).toBe('')
90+
expect(elements.lastName.value).toBe('')
91+
expect(elements.workEmail.value).toBe('')
16892

169-
const downloadWrapper = getDownloadButtonWrapper()
170-
expect(downloadWrapper?.classList.contains('hidden')).toBe(false)
171-
172-
const submitButton = getDownloadSubmitButton()
173-
expect(submitButton?.classList.contains('hidden')).toBe(true)
93+
fetchSpy.mockRestore()
17494
})
17595
})
17696

177-
test('displays error state when API fails', async () => {
178-
;(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
179-
ok: false,
180-
json: async () => ({ error: 'Server error' }),
181-
})
182-
183-
await withHydratedDownloadForm(async () => {
184-
const {
185-
form,
186-
firstName,
187-
lastName,
188-
workEmail,
189-
jobTitle,
190-
companyName,
191-
} = getDownloadFormElements()
192-
firstName.value = 'Sam'
193-
lastName.value = 'Lee'
194-
workEmail.value = 'sam@example.com'
195-
jobTitle.value = 'Analyst'
196-
companyName.value = 'Example'
197-
198-
await submitFormAndFlush(form)
199-
200-
const statusDiv = getDownloadStatusDiv()
201-
expect(statusDiv?.classList.contains('hidden')).toBe(false)
202-
expect(statusDiv?.classList.contains('error')).toBe(true)
203-
expect(statusDiv?.textContent).toContain('error')
97+
it('displays error state when API fails', async () => {
98+
await renderDownloadForm(async ({ elements, window }) => {
99+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
100+
ok: false,
101+
json: async () => ({ message: 'Server error' }),
102+
} as Response)
103+
104+
fillDownloadForm(elements)
105+
submitForm(window, elements.form)
106+
await flushPromises()
107+
108+
expect(fetchSpy).toHaveBeenCalled()
109+
expect(elements.statusDiv.classList.contains('hidden')).toBe(false)
110+
expect(elements.statusDiv.classList.contains('error')).toBe(true)
111+
expect(elements.statusDiv.textContent).toContain('There was an error processing your request')
112+
expect(elements.downloadButtonWrapper.classList.contains('hidden')).toBe(true)
113+
expect(elements.submitButton.classList.contains('hidden')).toBe(false)
114+
115+
fetchSpy.mockRestore()
204116
})
205117
})
206118

207-
test('disables submit button while request is pending', async () => {
208-
let resolveFetch: (() => void) | undefined
209-
;(global.fetch as ReturnType<typeof vi.fn>).mockImplementationOnce(
210-
() =>
211-
new Promise((resolve) => {
212-
resolveFetch = () =>
213-
resolve({
214-
ok: true,
215-
json: async () => ({ success: true }),
216-
})
217-
})
218-
)
219-
220-
await withHydratedDownloadForm(async () => {
221-
const {
222-
form,
223-
firstName,
224-
lastName,
225-
workEmail,
226-
jobTitle,
227-
companyName,
228-
} = getDownloadFormElements()
229-
firstName.value = 'Ava'
230-
lastName.value = 'Jones'
231-
workEmail.value = 'ava@example.com'
232-
jobTitle.value = 'PM'
233-
companyName.value = 'Acme'
234-
235-
const submitButton = getDownloadSubmitButton()
119+
it('disables submit button while request is pending', async () => {
120+
await renderDownloadForm(async ({ elements, window }) => {
121+
let resolveFetch: (() => void) | undefined
122+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(
123+
() =>
124+
new Promise(resolve => {
125+
resolveFetch = () => resolve(successfulResponse())
126+
}),
127+
)
236128

237-
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
238-
form.dispatchEvent(submitEvent)
129+
fillDownloadForm(elements)
130+
submitForm(window, elements.form)
239131

240-
expect(submitButton.disabled).toBe(true)
241-
expect(submitButton.textContent).toBe('Processing...')
132+
expect(elements.submitButton.disabled).toBe(true)
133+
expect(elements.submitButton.textContent).toBe('Processing...')
242134

243135
resolveFetch?.()
244-
await new Promise((resolve) => setTimeout(resolve, 0))
136+
await flushPromises()
245137

246-
expect(submitButton.disabled).toBe(false)
247-
expect(submitButton.textContent).toBe('Download Now')
138+
expect(fetchSpy).toHaveBeenCalled()
139+
expect(elements.submitButton.disabled).toBe(false)
140+
expect(elements.submitButton.textContent).toBe('Download Now')
141+
142+
fetchSpy.mockRestore()
248143
})
249144
})
250145
})
251146

252-
async function ensureDownloadFormClient() {
253-
if (!downloadFormClientPromise) {
254-
downloadFormClientPromise = import('@components/Forms/Download/client').then(
255-
({ registerDownloadFormWebComponent }) => {
256-
registerDownloadFormWebComponent()
257-
}
258-
)
259-
}
260-
return downloadFormClientPromise
261-
}
262-
263-
function getDownloadFormElements() {
264-
return {
265-
form: getDownloadFormElement(),
266-
firstName: getDownloadFirstNameInput(),
267-
lastName: getDownloadLastNameInput(),
268-
workEmail: getDownloadWorkEmailInput(),
269-
jobTitle: getDownloadJobTitleInput(),
270-
companyName: getDownloadCompanyNameInput(),
271-
}
272-
}
273-
274-
async function submitFormAndFlush(form: HTMLFormElement) {
275-
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
276-
form.dispatchEvent(submitEvent)
277-
await new Promise((resolve) => setTimeout(resolve, 0))
278-
}
279-

0 commit comments

Comments
 (0)