Added test package in nix flake to automatically run tests.

To be able to do such, it was required to add the vendor directory
to the git project.
This commit is contained in:
didier amyot committed 2023-06-10 09:33:04 -04:00
1 parent 459dc26b4f
commit 1f2ea8b7e5
15 files changed
+2619

No files matched your search

+570
View File
@@ -0,0 +1,570 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
// A module to print ANSI terminal colors. Inspired by chalk, kleur, and colors
// on npm.
/**
* String formatters and utilities for dealing with ANSI color codes.
*
* This module is browser compatible.
*
* This module supports `NO_COLOR` environmental variable disabling any coloring
* if `NO_COLOR` is set.
*
* @example
* ```typescript
* import {
* bgBlue,
* bgRgb24,
* bgRgb8,
* bold,
* italic,
* red,
* rgb24,
* rgb8,
* } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts";
*
* console.log(bgBlue(italic(red(bold("Hello, World!")))));
*
* // also supports 8bit colors
*
* console.log(rgb8("Hello, World!", 42));
*
* console.log(bgRgb8("Hello, World!", 42));
*
* // and 24bit rgb
*
* console.log(rgb24("Hello, World!", {
* r: 41,
* g: 42,
* b: 43,
* }));
*
* console.log(bgRgb24("Hello, World!", {
* r: 41,
* g: 42,
* b: 43,
* }));
* ```
*
* @module
*/
// deno-lint-ignore no-explicit-any
const { Deno } = globalThis as any;
const noColor = typeof Deno?.noColor === "boolean"
? Deno.noColor as boolean
: true;
interface Code {
open: string;
close: string;
regexp: RegExp;
}
/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */
interface Rgb {
r: number;
g: number;
b: number;
}
let enabled = !noColor;
/**
* Set changing text color to enabled or disabled
* @param value
*/
export function setColorEnabled(value: boolean) {
if (noColor) {
return;
}
enabled = value;
}
/** Get whether text color change is enabled or disabled. */
export function getColorEnabled(): boolean {
return enabled;
}
/**
* Builds color code
* @param open
* @param close
*/
function code(open: number[], close: number): Code {
return {
open: `\x1b[${open.join(";")}m`,
close: `\x1b[${close}m`,
regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
};
}
/**
* Applies color and background based on color code and its associated text
* @param str text to apply color settings to
* @param code color code to apply
*/
function run(str: string, code: Code): string {
return enabled
? `${code.open}${str.replace(code.regexp, code.open)}${code.close}`
: str;
}
/**
* Reset the text modified
* @param str text to reset
*/
export function reset(str: string): string {
return run(str, code([0], 0));
}
/**
* Make the text bold.
* @param str text to make bold
*/
export function bold(str: string): string {
return run(str, code([1], 22));
}
/**
* The text emits only a small amount of light.
* @param str text to dim
*/
export function dim(str: string): string {
return run(str, code([2], 22));
}
/**
* Make the text italic.
* @param str text to make italic
*/
export function italic(str: string): string {
return run(str, code([3], 23));
}
/**
* Make the text underline.
* @param str text to underline
*/
export function underline(str: string): string {
return run(str, code([4], 24));
}
/**
* Invert background color and text color.
* @param str text to invert its color
*/
export function inverse(str: string): string {
return run(str, code([7], 27));
}
/**
* Make the text hidden.
* @param str text to hide
*/
export function hidden(str: string): string {
return run(str, code([8], 28));
}
/**
* Put horizontal line through the center of the text.
* @param str text to strike through
*/
export function strikethrough(str: string): string {
return run(str, code([9], 29));
}
/**
* Set text color to black.
* @param str text to make black
*/
export function black(str: string): string {
return run(str, code([30], 39));
}
/**
* Set text color to red.
* @param str text to make red
*/
export function red(str: string): string {
return run(str, code([31], 39));
}
/**
* Set text color to green.
* @param str text to make green
*/
export function green(str: string): string {
return run(str, code([32], 39));
}
/**
* Set text color to yellow.
* @param str text to make yellow
*/
export function yellow(str: string): string {
return run(str, code([33], 39));
}
/**
* Set text color to blue.
* @param str text to make blue
*/
export function blue(str: string): string {
return run(str, code([34], 39));
}
/**
* Set text color to magenta.
* @param str text to make magenta
*/
export function magenta(str: string): string {
return run(str, code([35], 39));
}
/**
* Set text color to cyan.
* @param str text to make cyan
*/
export function cyan(str: string): string {
return run(str, code([36], 39));
}
/**
* Set text color to white.
* @param str text to make white
*/
export function white(str: string): string {
return run(str, code([37], 39));
}
/**
* Set text color to gray.
* @param str text to make gray
*/
export function gray(str: string): string {
return brightBlack(str);
}
/**
* Set text color to bright black.
* @param str text to make bright-black
*/
export function brightBlack(str: string): string {
return run(str, code([90], 39));
}
/**
* Set text color to bright red.
* @param str text to make bright-red
*/
export function brightRed(str: string): string {
return run(str, code([91], 39));
}
/**
* Set text color to bright green.
* @param str text to make bright-green
*/
export function brightGreen(str: string): string {
return run(str, code([92], 39));
}
/**
* Set text color to bright yellow.
* @param str text to make bright-yellow
*/
export function brightYellow(str: string): string {
return run(str, code([93], 39));
}
/**
* Set text color to bright blue.
* @param str text to make bright-blue
*/
export function brightBlue(str: string): string {
return run(str, code([94], 39));
}
/**
* Set text color to bright magenta.
* @param str text to make bright-magenta
*/
export function brightMagenta(str: string): string {
return run(str, code([95], 39));
}
/**
* Set text color to bright cyan.
* @param str text to make bright-cyan
*/
export function brightCyan(str: string): string {
return run(str, code([96], 39));
}
/**
* Set text color to bright white.
* @param str text to make bright-white
*/
export function brightWhite(str: string): string {
return run(str, code([97], 39));
}
/**
* Set background color to black.
* @param str text to make its background black
*/
export function bgBlack(str: string): string {
return run(str, code([40], 49));
}
/**
* Set background color to red.
* @param str text to make its background red
*/
export function bgRed(str: string): string {
return run(str, code([41], 49));
}
/**
* Set background color to green.
* @param str text to make its background green
*/
export function bgGreen(str: string): string {
return run(str, code([42], 49));
}
/**
* Set background color to yellow.
* @param str text to make its background yellow
*/
export function bgYellow(str: string): string {
return run(str, code([43], 49));
}
/**
* Set background color to blue.
* @param str text to make its background blue
*/
export function bgBlue(str: string): string {
return run(str, code([44], 49));
}
/**
* Set background color to magenta.
* @param str text to make its background magenta
*/
export function bgMagenta(str: string): string {
return run(str, code([45], 49));
}
/**
* Set background color to cyan.
* @param str text to make its background cyan
*/
export function bgCyan(str: string): string {
return run(str, code([46], 49));
}
/**
* Set background color to white.
* @param str text to make its background white
*/
export function bgWhite(str: string): string {
return run(str, code([47], 49));
}
/**
* Set background color to bright black.
* @param str text to make its background bright-black
*/
export function bgBrightBlack(str: string): string {
return run(str, code([100], 49));
}
/**
* Set background color to bright red.
* @param str text to make its background bright-red
*/
export function bgBrightRed(str: string): string {
return run(str, code([101], 49));
}
/**
* Set background color to bright green.
* @param str text to make its background bright-green
*/
export function bgBrightGreen(str: string): string {
return run(str, code([102], 49));
}
/**
* Set background color to bright yellow.
* @param str text to make its background bright-yellow
*/
export function bgBrightYellow(str: string): string {
return run(str, code([103], 49));
}
/**
* Set background color to bright blue.
* @param str text to make its background bright-blue
*/
export function bgBrightBlue(str: string): string {
return run(str, code([104], 49));
}
/**
* Set background color to bright magenta.
* @param str text to make its background bright-magenta
*/
export function bgBrightMagenta(str: string): string {
return run(str, code([105], 49));
}
/**
* Set background color to bright cyan.
* @param str text to make its background bright-cyan
*/
export function bgBrightCyan(str: string): string {
return run(str, code([106], 49));
}
/**
* Set background color to bright white.
* @param str text to make its background bright-white
*/
export function bgBrightWhite(str: string): string {
return run(str, code([107], 49));
}
/* Special Color Sequences */
/**
* Clam and truncate color codes
* @param n
* @param max number to truncate to
* @param min number to truncate from
*/
function clampAndTruncate(n: number, max = 255, min = 0): number {
return Math.trunc(Math.max(Math.min(n, max), min));
}
/**
* Set text color using paletted 8bit colors.
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
* @param str text color to apply paletted 8bit colors to
* @param color code
*/
export function rgb8(str: string, color: number): string {
return run(str, code([38, 5, clampAndTruncate(color)], 39));
}
/**
* Set background color using paletted 8bit colors.
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
* @param str text color to apply paletted 8bit background colors to
* @param color code
*/
export function bgRgb8(str: string, color: number): string {
return run(str, code([48, 5, clampAndTruncate(color)], 49));
}
/**
* Set text color using 24bit rgb.
* `color` can be a number in range `0x000000` to `0xffffff` or
* an `Rgb`.
*
* To produce the color magenta:
*
* ```ts
* import { rgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts";
* rgb24("foo", 0xff00ff);
* rgb24("foo", {r: 255, g: 0, b: 255});
* ```
* @param str text color to apply 24bit rgb to
* @param color code
*/
export function rgb24(str: string, color: number | Rgb): string {
if (typeof color === "number") {
return run(
str,
code(
[38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff],
39,
),
);
}
return run(
str,
code(
[
38,
2,
clampAndTruncate(color.r),
clampAndTruncate(color.g),
clampAndTruncate(color.b),
],
39,
),
);
}
/**
* Set background color using 24bit rgb.
* `color` can be a number in range `0x000000` to `0xffffff` or
* an `Rgb`.
*
* To produce the color magenta:
*
* ```ts
* import { bgRgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts";
* bgRgb24("foo", 0xff00ff);
* bgRgb24("foo", {r: 255, g: 0, b: 255});
* ```
* @param str text color to apply 24bit rgb to
* @param color code
*/
export function bgRgb24(str: string, color: number | Rgb): string {
if (typeof color === "number") {
return run(
str,
code(
[48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff],
49,
),
);
}
return run(
str,
code(
[
48,
2,
clampAndTruncate(color.r),
clampAndTruncate(color.g),
clampAndTruncate(color.b),
],
49,
),
);
}
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
const ANSI_PATTERN = new RegExp(
[
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
].join("|"),
"g",
);
/**
* Remove ANSI escape codes from the string.
* @param string to remove ANSI escape codes from
*/
export function stripColor(string: string): string {
return string.replace(ANSI_PATTERN, "");
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
export const DEFAULT_CHUNK_SIZE = 16_640;
export const DEFAULT_BUFFER_SIZE = 32 * 1024;
/** Generate longest proper prefix which is also suffix array. */
export function createLPS(pat: Uint8Array): Uint8Array {
const lps = new Uint8Array(pat.length);
lps[0] = 0;
let prefixEnd = 0;
let i = 1;
while (i < lps.length) {
if (pat[i] == pat[prefixEnd]) {
prefixEnd++;
lps[i] = prefixEnd;
i++;
} else if (prefixEnd === 0) {
lps[i] = 0;
i++;
} else {
prefixEnd = lps[prefixEnd - 1];
}
}
return lps;
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { DEFAULT_BUFFER_SIZE } from "./_common.ts";
import type { Reader, ReaderSync } from "../types.d.ts";
/** Turns a Reader, `r`, into an async iterator.
*
* ```ts
* import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
*
* let f = await Deno.open("/etc/passwd");
* for await (const chunk of iterateReader(f)) {
* console.log(chunk);
* }
* f.close();
* ```
*
* Second argument can be used to tune size of a buffer.
* Default size of the buffer is 32kB.
*
* ```ts
* import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
*
* let f = await Deno.open("/etc/passwd");
* const it = iterateReader(f, {
* bufSize: 1024 * 1024
* });
* for await (const chunk of it) {
* console.log(chunk);
* }
* f.close();
* ```
*/
export async function* iterateReader(
r: Reader,
options?: {
bufSize?: number;
},
): AsyncIterableIterator<Uint8Array> {
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
const b = new Uint8Array(bufSize);
while (true) {
const result = await r.read(b);
if (result === null) {
break;
}
yield b.slice(0, result);
}
}
/** Turns a ReaderSync, `r`, into an iterator.
*
* ```ts
* import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
*
* let f = Deno.openSync("/etc/passwd");
* for (const chunk of iterateReaderSync(f)) {
* console.log(chunk);
* }
* f.close();
* ```
*
* Second argument can be used to tune size of a buffer.
* Default size of the buffer is 32kB.
*
* ```ts
* import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
* let f = await Deno.open("/etc/passwd");
* const iter = iterateReaderSync(f, {
* bufSize: 1024 * 1024
* });
* for (const chunk of iter) {
* console.log(chunk);
* }
* f.close();
* ```
*
* Iterator uses an internal buffer of fixed size for efficiency; it returns
* a view on that buffer on each iteration. It is therefore caller's
* responsibility to copy contents of the buffer if needed; otherwise the
* next iteration will overwrite contents of previously returned chunk.
*/
export function* iterateReaderSync(
r: ReaderSync,
options?: {
bufSize?: number;
},
): IterableIterator<Uint8Array> {
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
const b = new Uint8Array(bufSize);
while (true) {
const result = r.readSync(b);
if (result === null) {
break;
}
yield b.slice(0, result);
}
}
+440
View File
@@ -0,0 +1,440 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import {
bgGreen,
bgRed,
bold,
gray,
green,
red,
white,
} from "../fmt/colors.ts";
interface FarthestPoint {
y: number;
id: number;
}
export enum DiffType {
removed = "removed",
common = "common",
added = "added",
}
export interface DiffResult<T> {
type: DiffType;
value: T;
details?: Array<DiffResult<T>>;
}
const REMOVED = 1;
const COMMON = 2;
const ADDED = 3;
function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
const common = [];
if (A.length === 0 || B.length === 0) return [];
for (let i = 0; i < Math.min(A.length, B.length); i += 1) {
if (
A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]
) {
common.push(A[reverse ? A.length - i - 1 : i]);
} else {
return common;
}
}
return common;
}
/**
* Renders the differences between the actual and expected values
* @param A Actual value
* @param B Expected value
*/
export function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
const prefixCommon = createCommon(A, B);
const suffixCommon = createCommon(
A.slice(prefixCommon.length),
B.slice(prefixCommon.length),
true,
).reverse();
A = suffixCommon.length
? A.slice(prefixCommon.length, -suffixCommon.length)
: A.slice(prefixCommon.length);
B = suffixCommon.length
? B.slice(prefixCommon.length, -suffixCommon.length)
: B.slice(prefixCommon.length);
const swapped = B.length > A.length;
[A, B] = swapped ? [B, A] : [A, B];
const M = A.length;
const N = B.length;
if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
if (!N) {
return [
...prefixCommon.map(
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
),
...A.map(
(a): DiffResult<typeof a> => ({
type: swapped ? DiffType.added : DiffType.removed,
value: a,
}),
),
...suffixCommon.map(
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
),
];
}
const offset = N;
const delta = M - N;
const size = M + N + 1;
const fp: FarthestPoint[] = Array.from(
{ length: size },
() => ({ y: -1, id: -1 }),
);
/**
* INFO:
* This buffer is used to save memory and improve performance.
* The first half is used to save route and last half is used to save diff
* type.
* This is because, when I kept new uint8array area to save type,performance
* worsened.
*/
const routes = new Uint32Array((M * N + size + 1) * 2);
const diffTypesPtrOffset = routes.length / 2;
let ptr = 0;
let p = -1;
function backTrace<T>(
A: T[],
B: T[],
current: FarthestPoint,
swapped: boolean,
): Array<{
type: DiffType;
value: T;
}> {
const M = A.length;
const N = B.length;
const result = [];
let a = M - 1;
let b = N - 1;
let j = routes[current.id];
let type = routes[current.id + diffTypesPtrOffset];
while (true) {
if (!j && !type) break;
const prev = j;
if (type === REMOVED) {
result.unshift({
type: swapped ? DiffType.removed : DiffType.added,
value: B[b],
});
b -= 1;
} else if (type === ADDED) {
result.unshift({
type: swapped ? DiffType.added : DiffType.removed,
value: A[a],
});
a -= 1;
} else {
result.unshift({ type: DiffType.common, value: A[a] });
a -= 1;
b -= 1;
}
j = routes[prev];
type = routes[prev + diffTypesPtrOffset];
}
return result;
}
function createFP(
slide: FarthestPoint,
down: FarthestPoint,
k: number,
M: number,
): FarthestPoint {
if (slide && slide.y === -1 && down && down.y === -1) {
return { y: 0, id: 0 };
}
if (
(down && down.y === -1) ||
k === M ||
(slide && slide.y) > (down && down.y) + 1
) {
const prev = slide.id;
ptr++;
routes[ptr] = prev;
routes[ptr + diffTypesPtrOffset] = ADDED;
return { y: slide.y, id: ptr };
} else {
const prev = down.id;
ptr++;
routes[ptr] = prev;
routes[ptr + diffTypesPtrOffset] = REMOVED;
return { y: down.y + 1, id: ptr };
}
}
function snake<T>(
k: number,
slide: FarthestPoint,
down: FarthestPoint,
_offset: number,
A: T[],
B: T[],
): FarthestPoint {
const M = A.length;
const N = B.length;
if (k < -N || M < k) return { y: -1, id: -1 };
const fp = createFP(slide, down, k, M);
while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) {
const prev = fp.id;
ptr++;
fp.id = ptr;
fp.y += 1;
routes[ptr] = prev;
routes[ptr + diffTypesPtrOffset] = COMMON;
}
return fp;
}
while (fp[delta + offset].y < N) {
p = p + 1;
for (let k = -p; k < delta; ++k) {
fp[k + offset] = snake(
k,
fp[k - 1 + offset],
fp[k + 1 + offset],
offset,
A,
B,
);
}
for (let k = delta + p; k > delta; --k) {
fp[k + offset] = snake(
k,
fp[k - 1 + offset],
fp[k + 1 + offset],
offset,
A,
B,
);
}
fp[delta + offset] = snake(
delta,
fp[delta - 1 + offset],
fp[delta + 1 + offset],
offset,
A,
B,
);
}
return [
...prefixCommon.map(
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
),
...backTrace(A, B, fp[delta + offset], swapped),
...suffixCommon.map(
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c }),
),
];
}
/**
* Renders the differences between the actual and expected strings
* Partially inspired from https://github.com/kpdecker/jsdiff
* @param A Actual string
* @param B Expected string
*/
export function diffstr(A: string, B: string) {
function unescape(string: string): string {
// unescape invisible characters.
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences
return string
.replaceAll("\b", "\\b")
.replaceAll("\f", "\\f")
.replaceAll("\t", "\\t")
.replaceAll("\v", "\\v")
.replaceAll( // does not remove line breaks
/\r\n|\r|\n/g,
(str) => str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n",
);
}
function tokenize(string: string, { wordDiff = false } = {}): string[] {
if (wordDiff) {
// Split string on whitespace symbols
const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
// Extended Latin character set
const words =
/^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u;
// Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars
for (let i = 0; i < tokens.length - 1; i++) {
if (
!tokens[i + 1] && tokens[i + 2] && words.test(tokens[i]) &&
words.test(tokens[i + 2])
) {
tokens[i] += tokens[i + 2];
tokens.splice(i + 1, 2);
i--;
}
}
return tokens.filter((token) => token);
} else {
// Split string on new lines symbols
const tokens = [], lines = string.split(/(\n|\r\n)/);
// Ignore final empty token when text ends with a newline
if (!lines[lines.length - 1]) {
lines.pop();
}
// Merge the content and line separators into single tokens
for (let i = 0; i < lines.length; i++) {
if (i % 2) {
tokens[tokens.length - 1] += lines[i];
} else {
tokens.push(lines[i]);
}
}
return tokens;
}
}
// Create details by filtering relevant word-diff for current line
// and merge "space-diff" if surrounded by word-diff for cleaner displays
function createDetails(
line: DiffResult<string>,
tokens: Array<DiffResult<string>>,
) {
return tokens.filter(({ type }) =>
type === line.type || type === DiffType.common
).map((result, i, t) => {
if (
(result.type === DiffType.common) && (t[i - 1]) &&
(t[i - 1]?.type === t[i + 1]?.type) && /\s+/.test(result.value)
) {
return {
...result,
type: t[i - 1].type,
};
}
return result;
});
}
// Compute multi-line diff
const diffResult = diff(
tokenize(`${unescape(A)}\n`),
tokenize(`${unescape(B)}\n`),
);
const added = [], removed = [];
for (const result of diffResult) {
if (result.type === DiffType.added) {
added.push(result);
}
if (result.type === DiffType.removed) {
removed.push(result);
}
}
// Compute word-diff
const aLines = added.length < removed.length ? added : removed;
const bLines = aLines === removed ? added : removed;
for (const a of aLines) {
let tokens = [] as Array<DiffResult<string>>,
b: undefined | DiffResult<string>;
// Search another diff line with at least one common token
while (bLines.length) {
b = bLines.shift();
tokens = diff(
tokenize(a.value, { wordDiff: true }),
tokenize(b?.value ?? "", { wordDiff: true }),
);
if (
tokens.some(({ type, value }) =>
type === DiffType.common && value.trim().length
)
) {
break;
}
}
// Register word-diff details
a.details = createDetails(a, tokens);
if (b) {
b.details = createDetails(b, tokens);
}
}
return diffResult;
}
/**
* Colors the output of assertion diffs
* @param diffType Difference type, either added or removed
*/
function createColor(
diffType: DiffType,
{ background = false } = {},
): (s: string) => string {
// TODO(@littledivy): Remove this when we can detect
// true color terminals.
// https://github.com/denoland/deno_std/issues/2575
background = false;
switch (diffType) {
case DiffType.added:
return (s: string): string =>
background ? bgGreen(white(s)) : green(bold(s));
case DiffType.removed:
return (s: string): string => background ? bgRed(white(s)) : red(bold(s));
default:
return white;
}
}
/**
* Prefixes `+` or `-` in diff output
* @param diffType Difference type, either added or removed
*/
function createSign(diffType: DiffType): string {
switch (diffType) {
case DiffType.added:
return "+ ";
case DiffType.removed:
return "- ";
default:
return " ";
}
}
export function buildMessage(
diffResult: ReadonlyArray<DiffResult<string>>,
{ stringDiff = false } = {},
): string[] {
const messages: string[] = [], diffMessages: string[] = [];
messages.push("");
messages.push("");
messages.push(
` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${
green(bold("Expected"))
}`,
);
messages.push("");
messages.push("");
diffResult.forEach((result: DiffResult<string>) => {
const c = createColor(result.type);
const line = result.details?.map((detail) =>
detail.type !== DiffType.common
? createColor(detail.type, { background: true })(detail.value)
: detail.value
).join("") ?? result.value;
diffMessages.push(c(`${createSign(result.type)}${line}`));
});
messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages));
messages.push("");
return messages;
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/**
* Converts the input into a string. Objects, Sets and Maps are sorted so as to
* make tests less flaky
* @param v Value to be formatted
*/
export function format(v: unknown): string {
// deno-lint-ignore no-explicit-any
const { Deno } = globalThis as any;
return typeof Deno?.inspect === "function"
? Deno.inspect(v, {
depth: Infinity,
sorted: true,
trailingComma: true,
compact: false,
iterableLimit: Infinity,
// getters should be true in assertEquals.
getters: true,
})
: `"${String(v).replace(/(?=["\\])/g, "\\")}"`;
}
+917
View File
@@ -0,0 +1,917 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
/** A library of assertion functions.
* If the assertion is false an `AssertionError` will be thrown which will
* result in pretty-printed diff of failing assertion.
*
* This module is browser compatible, but do not rely on good formatting of
* values for AssertionError messages in browsers.
*
* @module
*/
import { red, stripColor } from "../fmt/colors.ts";
import { buildMessage, diff, diffstr } from "./_diff.ts";
import { format } from "./_format.ts";
const CAN_NOT_DISPLAY = "[Cannot display]";
export class AssertionError extends Error {
override name = "AssertionError";
constructor(message: string) {
super(message);
}
}
function isKeyedCollection(x: unknown): x is Set<unknown> {
return [Symbol.iterator, "size"].every((k) => k in (x as Set<unknown>));
}
/**
* Deep equality comparison used in assertions
* @param c actual value
* @param d expected value
*/
export function equal(c: unknown, d: unknown): boolean {
const seen = new Map();
return (function compare(a: unknown, b: unknown): boolean {
// Have to render RegExp & Date for string comparison
// unless it's mistreated as object
if (
a &&
b &&
((a instanceof RegExp && b instanceof RegExp) ||
(a instanceof URL && b instanceof URL))
) {
return String(a) === String(b);
}
if (a instanceof Date && b instanceof Date) {
const aTime = a.getTime();
const bTime = b.getTime();
// Check for NaN equality manually since NaN is not
// equal to itself.
if (Number.isNaN(aTime) && Number.isNaN(bTime)) {
return true;
}
return aTime === bTime;
}
if (typeof a === "number" && typeof b === "number") {
return Number.isNaN(a) && Number.isNaN(b) || a === b;
}
if (Object.is(a, b)) {
return true;
}
if (a && typeof a === "object" && b && typeof b === "object") {
if (a && b && !constructorsEqual(a, b)) {
return false;
}
if (a instanceof WeakMap || b instanceof WeakMap) {
if (!(a instanceof WeakMap && b instanceof WeakMap)) return false;
throw new TypeError("cannot compare WeakMap instances");
}
if (a instanceof WeakSet || b instanceof WeakSet) {
if (!(a instanceof WeakSet && b instanceof WeakSet)) return false;
throw new TypeError("cannot compare WeakSet instances");
}
if (seen.get(a) === b) {
return true;
}
if (Object.keys(a || {}).length !== Object.keys(b || {}).length) {
return false;
}
seen.set(a, b);
if (isKeyedCollection(a) && isKeyedCollection(b)) {
if (a.size !== b.size) {
return false;
}
let unmatchedEntries = a.size;
for (const [aKey, aValue] of a.entries()) {
for (const [bKey, bValue] of b.entries()) {
/* Given that Map keys can be references, we need
* to ensure that they are also deeply equal */
if (
(aKey === aValue && bKey === bValue && compare(aKey, bKey)) ||
(compare(aKey, bKey) && compare(aValue, bValue))
) {
unmatchedEntries--;
break;
}
}
}
return unmatchedEntries === 0;
}
const merged = { ...a, ...b };
for (
const key of [
...Object.getOwnPropertyNames(merged),
...Object.getOwnPropertySymbols(merged),
]
) {
type Key = keyof typeof merged;
if (!compare(a && a[key as Key], b && b[key as Key])) {
return false;
}
if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) {
return false;
}
}
if (a instanceof WeakRef || b instanceof WeakRef) {
if (!(a instanceof WeakRef && b instanceof WeakRef)) return false;
return compare(a.deref(), b.deref());
}
return true;
}
return false;
})(c, d);
}
// deno-lint-ignore ban-types
function constructorsEqual(a: object, b: object) {
return a.constructor === b.constructor ||
a.constructor === Object && !b.constructor ||
!a.constructor && b.constructor === Object;
}
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
export function assert(expr: unknown, msg = ""): asserts expr {
if (!expr) {
throw new AssertionError(msg);
}
}
/** Make an assertion, error will be thrown if `expr` have truthy value. */
type Falsy = false | 0 | 0n | "" | null | undefined;
export function assertFalse(expr: unknown, msg = ""): asserts expr is Falsy {
if (expr) {
throw new AssertionError(msg);
}
}
/**
* Make an assertion that `actual` and `expected` are equal, deeply. If not
* deeply equal, then throw.
*
* Type parameter can be specified to ensure values under comparison have the same type.
*
* @example
* ```ts
* import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* Deno.test("example", function (): void {
* assertEquals("world", "world");
* assertEquals({ hello: "world" }, { hello: "world" });
* });
* ```
*/
export function assertEquals<T>(actual: T, expected: T, msg?: string) {
if (equal(actual, expected)) {
return;
}
const msgSuffix = msg ? `: ${msg}` : ".";
let message = `Values are not equal${msgSuffix}`;
const actualString = format(actual);
const expectedString = format(expected);
try {
const stringDiff = (typeof actual === "string") &&
(typeof expected === "string");
const diffResult = stringDiff
? diffstr(actual as string, expected as string)
: diff(actualString.split("\n"), expectedString.split("\n"));
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
message = `${message}\n${diffMsg}`;
} catch {
message = `${message}\n${red(CAN_NOT_DISPLAY)} + \n\n`;
}
throw new AssertionError(message);
}
/**
* Make an assertion that `actual` and `expected` are not equal, deeply.
* If not then throw.
*
* Type parameter can be specified to ensure values under comparison have the same type.
*
* @example
* ```ts
* import { assertNotEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* assertNotEquals<number>(1, 2)
* ```
*/
export function assertNotEquals<T>(actual: T, expected: T, msg?: string) {
if (!equal(actual, expected)) {
return;
}
let actualString: string;
let expectedString: string;
try {
actualString = String(actual);
} catch {
actualString = "[Cannot display]";
}
try {
expectedString = String(expected);
} catch {
expectedString = "[Cannot display]";
}
const msgSuffix = msg ? `: ${msg}` : ".";
throw new AssertionError(
`Expected actual: ${actualString} not to be: ${expectedString}${msgSuffix}`,
);
}
/**
* Make an assertion that `actual` and `expected` are strictly equal. If
* not then throw.
*
* @example
* ```ts
* import { assertStrictEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* Deno.test("isStrictlyEqual", function (): void {
* const a = {};
* const b = a;
* assertStrictEquals(a, b);
* });
*
* // This test fails
* Deno.test("isNotStrictlyEqual", function (): void {
* const a = {};
* const b = {};
* assertStrictEquals(a, b);
* });
* ```
*/
export function assertStrictEquals<T>(
actual: unknown,
expected: T,
msg?: string,
): asserts actual is T {
if (Object.is(actual, expected)) {
return;
}
const msgSuffix = msg ? `: ${msg}` : ".";
let message: string;
const actualString = format(actual);
const expectedString = format(expected);
if (actualString === expectedString) {
const withOffset = actualString
.split("\n")
.map((l) => ` ${l}`)
.join("\n");
message =
`Values have the same structure but are not reference-equal${msgSuffix}\n\n${
red(withOffset)
}\n`;
} else {
try {
const stringDiff = (typeof actual === "string") &&
(typeof expected === "string");
const diffResult = stringDiff
? diffstr(actual as string, expected as string)
: diff(actualString.split("\n"), expectedString.split("\n"));
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
message = `Values are not strictly equal${msgSuffix}\n${diffMsg}`;
} catch {
message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`;
}
}
throw new AssertionError(message);
}
/**
* Make an assertion that `actual` and `expected` are not strictly equal.
* If the values are strictly equal then throw.
*
* ```ts
* import { assertNotStrictEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* assertNotStrictEquals(1, 1)
* ```
*/
export function assertNotStrictEquals<T>(
actual: T,
expected: T,
msg?: string,
) {
if (!Object.is(actual, expected)) {
return;
}
const msgSuffix = msg ? `: ${msg}` : ".";
throw new AssertionError(
`Expected "actual" to be strictly unequal to: ${
format(actual)
}${msgSuffix}\n`,
);
}
/**
* Make an assertion that `actual` and `expected` are almost equal numbers through
* a given tolerance. It can be used to take into account IEEE-754 double-precision
* floating-point representation limitations.
* If the values are not almost equal then throw.
*
* @example
* ```ts
* import { assertAlmostEquals, assertThrows } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* assertAlmostEquals(0.1, 0.2);
*
* // Using a custom tolerance value
* assertAlmostEquals(0.1 + 0.2, 0.3, 1e-16);
* assertThrows(() => assertAlmostEquals(0.1 + 0.2, 0.3, 1e-17));
* ```
*/
export function assertAlmostEquals(
actual: number,
expected: number,
tolerance = 1e-7,
msg?: string,
) {
if (Object.is(actual, expected)) {
return;
}
const delta = Math.abs(expected - actual);
if (delta <= tolerance) {
return;
}
const msgSuffix = msg ? `: ${msg}` : ".";
const f = (n: number) => Number.isInteger(n) ? n : n.toExponential();
throw new AssertionError(
`Expected actual: "${f(actual)}" to be close to "${f(expected)}": \
delta "${f(delta)}" is greater than "${f(tolerance)}"${msgSuffix}`,
);
}
// deno-lint-ignore no-explicit-any
type AnyConstructor = new (...args: any[]) => any;
type GetConstructorType<T extends AnyConstructor> = T extends // deno-lint-ignore no-explicit-any
new (...args: any) => infer C ? C
: never;
/**
* Make an assertion that `obj` is an instance of `type`.
* If not then throw.
*/
export function assertInstanceOf<T extends AnyConstructor>(
actual: unknown,
expectedType: T,
msg = "",
): asserts actual is GetConstructorType<T> {
if (actual instanceof expectedType) return;
const msgSuffix = msg ? `: ${msg}` : ".";
const expectedTypeStr = expectedType.name;
let actualTypeStr = "";
if (actual === null) {
actualTypeStr = "null";
} else if (actual === undefined) {
actualTypeStr = "undefined";
} else if (typeof actual === "object") {
actualTypeStr = actual.constructor?.name ?? "Object";
} else {
actualTypeStr = typeof actual;
}
if (expectedTypeStr == actualTypeStr) {
msg =
`Expected object to be an instance of "${expectedTypeStr}"${msgSuffix}`;
} else if (actualTypeStr == "function") {
msg =
`Expected object to be an instance of "${expectedTypeStr}" but was not an instanced object${msgSuffix}`;
} else {
msg =
`Expected object to be an instance of "${expectedTypeStr}" but was "${actualTypeStr}"${msgSuffix}`;
}
throw new AssertionError(msg);
}
/**
* Make an assertion that `obj` is not an instance of `type`.
* If so, then throw.
*/
export function assertNotInstanceOf<A, T>(
actual: A,
// deno-lint-ignore no-explicit-any
unexpectedType: new (...args: any[]) => T,
msg?: string,
): asserts actual is Exclude<A, T> {
const msgSuffix = msg ? `: ${msg}` : ".";
msg =
`Expected object to not be an instance of "${typeof unexpectedType}"${msgSuffix}`;
assertFalse(actual instanceof unexpectedType, msg);
}
/**
* Make an assertion that actual is not null or undefined.
* If not then throw.
*/
export function assertExists<T>(
actual: T,
msg?: string,
): asserts actual is NonNullable<T> {
if (actual === undefined || actual === null) {
const msgSuffix = msg ? `: ${msg}` : ".";
msg =
`Expected actual: "${actual}" to not be null or undefined${msgSuffix}`;
throw new AssertionError(msg);
}
}
/**
* Make an assertion that actual includes expected. If not
* then throw.
*/
export function assertStringIncludes(
actual: string,
expected: string,
msg?: string,
) {
if (!actual.includes(expected)) {
const msgSuffix = msg ? `: ${msg}` : ".";
msg = `Expected actual: "${actual}" to contain: "${expected}"${msgSuffix}`;
throw new AssertionError(msg);
}
}
/**
* Make an assertion that `actual` includes the `expected` values.
* If not then an error will be thrown.
*
* Type parameter can be specified to ensure values under comparison have the same type.
*
* @example
* ```ts
* import { assertArrayIncludes } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* assertArrayIncludes<number>([1, 2], [2])
* ```
*/
export function assertArrayIncludes<T>(
actual: ArrayLike<T>,
expected: ArrayLike<T>,
msg?: string,
) {
const missing: unknown[] = [];
for (let i = 0; i < expected.length; i++) {
let found = false;
for (let j = 0; j < actual.length; j++) {
if (equal(expected[i], actual[j])) {
found = true;
break;
}
}
if (!found) {
missing.push(expected[i]);
}
}
if (missing.length === 0) {
return;
}
const msgSuffix = msg ? `: ${msg}` : ".";
msg = `Expected actual: "${format(actual)}" to include: "${
format(expected)
}"${msgSuffix}\nmissing: ${format(missing)}`;
throw new AssertionError(msg);
}
/**
* Make an assertion that `actual` match RegExp `expected`. If not
* then throw.
*/
export function assertMatch(
actual: string,
expected: RegExp,
msg?: string,
) {
if (!expected.test(actual)) {
const msgSuffix = msg ? `: ${msg}` : ".";
msg = `Expected actual: "${actual}" to match: "${expected}"${msgSuffix}`;
throw new AssertionError(msg);
}
}
/**
* Make an assertion that `actual` not match RegExp `expected`. If match
* then throw.
*/
export function assertNotMatch(
actual: string,
expected: RegExp,
msg?: string,
) {
if (expected.test(actual)) {
const msgSuffix = msg ? `: ${msg}` : ".";
msg =
`Expected actual: "${actual}" to not match: "${expected}"${msgSuffix}`;
throw new AssertionError(msg);
}
}
/**
* Make an assertion that `actual` object is a subset of `expected` object, deeply.
* If not, then throw.
*/
export function assertObjectMatch(
// deno-lint-ignore no-explicit-any
actual: Record<PropertyKey, any>,
expected: Record<PropertyKey, unknown>,
msg?: string,
) {
type loose = Record<PropertyKey, unknown>;
function filter(a: loose, b: loose) {
const seen = new WeakMap();
return fn(a, b);
function fn(a: loose, b: loose): loose {
// Prevent infinite loop with circular references with same filter
if ((seen.has(a)) && (seen.get(a) === b)) {
return a;
}
seen.set(a, b);
// Filter keys and symbols which are present in both actual and expected
const filtered = {} as loose;
const entries = [
...Object.getOwnPropertyNames(a),
...Object.getOwnPropertySymbols(a),
]
.filter((key) => key in b)
.map((key) => [key, a[key as string]]) as Array<[string, unknown]>;
for (const [key, value] of entries) {
// On array references, build a filtered array and filter nested objects inside
if (Array.isArray(value)) {
const subset = (b as loose)[key];
if (Array.isArray(subset)) {
filtered[key] = fn({ ...value }, { ...subset });
continue;
}
} // On regexp references, keep value as it to avoid loosing pattern and flags
else if (value instanceof RegExp) {
filtered[key] = value;
continue;
} // On nested objects references, build a filtered object recursively
else if (typeof value === "object") {
const subset = (b as loose)[key];
if ((typeof subset === "object") && (subset)) {
// When both operands are maps, build a filtered map with common keys and filter nested objects inside
if ((value instanceof Map) && (subset instanceof Map)) {
filtered[key] = new Map(
[...value].filter(([k]) => subset.has(k)).map((
[k, v],
) => [k, typeof v === "object" ? fn(v, subset.get(k)) : v]),
);
continue;
}
// When both operands are set, build a filtered set with common values
if ((value instanceof Set) && (subset instanceof Set)) {
filtered[key] = new Set([...value].filter((v) => subset.has(v)));
continue;
}
filtered[key] = fn(value as loose, subset as loose);
continue;
}
}
filtered[key] = value;
}
return filtered;
}
}
return assertEquals(
// get the intersection of "actual" and "expected"
// side effect: all the instances' constructor field is "Object" now.
filter(actual, expected),
// set (nested) instances' constructor field to be "Object" without changing expected value.
// see https://github.com/denoland/deno_std/pull/1419
filter(expected, expected),
msg,
);
}
/**
* Forcefully throws a failed assertion
*/
export function fail(msg?: string): never {
const msgSuffix = msg ? `: ${msg}` : ".";
assert(false, `Failed assertion${msgSuffix}`);
}
/**
* Make an assertion that `error` is an `Error`.
* If not then an error will be thrown.
* An error class and a string that should be included in the
* error message can also be asserted.
*/
export function assertIsError<E extends Error = Error>(
error: unknown,
// deno-lint-ignore no-explicit-any
ErrorClass?: new (...args: any[]) => E,
msgIncludes?: string,
msg?: string,
): asserts error is E {
const msgSuffix = msg ? `: ${msg}` : ".";
if (error instanceof Error === false) {
throw new AssertionError(
`Expected "error" to be an Error object${msgSuffix}}`,
);
}
if (ErrorClass && !(error instanceof ErrorClass)) {
msg = `Expected error to be instance of "${ErrorClass.name}", but was "${
typeof error === "object" ? error?.constructor?.name : "[not an object]"
}"${msgSuffix}`;
throw new AssertionError(msg);
}
if (
msgIncludes && (!(error instanceof Error) ||
!stripColor(error.message).includes(stripColor(msgIncludes)))
) {
msg = `Expected error message to include "${msgIncludes}", but got "${
error instanceof Error ? error.message : "[not an Error]"
}"${msgSuffix}`;
throw new AssertionError(msg);
}
}
/**
* Executes a function, expecting it to throw. If it does not, then it
* throws.
*
* @example
* ```ts
* import { assertThrows } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* Deno.test("doesThrow", function (): void {
* assertThrows((): void => {
* throw new TypeError("hello world!");
* });
* });
*
* // This test will not pass.
* Deno.test("fails", function (): void {
* assertThrows((): void => {
* console.log("Hello world");
* });
* });
* ```
*/
export function assertThrows(
fn: () => unknown,
msg?: string,
): unknown;
/**
* Executes a function, expecting it to throw. If it does not, then it
* throws. An error class and a string that should be included in the
* error message can also be asserted.
*
* @example
*
* ```ts
* import { assertThrows } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* Deno.test("doesThrow", function (): void {
* assertThrows((): void => {
* throw new TypeError("hello world!");
* }, TypeError);
* assertThrows(
* (): void => {
* throw new TypeError("hello world!");
* },
* TypeError,
* "hello",
* );
* });
*
* // This test will not pass.
* Deno.test("fails", function (): void {
* assertThrows((): void => {
* console.log("Hello world");
* });
* });
* ```
*/
export function assertThrows<E extends Error = Error>(
fn: () => unknown,
// deno-lint-ignore no-explicit-any
ErrorClass: new (...args: any[]) => E,
msgIncludes?: string,
msg?: string,
): E;
export function assertThrows<E extends Error = Error>(
fn: () => unknown,
errorClassOrMsg?:
// deno-lint-ignore no-explicit-any
| (new (...args: any[]) => E)
| string,
msgIncludesOrMsg?: string,
msg?: string,
): E | Error | unknown {
// deno-lint-ignore no-explicit-any
let ErrorClass: (new (...args: any[]) => E) | undefined = undefined;
let msgIncludes: string | undefined = undefined;
let err;
if (typeof errorClassOrMsg !== "string") {
if (
errorClassOrMsg === undefined ||
errorClassOrMsg.prototype instanceof Error ||
errorClassOrMsg.prototype === Error.prototype
) {
// deno-lint-ignore no-explicit-any
ErrorClass = errorClassOrMsg as new (...args: any[]) => E;
msgIncludes = msgIncludesOrMsg;
} else {
msg = msgIncludesOrMsg;
}
} else {
msg = errorClassOrMsg;
}
let doesThrow = false;
const msgSuffix = msg ? `: ${msg}` : ".";
try {
fn();
} catch (error) {
if (ErrorClass) {
if (error instanceof Error === false) {
throw new AssertionError(`A non-Error object was thrown${msgSuffix}`);
}
assertIsError(
error,
ErrorClass,
msgIncludes,
msg,
);
}
err = error;
doesThrow = true;
}
if (!doesThrow) {
msg = `Expected function to throw${msgSuffix}`;
throw new AssertionError(msg);
}
return err;
}
/**
* Executes a function which returns a promise, expecting it to reject.
*
* @example
* ```ts
* import { assertRejects } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* Deno.test("doesThrow", async function () {
* await assertRejects(
* async () => {
* throw new TypeError("hello world!");
* },
* );
* await assertRejects(
* async () => {
* return Promise.reject(new Error());
* },
* );
* });
*
* // This test will not pass.
* Deno.test("fails", async function () {
* await assertRejects(
* async () => {
* console.log("Hello world");
* },
* );
* });
* ```
*/
export function assertRejects(
fn: () => PromiseLike<unknown>,
msg?: string,
): Promise<unknown>;
/**
* Executes a function which returns a promise, expecting it to reject.
* If it does not, then it throws. An error class and a string that should be
* included in the error message can also be asserted.
*
* @example
* ```ts
* import { assertRejects } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
*
* Deno.test("doesThrow", async function () {
* await assertRejects(async () => {
* throw new TypeError("hello world!");
* }, TypeError);
* await assertRejects(
* async () => {
* throw new TypeError("hello world!");
* },
* TypeError,
* "hello",
* );
* });
*
* // This test will not pass.
* Deno.test("fails", async function () {
* await assertRejects(
* async () => {
* console.log("Hello world");
* },
* );
* });
* ```
*/
export function assertRejects<E extends Error = Error>(
fn: () => PromiseLike<unknown>,
// deno-lint-ignore no-explicit-any
ErrorClass: new (...args: any[]) => E,
msgIncludes?: string,
msg?: string,
): Promise<E>;
export async function assertRejects<E extends Error = Error>(
fn: () => PromiseLike<unknown>,
errorClassOrMsg?:
// deno-lint-ignore no-explicit-any
| (new (...args: any[]) => E)
| string,
msgIncludesOrMsg?: string,
msg?: string,
): Promise<E | Error | unknown> {
// deno-lint-ignore no-explicit-any
let ErrorClass: (new (...args: any[]) => E) | undefined = undefined;
let msgIncludes: string | undefined = undefined;
let err;
if (typeof errorClassOrMsg !== "string") {
if (
errorClassOrMsg === undefined ||
errorClassOrMsg.prototype instanceof Error ||
errorClassOrMsg.prototype === Error.prototype
) {
// deno-lint-ignore no-explicit-any
ErrorClass = errorClassOrMsg as new (...args: any[]) => E;
msgIncludes = msgIncludesOrMsg;
}
} else {
msg = errorClassOrMsg;
}
let doesThrow = false;
let isPromiseReturned = false;
const msgSuffix = msg ? `: ${msg}` : ".";
try {
const possiblePromise = fn();
if (
possiblePromise &&
typeof possiblePromise === "object" &&
typeof possiblePromise.then === "function"
) {
isPromiseReturned = true;
await possiblePromise;
}
} catch (error) {
if (!isPromiseReturned) {
throw new AssertionError(
`Function throws when expected to reject${msgSuffix}`,
);
}
if (ErrorClass) {
if (error instanceof Error === false) {
throw new AssertionError(`A non-Error object was rejected${msgSuffix}`);
}
assertIsError(
error,
ErrorClass,
msgIncludes,
msg,
);
}
err = error;
doesThrow = true;
}
if (!doesThrow) {
throw new AssertionError(
`Expected function to reject${msgSuffix}`,
);
}
return err;
}
/** Use this to stub out methods that will throw when invoked. */
export function unimplemented(msg?: string): never {
const msgSuffix = msg ? `: ${msg}` : ".";
throw new AssertionError(`Unimplemented${msgSuffix}`);
}
/** Use this to assert unreachable code. */
export function unreachable(): never {
throw new AssertionError("unreachable");
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** See the Contributing > Types section in the README for an explanation of this file. */
/** An abstract interface which when implemented provides an interface to read bytes into an array buffer asynchronously. */
export interface Reader {
/** Reads up to `p.byteLength` bytes into `p`. It resolves to the number of
* bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error
* encountered. Even if `read()` resolves to `n` < `p.byteLength`, it may
* use all of `p` as scratch space during the call. If some data is
* available but not `p.byteLength` bytes, `read()` conventionally resolves
* to what is available instead of waiting for more.
*
* When `read()` encounters end-of-file condition, it resolves to EOF
* (`null`).
*
* When `read()` encounters an error, it rejects with an error.
*
* Callers should always process the `n` > `0` bytes returned before
* considering the EOF (`null`). Doing so correctly handles I/O errors that
* happen after reading some bytes and also both of the allowed EOF
* behaviors.
*
* Implementations should not retain a reference to `p`.
*
* Use iterateReader() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a Reader into an
* AsyncIterator.
*/
read(p: Uint8Array): Promise<number | null>;
}
/** An abstract interface which when implemented provides an interface to read bytes into an array buffer synchronously. */
export interface ReaderSync {
/** Reads up to `p.byteLength` bytes into `p`. It resolves to the number
* of bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error
* encountered. Even if `read()` returns `n` < `p.byteLength`, it may use
* all of `p` as scratch space during the call. If some data is available
* but not `p.byteLength` bytes, `read()` conventionally returns what is
* available instead of waiting for more.
*
* When `readSync()` encounters end-of-file condition, it returns EOF
* (`null`).
*
* When `readSync()` encounters an error, it throws with an error.
*
* Callers should always process the `n` > `0` bytes returned before
* considering the EOF (`null`). Doing so correctly handles I/O errors that happen
* after reading some bytes and also both of the allowed EOF behaviors.
*
* Implementations should not retain a reference to `p`.
*
* Use iterateReaderSync() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a ReaderSync
* into an Iterator.
*/
readSync(p: Uint8Array): number | null;
}
/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource asynchronously. */
export interface Writer {
/** Writes `p.byteLength` bytes from `p` to the underlying data stream. It
* resolves to the number of bytes written from `p` (`0` <= `n` <=
* `p.byteLength`) or reject with the error encountered that caused the
* write to stop early. `write()` must reject with a non-null error if
* would resolve to `n` < `p.byteLength`. `write()` must not modify the
* slice data, even temporarily.
*
* Implementations should not retain a reference to `p`.
*/
write(p: Uint8Array): Promise<number>;
}
/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource synchronously. */
export interface WriterSync {
/** Writes `p.byteLength` bytes from `p` to the underlying data
* stream. It returns the number of bytes written from `p` (`0` <= `n`
* <= `p.byteLength`) and any error encountered that caused the write to
* stop early. `writeSync()` must throw a non-null error if it returns `n` <
* `p.byteLength`. `writeSync()` must not modify the slice data, even
* temporarily.
*
* Implementations should not retain a reference to `p`.
*/
writeSync(p: Uint8Array): number;
}
/** An abstract interface which when implemented provides an interface to close files/resources that were previously opened. */
export interface Closer {
/** Closes the resource, "freeing" the backing file/resource. */
close(): void;
}