parent
48df5c429c
commit
2d4956e634
4 changed files with 374 additions and 6 deletions
81
test/test.ts
81
test/test.ts
|
|
@ -466,6 +466,17 @@ const subClasses = new Map<TypeNameWithFixture, TypeNameWithFixture[]>([
|
|||
['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<keyof typeof isAssert.not, ({fixture: unknown} | {fixtures: unknown[]}) & {nonFixture: unknown; typeDescription: string}>;
|
||||
|
||||
// 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}`, () => {
|
||||
|
|
@ -2534,6 +2545,14 @@ test('custom assertion message', () => {
|
|||
isAssert.nativePromise(undefined, message);
|
||||
});
|
||||
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.not.undefined(undefined, message);
|
||||
});
|
||||
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.not.string('hello', message);
|
||||
});
|
||||
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.negativeNumber(undefined, message);
|
||||
});
|
||||
|
|
@ -2715,6 +2734,68 @@ test('custom assertion 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));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import {expectTypeOf} from 'expect-type';
|
||||
import is, {
|
||||
assert as isAssert,
|
||||
assertNotNullOrUndefined,
|
||||
assertNotPrimitive,
|
||||
assertNotString,
|
||||
assertNotUndefined,
|
||||
type EvenInteger,
|
||||
type FiniteNumber,
|
||||
type Integer,
|
||||
|
|
@ -12,10 +18,14 @@ import is, {
|
|||
type PositiveInfinity,
|
||||
type PositiveInteger,
|
||||
type PositiveNumber,
|
||||
type Primitive,
|
||||
type SafeInteger,
|
||||
type ValidLength,
|
||||
} from '../source/index.ts';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
||||
type UnknownNotPrimitive<Forbidden extends Primitive> = Exclude<Primitive, Forbidden> | object;
|
||||
|
||||
// For each predicate, verify two things:
|
||||
// 1. True branch narrows to the branded type.
|
||||
// 2. False branch on a `number` input stays `number` (not `never`).
|
||||
|
|
@ -197,6 +207,154 @@ const distinctNumericBrandsStayDistinct = (
|
|||
return negativeInteger;
|
||||
};
|
||||
|
||||
const assertNotUndefinedCheck = (value: string | undefined) => {
|
||||
isAssert.not.undefined(value);
|
||||
expectTypeOf(value).toEqualTypeOf<string>();
|
||||
};
|
||||
|
||||
const assertNotUndefinedUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.undefined(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<undefined>>();
|
||||
};
|
||||
|
||||
const assertNotUndefinedGenericCheck = <T>(value: T) => {
|
||||
isAssert.not.undefined(value);
|
||||
const _: Exclude<T, undefined> = value;
|
||||
};
|
||||
|
||||
const nullValue = null;
|
||||
type Null = typeof nullValue;
|
||||
|
||||
const assertNotNullUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.null(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<Null>>();
|
||||
};
|
||||
|
||||
const assertNotNullOrUndefinedCheck = (value: string | Null | undefined) => {
|
||||
isAssert.not.nullOrUndefined(value);
|
||||
expectTypeOf(value).toEqualTypeOf<string>();
|
||||
};
|
||||
|
||||
const assertNotNullOrUndefinedUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.nullOrUndefined(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<Null | undefined>>();
|
||||
};
|
||||
|
||||
const assertNotStringCheck = (value: string | number) => {
|
||||
isAssert.not.string(value);
|
||||
expectTypeOf(value).toEqualTypeOf<number>();
|
||||
};
|
||||
|
||||
const assertNotStringUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.string(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<string>>();
|
||||
};
|
||||
|
||||
const assertNotStringGenericCheck = <T>(value: T) => {
|
||||
isAssert.not.string(value);
|
||||
const _: Exclude<T, string> = value;
|
||||
};
|
||||
|
||||
const assertNotBooleanUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.boolean(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<boolean>>();
|
||||
};
|
||||
|
||||
const assertNotSymbolUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.symbol(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<symbol>>();
|
||||
};
|
||||
|
||||
const assertNotBigintUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.bigint(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<bigint>>();
|
||||
};
|
||||
|
||||
const assertNotPrimitiveUnknownCheck = (value: unknown) => {
|
||||
isAssert.not.primitive(value);
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
||||
expectTypeOf(value).toEqualTypeOf<object>();
|
||||
};
|
||||
|
||||
const assertNotPrimitiveGenericCheck = <T>(value: T) => {
|
||||
isAssert.not.primitive(value);
|
||||
const _: Exclude<T, Primitive> = value;
|
||||
};
|
||||
|
||||
const assertNotNamedUndefinedExportCheck = (value: 0 | false | '' | Null | undefined | 'ok') => {
|
||||
assertNotUndefined(value);
|
||||
expectTypeOf(value).toEqualTypeOf<0 | false | '' | Null | 'ok'>();
|
||||
};
|
||||
|
||||
const assertNotNamedNullOrUndefinedUnknownExportCheck = (value: unknown) => {
|
||||
assertNotNullOrUndefined(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<Null | undefined>>();
|
||||
};
|
||||
|
||||
const assertNotNamedStringExportCheck = (value: string | number) => {
|
||||
assertNotString(value);
|
||||
expectTypeOf(value).toEqualTypeOf<number>();
|
||||
};
|
||||
|
||||
const assertNotNamedStringUnknownExportCheck = (value: unknown) => {
|
||||
assertNotString(value);
|
||||
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<string>>();
|
||||
};
|
||||
|
||||
const assertNotNamedPrimitiveUnknownExportCheck = (value: unknown) => {
|
||||
assertNotPrimitive(value);
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
||||
expectTypeOf(value).toEqualTypeOf<object>();
|
||||
};
|
||||
|
||||
const assertNotCallableDoesNotExistCheck = (value: string | undefined) => {
|
||||
// @ts-expect-error -- Generic negative assertions cannot safely infer complement types from arbitrary predicates.
|
||||
isAssert.not(is.undefined, value);
|
||||
const _: string | undefined = value;
|
||||
};
|
||||
|
||||
const assertNotNumberDoesNotExistCheck = (value: string | number) => {
|
||||
// @ts-expect-error -- `is.number` rejects `NaN`, so a narrowing negative assertion would be unsound.
|
||||
isAssert.not.number(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||||
const _: string | number = value;
|
||||
};
|
||||
|
||||
const assertNotIntegerDoesNotExistCheck = (value: string | number) => {
|
||||
// @ts-expect-error -- Numeric refinements are intentionally excluded from `assert.not`.
|
||||
isAssert.not.integer(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||||
const _: string | number = value;
|
||||
};
|
||||
|
||||
const assertNotObjectDoesNotExistCheck = (value: Record<string, unknown> | string) => {
|
||||
// @ts-expect-error -- TypeScript's `{}` type includes primitives, so `not.object` cannot safely narrow every object-like input.
|
||||
isAssert.not.object(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||||
const _: Record<string, unknown> | string = value;
|
||||
};
|
||||
|
||||
const assertNotBlobDoesNotExistCheck = (value: Blob | File | string) => {
|
||||
// @ts-expect-error -- `File` extends `Blob` in TypeScript but does not match the exact runtime `Blob` check.
|
||||
isAssert.not.blob(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||||
const _: Blob | File | string = value;
|
||||
};
|
||||
|
||||
const assertNotMapDoesNotExistCheck = (value: Map<string, number> | string) => {
|
||||
// @ts-expect-error -- Structural object types such as `Map` can be assignable in TypeScript without matching the runtime brand check.
|
||||
isAssert.not.map(value); // eslint-disable-line @typescript-eslint/no-unsafe-call, unicorn/no-array-callback-reference
|
||||
const _: Map<string, number> | string = value;
|
||||
};
|
||||
|
||||
const assertNotSetDoesNotExistCheck = (value: Set<string> | string) => {
|
||||
// @ts-expect-error -- Structural object types such as `Set` can be assignable in TypeScript without matching the runtime brand check.
|
||||
isAssert.not.set(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||||
const _: Set<string> | string = value;
|
||||
};
|
||||
|
||||
const assertNotDateDoesNotExistCheck = (value: Date | string) => {
|
||||
// @ts-expect-error -- Structural object types such as `Date` can be assignable in TypeScript without matching the runtime brand check.
|
||||
isAssert.not.date(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||||
const _: Date | string = value;
|
||||
};
|
||||
|
||||
// Suppress unused variable warnings
|
||||
nanCheck(42);
|
||||
finiteNumberCheck(42);
|
||||
|
|
@ -218,3 +376,30 @@ integerMixedUnionCheck(1);
|
|||
positiveNumberMixedUnionCheck(1);
|
||||
chainedNumericGuardCheck(1);
|
||||
distinctNumericBrandsStayDistinct(42 as PositiveInteger, -1 as NegativeInteger, 0 as ValidLength);
|
||||
assertNotUndefinedCheck('🦄');
|
||||
assertNotUndefinedUnknownCheck('🦄');
|
||||
assertNotUndefinedGenericCheck<string | undefined>('🦄');
|
||||
assertNotNullUnknownCheck('🦄');
|
||||
assertNotNullOrUndefinedCheck('🦄');
|
||||
assertNotNullOrUndefinedUnknownCheck('🦄');
|
||||
assertNotStringCheck(1);
|
||||
assertNotStringUnknownCheck(1);
|
||||
assertNotStringGenericCheck<string | number>(1);
|
||||
assertNotBooleanUnknownCheck(1);
|
||||
assertNotSymbolUnknownCheck(1);
|
||||
assertNotBigintUnknownCheck(1);
|
||||
assertNotPrimitiveUnknownCheck({});
|
||||
assertNotPrimitiveGenericCheck<string | {unicorn: true}>({unicorn: true});
|
||||
assertNotNamedUndefinedExportCheck(0);
|
||||
assertNotNamedNullOrUndefinedUnknownExportCheck('🦄');
|
||||
assertNotNamedStringExportCheck(1);
|
||||
assertNotNamedStringUnknownExportCheck(1);
|
||||
assertNotNamedPrimitiveUnknownExportCheck({});
|
||||
assertNotCallableDoesNotExistCheck('🦄');
|
||||
assertNotNumberDoesNotExistCheck(Number.NaN);
|
||||
assertNotIntegerDoesNotExistCheck(1.5);
|
||||
assertNotObjectDoesNotExistCheck('🦄');
|
||||
assertNotBlobDoesNotExistCheck('🦄');
|
||||
assertNotMapDoesNotExistCheck('🦄');
|
||||
assertNotSetDoesNotExistCheck('🦄');
|
||||
assertNotDateDoesNotExistCheck('🦄');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue