Skip to content

Commit

Permalink
Merge pull request #387 from wheresrhys/tidies
Browse files Browse the repository at this point in the history
Tidies
  • Loading branch information
wheresrhys committed Nov 3, 2018
2 parents fce7e39 + e579d66 commit 5a03e51
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 247 deletions.
9 changes: 8 additions & 1 deletion .npmignore
Expand Up @@ -3,7 +3,14 @@
.eslintrc
ISSUE_TEMPLATE
karma.conf.js
LICENSE
Makefile
README.md
/docs/
/.nyc_output/
/coverage/
.eslint*
.gitignore
Gemfile
Gemfile.lock
.prettierrc
.codeclimate.yml
9 changes: 0 additions & 9 deletions .travis.yml

This file was deleted.

10 changes: 5 additions & 5 deletions package-lock.json

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

146 changes: 13 additions & 133 deletions src/lib/compile-route.js
@@ -1,117 +1,18 @@
const glob = require('glob-to-regexp');
const pathToRegexp = require('path-to-regexp');
const querystring = require('querystring');
const {
headers: headerUtils,
getPath,
getQuery,
normalizeUrl
} = require('./request-utils');
const generateMatcher = require('./generate-matcher');

const stringMatchers = {
begin: targetString => url => url.indexOf(targetString) === 0,
end: targetString => url => url.substr(-targetString.length) === targetString,
glob: targetString => {
const urlRX = glob(targetString);
return url => urlRX.test(url);
},
express: targetString => {
const urlRX = pathToRegexp(targetString);
return url => urlRX.test(getPath(url));
},
path: targetString => url => getPath(url) === targetString
};

function getHeaderMatcher({ headers: expectedHeaders }) {
const expectation = headerUtils.toLowerCase(expectedHeaders);
return (url, { headers = {} }) => {
const lowerCaseHeaders = headerUtils.toLowerCase(
headerUtils.normalize(headers)
);

return Object.keys(expectation).every(headerName =>
headerUtils.equal(lowerCaseHeaders[headerName], expectation[headerName])
);
};
}

const getMethodMatcher = ({ method: expectedMethod }) => {
return (url, { method }) =>
expectedMethod === (method ? method.toLowerCase() : 'get');
};

const getQueryStringMatcher = ({ query: expectedQuery }) => {
const keys = Object.keys(expectedQuery);
return url => {
const query = querystring.parse(getQuery(url));
return keys.every(key => query[key] === expectedQuery[key]);
};
};

const getParamsMatcher = ({ params: expectedParams, matcher }) => {
if (!/express:/.test(matcher)) {
throw new Error(
'fetch-mock: matching on params is only possible when using an express: matcher'
);
}
const expectedKeys = Object.keys(expectedParams);
const keys = [];
const re = pathToRegexp(matcher.replace(/^express:/, ''), keys);
return url => {
const vals = re.exec(getPath(url)) || [];
vals.shift();
const params = keys.reduce(
(map, { name }, i) =>
vals[i] ? Object.assign(map, { [name]: vals[i] }) : map,
{}
);
return expectedKeys.every(key => params[key] === expectedParams[key]);
};
};

