Skip to content

Commit

Permalink
Support relative request URLs (#34)
Browse files Browse the repository at this point in the history
Fixes #31 

This takes an approach similar to MSW, whereby we will add the absolute
base url to a request if we're able to. See
https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#resource
for which absolute base url is used in window vs worker contexts.
  • Loading branch information
IanVS authored Nov 7, 2024
1 parent 66aeefd commit b0295c8
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 48 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"type-check": "tsc",
"test": "vitest run",
"test:watch": "vitest run --watch",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"format": "prettier src --write",
"format:check": "prettier src --check",
"lint": "eslint src tests",
"lint:fix": "eslint src tests --fix",
"format": "prettier src tests --write",
"format:check": "prettier src tests --check",
"build": "tsc -p tsconfig.build.json"
},
"repository": {
Expand Down
31 changes: 29 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,33 @@ function requestNotMatches(request: Request, urlOrPredicate: UrlOrPredicate): bo
return !requestMatches(request, urlOrPredicate);
}

// Node 18 does not support URL.canParse()
export function canParseURL(url: string): boolean {
try {
new URL(url);
return true;
} catch (err) {
return false;
}
}

// Node Requests cannot be relative
function resolveInput(input: string): string {
if (canParseURL(input)) return input;

// Window context
if (typeof window.document !== 'undefined') {
return new URL(input, window.document.baseURI).toString();
}

// Worker context
if (typeof location !== 'undefined') {
return new URL(input, location.origin).toString();
}

return input;
}

function normalizeRequest(input: RequestInput, requestInit?: RequestInit): Request {
if (input instanceof Request) {
if (input.signal && input.signal.aborted) {
Expand All @@ -319,12 +346,12 @@ function normalizeRequest(input: RequestInput, requestInit?: RequestInit): Reque
if (requestInit && requestInit.signal && requestInit.signal.aborted) {
abort();
}
return new Request(input, requestInit);
return new Request(resolveInput(input), requestInit);
} else {
if (requestInit && requestInit.signal && requestInit.signal.aborted) {
abort();
}
return new Request(input.toString(), requestInit);
return new Request(resolveInput(input.toString()), requestInit);
}
}

Expand Down
90 changes: 49 additions & 41 deletions tests/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,49 @@ describe('testing mockResponse', () => {
expect(fetch.mock.calls[0]![0]).toEqual(new URL('https://instagram.com'));
});

it('should allow empty response bodies', async () => {
fetch.mockResponseOnce(null, { status: 204 });
fetch.mockResponseOnce(undefined, { status: 204 });
fetch.mockResponseOnce(() => null, { status: 204 });
fetch.mockResponseOnce(() => undefined, { status: 204 });
fetch.mockResponseOnce(() => Promise.resolve(null), { status: 204 });
fetch.mockResponseOnce(() => Promise.resolve(undefined), { status: 204 });
fetch.mockResponseOnce({ status: 204 });
fetch.mockResponseOnce(() => ({ status: 204 }));
fetch.mockResponseOnce(() => Promise.resolve({ status: 204 }));
fetch.mockResponseOnce(new Response(null, { status: 204 }));
fetch.mockResponseOnce(new Response(undefined, { status: 204 }));
fetch.mockResponseOnce(() => new Response(null, { status: 204 }));
fetch.mockResponseOnce(() => new Response(undefined, { status: 204 }));
fetch.mockResponseOnce(() => Promise.resolve(new Response(null, { status: 204 })));
fetch.mockResponseOnce(() => Promise.resolve(new Response(undefined, { status: 204 })));
fetch.mockResponseOnce('done');

expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('done');
});
it('should support relative request urls', async () => {
fetch.mockResponseOnce(JSON.stringify({ data: 'abcde' }), { status: 200 });

const response = await fetch('folder/file.json').then((res) => res.json());

expect(response).toEqual({ data: 'abcde' });
});

it('should allow empty response bodies', async () => {
fetch.mockResponseOnce(null, { status: 204 });
fetch.mockResponseOnce(undefined, { status: 204 });
fetch.mockResponseOnce(() => null, { status: 204 });
fetch.mockResponseOnce(() => undefined, { status: 204 });
fetch.mockResponseOnce(() => Promise.resolve(null), { status: 204 });
fetch.mockResponseOnce(() => Promise.resolve(undefined), { status: 204 });
fetch.mockResponseOnce({ status: 204 });
fetch.mockResponseOnce(() => ({ status: 204 }));
fetch.mockResponseOnce(() => Promise.resolve({ status: 204 }));
fetch.mockResponseOnce(new Response(null, { status: 204 }));
fetch.mockResponseOnce(new Response(undefined, { status: 204 }));
fetch.mockResponseOnce(() => new Response(null, { status: 204 }));
fetch.mockResponseOnce(() => new Response(undefined, { status: 204 }));
fetch.mockResponseOnce(() => Promise.resolve(new Response(null, { status: 204 })));
fetch.mockResponseOnce(() => Promise.resolve(new Response(undefined, { status: 204 })));
fetch.mockResponseOnce('done');

expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('');
expect(await request()).toBe('done');
});
});

describe('testing mockResponses', () => {
Expand Down Expand Up @@ -822,15 +830,15 @@ describe('overloads', () => {
expect(await request()).toBe('i');
});
});

it('works globally', async () => {
const fm = createFetchMock(vi);
fm.enableMocks();
const fm = createFetchMock(vi);
fm.enableMocks();

fetchMock.mockResponseOnce('foo');
expect(await request()).toBe('foo');
fetchMock.mockResponseOnce('foo');
expect(await request()).toBe('foo');

fm.disableMocks();
fm.disableMocks();
});

it('enable/disable', async () => {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"es2022"
"es2022",
"dom",
],
"baseUrl": "./",
"paths": {
Expand Down
3 changes: 3 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export default defineConfig({
'vitest-fetch-mock': './src/index',
},
},
test: {
environment: 'jsdom',
},
});

0 comments on commit b0295c8

Please sign in to comment.