playwright/utils/testrunner/Matchers.js
2019-11-19 10:58:15 -08:00

131 lines
3.9 KiB
JavaScript

/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = class Matchers {
constructor(customMatchers = {}) {
this._matchers = {};
Object.assign(this._matchers, DefaultMatchers);
Object.assign(this._matchers, customMatchers);
this.expect = this.expect.bind(this);
}
addMatcher(name, matcher) {
this._matchers[name] = matcher;
}
expect(value) {
return new Expect(value, this._matchers);
}
};
class Expect {
constructor(value, matchers) {
this.not = {};
this.not.not = this;
for (const matcherName of Object.keys(matchers)) {
const matcher = matchers[matcherName];
this[matcherName] = applyMatcher.bind(null, matcherName, matcher, false /* inverse */, value);
this.not[matcherName] = applyMatcher.bind(null, matcherName, matcher, true /* inverse */, value);
}
function applyMatcher(matcherName, matcher, inverse, value, ...args) {
const result = matcher.call(null, value, ...args);
const message = `expect.${inverse ? 'not.' : ''}${matcherName} failed` + (result.message ? `: ${result.message}` : '');
if (result.pass === inverse)
throw new Error(message);
}
}
}
const DefaultMatchers = {
toBe: function(value, other, message) {
message = message || `${value} == ${other}`;
return { pass: value === other, message };
},
toBeFalsy: function(value, message) {
message = message || `${value}`;
return { pass: !value, message };
},
toBeTruthy: function(value, message) {
message = message || `${value}`;
return { pass: !!value, message };
},
toBeGreaterThan: function(value, other, message) {
message = message || `${value} > ${other}`;
return { pass: value > other, message };
},
toBeGreaterThanOrEqual: function(value, other, message) {
message = message || `${value} >= ${other}`;
return { pass: value >= other, message };
},
toBeLessThan: function(value, other, message) {
message = message || `${value} < ${other}`;
return { pass: value < other, message };
},
toBeLessThanOrEqual: function(value, other, message) {
message = message || `${value} <= ${other}`;
return { pass: value <= other, message };
},
toBeNull: function(value, message) {
message = message || `${value} == null`;
return { pass: value === null, message };
},
toContain: function(value, other, message) {
message = message || `${value}${other}`;
return { pass: value.includes(other), message };
},
toEqual: function(value, other, message) {
const valueJson = stringify(value);
const otherJson = stringify(other);
message = message || `\n${valueJson}${otherJson}`;
return { pass: valueJson === otherJson, message };
},
toBeCloseTo: function(value, other, precision, message) {
return {
pass: Math.abs(value - other) < Math.pow(10, -precision),
message
};
},
toBeInstanceOf: function(value, other, message) {
message = message || `${value.constructor.name} instanceof ${other.name}`;
return { pass: value instanceof other, message };
},
};
function stringify(value) {
function stabilize(key, object) {
if (typeof object !== 'object' || object === undefined || object === null)
return object;
const result = {};
for (const key of Object.keys(object).sort())
result[key] = object[key];
return result;
}
return JSON.stringify(stabilize(null, value), stabilize, 2);
}