Skip to content

Commit

Permalink
Merge pull request #733 from wheresrhys/rhys/type-tweaks
Browse files Browse the repository at this point in the history
Rhys/type tweaks
  • Loading branch information
wheresrhys authored Jul 22, 2024
2 parents d18bb24 + 664a6df commit f64bfd7
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 61 deletions.
1 change: 0 additions & 1 deletion docs/blog/2024-07-21-introducing-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ These are the new methods for resetting fetch mock to its default state. The nam

A replacement for `sandbox()` that eschews all the weird wiring that `.sandbox()` used. Possibly not very useful for the average user, but I use it a lot in my tests for fetch mock, so it stays :-).


## What's still to come

There are a bunch of [breaking changes](https://github.com/wheresrhys/fetch-mock/issues?q=is%3Aopen+is%3Aissue+label%3A%22breaking+change%22) I'd like to ship before getting to v1.0.0. I also want to give users an incentive to migrate so there are a variety of new features I'd like to add and bugs to fix. Have a look at [the issues list](https://github.com/wheresrhys/fetch-mock/issues) and vote for any you like.
2 changes: 2 additions & 0 deletions docs/docs/@fetch-mock/core/CallHistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ An options object compatible with the [route api](#api-mockingmock_options) to b

`fetchMock.callHistory` exposes the following methods.

> Note that fetch calls made using `(url, options)` pairs are added synchronously, but calls using a `Request` are added asynchronously. This is because when a `Request` is used access to many of its internals is via asynchronous methods, while for an options object they can be read directly. In general it's best to `await` your code to complete before attempting to access call history.
### .recordCall(callLog)

For internal use.
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/CallHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/** @typedef {import('./RequestUtils').NormalizedRequestOptions} NormalizedRequestOptions */
/** @typedef {import('./Matchers').RouteMatcher} RouteMatcher */
/** @typedef {import('./FetchMock').FetchMockConfig} FetchMockConfig */
import { createCallLog } from './RequestUtils.js';
import { createCallLogFromUrlAndOptions } from './RequestUtils.js';
import { isUrlMatcher } from './Matchers.js';
import Route from './Route.js';
import Router from './Router.js';
Expand All @@ -18,8 +18,8 @@ import Router from './Router.js';
* @property {AbortSignal} [signal]
* @property {Route} [route]
* @property {Response} [response]
* @property {Object.<string, string>} [expressParameters]
* @property {Object.<string, string>} [queryParameters]
* @property {Object.<string, string>} [expressParams]
* @property {Object.<string, string>} [queryParams]
* @property {Promise<any>[]} pendingPromises
*/

Expand Down Expand Up @@ -142,7 +142,7 @@ class CallHistory {
});

calls = calls.filter(({ url, options }) => {
return matcher(createCallLog(url, options, this.config.Request));
return matcher(createCallLogFromUrlAndOptions(url, options));
});

return calls;
Expand Down
19 changes: 13 additions & 6 deletions packages/core/src/FetchMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,20 @@ const FetchMock = {
* @this {FetchMock}
* @returns {Promise<Response>}
*/
fetchHandler(requestInput, requestInit) {
async fetchHandler(requestInput, requestInit) {
// TODO move into router
const callLog = requestUtils.createCallLog(
requestInput,
requestInit,
this.config.Request,
);
let callLog;
if (requestUtils.isRequest(requestInput, this.config.Request)) {
callLog = await requestUtils.createCallLogFromRequest(
requestInput,
requestInit,
);
} else {
callLog = requestUtils.createCallLogFromUrlAndOptions(
requestInput,
requestInit,
);
}

this.callHistory.recordCall(callLog);
const responsePromise = this.router.execute(callLog);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/Matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ const getBodyMatcher = (route) => {
let sentBody;

try {
sentBody = JSON.parse(body);
if (typeof body === 'string') {
sentBody = JSON.parse(body);
}
} catch (err) {}

return (
Expand Down
91 changes: 48 additions & 43 deletions packages/core/src/RequestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ const protocolRelativeUrlRX = new RegExp('^//', 'i');
/**
* @typedef DerivedRequestOptions
* @property {string} method
* @property {Promise<string>} [body]
* @property {string} [body]
* @property {{ [key: string]: string }} [headers]
*/

/** @typedef {RequestInit | (RequestInit & DerivedRequestOptions) } NormalizedRequestOptions */
/** @typedef {import('./CallHistory').CallLog} CallLog */

/**
*
* @param {string} url
* @param {string | string | URL} url
* @returns {string}
*/
export function normalizeUrl(url) {
if (url instanceof URL) {
return url.href;
}
if (absoluteUrlRX.test(url)) {
const u = new URL(url);
return u.href;
return new URL(url).href;
}
if (protocolRelativeUrlRX.test(url)) {
const u = new URL(url, 'http://dummy');
return u.href;
return new URL(url, 'http://dummy').href;
}
const u = new URL(url, 'http://dummy');
return u.pathname + u.search;
Expand All @@ -37,64 +37,69 @@ export function normalizeUrl(url) {
* @param {typeof Request} Request
* @returns {urlOrRequest is Request}
*/
const isRequest = (urlOrRequest, Request) =>
export const isRequest = (urlOrRequest, Request) =>
Request.prototype.isPrototypeOf(urlOrRequest);

/**
*
* @param {string|Request} urlOrRequest
* @param {string | object} url
* @param {RequestInit} options
* @param {typeof Request} Request
* @returns {CallLog}
*/
export function createCallLog(urlOrRequest, options, Request) {
export function createCallLogFromUrlAndOptions(url, options) {
/** @type {Promise<any>[]} */
const pendingPromises = [];
if (isRequest(urlOrRequest, Request)) {
/** @type {NormalizedRequestOptions} */
const derivedOptions = {
method: urlOrRequest.method,
};

try {
derivedOptions.body = urlOrRequest.clone().text();
} catch (err) {}

if (urlOrRequest.headers) {
derivedOptions.headers = normalizeHeaders(urlOrRequest.headers);
}
const callLog = {
arguments: [urlOrRequest, options],
url: normalizeUrl(urlOrRequest.url),
options: Object.assign(derivedOptions, options),
request: urlOrRequest,
signal: (options && options.signal) || urlOrRequest.signal,
pendingPromises,
};
return callLog;
}
if (
typeof urlOrRequest === 'string' ||
/** @type {object} */ (urlOrRequest) instanceof String ||
// horrible URL object duck-typing
(typeof urlOrRequest === 'object' && 'href' in urlOrRequest)
) {
if (typeof url === 'string' || url instanceof String || url instanceof URL) {
return {
arguments: [urlOrRequest, options],
url: normalizeUrl(urlOrRequest),
arguments: [url, options],
// @ts-ignore - jsdoc doesn't distinguish between string and String, but typechecker complains
url: normalizeUrl(url),
options: options || {},
signal: options && options.signal,
pendingPromises,
};
}
if (typeof urlOrRequest === 'object') {
if (typeof url === 'object') {
throw new TypeError(
'fetch-mock: Unrecognised Request object. Read the Config and Installation sections of the docs',
);
} else {
throw new TypeError('fetch-mock: Invalid arguments passed to fetch');
}
}

/**
*
* @param {Request} request
* @param {RequestInit} options
* @returns {Promise<CallLog>}
*/
export async function createCallLogFromRequest(request, options) {
/** @type {Promise<any>[]} */
const pendingPromises = [];
/** @type {NormalizedRequestOptions} */
const derivedOptions = {
method: request.method,
};

try {
derivedOptions.body = await request.clone().text();
} catch (err) {}

if (request.headers) {
derivedOptions.headers = normalizeHeaders(request.headers);
}
const callLog = {
arguments: [request, options],
url: normalizeUrl(request.url),
options: Object.assign(derivedOptions, options || {}),
request: request,
signal: (options && options.signal) || request.signal,
pendingPromises,
};
return callLog;
}

/**
* @param {string} url
* @returns {string}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/__tests__/CallHistory.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ describe('CallHistory', () => {
);
});

it('when called with Request instance', () => {
it('when called with Request instance', async () => {
fm.catch();
const req = new Request('http://a.com/', {
method: 'post',
});
fm.fetchHandler(req);
await fm.fetchHandler(req);
expect(fm.callHistory.calls()[0]).toEqual(
expect.objectContaining({
url: 'http://a.com/',
Expand All @@ -133,12 +133,12 @@ describe('CallHistory', () => {
}),
);
});
it('when called with Request instance and arbitrary option', () => {
it('when called with Request instance and arbitrary option', async () => {
fm.catch();
const req = new Request('http://a.com/', {
method: 'POST',
});
fm.fetchHandler(req, { arbitraryOption: true });
await fm.fetchHandler(req, { arbitraryOption: true });
expect(fm.callHistory.calls()[0]).toEqual(
expect.objectContaining({
url: 'http://a.com/',
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/__tests__/Matchers/body.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import Route from '../../Route.js';
import Router from '../../Router.js';
import { createCallLog } from '../../RequestUtils.js';
import { createCallLogFromRequest } 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', () => {
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('body matching', () => {
Response,
});
const router = new Router({ Request, Headers }, { routes: [route] });
const normalizedRequest = createCallLog(
const normalizedRequest = await createCallLogFromRequest(
new Request('http://a.com/', {
method: 'POST',
body: JSON.stringify({ foo: 'bar' }),
Expand Down

0 comments on commit f64bfd7

Please sign in to comment.