mockzilla
Version:
A mocking toolkit leveraging the power of TypeScript to enhance your jest experience.
216 lines (215 loc) • 8.26 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MockzillaNode = void 0;
const chalk_1 = __importDefault(require("chalk"));
const jest_diff_1 = require("jest-diff");
const expect_utils_1 = require("@jest/expect-utils");
const error_1 = require("./error");
const EXPECTATION = chalk_1.default.green("Expectation");
const ERROR = chalk_1.default.red("ERROR");
class MockzillaNode {
constructor(path) {
this.disabled = false;
this.children = {};
this.pathTo = (key) => (key ? `${this.path}.${key}` : this.path);
this.path = path;
this.traps = {
ownKeys: () => {
this.disabledCheck("ownKeys");
return Object.getOwnPropertyNames(this.children);
},
has: (target, prop) => this.disabledCheck(prop) || prop in this.children || false,
get: (target, prop) => {
this.disabledCheck(prop);
if (prop in this.children)
return this.children[prop].value;
this.notImplemented(prop);
},
// not to be called
apply: () => this.disabledCheckNotImplemented("apply", undefined),
getPrototypeOf: () => this.disabledCheckNotImplemented("getPrototypeOf", null),
setPrototypeOf: () => this.disabledCheckNotImplemented("setPrototypeOf", false),
isExtensible: () => this.disabledCheckNotImplemented("isExtensible", false),
preventExtensions: () => this.disabledCheckNotImplemented("preventExtensions", false),
set: () => this.disabledCheckNotImplemented("set", false),
deleteProperty: () => this.disabledCheckNotImplemented("deleteProperty", false),
construct: () => this.disabledCheckNotImplemented("construct", {}),
getOwnPropertyDescriptor: () => this.disabledCheckNotImplemented("getOwnPropertyDescriptor", undefined),
defineProperty: () => this.disabledCheckNotImplemented("defineProperty", false),
};
this.proxy = new Proxy({}, this.traps);
}
notImplemented(what) {
throw new error_1.MockzillaError(`Mock "${this.pathTo(what)}" is not implemented`);
}
disabledCheck(what) {
if (this.disabled)
throw new error_1.MockzillaError(`Mock "${this.pathTo(what)}" has been used after tests have finished!`);
return false;
}
disabledCheckNotImplemented(what, ret) {
this.disabledCheck(what) || this.notImplemented(what);
return ret;
}
getNested(keys) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const nestedKey = keys.pop();
const nestedNode = keys.reduce((node, key) => node.getChildNode(key), this);
return [nestedKey, nestedNode];
}
allow(key) {
const parts = key.split(".");
if (parts.length > 1) {
const [nestedKey, nestedNode] = this.getNested(parts);
nestedNode.allow(nestedKey);
return;
}
this.getChildNode(key);
}
setValue(key, value) {
const parts = key.split(".");
if (parts.length > 1) {
const [nestedKey, nestedNode] = this.getNested(parts);
nestedNode.setValue(nestedKey, value);
return;
}
let child = this.getChild(key, "value");
if (!child) {
child = {
type: "value",
value,
};
this.children[key] = child;
}
}
addExpectation(key, expectation) {
const parts = key.split(".");
if (parts.length > 1) {
const [nestedKey, nestedConfig] = this.getNested(parts);
nestedConfig.addExpectation(nestedKey, expectation);
return;
}
let child = this.getChild(key, "method");
if (!child) {
child = this.createMethod(key);
this.children[key] = child;
}
if (expectation)
child.expectations.push(expectation);
}
getCalls(key) {
const parts = key.split(".");
if (parts.length > 1) {
const [nestedKey, nestedConfig] = this.getNested(parts);
return nestedConfig.getCalls(nestedKey);
}
let child = this.getChild(key, "method");
if (!child) {
child = this.createMethod(key);
this.children[key] = child;
}
return child.calls;
}
getChild(key, type) {
const child = this.children[key];
if (!child)
return null;
if (child.type !== type)
throw new error_1.MockzillaError(`Expect a mock ${type}, but found a mock ${child.type} at ${this.path}.${key}`);
return child;
}
createMethod(key) {
const name = chalk_1.default.dim(`${this.pathTo(key)}()`);
const expectations = [];
const calls = [];
return {
type: "method",
value(...args) {
calls.push(args);
const expectation = expectations.shift();
if (!expectation)
throw new error_1.MockzillaError(`${ERROR}: ${name} has been called unexpectedly!`);
if (expectation.spy)
return expectation.spy(...args);
if (expectation.args) {
if (!(0, expect_utils_1.equals)(args, expectation.args, [expect_utils_1.iterableEquality])) {
const title = `Expectation of call to ${name} did not match invocation`;
const expectationStack = (0, error_1.colorizeStack)(expectation.stack, true);
const argsDiff = (0, jest_diff_1.diff)(expectation.args, args);
throw new error_1.MockzillaError(`${EXPECTATION}: ${expectationStack}\n \n${ERROR}: ${title}:\n${argsDiff}`);
}
}
if (expectation.throws)
throw expectation.throws;
return expectation.returns;
},
calls,
expectations,
};
}
getChildNode(key) {
let child = this.getChild(key, "node");
if (!child) {
const node = new MockzillaNode(this.pathTo(key));
child = {
type: "node",
value: node.proxy,
node,
};
this.children[key] = child;
}
return child.node;
}
getProxy() {
return this.proxy;
}
disable() {
if (!this.disabled) {
this.disabled = true;
for (const key of Object.keys(this.children)) {
const child = this.children[key];
if (child.type === "node")
child.node.disable();
}
this.children = {};
}
}
enable() {
this.disabled = false;
}
isDisabled() {
return this.disabled;
}
verify() {
if (this.disabled)
return;
for (const key of Object.keys(this.children)) {
const child = this.children[key];
if (child.type === "node") {
child.node.verify();
}
else if (child.type === "method") {
const { expectations } = child;
if (expectations.length) {
const callName = `${this.pathTo(key)}()`;
const uniqueExpectations = expectations.filter((e, i, self) => self.indexOf(e) === i);
throw new error_1.MockzillaError(`Missing ${expectations.length} calls to ${chalk_1.default.dim(callName)}:\n${uniqueExpectations
.map((expectation) => (0, error_1.colorizeStack)(expectation.stack, true))
.join("\n")}`, true);
}
}
}
}
verifyAndDisable() {
try {
this.verify();
}
finally {
this.disable();
}
}
}
exports.MockzillaNode = MockzillaNode;