UNPKG

@serenity-js/core

Version:

The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure

248 lines 8.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorFactory = void 0; const node_util_1 = require("node:util"); const diff_1 = require("diff"); const objects_1 = require("tiny-types/lib/objects"); const io_1 = require("../io"); const Unanswered_1 = require("../screenplay/questions/Unanswered"); const AnsiDiffFormatter_1 = require("./diff/AnsiDiffFormatter"); /** * Generates Serenity/JS [`RuntimeError`](https://serenity-js.org/api/core/class/RuntimeError/) objects based * on the provided [configuration](https://serenity-js.org/api/core/interface/ErrorOptions/). * * @group Errors */ class ErrorFactory { formatter; constructor(formatter = new AnsiDiffFormatter_1.AnsiDiffFormatter()) { this.formatter = formatter; } create(errorType, options) { const message = [ this.title(options.message), options.expectation && `\nExpectation: ${options.expectation}`, options.diff && ('\n' + this.diffFrom(options.diff)), options.location && (` at ${options.location.path.value}:${options.location.line}:${options.location.column}`), ]. filter(Boolean). join('\n'); return new errorType(message, options?.cause); } title(value) { return String(value).trim(); } diffFrom(diff) { return new Diff(diff.expected, diff.actual) .lines() .map(line => line.decorated(this.formatter)) .join('\n'); } } exports.ErrorFactory = ErrorFactory; class DiffLine { type; value; static markers = { 'expected': '- ', 'received': '+ ', 'unchanged': ' ', }; static empty = () => new DiffLine('unchanged', ''); static unchanged = (line) => new DiffLine('unchanged', String(line)); static expected = (line) => new DiffLine('expected', String(line)); static received = (line) => new DiffLine('received', String(line)); static changed = (change, line) => { if (change.removed) { return this.expected(line); } if (change.added) { return this.received(line); } return this.unchanged(line); }; constructor(type, value) { this.type = type; this.value = value; } prependMarker() { return this.prepend(this.marker()); } appendMarker() { return this.append(this.marker()); } prepend(text) { return new DiffLine(this.type, String(text) + this.value); } append(text) { return new DiffLine(this.type, this.value + String(text)); } decorated(decorator) { return decorator[this.type](this.value); } marker() { return DiffLine.markers[this.type]; } } class DiffValue { value; nameAndType; summary; changes; desiredNameFieldLength; constructor(name, value) { this.value = value; this.nameAndType = `${name} ${io_1.ValueInspector.typeOf(value)}`; this.desiredNameFieldLength = this.nameAndType.length; this.summary = this.summaryOf(value); } withDesiredFieldLength(columns) { this.desiredNameFieldLength = columns; return this; } hasSummary() { return this.summary !== undefined; } type() { return io_1.ValueInspector.typeOf(this.value); } isComplex() { return (typeof this.value === 'object' || node_util_1.types.isProxy(this.value)) && !(this.value instanceof RegExp) && !(this.value instanceof Unanswered_1.Unanswered); } isArray() { return Array.isArray(this.value); } isComparableAsJson() { if (!this.value || this.value instanceof Unanswered_1.Unanswered) { return false; } return io_1.ValueInspector.isPlainObject(this.value) || this.value['toJSON']; } toString() { const labelWidth = this.desiredNameFieldLength - this.nameAndType.length; return [ this.nameAndType, this.summary && ': '.padEnd(labelWidth + 2), this.summary, this.changes && this.changes.padStart(labelWidth + 5), ]. filter(Boolean). join(''); } summaryOf(value) { if (value instanceof Date) { return value.toISOString(); } const isDefined = value !== undefined && value !== null; if (isDefined && (io_1.ValueInspector.isPrimitive(value) || value instanceof RegExp)) { return String(value); } return undefined; } } class Diff { diff; constructor(expectedValue, actualValue) { this.diff = this.diffFrom(expectedValue, actualValue); } diffFrom(expectedValue, actualValue) { const { expected, actual } = this.aligned(new DiffValue('Expected', expectedValue), new DiffValue('Received', actualValue)); if (this.shouldRenderActualValueOnly(expected, actual)) { return this.renderActualValue(expected, actual); } if (this.shouldRenderJsonDiff(expected, actual)) { return this.renderJsonDiff(expected, actual); } if (this.shouldRenderArrayDiff(expected, actual)) { return this.renderArrayDiff(expected, actual); } return [ DiffLine.expected(expected), DiffLine.received(actual), DiffLine.empty(), ]; } shouldRenderActualValueOnly(expected, actual) { return actual.isComplex() && !actual.hasSummary() && expected.type() !== actual.type(); } shouldRenderJsonDiff(expected, actual) { return expected.isComparableAsJson() && actual.isComparableAsJson() && !expected.hasSummary() && !actual.hasSummary(); } shouldRenderArrayDiff(expected, actual) { return expected.isArray() && actual.isArray(); } aligned(expected, actual) { const maxFieldLength = Math.max(expected.desiredNameFieldLength, actual.desiredNameFieldLength); return { expected: expected.withDesiredFieldLength(maxFieldLength), actual: actual.withDesiredFieldLength(maxFieldLength) }; } renderActualValue(expected, actual) { const lines = (0, io_1.inspected)(actual.value) .split('\n') .map(DiffLine.unchanged); return [ DiffLine.expected(expected), DiffLine.received(actual), DiffLine.empty(), ...lines, DiffLine.empty(), ]; } renderJsonDiff(expected, actual) { const changes = (0, diff_1.diffJson)(expected.value, actual.value); const lines = changes.reduce((acc, change) => { const changedLines = change.value.trimEnd().split('\n'); return acc.concat(changedLines.map(line => DiffLine.changed(change, line).prependMarker())); }, []); const { added, removed } = this.countOf(changes); return [ DiffLine.expected(expected).append(' ').appendMarker().append(`${removed}`), DiffLine.received(actual).append(' ').appendMarker().append(`${added}`), DiffLine.empty(), ...lines, DiffLine.empty(), ]; } renderArrayDiff(expected, actual) { const changes = (0, diff_1.diffArrays)(expected.value, actual.value, { comparator: objects_1.equal }); const lines = changes.reduce((acc, change) => { const items = change.value; return acc.concat(items.map(item => DiffLine.changed(change, (0, io_1.inspected)(item, { compact: true })) .prepend(' ') .prependMarker())); }, []); const { added, removed } = this.countOf(changes); return [ DiffLine.expected(expected).append(' ').appendMarker().append(`${removed}`), DiffLine.received(actual).append(' ').appendMarker().append(`${added}`), DiffLine.empty(), DiffLine.unchanged(' ['), ...lines, DiffLine.unchanged(' ]'), DiffLine.empty(), ]; } countOf(changes) { return changes.reduce(({ removed, added }, change) => { return { removed: removed + (change.removed ? change.count : 0), added: added + (change.added ? change.count : 0), }; }, { removed: 0, added: 0 }); } lines() { return this.diff; } } //# sourceMappingURL=ErrorFactory.js.map