From f41d8f909350961e40a4df9dfb4817a3eaba09cd Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 20 Jul 2024 20:58:20 +0100 Subject: [PATCH 1/2] feat!: removed support for passing in a matcher under the generic name matcher --- packages/core/src/CallHistory.js | 2 +- packages/core/src/Route.js | 9 +--- packages/core/src/Router.js | 15 ++---- .../src/__tests__/Matchers/express.test.js | 8 +-- .../src/__tests__/Matchers/function.test.js | 6 +-- .../src/__tests__/Matchers/method.test.js | 4 +- .../Matchers/route-config-object.test.js | 11 ++-- .../core/src/__tests__/Matchers/url.test.js | 50 +++++++++---------- 8 files changed, 45 insertions(+), 60 deletions(-) diff --git a/packages/core/src/CallHistory.js b/packages/core/src/CallHistory.js index 08208233..3f57aa07 100644 --- a/packages/core/src/CallHistory.js +++ b/packages/core/src/CallHistory.js @@ -125,7 +125,7 @@ class CallHistory { } } else { if (isUrlMatcher(filter)) { - options = { matcher: filter, ...(options || {}) }; + options = { url: filter, ...(options || {}) }; } else { options = { ...filter, ...(options || {}) }; } diff --git a/packages/core/src/Route.js b/packages/core/src/Route.js index e7c7814a..830401c3 100644 --- a/packages/core/src/Route.js +++ b/packages/core/src/Route.js @@ -1,5 +1,5 @@ //@type-check -import { builtInMatchers, isUrlMatcher, isFunctionMatcher } from './Matchers'; +import { builtInMatchers } from './Matchers'; import statusTextMap from './StatusTextMap'; /** @typedef {import('./Matchers').RouteMatcher} RouteMatcher */ @@ -134,13 +134,6 @@ class Route { if (this.config.method) { this.config.method = this.config.method.toLowerCase(); } - if (isUrlMatcher(this.config.matcher)) { - this.config.url = this.config.matcher; - delete this.config.matcher; - } - if (isFunctionMatcher(this.config.matcher)) { - this.config.func = this.config.matcher; - } } /** * @returns {void} diff --git a/packages/core/src/Router.js b/packages/core/src/Router.js index 7fed0edb..da16a22c 100644 --- a/packages/core/src/Router.js +++ b/packages/core/src/Router.js @@ -318,8 +318,10 @@ export default class Router { addRoute(matcher, response, nameOrOptions) { /** @type {RouteConfig} */ const config = {}; - if (isUrlMatcher(matcher) || isFunctionMatcher(matcher)) { - config.matcher = matcher; + if (isUrlMatcher(matcher)) { + config.url = matcher; + } else if (isFunctionMatcher(matcher)) { + config.func = matcher; } else { Object.assign(config, matcher); } @@ -366,14 +368,7 @@ export default class Router { } this.fallbackRoute = new Route({ - matcher: (url, options) => { - if (this.config.warnOnFallback) { - console.warn( - `Unmatched ${(options && options.method) || 'GET'} to ${url}`, - ); // eslint-disable-line - } - return true; - }, + func: () => true, response: response || 'ok', ...this.config, }); diff --git a/packages/core/src/__tests__/Matchers/express.test.js b/packages/core/src/__tests__/Matchers/express.test.js index 4973d6db..0f19ef11 100644 --- a/packages/core/src/__tests__/Matchers/express.test.js +++ b/packages/core/src/__tests__/Matchers/express.test.js @@ -4,7 +4,7 @@ import Route from '../../Route.js'; describe('express path parameter matching', () => { it('can match a path parameters', () => { const route = new Route({ - matcher: 'express:/type/:instance', + url: 'express:/type/:instance', response: 200, params: { instance: 'b' }, }); @@ -15,7 +15,7 @@ describe('express path parameter matching', () => { it('can match multiple path parameters', () => { const route = new Route({ - matcher: 'express:/:type/:instance', + url: 'express:/:type/:instance', response: 200, params: { instance: 'b', type: 'cat' }, }); @@ -28,7 +28,7 @@ describe('express path parameter matching', () => { it('can match a path parameter on a full url', () => { const route = new Route({ - matcher: 'express:/type/:instance', + url: 'express:/type/:instance', response: 200, params: { instance: 'b' }, }); @@ -38,7 +38,7 @@ describe('express path parameter matching', () => { }); it('can match fully qualified url', () => { - const route = new Route({ matcher: 'express:/apps/:id', response: 200 }); + const route = new Route({ url: 'express:/apps/:id', response: 200 }); expect(route.matcher('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 46af89bc..20705e1f 100644 --- a/packages/core/src/__tests__/Matchers/function.test.js +++ b/packages/core/src/__tests__/Matchers/function.test.js @@ -4,7 +4,7 @@ import Route from '../../Route.js'; describe('function matching', () => { it('match using custom function', () => { const route = new Route({ - matcher: (url, opts) => + func: (url, opts) => url.indexOf('logged-in') > -1 && opts && opts.headers && @@ -27,7 +27,7 @@ describe('function matching', () => { it('match using custom function using request body', () => { const route = new Route({ - matcher: (url, opts) => opts.body === 'a string', + func: (url, opts) => opts.body === 'a string', response: 200, }); expect(route.matcher('http://a.com/logged-in')).toBe(false); @@ -41,7 +41,7 @@ describe('function matching', () => { it('match using custom function alongside other matchers', () => { const route = new Route({ - matcher: 'end:profile', + url: 'end:profile', response: 200, func: (url, opts) => opts && opts.headers && opts.headers.authorized === true, diff --git a/packages/core/src/__tests__/Matchers/method.test.js b/packages/core/src/__tests__/Matchers/method.test.js index 92eb6c8d..60fced91 100644 --- a/packages/core/src/__tests__/Matchers/method.test.js +++ b/packages/core/src/__tests__/Matchers/method.test.js @@ -4,7 +4,7 @@ import Route from '../../Route.js'; describe('method matching', () => { it('match any method by default', () => { - const route = new Route({ matcher: '*', response: 200 }); + 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); @@ -43,7 +43,7 @@ describe('method matching', () => { it('can be used alongside function matchers', () => { const route = new Route({ method: 'POST', - matcher: (url) => /a\.com/.test(url), + func: (url) => /a\.com/.test(url), response: 200, }); 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 b9ad11a3..f8e95b58 100644 --- a/packages/core/src/__tests__/Matchers/route-config-object.test.js +++ b/packages/core/src/__tests__/Matchers/route-config-object.test.js @@ -5,7 +5,7 @@ import Route from '../../Route.js'; // as it's mainly about the shape of optiosn passed into to addRoute describe('matcher object', () => { it('use matcher object with matcher property', () => { - const route = new Route({ matcher: 'http://a.com', response: 200 }); + const route = new Route({ url: 'http://a.com', response: 200 }); expect(route.matcher('http://a.com')).toBe(true); }); @@ -14,10 +14,10 @@ describe('matcher object', () => { expect(route.matcher('http://a.com')).toBe(true); }); - it('can use matcher and url simultaneously', () => { + it('can use function and url simultaneously', () => { const route = new Route({ url: 'end:path', - matcher: (url, opts) => + func: (url, opts) => opts && opts.headers && opts.headers.authorized === true, response: 200, }); @@ -41,7 +41,7 @@ describe('matcher object', () => { expect(route.matcher('http://a.com')).toBe(true); }); - //TODO be strionger on discouraging this + //TODO be stronger on discouraging this it.skip('deprecated message on using func (prefer matcher)', () => { new Route({ url: 'end:profile', @@ -115,9 +115,6 @@ describe('matcher object', () => { ).toBe(true); }); - // TODO new tests for how multiple routes that match can be addeed - it.skip('support setting overwrite routes on matcher parameter', () => {}); - it('support setting matchPartialBody on matcher parameter', () => { const route = new Route({ body: { a: 1 }, diff --git a/packages/core/src/__tests__/Matchers/url.test.js b/packages/core/src/__tests__/Matchers/url.test.js index f49bcfa3..1178cad5 100644 --- a/packages/core/src/__tests__/Matchers/url.test.js +++ b/packages/core/src/__tests__/Matchers/url.test.js @@ -3,7 +3,7 @@ import Route from '../../Route.js'; describe('url matching', () => { it('match exact strings', () => { - const route = new Route({ matcher: 'http://a.com/path', response: 200 }); + 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); @@ -12,32 +12,32 @@ describe('url matching', () => { }); it('match string objects', () => { - const route = new Route({ matcher: 'http://a.com/path', response: 200 }); + 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 }); it('match exact strings with relative url', () => { - const route = new Route({ matcher: '/path', response: 200 }); + 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); }); it('match exact string against URL object', () => { - const route = new Route({ matcher: 'http://a.com/path', response: 200 }); + 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); }); it('match using URL object as matcher', () => { const url = new URL('http://a.com/path'); - const route = new Route({ matcher: url, response: 200 }); + const route = new Route({ url: url, response: 200 }); expect(route.matcher('http://a.com/path')).toBe(true); }); it('match begin: keyword', () => { const route = new Route({ - matcher: 'begin:http://a.com/path', + url: 'begin:http://a.com/path', response: 200, }); @@ -48,7 +48,7 @@ describe('url matching', () => { }); it('match end: keyword', () => { - const route = new Route({ matcher: 'end:com/path', response: 200 }); + 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); @@ -56,14 +56,14 @@ describe('url matching', () => { }); it('match glob: keyword', () => { - const route = new Route({ matcher: 'glob:/its/*/*', response: 200 }); + 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); }); it('match express: keyword', () => { - const route = new Route({ matcher: 'express:/its/:word', response: 200 }); + const route = new Route({ url: 'express:/its/:word', response: 200 }); expect(route.matcher('/its')).toBe(false); expect(route.matcher('/its/')).toBe(false); @@ -72,7 +72,7 @@ describe('url matching', () => { }); it('match path: keyword', () => { - const route = new Route({ matcher: 'path:/its/:word', response: 200 }); + 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); @@ -81,14 +81,14 @@ describe('url matching', () => { }); it('match wildcard string', () => { - const route = new Route({ matcher: '*', response: 200 }); + const route = new Route({ url: '*', response: 200 }); expect(route.matcher('http://a.com')).toBe(true); }); it('match regular expressions', () => { const rx = /http\:\/\/a\.com\/\d+/; - const route = new Route({ matcher: rx, response: 200 }); + 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); @@ -96,29 +96,29 @@ describe('url matching', () => { }); it('match relative urls', () => { - const route = new Route({ matcher: '/a.com/', response: 200 }); + const route = new Route({ url: '/a.com/', response: 200 }); expect(route.matcher('/a.com/')).toBe(true); }); it('match relative urls with dots', () => { - const route = new Route({ matcher: '/it.at/there/', response: 200 }); + 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); }); it('match absolute urls with dots', () => { - const route = new Route({ matcher: 'http://it.at/there/', response: 200 }); + const route = new Route({ url: 'http://it.at/there/', response: 200 }); expect(route.matcher('http://it.at/not/../there/')).toBe(true); }); describe('host normalisation', () => { it('match exact pathless urls regardless of trailing slash', () => { - const route = new Route({ matcher: 'http://a.com/', response: 200 }); + 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); - const route2 = new Route({ matcher: 'http://b.com', response: 200 }); + 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); }); @@ -127,7 +127,7 @@ describe('url matching', () => { describe('data: URLs', () => { it('match exact strings', () => { const route = new Route({ - matcher: 'data:text/plain,path', + url: 'data:text/plain,path', response: 200, }); expect(route.matcher('data:text/plain,pat')).toBe(false); @@ -137,7 +137,7 @@ describe('url matching', () => { }); it('match exact string against URL object', () => { const route = new Route({ - matcher: 'data:text/plain,path', + url: 'data:text/plain,path', response: 200, }); const url = new URL('data:text/plain,path'); @@ -145,12 +145,12 @@ describe('url matching', () => { }); it('match using URL object as matcher', () => { const url = new URL('data:text/plain,path'); - const route = new Route({ matcher: url, response: 200 }); + const route = new Route({ url: url, response: 200 }); expect(route.matcher('data:text/plain,path')).toBe(true); }); it('match begin: keyword', () => { const route = new Route({ - matcher: 'begin:data:text/plain', + url: 'begin:data:text/plain', response: 200, }); expect(route.matcher('http://a.com/path')).toBe(false); @@ -159,25 +159,25 @@ describe('url matching', () => { expect(route.matcher('data:text/plain;base64,cGF0aA')).toBe(true); }); it('match end: keyword', () => { - const route = new Route({ matcher: 'end:sky', response: 200 }); + 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); }); it('match glob: keyword', () => { - const route = new Route({ matcher: 'glob:data:* sky', response: 200 }); + 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); }); it('match wildcard string', () => { - const route = new Route({ matcher: '*', response: 200 }); + const route = new Route({ url: '*', response: 200 }); expect(route.matcher('data:text/plain,path')).toBe(true); }); it('match regular expressions', () => { const rx = /data\:text\/plain,\d+/; - const route = new Route({ matcher: rx, response: 200 }); + 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); From e5679a72f663d5187d08934aa510951f1d438adc Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 20 Jul 2024 20:59:34 +0100 Subject: [PATCH 2/2] feat!: renamed func to matcherFunction --- docs/docs/@fetch-mock/core/route/matcher.md | 2 +- packages/core/src/Matchers.js | 4 ++-- packages/core/src/Route.js | 2 +- packages/core/src/Router.js | 8 ++++---- packages/core/src/__tests__/Matchers/function.test.js | 6 +++--- packages/core/src/__tests__/Matchers/method.test.js | 2 +- .../src/__tests__/Matchers/route-config-object.test.js | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/docs/@fetch-mock/core/route/matcher.md b/docs/docs/@fetch-mock/core/route/matcher.md index b9b1a7fc..ac7abe04 100644 --- a/docs/docs/@fetch-mock/core/route/matcher.md +++ b/docs/docs/@fetch-mock/core/route/matcher.md @@ -141,7 +141,7 @@ This option can also be [set in the global configuration](/fetch-mock/docs/@fetc For use cases not covered by all the built in matchers, a custom function can be used. It should return `true` to indicate a route should respond to a request. It 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` available as a third argument. -As well as being passed as a standalone argument, it can also be added to the matcher object as the property `{func: ...}` when combining with other matchers or options. +As well as being passed as a standalone argument, it can also be added to the matcher object as the property `{matcherFunction: ...}` when combining with other matchers or options. ### Examples diff --git a/packages/core/src/Matchers.js b/packages/core/src/Matchers.js index c24ba2f7..50f540c5 100644 --- a/packages/core/src/Matchers.js +++ b/packages/core/src/Matchers.js @@ -196,7 +196,7 @@ const getFullUrlMatcher = (route, matcherUrl, query) => { /** * @type {MatcherGenerator} */ -const getFunctionMatcher = ({ func }) => func; +const getFunctionMatcher = ({ matcherFunction }) => matcherFunction; /** * @type {MatcherGenerator} */ @@ -236,6 +236,6 @@ export const builtInMatchers = [ { name: 'headers', matcher: getHeaderMatcher }, { name: 'params', matcher: getParamsMatcher }, { name: 'body', matcher: getBodyMatcher, usesBody: true }, - { name: 'func', matcher: getFunctionMatcher }, + { name: 'matcherFunction', matcher: getFunctionMatcher }, { name: 'url', matcher: getUrlMatcher }, ]; diff --git a/packages/core/src/Route.js b/packages/core/src/Route.js index 830401c3..1341b44e 100644 --- a/packages/core/src/Route.js +++ b/packages/core/src/Route.js @@ -41,7 +41,7 @@ import statusTextMap from './StatusTextMap'; * @property {{ [key: string]: string }} [query] * @property {{ [key: string]: string }} [params] * @property {object} [body] - * @property {RouteMatcherFunction} [func] + * @property {RouteMatcherFunction} [matcherFunction] * @property {RouteMatcher} [matcher] * @property {RouteMatcherUrl} [url] * @property {RouteResponse | RouteResponseFunction} [response] diff --git a/packages/core/src/Router.js b/packages/core/src/Router.js index da16a22c..7e3c0fb2 100644 --- a/packages/core/src/Router.js +++ b/packages/core/src/Router.js @@ -278,8 +278,8 @@ export default class Router { if (typeof response[name] === 'function') { //@ts-ignore return new Proxy(response[name], { - apply: (func, thisArg, args) => { - const result = func.apply(response, args); + apply: (matcherFunction, thisArg, args) => { + const result = matcherFunction.apply(response, args); if (result.then) { pendingPromises.push( result.catch(/** @type {function(): void} */ () => undefined), @@ -321,7 +321,7 @@ export default class Router { if (isUrlMatcher(matcher)) { config.url = matcher; } else if (isFunctionMatcher(matcher)) { - config.func = matcher; + config.matcherFunction = matcher; } else { Object.assign(config, matcher); } @@ -368,7 +368,7 @@ export default class Router { } this.fallbackRoute = new Route({ - func: () => true, + matcherFunction: () => true, response: response || 'ok', ...this.config, }); diff --git a/packages/core/src/__tests__/Matchers/function.test.js b/packages/core/src/__tests__/Matchers/function.test.js index 20705e1f..d0f4d0db 100644 --- a/packages/core/src/__tests__/Matchers/function.test.js +++ b/packages/core/src/__tests__/Matchers/function.test.js @@ -4,7 +4,7 @@ import Route from '../../Route.js'; describe('function matching', () => { it('match using custom function', () => { const route = new Route({ - func: (url, opts) => + matcherFunction: (url, opts) => url.indexOf('logged-in') > -1 && opts && opts.headers && @@ -27,7 +27,7 @@ describe('function matching', () => { it('match using custom function using request body', () => { const route = new Route({ - func: (url, opts) => opts.body === 'a string', + matcherFunction: (url, opts) => opts.body === 'a string', response: 200, }); expect(route.matcher('http://a.com/logged-in')).toBe(false); @@ -43,7 +43,7 @@ describe('function matching', () => { const route = new Route({ url: 'end:profile', response: 200, - func: (url, opts) => + matcherFunction: (url, opts) => opts && opts.headers && opts.headers.authorized === true, }); diff --git a/packages/core/src/__tests__/Matchers/method.test.js b/packages/core/src/__tests__/Matchers/method.test.js index 60fced91..3d8a940d 100644 --- a/packages/core/src/__tests__/Matchers/method.test.js +++ b/packages/core/src/__tests__/Matchers/method.test.js @@ -43,7 +43,7 @@ describe('method matching', () => { it('can be used alongside function matchers', () => { const route = new Route({ method: 'POST', - func: (url) => /a\.com/.test(url), + matcherFunction: (url) => /a\.com/.test(url), response: 200, }); 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 f8e95b58..fff37d0b 100644 --- a/packages/core/src/__tests__/Matchers/route-config-object.test.js +++ b/packages/core/src/__tests__/Matchers/route-config-object.test.js @@ -17,7 +17,7 @@ describe('matcher object', () => { it('can use function and url simultaneously', () => { const route = new Route({ url: 'end:path', - func: (url, opts) => + matcherFunction: (url, opts) => opts && opts.headers && opts.headers.authorized === true, response: 200, }); @@ -42,10 +42,10 @@ describe('matcher object', () => { }); //TODO be stronger on discouraging this - it.skip('deprecated message on using func (prefer matcher)', () => { + it.skip('deprecated message on using matcherFunction (prefer matcher)', () => { new Route({ url: 'end:profile', - func: (url, opts) => + matcherFunction: (url, opts) => opts && opts.headers && opts.headers.authorized === true, response: 200, });