testdouble
Version:
A minimal test double library for TDD with JavaScript
84 lines (83 loc) • 3.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("../wrap/lodash");
const imitate_1 = require("../imitate");
const log_1 = require("../log");
const reset_1 = require("../reset");
const anything_1 = require("../stringify/anything");
function default_1(object, property, manualReplacement) {
const isManual = arguments.length > 2;
const realThingExists = object[property] ||
Object.prototype.hasOwnProperty.call(object, property);
if (isManual || realThingExists) {
const getter = findGetterOnly(object, property);
if (getter && getter.isOwn && !getter.descriptor.configurable) {
return log_1.default.error('td.replace', `"${property}" is defined with a getter that cannot be redefined. Compilers like SWC and Babel define ES module exports this way. To replace a module's export, replace the whole module with td.replace('path/to/module') instead.`);
}
const realThing = object[property];
return lodash_1.default.tap(getFake(isManual, property, manualReplacement, realThing), (fakeThing) => {
if (getter) {
defineValue(object, property, fakeThing, getter.descriptor.enumerable);
}
else {
object[property] = fakeThing;
}
reset_1.default.onNextReset(() => {
if (getter && getter.isOwn) {
Object.defineProperty(object, property, getter.descriptor);
}
else if (getter) {
delete object[property];
}
else if (realThingExists) {
object[property] = realThing;
}
else {
delete object[property];
}
});
});
}
else {
log_1.default.error('td.replace', `No "${property}" property was found.`);
}
}
exports.default = default_1;
// Assigning to a getter with no setter throws, so find any such accessor
// (on the object or up its prototype chain) to replace it by definition
const findGetterOnly = (object, property) => {
let thing = object;
while (thing != null) {
const descriptor = Object.getOwnPropertyDescriptor(thing, property);
if (descriptor) {
return descriptor.get && !descriptor.set
? { descriptor, isOwn: thing === object }
: undefined;
}
thing = Object.getPrototypeOf(thing);
}
};
const defineValue = (object, property, value, enumerable) => {
Object.defineProperty(object, property, {
configurable: true,
writable: true,
enumerable,
value
});
};
const getFake = (isManual, property, manualReplacement, realThing) => {
if (isManual) {
warnIfTypeMismatch(property, manualReplacement, realThing);
return manualReplacement;
}
else {
return (0, imitate_1.default)(realThing, [property]);
}
};
const warnIfTypeMismatch = (property, fakeThing, realThing) => {
const fakeType = typeof fakeThing;
const realType = typeof realThing;
if (realThing !== undefined && fakeType !== realType) {
log_1.default.warn('td.replace', `property "${property}" ${(0, anything_1.default)(realThing)} (${lodash_1.default.capitalize(realType)}) was replaced with ${(0, anything_1.default)(fakeThing)}, which has a different type (${lodash_1.default.capitalize(fakeType)}).`);
}
};