expect-webdriverio
Version:
WebdriverIO Assertion Library
186 lines (185 loc) • 6.42 kB
JavaScript
import { waitUntil, enhanceError } from '../../utils.js';
import { equals } from '../../jasmineUtils.js';
import { DEFAULT_OPTIONS } from '../../constants.js';
const STR_LIMIT = 80;
const KEY_LIMIT = 12;
function reduceHeaders(headers) {
return Object.entries(headers).reduce((acc, [, value]) => {
acc[value.name] = value.value.value;
return acc;
}, {});
}
export async function toBeRequestedWith(received, expectedValue = {}, options = DEFAULT_OPTIONS) {
const isNot = this.isNot || false;
const { expectation = 'called with', verb = 'be' } = this;
await options.beforeAssertion?.({
matcherName: 'toBeRequestedWith',
expectedValue,
options,
});
let actual;
const pass = await waitUntil(async () => {
for (const call of received.calls) {
actual = call;
if (methodMatcher(call.request.method, expectedValue.method) &&
statusCodeMatcher(call.response.status, expectedValue.statusCode) &&
urlMatcher(call.request.url, expectedValue.url) &&
headersMatcher(reduceHeaders(call.request.headers), expectedValue.requestHeaders) &&
headersMatcher(reduceHeaders(call.response.headers), expectedValue.responseHeaders)) {
return true;
}
}
return false;
}, isNot, { ...options, wait: isNot ? 0 : options.wait });
const message = enhanceError('mock', minifyRequestedWith(expectedValue), minifyRequestMock(actual, expectedValue) || 'was not called', this, verb, expectation, '', options);
const result = {
pass,
message: () => message
};
await options.afterAssertion?.({
matcherName: 'toBeRequestedWith',
expectedValue,
options,
result
});
return result;
}
const methodMatcher = (method, expected) => {
if (typeof expected === 'undefined') {
return true;
}
if (!Array.isArray(expected)) {
expected = [expected];
}
return expected
.map((m) => {
if (typeof m !== 'string') {
return console.error('expect.toBeRequestedWith: unsupported value passed to method ' + m);
}
return m.toUpperCase();
})
.includes(method);
};
const statusCodeMatcher = (statusCode, expected) => {
if (typeof expected === 'undefined') {
return true;
}
if (!Array.isArray(expected)) {
expected = [expected];
}
return expected.includes(statusCode);
};
const urlMatcher = (url, expected) => {
if (typeof expected === 'undefined') {
return true;
}
if (typeof expected === 'function') {
return expected(url);
}
return equals(url, expected);
};
const headersMatcher = (headers, expected) => {
if (typeof expected === 'undefined' ||
typeof expected === 'object' && Object.keys(expected).length === 0) {
return true;
}
if (typeof expected === 'function') {
return expected(headers);
}
return equals(headers, expected);
};
const isMatcher = (filter) => {
return (typeof filter === 'object' &&
filter !== null &&
'__proto__' in filter &&
typeof filter.__proto__ === 'object' &&
filter.__proto__ &&
'asymmetricMatch' in filter.__proto__ &&
typeof filter.__proto__.asymmetricMatch === 'function');
};
const minifyRequestMock = (requestMock, requestedWith) => {
if (typeof requestMock === 'undefined') {
return requestMock;
}
const r = {
url: requestMock.request.url,
method: requestMock.request.method,
requestHeaders: requestMock.request.headers,
responseHeaders: requestMock.response.headers,
};
deleteUndefinedValues(r, requestedWith);
return minifyRequestedWith(r);
};
const minifyRequestedWith = (r) => {
const result = {
url: requestedWithParamToString(r.url),
method: r.method,
requestHeaders: requestedWithParamToString(r.requestHeaders, shortenJson),
responseHeaders: requestedWithParamToString(r.responseHeaders, shortenJson),
postData: requestedWithParamToString(r.postData, shortenJson),
response: requestedWithParamToString(r.response, shortenJson),
};
deleteUndefinedValues(result);
return result;
};
const requestedWithParamToString = (param, transformFn) => {
if (typeof param === 'undefined') {
return;
}
if (typeof param === 'function') {
param = param.toString();
}
else if (isMatcher(param)) {
return (param.constructor.name +
' ' +
(JSON.stringify(param.sample) || ''));
}
else if (transformFn && typeof param === 'object' && param !== null) {
param = transformFn(param);
}
if (typeof param === 'string') {
param = shortenString(param);
}
return param;
};
const shortenJson = (obj, lengthLimit = STR_LIMIT * 2, keyLimit = KEY_LIMIT) => {
if (JSON.stringify(obj).length < lengthLimit) {
return obj;
}
if (Array.isArray(obj)) {
const firstItem = typeof obj[0] === 'object' && obj[0] !== null
? shortenJson(obj[0], lengthLimit / 2, keyLimit / 4)
: shortenString(JSON.stringify(obj[0]));
return [firstItem, `... ${obj.length - 1} more items`];
}
const minifiedObject = {};
const entries = Object.entries(obj);
if (keyLimit >= 4) {
entries.slice(0, keyLimit).forEach(([k, v]) => {
if (typeof v === 'object' && v !== null) {
v = shortenJson(v, lengthLimit / 2, keyLimit / 4);
}
else if (typeof v === 'string') {
v = shortenString(v, 16);
}
minifiedObject[shortenString(k, 24)] = v;
});
}
if (entries.length > keyLimit) {
minifiedObject['...'] = `${entries.length} items in total`;
}
return minifiedObject;
};
const shortenString = (str, limit = STR_LIMIT) => {
return str.length > limit ? str.substring(0, limit / 2 - 1) + '..' + str.substr(1 - limit / 2) : str;
};
const deleteUndefinedValues = (obj, baseline = obj) => {
Object.keys(obj).forEach((k) => {
if (typeof baseline[k] === 'undefined') {
delete obj[k];
}
});
};
export function toBeRequestedWithResponse(...args) {
return toBeRequestedWith.call(this, ...args);
}