blob: 78d8410a6a4698de311aad06ebbb4b99fdf34f68 [file] [edit]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
assertDefined,
assertExists,
assertFalse,
assertIsArrayBufferView,
assertIsInstance,
assertTrue,
assertUnreachable,
ensureDefined,
ensureExists,
ensureIsInstance,
} from './assert';
class Animal {}
class Dog extends Animal {}
describe('assertTrue', () => {
test('throws', () => {
// assertTrue is declared `asserts x`, so strict-boolean-expressions treats
// the argument as a condition and flags these constant values. Passing them
// is the whole point of the test, so silence the rule here.
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
expect(() => assertTrue(true)).not.toThrow();
expect(() => assertTrue('string')).not.toThrow();
expect(() => assertTrue({})).not.toThrow();
expect(() => assertTrue(new Map())).not.toThrow();
expect(() => assertTrue(false)).toThrow();
expect(() => assertTrue(null)).toThrow();
expect(() => assertTrue(undefined)).toThrow();
expect(() => assertTrue('')).toThrow();
expect(() => assertTrue(0)).toThrow();
expect(() => assertTrue(0n)).toThrow();
expect(() => assertTrue(NaN)).toThrow();
expect(() => assertTrue(Number.NaN)).toThrow();
/* eslint-enable @typescript-eslint/strict-boolean-expressions */
});
test('throws with custom message', () => {
expect(() => assertTrue(false, 'boom')).toThrow('boom');
});
test('narrows types', () => {
// Typescript test - this should compile
const x: boolean = true;
assertTrue(x);
const check: true = x; // compiles only if x narrowed correctly
check; // Silence unused const warning
});
});
describe('assertFalse', () => {
test('throws', () => {
expect(() => assertFalse(false)).not.toThrow();
expect(() => assertFalse(null)).not.toThrow();
expect(() => assertFalse(undefined)).not.toThrow();
expect(() => assertFalse('')).not.toThrow();
expect(() => assertFalse(0)).not.toThrow();
expect(() => assertFalse(0n)).not.toThrow();
expect(() => assertFalse(NaN)).not.toThrow();
expect(() => assertFalse(Number.NaN)).not.toThrow();
expect(() => assertFalse(true)).toThrow();
expect(() => assertFalse('string')).toThrow();
expect(() => assertFalse({})).toThrow();
expect(() => assertFalse(new Map())).toThrow();
});
test('throws with custom message', () => {
expect(() => assertFalse(true, 'boom')).toThrow('boom');
});
test('narrows types', () => {
// Typescript test - this should compile
const x: boolean = false;
assertFalse(x);
const check: false = x; // compiles only if x narrowed correctly
check; // Silence unused const warning
});
});
describe('ensureExists', () => {
test('returns the value when present', () => {
expect(ensureExists('a')).toBe('a');
const obj = {};
expect(ensureExists(obj)).toBe(obj);
});
test('passes through falsy non-nullish values', () => {
expect(ensureExists(0)).toBe(0);
expect(ensureExists('')).toBe('');
expect(ensureExists(false)).toBe(false);
});
test('throws on null and undefined', () => {
expect(() => ensureExists(null)).toThrow();
expect(() => ensureExists(undefined)).toThrow();
});
test('throws with custom message', () => {
expect(() => ensureExists(null, 'boom')).toThrow('boom');
});
test('narrows the return type', () => {
const x: string | null | undefined = 'a';
const check: string = ensureExists(x); // compiles only if narrowed
check;
});
});
describe('assertExists', () => {
test('does not throw when present', () => {
expect(() => assertExists('a')).not.toThrow();
expect(() => assertExists(0)).not.toThrow();
expect(() => assertExists('')).not.toThrow();
expect(() => assertExists(false)).not.toThrow();
});
test('throws on null and undefined', () => {
expect(() => assertExists(null)).toThrow();
expect(() => assertExists(undefined)).toThrow();
});
test('throws with custom message', () => {
expect(() => assertExists(undefined, 'boom')).toThrow('boom');
});
test('narrows the value in place', () => {
const x: string | null | undefined = 'a';
assertExists(x);
const check: string = x; // compiles only if null|undefined removed
check;
});
});
describe('ensureDefined', () => {
test('returns the value when defined', () => {
expect(ensureDefined('a')).toBe('a');
expect(ensureDefined(0)).toBe(0);
});
test('passes null through (only undefined is rejected)', () => {
expect(ensureDefined(null)).toBeNull();
});
test('throws on undefined', () => {
expect(() => ensureDefined(undefined)).toThrow();
});
test('throws with custom message', () => {
expect(() => ensureDefined(undefined, 'boom')).toThrow('boom');
});
test('narrows the return type but keeps null', () => {
const x: string | null | undefined = null;
const check: string | null = ensureDefined(x); // undefined removed
check;
});
});
describe('assertDefined', () => {
test('does not throw for defined values, including null', () => {
expect(() => assertDefined('a')).not.toThrow();
expect(() => assertDefined(null)).not.toThrow();
expect(() => assertDefined(0)).not.toThrow();
});
test('throws on undefined', () => {
expect(() => assertDefined(undefined)).toThrow();
});
test('throws with custom message', () => {
expect(() => assertDefined(undefined, 'boom')).toThrow('boom');
});
test('narrows the value in place but keeps null', () => {
const x: string | null | undefined = null;
assertDefined(x);
const check: string | null = x; // only undefined removed
check;
});
});
describe('ensureIsInstance', () => {
test('returns the value when it matches', () => {
const dog = new Dog();
expect(ensureIsInstance(dog, Dog)).toBe(dog);
// Subclass instance is also an instance of the base class.
expect(ensureIsInstance(dog, Animal)).toBe(dog);
});
test('throws when it does not match', () => {
expect(() => ensureIsInstance(new Animal(), Dog)).toThrow();
expect(() => ensureIsInstance({}, Dog)).toThrow();
expect(() => ensureIsInstance(null, Dog)).toThrow();
});
test('default message names the class', () => {
expect(() => ensureIsInstance({}, Dog)).toThrow('Dog');
});
test('throws with custom message', () => {
expect(() => ensureIsInstance({}, Dog, 'boom')).toThrow('boom');
});
test('narrows the return type', () => {
const x: unknown = new Dog();
const check: Dog = ensureIsInstance(x, Dog); // compiles only if narrowed
check;
});
});
describe('assertIsInstance', () => {
test('does not throw when it matches', () => {
expect(() => assertIsInstance(new Dog(), Dog)).not.toThrow();
expect(() => assertIsInstance(new Dog(), Animal)).not.toThrow();
});
test('throws when it does not match', () => {
expect(() => assertIsInstance(new Animal(), Dog)).toThrow();
expect(() => assertIsInstance({}, Dog)).toThrow();
});
test('throws with custom message', () => {
expect(() => assertIsInstance({}, Dog, 'boom')).toThrow('boom');
});
test('narrows the value in place', () => {
const x: unknown = new Dog();
assertIsInstance(x, Dog);
const check: Dog = x; // compiles only if narrowed to Dog
check;
});
});
describe('assertUnreachable', () => {
test('always throws', () => {
expect(() => assertUnreachable('x' as never)).toThrow();
});
test('throws with custom message', () => {
expect(() => assertUnreachable('x' as never, 'boom')).toThrow('boom');
});
test('enforces exhaustive switches at compile time', () => {
type Color = 'red' | 'green';
const name = (c: Color): string => {
switch (c) {
case 'red':
return 'red';
case 'green':
return 'green';
default:
// Compiles only if every Color member is handled above; adding a new
// member would make `c` not `never` and fail to compile here.
return assertUnreachable(c);
}
};
expect(name('red')).toBe('red');
});
});
describe('assertIsArrayBufferView', () => {
test('does not throw for an ArrayBuffer-backed view', () => {
const view = new Uint8Array(new ArrayBuffer(8));
expect(() => assertIsArrayBufferView(view)).not.toThrow();
});
test('throws for a SharedArrayBuffer-backed view', () => {
const view = new Uint8Array(new SharedArrayBuffer(8));
expect(() => assertIsArrayBufferView(view)).toThrow();
});
});