@rcompat/test 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,362 @@
1
+ # @rcompat/test
2
+
3
+ Testing library for JavaScript runtimes.
4
+
5
+ ## What is @rcompat/test?
6
+
7
+ A cross-runtime testing library with a fluent assertion API. Provides deep
8
+ equality checks, type assertions, and exception testing. Designed to work
9
+ with the `proby` test runner. Works consistently across Node, Deno, and Bun.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @rcompat/test
15
+ ```
16
+
17
+ ```bash
18
+ pnpm add @rcompat/test
19
+ ```
20
+
21
+ ```bash
22
+ yarn add @rcompat/test
23
+ ```
24
+
25
+ ```bash
26
+ bun add @rcompat/test
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Writing tests
32
+
33
+ Create a `.spec.ts` or `.spec.js` file:
34
+
35
+ ```js
36
+ import test from "@rcompat/test";
37
+
38
+ test.case("addition works", assert => {
39
+ assert(1 + 1).equals(2);
40
+ });
41
+
42
+ test.case("string concatenation", assert => {
43
+ assert("hello" + " " + "world").equals("hello world");
44
+ });
45
+ ```
46
+
47
+ ### Running tests
48
+
49
+ Use the `proby` test runner:
50
+
51
+ ```bash
52
+ npx proby
53
+ ```
54
+
55
+ ### Equality assertions
56
+
57
+ ```js
58
+ import test from "@rcompat/test";
59
+
60
+ test.case("equals", assert => {
61
+ // primitives
62
+ assert(42).equals(42);
63
+ assert("hello").equals("hello");
64
+ assert(true).equals(true);
65
+
66
+ // objects (deep equality)
67
+ assert({ a: 1, b: 2 }).equals({ a: 1, b: 2 });
68
+ assert([1, 2, 3]).equals([1, 2, 3]);
69
+
70
+ // nested structures
71
+ assert({ users: [{ name: "Alice" }] }).equals({
72
+ users: [{ name: "Alice" }],
73
+ });
74
+ });
75
+
76
+ test.case("nequals", assert => {
77
+ assert(1).nequals(2);
78
+ assert("foo").nequals("bar");
79
+ assert({ a: 1 }).nequals({ a: 2 });
80
+ });
81
+ ```
82
+
83
+ ### Boolean assertions
84
+
85
+ ```js
86
+ import test from "@rcompat/test";
87
+
88
+ test.case("boolean checks", assert => {
89
+ assert(1 === 1).true();
90
+ assert(1 === 2).false();
91
+ assert(null).null();
92
+ assert(undefined).undefined();
93
+ });
94
+ ```
95
+
96
+ ### Instance assertions
97
+
98
+ ```js
99
+ import test from "@rcompat/test";
100
+
101
+ test.case("instanceof", assert => {
102
+ assert(new Date()).instance(Date);
103
+ assert(new Map()).instance(Map);
104
+ assert([1, 2, 3]).instance(Array);
105
+ });
106
+ ```
107
+
108
+ ### Exception assertions
109
+
110
+ ```js
111
+ import test from "@rcompat/test";
112
+
113
+ test.case("throws", assert => {
114
+ // check that function throws
115
+ assert(() => {
116
+ throw new Error("oops");
117
+ }).throws();
118
+
119
+ // Check specific error message
120
+ assert(() => {
121
+ throw new Error("invalid input");
122
+ }).throws("invalid input");
123
+ });
124
+
125
+ test.case("tries (does not throw)", assert => {
126
+ assert(() => {
127
+ return 42;
128
+ }).tries();
129
+ });
130
+ ```
131
+
132
+ ### Type assertions
133
+
134
+ ```js
135
+ import test from "@rcompat/test";
136
+
137
+ test.case("type checking", assert => {
138
+ // compile-time type checks
139
+ assert<string>().type<string>();
140
+ assert("hello").type<string>();
141
+
142
+ // check types don't match
143
+ assert<string>().nottype<number>();
144
+ assert(42).nottype<string>();
145
+
146
+ // literal types
147
+ assert<"foo">().type<"foo">();
148
+ assert<"foo">().nottype<"bar">();
149
+ });
150
+ ```
151
+
152
+ ### Async tests
153
+
154
+ ```js
155
+ import test from "@rcompat/test";
156
+
157
+ test.case("async operations", async assert => {
158
+ const result = await Promise.resolve(42);
159
+ assert(result).equals(42);
160
+ });
161
+
162
+ test.case("fetch data", async assert => {
163
+ const response = await fetch("https://api.example.com/data");
164
+ assert(response.ok).true();
165
+ });
166
+ ```
167
+
168
+ ### Cleanup with ended
169
+
170
+ ```js
171
+ import test from "@rcompat/test";
172
+
173
+ test.case("database test", async assert => {
174
+ const db = await openDatabase();
175
+ const user = await db.createUser({ name: "Alice" });
176
+ assert(user.name).equals("Alice");
177
+ });
178
+
179
+ test.ended(async () => {
180
+ // cleanup after all tests in this file
181
+ await closeDatabase();
182
+ });
183
+ ```
184
+
185
+ ## API Reference
186
+
187
+ ### `test.case`
188
+
189
+ ```ts
190
+ test.case(name: string, body: (assert: Asserter) => void | Promise<void>): void;
191
+ ```
192
+
193
+ Define a test case.
194
+
195
+ | Parameter | Type | Description |
196
+ | --------- | ---------- | ------------------------------------- |
197
+ | `name` | `string` | Test case name |
198
+ | `body` | `function` | Test function receiving assert helper |
199
+
200
+ ### `test.ended`
201
+
202
+ ```ts
203
+ test.ended(callback: () => void | Promise<void>): void;
204
+ ```
205
+
206
+ Register a cleanup callback to run after all tests in the file.
207
+
208
+ ### `Asserter`
209
+
210
+ ```ts
211
+ type Asserter = <T>(actual?: T) => Assert<T>;
212
+ ```
213
+
214
+ The assert function passed to test cases.
215
+
216
+ ### `Assert<T>`
217
+
218
+ | Method | Description |
219
+ | ----------------------- | ----------------------------------------- |
220
+ | `equals(expected)` | Deep equality check |
221
+ | `nequals(expected)` | Deep inequality check |
222
+ | `true()` | Assert value is `true` |
223
+ | `false()` | Assert value is `false` |
224
+ | `null()` | Assert value is `null` |
225
+ | `undefined()` | Assert value is `undefined` |
226
+ | `instance(constructor)` | Assert value is instance of class |
227
+ | `throws(message?)` | Assert function throws (optional message) |
228
+ | `tries()` | Assert function does not throw |
229
+ | `type<T>()` | Compile-time type assertion |
230
+ | `nottype<T>()` | Compile-time negative type assertion |
231
+ | `pass()` | Manually pass the assertion |
232
+ | `fail(reason?)` | Manually fail the assertion |
233
+
234
+ ### Utilities
235
+
236
+ #### `equals`
237
+
238
+ ```ts
239
+ import equals from "@rcompat/test/equals";
240
+
241
+ equals(a: unknown, b: unknown): boolean;
242
+ ```
243
+
244
+ Deep equality check supporting primitives, objects, arrays, maps, sets, dates.
245
+
246
+ #### `any`
247
+
248
+ ```ts
249
+ import any from "@rcompat/test/any";
250
+
251
+ any(value: unknown): never;
252
+ ```
253
+
254
+ Cast any value to `never` type for testing purposes.
255
+
256
+ #### `undef`
257
+
258
+ ```ts
259
+ import undef from "@rcompat/test/undef";
260
+
261
+ const value: never = undef;
262
+ ```
263
+
264
+ Pre-cast `undefined` value typed as `never`.
265
+
266
+ #### `E`
267
+
268
+ ```ts
269
+ import E from "@rcompat/test/E";
270
+
271
+ E(error: unknown): { message: string };
272
+ ```
273
+
274
+ Extract error message from unknown error type.
275
+
276
+ ## Examples
277
+
278
+ ### Testing a utility function
279
+
280
+ ```js
281
+ // sum.ts
282
+ export default (a, b) => a + b;
283
+
284
+ // sum.spec.ts
285
+ import test from "@rcompat/test";
286
+ import sum from "./sum.js";
287
+
288
+ test.case("sum adds two numbers", assert => {
289
+ assert(sum(1, 2)).equals(3);
290
+ assert(sum(-1, 1)).equals(0);
291
+ assert(sum(0, 0)).equals(0);
292
+ });
293
+ ```
294
+
295
+ ### Testing a class
296
+
297
+ ```js
298
+ import test from "@rcompat/test";
299
+
300
+ class Calculator {
301
+ #value = 0;
302
+ add(n) {
303
+ this.#value += n;
304
+ return this;
305
+ }
306
+ subtract(n) {
307
+ this.#value -= n;
308
+ return this;
309
+ }
310
+ get value() {
311
+ return this.#value;
312
+ }
313
+ }
314
+
315
+ test.case("calculator operations", assert => {
316
+ const calc = new Calculator();
317
+ calc.add(10).subtract(3);
318
+ assert(calc.value).equals(7);
319
+ });
320
+
321
+ test.case("calculator is instance", assert => {
322
+ assert(new Calculator()).instance(Calculator);
323
+ });
324
+ ```
325
+
326
+ ### Testing error handling
327
+
328
+ ```js
329
+ import test from "@rcompat/test";
330
+
331
+ function divide(a, b) {
332
+ if (b === 0) throw new Error("Division by zero");
333
+ return a / b;
334
+ }
335
+
336
+ test.case("divide throws on zero", assert => {
337
+ assert(() => divide(10, 0)).throws("Division by zero");
338
+ });
339
+
340
+ test.case("divide works normally", assert => {
341
+ assert(() => divide(10, 2)).tries();
342
+ assert(divide(10, 2)).equals(5);
343
+ });
344
+ ```
345
+
346
+ ## Cross-Runtime Compatibility
347
+
348
+ | Runtime | Supported |
349
+ | ------- | --------- |
350
+ | Node.js | ✓ |
351
+ | Deno | ✓ |
352
+ | Bun | ✓ |
353
+
354
+ No configuration required — just import and use.
355
+
356
+ ## License
357
+
358
+ MIT
359
+
360
+ ## Contributing
361
+
362
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md) in the repository root.
@@ -1,7 +1,7 @@
1
1
  import type UnknownFunction from "@rcompat/type/UnknownFunction";
2
2
  declare const _default: {
3
- is: (x: unknown) => x is UnknownFunction;
4
3
  equal: <T extends UnknownFunction>(x: T, y: T) => boolean;
4
+ is: (x: unknown) => x is UnknownFunction;
5
5
  };
6
6
  export default _default;
7
7
  //# sourceMappingURL=fn.d.ts.map
@@ -1,4 +1,4 @@
1
1
  const is = (x) => typeof x === "function";
2
2
  const equal = (x, y) => x.length === y.length;
3
- export default { is, equal };
3
+ export default { equal, is };
4
4
  //# sourceMappingURL=fn.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rcompat/test",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Standard library testing",
5
5
  "bugs": "https://github.com/rcompat/rcompat/issues",
6
6
  "license": "MIT",
@@ -15,7 +15,7 @@
15
15
  "directory": "packages/test"
16
16
  },
17
17
  "devDependencies": {
18
- "@rcompat/fs": "^0.17.0"
18
+ "@rcompat/fs": "^0.23.0"
19
19
  },
20
20
  "type": "module",
21
21
  "imports": {