|
1 | 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
| 4 | +import { waitFor } from '@testing-library/react'; |
| 5 | + |
4 | 6 | import { GIT_SHA } from '../../../../../lib/components/internal/environment'; |
5 | | -import { checkMissingStyles } from '../../../../../lib/components/internal/hooks/use-base-component/styles-check'; |
| 7 | +import { |
| 8 | + checkMissingStyles, |
| 9 | + documentReadyAndIdle, |
| 10 | +} from '../../../../../lib/components/internal/hooks/use-base-component/styles-check'; |
6 | 11 | import { metrics } from '../../../../../lib/components/internal/metrics'; |
7 | | -import { idleWithDelay } from '../styles-check'; |
8 | 12 |
|
9 | 13 | jest.mock('../../../../../lib/components/internal/environment', () => ({ |
10 | 14 | ...jest.requireActual('../../../../../lib/components/internal/environment'), |
@@ -112,50 +116,70 @@ describe('checkMissingStyles', () => { |
112 | 116 | }); |
113 | 117 | }); |
114 | 118 |
|
115 | | -describe('idleWithDelay', () => { |
| 119 | +describe('documentReadyAndIdle', () => { |
| 120 | + function createDocumentMock(readyState: DocumentReadyState): Document { |
| 121 | + return { |
| 122 | + readyState, |
| 123 | + defaultView: { |
| 124 | + addEventListener: jest.fn() as Window['addEventListener'], |
| 125 | + }, |
| 126 | + } as Document; |
| 127 | + } |
| 128 | + |
116 | 129 | beforeEach(() => { |
117 | | - jest.useFakeTimers(); |
118 | 130 | // simulate requestIdleCallback for JSDOM |
119 | 131 | globalThis.requestIdleCallback = cb => setTimeout(cb, 0); |
120 | 132 | }); |
121 | 133 |
|
122 | 134 | afterEach(() => { |
123 | | - jest.useRealTimers(); |
124 | 135 | // @ts-expect-error reset to initial state |
125 | 136 | globalThis.requestIdleCallback = undefined; |
126 | 137 | }); |
127 | 138 |
|
128 | | - test('does nothing if requestIdleCallback not supported', () => { |
129 | | - // @ts-expect-error simulate missing API |
130 | | - globalThis.requestIdleCallback = undefined; |
| 139 | + test('runs callback when document is idle', async () => { |
| 140 | + const document = createDocumentMock('complete'); |
131 | 141 | const cb = jest.fn(); |
132 | | - expect(requestIdleCallback).toBe(undefined); |
133 | | - idleWithDelay(cb); |
134 | | - jest.runAllTimers(); |
135 | | - expect(cb).not.toHaveBeenCalled(); |
| 142 | + documentReadyAndIdle(document, new AbortController().signal).then(cb); |
| 143 | + await waitFor( |
| 144 | + () => { |
| 145 | + expect(document.defaultView!.addEventListener).not.toHaveBeenCalled(); |
| 146 | + expect(cb).toHaveBeenCalled(); |
| 147 | + }, |
| 148 | + { timeout: 2000 } |
| 149 | + ); |
136 | 150 | }); |
137 | 151 |
|
138 | | - test('runs callback after a delay', () => { |
| 152 | + test('waits for document to be loaded if it is not ready', async () => { |
| 153 | + const document = createDocumentMock('loading'); |
139 | 154 | const cb = jest.fn(); |
140 | | - idleWithDelay(cb); |
141 | | - jest.runAllTimers(); |
142 | | - expect(cb).toHaveBeenCalled(); |
| 155 | + documentReadyAndIdle(document, new AbortController().signal).then(cb); |
| 156 | + expect(document.defaultView!.addEventListener).toHaveBeenCalledWith('load', expect.any(Function), { once: true }); |
| 157 | + (document.defaultView!.addEventListener as any).mock.lastCall[1](); |
| 158 | + await waitFor( |
| 159 | + () => { |
| 160 | + expect(cb).toHaveBeenCalled(); |
| 161 | + }, |
| 162 | + { timeout: 2000 } |
| 163 | + ); |
143 | 164 | }); |
144 | 165 |
|
145 | | - test('delay can be aborted before setTimeout phase', () => { |
146 | | - const cb = jest.fn(); |
147 | | - const abort = idleWithDelay(cb); |
148 | | - abort!(); |
149 | | - jest.runAllTimers(); |
150 | | - expect(cb).not.toHaveBeenCalled(); |
| 166 | + test('delay can be aborted before loading state phase', async () => { |
| 167 | + const document = createDocumentMock('loading'); |
| 168 | + const onAbort = jest.fn(); |
| 169 | + const abortController = new AbortController(); |
| 170 | + documentReadyAndIdle(document, abortController.signal).then(() => {}, onAbort); |
| 171 | + expect(document.defaultView!.addEventListener).toHaveBeenCalledWith('load', expect.any(Function), { once: true }); |
| 172 | + abortController.abort(); |
| 173 | + await waitFor(() => expect(onAbort).toHaveBeenCalledWith(new DOMException('Aborted', 'AbortError'))); |
151 | 174 | }); |
152 | 175 |
|
153 | | - test('delay can be aborted before requestIdleCallback phase', () => { |
154 | | - const cb = jest.fn(); |
155 | | - const abort = idleWithDelay(cb); |
156 | | - jest.runOnlyPendingTimers(); // flush setTimeout |
157 | | - abort!(); |
158 | | - jest.runOnlyPendingTimers(); // flush following requestIdleCallback |
159 | | - expect(cb).not.toHaveBeenCalled(); |
| 176 | + test('delay can be aborted before requestIdleCallback phase', async () => { |
| 177 | + const document = createDocumentMock('complete'); |
| 178 | + const onAbort = jest.fn(); |
| 179 | + const abortController = new AbortController(); |
| 180 | + documentReadyAndIdle(document, abortController.signal).then(() => {}, onAbort); |
| 181 | + expect(document.defaultView!.addEventListener).not.toHaveBeenCalled(); |
| 182 | + abortController.abort(); |
| 183 | + await waitFor(() => expect(onAbort).toHaveBeenCalledWith(new DOMException('Aborted', 'AbortError'))); |
160 | 184 | }); |
161 | 185 | }); |
0 commit comments