'use strict';

require('../common');
const assert = require('assert');
const { Assert } = require('assert');
const { inspect } = require('util');
const { test } = require('node:test');

// Disable colored output to prevent color codes from breaking assertion
// message comparisons. This should only be an issue when process.stdout
// is a TTY.
if (process.stdout.isTTY) {
  process.env.NODE_DISABLE_COLORS = '1';
}

test('Assert constructor requires new', () => {
  assert.throws(() => Assert(), {
    code: 'ERR_CONSTRUCT_CALL_REQUIRED',
    name: 'TypeError',
  });
});

test('Assert class non strict', () => {
  const assertInstance = new Assert({ diff: undefined, strict: false });

  assertInstance.ok(
    assert.AssertionError.prototype instanceof Error,
    'assert.AssertionError instanceof Error'
  );
  assert.strictEqual(typeof assertInstance.ok, 'function');
  assert.strictEqual(assertInstance.ok.strictEqual, undefined);
  assert.strictEqual(typeof assertInstance.strictEqual, 'function');
  assertInstance.ok(true);
  assertInstance.throws(
    () => {
      assertInstance.fail();
    },
    {
      code: 'ERR_ASSERTION',
      name: 'AssertionError',
      message: 'Failed',
      operator: 'fail',
      actual: undefined,
      expected: undefined,
      generatedMessage: true,
      stack: /Failed/,
    }
  );
  assertInstance.equal(undefined, undefined);
  assertInstance.equal(null, undefined);
  assertInstance.equal(2, '2');
  assertInstance.notEqual(true, false);
  assertInstance.throws(() => assertInstance.deepEqual(/a/), {
    code: 'ERR_MISSING_ARGS',
  });
  assertInstance.throws(() => assertInstance.notDeepEqual('test'), {
    code: 'ERR_MISSING_ARGS',
  });
  assertInstance.notStrictEqual(2, '2');
  assertInstance.throws(
    () => assertInstance.strictEqual(2, '2'),
    assertInstance.AssertionError,
    "strictEqual(2, '2')"
  );
  assertInstance.throws(
    () => {
      assertInstance.partialDeepStrictEqual(
        { a: true },
        { a: false },
        'custom message'
      );
    },
    {
      code: 'ERR_ASSERTION',
      name: 'AssertionError',
      message:
        'custom message\n+ actual - expected\n\n  {\n+   a: true\n-   a: false\n  }\n',
    }
  );
  assertInstance.throws(() => assertInstance.match(/abc/, 'string'), {
    code: 'ERR_INVALID_ARG_TYPE',
    message:
      'The "regexp" argument must be an instance of RegExp. ' +
      "Received type string ('string')",
  });
  assertInstance.throws(() => assertInstance.doesNotMatch(/abc/, 'string'), {
    code: 'ERR_INVALID_ARG_TYPE',
    message:
      'The "regexp" argument must be an instance of RegExp. ' +
      "Received type string ('string')",
  });

  /* eslint-disable no-restricted-syntax */
  {
    function thrower(errorConstructor) {
      throw new errorConstructor({});
    }

    let threw = false;
    try {
      assertInstance.doesNotThrow(
        () => thrower(TypeError),
        assertInstance.AssertionError
      );
    } catch (e) {
      threw = true;
      assertInstance.ok(e instanceof TypeError);
    }
    assertInstance.ok(
      threw,
      'assertInstance.doesNotThrow with an explicit error is eating extra errors'
    );
  }
  {
    let threw = false;
    const rangeError = new RangeError('my range');

    try {
      assertInstance.doesNotThrow(
        () => {
          throw new TypeError('wrong type');
        },
        TypeError,
        rangeError
      );
    } catch (e) {
      threw = true;
      assertInstance.ok(e.message.includes(rangeError.message));
      assertInstance.ok(e instanceof assertInstance.AssertionError);
      assertInstance.ok(!e.stack.includes('doesNotThrow'), e);
    }
    assertInstance.ok(threw);
  }
  /* eslint-enable no-restricted-syntax */
});

test('Assert class strict', () => {
  const assertInstance = new Assert();

  assertInstance.equal(assertInstance.equal, assertInstance.strictEqual);
  assertInstance.equal(
    assertInstance.deepEqual,
    assertInstance.deepStrictEqual
  );
  assertInstance.equal(assertInstance.notEqual, assertInstance.notStrictEqual);
  assertInstance.equal(
    assertInstance.notDeepEqual,
    assertInstance.notDeepStrictEqual
  );
});

test('Assert class with invalid diff option', () => {
  assert.throws(() => new Assert({ diff: 'invalid' }), {
    code: 'ERR_INVALID_ARG_VALUE',
    name: 'TypeError',
    message: "The property 'options.diff' must be one of: 'simple', 'full'. Received 'invalid'",
  });
});

const longLinesOfAs = 'A\n'.repeat(100);
const longLinesOFBs = 'B\n'.repeat(100);
const truncatedAs = 'A\\n'.repeat(10) + '...';
const truncatedBs = 'B\\n'.repeat(10) + '...';

