Skip to content

Commit

Permalink
fix(setupWorker): resolve the "start" promise after the worker has ac…
Browse files Browse the repository at this point in the history
…tivated (#1146)
  • Loading branch information
kettanaito committed Mar 6, 2022
1 parent d7bdeec commit f6e709c
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 25 deletions.
30 changes: 25 additions & 5 deletions src/setupWorker/start/createStartHandler.ts
Expand Up @@ -92,10 +92,6 @@ If this message still persists after updating, please report an issue: https://g
`)
}

await enableMocking(context, options).catch((err) => {
throw new Error(`Failed to enable mocking: ${err?.message}`)
})

context.keepAliveInterval = window.setInterval(
() => context.workerChannel.send('KEEPALIVE_REQUEST'),
5000,
Expand All @@ -108,7 +104,31 @@ If this message still persists after updating, please report an issue: https://g
return registration
}

const workerRegistration = startWorkerInstance()
const workerRegistration = startWorkerInstance().then(
async (registration) => {
const pendingInstance = registration.installing || registration.waiting

// Wait until the worker is activated.
// Assume the worker is already activated if there's no pending registration
// (i.e. when reloading the page after a successful activation).
if (pendingInstance) {
await new Promise<void>((resolve) => {
pendingInstance.addEventListener('statechange', () => {
if (pendingInstance.state === 'activated') {
return resolve()
}
})
})
}

// Print the activation message only after the worker has been activated.
await enableMocking(context, options).catch((error) => {
throw new Error(`Failed to enable mocking: ${error?.message}`)
})

return registration
},
)

// Defer any network requests until the Service Worker instance is ready.
// This prevents a race condition between the Service Worker registration
Expand Down
15 changes: 11 additions & 4 deletions test/msw-api/setup-worker/start/start.mocks.ts
Expand Up @@ -8,8 +8,15 @@ const worker = setupWorker(

// @ts-ignore
window.msw = {
registration: worker.start().then((reg) => {
console.log('Registration Promise resolved')
return reg.constructor.name
}),
async startWorker() {
await worker.start({
serviceWorker: {
// Use a custom Service Worker for this test that intentionally
// delays the worker installation time. This allows us to test
// that the "worker.start()" Promise indeed resolves only after
// the worker has been activated and not just registered.
url: '/worker.js',
},
})
},
}
57 changes: 41 additions & 16 deletions test/msw-api/setup-worker/start/start.test.ts
@@ -1,34 +1,59 @@
import * as path from 'path'
import { pageWith } from 'page-with'
import { SetupWorkerApi } from 'msw'
import { waitFor } from '../../../support/waitFor'

declare namespace window {
export const msw: {
registration: ReturnType<SetupWorkerApi['start']>
startWorker(): ReturnType<SetupWorkerApi['start']>
}
}

test('resolves the "start" Promise after the worker has been activated', async () => {
const { page, consoleSpy } = await pageWith({
test('resolves the "start" Promise when the worker has been activated', async () => {
const runtime = await pageWith({
example: path.resolve(__dirname, 'start.mocks.ts'),
routes(app) {
app.get('/worker.js', (req, res) => {
res.sendFile(path.resolve(__dirname, 'start.worker.js'))
})
},
})
const resolvedPayload = await page.evaluate(() => {
return window.msw.registration
})

expect(resolvedPayload).toBe('ServiceWorkerRegistration')
const events: string[] = []

const untilWorkerActivated = runtime.page
.evaluate(() => {
return new Promise((resolve) =>
navigator.serviceWorker.addEventListener('controllerchange', resolve),
)
})
.then(() => {
events.push('worker activated')
})

const activationMessageIndex = consoleSpy
.get('startGroupCollapsed')
.findIndex((text) => {
return text.includes('[MSW] Mocking enabled')
const untilStartResolved = runtime.page
.evaluate(() => {
return window.msw.startWorker()
})
.then(() => {
events.push('start resolved')
})

const customMessageIndex = consoleSpy.get('log').findIndex((text) => {
return text.includes('Registration Promise resolved')
const untilActivationMessage = waitFor(() => {
expect(runtime.consoleSpy.get('startGroupCollapsed')).toContain(
'[MSW] Mocking enabled.',
)
events.push('enabled message')
})

expect(activationMessageIndex).toBeGreaterThan(-1)
expect(customMessageIndex).toBeGreaterThan(-1)
expect(customMessageIndex).toBeGreaterThan(activationMessageIndex)
await Promise.all([
untilActivationMessage,
untilWorkerActivated,
untilStartResolved,
])

expect(events[0]).toEqual('worker activated')
expect(events[1]).toEqual('start resolved')
expect(events[2]).toEqual('enabled message')
expect(events).toHaveLength(3)
})
10 changes: 10 additions & 0 deletions test/msw-api/setup-worker/start/start.worker.js
@@ -0,0 +1,10 @@
importScripts('/mockServiceWorker.js')

self.addEventListener('install', (event) => {
event.waitUntil(
new Promise((resolve) => {
// Emulate long worker installation.
setTimeout(resolve, 500)
}),
)
})

0 comments on commit f6e709c

Please sign in to comment.