From 306357db486c9c7aa621f430cd08621420efc724 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 21 Jul 2024 10:36:06 +0100 Subject: [PATCH 1/2] feat: response builder function now expects a calllog --- docs/docs/@fetch-mock/core/route/response.md | 6 +++--- packages/core/src/Router.js | 3 +-- .../src/__tests__/FetchMock/response-construction.test.js | 4 ++-- .../src/__tests__/FetchMock/response-negotiation.test.js | 8 ++++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/docs/@fetch-mock/core/route/response.md b/docs/docs/@fetch-mock/core/route/response.md index 444da0ea..81bd4c15 100644 --- a/docs/docs/@fetch-mock/core/route/response.md +++ b/docs/docs/@fetch-mock/core/route/response.md @@ -76,9 +76,9 @@ A `Promise` that resolves to any of the options documented above e.g. `new Promi `{Function}` -A function that returns any of the options documented above (including `Promise`. The function will be passed the `url` and `options` `fetch` was called with. If `fetch` was called with a `Request` instance, it will be passed `url` and `options` inferred from the `Request` instance, with the original `Request` passed as a third argument. +A function that is passed a [`CallLog`](/fetch-mock/docs/@fetch-mock/core/CallHistory#calllog-schema) and returns any of the options documented above (including `Promise`). ### Examples -- `(url, opts) => opts.headers.Authorization ? 200 : 403` -- `(_, _, request) => request.headers.get('Authorization') ? 200 : 403` +- `({url, options}) => options.headers.Authorization ? 200 : 403` +- `({request}) => request.headers.get('Authorization') ? 200 : 403` diff --git a/packages/core/src/Router.js b/packages/core/src/Router.js index 7e3c0fb2..a3f8f08f 100644 --- a/packages/core/src/Router.js +++ b/packages/core/src/Router.js @@ -100,7 +100,6 @@ function shouldSendAsObject(responseInput) { * @returns */ const resolveUntilResponseConfig = async (response, normalizedRequest) => { - const { url, options, request } = normalizedRequest; // We want to allow things like // - function returning a Promise for a response // - delaying (using a timeout Promise) a function's execution to generate @@ -111,7 +110,7 @@ const resolveUntilResponseConfig = async (response, normalizedRequest) => { //eslint-disable-next-line no-constant-condition while (true) { if (typeof response === 'function') { - response = response(url, options, request); + response = response(normalizedRequest); } else if (isPromise(response)) { response = await response; // eslint-disable-line no-await-in-loop } else { diff --git a/packages/core/src/__tests__/FetchMock/response-construction.test.js b/packages/core/src/__tests__/FetchMock/response-construction.test.js index c4bb5655..95d5deb5 100644 --- a/packages/core/src/__tests__/FetchMock/response-construction.test.js +++ b/packages/core/src/__tests__/FetchMock/response-construction.test.js @@ -197,7 +197,7 @@ describe('response generation', () => { }); it('construct a response based on the request', async () => { - fm.route('*', (url, opts) => url + opts.headers.header); + fm.route('*', ({ url, options }) => url + options.headers.header); const res = await fm.fetchHandler('http://a.com/', { headers: { header: 'val' }, }); @@ -206,7 +206,7 @@ describe('response generation', () => { }); it('construct a response based on a Request instance', async () => { - fm.route('*', (url, opts, request) => request.json().then(({ a }) => a)); + fm.route('*', ({ request }) => request.json().then(({ a }) => a)); const res = await fm.fetchHandler( new fm.config.Request('http://a.com', { body: JSON.stringify({ a: 'b' }), diff --git a/packages/core/src/__tests__/FetchMock/response-negotiation.test.js b/packages/core/src/__tests__/FetchMock/response-negotiation.test.js index 78cc3db0..a930a3e7 100644 --- a/packages/core/src/__tests__/FetchMock/response-negotiation.test.js +++ b/packages/core/src/__tests__/FetchMock/response-negotiation.test.js @@ -9,7 +9,7 @@ describe('response negotiation', () => { }); it('function', async () => { - fm.route('*', (url) => url); + fm.route('*', ({ url }) => url); const res = await fm.fetchHandler('http://a.com/'); expect(res.status).toEqual(200); expect(await res.text()).toEqual('http://a.com/'); @@ -27,7 +27,7 @@ describe('response negotiation', () => { expect(res.status).toEqual(300); }); it('function that returns a Promise for a body', async () => { - fm.route('*', (url) => Promise.resolve(`test: ${url}`)); + fm.route('*', ({ url }) => Promise.resolve(`test: ${url}`)); const res = await fm.fetchHandler('http://a.com/'); expect(res.status).toEqual(200); expect(await res.text()).toEqual('test: http://a.com/'); @@ -36,7 +36,7 @@ describe('response negotiation', () => { it('Promise for a function that returns a response', async () => { fm.route( 'http://a.com/', - Promise.resolve((url) => `test: ${url}`), + Promise.resolve(({ url }) => `test: ${url}`), ); const res = await fm.fetchHandler('http://a.com/'); expect(res.status).toEqual(200); @@ -79,7 +79,7 @@ describe('response negotiation', () => { }); it('pass values to delayed function', async () => { - fm.route('*', (url) => `delayed: ${url}`, { + fm.route('*', ({ url }) => `delayed: ${url}`, { delay: 10, }); const req = fm.fetchHandler('http://a.com/'); From da9dfe80475f2c95ea9a3652bfe8682ccd4c65fd Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 21 Jul 2024 11:35:54 +0100 Subject: [PATCH 2/2] refactor!: matchers now take normalized requests as input --- .../@fetch-mock/core/more-routing-methods.md | 4 +- packages/core/src/CallHistory.js | 7 +- packages/core/src/FetchMock.js | 1 + packages/core/src/Matchers.js | 39 +++-- packages/core/src/RequestUtils.js | 2 +- packages/core/src/Route.js | 4 +- packages/core/src/Router.js | 4 +- .../core/src/__tests__/CallHistory.test.js | 2 +- .../core/src/__tests__/Matchers/body.test.js | 157 ++++++++++++------ .../src/__tests__/Matchers/express.test.js | 26 +-- .../src/__tests__/Matchers/function.test.js | 61 ++++--- .../core/src/__tests__/Matchers/header.js | 74 ++++++--- .../src/__tests__/Matchers/method.test.js | 66 +++++--- .../__tests__/Matchers/query-string.test.js | 94 ++++++----- .../Matchers/route-config-object.test.js | 85 ++++++---- .../core/src/__tests__/Matchers/url.test.js | 130 ++++++++------- .../src/__tests__/router-integration.test.js | 24 +-- 17 files changed, 472 insertions(+), 308 deletions(-) diff --git a/docs/docs/@fetch-mock/core/more-routing-methods.md b/docs/docs/@fetch-mock/core/more-routing-methods.md index c3795db6..9df0fa93 100644 --- a/docs/docs/@fetch-mock/core/more-routing-methods.md +++ b/docs/docs/@fetch-mock/core/more-routing-methods.md @@ -95,7 +95,9 @@ If your matcher requires access to the body of the request set this to true; bec `{Function}` -A function which takes a route definition object as input, and returns a function of the signature `(url, options, request) => Boolean`. See the examples below for more detail. The function is passed the fetchMock instance as a second parameter in case you need to access any config. +A function which takes a route definition object as input, and returns a function of the signature `(NormalizedRequest) => Boolean`. See the examples below for more detail. The function is passed the fetchMock instance as a second parameter in case you need to access any config. + +// TODO at time of writing the NormalizedRequest schema is still evolving, so more detailed docs will emerge soon ##### Examples diff --git a/packages/core/src/CallHistory.js b/packages/core/src/CallHistory.js index 3f57aa07..3886175a 100644 --- a/packages/core/src/CallHistory.js +++ b/packages/core/src/CallHistory.js @@ -137,12 +137,7 @@ class CallHistory { }); calls = calls.filter(({ url, options }) => { - const { - url: normalizedUrl, - options: normalizedOptions, - request, - } = normalizeRequest(url, options, this.config.Request); - return matcher(normalizedUrl, normalizedOptions, request); + return matcher(normalizeRequest(url, options, this.config.Request)); }); return calls; diff --git a/packages/core/src/FetchMock.js b/packages/core/src/FetchMock.js index e893cc10..3357d3d2 100644 --- a/packages/core/src/FetchMock.js +++ b/packages/core/src/FetchMock.js @@ -80,6 +80,7 @@ const FetchMock = { requestInit, this.config.Request, ); + /** @type {Promise[]} */ const pendingPromises = []; const callLog = { ...normalizedRequest, pendingPromises }; diff --git a/packages/core/src/Matchers.js b/packages/core/src/Matchers.js index 50f540c5..3cd4482f 100644 --- a/packages/core/src/Matchers.js +++ b/packages/core/src/Matchers.js @@ -1,6 +1,6 @@ //@type-check /** @typedef {import('./Route').RouteConfig} RouteConfig */ -/** @typedef {import('./RequestUtils').NormalizedRequestOptions} NormalizedRequestOptions */ +/** @typedef {import('./RequestUtils').NormalizedRequest} NormalizedRequest */ import glob from 'globrex'; import * as regexparam from 'regexparam'; import querystring from 'querystring'; @@ -32,7 +32,7 @@ export const isFunctionMatcher = (matcher) => typeof matcher === 'function'; /** @typedef {string | RegExp | URL} RouteMatcherUrl */ /** @typedef {function(string): boolean} UrlMatcher */ /** @typedef {function(string): UrlMatcher} UrlMatcherGenerator */ -/** @typedef {function(string, NormalizedRequestOptions, Request): boolean} RouteMatcherFunction */ +/** @typedef {function(NormalizedRequest): boolean} RouteMatcherFunction */ /** @typedef {function(RouteConfig): RouteMatcherFunction} MatcherGenerator */ /** @typedef {RouteMatcherUrl | RouteMatcherFunction} RouteMatcher */ @@ -47,19 +47,27 @@ export const isFunctionMatcher = (matcher) => typeof matcher === 'function'; * @type {Object.} */ const stringMatchers = { - begin: (targetString) => (url) => url.indexOf(targetString) === 0, - end: (targetString) => (url) => - url.substr(-targetString.length) === targetString, + begin: + (targetString) => + ({ url }) => + url.indexOf(targetString) === 0, + end: + (targetString) => + ({ url }) => + url.substr(-targetString.length) === targetString, glob: (targetString) => { const urlRX = glob(targetString); - return (url) => urlRX.regex.test(url); + return ({ url }) => urlRX.regex.test(url); }, express: (targetString) => { const urlRX = regexparam.parse(targetString); - return (url) => urlRX.pattern.test(getPath(url)); + return ({ url }) => urlRX.pattern.test(getPath(url)); }, - path: (targetString) => (url) => getPath(url) === targetString, + path: + (targetString) => + ({ url }) => + getPath(url) === targetString, }; /** * @type {MatcherGenerator} @@ -69,7 +77,7 @@ const getHeaderMatcher = ({ headers: expectedHeaders }) => { return; } const expectation = normalizeHeaders(expectedHeaders); - return (url, { headers = {} }) => { + return ({ options: { headers = {} } }) => { // TODO do something to handle multi value headers const lowerCaseHeaders = normalizeHeaders(headers); return Object.keys(expectation).every( @@ -84,7 +92,7 @@ const getMethodMatcher = ({ method: expectedMethod }) => { if (!expectedMethod) { return; } - return (url, { method }) => { + return ({ options: { method } = {} }) => { const actualMethod = method ? method.toLowerCase() : 'get'; return expectedMethod === actualMethod; }; @@ -98,7 +106,7 @@ const getQueryStringMatcher = ({ query: passedQuery }) => { } const expectedQuery = querystring.parse(querystring.stringify(passedQuery)); const keys = Object.keys(expectedQuery); - return (url) => { + return ({ url }) => { const query = querystring.parse(getQuery(url)); return keys.every((key) => { if (Array.isArray(query[key])) { @@ -129,7 +137,7 @@ const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => { } const expectedKeys = Object.keys(expectedParams); const re = regexparam.parse(matcherUrl.replace(/^express:/, '')); - return (url) => { + return ({ url }) => { const vals = re.pattern.exec(getPath(url)) || []; vals.shift(); /** @type {Object.} */ @@ -148,7 +156,8 @@ const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => { const getBodyMatcher = (route) => { const { body: expectedBody } = route; - return (url, { body, method = 'get' }) => { + return ({ url, options: { body, method = 'get' } }) => { + console.log({ url, body, method }); if (method.toLowerCase() === 'get') { // GET requests don’t send a body so the body matcher should be ignored for them return true; @@ -185,7 +194,7 @@ const getFullUrlMatcher = (route, matcherUrl, query) => { route.url = expectedUrl; } - return (url) => { + return ({ url }) => { if (query && expectedUrl.indexOf('?')) { return getPath(url) === getPath(expectedUrl); } @@ -208,7 +217,7 @@ const getUrlMatcher = (route) => { } if (matcherUrl instanceof RegExp) { - return (url) => matcherUrl.test(url); + return ({ url }) => matcherUrl.test(url); } if (matcherUrl instanceof URL) { if (matcherUrl.href) { diff --git a/packages/core/src/RequestUtils.js b/packages/core/src/RequestUtils.js index 65dad0b1..838fc8bc 100644 --- a/packages/core/src/RequestUtils.js +++ b/packages/core/src/RequestUtils.js @@ -53,7 +53,7 @@ const isRequest = (urlOrRequest, Request) => * @param {typeof Request} Request * @returns {NormalizedRequest} */ -export function normalizeRequest(urlOrRequest, options, Request) { +export function normalizeRequest(urlOrRequest, options = {}, Request) { if (isRequest(urlOrRequest, Request)) { /** @type {NormalizedRequestOptions} */ const derivedOptions = { diff --git a/packages/core/src/Route.js b/packages/core/src/Route.js index 1341b44e..c3aa15ef 100644 --- a/packages/core/src/Route.js +++ b/packages/core/src/Route.js @@ -148,8 +148,8 @@ class Route { })); this.config.usesBody = activeMatchers.some(({ usesBody }) => usesBody); /** @type {RouteMatcherFunction} */ - this.matcher = (url, options = {}, request) => - activeMatchers.every(({ matcher }) => matcher(url, options, request)); + this.matcher = (normalizedRequest) => + activeMatchers.every(({ matcher }) => matcher(normalizedRequest)); } /** * @returns {void} diff --git a/packages/core/src/Router.js b/packages/core/src/Router.js index a3f8f08f..f1ad4d7f 100644 --- a/packages/core/src/Router.js +++ b/packages/core/src/Router.js @@ -163,7 +163,7 @@ export default class Router { } normalizedRequest.signal.addEventListener('abort', abort); } - + console.log(this.routes); if (this.needsToReadBody(request)) { options.body = await options.body; } @@ -172,7 +172,7 @@ export default class Router { ? [...this.routes, this.fallbackRoute] : this.routes; const route = routesToTry.find((route) => - route.matcher(url, options, request), + route.matcher(normalizedRequest), ); if (route) { diff --git a/packages/core/src/__tests__/CallHistory.test.js b/packages/core/src/__tests__/CallHistory.test.js index 47c70989..3b31bcdc 100644 --- a/packages/core/src/__tests__/CallHistory.test.js +++ b/packages/core/src/__tests__/CallHistory.test.js @@ -231,7 +231,7 @@ describe('CallHistory', () => { }); describe('filtering with a matcher', () => { - //TODO write a test that just makes it clear this is contracted out to Route + // TODO write a test that just makes it clear this is contracted out to Route // spy on route constructor, and then on matcher for that route it('should be able to filter with a url matcher', async () => { fm.catch(); diff --git a/packages/core/src/__tests__/Matchers/body.test.js b/packages/core/src/__tests__/Matchers/body.test.js index 911434b4..676b4a85 100644 --- a/packages/core/src/__tests__/Matchers/body.test.js +++ b/packages/core/src/__tests__/Matchers/body.test.js @@ -1,14 +1,18 @@ import { describe, expect, it } from 'vitest'; import Route from '../../Route.js'; - +import Router from '../../Router.js'; +import { normalizeRequest } from '../../RequestUtils.js'; describe('body matching', () => { //TODO add a test for matching an asynchronous body it('should not match if no body provided in request', () => { const route = new Route({ body: { foo: 'bar' }, response: 200 }); expect( - route.matcher('http://a.com/', { - method: 'POST', + route.matcher({ + url: 'http://a.com/', + options: { + method: 'POST', + }, }), ).toBe(false); }); @@ -17,34 +21,53 @@ describe('body matching', () => { const route = new Route({ body: { foo: 'bar' }, response: 200 }); expect( - route.matcher('http://a.com/', { - method: 'POST', - body: JSON.stringify({ foo: 'bar' }), + route.matcher({ + url: 'http://a.com/', + options: { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + }, }), ).toBe(true); }); - it('should match when using Request', () => { - const route = new Route({ body: { foo: 'bar' }, response: 200 }); - - expect( - route.matcher( - new Request('http://a.com/', { - method: 'POST', - body: JSON.stringify({ foo: 'bar' }), - }), - ), - ).toBe(true); + // Note, using Router to test this as normalization of Request to normalizedRequest + // happens in there + // TODO Q: Should it? + // TODO need to split execute into 2?? + // 1. .route() (which can be used to test Routes with normalization applied up front) + // 2. .respond() + it('should match when using Request', async () => { + const route = new Route({ + body: { foo: 'bar' }, + response: 200, + Headers, + Response, + }); + const router = new Router({ Request, Headers }, { routes: [route] }); + const normalizedRequest = normalizeRequest( + new Request('http://a.com/', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + }), + undefined, + Request, + ); + const response = await router.execute(normalizedRequest, normalizedRequest); + expect(response.status).toBe(200); }); it('should match if body sent matches expected body', () => { const route = new Route({ body: { foo: 'bar' }, response: 200 }); expect( - route.matcher('http://a.com/', { - method: 'POST', - body: JSON.stringify({ foo: 'bar' }), - headers: { 'Content-Type': 'application/json' }, + route.matcher({ + url: 'http://a.com/', + options: { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + headers: { 'Content-Type': 'application/json' }, + }, }), ).toBe(true); }); @@ -53,10 +76,13 @@ describe('body matching', () => { const route = new Route({ body: { foo: 'bar' }, response: 200 }); expect( - route.matcher('http://a.com/', { - method: 'POST', - body: JSON.stringify({ foo: 'woah!!!' }), - headers: { 'Content-Type': 'application/json' }, + route.matcher({ + url: 'http://a.com/', + options: { + method: 'POST', + body: JSON.stringify({ foo: 'woah!!!' }), + headers: { 'Content-Type': 'application/json' }, + }, }), ).toBe(false); }); @@ -65,10 +91,13 @@ describe('body matching', () => { const route = new Route({ body: { foo: 'bar' }, response: 200 }); expect( - route.matcher('http://a.com/', { - method: 'POST', - body: new ArrayBuffer(8), - headers: { 'Content-Type': 'application/json' }, + route.matcher({ + url: 'http://a.com/', + options: { + method: 'POST', + body: new ArrayBuffer(8), + headers: { 'Content-Type': 'application/json' }, + }, }), ).toBe(false); }); @@ -84,19 +113,22 @@ describe('body matching', () => { }); expect( - route.matcher('http://a.com/', { - method: 'POST', - body: JSON.stringify({ - baz: 'qux', - foo: 'bar', - }), - headers: { 'Content-Type': 'application/json' }, + route.matcher({ + url: 'http://a.com/', + options: { + method: 'POST', + body: JSON.stringify({ + baz: 'qux', + foo: 'bar', + }), + headers: { 'Content-Type': 'application/json' }, + }, }), ).toBe(true); }); // TODO - I think this shoudl actually throw - it('should ignore the body option matcher if request was GET', () => { + it.skip('should ignore the body option matcher if request was GET', () => { const route = new Route({ body: { foo: 'bar', @@ -106,7 +138,7 @@ describe('body matching', () => { response: 200, }); - expect(route.matcher('http://a.com/')).toBe(true); + expect(route.matcher({ url: 'http://a.com/' })).toBe(true); }); describe('partial body matching', () => { @@ -117,9 +149,12 @@ describe('body matching', () => { response: 200, }); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ ham: 'sandwich', egg: 'mayonaise' }), + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ ham: 'sandwich', egg: 'mayonaise' }), + }, }), ).toBe(true); }); @@ -131,11 +166,14 @@ describe('body matching', () => { response: 200, }); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ - meal: { ham: 'sandwich', egg: 'mayonaise' }, - }), + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ + meal: { ham: 'sandwich', egg: 'mayonaise' }, + }), + }, }), ).toBe(true); }); @@ -147,9 +185,12 @@ describe('body matching', () => { response: 200, }); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ meal: { ham: 'sandwich' } }), + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ meal: { ham: 'sandwich' } }), + }, }), ).toBe(false); }); @@ -161,9 +202,12 @@ describe('body matching', () => { response: 200, }); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ ham: [1, 2, 3] }), + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ ham: [1, 2, 3] }), + }, }), ).toBe(true); }); @@ -175,9 +219,12 @@ describe('body matching', () => { response: 200, }); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ ham: [1, 2, 3] }), + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ ham: [1, 2, 3] }), + }, }), ).toBe(false); }); diff --git a/packages/core/src/__tests__/Matchers/express.test.js b/packages/core/src/__tests__/Matchers/express.test.js index 0f19ef11..678799e3 100644 --- a/packages/core/src/__tests__/Matchers/express.test.js +++ b/packages/core/src/__tests__/Matchers/express.test.js @@ -8,9 +8,9 @@ describe('express path parameter matching', () => { response: 200, params: { instance: 'b' }, }); - expect(route.matcher('/')).toBe(false); - expect(route.matcher('/type/a')).toBe(false); - expect(route.matcher('/type/b')).toBe(true); + expect(route.matcher({ url: '/' })).toBe(false); + expect(route.matcher({ url: '/type/a' })).toBe(false); + expect(route.matcher({ url: '/type/b' })).toBe(true); }); it('can match multiple path parameters', () => { @@ -19,11 +19,11 @@ describe('express path parameter matching', () => { response: 200, params: { instance: 'b', type: 'cat' }, }); - expect(route.matcher('/')).toBe(false); - expect(route.matcher('/dog/a')).toBe(false); - expect(route.matcher('/cat/a')).toBe(false); - expect(route.matcher('/dog/b')).toBe(false); - expect(route.matcher('/cat/b')).toBe(true); + expect(route.matcher({ url: '/' })).toBe(false); + expect(route.matcher({ url: '/dog/a' })).toBe(false); + expect(route.matcher({ url: '/cat/a' })).toBe(false); + expect(route.matcher({ url: '/dog/b' })).toBe(false); + expect(route.matcher({ url: '/cat/b' })).toBe(true); }); it('can match a path parameter on a full url', () => { @@ -32,14 +32,16 @@ describe('express path parameter matching', () => { response: 200, params: { instance: 'b' }, }); - expect(route.matcher('http://site.com/')).toBe(false); - expect(route.matcher('http://site.com/type/a')).toBe(false); - expect(route.matcher('http://site.com/type/b')).toBe(true); + expect(route.matcher({ url: 'http://site.com/' })).toBe(false); + expect(route.matcher({ url: 'http://site.com/type/a' })).toBe(false); + expect(route.matcher({ url: 'http://site.com/type/b' })).toBe(true); }); it('can match fully qualified url', () => { const route = new Route({ url: 'express:/apps/:id', response: 200 }); - expect(route.matcher('https://api.example.com/apps/abc')).toBe(true); + expect(route.matcher({ url: 'https://api.example.com/apps/abc' })).toBe( + true, + ); }); }); diff --git a/packages/core/src/__tests__/Matchers/function.test.js b/packages/core/src/__tests__/Matchers/function.test.js index d0f4d0db..7efc0662 100644 --- a/packages/core/src/__tests__/Matchers/function.test.js +++ b/packages/core/src/__tests__/Matchers/function.test.js @@ -4,37 +4,50 @@ import Route from '../../Route.js'; describe('function matching', () => { it('match using custom function', () => { const route = new Route({ - matcherFunction: (url, opts) => + matcherFunction: ({ url, options }) => url.indexOf('logged-in') > -1 && - opts && - opts.headers && - opts.headers.authorized === true, + options && + options.headers && + options.headers.authorized === true, response: 200, }); expect( - route.matcher('http://a.com/12345', { - headers: { authorized: true }, + route.matcher({ + url: 'http://a.com/12345', + options: { + headers: { authorized: true }, + }, }), ).toBe(false); - expect(route.matcher('http://a.com/logged-in')).toBe(false); + expect(route.matcher({ url: 'http://a.com/logged-in' })).toBe(false); expect( - route.matcher('http://a.com/logged-in', { - headers: { authorized: true }, + route.matcher({ + url: 'http://a.com/logged-in', + options: { + headers: { authorized: true }, + }, }), ).toBe(true); }); it('match using custom function using request body', () => { const route = new Route({ - matcherFunction: (url, opts) => opts.body === 'a string', + matcherFunction: (req) => { + return req.options.body === 'a string'; + }, response: 200, }); - expect(route.matcher('http://a.com/logged-in')).toBe(false); + expect(route.matcher({ url: 'http://a.com/logged-in', options: {} })).toBe( + false, + ); expect( - route.matcher('http://a.com/logged-in', { - method: 'post', - body: 'a string', + route.matcher({ + url: 'http://a.com/logged-in', + options: { + method: 'post', + body: 'a string', + }, }), ).toBe(true); }); @@ -43,19 +56,25 @@ describe('function matching', () => { const route = new Route({ url: 'end:profile', response: 200, - matcherFunction: (url, opts) => - opts && opts.headers && opts.headers.authorized === true, + matcherFunction: ({ options }) => + options && options.headers && options.headers.authorized === true, }); - expect(route.matcher('http://a.com/profile')).toBe(false); + expect(route.matcher({ url: 'http://a.com/profile' })).toBe(false); expect( - route.matcher('http://a.com/not', { - headers: { authorized: true }, + route.matcher({ + url: 'http://a.com/not', + options: { + headers: { authorized: true }, + }, }), ).toBe(false); expect( - route.matcher('http://a.com/profile', { - headers: { authorized: true }, + route.matcher({ + url: 'http://a.com/profile', + options: { + headers: { authorized: true }, + }, }), ).toBe(true); }); diff --git a/packages/core/src/__tests__/Matchers/header.js b/packages/core/src/__tests__/Matchers/header.js index a95dc27a..1f64b586 100644 --- a/packages/core/src/__tests__/Matchers/header.js +++ b/packages/core/src/__tests__/Matchers/header.js @@ -9,7 +9,7 @@ describe('header matching', () => { response: 200, }); - expect(route.matcher('http://a.com/')).toBe(true); + expect(route.matcher({ url: 'http://a.com/' })).toBe(true); }); it("not match when headers don't match", () => { @@ -20,8 +20,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { a: 'c' }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: 'c' }, + }, }), ).toBe(false); }); @@ -34,8 +37,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { a: 'b' }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: 'b' }, + }, }), ).toBe(true); }); @@ -48,8 +54,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { A: 'b' }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { A: 'b' }, + }, }), ).toBe(true); }); @@ -63,8 +72,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { a: ['b', 'c'] }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: ['b', 'c'] }, + }, }), ).toBe(true); }); @@ -77,8 +89,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { a: ['b', 'c'] }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: ['b', 'c'] }, + }, }), ).toBe(false); }); @@ -91,8 +106,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { a: 'b', c: 'd' }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: 'b', c: 'd' }, + }, }), ).toBe(true); }); @@ -105,8 +123,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: { a: 'b' }, + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: 'b' }, + }, }), ).toBe(false); }); @@ -119,8 +140,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com/', { - headers: new Headers({ a: 'b' }), + route.matcher({ + url: 'http://a.com/', + options: { + headers: new Headers({ a: 'b' }), + }, }), ).toBe(true); }); @@ -132,10 +156,13 @@ describe('header matching', () => { headers: { a: 'b' }, }); - expect(route.matcher('http://domain.com/person')).toBe(false); + expect(route.matcher({ url: 'http://domain.com/person' })).toBe(false); expect( - route.matcher('http://domain.com/person', { - headers: { a: 'b' }, + route.matcher({ + url: 'http://domain.com/person', + options: { + headers: { a: 'b' }, + }, }), ).toBe(true); }); @@ -162,8 +189,11 @@ describe('header matching', () => { }); expect( - route.matcher('http://a.com', { - headers: new MyHeaders({ a: 'b' }), + route.matcher({ + url: 'http://a.com', + options: { + headers: new MyHeaders({ a: 'b' }), + }, }), ).toBe(true); }); diff --git a/packages/core/src/__tests__/Matchers/method.test.js b/packages/core/src/__tests__/Matchers/method.test.js index 3d8a940d..d30c8203 100644 --- a/packages/core/src/__tests__/Matchers/method.test.js +++ b/packages/core/src/__tests__/Matchers/method.test.js @@ -6,50 +6,74 @@ describe('method matching', () => { it('match any method by default', () => { const route = new Route({ url: '*', response: 200 }); - expect(route.matcher('http://a.com/', { method: 'GET' })).toBe(true); - expect(route.matcher('http://a.com/', { method: 'POST' })).toBe(true); + expect( + route.matcher({ url: 'http://a.com/', options: { method: 'GET' } }), + ).toBe(true); + expect( + route.matcher({ url: 'http://a.com/', options: { method: 'POST' } }), + ).toBe(true); }); it('configure an exact method to match', () => { const route = new Route({ method: 'POST', response: 200 }); - expect(route.matcher('http://a.com/', { method: 'GET' })).toBe(false); - expect(route.matcher('http://a.com/', { method: 'POST' })).toBe(true); + expect( + route.matcher({ url: 'http://a.com/', options: { method: 'GET' } }), + ).toBe(false); + expect( + route.matcher({ url: 'http://a.com/', options: { method: 'POST' } }), + ).toBe(true); }); it('match implicit GET', () => { const route = new Route({ method: 'GET', response: 200 }); - expect(route.matcher('http://a.com/')).toBe(true); + expect(route.matcher({ url: 'http://a.com/' })).toBe(true); }); it('be case insensitive', () => { const upperCaseRoute = new Route({ method: 'POST', response: 200 }); const lowerCaseRoute = new Route({ method: 'post', response: 200 }); - expect(upperCaseRoute.matcher('http://a.com/', { method: 'post' })).toBe( - true, - ); - expect(upperCaseRoute.matcher('http://a.com/', { method: 'POST' })).toBe( - true, - ); - expect(lowerCaseRoute.matcher('http://a.com/', { method: 'post' })).toBe( - true, - ); - expect(lowerCaseRoute.matcher('http://a.com/', { method: 'POST' })).toBe( - true, - ); + expect( + upperCaseRoute.matcher({ + url: 'http://a.com/', + options: { method: 'post' }, + }), + ).toBe(true); + expect( + upperCaseRoute.matcher({ + url: 'http://a.com/', + options: { method: 'POST' }, + }), + ).toBe(true); + expect( + lowerCaseRoute.matcher({ + url: 'http://a.com/', + options: { method: 'post' }, + }), + ).toBe(true); + expect( + lowerCaseRoute.matcher({ + url: 'http://a.com/', + options: { method: 'POST' }, + }), + ).toBe(true); }); it('can be used alongside function matchers', () => { const route = new Route({ method: 'POST', - matcherFunction: (url) => /a\.com/.test(url), + matcherFunction: ({ url }) => /a\.com/.test(url), response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://b.com', { method: 'POST' })).toBe(false); - expect(route.matcher('http://a.com', { method: 'POST' })).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect( + route.matcher({ url: 'http://b.com', options: { method: 'POST' } }), + ).toBe(false); + expect( + route.matcher({ url: 'http://a.com', options: { method: 'POST' } }), + ).toBe(true); }); }); diff --git a/packages/core/src/__tests__/Matchers/query-string.test.js b/packages/core/src/__tests__/Matchers/query-string.test.js index 127a3741..363c301d 100644 --- a/packages/core/src/__tests__/Matchers/query-string.test.js +++ b/packages/core/src/__tests__/Matchers/query-string.test.js @@ -8,8 +8,8 @@ describe('query string matching', () => { response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b&c=d')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b&c=d' })).toBe(true); }); it('match a query string against a URL object', () => { @@ -20,7 +20,7 @@ describe('query string matching', () => { const url = new URL('http://a.com/path'); url.searchParams.append('a', 'b'); url.searchParams.append('c', 'd'); - expect(route.matcher(url)).toBe(true); + expect(route.matcher({ url })).toBe(true); }); it('match a query string against a relative path', () => { @@ -29,7 +29,7 @@ describe('query string matching', () => { response: 200, }); const url = '/path?a=b'; - expect(route.matcher(url)).toBe(true); + expect(route.matcher({ url })).toBe(true); }); it('match multiple query strings', () => { @@ -38,10 +38,10 @@ describe('query string matching', () => { response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(false); - expect(route.matcher('http://a.com?a=b&c=d')).toBe(true); - expect(route.matcher('http://a.com?c=d&a=b')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b&c=d' })).toBe(true); + expect(route.matcher({ url: 'http://a.com?c=d&a=b' })).toBe(true); }); it('ignore irrelevant query strings', () => { @@ -50,7 +50,7 @@ describe('query string matching', () => { response: 200, }); - expect(route.matcher('http://a.com?a=b&c=d&e=f')).toBe(true); + expect(route.matcher({ url: 'http://a.com?a=b&c=d&e=f' })).toBe(true); }); it('match an empty query string', () => { const route = new Route({ @@ -58,8 +58,8 @@ describe('query string matching', () => { response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=' })).toBe(true); }); describe('value coercion', () => { @@ -70,8 +70,8 @@ describe('query string matching', () => { }, response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=1')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=1' })).toBe(true); }); it('coerce floats to strings and match', () => { @@ -81,8 +81,8 @@ describe('query string matching', () => { }, response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=1.2')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=1.2' })).toBe(true); }); it('coerce booleans to strings and match', () => { @@ -99,10 +99,10 @@ describe('query string matching', () => { response: 200, }); - expect(trueRoute.matcher('http://a.com')).toBe(false); - expect(falseRoute.matcher('http://a.com')).toBe(false); - expect(trueRoute.matcher('http://a.com?a=true')).toBe(true); - expect(falseRoute.matcher('http://a.com?b=false')).toBe(true); + expect(trueRoute.matcher({ url: 'http://a.com' })).toBe(false); + expect(falseRoute.matcher({ url: 'http://a.com' })).toBe(false); + expect(trueRoute.matcher({ url: 'http://a.com?a=true' })).toBe(true); + expect(falseRoute.matcher({ url: 'http://a.com?b=false' })).toBe(true); }); it('coerce undefined to an empty string and match', () => { @@ -112,8 +112,8 @@ describe('query string matching', () => { }, response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=' })).toBe(true); }); it('coerce null to an empty string and match', () => { @@ -123,8 +123,8 @@ describe('query string matching', () => { }, response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=' })).toBe(true); }); it('coerce an object to an empty string and match', () => { @@ -134,8 +134,8 @@ describe('query string matching', () => { }, response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=' })).toBe(true); }); it('can match a query string with different value types', () => { @@ -150,9 +150,11 @@ describe('query string matching', () => { }, }); - expect(route.matcher('http://a.com')).toBe(false); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); expect( - route.matcher('http://a.com?t=true&f=false&u=&num=1&arr=a&arr='), + route.matcher({ + url: 'http://a.com?t=true&f=false&u=&num=1&arr=a&arr=', + }), ).toBe(true); }); }); @@ -162,26 +164,26 @@ describe('query string matching', () => { it('match repeated query strings', () => { const route = new Route({ query: { a: ['b', 'c'] }, response: 200 }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(false); - expect(route.matcher('http://a.com?a=b&a=c')).toBe(true); - expect(route.matcher('http://a.com?a=b&a=c&a=d')).toBe(false); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b&a=c' })).toBe(true); + expect(route.matcher({ url: 'http://a.com?a=b&a=c&a=d' })).toBe(false); }); it('match repeated query strings in any order', () => { const route = new Route({ query: { a: ['b', 'c'] }, response: 200 }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b&a=c')).toBe(true); - expect(route.matcher('http://a.com?a=c&a=b')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b&a=c' })).toBe(true); + expect(route.matcher({ url: 'http://a.com?a=c&a=b' })).toBe(true); }); it('match a query string array of length 1', () => { const route = new Route({ query: { a: ['b'] }, response: 200 }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(true); - expect(route.matcher('http://a.com?a=b&a=c')).toBe(false); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(true); + expect(route.matcher({ url: 'http://a.com?a=b&a=c' })).toBe(false); }); it('match a repeated query string with an empty value', () => { @@ -190,9 +192,9 @@ describe('query string matching', () => { response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(false); - expect(route.matcher('http://a.com?a=b&a=')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b&a=' })).toBe(true); }); }); @@ -205,10 +207,10 @@ describe('query string matching', () => { query: { a: 'b' }, }); - expect(route.matcher('http://a.com?c=d')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(false); - expect(route.matcher('http://a.com?c=d&a=b')).toBe(true); - expect(route.matcher('http://a.com?a=b&c=d')).toBe(true); + expect(route.matcher({ url: 'http://a.com?c=d' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?c=d&a=b' })).toBe(true); + expect(route.matcher({ url: 'http://a.com?a=b&c=d' })).toBe(true); }); it('can be used alongside function matchers', () => { @@ -218,8 +220,8 @@ describe('query string matching', () => { query: { a: 'b' }, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(true); }); }); }); diff --git a/packages/core/src/__tests__/Matchers/route-config-object.test.js b/packages/core/src/__tests__/Matchers/route-config-object.test.js index fff37d0b..b9b7c4cd 100644 --- a/packages/core/src/__tests__/Matchers/route-config-object.test.js +++ b/packages/core/src/__tests__/Matchers/route-config-object.test.js @@ -6,31 +6,37 @@ import Route from '../../Route.js'; describe('matcher object', () => { it('use matcher object with matcher property', () => { const route = new Route({ url: 'http://a.com', response: 200 }); - expect(route.matcher('http://a.com')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(true); }); it('use matcher object with url property', () => { const route = new Route({ url: 'http://a.com', response: 200 }); - expect(route.matcher('http://a.com')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(true); }); it('can use function and url simultaneously', () => { const route = new Route({ url: 'end:path', - matcherFunction: (url, opts) => - opts && opts.headers && opts.headers.authorized === true, + matcherFunction: ({ options }) => + options && options.headers && options.headers.authorized === true, response: 200, }); - expect(route.matcher('http://a.com/path')).toBe(false); + expect(route.matcher({ url: 'http://a.com/path' })).toBe(false); expect( - route.matcher('http://a.com', { - headers: { authorized: true }, + route.matcher({ + url: 'http://a.com', + options: { + headers: { authorized: true }, + }, }), ).toBe(false); expect( - route.matcher('http://a.com/path', { - headers: { authorized: true }, + route.matcher({ + url: 'http://a.com/path', + options: { + headers: { authorized: true }, + }, }), ).toBe(true); }); @@ -38,7 +44,7 @@ describe('matcher object', () => { // TODO this shoudl probably be an error it.skip('if no url provided, match any url', () => { const route = new Route({ response: 200 }); - expect(route.matcher('http://a.com')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(true); }); //TODO be stronger on discouraging this @@ -59,13 +65,19 @@ describe('matcher object', () => { }); expect( - route.matcher('http://a.com', { - headers: { a: 'c' }, + route.matcher({ + url: 'http://a.com', + options: { + headers: { a: 'c' }, + }, }), ).toBe(false); expect( - route.matcher('http://a.com', { - headers: { a: 'b' }, + route.matcher({ + url: 'http://a.com', + options: { + headers: { a: 'b' }, + }, }), ).toBe(true); }); @@ -77,8 +89,8 @@ describe('matcher object', () => { response: 200, }); - expect(route.matcher('http://a.com')).toBe(false); - expect(route.matcher('http://a.com?a=b')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(false); + expect(route.matcher({ url: 'http://a.com?a=b' })).toBe(true); }); it('can match path parameter', () => { @@ -87,30 +99,40 @@ describe('matcher object', () => { params: { var: 'b' }, response: 200, }); - expect(route.matcher('/')).toBe(false); - expect(route.matcher('/type/a')).toBe(false); - expect(route.matcher('/type/b')).toBe(true); + expect(route.matcher({ url: '/' })).toBe(false); + expect(route.matcher({ url: '/type/a' })).toBe(false); + expect(route.matcher({ url: '/type/b' })).toBe(true); }); it('can match method', () => { const route = new Route({ method: 'POST', response: 200 }); - expect(route.matcher('http://a.com', { method: 'GET' })).toBe(false); - expect(route.matcher('http://a.com', { method: 'POST' })).toBe(true); + expect( + route.matcher({ url: 'http://a.com', options: { method: 'GET' } }), + ).toBe(false); + expect( + route.matcher({ url: 'http://a.com', options: { method: 'POST' } }), + ).toBe(true); }); it('can match body', () => { const route = new Route({ body: { foo: 'bar' }, response: 200 }); expect( - route.matcher('http://a.com', { - method: 'POST', + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + }, }), ).toBe(false); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ foo: 'bar' }), - headers: { 'Content-Type': 'application/json' }, + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + headers: { 'Content-Type': 'application/json' }, + }, }), ).toBe(true); }); @@ -122,9 +144,12 @@ describe('matcher object', () => { response: 200, }); expect( - route.matcher('http://a.com', { - method: 'POST', - body: JSON.stringify({ a: 1, b: 2 }), + route.matcher({ + url: 'http://a.com', + options: { + method: 'POST', + body: JSON.stringify({ a: 1, b: 2 }), + }, }), ).toBe(true); }); diff --git a/packages/core/src/__tests__/Matchers/url.test.js b/packages/core/src/__tests__/Matchers/url.test.js index 1178cad5..938efb4b 100644 --- a/packages/core/src/__tests__/Matchers/url.test.js +++ b/packages/core/src/__tests__/Matchers/url.test.js @@ -4,35 +4,35 @@ import Route from '../../Route.js'; describe('url matching', () => { it('match exact strings', () => { const route = new Route({ url: 'http://a.com/path', response: 200 }); - expect(route.matcher('http://a.com/pat')).toBe(false); - expect(route.matcher('http://a.com/paths')).toBe(false); - expect(route.matcher('http://a.co/path')).toBe(false); - expect(route.matcher('http://a.com/path')).toBe(true); - expect(route.matcher('//a.com/path')).toBe(true); + expect(route.matcher({ url: 'http://a.com/pat' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/paths' })).toBe(false); + expect(route.matcher({ url: 'http://a.co/path' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/path' })).toBe(true); + expect(route.matcher({ url: '//a.com/path' })).toBe(true); }); it('match string objects', () => { const route = new Route({ url: 'http://a.com/path', response: 200 }); - expect(route.matcher(new String('http://a.com/path'))).toBe(true); // eslint-disable-line no-new-wrappers + expect(route.matcher({ url: new String('http://a.com/path') })).toBe(true); // eslint-disable-line no-new-wrappers }); it('match exact strings with relative url', () => { const route = new Route({ url: '/path', response: 200 }); - expect(route.matcher('/pat')).toBe(false); - expect(route.matcher('/paths')).toBe(false); - expect(route.matcher('/path')).toBe(true); + expect(route.matcher({ url: '/pat' })).toBe(false); + expect(route.matcher({ url: '/paths' })).toBe(false); + expect(route.matcher({ url: '/path' })).toBe(true); }); it('match exact string against URL object', () => { const route = new Route({ url: 'http://a.com/path', response: 200 }); const url = new URL('http://a.com/path'); - expect(route.matcher(url)).toBe(true); + expect(route.matcher({ url })).toBe(true); }); it('match using URL object as matcher', () => { const url = new URL('http://a.com/path'); const route = new Route({ url: url, response: 200 }); - expect(route.matcher('http://a.com/path')).toBe(true); + expect(route.matcher({ url: 'http://a.com/path' })).toBe(true); }); it('match begin: keyword', () => { @@ -41,86 +41,86 @@ describe('url matching', () => { response: 200, }); - expect(route.matcher('http://b.com/path')).toBe(false); - expect(route.matcher('http://a.com/pat')).toBe(false); - expect(route.matcher('http://a.com/path')).toBe(true); - expect(route.matcher('http://a.com/paths')).toBe(true); + expect(route.matcher({ url: 'http://b.com/path' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/pat' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/path' })).toBe(true); + expect(route.matcher({ url: 'http://a.com/paths' })).toBe(true); }); it('match end: keyword', () => { const route = new Route({ url: 'end:com/path', response: 200 }); - expect(route.matcher('http://a.com/paths')).toBe(false); - expect(route.matcher('http://a.com/pat')).toBe(false); - expect(route.matcher('http://a.com/path')).toBe(true); - expect(route.matcher('http://b.com/path')).toBe(true); + expect(route.matcher({ url: 'http://a.com/paths' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/pat' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/path' })).toBe(true); + expect(route.matcher({ url: 'http://b.com/path' })).toBe(true); }); it('match glob: keyword', () => { const route = new Route({ url: 'glob:/its/*/*', response: 200 }); - expect(route.matcher('/its/alive')).toBe(false); - expect(route.matcher('/its/a/boy')).toBe(true); - expect(route.matcher('/its/a/girl')).toBe(true); + expect(route.matcher({ url: '/its/alive' })).toBe(false); + expect(route.matcher({ url: '/its/a/boy' })).toBe(true); + expect(route.matcher({ url: '/its/a/girl' })).toBe(true); }); it('match express: keyword', () => { const route = new Route({ url: 'express:/its/:word', response: 200 }); - expect(route.matcher('/its')).toBe(false); - expect(route.matcher('/its/')).toBe(false); - expect(route.matcher('/its/a/girl')).toBe(false); - expect(route.matcher('/its/alive')).toBe(true); + expect(route.matcher({ url: '/its' })).toBe(false); + expect(route.matcher({ url: '/its/' })).toBe(false); + expect(route.matcher({ url: '/its/a/girl' })).toBe(false); + expect(route.matcher({ url: '/its/alive' })).toBe(true); }); it('match path: keyword', () => { const route = new Route({ url: 'path:/its/:word', response: 200 }); - expect(route.matcher('/its/boy')).toBe(false); - expect(route.matcher('/its/:word/still')).toBe(false); - expect(route.matcher('/its/:word')).toBe(true); - expect(route.matcher('/its/:word?brain=false')).toBe(true); + expect(route.matcher({ url: '/its/boy' })).toBe(false); + expect(route.matcher({ url: '/its/:word/still' })).toBe(false); + expect(route.matcher({ url: '/its/:word' })).toBe(true); + expect(route.matcher({ url: '/its/:word?brain=false' })).toBe(true); }); it('match wildcard string', () => { const route = new Route({ url: '*', response: 200 }); - expect(route.matcher('http://a.com')).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(true); }); it('match regular expressions', () => { const rx = /http\:\/\/a\.com\/\d+/; const route = new Route({ url: rx, response: 200 }); - expect(route.matcher('http://a.com/')).toBe(false); - expect(route.matcher('http://a.com/abcde')).toBe(false); - expect(route.matcher('http://a.com/12345')).toBe(true); + expect(route.matcher({ url: 'http://a.com/' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/abcde' })).toBe(false); + expect(route.matcher({ url: 'http://a.com/12345' })).toBe(true); }); it('match relative urls', () => { const route = new Route({ url: '/a.com/', response: 200 }); - expect(route.matcher('/a.com/')).toBe(true); + expect(route.matcher({ url: '/a.com/' })).toBe(true); }); it('match relative urls with dots', () => { const route = new Route({ url: '/it.at/there/', response: 200 }); - expect(route.matcher('/it.at/not/../there/')).toBe(true); - expect(route.matcher('./it.at/there/')).toBe(true); + expect(route.matcher({ url: '/it.at/not/../there/' })).toBe(true); + expect(route.matcher({ url: './it.at/there/' })).toBe(true); }); it('match absolute urls with dots', () => { const route = new Route({ url: 'http://it.at/there/', response: 200 }); - expect(route.matcher('http://it.at/not/../there/')).toBe(true); + expect(route.matcher({ url: 'http://it.at/not/../there/' })).toBe(true); }); describe('host normalisation', () => { it('match exact pathless urls regardless of trailing slash', () => { const route = new Route({ url: 'http://a.com/', response: 200 }); - expect(route.matcher('http://a.com/')).toBe(true); - expect(route.matcher('http://a.com')).toBe(true); + expect(route.matcher({ url: 'http://a.com/' })).toBe(true); + expect(route.matcher({ url: 'http://a.com' })).toBe(true); const route2 = new Route({ url: 'http://b.com', response: 200 }); - expect(route2.matcher('http://b.com/')).toBe(true); - expect(route2.matcher('http://b.com')).toBe(true); + expect(route2.matcher({ url: 'http://b.com/' })).toBe(true); + expect(route2.matcher({ url: 'http://b.com' })).toBe(true); }); }); @@ -130,10 +130,10 @@ describe('url matching', () => { url: 'data:text/plain,path', response: 200, }); - expect(route.matcher('data:text/plain,pat')).toBe(false); - expect(route.matcher('data:text/plain,paths')).toBe(false); - expect(route.matcher('data:text/html,path')).toBe(false); - expect(route.matcher('data:text/plain,path')).toBe(true); + expect(route.matcher({ url: 'data:text/plain,pat' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,paths' })).toBe(false); + expect(route.matcher({ url: 'data:text/html,path' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,path' })).toBe(true); }); it('match exact string against URL object', () => { const route = new Route({ @@ -141,46 +141,50 @@ describe('url matching', () => { response: 200, }); const url = new URL('data:text/plain,path'); - expect(route.matcher(url)).toBe(true); + expect(route.matcher({ url })).toBe(true); }); it('match using URL object as matcher', () => { const url = new URL('data:text/plain,path'); const route = new Route({ url: url, response: 200 }); - expect(route.matcher('data:text/plain,path')).toBe(true); + expect(route.matcher({ url: 'data:text/plain,path' })).toBe(true); }); it('match begin: keyword', () => { const route = new Route({ url: 'begin:data:text/plain', response: 200, }); - expect(route.matcher('http://a.com/path')).toBe(false); - expect(route.matcher('data:text/html,path')).toBe(false); - expect(route.matcher('data:text/plain,path')).toBe(true); - expect(route.matcher('data:text/plain;base64,cGF0aA')).toBe(true); + expect(route.matcher({ url: 'http://a.com/path' })).toBe(false); + expect(route.matcher({ url: 'data:text/html,path' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,path' })).toBe(true); + expect(route.matcher({ url: 'data:text/plain;base64,cGF0aA' })).toBe( + true, + ); }); it('match end: keyword', () => { const route = new Route({ url: 'end:sky', response: 200 }); - expect(route.matcher('data:text/plain,blue lake')).toBe(false); - expect(route.matcher('data:text/plain,blue sky research')).toBe(false); - expect(route.matcher('data:text/plain,blue sky')).toBe(true); - expect(route.matcher('data:text/plain,grey sky')).toBe(true); + expect(route.matcher({ url: 'data:text/plain,blue lake' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,blue sky research' })).toBe( + false, + ); + expect(route.matcher({ url: 'data:text/plain,blue sky' })).toBe(true); + expect(route.matcher({ url: 'data:text/plain,grey sky' })).toBe(true); }); it('match glob: keyword', () => { const route = new Route({ url: 'glob:data:* sky', response: 200 }); - expect(route.matcher('data:text/plain,blue lake')).toBe(false); - expect(route.matcher('data:text/plain,blue sky')).toBe(true); - expect(route.matcher('data:text/plain,grey sky')).toBe(true); + expect(route.matcher({ url: 'data:text/plain,blue lake' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,blue sky' })).toBe(true); + expect(route.matcher({ url: 'data:text/plain,grey sky' })).toBe(true); }); it('match wildcard string', () => { const route = new Route({ url: '*', response: 200 }); - expect(route.matcher('data:text/plain,path')).toBe(true); + expect(route.matcher({ url: 'data:text/plain,path' })).toBe(true); }); it('match regular expressions', () => { const rx = /data\:text\/plain,\d+/; const route = new Route({ url: rx, response: 200 }); - expect(route.matcher('data:text/html,12345')).toBe(false); - expect(route.matcher('data:text/plain,path')).toBe(false); - expect(route.matcher('data:text/plain,12345')).toBe(true); + expect(route.matcher({ url: 'data:text/html,12345' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,path' })).toBe(false); + expect(route.matcher({ url: 'data:text/plain,12345' })).toBe(true); }); }); }); diff --git a/packages/core/src/__tests__/router-integration.test.js b/packages/core/src/__tests__/router-integration.test.js index bf6a430d..b892e874 100644 --- a/packages/core/src/__tests__/router-integration.test.js +++ b/packages/core/src/__tests__/router-integration.test.js @@ -15,7 +15,7 @@ describe('Router', () => { it('match using custom function with Request', async () => { const fm = fetchMock.createInstance(); - fm.route((url, options) => { + fm.route(({ url, options }) => { return url.indexOf('logged-in') > -1 && options.headers.authorized; }, 200); @@ -37,10 +37,7 @@ describe('Router', () => { const valueToSet = propertyToCheck === 'credentials' ? 'include' : false; const fm = fetchMock.createInstance(); - fm.route( - (url, options, request) => request[propertyToCheck] === valueToSet, - 200, - ); + fm.route(({ request }) => request[propertyToCheck] === valueToSet, 200); await expect( fm.fetchHandler(new Request('http://a.com/logged-in')), @@ -59,7 +56,10 @@ describe('Router', () => { const fm = fetchMock.createInstance(); fm.defineMatcher({ name: 'syncMatcher', - matcher: (route) => (url) => url.indexOf(route.syncMatcher) > -1, + matcher: + (route) => + ({ url }) => + url.indexOf(route.syncMatcher) > -1, }); fm.route( { @@ -77,8 +77,10 @@ describe('Router', () => { const fm = fetchMock.createInstance(); fm.defineMatcher({ name: 'bodyMatcher', - matcher: (route) => (url, options) => - JSON.parse(options.body)[route.bodyMatcher] === true, + matcher: + (route) => + ({ options }) => + JSON.parse(options.body)[route.bodyMatcher] === true, usesBody: true, }); fm.route( @@ -115,8 +117,10 @@ describe('Router', () => { const fm = fetchMock.createInstance(); fm.defineMatcher({ name: 'asyncBodyMatcher', - matcher: (route) => (url, options) => - JSON.parse(options.body)[route.asyncBodyMatcher] === true, + matcher: + (route) => + ({ options }) => + JSON.parse(options.body)[route.asyncBodyMatcher] === true, }); fm.route( {