generated from ministryofjustice/hmpps-template-typescript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jest.setup.ts
100 lines (85 loc) · 3.35 KB
/
jest.setup.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom'
// @ts-expect-error setImmediate args have any types
globalThis.setImmediate = globalThis.setImmediate || ((fn, ...args) => global.setTimeout(fn, 0, ...args))
// Reset JSDOM
type AddEventListenerParams = Parameters<Document['addEventListener']>
const sideEffects = {
document: {
addEventListener: {
fn: document.addEventListener,
refs: [] as {
type: AddEventListenerParams['0']
listener: AddEventListenerParams['1']
options: AddEventListenerParams['2']
}[],
},
keys: Object.keys(document) as unknown as (keyof typeof document)[],
},
window: {
addEventListener: {
fn: window.addEventListener,
refs: [] as {
type: AddEventListenerParams['0']
listener: AddEventListenerParams['1']
options: AddEventListenerParams['2']
}[],
},
keys: Object.keys(window) as unknown as (keyof typeof window)[],
},
}
// Lifecycle Hooks
// -----------------------------------------------------------------------------
beforeAll(async () => {
// Spy addEventListener
;['document', 'window'].forEach(_obj => {
const obj: 'document' | 'window' = _obj as 'document' | 'window'
const { fn } = sideEffects[obj].addEventListener
const { refs } = sideEffects[obj].addEventListener
const addEventListenerSpy: Document['addEventListener'] = (
type: string,
listener: EventListenerOrEventListenerObject,
options: boolean | EventListenerOptions,
) => {
// Store listener reference so it can be removed during reset
refs.push({ type, listener, options })
// Call original window.addEventListener
fn(type, listener, options)
}
// Add to default key array to prevent removal during reset
sideEffects[obj].keys.push('addEventListener')
// Replace addEventListener with mock
global[obj].addEventListener = addEventListenerSpy
})
})
// Reset JSDOM. This attempts to remove side effects from tests, however it does
// not reset all changes made to globals like the window and document
// objects. Tests requiring a full JSDOM reset should be stored in separate
// files, which is only way to do a complete JSDOM reset with Jest.
beforeEach(async () => {
const rootElm = document.documentElement
// Remove attributes on root element
;[...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name))
// Remove elements (faster than setting innerHTML)
while (rootElm.firstChild) {
rootElm.removeChild(rootElm.firstChild)
}
// Remove global listeners and keys
;['document', 'window'].forEach(_obj => {
const obj: 'document' | 'window' = _obj as 'document' | 'window'
const { refs } = sideEffects[obj].addEventListener
refs.forEach(ref => {
const { type, listener, options } = ref
global[obj].removeEventListener(type, listener, options)
})
// need a semicolon to start here because we have semis turned off and ASI won't work and eslint tries to blend the two together
;(Object.keys(global[obj]) as unknown as (keyof (typeof global)[typeof obj])[])
.filter(key => !sideEffects[obj].keys.includes(key) && !key.includes('coverage'))
.forEach(key => {
delete global[obj][key]
})
})
// Restore base elements
rootElm.innerHTML = '<head></head><body></body>'
})
export {}