Skip to content

Commit

Permalink
feat: support multiple url matchers at once
Browse files Browse the repository at this point in the history
  • Loading branch information
wheresrhys committed Jul 24, 2024
1 parent 741fc13 commit c83d9f9
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 5 deletions.
17 changes: 15 additions & 2 deletions docs/docs/@fetch-mock/core/route/matcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ Match a url that satisfies an [express style path](https://www.npmjs.com/package

When the `express:` keyword is used in a string matcher, it can be combined with the `{params: ...}` matcher to match only requests whose express parameters evaluate to certain values. e.g.

```
```js
{
express: "/:section/user/:user",
url: "express:/:section/user/:user",
params: {"section": "feed", "user": "geoff"}
}
```
Expand All @@ -83,6 +83,19 @@ The values of express parameters are made available in the `expressParams` prope
- [Inspecting call history](/fetch-mock/docs/@fetch-mock/core/CallHistory#calllog-schema)
- [Using a function to construct a response](/fetch-mock/docs/@fetch-mock/core/route/response#function)

### Multiple url matchers

All of the above (with the exception of the full url matcher) can be combined in an object in order to match multiple patterns at once e.g.

```js
{
url: {
begin: 'https',
path: '/could/be/any/host'
}
}
```

## Other matching criteria

### method
Expand Down
36 changes: 34 additions & 2 deletions packages/core/src/Matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import { isSubsetOf } from 'is-subset-of';
import { dequal as isEqual } from 'dequal';
import { normalizeHeaders, getPath, normalizeUrl } from './RequestUtils.js';

/** @typedef {string | RegExp | URL} RouteMatcherUrl */
/**
* @typedef URLMatcherObject
* @property {string} [begin]
* @property {string} [end]
* @property {string} [glob]
* @property {string} [express]
* @property {string} [path]
* @property {RegExp} [regexp]
*/
/** @typedef {string | RegExp | URL | URLMatcherObject} RouteMatcherUrl */
/** @typedef {function(string): RouteMatcherFunction} UrlMatcherGenerator */
/** @typedef {function(CallLog): boolean} RouteMatcherFunction */
/** @typedef {function(RouteConfig): RouteMatcherFunction} MatcherGenerator */
Expand Down Expand Up @@ -211,6 +220,15 @@ const getBodyMatcher = (route) => {
*/
const getFunctionMatcher = ({ matcherFunction }) => matcherFunction;

/**
* @param {RegExp} regexp
* @returns {RouteMatcherFunction}
*/
const getRegexpMatcher =
(regexp) =>
({ url }) =>
regexp.test(url);

/**
*
* @param {RouteConfig} route
Expand Down Expand Up @@ -247,7 +265,7 @@ const getUrlMatcher = (route) => {
}

if (matcherUrl instanceof RegExp) {
return ({ url }) => matcherUrl.test(url);
return getRegexpMatcher(matcherUrl);
}
if (matcherUrl instanceof URL) {
if (matcherUrl.href) {
Expand All @@ -266,6 +284,20 @@ const getUrlMatcher = (route) => {
}
return getFullUrlMatcher(route, matcherUrl, query);
}

if (typeof matcherUrl === 'object') {
const matchers = Object.entries(matcherUrl).map(([key, pattern]) => {
if (key === 'regexp') {
return getRegexpMatcher(pattern);
} else if (key in stringMatchers) {
return stringMatchers[key](pattern);
} else {
throw new Error(`unrecognised url matching pattern: ${key}`);
}
});

return (route) => matchers.every((matcher) => matcher(route));
}
};

/** @type {MatcherDefinition[]} */
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/__tests__/Matchers/url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ describe('url matching', () => {
expect(route.matcher({ url: 'http://it.at/not/../there/' })).toBe(true);
});

it('match with multiple url patterns at once', () => {
const route = new Route({
url: {
begin: 'http',
end: 'jam',
path: '/jar/of/jam',
express: '/:container/of/:stuff',
},
response: 200,
});
expect(route.matcher({ url: 'http://a.com/jar/of/jam' })).toBe(true);
});

describe('host normalisation', () => {
it('match exact pathless urls regardless of trailing slash', () => {
const route = new Route({ url: 'http://a.com/', response: 200 });
Expand Down
10 changes: 9 additions & 1 deletion packages/core/types/Matchers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ export function isFunctionMatcher(matcher: RouteMatcher | RouteConfig): matcher
export const builtInMatchers: MatcherDefinition[];
export type RouteConfig = import("./Route.js").RouteConfig;
export type CallLog = import("./CallHistory.js").CallLog;
export type RouteMatcherUrl = string | RegExp | URL;
export type URLMatcherObject = {
begin?: string;
end?: string;
glob?: string;
express?: string;
path?: string;
regexp?: RegExp;
};
export type RouteMatcherUrl = string | RegExp | URL | URLMatcherObject;
export type UrlMatcherGenerator = (arg0: string) => RouteMatcherFunction;
export type RouteMatcherFunction = (arg0: CallLog) => boolean;
export type MatcherGenerator = (arg0: RouteConfig) => RouteMatcherFunction;
Expand Down

0 comments on commit c83d9f9

Please sign in to comment.