"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.toHaveNthReturnedWith = exports.toHaveReturnedTimes = exports.toHaveLastReturnedWith = exports.toHaveReturned = exports.toHaveReturnedWith = exports.toHaveBeenNthCalledWith = exports.toHaveBeenLastCalledWith = exports.toHaveBeenCalledWith = exports.toHaveBeenCalledTimes = exports.toHaveBeenCalled = exports.toThrow = exports.toContain = exports.toHaveLength = exports.toHaveProperty = exports.toMatch = exports.toBeInstanceOf = exports.toBeNaN = exports.toBeNull = exports.toBeUndefined = exports.toBeDefined = exports.toBeFalsy = exports.toBeTruthy = exports.toBeLessThanOrEqual = exports.toBeGreaterThanOrEqual = exports.toBeLessThan = exports.toBeGreaterThan = exports.toEqual = exports.toBe = void 0;
const dntShim = __importStar(require("../../../../_dnt.test_shims.js"));
const asserts_js_1 = require("../../std@0.97.0/testing/asserts.js");
const _diff_js_1 = require("../../std@0.97.0/testing/_diff.js");
const colors_js_1 = require("../../std@0.97.0/fmt/colors.js");
const mock = __importStar(require("./mock.js"));
const ACTUAL = (0, colors_js_1.red)((0, colors_js_1.bold)('actual'));
const EXPECTED = (0, colors_js_1.green)((0, colors_js_1.bold)('expected'));
const CAN_NOT_DISPLAY = '[Cannot display]';
function createStr(v) {
    try {
        return dntShim.Deno.inspect(v);
    }
    catch (e) {
        return (0, colors_js_1.red)(CAN_NOT_DISPLAY);
    }
}
function createColor(diffType) {
    switch (diffType) {
        case _diff_js_1.DiffType.added:
            return (s) => (0, colors_js_1.green)((0, colors_js_1.bold)(s));
        case _diff_js_1.DiffType.removed:
            return (s) => (0, colors_js_1.red)((0, colors_js_1.bold)(s));
        default:
            return colors_js_1.white;
    }
}
function createSign(diffType) {
    switch (diffType) {
        case _diff_js_1.DiffType.added:
            return '+   ';
        case _diff_js_1.DiffType.removed:
            return '-   ';
        default:
            return '    ';
    }
}
function buildMessage(diffResult) {
    return diffResult
        .map((result) => {
        const c = createColor(result.type);
        return c(`${createSign(result.type)}${result.value}`);
    })
        .join('\n');
}
function buildDiffMessage(actual, expected) {
    const actualString = createStr(actual);
    const expectedString = createStr(expected);
    let message;
    try {
        const diffResult = (0, _diff_js_1.diff)(actualString.split('\n'), expectedString.split('\n'));
        return buildMessage(diffResult);
    }
    catch (e) {
        return `\n${(0, colors_js_1.red)(CAN_NOT_DISPLAY)} + \n\n`;
    }
}
function buildFail(message) {
    return {
        pass: false,
        message
    };
}
function toBe(actual, expected) {
    if (actual === expected)
        return { pass: true };
    return buildFail(`expect(${ACTUAL}).toBe(${EXPECTED})\n\n${buildDiffMessage(actual, expected)}`);
}
exports.toBe = toBe;
function toEqual(actual, expected) {
    if ((0, asserts_js_1.equal)(actual, expected))
        return { pass: true };
    return buildFail(`expect(${ACTUAL}).toEqual(${EXPECTED})\n\n${buildDiffMessage(actual, expected)}`);
}
exports.toEqual = toEqual;
function toBeGreaterThan(actual, comparison) {
    if (actual > comparison)
        return { pass: true };
    const actualString = createStr(actual);
    const comparisonString = createStr(comparison);
    return buildFail(`expect(${ACTUAL}).toBeGreaterThan(${EXPECTED})\n\n  ${(0, colors_js_1.red)(actualString)} is not greater than ${(0, colors_js_1.green)(comparisonString)}`);
}
exports.toBeGreaterThan = toBeGreaterThan;
function toBeLessThan(actual, comparison) {
    if (actual < comparison)
        return { pass: true };
    const actualString = createStr(actual);
    const comparisonString = createStr(comparison);
    return buildFail(`expect(${ACTUAL}).toBeLessThan(${EXPECTED})\n\n  ${(0, colors_js_1.red)(actualString)} is not less than ${(0, colors_js_1.green)(comparisonString)}`);
}
exports.toBeLessThan = toBeLessThan;
function toBeGreaterThanOrEqual(actual, comparison) {
    if (actual >= comparison)
        return { pass: true };
    const actualString = createStr(actual);
    const comparisonString = createStr(comparison);
    return buildFail(`expect(${ACTUAL}).toBeGreaterThanOrEqual(${EXPECTED})\n\n  ${(0, colors_js_1.red)(actualString)} is not greater than or equal to ${(0, colors_js_1.green)(comparisonString)}`);
}
exports.toBeGreaterThanOrEqual = toBeGreaterThanOrEqual;
function toBeLessThanOrEqual(actual, comparison) {
    if (actual <= comparison)
        return { pass: true };
    const actualString = createStr(actual);
    const comparisonString = createStr(comparison);
    return buildFail(`expect(${ACTUAL}).toBeLessThanOrEqual(${EXPECTED})\n\n  ${(0, colors_js_1.red)(actualString)} is not less than or equal to ${(0, colors_js_1.green)(comparisonString)}`);
}
exports.toBeLessThanOrEqual = toBeLessThanOrEqual;
function toBeTruthy(value) {
    if (value)
        return { pass: true };
    const actualString = createStr(value);
    return buildFail(`expect(${ACTUAL}).toBeTruthy()

      ${(0, colors_js_1.red)(actualString)} is not truthy`);
}
exports.toBeTruthy = toBeTruthy;
function toBeFalsy(value) {
    if (!value)
        return { pass: true };
    const actualString = createStr(value);
    return buildFail(`expect(${ACTUAL}).toBeFalsy()\n\n    ${(0, colors_js_1.red)(actualString)} is not falsy`);
}
exports.toBeFalsy = toBeFalsy;
function toBeDefined(value) {
    if (typeof value !== 'undefined')
        return { pass: true };
    const actualString = createStr(value);
    return buildFail(`expect(${ACTUAL}).toBeDefined()\n\n    ${(0, colors_js_1.red)(actualString)} is not defined`);
}
exports.toBeDefined = toBeDefined;
function toBeUndefined(value) {
    if (typeof value === 'undefined')
        return { pass: true };
    const actualString = createStr(value);
    return buildFail(`expect(${ACTUAL}).toBeUndefined()\n\n    ${(0, colors_js_1.red)(actualString)} is defined but should be undefined`);
}
exports.toBeUndefined = toBeUndefined;
function toBeNull(value) {
    if (value === null)
        return { pass: true };
    const actualString = createStr(value);
    return buildFail(`expect(${ACTUAL}).toBeNull()\n\n    ${(0, colors_js_1.red)(actualString)} should be null`);
}
exports.toBeNull = toBeNull;
function toBeNaN(value) {
    if (typeof value === 'number' && isNaN(value))
        return { pass: true };
    const actualString = createStr(value);
    return buildFail(`expect(${ACTUAL}).toBeNaN()\n\n    ${(0, colors_js_1.red)(actualString)} should be NaN`);
}
exports.toBeNaN = toBeNaN;
function toBeInstanceOf(value, expected) {
    if (value instanceof expected)
        return { pass: true };
    const actualString = createStr(value);
    const expectedString = createStr(expected);
    return buildFail(`expect(${ACTUAL}).toBeInstanceOf(${EXPECTED})\n\n    expected ${(0, colors_js_1.green)(expected.name)} but received ${(0, colors_js_1.red)(actualString)}`);
}
exports.toBeInstanceOf = toBeInstanceOf;
function toMatch(value, pattern) {
    const valueStr = value.toString();
    if (typeof pattern === 'string') {
        if (valueStr.indexOf(pattern) !== -1)
            return { pass: true };
        const actualString = createStr(value);
        const patternString = createStr(pattern);
        return buildFail(`expect(${ACTUAL}).toMatch(${EXPECTED})\n\n    expected ${(0, colors_js_1.red)(actualString)} to contain ${(0, colors_js_1.green)(patternString)}`);
    }
    else if (pattern instanceof RegExp) {
        if (pattern.exec(valueStr))
            return { pass: true };
        const actualString = createStr(value);
        const patternString = createStr(pattern);
        return buildFail(`expect(${ACTUAL}).toMatch(${EXPECTED})\n\n    ${(0, colors_js_1.red)(actualString)} did not match regex ${(0, colors_js_1.green)(patternString)}`);
    }
    else {
        return buildFail('Invalid internal state');
    }
}
exports.toMatch = toMatch;
function toHaveProperty(value, propName) {
    if (typeof value === 'object' && typeof value[propName] !== 'undefined') {
        return { pass: true };
    }
    const actualString = createStr(value);
    const propNameString = createStr(propName);
    return buildFail(`expect(${ACTUAL}).toHaveProperty(${EXPECTED})\n\n    ${(0, colors_js_1.red)(actualString)} did not contain property ${(0, colors_js_1.green)(propNameString)}`);
}
exports.toHaveProperty = toHaveProperty;
function toHaveLength(value, length) {
    if (value?.length === length)
        return { pass: true };
    const actualString = createStr(value.length);
    const lengthString = createStr(length);
    return buildFail(`expect(${ACTUAL}).toHaveLength(${EXPECTED})\n\n    expected array to have length ${(0, colors_js_1.green)(lengthString)} but was ${(0, colors_js_1.red)(actualString)}`);
}
exports.toHaveLength = toHaveLength;
function toContain(value, item) {
    if (value && typeof value.includes === 'function' && value.includes(item)) {
        return { pass: true };
    }
    const actualString = createStr(value);
    const itemString = createStr(item);
    if (value && typeof value.includes === 'function') {
        return buildFail(`expect(${ACTUAL}).toContain(${EXPECTED})\n\n    ${(0, colors_js_1.red)(actualString)} did not contain ${(0, colors_js_1.green)(itemString)}`);
    }
    else {
        return buildFail(`expect(${ACTUAL}).toContain(${EXPECTED})\n\n    expected ${(0, colors_js_1.red)(actualString)} to have an includes method but it is ${(0, colors_js_1.green)(itemString)}`);
    }
}
exports.toContain = toContain;
function toThrow(value, error) {
    let fn;
    if (typeof value === 'function') {
        fn = value;
        try {
            value = value();
        }
        catch (err) {
            value = err;
        }
    }
    const actualString = createStr(fn);
    const errorString = createStr(error);
    if (value instanceof Error) {
        if (typeof error === 'string') {
            if (!value.message.includes(error)) {
                return buildFail(`expect(${ACTUAL}).toThrow(${EXPECTED})\n\nexpected ${(0, colors_js_1.red)(actualString)} to throw error matching ${(0, colors_js_1.green)(errorString)} but it threw ${(0, colors_js_1.red)(value.toString())}`);
            }
        }
        else if (error instanceof RegExp) {
            if (!value.message.match(error)) {
                return buildFail(`expect(${ACTUAL}).toThrow(${EXPECTED})\n\nexpected ${(0, colors_js_1.red)(actualString)} to throw error matching ${(0, colors_js_1.green)(errorString)} but it threw ${(0, colors_js_1.red)(value.toString())}`);
            }
        }
        return { pass: true };
    }
    else {
        return buildFail(`expect(${ACTUAL}).toThrow(${EXPECTED})\n\nexpected ${(0, colors_js_1.red)(actualString)} to throw but it did not`);
    }
}
exports.toThrow = toThrow;
function extractMockCalls(value, name) {
    if (typeof value !== 'function') {
        return {
            calls: null,
            error: `${name} only works on mock functions. received: ${value}`
        };
    }
    const calls = mock.calls(value);
    if (calls === null) {
        return { calls: null, error: `${name} only works on mock functions` };
    }
    return { calls };
}
function toHaveBeenCalled(value) {
    const { calls, error } = extractMockCalls(value, 'toHaveBeenCalled');
    if (error)
        return buildFail(error);
    const actualString = createStr(value);
    if (calls && calls.length !== 0)
        return { pass: true };
    return buildFail(`expect(${ACTUAL}).toHaveBeenCalled()\n\n    ${(0, colors_js_1.red)(actualString)} was not called`);
}
exports.toHaveBeenCalled = toHaveBeenCalled;
function toHaveBeenCalledTimes(value, times) {
    const { calls, error } = extractMockCalls(value, 'toHaveBeenCalledTimes');
    if (error)
        return buildFail(error);
    if (!calls)
        return buildFail('Invalid internal state');
    if (calls && calls.length === times)
        return { pass: true };
    return buildFail(`expect(${ACTUAL}).toHaveBeenCalledTimes(${EXPECTED})\n\n    expected ${times} calls but was called: ${calls.length}`);
}
exports.toHaveBeenCalledTimes = toHaveBeenCalledTimes;
function toHaveBeenCalledWith(value, ...args) {
    const { calls, error } = extractMockCalls(value, 'toHaveBeenCalledWith');
    if (error)
        return buildFail(error);
    const wasCalledWith = calls && calls.some((c) => (0, asserts_js_1.equal)(c.args, args));
    if (wasCalledWith)
        return { pass: true };
    const argsString = createStr(args);
    return buildFail(`expect(${ACTUAL}).toHaveBeenCalledWith(${EXPECTED})\n\n    function was not called with: ${(0, colors_js_1.green)(argsString)}`);
}
exports.toHaveBeenCalledWith = toHaveBeenCalledWith;
function toHaveBeenLastCalledWith(value, ...args) {
    const { calls, error } = extractMockCalls(value, 'toHaveBeenLastCalledWith');
    if (error)
        return buildFail(error);
    if (!calls || !calls.length) {
        return buildFail(`expect(${ACTUAL}).toHaveBeenLastCalledWith(...${EXPECTED})\n\n    expect last call args to be ${args} but was not called`);
    }
    const lastCall = calls[calls.length - 1];
    if ((0, asserts_js_1.equal)(lastCall.args, args))
        return { pass: true };
    return buildFail(`expect(${ACTUAL}).toHaveBeenLastCalledWith(...${EXPECTED})\n\n    expect last call args to be ${args} but was: ${lastCall.args}`);
}
exports.toHaveBeenLastCalledWith = toHaveBeenLastCalledWith;
function toHaveBeenNthCalledWith(value, nth, ...args) {
    const { calls, error } = extractMockCalls(value, 'toHaveBeenNthCalledWith');
    if (error)
        return buildFail(error);
    const nthCall = calls && calls[nth - 1];
    if (nthCall) {
        if ((0, asserts_js_1.equal)(nthCall.args, args))
            return { pass: true };
        return buildFail(`expect(${ACTUAL}).toHaveBeenNthCalledWith(${EXPECTED})\n\n    expect ${nth}th call args to be ${args} but was: ${nthCall.args}`);
    }
    else {
        return buildFail(`expect(${ACTUAL}).toHaveBeenNthCalledWith(${EXPECTED})\n\n    ${nth}th call was not made.`);
    }
}
exports.toHaveBeenNthCalledWith = toHaveBeenNthCalledWith;
function toHaveReturnedWith(value, result) {
    const { calls, error } = extractMockCalls(value, 'toHaveReturnedWith');
    if (error)
        return buildFail(error);
    const wasReturnedWith = calls && calls.some((c) => c.returns && (0, asserts_js_1.equal)(c.returned, result));
    if (wasReturnedWith)
        return { pass: true };
    return buildFail(`expect(${ACTUAL}).toHaveReturnedWith(${EXPECTED})\n\n    function did not return: ${result}`);
}
exports.toHaveReturnedWith = toHaveReturnedWith;
function toHaveReturned(value) {
    const { calls, error } = extractMockCalls(value, 'toHaveReturned');
    if (error)
        return buildFail(error);
    if (calls && calls.some((c) => c.returns))
        return { pass: true };
    // TODO(allain): better messages
    return buildFail(`expected function to return but it never did`);
}
exports.toHaveReturned = toHaveReturned;
// TODO(allain): better messages
function toHaveLastReturnedWith(value, expected) {
    const { calls, error } = extractMockCalls(value, 'toHaveLastReturnedWith');
    if (error)
        return buildFail(error);
    const lastCall = calls && calls[calls.length - 1];
    if (!lastCall) {
        return buildFail('no calls made to function');
    }
    if (lastCall.throws) {
        return buildFail(`last call to function threw: ${lastCall.thrown}`);
    }
    if ((0, asserts_js_1.equal)(lastCall.returned, expected))
        return { pass: true };
    return buildFail(`expected last call to return ${expected} but returned: ${lastCall.returned}`);
}
exports.toHaveLastReturnedWith = toHaveLastReturnedWith;
function toHaveReturnedTimes(value, times) {
    const { calls, error } = extractMockCalls(value, 'toHaveReturnedTimes');
    if (error)
        return buildFail(error);
    const returnCount = calls && calls.filter((c) => c.returns).length;
    if (returnCount !== times) {
        return buildFail(`expected ${times} returned times but returned ${returnCount} times`);
    }
    return { pass: true };
}
exports.toHaveReturnedTimes = toHaveReturnedTimes;
function toHaveNthReturnedWith(value, nth, expected) {
    const { calls, error } = extractMockCalls(value, 'toHaveNthReturnedWith');
    if (error)
        return buildFail(error);
    const nthCall = calls && calls[nth - 1];
    if (!nthCall) {
        return buildFail(`${nth} calls were now made`);
    }
    if (nthCall.throws) {
        return buildFail(`${nth}th call to function threw: ${nthCall.thrown}`);
    }
    if (!(0, asserts_js_1.equal)(nthCall.returned, expected)) {
        return buildFail(`expected ${nth}th call to return ${expected} but returned: ${nthCall.returned}`);
    }
    return { pass: true };
}
exports.toHaveNthReturnedWith = toHaveNthReturnedWith;
