From 78d155fd46b7b711dfd6c70f62da96fbad5c55fa Mon Sep 17 00:00:00 2001 From: Shinji Nakamatsu <19329+snaka@users.noreply.github.com> Date: Sat, 1 Oct 2022 21:29:22 +0900 Subject: [PATCH] fix: remove duplicate response logging in the browser console (#1418) --- package.json | 2 +- src/utils/handleRequest.test.ts | 72 ++++++++++++------- .../setup-worker/response-logging.test.ts | 46 ++++++++++++ test/rest-api/body.mocks.ts | 38 +++++----- yarn.lock | 14 ++-- 5 files changed, 119 insertions(+), 53 deletions(-) create mode 100644 test/msw-api/setup-worker/response-logging.test.ts diff --git a/package.json b/package.json index 68edfbb50..2953a2959 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "outvariant": "^1.3.0", "path-to-regexp": "^6.2.0", "statuses": "^2.0.0", - "strict-event-emitter": "^0.2.0", + "strict-event-emitter": "^0.2.6", "type-fest": "^2.19.0", "yargs": "^17.3.1" }, diff --git a/src/utils/handleRequest.test.ts b/src/utils/handleRequest.test.ts index 998a1d491..8131107db 100644 --- a/src/utils/handleRequest.test.ts +++ b/src/utils/handleRequest.test.ts @@ -9,17 +9,6 @@ import { response } from '../response' import { context, MockedRequest } from '..' import { RequiredDeep } from '../typeUtils' -const emitter = new StrictEventEmitter() -const listener = jest.fn() -function createMockListener(name: string) { - return (...args: any) => { - listener(name, ...args) - } -} -function getEmittedEvents() { - return listener.mock.calls -} - const options: RequiredDeep = { onUnhandledRequest: jest.fn(), } @@ -28,27 +17,44 @@ const callbacks: Partial, any>> = { onMockedResponse: jest.fn(), } -beforeEach(() => { - jest.spyOn(global.console, 'warn').mockImplementation() +function setup() { + const emitter = new StrictEventEmitter() + const listener = jest.fn() + + const createMockListener = (name: string) => { + return (...args: any) => { + listener(name, ...args) + } + } emitter.on('request:start', createMockListener('request:start')) emitter.on('request:match', createMockListener('request:match')) emitter.on('request:unhandled', createMockListener('request:unhandled')) emitter.on('request:end', createMockListener('request:end')) + emitter.on('response:mocked', createMockListener('response:mocked')) + emitter.on('response:bypass', createMockListener('response:bypass')) + + const events = listener.mock.calls + return { emitter, events } +} + +beforeEach(() => { + jest.spyOn(global.console, 'warn').mockImplementation() }) afterEach(() => { jest.resetAllMocks() - emitter.removeAllListeners() }) test('returns undefined for a request with the "x-msw-bypass" header equal to "true"', async () => { + const { emitter, events } = setup() + const request = new MockedRequest(new URL('http://localhost/user'), { headers: new Headers({ 'x-msw-bypass': 'true', }), }) - const handlers: RequestHandler[] = [] + const handlers: Array = [] const result = await handleRequest( request, @@ -59,7 +65,7 @@ test('returns undefined for a request with the "x-msw-bypass" header equal to "t ) expect(result).toBeUndefined() - expect(getEmittedEvents()).toEqual([ + expect(events).toEqual([ ['request:start', request], ['request:end', request], ]) @@ -69,12 +75,14 @@ test('returns undefined for a request with the "x-msw-bypass" header equal to "t }) test('does not bypass a request with "x-msw-bypass" header set to arbitrary value', async () => { + const { emitter } = setup() + const request = new MockedRequest(new URL('http://localhost/user'), { headers: new Headers({ 'x-msw-bypass': 'anything', }), }) - const handlers: RequestHandler[] = [ + const handlers: Array = [ rest.get('/user', (req, res, ctx) => { return res(ctx.text('hello world')) }), @@ -94,8 +102,10 @@ test('does not bypass a request with "x-msw-bypass" header set to arbitrary valu }) test('reports request as unhandled when it has no matching request handlers', async () => { + const { emitter, events } = setup() + const request = new MockedRequest(new URL('http://localhost/user')) - const handlers: RequestHandler[] = [] + const handlers: Array = [] const result = await handleRequest( request, @@ -106,7 +116,7 @@ test('reports request as unhandled when it has no matching request handlers', as ) expect(result).toBeUndefined() - expect(getEmittedEvents()).toEqual([ + expect(events).toEqual([ ['request:start', request], ['request:unhandled', request], ['request:end', request], @@ -120,8 +130,10 @@ test('reports request as unhandled when it has no matching request handlers', as }) test('returns undefined and warns on a request handler that returns no response', async () => { + const { emitter, events } = setup() + const request = new MockedRequest(new URL('http://localhost/user')) - const handlers: RequestHandler[] = [ + const handlers: Array = [ rest.get('/user', () => { // Intentionally blank response resolver. return @@ -137,7 +149,7 @@ test('returns undefined and warns on a request handler that returns no response' ) expect(result).toBeUndefined() - expect(getEmittedEvents()).toEqual([ + expect(events).toEqual([ ['request:start', request], ['request:end', request], ]) @@ -156,9 +168,11 @@ test('returns undefined and warns on a request handler that returns no response' }) test('returns the mocked response for a request with a matching request handler', async () => { + const { emitter, events } = setup() + const request = new MockedRequest(new URL('http://localhost/user')) const mockedResponse = await response(context.json({ firstName: 'John' })) - const handlers: RequestHandler[] = [ + const handlers: Array = [ rest.get('/user', () => { return mockedResponse }), @@ -179,7 +193,7 @@ test('returns the mocked response for a request with a matching request handler' ) expect(result).toEqual(mockedResponse) - expect(getEmittedEvents()).toEqual([ + expect(events).toEqual([ ['request:start', request], ['request:match', request], ['request:end', request], @@ -193,9 +207,11 @@ test('returns the mocked response for a request with a matching request handler' }) test('returns a transformed response if the "transformResponse" option is provided', async () => { + const { emitter, events } = setup() + const request = new MockedRequest(new URL('http://localhost/user')) const mockedResponse = await response(context.json({ firstName: 'John' })) - const handlers: RequestHandler[] = [ + const handlers: Array = [ rest.get('/user', () => { return mockedResponse }), @@ -217,7 +233,7 @@ test('returns a transformed response if the "transformResponse" option is provid }) expect(result).toEqual(finalResponse) - expect(getEmittedEvents()).toEqual([ + expect(events).toEqual([ ['request:start', request], ['request:match', request], ['request:end', request], @@ -232,8 +248,10 @@ test('returns a transformed response if the "transformResponse" option is provid }) it('returns undefined without warning on a passthrough request', async () => { + const { emitter, events } = setup() + const request = new MockedRequest(new URL('http://localhost/user')) - const handlers: RequestHandler[] = [ + const handlers: Array = [ rest.get('/user', (req) => { return req.passthrough() }), @@ -248,7 +266,7 @@ it('returns undefined without warning on a passthrough request', async () => { ) expect(result).toBeUndefined() - expect(getEmittedEvents()).toEqual([ + expect(events).toEqual([ ['request:start', request], ['request:end', request], ]) diff --git a/test/msw-api/setup-worker/response-logging.test.ts b/test/msw-api/setup-worker/response-logging.test.ts new file mode 100644 index 000000000..9b3e5f6c2 --- /dev/null +++ b/test/msw-api/setup-worker/response-logging.test.ts @@ -0,0 +1,46 @@ +import { pageWith } from 'page-with' +import { waitFor } from '../../support/waitFor' + +function createResponseLogRegexp(username: string): RegExp { + return new RegExp( + `^\\[MSW\\] \\d{2}:\\d{2}:\\d{2} GET https://api\\.github\\.com/users/${username} 200 OK$`, + ) +} + +test('prints the response info to the console', async () => { + const runtime = await pageWith({ + example: require.resolve('../../rest-api/basic.mocks.ts'), + }) + + const waitForResponseLog = async (exp: RegExp) => { + await waitFor(() => { + expect(runtime.consoleSpy.get('startGroupCollapsed')).toEqual( + expect.arrayContaining([expect.stringMatching(exp)]), + ) + }) + } + + const getResponseLogs = (exp: RegExp) => { + return runtime.consoleSpy.get('startGroupCollapsed').filter((log) => { + return exp.test(log) + }) + } + + const firstResponseLogRegexp = createResponseLogRegexp('octocat') + await runtime.request('https://api.github.com/users/octocat') + await waitForResponseLog(firstResponseLogRegexp) + + // Must print the response summary to the console. + expect(getResponseLogs(firstResponseLogRegexp)).toHaveLength(1) + + const secondResopnseLogRegexp = createResponseLogRegexp('john.doe') + await runtime.request('https://api.github.com/users/john.doe') + await waitForResponseLog(secondResopnseLogRegexp) + + /** + * Must not duplicate response logs for the current and previous requests. + * @see https://github.com/mswjs/msw/issues/1411 + */ + expect(getResponseLogs(secondResopnseLogRegexp)).toHaveLength(1) + expect(getResponseLogs(firstResponseLogRegexp)).toHaveLength(1) +}) diff --git a/test/rest-api/body.mocks.ts b/test/rest-api/body.mocks.ts index e74437512..ead939479 100644 --- a/test/rest-api/body.mocks.ts +++ b/test/rest-api/body.mocks.ts @@ -16,28 +16,30 @@ const handleRequestBody: ResponseResolver = ( return res(ctx.json({ body })) } -const handleMultipartRequestBody: ResponseResolver = - async (req, res, ctx) => { - const { body } = req - - if (typeof body !== 'object') { - throw new Error( - 'Expected multipart request body to be parsed but got string', - ) - } +const handleMultipartRequestBody: ResponseResolver< + MockedRequest, + RestContext +> = async (req, res, ctx) => { + const { body } = req - const resBody: Record = {} - for (const [name, value] of Object.entries(body)) { - if (value instanceof File) { - resBody[name] = await value.text() - } else { - resBody[name] = value as string - } - } + if (typeof body !== 'object') { + throw new Error( + 'Expected multipart request body to be parsed but got string', + ) + } - return res(ctx.json({ body: resBody })) + const resBody: Record = {} + for (const [name, value] of Object.entries(body)) { + if (value instanceof File) { + resBody[name] = await value.text() + } else { + resBody[name] = value as string + } } + return res(ctx.json({ body: resBody })) +} + const worker = setupWorker( rest.get('/login', handleRequestBody), rest.post('/login', handleRequestBody), diff --git a/yarn.lock b/yarn.lock index c0d479b3e..73e9f9d8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9131,13 +9131,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-event-emitter@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd" - integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w== - dependencies: - events "^3.3.0" - strict-event-emitter@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz#365714f0c95f059db31064ca745d5b33e5b30f6e" @@ -9145,6 +9138,13 @@ strict-event-emitter@^0.2.4: dependencies: events "^3.3.0" +strict-event-emitter@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.6.tgz#7bb2022bdabcbf0058cec7118a7bbbfd64367366" + integrity sha512-qDZOqEBoNtKLPb/qAutkXUt7hs3zXgYA1xX4pVa+gZHCZZVLr2r81AzHsK5YrQQhRNphMtkOUyAyOr9e1IxJTw== + dependencies: + events "^3.3.0" + string-argv@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"