Skip to content

Commit

Permalink
Merge pull request #747 from wheresrhys/rhys/query-params-access
Browse files Browse the repository at this point in the history
refactor: remove query string dependency
  • Loading branch information
wheresrhys authored Jul 24, 2024
2 parents 1e12c1c + 8ec57ac commit 0132ec6
Show file tree
Hide file tree
Showing 11 changed files with 385 additions and 111 deletions.
17 changes: 9 additions & 8 deletions docs/docs/@fetch-mock/core/CallHistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ sidebar_position: 4

Calls are recorded, and returned, in a standard format with the following properties:

- `[string|Request,Object]` - the original arguments passed in to `fetch`
- `{string} url` - The url being fetched
- `{NormalizedRequestOptions} options` - The options passed in to the fetch (may be derived from a `Request` if one was used)
- `{Request} [request]` - The `Request` passed to fetch, if one was used
- `{Route} [route]` - The route used to handle the request
- `{Response} [response]` - The `Response` returned to the user
- `{Object.<string,string>}` - Any express parameters extracted from the `url`
- `{Promise<any>[]} pendingPromises` - An internal structure used by the `.flush()` method documented below
- **arguments** `[string|Request,Object]` - the original arguments passed in to `fetch`
- **url** `{string}` - The url being fetched
- **options** `{NormalizedRequestOptions}` - The options passed in to the fetch (may be derived from a `Request` if one was used)
- **request** `{Request}` - The `Request` passed to fetch, if one was used
- **route** `{Route}` - The route used to handle the request
- **response** `{Response}` - The `Response` returned to the user
- **expressParams** `{Object.<string,string>}` - Any express parameters extracted from the `url`
- **queryParams** `{URLSearchParams}` - Any query parameters extracted from the `url`
- **pendingPromises** `{Promise<any>[]} ` - An internal structure used by the `.flush()` method documented below

## Filtering

Expand Down
12 changes: 1 addition & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"lint:ci": "eslint --ext .js,.cjs .",
"prettier": "prettier --cache --write *.md \"./**/*.md\"",
"prettier:ci": "prettier *.md \"./**/*.md\"",
"types:check": "tsc --project ./jsconfig.json",
"types:check": "tsc --project ./jsconfig.json && echo 'types check done'",
"types:lint": "dtslint --expectOnly packages/fetch-mock/types",
"prepare": "husky || echo \"husky not available\"",
"build": "npm run build -w=packages/fetch-mock -w=packages/core",
Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"dequal": "^2.0.3",
"globrex": "^0.1.2",
"is-subset-of": "^3.1.10",
"querystring": "^0.2.1",
"regexparam": "^3.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/core/src/CallHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Router from './Router.js';
* @property {Route} [route]
* @property {Response} [response]
* @property {Object.<string, string>} [expressParams]
* @property {Object.<string, string>} [queryParams]
* @property {URLSearchParams} [queryParams]
* @property {Promise<any>[]} pendingPromises
*/

Expand Down
63 changes: 40 additions & 23 deletions packages/core/src/Matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@
/** @typedef {import('./CallHistory.js').CallLog} CallLog */
import glob from 'globrex';
import * as regexparam from 'regexparam';
import querystring from 'querystring';
import { isSubsetOf } from 'is-subset-of';
import { dequal as isEqual } from 'dequal';
import {
normalizeHeaders,
getPath,
getQuery,
normalizeUrl,
} from './RequestUtils.js';
import { normalizeHeaders, getPath, normalizeUrl } from './RequestUtils.js';

/** @typedef {string | RegExp | URL} RouteMatcherUrl */
/** @typedef {function(string): RouteMatcherFunction} UrlMatcherGenerator */
Expand Down Expand Up @@ -113,32 +107,55 @@ const getMethodMatcher = ({ method: expectedMethod }) => {
/**
* @type {MatcherGenerator}
*/
const getQueryStringMatcher = ({ query: passedQuery }) => {
const getQueryParamsMatcher = ({ query: passedQuery }) => {
if (!passedQuery) {
return;
}
const expectedQuery = querystring.parse(querystring.stringify(passedQuery));
const keys = Object.keys(expectedQuery);
return ({ url }) => {
const query = querystring.parse(getQuery(url));
const expectedQuery = new URLSearchParams();
for (const [key, value] of Object.entries(passedQuery)) {
if (Array.isArray(value)) {
for (const item of value) {
expectedQuery.append(
key,
typeof item === 'object' || typeof item === 'undefined'
? ''
: item.toString(),
);
}
} else {
expectedQuery.append(
key,
typeof value === 'object' || typeof value === 'undefined'
? ''
: value.toString(),
);
}
}

const keys = Array.from(expectedQuery.keys());
return ({ queryParams }) => {
return keys.every((key) => {
if (Array.isArray(query[key])) {
if (!Array.isArray(expectedQuery[key])) {
return false;
}
return isEqual(
/** @type {string[]}*/ (query[key]).sort(),
/** @type {string[]}*/ (expectedQuery[key]).sort(),
const expectedValues = expectedQuery.getAll(key).sort();
const actualValues = queryParams.getAll(key).sort();

if (expectedValues.length !== actualValues.length) {
return false;
}

if (Array.isArray(passedQuery[key])) {
return expectedValues.every(
(expected, index) => expected === actualValues[index],
);
}
return query[key] === expectedQuery[key];

return isEqual(actualValues, expectedValues);
});
};
};
/**
* @type {MatcherGenerator}
*/
const getParamsMatcher = ({ params: expectedParams, url }) => {
const getExpressParamsMatcher = ({ params: expectedParams, url }) => {
if (!expectedParams) {
return;
}
Expand Down Expand Up @@ -248,10 +265,10 @@ const getUrlMatcher = (route) => {
/** @type {MatcherDefinition[]} */
export const builtInMatchers = [
{ name: 'url', matcher: getUrlMatcher },
{ name: 'query', matcher: getQueryStringMatcher },
{ name: 'query', matcher: getQueryParamsMatcher },
{ name: 'method', matcher: getMethodMatcher },
{ name: 'headers', matcher: getHeaderMatcher },
{ name: 'params', matcher: getParamsMatcher },
{ name: 'params', matcher: getExpressParamsMatcher },
{ name: 'body', matcher: getBodyMatcher, usesBody: true },
{ name: 'matcherFunction', matcher: getFunctionMatcher },
];
10 changes: 7 additions & 3 deletions packages/core/src/RequestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export function createCallLogFromUrlAndOptions(url, options) {
/** @type {Promise<any>[]} */
const pendingPromises = [];
if (typeof url === 'string' || url instanceof String || url instanceof URL) {
// @ts-ignore - jsdoc doesn't distinguish between string and String, but typechecker complains
url = normalizeUrl(url);
return {
arguments: [url, options],
// @ts-ignore - jsdoc doesn't distinguish between string and String, but typechecker complains
url: normalizeUrl(url),
url,
queryParams: new URLSearchParams(getQuery(url)),
options: options || {},
signal: options && options.signal,
pendingPromises,
Expand Down Expand Up @@ -81,9 +83,11 @@ export async function createCallLogFromRequest(request, options) {
if (request.headers) {
derivedOptions.headers = normalizeHeaders(request.headers);
}
const url = normalizeUrl(request.url);
const callLog = {
arguments: [request, options],
url: normalizeUrl(request.url),
url,
queryParams: new URLSearchParams(getQuery(url)),
options: Object.assign(derivedOptions, options || {}),
request: request,
signal: (options && options.signal) || request.signal,
Expand Down
Loading

0 comments on commit 0132ec6

Please sign in to comment.