const getUrlMatcher = route => {
const { matcher, query } = route;

if (typeof matcher === 'function') {
return matcher;
}

if (matcher instanceof RegExp) {
return url => matcher.test(url);
}

if (matcher === '*') {
return () => true;
}

for (const shorthand in stringMatchers) {
if (matcher.indexOf(shorthand + ':') === 0) {
const url = matcher.replace(new RegExp(`^${shorthand}:`), '');
return stringMatchers[shorthand](url);
}
}
const sanitizeRoute = route => {
route = Object.assign({}, route);

// if none of the special syntaxes apply, it's just a simple string match
// but we have to be careful to normalize the url we check and the name
// of the route to allow for e.g. http://it.at.there being indistinguishable
// from http://it.at.there/ once we start generating Request/Url objects
const expectedUrl = normalizeUrl(matcher);
if (route.identifier === matcher) {
route.identifier = expectedUrl;
if (route.method) {
route.method = route.method.toLowerCase();
}
route.identifier = route.name || route.matcher;

return url => {
if (query && expectedUrl.indexOf('?')) {
return url.indexOf(expectedUrl) === 0;
}
return normalizeUrl(url) === expectedUrl;
};
return route;
};

const sanitizeRoute = route => {
route = Object.assign({}, route);

if (typeof route.response === 'undefined') {
const validateRoute = route => {
if (!('response' in route)) {
throw new Error('fetch-mock: Each route must define a response');
}

Expand All @@ -120,27 +21,6 @@ const sanitizeRoute = route => {
'fetch-mock: Each route must specify a string, regex or function to match calls to fetch'
);
}

if (route.method) {
route.method = route.method.toLowerCase();
}
route.identifier = route.name || route.matcher;

return route;
};

const generateMatcher = route => {
const matchers = [
route.query && getQueryStringMatcher(route),
route.method && getMethodMatcher(route),
route.headers && getHeaderMatcher(route),
route.params && getParamsMatcher(route),
getUrlMatcher(route)
].filter(matcher => !!matcher);

return (url, options = {}, request) => {
return matchers.every(matcher => matcher(url, options, request));
};
};

const limitMatcher = route => {
Expand All @@ -160,12 +40,12 @@ const limitMatcher = route => {
route.reset = () => (timesLeft = route.repeat);
};

module.exports = function(route) {
module.exports = route => {
validateRoute(route);
route = sanitizeRoute(route);

route.matcher = generateMatcher(route);

limitMatcher(route);

return route;
};

module.exports.sanitizeRoute = sanitizeRoute;
66 changes: 18 additions & 48 deletions src/lib/fetch-handler.js
@@ -1,42 +1,15 @@
const ResponseBuilder = require('./response-builder');
const responseBuilder = require('./response-builder');
const requestUtils = require('./request-utils');
const FetchMock = {};

const normalizeRequest = (url, options, Request) => {
if (Request.prototype.isPrototypeOf(url)) {
const obj = {
url: requestUtils.normalizeUrl(url.url),
opts: {
method: url.method
},
request: url
};

const headers = requestUtils.headers.toArray(url.headers);

if (headers.length) {
obj.opts.headers = requestUtils.headers.zip(headers);
}
return obj;
} else if (
typeof url === 'string' ||
// horrible URL object duck-typing
(typeof url === 'object' && 'href' in url)
) {
return {
url: requestUtils.normalizeUrl(url),
opts: options
};
} else 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');
}
};

const resolve = async (response, url, opts) => {
// We want to allow things like
// - function returning a Promise for a response
// - delaying (using a timeout Promise) a function's execution to generate
// a response
// Because of this we can't safely check for function before Promisey-ness,
// or vice versa. So to keep it DRY, and flexible, we keep trying until we
// have something that looks like neither Promise nor function
while (
typeof response === 'function' ||
typeof response.then === 'function'
Expand All @@ -51,7 +24,11 @@ const resolve = async (response, url, opts) => {
};

FetchMock.fetchHandler = function(url, opts, request) {
({ url, opts, request } = normalizeRequest(url, opts, this.config.Request));
({ url, opts, request } = requestUtils.normalizeRequest(
url,
opts,
this.config.Request
));

const route = this.executeRouter(url, opts, request);

Expand Down Expand Up @@ -92,7 +69,7 @@ FetchMock.executeRouter = function(url, options, request) {
console.warn(`Unmatched ${(options && options.method) || 'GET'} to ${url}`); // eslint-disable-line
}

this.push({ url, options, request, unmatched: true });
this.push({ url, options, request, isUnmatched: true });

if (this.fallbackResponse) {
return { response: this.fallbackResponse };
Expand All @@ -110,13 +87,6 @@ FetchMock.executeRouter = function(url, options, request) {
};

FetchMock.generateResponse = async function(route, url, opts) {
// We want to allow things like
// - function returning a Promise for a response
// - delaying (using a timeout Promise) a function's execution to generate
// a response
// Because of this we can't safely check for function before Promisey-ness,
// or vice versa. So to keep it DRY, and flexible, we keep trying until we
// have something that looks like neither Promise nor function
const response = await resolve(route.response, url, opts);

// If the response says to throw an error, throw it
Expand All @@ -131,12 +101,12 @@ FetchMock.generateResponse = async function(route, url, opts) {
}

// finally, if we need to convert config into a response, we do it
return new ResponseBuilder({
return responseBuilder({
url,
shorthandResponse: response,
fetchMock: this,
route
}).exec();
});
};

FetchMock.router = function(url, options, request) {
Expand All @@ -163,11 +133,11 @@ FetchMock.getNativeFetch = function() {
return func;
};

FetchMock.push = function({ url, options, request, unmatched, identifier }) {
FetchMock.push = function({ url, options, request, isUnmatched, identifier }) {
const args = [url, options];
args.request = request;
args.identifier = identifier;
args.unmatched = unmatched;
args.isUnmatched = isUnmatched;
this._calls.push(args);
};

Expand Down

0 comments on commit 5a03e51

Please sign in to comment.