quixote
Version:
CSS unit and integration testing
167 lines (147 loc) • 6.11 kB
JavaScript
// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
;
var ensure = require("../util/ensure.js");
var oop = require("../util/oop.js");
var Value = require("../values/value.js");
var Me = module.exports = function Descriptor() {
ensure.unreachable("Descriptor is abstract and should not be constructed directly.");
};
Me.extend = oop.extendFn(Me);
oop.makeAbstract(Me, [
"value",
"toString"
]);
// WORKAROUND IE 8: Doesn't support Object.defineProperty(), which would allow us to create Me.prototype.should
// directly on this class as an accessor method.
// WORKAROUND IE 11: Doesn't support ES6 'class' syntax, which would allow us to use getter methods and inheritance.
Me.prototype.createShould = function createAssert() {
var self = this;
return {
equal: function(expected, message) {
self.doAssertion(expected, message, function(actualValue, expectedValue, expectedDesc, message) {
if (!actualValue.equals(expectedValue)) {
return message + self + " should be " + expectedValue.diff(actualValue) + ".\n" +
" Expected: " + expectedDesc + "\n" +
" But was: " + actualValue;
}
});
},
notEqual: function(expected, message) {
self.doAssertion(expected, message, function(actualValue, expectedValue, expectedDesc, message) {
if (actualValue.equals(expectedValue)) {
return message + self + " shouldn't be " + expectedValue + ".\n" +
" Expected: anything but " + expectedDesc + "\n" +
" But was: " + actualValue;
}
});
},
};
};
Me.prototype.doAssertion = function doAssertion(expected, message, assertFn) {
message = message === undefined ? "" : message + ": ";
expected = convertPrimitiveExpectationToValueObjectIfNeeded(this, expected, message);
var actualValue;
var expectedValue;
try {
actualValue = this.value();
expectedValue = expected.value();
}
catch (err) {
throw new Error(
message + "Error in test. Unable to convert descriptors to values.\n" +
"Error message: " + err.message + "\n" +
" 'actual' descriptor: " + this + " (" + oop.instanceName(this) + ")\n" +
" 'expected' descriptor: " + expected + " (" + oop.instanceName(expected) + ")\n" +
"If this error is unclear or you think Quixote is at fault, please open\n" +
"an issue at https://github.com/jamesshore/quixote/issues. Include this\n" +
"error message and a standalone example test that reproduces the error.\n" +
"Error stack trace:\n" +
err.stack
);
}
if (!actualValue.isCompatibleWith(expectedValue)) {
throwBadExpectation(
this, oop.instanceName(expected) + " (" + expected + ")", message,
"Attempted to compare two incompatible types:"
);
}
var expectedDesc = expectedValue.toString();
if (expected instanceof Me) expectedDesc += " (" + expected + ")";
var failure;
try {
failure = assertFn(actualValue, expectedValue, expectedDesc, message);
}
catch (err2) {
throw new Error(
message + "Error in test. Unable to perform assertion.\n" +
"Error message: " + err2.message + "\n" +
" 'actual' descriptor: " + this + " (" + oop.instanceName(this) + ")\n" +
" 'expected' descriptor: " + expected + " (" + oop.instanceName(expected) + ")\n" +
" 'actual' value: " + actualValue + " (" + oop.instanceName(actualValue) + ")\n" +
" 'expected' value: " + expectedValue + " (" + oop.instanceName(expectedValue) + ")\n" +
"If this error is unclear or you think Quixote is at fault, please open\n" +
"an issue at https://github.com/jamesshore/quixote/issues. Include this\n" +
"error message and a standalone example test that reproduces the error.\n"
);
}
if (failure !== undefined) throw new Error(failure);
};
Me.prototype.diff = function diff(expected) {
// Legacy code, strictly for compatibility with deprecated Assertable.equals() and Assertable.diff() methods.
// It's weird because we moved to should.equals(), which always throws an exception, but diff returns a string.
// To avoid duplicating complex logic, we call should.equals() and then unwrap the exception, but only if it's
// the right kind of exception.
try {
this.should.equal(expected);
return "";
}
catch (err) {
var message = err.message;
if (message.indexOf("But was:") === -1) throw err; // it's not an assertion error, it's some other exception
return message;
}
};
Me.prototype.convert = function convert(arg, type) {
// This method is meant to be overridden by subclasses. It should return 'undefined' when an argument
// can't be converted. In this default implementation, no arguments can be converted, so we always
// return 'undefined'.
return undefined;
};
Me.prototype.equals = function equals(that) {
// Descriptors aren't value objects. They're never equal to anything. But sometimes
// they're used in the same places value objects are used, and then this method gets called.
return false;
};
function convertPrimitiveExpectationToValueObjectIfNeeded(self, expected, message) {
var expectedType = typeof expected;
if (expected === null) expectedType = "null";
if (expectedType === "object" && (expected instanceof Me)) return expected;
if (expected === undefined) {
throwBadExpectation(
self, "undefined", message,
"The 'expected' parameter is undefined. Did you misspell a property name?"
);
}
else if (expectedType === "object") {
throwBadExpectation(
self, oop.instanceName(expected), message,
"The 'expected' parameter should be a descriptor, but it wasn't recognized."
);
}
else {
var converted = self.convert(expected, expectedType);
if (converted !== undefined) return converted;
throwBadExpectation(
self, expectedType, message,
"The 'expected' primitive isn't equivalent to the 'actual' descriptor."
);
}
}
function throwBadExpectation(self, expectedType, message, headline) {
throw new Error(
message + "Error in test. Use a different 'expected' parameter.\n" +
headline + "\n" +
" 'actual' type: " + oop.instanceName(self) + " (" + self + ")\n" +
" 'expected' type: " + expectedType
);
}