/* eslint-disable @typescript-eslint/no-empty-function, @stylistic/curly-newline, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/no-unsafe-argument */ import {Buffer} from 'node:buffer'; import fs from 'node:fs'; import net from 'node:net'; import Stream from 'node:stream'; import {inspect} from 'node:util'; import {test} from 'node:test'; import assert from 'node:assert/strict'; import {JSDOM} from 'jsdom'; import {Subject, Observable} from 'rxjs'; import {temporaryFile} from 'tempy'; import {expectTypeOf} from 'expect-type'; import ZenObservable from 'zen-observable'; import is, { assert as isAssert, assertPropertyKey, type AssertionTypeDescription, type NaN as NaNType, type Predicate, type Primitive, type TypedArray, type TypeName, type UrlString, } from '../source/index.ts'; import {keysOf} from '../source/utilities.ts'; class PromiseSubclassFixture extends Promise {} class ErrorSubclassFixture extends Error {} const {window} = new JSDOM(); const {document} = window; type Test = Readonly<{ fixtures: unknown[]; typename?: TypeName; typeDescription?: AssertionTypeDescription; }>; // Every entry should be unique and belongs in the most specific type for that entry const reusableFixtures = { asyncFunction: [async function () {}, async () => {}], asyncGeneratorFunction: [ async function * () {}, async function * () { yield 4; }, ], boundFunction: [() => {}, function () {}.bind(null)], // eslint-disable-line no-extra-bind buffer: [Buffer.from('🦄')], emptyArray: [[], new Array()], // eslint-disable-line @typescript-eslint/no-array-constructor emptyMap: [new Map()], emptySet: [new Set()], emptyString: ['', String()], function: [ function foo() {}, // eslint-disable-line func-names function () {}, ], generatorFunction: [ function * () {}, function * () { yield 4; }, ], infinite: [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY], integer: [0, -0, 6], nativePromise: [Promise.resolve(), PromiseSubclassFixture.resolve()], number: [1.4], numericString: ['5', '-3.2', 'Infinity', '0x56'], plainObject: [ {x: 1}, Object.create(null), new Object(), // eslint-disable-line no-object-constructor structuredClone({x: 1}), structuredClone(Object.create(null)), structuredClone(new Object()), // eslint-disable-line no-object-constructor ], promise: [Object.create({then() {}, catch() {}})], // eslint-disable-line unicorn/no-thenable safeInteger: [(2 ** 53) - 1, -(2 ** 53) + 1], } as const satisfies Partial<{[K in keyof typeof is]: unknown[]}>; const primitiveTypes = { undefined: { fixtures: [ undefined, ], typename: 'undefined', }, null: { fixtures: [ null, ], typename: 'null', }, string: { fixtures: [ '🦄', 'hello world', ...reusableFixtures.emptyString, ...reusableFixtures.numericString, ], typename: 'string', }, emptyString: { fixtures: [...reusableFixtures.emptyString], typename: 'string', typeDescription: 'empty string', }, number: { fixtures: [ ...reusableFixtures.number, ...reusableFixtures.infinite, ...reusableFixtures.integer, ...reusableFixtures.safeInteger, ], typename: 'number', }, bigint: { fixtures: [ 1n, 0n, -0n, 1234n, ], typename: 'bigint', }, boolean: { fixtures: [ true, false, ], typename: 'boolean', }, numericString: { fixtures: [...reusableFixtures.numericString], typename: 'string', typeDescription: 'string with a number', }, nan: { fixtures: [ NaN, // eslint-disable-line unicorn/prefer-number-properties Number.NaN, ], typename: 'NaN', typeDescription: 'NaN', }, nullOrUndefined: { fixtures: [ null, undefined, ], typeDescription: 'null or undefined', }, integer: { fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger], typename: 'number', typeDescription: 'integer', }, safeInteger: { fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger], typename: 'number', typeDescription: 'safe integer', }, infinite: { fixtures: [...reusableFixtures.infinite], typename: 'number', typeDescription: 'infinite number', }, } as const satisfies Partial<{[K in keyof typeof is]: Test}>; const objectTypes = { symbol: { fixtures: [ Symbol('🦄'), ], typename: 'symbol', }, array: { fixtures: [ [1, 2], Array.from({length: 2}), ...reusableFixtures.emptyArray, ], typename: 'Array', }, emptyArray: { fixtures: [...reusableFixtures.emptyArray], typename: 'Array', typeDescription: 'empty array', }, function: { fixtures: [ ...reusableFixtures.asyncFunction, ...reusableFixtures.asyncGeneratorFunction, ...reusableFixtures.boundFunction, ...reusableFixtures.function, ...reusableFixtures.generatorFunction, ], typename: 'Function', }, buffer: { fixtures: [...reusableFixtures.buffer], typename: 'Buffer', }, blob: { fixtures: [ new window.Blob(), ], typename: 'Blob', }, object: { fixtures: [ Object.create({x: 1}), {[Symbol.toStringTag]: 'String'}, ...reusableFixtures.plainObject, ], typename: 'Object', }, regExp: { fixtures: [ /\w/v, // eslint-disable-next-line prefer-regex-literals new RegExp(String.raw`\w`, 'v'), ], typename: 'RegExp', }, date: { fixtures: [ new Date(), ], typename: 'Date', }, error: { fixtures: [ new Error('🦄'), new ErrorSubclassFixture(), ], typename: 'Error', }, nativePromise: { fixtures: [...reusableFixtures.nativePromise], typename: 'Promise', typeDescription: 'native Promise', }, promise: { fixtures: [ ...reusableFixtures.nativePromise, ...reusableFixtures.promise, ], typename: 'Promise', typeDescription: 'Promise', }, generator: { fixtures: [ (function * () { yield 4; })(), ], typename: 'Generator', }, asyncGenerator: { fixtures: [ (async function * () { yield 4; })(), ], typename: 'AsyncGenerator', }, generatorFunction: { fixtures: [...reusableFixtures.generatorFunction], typename: 'Function', typeDescription: 'GeneratorFunction', }, asyncGeneratorFunction: { fixtures: [...reusableFixtures.asyncGeneratorFunction], typename: 'Function', typeDescription: 'AsyncGeneratorFunction', }, asyncFunction: { fixtures: [...reusableFixtures.asyncFunction], typename: 'Function', typeDescription: 'AsyncFunction', }, boundFunction: { fixtures: [...reusableFixtures.boundFunction, ...reusableFixtures.asyncFunction], typename: 'Function', typeDescription: 'bound Function', }, map: { fixtures: [ new Map([['one', '1']]), ...reusableFixtures.emptyMap, ], typename: 'Map', }, emptyMap: { fixtures: [...reusableFixtures.emptyMap], typename: 'Map', typeDescription: 'empty map', }, set: { fixtures: [ new Set(['one']), ...reusableFixtures.emptySet, ], typename: 'Set', }, emptySet: { fixtures: [...reusableFixtures.emptySet], typename: 'Set', typeDescription: 'empty set', }, weakSet: { fixtures: [ new WeakSet(), ], typename: 'WeakSet', }, weakRef: { fixtures: [ new window.WeakRef({}), ], typename: 'WeakRef', }, weakMap: { fixtures: [ new WeakMap(), ], typename: 'WeakMap', }, int8Array: { fixtures: [ new Int8Array(), ], typename: 'Int8Array', }, uint8Array: { fixtures: [ new Uint8Array(), ], typename: 'Uint8Array', }, uint8ClampedArray: { fixtures: [ new Uint8ClampedArray(), ], typename: 'Uint8ClampedArray', }, int16Array: { fixtures: [ new Int16Array(), ], typename: 'Int16Array', }, uint16Array: { fixtures: [ new Uint16Array(), ], typename: 'Uint16Array', }, int32Array: { fixtures: [ new Int32Array(), ], typename: 'Int32Array', }, uint32Array: { fixtures: [ new Uint32Array(), ], typename: 'Uint32Array', }, float32Array: { fixtures: [ new Float32Array(), ], typename: 'Float32Array', }, float64Array: { fixtures: [ new Float64Array(), ], typename: 'Float64Array', }, bigInt64Array: { fixtures: [ new BigInt64Array(), ], typename: 'BigInt64Array', }, bigUint64Array: { fixtures: [ new BigUint64Array(), ], typename: 'BigUint64Array', }, arrayBuffer: { fixtures: [ new ArrayBuffer(10), ], typename: 'ArrayBuffer', }, dataView: { fixtures: [ new DataView(new ArrayBuffer(10)), ], typename: 'DataView', }, plainObject: { fixtures: [ ...reusableFixtures.plainObject, ], typename: 'Object', typeDescription: 'plain object', }, htmlElement: { fixtures: [ 'div', 'input', 'span', 'img', 'canvas', 'script', ] .map(fixture => document.createElement(fixture)), typeDescription: 'HTMLElement', }, observable: { fixtures: [ new Observable(), new Subject(), new ZenObservable(() => {}), ], typename: 'Observable', }, nodeStream: { fixtures: [ fs.createReadStream('readme.md'), fs.createWriteStream(temporaryFile()), new net.Socket(), new Stream.Duplex(), new Stream.PassThrough(), new Stream.Readable(), new Stream.Transform(), new Stream.Stream(), new Stream.Writable(), ], typename: 'Object', typeDescription: 'Node.js Stream', }, formData: { fixtures: [ new window.FormData(), ], typename: 'FormData', }, } as const satisfies Partial<{[K in keyof typeof is]: Test}>; const types = { ...objectTypes, ...primitiveTypes, } as const satisfies Partial<{[K in keyof typeof is]: Test}>; type TypeNameWithFixture = keyof typeof types; const subClasses = new Map([ ['uint8Array', ['buffer']], // It's too hard to differentiate the two ['object', keysOf(objectTypes)], ]); const notAssertionFixtures = { bigint: {fixture: 1n, nonFixture: '🦄', typeDescription: 'bigint'}, boolean: {fixture: false, nonFixture: '🦄', typeDescription: 'boolean'}, null: {fixture: null, nonFixture: '🦄', typeDescription: 'null'}, nullOrUndefined: {fixtures: [null, undefined], nonFixture: '🦄', typeDescription: 'null or undefined'}, primitive: {fixtures: [false, null, undefined], nonFixture: [], typeDescription: 'primitive'}, string: {fixture: '🦄', nonFixture: 1, typeDescription: 'string'}, symbol: {fixture: Symbol('🦄'), nonFixture: '🦄', typeDescription: 'symbol'}, undefined: {fixture: undefined, nonFixture: null, typeDescription: 'undefined'}, } as const satisfies Record; // This ensures a certain method matches only the types it's supposed to and none of the other methods' types for (const type of keysOf(types)) { test(`is.${type}`, () => { const {fixtures, typeDescription, typename} = types[type] as Test; const valueType = typeDescription ?? typename ?? 'unspecified'; const testAssert: (value: unknown) => never | void = isAssert[type]; const testIs: Predicate = is[type]; for (const fixture of fixtures) { assert.ok(testIs(fixture), `Value: ${inspect(fixture)}`); assert.doesNotThrow(() => { testAssert(fixture); }); if (typename !== undefined) { assert.strictEqual(is(fixture), typename); } } for (const key of keysOf(types).filter(key => key !== type)) { if (subClasses.has(type) && subClasses.get(type)?.includes(key)) { continue; } for (let i = 0; i < types[key].fixtures.length; i += 1) { const fixture: unknown = types[key].fixtures[i]; if (fixtures.includes(fixture)) { continue; } assert.strictEqual(testIs(fixture), false, `${key}.fixture[${i}]: ${inspect(fixture)} should not be ${type}`); assert.throws(() => { testAssert(fixture); }, { message: `Expected value which is \`${valueType}\`, received value of type \`${is(fixture)}\`.`, }); } } }); } test('is.positiveNumber', () => { assert.ok(is.positiveNumber(6)); assert.ok(is.positiveNumber(1.4)); assert.ok(is.positiveNumber(Number.POSITIVE_INFINITY)); assert.doesNotThrow(() => { isAssert.positiveNumber(6); }); assert.doesNotThrow(() => { isAssert.positiveNumber(1.4); }); assert.doesNotThrow(() => { isAssert.positiveNumber(Number.POSITIVE_INFINITY); }); assert.strictEqual(is.positiveNumber(0), false); assert.strictEqual(is.positiveNumber(-0), false); assert.strictEqual(is.positiveNumber(-6), false); assert.strictEqual(is.positiveNumber(-1.4), false); assert.strictEqual(is.positiveNumber(Number.NEGATIVE_INFINITY), false); assert.strictEqual(is.positiveNumber(Number.NaN), false); assert.throws(() => { isAssert.positiveNumber(0); }); assert.throws(() => { isAssert.positiveNumber(-0); }); assert.throws(() => { isAssert.positiveNumber(-6); }); assert.throws(() => { isAssert.positiveNumber(-1.4); }); assert.throws(() => { isAssert.positiveNumber(Number.NEGATIVE_INFINITY); }); }); test('is.nan', () => { assert.ok(is.nan(Number.NaN)); assert.ok(is.nan(NaN)); // eslint-disable-line unicorn/prefer-number-properties assert.doesNotThrow(() => { isAssert.nan(Number.NaN); }); assert.strictEqual(is.nan(0), false); assert.strictEqual(is.nan(-0), false); assert.strictEqual(is.nan(1), false); assert.strictEqual(is.nan(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.nan(Number.NEGATIVE_INFINITY), false); assert.strictEqual(is.nan('NaN'), false); assert.strictEqual(is.nan(undefined), false); assert.throws(() => { isAssert.nan(0); }); assert.throws(() => { isAssert.nan(1); }); assert.throws(() => { isAssert.nan('NaN'); }); }); test('is.finiteNumber', () => { assert.ok(is.finiteNumber(6)); assert.ok(is.finiteNumber(-6)); assert.ok(is.finiteNumber(0)); assert.ok(is.finiteNumber(1.4)); assert.doesNotThrow(() => { isAssert.finiteNumber(6); }); assert.doesNotThrow(() => { isAssert.finiteNumber(0); }); assert.strictEqual(is.finiteNumber(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.finiteNumber(Number.NEGATIVE_INFINITY), false); assert.strictEqual(is.finiteNumber(Number.NaN), false); assert.throws(() => { isAssert.finiteNumber(Number.POSITIVE_INFINITY); }); assert.throws(() => { isAssert.finiteNumber(Number.NEGATIVE_INFINITY); }); assert.throws(() => { isAssert.finiteNumber(Number.NaN); }); }); test('is.negativeNumber', () => { assert.ok(is.negativeNumber(-6)); assert.ok(is.negativeNumber(-1.4)); assert.ok(is.negativeNumber(Number.NEGATIVE_INFINITY)); assert.doesNotThrow(() => { isAssert.negativeNumber(-6); }); assert.doesNotThrow(() => { isAssert.negativeNumber(-1.4); }); assert.doesNotThrow(() => { isAssert.negativeNumber(Number.NEGATIVE_INFINITY); }); assert.strictEqual(is.negativeNumber(0), false); assert.strictEqual(is.negativeNumber(-0), false); assert.strictEqual(is.negativeNumber(6), false); assert.strictEqual(is.negativeNumber(1.4), false); assert.strictEqual(is.negativeNumber(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.negativeNumber(Number.NaN), false); assert.throws(() => { isAssert.negativeNumber(0); }); assert.throws(() => { isAssert.negativeNumber(-0); }); assert.throws(() => { isAssert.negativeNumber(6); }); assert.throws(() => { isAssert.negativeNumber(1.4); }); assert.throws(() => { isAssert.negativeNumber(Number.POSITIVE_INFINITY); }); }); test('is.nonNegativeNumber', () => { assert.ok(is.nonNegativeNumber(0)); assert.ok(is.nonNegativeNumber(6)); assert.ok(is.nonNegativeNumber(1.4)); assert.ok(is.nonNegativeNumber(Number.POSITIVE_INFINITY)); assert.doesNotThrow(() => { isAssert.nonNegativeNumber(0); }); assert.doesNotThrow(() => { isAssert.nonNegativeNumber(6); }); assert.ok(is.nonNegativeNumber(-0)); // -0 >= 0 is true in JavaScript assert.strictEqual(is.nonNegativeNumber(-6), false); assert.strictEqual(is.nonNegativeNumber(-1.4), false); assert.strictEqual(is.nonNegativeNumber(Number.NEGATIVE_INFINITY), false); assert.strictEqual(is.nonNegativeNumber(Number.NaN), false); assert.throws(() => { isAssert.nonNegativeNumber(-6); }); assert.throws(() => { isAssert.nonNegativeNumber(Number.NEGATIVE_INFINITY); }); }); test('is.positiveInteger', () => { assert.ok(is.positiveInteger(1)); assert.ok(is.positiveInteger(6)); assert.ok(is.positiveInteger(100)); assert.doesNotThrow(() => { isAssert.positiveInteger(1); }); assert.doesNotThrow(() => { isAssert.positiveInteger(6); }); assert.strictEqual(is.positiveInteger(0), false); assert.strictEqual(is.positiveInteger(-1), false); assert.strictEqual(is.positiveInteger(1.5), false); assert.strictEqual(is.positiveInteger(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.positiveInteger(Number.NaN), false); assert.throws(() => { isAssert.positiveInteger(0); }); assert.throws(() => { isAssert.positiveInteger(-1); }); assert.throws(() => { isAssert.positiveInteger(1.5); }); }); test('is.negativeInteger', () => { assert.ok(is.negativeInteger(-1)); assert.ok(is.negativeInteger(-6)); assert.ok(is.negativeInteger(-100)); assert.doesNotThrow(() => { isAssert.negativeInteger(-1); }); assert.doesNotThrow(() => { isAssert.negativeInteger(-6); }); assert.strictEqual(is.negativeInteger(0), false); assert.strictEqual(is.negativeInteger(-0), false); // -0 < 0 is false in JavaScript assert.strictEqual(is.negativeInteger(1), false); assert.strictEqual(is.negativeInteger(-1.5), false); assert.strictEqual(is.negativeInteger(Number.NEGATIVE_INFINITY), false); assert.strictEqual(is.negativeInteger(Number.NaN), false); assert.throws(() => { isAssert.negativeInteger(0); }); assert.throws(() => { isAssert.negativeInteger(1); }); assert.throws(() => { isAssert.negativeInteger(-1.5); }); assert.throws(() => { isAssert.negativeInteger(Number.NEGATIVE_INFINITY); }); }); test('is.nonNegativeInteger', () => { assert.ok(is.nonNegativeInteger(0)); assert.ok(is.nonNegativeInteger(1)); assert.ok(is.nonNegativeInteger(100)); assert.doesNotThrow(() => { isAssert.nonNegativeInteger(0); }); assert.doesNotThrow(() => { isAssert.nonNegativeInteger(1); }); assert.ok(is.nonNegativeInteger(-0)); // -0 >= 0 is true in JavaScript assert.strictEqual(is.nonNegativeInteger(-1), false); assert.strictEqual(is.nonNegativeInteger(1.5), false); assert.strictEqual(is.nonNegativeInteger(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.nonNegativeInteger(Number.NaN), false); assert.throws(() => { isAssert.nonNegativeInteger(-1); }); assert.throws(() => { isAssert.nonNegativeInteger(1.5); }); assert.throws(() => { isAssert.nonNegativeInteger(Number.POSITIVE_INFINITY); }); }); test('is.infinite', () => { assert.ok(is.infinite(Number.POSITIVE_INFINITY)); assert.ok(is.infinite(Number.NEGATIVE_INFINITY)); assert.doesNotThrow(() => { isAssert.infinite(Number.POSITIVE_INFINITY); }); assert.doesNotThrow(() => { isAssert.infinite(Number.NEGATIVE_INFINITY); }); assert.strictEqual(is.infinite(0), false); assert.strictEqual(is.infinite(1), false); assert.strictEqual(is.infinite(-1), false); assert.strictEqual(is.infinite(Number.NaN), false); assert.strictEqual(is.infinite(Number.MAX_VALUE), false); assert.strictEqual(is.infinite('Infinity'), false); assert.throws(() => { isAssert.infinite(0); }); assert.throws(() => { isAssert.infinite(Number.NaN); }); }); test('is.numericString supplemental', () => { assert.strictEqual(is.numericString(''), false); assert.strictEqual(is.numericString(' '), false); assert.strictEqual(is.numericString(' \t\t\n'), false); assert.strictEqual(is.numericString(1), false); assert.strictEqual(is.numericString(' 5'), false); assert.strictEqual(is.numericString('5 '), false); assert.strictEqual(is.numericString(' 5 '), false); assert.strictEqual(is.numericString('\t3'), false); assert.throws(() => { isAssert.numericString(''); }); assert.throws(() => { isAssert.numericString(1); }); }); test('is.array supplemental', () => { assert.ok(is.array([1, 2, 3], is.number)); assert.strictEqual(is.array([1, '2', 3], is.number), false); assert.doesNotThrow(() => { isAssert.array([1, 2], isAssert.number); }); assert.throws(() => { isAssert.array([1, '2'], isAssert.number); }); assert.doesNotThrow(() => { const x: unknown[] = [1, 2, 3]; isAssert.array(x, isAssert.number); x[0]?.toFixed(0); }); assert.doesNotThrow(() => { const x: unknown[] = [1, 2, 3]; if (is.array(x, is.number)) { x[0]?.toFixed(0); } }); assert.throws(() => { isAssert.array([1, '2'], isAssert.number, 'Expected numbers'); }, /Expected numbers/v); }); test('is.arrayOf', () => { const isStringArray = is.arrayOf(is.string); assert.ok(isStringArray(['a', 'b', 'c'])); assert.ok(isStringArray([])); assert.strictEqual(isStringArray([1, 2, 3]), false); assert.strictEqual(isStringArray(['a', 1]), false); assert.strictEqual(isStringArray('not an array'), false); assert.strictEqual(isStringArray(undefined), false); const isNumberArray = is.arrayOf(is.number); assert.ok(isNumberArray([1, 2, 3])); assert.strictEqual(isNumberArray([1, '2']), false); }); test('is.oneOf', () => { const isDirection = is.oneOf(['north', 'south', 'east', 'west'] as const); assert.ok(isDirection('north')); assert.ok(isDirection('west')); assert.strictEqual(isDirection('up'), false); assert.strictEqual(isDirection(1), false); assert.strictEqual(isDirection(undefined), false); const isSmallNumber = is.oneOf([1, 2, 3] as const); assert.ok(isSmallNumber(1)); assert.strictEqual(isSmallNumber(4), false); // Empty values array always returns false const isNever = is.oneOf([] as const); assert.strictEqual(isNever('anything'), false); // Array.includes uses SameValueZero, so NaN matches NaN (unlike ===) const isNanValue = is.oneOf([Number.NaN] as const); assert.ok(isNanValue(Number.NaN)); }); test('is.boundFunction supplemental', () => { assert.strictEqual(is.boundFunction(function () {}), false); // eslint-disable-line prefer-arrow-callback assert.throws(() => { isAssert.boundFunction(function () {}); // eslint-disable-line prefer-arrow-callback }); }); test('is.asyncFunction supplemental', () => { const fixture = async () => {}; if (is.asyncFunction(fixture)) { assert.ok(is.function(fixture().then)); assert.doesNotThrow(() => { isAssert.function(fixture().then); }); } }); test('is.asyncGenerator supplemental', () => { const fixture = (async function * () { yield 4; })(); if (is.asyncGenerator(fixture)) { assert.ok(is.function(fixture.next)); } }); test('is.asyncGeneratorFunction supplemental', () => { const fixture = async function * () { yield 4; }; if (is.asyncGeneratorFunction(fixture)) { assert.ok(is.function(fixture().next)); } }); test('is.enumCase', () => { enum NonNumericalEnum { Key1 = 'key1', Key2 = 'key2', } enum NumericKeyStringEnum { // eslint-disable-next-line @stylistic/quote-props '0' = 'zero', '01' = 'padded', } assert.ok(is.enumCase('key1', NonNumericalEnum)); assert.doesNotThrow(() => { isAssert.enumCase('key1', NonNumericalEnum); }); assert.strictEqual(is.enumCase('invalid', NonNumericalEnum), false); assert.throws(() => { isAssert.enumCase('invalid', NonNumericalEnum); }); assert.ok(is.enumCase('zero', NumericKeyStringEnum)); assert.ok(is.enumCase('padded', NumericKeyStringEnum)); assert.doesNotThrow(() => { isAssert.enumCase('zero', NumericKeyStringEnum); }); assert.doesNotThrow(() => { isAssert.enumCase('padded', NumericKeyStringEnum); }); enum NumericalEnum { Key1 = 0, Key2 = 1, } assert.ok(is.enumCase(0, NumericalEnum)); assert.ok(is.enumCase(1, NumericalEnum)); assert.strictEqual(is.enumCase('Key1', NumericalEnum), false); assert.strictEqual(is.enumCase('Key2', NumericalEnum), false); assert.doesNotThrow(() => { isAssert.enumCase(0, NumericalEnum); }); assert.throws(() => { isAssert.enumCase('Key1', NumericalEnum); }); enum HeterogeneousEnum { A = 1, B = 'hello', } assert.ok(is.enumCase(1, HeterogeneousEnum)); assert.ok(is.enumCase('hello', HeterogeneousEnum)); assert.strictEqual(is.enumCase('A', HeterogeneousEnum), false); }); test('is.directInstanceOf', () => { const error = new Error('fixture'); const errorSubclass = new ErrorSubclassFixture(); assert.ok(is.directInstanceOf(error, Error)); assert.ok(is.directInstanceOf(errorSubclass, ErrorSubclassFixture)); assert.doesNotThrow(() => { isAssert.directInstanceOf(error, Error); }); assert.doesNotThrow(() => { isAssert.directInstanceOf(errorSubclass, ErrorSubclassFixture); }); assert.strictEqual(is.directInstanceOf(error, ErrorSubclassFixture), false); assert.strictEqual(is.directInstanceOf(errorSubclass, Error), false); assert.throws(() => { isAssert.directInstanceOf(error, ErrorSubclassFixture); }); assert.throws(() => { isAssert.directInstanceOf(errorSubclass, Error); }); assert.strictEqual(is.directInstanceOf(undefined, Error), false); assert.strictEqual(is.directInstanceOf(null, Error), false); }); test('is.urlInstance', () => { const url = new URL('https://example.com'); assert.ok(is.urlInstance(url)); assert.strictEqual(is.urlInstance({}), false); assert.strictEqual(is.urlInstance(undefined), false); assert.strictEqual(is.urlInstance(null), false); assert.doesNotThrow(() => { isAssert.urlInstance(url); }); assert.throws(() => { isAssert.urlInstance({}); }); assert.throws(() => { isAssert.urlInstance(undefined); }); assert.throws(() => { isAssert.urlInstance(null); }); }); test('is.urlString', () => { const url = 'https://example.com'; assert.ok(is.urlString(url)); assert.strictEqual(is.urlString(new URL(url)), false); assert.strictEqual(is.urlString({}), false); assert.strictEqual(is.urlString(undefined), false); assert.strictEqual(is.urlString(null), false); assert.doesNotThrow(() => { isAssert.urlString(url); }); assert.throws(() => { isAssert.urlString(new URL(url)); }); assert.throws(() => { isAssert.urlString({}); }); assert.throws(() => { isAssert.urlString(undefined); }); assert.throws(() => { isAssert.urlString(null); }); }); // Type test for urlString narrowing fix (issue #212) // This test demonstrates that the fix allows proper type narrowing in both branches (() => { const value: unknown = 'test'; if (is.urlString(value)) { // ✅ In true branch: value is narrowed to UrlString expectTypeOf(value).toEqualTypeOf(); expectTypeOf(value).toMatchTypeOf(); } else { // ✅ In false branch: value remains unknown (not incorrectly narrowed) expectTypeOf(value).toEqualTypeOf(); // ✅ Manual narrowing to string still works if (typeof value === 'string') { expectTypeOf(value).toEqualTypeOf(); } } })(); // Type test for is.nan branded-type narrowing (() => { const value: unknown = Number.NaN; if (is.nan(value)) { // ✅ In true branch: value is narrowed to the branded NaN type expectTypeOf(value).toEqualTypeOf(); expectTypeOf(value).toMatchTypeOf(); } else { // ✅ In false branch: value remains unknown (not incorrectly narrowed) expectTypeOf(value).toEqualTypeOf(); } })(); test('is.truthy', () => { assert.ok(is.truthy('unicorn')); assert.ok(is.truthy('🦄')); assert.ok(is.truthy(new Set())); assert.ok(is.truthy(Symbol('🦄'))); assert.ok(is.truthy(true)); assert.ok(is.truthy(1)); assert.ok(is.truthy(1n)); assert.doesNotThrow(() => { isAssert.truthy('unicorn'); }); assert.doesNotThrow(() => { isAssert.truthy('🦄'); }); assert.doesNotThrow(() => { isAssert.truthy(new Set()); }); assert.doesNotThrow(() => { isAssert.truthy(Symbol('🦄')); }); assert.doesNotThrow(() => { isAssert.truthy(true); }); assert.doesNotThrow(() => { isAssert.truthy(1); }); assert.doesNotThrow(() => { isAssert.truthy(1n); }); // Checks that `isAssert.truthy` narrow downs boolean type to `true`. { const booleans = [true, false]; const function_ = (value: true) => value; isAssert.truthy(booleans[0]); function_(booleans[0]); } // Checks that `isAssert.truthy` excludes zero value from number type. { const bits: Array<0 | 1> = [1, 0, -0]; const function_ = (value: 1) => value; isAssert.truthy(bits[0]); function_(bits[0]); } // Checks that `isAssert.truthy` excludes zero value from bigint type. { const bits: Array<0n | 1n> = [1n, 0n, -0n]; const function_ = (value: 1n) => value; isAssert.truthy(bits[0]); function_(bits[0]); } // Checks that `isAssert.truthy` excludes empty string from string type. { const strings: Array<'nonEmpty' | ''> = ['nonEmpty', '']; const function_ = (value: 'nonEmpty') => value; isAssert.truthy(strings[0]); function_(strings[0]); } // Checks that `isAssert.truthy` excludes undefined from mixed type. { const maybeUndefineds = ['🦄', undefined]; const function_ = (value: string) => value; isAssert.truthy(maybeUndefineds[0]); function_(maybeUndefineds[0]); } // Checks that `isAssert.truthy` excludes null from mixed type. { const maybeNulls = ['🦄', null]; const function_ = (value: string) => value; isAssert.truthy(maybeNulls[0]); function_(maybeNulls[0]); } }); test('is.falsy', () => { assert.ok(is.falsy(false)); assert.ok(is.falsy(0)); assert.ok(is.falsy('')); assert.ok(is.falsy(null)); assert.ok(is.falsy(undefined)); assert.ok(is.falsy(Number.NaN)); assert.ok(is.falsy(0n)); assert.doesNotThrow(() => { isAssert.falsy(false); }); assert.doesNotThrow(() => { isAssert.falsy(0); }); assert.doesNotThrow(() => { isAssert.falsy(''); }); assert.doesNotThrow(() => { isAssert.falsy(null); }); assert.doesNotThrow(() => { isAssert.falsy(undefined); }); assert.doesNotThrow(() => { isAssert.falsy(Number.NaN); }); assert.doesNotThrow(() => { isAssert.falsy(0n); }); // Checks that `isAssert.falsy` narrow downs boolean type to `false`. { const booleans = [false, true]; const function_ = (value?: false) => value; isAssert.falsy(booleans[0]); function_(booleans[0]); } // Checks that `isAssert.falsy` narrow downs number type to `0`. { const bits = [0, -0, 1]; const function_ = (value?: 0) => value; isAssert.falsy(bits[0]); function_(bits[0]); isAssert.falsy(bits[1]); function_(bits[1]); } // Checks that `isAssert.falsy` narrow downs bigint type to `0n`. { const bits = [0n, -0n, 1n]; const function_ = (value?: 0n) => value; isAssert.falsy(bits[0]); function_(bits[0]); isAssert.falsy(bits[1]); function_(bits[1]); } // Checks that `isAssert.falsy` narrow downs string type to empty string. { const strings = ['', 'nonEmpty']; const function_ = (value?: '') => value; isAssert.falsy(strings[0]); function_(strings[0]); } // Checks that `isAssert.falsy` can narrow down mixed type to undefined. { const maybeUndefineds = [undefined, Symbol('🦄')]; const function_ = (value: undefined) => value; isAssert.falsy(maybeUndefineds[0]); function_(maybeUndefineds[0]); } // Checks that `isAssert.falsy` can narrow down mixed type to null. { const maybeNulls = [null, Symbol('🦄')]; // eslint-disable-next-line @typescript-eslint/no-restricted-types const function_ = (value?: null) => value; isAssert.falsy(maybeNulls[0]); function_(maybeNulls[0]); } }); test('is.primitive', () => { const primitives: Primitive[] = [ undefined, null, '🦄', 6, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, true, false, Symbol('🦄'), 6n, ]; for (const element of primitives) { assert.ok(is.primitive(element)); assert.doesNotThrow(() => { isAssert.primitive(element); }); } }); test('is.integer supplemental', () => { assert.strictEqual(is.integer(1.4), false); assert.throws(() => { isAssert.integer(1.4); }); }); test('is.safeInteger supplemental', () => { assert.strictEqual(is.safeInteger(2 ** 53), false); assert.strictEqual(is.safeInteger(-(2 ** 53)), false); assert.throws(() => { isAssert.safeInteger(2 ** 53); }); assert.throws(() => { isAssert.safeInteger(-(2 ** 53)); }); }); test('is.iterable', () => { assert.ok(is.iterable('')); assert.ok(is.iterable([])); assert.ok(is.iterable(new Map())); assert.strictEqual(is.iterable(null), false); assert.strictEqual(is.iterable(undefined), false); assert.strictEqual(is.iterable(0), false); assert.strictEqual(is.iterable(Number.NaN), false); assert.strictEqual(is.iterable(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.iterable({}), false); assert.doesNotThrow(() => { isAssert.iterable(''); }); assert.doesNotThrow(() => { isAssert.iterable([]); }); assert.doesNotThrow(() => { isAssert.iterable(new Map()); }); assert.throws(() => { isAssert.iterable(null); }); assert.throws(() => { isAssert.iterable(undefined); }); assert.throws(() => { isAssert.iterable(0); }); assert.throws(() => { isAssert.iterable(Number.NaN); }); assert.throws(() => { isAssert.iterable(Number.POSITIVE_INFINITY); }); assert.throws(() => { isAssert.iterable({}); }); }); test('is.asyncIterable', () => { assert.ok(is.asyncIterable({ [Symbol.asyncIterator]() {}, })); assert.strictEqual(is.asyncIterable(null), false); assert.strictEqual(is.asyncIterable(undefined), false); assert.strictEqual(is.asyncIterable(0), false); assert.strictEqual(is.asyncIterable(Number.NaN), false); assert.strictEqual(is.asyncIterable(Number.POSITIVE_INFINITY), false); assert.strictEqual(is.asyncIterable({}), false); assert.doesNotThrow(() => { isAssert.asyncIterable({ [Symbol.asyncIterator]() {}, }); }); assert.throws(() => { isAssert.asyncIterable(null); }); assert.throws(() => { isAssert.asyncIterable(undefined); }); assert.throws(() => { isAssert.asyncIterable(0); }); assert.throws(() => { isAssert.asyncIterable(Number.NaN); }); assert.throws(() => { isAssert.asyncIterable(Number.POSITIVE_INFINITY); }); assert.throws(() => { isAssert.asyncIterable({}); }); }); test('is.class', () => { class Foo {} // eslint-disable-line @typescript-eslint/no-extraneous-class // Note: Using new Function to test a minified class (no whitespace in source) const minifiedClass = new Function('return class{};'); // eslint-disable-line no-new-func const classDeclarations = [ Foo, class Bar extends Foo {}, minifiedClass(), ]; for (const classDeclaration of classDeclarations) { assert.ok(is.class(classDeclaration)); assert.doesNotThrow(() => { isAssert.class(classDeclaration); }); } }); test('is.typedArray', () => { const typedArrays: TypedArray[] = [ new Int8Array(), new Uint8Array(), new Uint8ClampedArray(), new Int16Array(), new Uint16Array(), new Int32Array(), new Uint32Array(), new Float32Array(), new Float64Array(), new BigInt64Array(), new BigUint64Array(), ]; for (const item of typedArrays) { assert.ok(is.typedArray(item)); assert.doesNotThrow(() => { isAssert.typedArray(item); }); } assert.strictEqual(is.typedArray(new ArrayBuffer(1)), false); assert.strictEqual(is.typedArray([]), false); assert.strictEqual(is.typedArray({}), false); assert.throws(() => { isAssert.typedArray(new ArrayBuffer(1)); }); assert.throws(() => { isAssert.typedArray([]); }); assert.throws(() => { isAssert.typedArray({}); }); }); test('is.arrayLike', () => { (function () { assert.ok(is.arrayLike(arguments)); // eslint-disable-line prefer-rest-params })(); assert.ok(is.arrayLike([])); assert.ok(is.arrayLike('unicorn')); assert.strictEqual(is.arrayLike({}), false); assert.strictEqual(is.arrayLike(() => {}), false); assert.strictEqual(is.arrayLike(new Map()), false); (function () { assert.doesNotThrow(function () { isAssert.arrayLike(arguments); // eslint-disable-line prefer-rest-params }); })(); assert.doesNotThrow(() => { isAssert.arrayLike([]); }); assert.doesNotThrow(() => { isAssert.arrayLike('unicorn'); }); assert.throws(() => { isAssert.arrayLike({}); }); assert.throws(() => { isAssert.arrayLike(() => {}); }); assert.throws(() => { isAssert.arrayLike(new Map()); }); }); test('is.tupleLike', () => { (function () { assert.strictEqual(is.tupleLike(arguments, []), false); // eslint-disable-line prefer-rest-params })(); assert.ok(is.tupleLike([], [])); assert.ok(is.tupleLike([1, '2', true, {}, [], undefined, null], [is.number, is.string, is.boolean, is.object, is.array, is.undefined, is.nullOrUndefined])); assert.strictEqual(is.tupleLike('unicorn', [is.string]), false); assert.strictEqual(is.tupleLike({}, []), false); assert.strictEqual(is.tupleLike(() => {}, [is.function]), false); assert.strictEqual(is.tupleLike(new Map(), [is.map]), false); (function () { assert.throws(function () { isAssert.tupleLike(arguments, []); // eslint-disable-line prefer-rest-params }); })(); assert.doesNotThrow(() => { isAssert.tupleLike([], []); }); assert.throws(() => { isAssert.tupleLike('unicorn', [is.string]); }); assert.throws(() => { isAssert.tupleLike({}, [is.object]); }); assert.throws(() => { isAssert.tupleLike(() => {}, [is.function]); }); assert.throws(() => { isAssert.tupleLike(new Map(), [is.map]); }); { const tuple = [[false, 'unicorn'], 'string', true]; if (is.tupleLike(tuple, [is.array, is.string, is.boolean])) { if (is.tupleLike(tuple[0], [is.boolean, is.string])) { // eslint-disable-line unicorn/no-lonely-if const value = tuple[0][1]; expectTypeOf(value).toEqualTypeOf(); } } } { const tuple = [{isTest: true}, '1', true, null]; if (is.tupleLike(tuple, [is.nonEmptyObject, is.string, is.boolean, is.null])) { const value = tuple[0]; expectTypeOf(value).toEqualTypeOf>(); } } { const tuple = [1, '1', true, null, undefined]; if (is.tupleLike(tuple, [is.number, is.string, is.boolean, is.null, is.undefined])) { const numericValue = tuple[0]; const stringValue = tuple[1]; const booleanValue = tuple[2]; const nullValue = tuple[3]; const undefinedValue = tuple[4]; expectTypeOf(numericValue).toEqualTypeOf(); expectTypeOf(stringValue).toEqualTypeOf(); expectTypeOf(booleanValue).toEqualTypeOf(); // eslint-disable-next-line @typescript-eslint/no-restricted-types expectTypeOf(nullValue).toEqualTypeOf(); expectTypeOf(undefinedValue).toEqualTypeOf(); } } }); test('is.inRange', () => { const x = 3; assert.ok(is.inRange(x, [0, 5])); assert.ok(is.inRange(x, [5, 0])); assert.ok(is.inRange(x, [-5, 5])); assert.ok(is.inRange(x, [5, -5])); assert.strictEqual(is.inRange(x, [4, 8]), false); assert.ok(is.inRange(-7, [-5, -10])); assert.ok(is.inRange(-5, [-5, -10])); assert.ok(is.inRange(-10, [-5, -10])); assert.ok(is.inRange(x, 10)); assert.ok(is.inRange(0, 0)); assert.ok(is.inRange(-2, -3)); assert.strictEqual(is.inRange(x, 2), false); assert.strictEqual(is.inRange(-3, -2), false); assert.throws(() => { // @ts-expect-error invalid argument is.inRange(0, []); }); assert.throws(() => { // @ts-expect-error invalid argument is.inRange(0, [5]); }); assert.throws(() => { // @ts-expect-error invalid argument is.inRange(0, [1, 2, 3]); }); assert.throws(() => { is.inRange(5, [Number.NaN, 10]); }, TypeError); assert.throws(() => { is.inRange(5, [0, Number.NaN]); }, TypeError); assert.doesNotThrow(() => { isAssert.inRange(x, [0, 5]); }); assert.doesNotThrow(() => { isAssert.inRange(x, [5, 0]); }); assert.doesNotThrow(() => { isAssert.inRange(x, [-5, 5]); }); assert.doesNotThrow(() => { isAssert.inRange(x, [5, -5]); }); assert.throws(() => { isAssert.inRange(x, [4, 8]); }); assert.doesNotThrow(() => { isAssert.inRange(-7, [-5, -10]); }); assert.doesNotThrow(() => { isAssert.inRange(-5, [-5, -10]); }); assert.doesNotThrow(() => { isAssert.inRange(-10, [-5, -10]); }); assert.doesNotThrow(() => { isAssert.inRange(x, 10); }); assert.doesNotThrow(() => { isAssert.inRange(0, 0); }); assert.doesNotThrow(() => { isAssert.inRange(-2, -3); }); assert.throws(() => { isAssert.inRange(x, 2); }); assert.throws(() => { isAssert.inRange(-3, -2); }); assert.throws(() => { // @ts-expect-error invalid argument isAssert.inRange(0, []); }); assert.throws(() => { // @ts-expect-error invalid argument isAssert.inRange(0, [5]); }); assert.throws(() => { // @ts-expect-error invalid argument isAssert.inRange(0, [1, 2, 3]); }); }); test('is.htmlElement supplemental', () => { assert.strictEqual(is.htmlElement({nodeType: 1, nodeName: 'div'}), false); assert.throws(() => { isAssert.htmlElement({nodeType: 1, nodeName: 'div'}); }); const tagNames = [ 'div', 'input', 'span', 'img', 'canvas', 'script', ] as const; for (const tagName of tagNames) { const element = document.createElement(tagName); assert.strictEqual(is(element), 'HTMLElement'); } const nonHtmlElements = [ document.createTextNode('data'), document.createProcessingInstruction('xml-stylesheet', 'href="mycss.css" type="text/css"'), document.createComment('This is a comment'), document, document.implementation.createDocumentType('svg:svg', '-//W3C//DTD SVG 1.1//EN', 'https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'), document.createDocumentFragment(), ] as const; for (const element of nonHtmlElements) { assert.throws(() => { isAssert.htmlElement(element); }); } }); test('is.evenInteger', () => { for (const element of [-6, 2, 4]) { assert.ok(is.evenInteger(element)); assert.doesNotThrow(() => { isAssert.evenInteger(element); }); } for (const element of [-3, 1, 5]) { assert.strictEqual(is.evenInteger(element), false); assert.throws(() => { isAssert.evenInteger(element); }); } }); test('is.oddInteger', () => { for (const element of [-5, 7, 13]) { assert.ok(is.oddInteger(element)); assert.doesNotThrow(() => { isAssert.oddInteger(element); }); } for (const element of [-8, 8, 10]) { assert.strictEqual(is.oddInteger(element), false); assert.throws(() => { isAssert.oddInteger(element); }); } }); test('is.nonEmptyArray', () => { assert.ok(is.nonEmptyArray([1, 2, 3])); assert.strictEqual(is.nonEmptyArray([]), false); assert.strictEqual(is.nonEmptyArray(new Array()), false); // eslint-disable-line @typescript-eslint/no-array-constructor assert.doesNotThrow(() => { isAssert.nonEmptyArray([1, 2, 3]); }); assert.throws(() => { isAssert.nonEmptyArray([]); }); assert.throws(() => { isAssert.nonEmptyArray(new Array()); // eslint-disable-line @typescript-eslint/no-array-constructor }); { const strings = ['🦄', 'unicorn'] as string[] | undefined; const function_ = (value: string) => value; if (is.nonEmptyArray(strings)) { const value = strings[0]; function_(value); } } { const mixed = ['🦄', 'unicorn', 1, 2]; const function_ = (value: string | number) => value; if (is.nonEmptyArray(mixed)) { const value = mixed[0]; function_(value); } } { const arrays = [['🦄'], ['unicorn']]; const function_ = (value: string[]) => value; if (is.nonEmptyArray(arrays)) { const value = arrays[0]; function_(value); } } { const strings = ['🦄', 'unicorn'] as string[] | undefined; const function_ = (value: string) => value; isAssert.nonEmptyArray(strings); const value = strings[0]; function_(value); } { const mixed = ['🦄', 'unicorn', 1, 2]; const function_ = (value: string | number) => value; isAssert.nonEmptyArray(mixed); const value = mixed[0]; function_(value); } { const arrays = [['🦄'], ['unicorn']]; const function_ = (value: string[]) => value; isAssert.nonEmptyArray(arrays); const value = arrays[0]; function_(value); } }); test('is.emptyString supplemental', () => { assert.strictEqual(is.emptyString('🦄'), false); assert.throws(() => { isAssert.emptyString('🦄'); }); }); test('is.emptyStringOrWhitespace supplemental', () => { assert.ok(is.emptyStringOrWhitespace(' ')); assert.strictEqual(is.emptyStringOrWhitespace('🦄'), false); assert.strictEqual(is.emptyStringOrWhitespace('unicorn'), false); assert.doesNotThrow(() => { isAssert.emptyStringOrWhitespace(' '); }); assert.throws(() => { isAssert.emptyStringOrWhitespace('🦄'); }); assert.throws(() => { isAssert.emptyStringOrWhitespace('unicorn'); }); let value = 'test'; // eslint-disable-line prefer-const -- can't use `const` here because then it will be inferred as `never` in the `if` block if (is.emptyStringOrWhitespace(value)) { value.charAt(0); // Should be inferred as `'' | Whitespace` and not `never` } else { value.charAt(0); // Should be inferred as `string` and not `never` } }); test('is.nonEmptyString', () => { assert.strictEqual(is.nonEmptyString(''), false); assert.strictEqual(is.nonEmptyString(String()), false); assert.ok(is.nonEmptyString('🦄')); assert.throws(() => { isAssert.nonEmptyString(''); }); assert.throws(() => { isAssert.nonEmptyString(String()); }); assert.doesNotThrow(() => { isAssert.nonEmptyString('🦄'); }); }); test('is.nonEmptyStringAndNotWhitespace', () => { assert.strictEqual(is.nonEmptyStringAndNotWhitespace(' '), false); assert.ok(is.nonEmptyStringAndNotWhitespace('🦄')); for (const value of [null, undefined, 5, Number.NaN, {}, []]) { assert.strictEqual(is.nonEmptyStringAndNotWhitespace(value), false); assert.throws(() => { isAssert.nonEmptyStringAndNotWhitespace(value); }); } assert.throws(() => { isAssert.nonEmptyStringAndNotWhitespace(''); }); assert.doesNotThrow(() => { isAssert.nonEmptyStringAndNotWhitespace('🦄'); }); }); test('is.emptyObject', () => { assert.ok(is.emptyObject({})); assert.ok(is.emptyObject(new Object())); // eslint-disable-line no-object-constructor assert.strictEqual(is.emptyObject({unicorn: '🦄'}), false); assert.strictEqual(is.emptyObject(function () {}), false); // eslint-disable-line prefer-arrow-callback assert.strictEqual(is.emptyObject(() => {}), false); assert.strictEqual(is.emptyObject(class Foo {}), false); // eslint-disable-line @typescript-eslint/no-extraneous-class assert.strictEqual(is.emptyObject([]), false); assert.strictEqual(is.emptyObject(['unicorn']), false); assert.doesNotThrow(() => { isAssert.emptyObject({}); }); assert.doesNotThrow(() => { isAssert.emptyObject(new Object()); // eslint-disable-line no-object-constructor }); assert.throws(() => { isAssert.emptyObject({unicorn: '🦄'}); }); assert.throws(() => { isAssert.emptyObject(function () {}); // eslint-disable-line prefer-arrow-callback }); }); test('is.nonEmptyObject', () => { const foo = {}; is.nonEmptyObject(foo); assert.strictEqual(is.nonEmptyObject({}), false); assert.strictEqual(is.nonEmptyObject(new Object()), false); // eslint-disable-line no-object-constructor assert.ok(is.nonEmptyObject({unicorn: '🦄'})); assert.strictEqual(is.nonEmptyObject([]), false); assert.strictEqual(is.nonEmptyObject(['unicorn']), false); const functionWithProperty = function () {}; (functionWithProperty as any).custom = 'value'; assert.strictEqual(is.nonEmptyObject(functionWithProperty), false); assert.throws(() => { isAssert.nonEmptyObject({}); }); assert.throws(() => { isAssert.nonEmptyObject(new Object()); // eslint-disable-line no-object-constructor }); assert.doesNotThrow(() => { isAssert.nonEmptyObject({unicorn: '🦄'}); }); }); test('is.nonEmptySet', () => { const temporarySet = new Set(); assert.strictEqual(is.nonEmptySet(temporarySet), false); assert.throws(() => { isAssert.nonEmptySet(temporarySet); }); temporarySet.add(1); assert.ok(is.nonEmptySet(temporarySet)); assert.doesNotThrow(() => { isAssert.nonEmptySet(temporarySet); }); }); test('is.nonEmptyMap', () => { const temporaryMap = new Map(); assert.strictEqual(is.nonEmptyMap(temporaryMap), false); assert.throws(() => { isAssert.nonEmptyMap(temporaryMap); }); temporaryMap.set('unicorn', '🦄'); assert.ok(is.nonEmptyMap(temporaryMap)); assert.doesNotThrow(() => { isAssert.nonEmptyMap(temporaryMap); }); }); test('is.propertyKey', () => { assert.ok(is.propertyKey('key')); assert.ok(is.propertyKey(42)); assert.ok(is.propertyKey(Symbol(''))); assert.strictEqual(is.propertyKey(null), false); assert.strictEqual(is.propertyKey(undefined), false); assert.strictEqual(is.propertyKey(true), false); assert.strictEqual(is.propertyKey({}), false); assert.strictEqual(is.propertyKey([]), false); assert.strictEqual(is.propertyKey(new Map()), false); assert.strictEqual(is.propertyKey(new Set()), false); // AssertPropertyKey should narrow to PropertyKey (string | number | symbol), not just number const symbolValue: unknown = Symbol('test'); assertPropertyKey(symbolValue); expectTypeOf(symbolValue).toEqualTypeOf(); }); test('is.any', () => { assert.ok(is.any(is.string, {}, true, '🦄')); assert.ok(is.any(is.object, false, {}, 'unicorns')); assert.strictEqual(is.any(is.boolean, '🦄', [], 3), false); assert.strictEqual(is.any(is.integer, true, 'lol', {}), false); assert.ok(is.any([is.string, is.number], {}, true, '🦄')); assert.strictEqual(is.any([is.boolean, is.number], 'unicorns', [], new Map()), false); assert.strictEqual(typeof is.any([is.string, is.number]), 'function'); assert.throws(() => { is.any(null as any, true); }); assert.throws(() => { is.any([], 'value'); }); assert.throws(() => { is.any(is.string); }); assert.doesNotThrow(() => { isAssert.any(is.string, {}, true, '🦄'); }); assert.doesNotThrow(() => { isAssert.any(is.object, false, {}, 'unicorns'); }); assert.throws(() => { isAssert.any([is.string, is.number]); }); assert.throws(() => { isAssert.any(is.boolean, '🦄', [], 3); }); assert.throws(() => { isAssert.any(is.integer, true, 'lol', {}); }); assert.throws(() => { isAssert.any(null as any, true); }); assert.throws(() => { isAssert.any([], 'value'); }); assert.throws(() => { isAssert.any(is.string); }); assert.throws(() => { isAssert.any(is.string, 1, 2, 3); }, { // Includes expected type and removes duplicates from received types: // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `string`. Received values of type `number`.', 'v'), }); assert.throws(() => { isAssert.any(is.string, 1, [4]); }, { // Includes expected type and lists all received types: // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `string`. Received values of types `number` and `Array`.', 'v'), }); assert.throws(() => { isAssert.any([is.string, is.nullOrUndefined], 1); }, { // Handles array as first argument: // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `string` or `null or undefined`. Received values of type `number`.', 'v'), }); assert.throws(() => { isAssert.any([is.string, is.number, is.boolean], null, undefined, Number.NaN); }, { // Handles more than 2 expected and received types: // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `string`, `number`, or `boolean`. Received values of types `null`, `undefined`, and `NaN`.', 'v'), }); assert.throws(() => { isAssert.any(() => false, 1); }, { // Default type assertion message // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `predicate returns truthy for any value`.', 'v'), }); }); test('is.all', () => { assert.ok(is.all(is.object, {}, new Set(), new Map())); assert.ok(is.all(is.boolean, true, false)); assert.strictEqual(is.all(is.string, '🦄', []), false); assert.strictEqual(is.all(is.set, new Map(), {}), false); assert.ok(is.all(is.array, ['1'], ['2'])); assert.ok(is.all([is.string, is.nonEmptyString], '🦄', 'unicorns')); assert.strictEqual(is.all([is.string, is.number], '🦄'), false); assert.throws(() => { is.all(null as any, true); }); assert.throws(() => { is.all([], 'value'); }); assert.throws(() => { is.all(is.string); }); assert.doesNotThrow(() => { isAssert.all(is.object, {}, new Set(), new Map()); }); assert.doesNotThrow(() => { isAssert.all(is.boolean, true, false); }); assert.throws(() => { isAssert.all([is.string, is.number]); }); assert.doesNotThrow(() => { isAssert.all([is.string, is.nonEmptyString], '🦄', 'unicorns'); }); assert.throws(() => { isAssert.all(is.string, '🦄', []); }); assert.throws(() => { isAssert.all([is.string, is.number], '🦄'); }); assert.throws(() => { isAssert.all(is.set, new Map(), {}); }); assert.throws(() => { isAssert.all(null as any, true); }); assert.throws(() => { isAssert.all([], 'value'); }); assert.throws(() => { isAssert.all(is.string); }); assert.throws(() => { isAssert.all(is.string, 1, 2, 3); }, { // Includes expected type and removes duplicates from received types: // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `string`. Received values of type `number`.', 'v'), }); assert.throws(() => { isAssert.all(is.string, 1, [4]); }, { // Includes expected type and lists all received types: // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `string`. Received values of types `number` and `Array`.', 'v'), }); assert.throws(() => { isAssert.all(() => false, 1); }, { // Default type assertion message // eslint-disable-next-line prefer-regex-literals message: new RegExp('Expected values which are `predicate returns truthy for all values`.', 'v'), }); }); test('is.any as predicate factory', () => { // Returns a type guard function when called with only predicates const isStringOrNumber = is.any([is.string, is.number]); assert.strictEqual(typeof isStringOrNumber, 'function'); assert.ok(isStringOrNumber('hello')); assert.ok(isStringOrNumber(123)); assert.strictEqual(isStringOrNumber(true), false); assert.strictEqual(isStringOrNumber({}), false); // Type narrowing works correctly (compile-time check) const value: unknown = 'test'; if (isStringOrNumber(value)) { // TypeScript should narrow to string | number const narrowed: string | number = value; assert.ok(typeof narrowed === 'string' || typeof narrowed === 'number'); } // Works with is.optional assert.ok(is.optional(undefined, is.any([is.string, is.number]))); assert.ok(is.optional('test', is.any([is.string, is.number]))); assert.ok(is.optional(42, is.any([is.string, is.number]))); assert.strictEqual(is.optional(true, is.any([is.string, is.number])), false); const predicateArray: Predicate[] = [is.string, is.number]; const isStringOrNumberFromArray = is.any(predicateArray); assert.strictEqual(typeof isStringOrNumberFromArray, 'function'); assert.ok(isStringOrNumberFromArray('hello')); assert.ok(isStringOrNumberFromArray(123)); assert.strictEqual(isStringOrNumberFromArray(true), false); // Type narrowing with is.optional (compile-time check) const optionalValue: unknown = undefined; if (is.optional(optionalValue, is.any([is.string, is.number]))) { // TypeScript should narrow to string | number | undefined const narrowed: string | number | undefined = optionalValue; assert.ok(narrowed === undefined || typeof narrowed === 'string' || typeof narrowed === 'number'); } // Works with more predicates const isStringOrNumberOrBoolean = is.any([is.string, is.number, is.boolean]); assert.ok(isStringOrNumberOrBoolean('hello')); assert.ok(isStringOrNumberOrBoolean(123)); assert.ok(isStringOrNumberOrBoolean(true)); assert.strictEqual(isStringOrNumberOrBoolean({}), false); assert.throws(() => { is.any([is.string, 123 as any]); }); }); test('is.all as predicate factory', () => { // Returns a type guard function when called with only predicates const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]); assert.strictEqual(typeof isArrayAndNonEmpty, 'function'); assert.ok(isArrayAndNonEmpty(['hello'])); assert.strictEqual(isArrayAndNonEmpty([]), false); assert.strictEqual(isArrayAndNonEmpty('hello'), false); // Type narrowing works correctly const value: unknown = ['test']; if (isArrayAndNonEmpty(value)) { // TypeScript should narrow to the intersection type assert.ok(Array.isArray(value)); assert.ok(value.length > 0); } // Works with is.optional assert.ok(is.optional(undefined, is.all([is.object, is.plainObject]))); assert.ok(is.optional({foo: 'bar'}, is.all([is.object, is.plainObject]))); assert.strictEqual(is.optional([], is.all([is.object, is.plainObject])), false); assert.throws(() => { is.all([is.string, 123 as any]); }); }); test('is.formData supplemental', () => { const data = new window.FormData(); assert.ok(is.formData(data)); assert.strictEqual(is.formData({}), false); assert.strictEqual(is.formData(undefined), false); assert.strictEqual(is.formData(null), false); assert.doesNotThrow(() => { isAssert.formData(data); }); assert.throws(() => { isAssert.formData({}); }); assert.throws(() => { isAssert.formData(undefined); }); assert.throws(() => { isAssert.formData(null); }); }); test('is.urlSearchParams', () => { const searchParameters = new URLSearchParams(); assert.ok(is.urlSearchParams(searchParameters)); assert.strictEqual(is.urlSearchParams({}), false); assert.strictEqual(is.urlSearchParams(undefined), false); assert.strictEqual(is.urlSearchParams(null), false); assert.doesNotThrow(() => { isAssert.urlSearchParams(searchParameters); }); assert.throws(() => { isAssert.urlSearchParams({}); }); assert.throws(() => { isAssert.urlSearchParams(undefined); }); assert.throws(() => { isAssert.urlSearchParams(null); }); }); test('is.validDate', () => { assert.ok(is.validDate(new Date())); assert.strictEqual(is.validDate(new Date('x')), false); assert.doesNotThrow(() => { isAssert.validDate(new Date()); }); assert.throws(() => { isAssert.validDate(new Date('x')); }); }); test('is.validLength', () => { assert.ok(is.validLength(1)); assert.ok(is.validLength(0)); assert.strictEqual(is.validLength(-1), false); assert.strictEqual(is.validLength(0.1), false); assert.doesNotThrow(() => { isAssert.validLength(1); }); assert.throws(() => { isAssert.validLength(-1); }); assert.throws(() => { isAssert.validLength(0.1); }); }); test('is.whitespaceString', () => { assert.ok(is.whitespaceString(' ')); assert.ok(is.whitespaceString(' ')); assert.ok(is.whitespaceString('   ')); assert.ok(is.whitespaceString('\u3000')); assert.ok(is.whitespaceString(' ')); assert.strictEqual(is.whitespaceString(''), false); assert.strictEqual(is.whitespaceString('-'), false); assert.strictEqual(is.whitespaceString(' hi '), false); assert.doesNotThrow(() => { isAssert.whitespaceString(' '); }); assert.throws(() => { isAssert.whitespaceString(''); }); assert.throws(() => { isAssert.whitespaceString(' hi '); }); }); test('assert', () => { // Contrived test showing that TypeScript acknowledges the type assertion in `isAssert.number()`. // Real-world usage includes asserting user input, but here we use a random number/string generator. const getNumberOrStringRandomly = (): number | string => { const random = Math.random(); if (random < 0.5) { return 'sometimes this function returns text'; } return random; }; const canUseOnlyNumber = (badlyTypedArgument: any): number => { // Narrow the type to number, or throw an error at runtime for non-numbers. isAssert.number(badlyTypedArgument); // Both the type and runtime value is number. return 1000 * badlyTypedArgument; }; const badlyTypedVariable: any = getNumberOrStringRandomly(); assert.ok(is.number(badlyTypedVariable) || is.string(badlyTypedVariable)); // Using try/catch for test purposes only. try { const result = canUseOnlyNumber(badlyTypedVariable); // Got lucky, the input was a number yielding a good result. assert.ok(is.number(result)); } catch { // Assertion was tripped. assert.ok(is.string(badlyTypedVariable)); } }); test('custom assertion message', () => { const message = 'Custom error message'; const assertThrowsTypeErrorWithMessage = (assertion: () => void) => { // `node:assert` does not verify the error class when matching only on `{message}`. assert.throws(assertion, error => { assert.ok(error instanceof TypeError); assert.strictEqual(error.message, message); return true; }); }; assertThrowsTypeErrorWithMessage(() => { isAssert.array(undefined, undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.arrayBuffer(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.arrayLike(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.asyncFunction(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.asyncGenerator(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.asyncGeneratorFunction(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.asyncIterable(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.bigInt64Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.bigUint64Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.bigint(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.blob(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.boolean(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.boundFunction(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.buffer(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.class(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.dataView(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.date(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.directInstanceOf(undefined, Error, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.emptyArray(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.emptyMap(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.emptyObject(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.emptySet(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.emptyString(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.emptyStringOrWhitespace(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { enum Enum {} isAssert.enumCase('invalid', Enum, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.error(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.evenInteger(33, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.falsy(true, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.finiteNumber(Number.POSITIVE_INFINITY, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.float32Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.float64Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.formData(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.function(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.generator(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.generatorFunction(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.htmlElement(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.inRange(5, [1, 2], message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.infinite(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.int16Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.int32Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.int8Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.integer(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.iterable(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.map(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nan(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nativePromise(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.not.undefined(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.not.string('hello', message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.negativeNumber(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nodeStream(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyArray(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyMap(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyObject(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptySet(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyString(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonEmptyStringAndNotWhitespace(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nonNegativeNumber(-1, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.null(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.nullOrUndefined(false, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.number(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.numericString(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.object(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.observable(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.oddInteger(42, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.plainObject(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.positiveInteger(0, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.positiveNumber(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.primitive([], message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.promise(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.propertyKey(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.regExp(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.safeInteger(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.set(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.sharedArrayBuffer(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.string(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.symbol(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.truthy(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.tupleLike(undefined, [], message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.typedArray(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.uint16Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.uint32Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.uint8Array(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.uint8ClampedArray(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.undefined(false, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.urlInstance(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.urlSearchParams(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.urlString(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.validDate(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.validLength(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.weakMap(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.weakRef(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.weakSet(undefined, message); }); assertThrowsTypeErrorWithMessage(() => { isAssert.whitespaceString(undefined, message); }); }); test('isAssert.not.undefined', () => { assert.throws(() => { isAssert.not.undefined(undefined); }, { message: 'Expected value which is not `undefined`, received value of type `undefined`.', }); assert.doesNotThrow(() => { isAssert.not.undefined(null); }); assert.doesNotThrow(() => { isAssert.not.undefined(false); }); assert.doesNotThrow(() => { isAssert.not.undefined(0); }); assert.doesNotThrow(() => { isAssert.not.undefined(''); }); }); test('isAssert.not', () => { assert.deepStrictEqual(new Set(keysOf(isAssert.not)), new Set(keysOf(notAssertionFixtures))); for (const type of keysOf(notAssertionFixtures)) { const {nonFixture, typeDescription} = notAssertionFixtures[type]; const testAssert = isAssert.not[type]; const fixtures = 'fixtures' in notAssertionFixtures[type] ? notAssertionFixtures[type].fixtures : [notAssertionFixtures[type].fixture]; for (const fixture of fixtures) { assert.throws(() => { testAssert(fixture); }, { message: `Expected value which is not \`${typeDescription}\`, received value of type \`${is(fixture)}\`.`, }); } assert.doesNotThrow(() => { testAssert(nonFixture); }); } assert.strictEqual('number' in isAssert.not, false); assert.strictEqual('integer' in isAssert.not, false); assert.strictEqual('object' in isAssert.not, false); assert.strictEqual('blob' in isAssert.not, false); assert.strictEqual('array' in isAssert.not, false); assert.strictEqual('date' in isAssert.not, false); assert.strictEqual('function' in isAssert.not, false); assert.strictEqual('map' in isAssert.not, false); assert.strictEqual('set' in isAssert.not, false); }); test('isAssert.not edge cases', () => { assert.doesNotThrow(() => { isAssert.not.null(undefined); }); }); test('is.optional', () => { assert.ok(is.optional(undefined, is.string)); assert.ok(is.optional('🦄', is.string)); assert.strictEqual(is.optional(123, is.string), false); assert.strictEqual(is.optional(null, is.string), false); }); test('isAssert.optional', () => { assert.doesNotThrow(() => { isAssert.optional(undefined, isAssert.string); }); assert.doesNotThrow(() => { isAssert.optional('🦄', isAssert.string); }); assert.throws(() => { isAssert.optional(123, isAssert.string); }); assert.throws(() => { isAssert.optional(null, isAssert.string); }); });