const longStringOfAs = 'A'.repeat(10_000);
const longStringOfBs = 'B'.repeat(10_000);

const longLinesOfAsWithEllipsis = longStringOfAs.substring(0, 9_488) + '...';
const longLinesOFBsWithEllipsis = longStringOfBs.substring(0, 9_488) + '...';
test('Assert class non strict with full diff', () => {
  const assertInstance = new Assert({ diff: 'full', strict: false });

  // long strings
  {
    assertInstance.throws(
      () => {
        assertInstance.strictEqual(longStringOfAs, longStringOfBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'strictEqual');
        assertInstance.strictEqual(err.diff, 'full');
        assertInstance.strictEqual(err.actual, longStringOfAs);
        assertInstance.strictEqual(err.expected, longStringOfBs);

        assertInstance.strictEqual(
          err.message,
          `Expected values to be strictly equal:\n+ actual - expected\n\n` +
            `+ '${longStringOfAs}'\n- '${longStringOfBs}'\n`
        );
        assertInstance.ok(
          inspect(err).includes(`actual: '${longLinesOfAsWithEllipsis}'`)
        );
        assertInstance.ok(
          inspect(err).includes(`expected: '${longLinesOFBsWithEllipsis}'`)
        );
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.notStrictEqual(longStringOfAs, longStringOfAs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'notStrictEqual');
        assertInstance.strictEqual(err.diff, 'full');
        assertInstance.strictEqual(err.actual, longStringOfAs);
        assertInstance.strictEqual(err.expected, longStringOfAs);

        assertInstance.strictEqual(
          err.message,
          `Expected "actual" to be strictly unequal to:\n\n` +
            `'${longStringOfAs}'`
        );
        assertInstance.ok(
          inspect(err).includes(`actual: '${longLinesOfAsWithEllipsis}'`)
        );
        assertInstance.ok(
          inspect(err).includes(`expected: '${longLinesOfAsWithEllipsis}'`)
        );
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.deepEqual(longStringOfAs, longStringOfBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'deepEqual');
        assertInstance.strictEqual(err.diff, 'full');
        assertInstance.strictEqual(err.actual, longStringOfAs);
        assertInstance.strictEqual(err.expected, longStringOfBs);

        assertInstance.strictEqual(
          err.message,
          `Expected values to be loosely deep-equal:\n\n` +
            `'${longStringOfAs}'\n\nshould loosely deep-equal\n\n'${longStringOfBs}'`
        );
        assertInstance.ok(
          inspect(err).includes(`actual: '${longLinesOfAsWithEllipsis}'`)
        );
        assertInstance.ok(
          inspect(err).includes(`expected: '${longLinesOFBsWithEllipsis}'`)
        );
        return true;
      }
    );
  }

  // long lines
  {
    assertInstance.throws(
      () => {
        assertInstance.strictEqual(longLinesOfAs, longLinesOFBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'strictEqual');
        assertInstance.strictEqual(err.diff, 'full');
        assertInstance.strictEqual(err.actual, longLinesOfAs);
        assertInstance.strictEqual(err.expected, longLinesOFBs);

        assertInstance.strictEqual(err.message.split('\n').length, 204);
        assertInstance.strictEqual(err.actual.split('\n').length, 101);
        assertInstance.ok(
          err.message.includes('Expected values to be strictly equal')
        );
        assertInstance.ok(inspect(err).includes(`actual: '${truncatedAs}`));
        assertInstance.ok(inspect(err).includes(`expected: '${truncatedBs}`));
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.notStrictEqual(longLinesOfAs, longLinesOfAs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'notStrictEqual');
        assertInstance.strictEqual(err.diff, 'full');
        assertInstance.strictEqual(err.actual, longLinesOfAs);
        assertInstance.strictEqual(err.expected, longLinesOfAs);

        assertInstance.strictEqual(err.message.split('\n').length, 103);
        assertInstance.strictEqual(err.actual.split('\n').length, 101);
        assertInstance.ok(
          err.message.includes(`Expected "actual" to be strictly unequal to:`)
        );
        assertInstance.ok(inspect(err).includes(`actual: '${truncatedAs}`));
        assertInstance.ok(inspect(err).includes(`expected: '${truncatedAs}`));
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.deepEqual(longLinesOfAs, longLinesOFBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'deepEqual');
        assertInstance.strictEqual(err.diff, 'full');
        assertInstance.strictEqual(err.actual, longLinesOfAs);
        assertInstance.strictEqual(err.expected, longLinesOFBs);

        assertInstance.strictEqual(err.message.split('\n').length, 205);
        assertInstance.strictEqual(err.actual.split('\n').length, 101);
        assertInstance.ok(
          err.message.includes(`Expected values to be loosely deep-equal:`)
        );
        assertInstance.ok(inspect(err).includes(`actual: '${truncatedAs}`));
        assertInstance.ok(inspect(err).includes(`expected: '${truncatedBs}`));
        return true;
      }
    );
  }
});

