UNPKG

5.35 kBJavaScriptView Raw
1import { dequal } from 'dequal';
2import { compare, lines } from 'uvu/diff';
3
4function dedent(str) {
5 str = str.replace(/\r?\n/g, '\n');
6 let arr = str.match(/^[ \t]*(?=\S)/gm);
7 let i = 0, min = 1/0, len = (arr||[]).length;
8 for (; i < len; i++) min = Math.min(min, arr[i].length);
9 return len && min ? str.replace(new RegExp(`^[ \\t]{${min}}`, 'gm'), '') : str;
10}
11
12export class Assertion extends Error {
13 constructor(opts={}) {
14 super(opts.message);
15 this.name = 'Assertion';
16 this.code = 'ERR_ASSERTION';
17 if (Error.captureStackTrace) {
18 Error.captureStackTrace(this, this.constructor);
19 }
20 this.details = opts.details || false;
21 this.generated = !!opts.generated;
22 this.operator = opts.operator;
23 this.expects = opts.expects;
24 this.actual = opts.actual;
25 }
26}
27
28function assert(bool, actual, expects, operator, detailer, backup, msg) {
29 if (bool) return;
30 let message = msg || backup;
31 if (msg instanceof Error) throw msg;
32 let details = detailer && detailer(actual, expects);
33 throw new Assertion({ actual, expects, operator, message, details, generated: !msg });
34}
35
36export function ok(val, msg) {
37 assert(!!val, false, true, 'ok', false, 'Expected value to be truthy', msg);
38}
39
40export function is(val, exp, msg) {
41 assert(val === exp, val, exp, 'is', compare, 'Expected values to be strictly equal:', msg);
42}
43
44export function equal(val, exp, msg) {
45 assert(dequal(val, exp), val, exp, 'equal', compare, 'Expected values to be deeply equal:', msg);
46}
47
48export function unreachable(msg) {
49 assert(false, true, false, 'unreachable', false, 'Expected not to be reached!', msg);
50}
51
52export function type(val, exp, msg) {
53 let tmp = typeof val;
54 assert(tmp === exp, tmp, exp, 'type', false, `Expected "${tmp}" to be "${exp}"`, msg);
55}
56
57export function instance(val, exp, msg) {
58 let name = '`' + (exp.name || exp.constructor.name) + '`';
59 assert(val instanceof exp, val, exp, 'instance', false, `Expected value to be an instance of ${name}`, msg);
60}
61
62export function match(val, exp, msg) {
63 if (typeof exp === 'string') {
64 assert(val.includes(exp), val, exp, 'match', false, `Expected value to include "${exp}" substring`, msg);
65 } else {
66 assert(exp.test(val), val, exp, 'match', false, `Expected value to match \`${String(exp)}\` pattern`, msg);
67 }
68}
69
70export function snapshot(val, exp, msg) {
71 val=dedent(val); exp=dedent(exp);
72 assert(val === exp, val, exp, 'snapshot', lines, 'Expected value to match snapshot:', msg);
73}
74
75const lineNums = (x, y) => lines(x, y, 1);
76export function fixture(val, exp, msg) {
77 val=dedent(val); exp=dedent(exp);
78 assert(val === exp, val, exp, 'fixture', lineNums, 'Expected value to match fixture:', msg);
79}
80
81export function throws(blk, exp, msg) {
82 if (!msg && typeof exp === 'string') {
83 msg = exp; exp = null;
84 }
85
86 try {
87 blk();
88 assert(false, false, true, 'throws', false, 'Expected function to throw', msg);
89 } catch (err) {
90 if (err instanceof Assertion) throw err;
91
92 if (typeof exp === 'function') {
93 assert(exp(err), false, true, 'throws', false, 'Expected function to throw matching exception', msg);
94 } else if (exp instanceof RegExp) {
95 assert(exp.test(err.message), false, true, 'throws', false, `Expected function to throw exception matching \`${String(exp)}\` pattern`, msg);
96 }
97 }
98}
99
100// ---
101
102export function not(val, msg) {
103 assert(!val, true, false, 'not', false, 'Expected value to be falsey', msg);
104}
105
106not.ok = not;
107
108is.not = function (val, exp, msg) {
109 assert(val !== exp, val, exp, 'is.not', false, 'Expected values not to be strictly equal', msg);
110}
111
112not.equal = function (val, exp, msg) {
113 assert(!dequal(val, exp), val, exp, 'not.equal', false, 'Expected values not to be deeply equal', msg);
114}
115
116not.type = function (val, exp, msg) {
117 let tmp = typeof val;
118 assert(tmp !== exp, tmp, exp, 'not.type', false, `Expected "${tmp}" not to be "${exp}"`, msg);
119}
120
121not.instance = function (val, exp, msg) {
122 let name = '`' + (exp.name || exp.constructor.name) + '`';
123 assert(!(val instanceof exp), val, exp, 'not.instance', false, `Expected value not to be an instance of ${name}`, msg);
124}
125
126not.snapshot = function (val, exp, msg) {
127 val=dedent(val); exp=dedent(exp);
128 assert(val !== exp, val, exp, 'not.snapshot', false, 'Expected value not to match snapshot', msg);
129}
130
131not.fixture = function (val, exp, msg) {
132 val=dedent(val); exp=dedent(exp);
133 assert(val !== exp, val, exp, 'not.fixture', false, 'Expected value not to match fixture', msg);
134}
135
136not.match = function (val, exp, msg) {
137 if (typeof exp === 'string') {
138 assert(!val.includes(exp), val, exp, 'not.match', false, `Expected value not to include "${exp}" substring`, msg);
139 } else {
140 assert(!exp.test(val), val, exp, 'not.match', false, `Expected value not to match \`${String(exp)}\` pattern`, msg);
141 }
142}
143
144not.throws = function (blk, exp, msg) {
145 if (!msg && typeof exp === 'string') {
146 msg = exp; exp = null;
147 }
148
149 try {
150 blk();
151 } catch (err) {
152 if (typeof exp === 'function') {
153 assert(!exp(err), true, false, 'not.throws', false, 'Expected function not to throw matching exception', msg);
154 } else if (exp instanceof RegExp) {
155 assert(!exp.test(err.message), true, false, 'not.throws', false, `Expected function not to throw exception matching \`${String(exp)}\` pattern`, msg);
156 } else if (!exp) {
157 assert(false, true, false, 'not.throws', false, 'Expected function not to throw', msg);
158 }
159 }
160}