test('Assert class non strict with simple diff', () => {
  const assertInstance = new Assert({ diff: 'simple', strict: false });

  // long strings
  {
    assertInstance.throws(
      () => {
        assertInstance.strictEqual(longStringOfAs, longStringOfBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'strictEqual');
        assertInstance.strictEqual(err.diff, 'simple');
        assertInstance.strictEqual(err.actual, longStringOfAs);
        assertInstance.strictEqual(err.expected, longStringOfBs);

        assertInstance.strictEqual(
          err.message,
          `Expected values to be strictly equal:\n+ actual - expected\n\n` +
            `+ '${longStringOfAs}'\n- '${longStringOfBs}'\n`
        );
        assertInstance.ok(
          inspect(err).includes(`actual: '${longLinesOfAsWithEllipsis}'`)
        );
        assertInstance.ok(
          inspect(err).includes(`expected: '${longLinesOFBsWithEllipsis}'`)
        );
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.notStrictEqual(longStringOfAs, longStringOfAs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'notStrictEqual');
        assertInstance.strictEqual(err.diff, 'simple');
        assertInstance.strictEqual(err.actual, longStringOfAs);
        assertInstance.strictEqual(err.expected, longStringOfAs);

        assertInstance.strictEqual(
          err.message,
          `Expected "actual" to be strictly unequal to:\n\n` +
            `'${longStringOfAs}'`
        );
        assertInstance.ok(
          inspect(err).includes(`actual: '${longLinesOfAsWithEllipsis}'`)
        );
        assertInstance.ok(
          inspect(err).includes(`expected: '${longLinesOfAsWithEllipsis}'`)
        );
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.deepEqual(longStringOfAs, longStringOfBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'deepEqual');
        assertInstance.strictEqual(err.diff, 'simple');
        assertInstance.strictEqual(err.actual, longStringOfAs);
        assertInstance.strictEqual(err.expected, longStringOfBs);

        assertInstance.strictEqual(
          err.message,
          `Expected values to be loosely deep-equal:\n\n` +
            `'${
              longStringOfAs.substring(0, 508) + '...'
            }\n\nshould loosely deep-equal\n\n'${
              longStringOfBs.substring(0, 508) + '...'
            }`
        );
        assertInstance.ok(
          inspect(err).includes(`actual: '${longLinesOfAsWithEllipsis}'`)
        );
        assertInstance.ok(
          inspect(err).includes(`expected: '${longLinesOFBsWithEllipsis}'`)
        );
        return true;
      }
    );
  }

  // long lines
  {
    assertInstance.throws(
      () => {
        assertInstance.strictEqual(longLinesOfAs, longLinesOFBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'strictEqual');
        assertInstance.strictEqual(err.diff, 'simple');
        assertInstance.strictEqual(err.actual, longLinesOfAs);
        assertInstance.strictEqual(err.expected, longLinesOFBs);
        assertInstance.strictEqual(err.message.split('\n').length, 204);
        assertInstance.strictEqual(err.actual.split('\n').length, 101);

        assertInstance.ok(
          err.message.includes('Expected values to be strictly equal')
        );
        assertInstance.ok(inspect(err).includes(`actual: '${truncatedAs}`));
        assertInstance.ok(inspect(err).includes(`expected: '${truncatedBs}`));
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.notStrictEqual(longLinesOfAs, longLinesOfAs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'notStrictEqual');
        assertInstance.strictEqual(err.diff, 'simple');
        assertInstance.strictEqual(err.actual, longLinesOfAs);
        assertInstance.strictEqual(err.expected, longLinesOfAs);

        assertInstance.strictEqual(err.message.split('\n').length, 50);
        assertInstance.strictEqual(err.actual.split('\n').length, 101);
        assertInstance.ok(
          err.message.includes(`Expected "actual" to be strictly unequal to:`)
        );
        assertInstance.ok(inspect(err).includes(`actual: '${truncatedAs}`));
        assertInstance.ok(inspect(err).includes(`expected: '${truncatedAs}`));
        return true;
      }
    );

    assertInstance.throws(
      () => {
        assertInstance.deepEqual(longLinesOfAs, longLinesOFBs);
      },
      (err) => {
        assertInstance.strictEqual(err.code, 'ERR_ASSERTION');
        assertInstance.strictEqual(err.operator, 'deepEqual');
        assertInstance.strictEqual(err.diff, 'simple');
        assertInstance.strictEqual(err.actual, longLinesOfAs);
        assertInstance.strictEqual(err.expected, longLinesOFBs);

        assertInstance.strictEqual(err.message.split('\n').length, 109);
        assertInstance.strictEqual(err.actual.split('\n').length, 101);
        assertInstance.ok(
          err.message.includes(`Expected values to be loosely deep-equal:`)
        );
        assertInstance.ok(inspect(err).includes(`actual: '${truncatedAs}`));
        assertInstance.ok(inspect(err).includes(`expected: '${truncatedBs}`));
        return true;
      }
    );
  }
});
