vitest-pool-assemblyscript 0.3.0 → 0.4.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 CHANGED
@@ -141,7 +141,8 @@ npx vitest run
141
141
  - Suite and test definition using `describe()` and `test()` in AssemblyScript
142
142
  - Inline test option configuration for common vitest options: `timeout`, `retry`, `skip`, `only`, `fails`
143
143
  - Assertion matching API based on vitest/jest `expect()` API
144
- - `.not`, `toBe`, `toBeCloseTo`, `toEqual` (with caveats*), `toStrictEqual`, `toHaveLength`, `toThrowError`, `toBeTruthy`, `toBeFalsy`, `toBeNull`, `toBeNullable`, `toBeNaN` - See [Matchers API](docs/matchers-api.md) for details and differences from JavaScript
144
+ - `.not`, `toBe`, `toBeCloseTo`, `toEqual` (with caveats*), `toStrictEqual`, `toBeGreaterThan`, `toBeGreaterThanOrEqual`, `toBeLessThan`, `toBeLessThanOrEqual`, `toHaveLength`, `toThrowError`, `toBeTruthy`, `toBeFalsy`, `toBeNull`, `toBeNullable`, `toBeNaN`
145
+ - See [Matchers API](docs/matchers-api.md) for details and differences from JavaScript
145
146
  - Highlighted diffs for assertion and runtime failures, which point to source code
146
147
  - Source-mapped WASM error stack traces (accurate AssemblyScript source `function file:line:column`)
147
148
  - AssemblyScript console output captured and provided to vitest for display
@@ -286,12 +287,13 @@ These are known limitations which are currently being worked on.
286
287
  - Lifecycle hooks (`beforeEach`, `afterEach`, `beforeAll`, `afterAll`)
287
288
  - Watch mode: re-run applicable tests on source file changes
288
289
  - `toEqual` reflection for deep equality inspection of user objects
290
+ - `describe.for/each` and `test.for/each`
289
291
  - expect.soft to prevent fail-fast behavior
290
292
  - Allow delegating JS/TS to istanbul coverage provider in addition to v8
291
293
  - Maybe: Per-file compilation setting override
292
294
 
293
295
  **Epic: Expand expect matcher API**
294
- - Planned: `toBeDefined`, `toBeUndefined`, `toBeGreaterThan`, `toBeGreaterThanOrEqual`, `toBeLessThan`, `toBeLessThanOrEqual`, `toContain`, `toContainEqual`
296
+ - Planned: `toBeDefined`, `toBeUndefined`, `toContain`, `toContainEqual`
295
297
  - Likely: `toBeOneOf`, `toBeTypeOf`, `toBeInstanceOf`, `toHaveProperty`, `toMatch`
296
298
 
297
299
  **Epic: Spy and Mock**
@@ -309,10 +311,10 @@ These are known limitations which are currently being worked on.
309
311
  ## Performance
310
312
 
311
313
  Efforts have been made to compile and run tests as quickly as possible:
312
- - Separate compile and test execution threads for quicker startup and respawn
314
+ - Separate compile and test execution thread pools, workers, and runners for quicker startup and respawn
313
315
  - Compile threads tuned to take advantage of significant Node V8 engine warmup time savings on consecutive AssemblyScript compilations
314
316
  - In-memory compiled files and source maps to eliminate intermediate disk I/O
315
- - Enforced hard timeouts for long-running WASM via thread termination, with intelligent resume
317
+ - Enforced hard timeouts for long-running WASM tests via thread termination, with intelligent resume
316
318
 
317
319
  As such, it is capable of compiling dozens of test files comprising hundreds of tests in a few seconds.
318
320
 
@@ -219,6 +219,133 @@ export function equals<T, U>(actual: T, expected: U): bool {
219
219
  );
220
220
  }
221
221
 
222
+ export enum InequalityOperation {
223
+ LessThan,
224
+ LessThanOrEqual,
225
+ GreaterThan,
226
+ GreaterThanOrEqual,
227
+ }
228
+
229
+ /**
230
+ * Applies an inequality operation to two values of the same promoted type.
231
+ * Handles <, <=, >, >= for any type that supports these operators (numbers, strings).
232
+ */
233
+ function applyInequalityOp<T>(a: T, b: T, op: InequalityOperation): bool {
234
+ if (op == InequalityOperation.LessThan) return a < b;
235
+ if (op == InequalityOperation.LessThanOrEqual) return a <= b;
236
+ if (op == InequalityOperation.GreaterThan) return a > b;
237
+ return a >= b; // GreaterThanOrEqual
238
+ }
239
+
240
+ /**
241
+ * Generic inequality comparison. Promotes both values to a common type and applies
242
+ * the requested inequality operation.
243
+ *
244
+ * Strings are compared lexicographically. Booleans are treated as integers (true=1, false=0).
245
+ * Non-string references are not comparable and throw an error.
246
+ *
247
+ * Cross-sign integer comparisons are supported (more permissive than AS's own operators)
248
+ * via signed-negative early return + u64 promotion. Float/integer combinations where the
249
+ * float's mantissa cannot losslessly represent the integer type's range are rejected,
250
+ * matching AS compiler behavior. See docs/matcher-research.md for details.
251
+ */
252
+ export function compareInequality<T, U>(actual: T, compareTo: U, expectedOperation: InequalityOperation): bool {
253
+ // --- Strings: lexicographic comparison ---
254
+ if (isString<T>() && isString<U>()) {
255
+ // Guard against null before casting, mirroring identical()'s pattern
256
+ const actualIsNull = isNullable<T>() && actual == null;
257
+ const compareToIsNull = isNullable<U>() && compareTo == null;
258
+ if (actualIsNull || compareToIsNull) {
259
+ throw new Error(
260
+ "Cannot compare null string with inequality operators: the result is undefined."
261
+ + " Use toBeNull() to check for null values."
262
+ );
263
+ }
264
+ return applyInequalityOp(<string>actual, <string>compareTo, expectedOperation);
265
+ }
266
+
267
+ // --- Reject non-string references (objects, arrays, etc.) ---
268
+ if (isReference<T>() || isReference<U>()) {
269
+ throw new Error(
270
+ "Inequality comparison is not supported for " + nameof<T>() + " and " + nameof<U>()
271
+ + ". Only numeric types and strings can be compared with inequality matchers."
272
+ );
273
+ }
274
+
275
+ // --- Float/integer precision-loss rejection ---
276
+ // Reject combinations where the float's mantissa cannot losslessly represent
277
+ // the integer type's full range (sizeof(integer) >= sizeof(float)).
278
+ // This mirrors AS's own operator rejection (e.g. f32 > i32, f64 > i64).
279
+ if (isFloat<T>() && isInteger<U>()) {
280
+ if (sizeof<U>() >= sizeof<T>()) {
281
+ throw new Error(
282
+ "Cannot compare " + nameof<T>() + " with " + nameof<U>()
283
+ + ": float precision is insufficient for the integer type's range."
284
+ + " Cast both values to f64 before comparing, e.g. expect(f64(a)).toBeGreaterThan(f64(b))."
285
+ + " Note: large integer values may lose precision when cast to f64, which could cause false positives."
286
+ );
287
+ }
288
+ } else if (isInteger<T>() && isFloat<U>()) {
289
+ if (sizeof<T>() >= sizeof<U>()) {
290
+ throw new Error(
291
+ "Cannot compare " + nameof<T>() + " with " + nameof<U>()
292
+ + ": float precision is insufficient for the integer type's range."
293
+ + " Cast both values to f64 before comparing, e.g. expect(f64(a)).toBeGreaterThan(f64(b))."
294
+ + " Note: large integer values may lose precision when cast to f64, which could cause false positives."
295
+ );
296
+ }
297
+ }
298
+
299
+ // --- Numeric comparisons ---
300
+ // Booleans flow through here naturally (isInteger<bool>() is true in AS).
301
+
302
+ if (isInteger<T>() && isInteger<U>()) {
303
+ // Both signed → promote to i64
304
+ if (isSigned<T>() && isSigned<U>()) {
305
+ return applyInequalityOp(i64(actual), i64(compareTo), expectedOperation);
306
+ }
307
+
308
+ // Both unsigned → promote to u64
309
+ if (!isSigned<T>() && !isSigned<U>()) {
310
+ return applyInequalityOp(u64(actual), u64(compareTo), expectedOperation);
311
+ }
312
+
313
+ // Mixed sign — more permissive than AS, which rejects these at compile time.
314
+ // If the signed value is negative, the result is deterministic: signed < unsigned.
315
+ if (isSigned<T>() && !isSigned<U>()) {
316
+ if (i64(actual) < 0) {
317
+ // actual (signed negative) is always less than compareTo (unsigned)
318
+ return expectedOperation == InequalityOperation.LessThan
319
+ || expectedOperation == InequalityOperation.LessThanOrEqual;
320
+ }
321
+ return applyInequalityOp(u64(actual), u64(compareTo), expectedOperation);
322
+ } else {
323
+ // !isSigned<T>() && isSigned<U>()
324
+ if (i64(compareTo) < 0) {
325
+ // compareTo (signed negative) is always less than actual (unsigned)
326
+ return expectedOperation == InequalityOperation.GreaterThan
327
+ || expectedOperation == InequalityOperation.GreaterThanOrEqual;
328
+ }
329
+ return applyInequalityOp(u64(actual), u64(compareTo), expectedOperation);
330
+ }
331
+ }
332
+
333
+ // Both floats → promote to f64
334
+ if (isFloat<T>() && isFloat<U>()) {
335
+ return applyInequalityOp(f64(actual), f64(compareTo), expectedOperation);
336
+ }
337
+
338
+ // Supported float/integer combo (passed precision-loss check above) → promote to f64
339
+ if ( (isFloat<T>() && isInteger<U>()) || (isInteger<T>() && isFloat<U>()) ) {
340
+ return applyInequalityOp(f64(actual), f64(compareTo), expectedOperation);
341
+ }
342
+
343
+ // Unsupported type combination (e.g. vectors)
344
+ throw new Error(
345
+ "Inequality comparison is not supported for " + nameof<T>() + " and " + nameof<U>() + "."
346
+ );
347
+ }
348
+
222
349
  export function truthyOrFalsey<T>(actual: T, expected: bool): bool {
223
350
  return actual ? expected == true : expected == false;
224
351
  }
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  closeTo,
3
+ compareInequality,
3
4
  equals,
4
5
  identical,
6
+ InequalityOperation,
5
7
  isNull,
6
8
  nan,
7
9
  truthyOrFalsey
@@ -97,11 +99,12 @@ abstract class BaseExpectMatcher<T> {
97
99
  * objects, arrays, etc).
98
100
  *
99
101
  * Cross-type numeric comparisons are allowed where AssemblyScript's own `==` operator
100
- * permits them (e.g. `f64` vs `i32`). Combinations where the float type lacks sufficient
101
- * mantissa precision for the integer type's range are rejected with an error, mirroring
102
- * the AS compiler's behavior (e.g. `f32` vs `i32`, `f64` vs `i64`).
102
+ * permits them (e.g. `f64` vs `i32`). `toBeCloseTo()` is safer for any comparison
103
+ * involving a float and allows all numeric types because it can still produce accurate
104
+ * results in precision-loss casting edge cases.
103
105
  *
104
- * Don't use `toBe` to compare two floating-point numbers see `toBeCloseTo` instead.
106
+ * @throws When comparing float/integer types where the float's mantissa cannot losslessly
107
+ * represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
105
108
  *
106
109
  * @example
107
110
  * expect(1 + 1).toBe(2);
@@ -122,15 +125,17 @@ abstract class BaseExpectMatcher<T> {
122
125
  }
123
126
 
124
127
  /**
125
- * Checks if a value is close to what you expect. Using exact equality with floating point
126
- * numbers often doesn't work correctly, because small internal rounding occurs to be able
127
- * to represent floats in binary. This rounding means intuitive comparisons will often fail.
128
+ * Checks if a value is close to what you expect, most useful for comparing floating point
129
+ * numbers to any other numbers. Using exact equality with floating point numbers often doesn't
130
+ * work correctly, because of internal rounding to represent floats in binary. This rounding
131
+ * means intuitive comparisons will often fail, so this matcher checks if they are "close enough"
132
+ * to be considered equal.
128
133
  *
129
- * Strings are compared by value equality. Non-numeric, non-string types return false.
134
+ * Strings are compared by value equality as with `toBe`. Non-numeric, non-string types return false.
130
135
  *
131
- * @param precision - Specify an integer representing the number of decimal places
132
- * that must match for values to be considered close. Defaults to 2 digits, meaning effectively
133
- * that values must be within 0.005 of each other.
136
+ * @param precision - Specify the number of decimal places that must match for values to be
137
+ * considered close. Defaults to 2 digits, meaning effectively that values must be within 0.005 of
138
+ * each other.
134
139
  *
135
140
  * @example
136
141
  * expect(0.1 + 0.2).toBeCloseTo(0.3);
@@ -139,7 +144,109 @@ abstract class BaseExpectMatcher<T> {
139
144
  toBeCloseTo<U>(val: U, precision: i32 = 2): void {
140
145
  this.assertComparison(closeTo(this.actual, val, precision), this.actual, val, "to be close to", true);
141
146
  }
142
-
147
+
148
+ /**
149
+ * Checks that a value is greater than the expected value. Supports numeric types
150
+ * (integers, floats, booleans) and strings (lexicographic comparison).
151
+ *
152
+ * Cross-type numeric comparisons are allowed where safe, including cross-sign integers
153
+ * (more permissive than AS's own `>` operator). Booleans are treated as numeric
154
+ * (true=1, false=0).
155
+ *
156
+ * @throws When comparing float/integer types where the float's mantissa cannot losslessly
157
+ * represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
158
+ * @throws When comparing nullable strings where either value is null. Use `toBeNull()`
159
+ * to check for null values.
160
+ * @throws When comparing non-string reference types (objects, arrays, etc).
161
+ *
162
+ * @example
163
+ * expect(10).toBeGreaterThan(5);
164
+ * expect(3.14).toBeGreaterThan(3);
165
+ * expect("banana").toBeGreaterThan("apple");
166
+ */
167
+ toBeGreaterThan<U>(val: U): void {
168
+ this.assertComparison(
169
+ compareInequality(this.actual, val, InequalityOperation.GreaterThan),
170
+ this.actual, val, "to be greater than", true
171
+ );
172
+ }
173
+
174
+ /**
175
+ * Checks that a value is greater than or equal to the expected value. Supports numeric
176
+ * types (integers, floats, booleans) and strings (lexicographic comparison).
177
+ *
178
+ * Cross-type numeric comparisons are allowed where safe, including cross-sign integers
179
+ * (more permissive than AS's own `>=` operator). Booleans are treated as numeric
180
+ * (true=1, false=0).
181
+ *
182
+ * @throws When comparing float/integer types where the float's mantissa cannot losslessly
183
+ * represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
184
+ * @throws When comparing nullable strings where either value is null. Use `toBeNull()`
185
+ * to check for null values.
186
+ * @throws When comparing non-string reference types (objects, arrays, etc).
187
+ *
188
+ * @example
189
+ * expect(10).toBeGreaterThanOrEqual(10);
190
+ * expect(3.14).toBeGreaterThanOrEqual(3);
191
+ */
192
+ toBeGreaterThanOrEqual<U>(val: U): void {
193
+ this.assertComparison(
194
+ compareInequality(this.actual, val, InequalityOperation.GreaterThanOrEqual),
195
+ this.actual, val, "to be greater than or equal to", true
196
+ );
197
+ }
198
+
199
+ /**
200
+ * Checks that a value is less than the expected value. Supports numeric types
201
+ * (integers, floats, booleans) and strings (lexicographic comparison).
202
+ *
203
+ * Cross-type numeric comparisons are allowed where safe, including cross-sign integers
204
+ * (more permissive than AS's own `<` operator). Booleans are treated as numeric
205
+ * (true=1, false=0).
206
+ *
207
+ * @throws When comparing float/integer types where the float's mantissa cannot losslessly
208
+ * represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
209
+ * @throws When comparing nullable strings where either value is null. Use `toBeNull()`
210
+ * to check for null values.
211
+ * @throws When comparing non-string reference types (objects, arrays, etc).
212
+ *
213
+ * @example
214
+ * expect(5).toBeLessThan(10);
215
+ * expect(3).toBeLessThan(3.14);
216
+ * expect("apple").toBeLessThan("banana");
217
+ */
218
+ toBeLessThan<U>(val: U): void {
219
+ this.assertComparison(
220
+ compareInequality(this.actual, val, InequalityOperation.LessThan),
221
+ this.actual, val, "to be less than", true
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Checks that a value is less than or equal to the expected value. Supports numeric
227
+ * types (integers, floats, booleans) and strings (lexicographic comparison).
228
+ *
229
+ * Cross-type numeric comparisons are allowed where safe, including cross-sign integers
230
+ * (more permissive than AS's own `<=` operator). Booleans are treated as numeric
231
+ * (true=1, false=0).
232
+ *
233
+ * @throws When comparing float/integer types where the float's mantissa cannot losslessly
234
+ * represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
235
+ * @throws When comparing nullable strings where either value is null. Use `toBeNull()`
236
+ * to check for null values.
237
+ * @throws When comparing non-string reference types (objects, arrays, etc).
238
+ *
239
+ * @example
240
+ * expect(5).toBeLessThanOrEqual(5);
241
+ * expect(3).toBeLessThanOrEqual(3.14);
242
+ */
243
+ toBeLessThanOrEqual<U>(val: U): void {
244
+ this.assertComparison(
245
+ compareInequality(this.actual, val, InequalityOperation.LessThanOrEqual),
246
+ this.actual, val, "to be less than or equal to", true
247
+ );
248
+ }
249
+
143
250
  /**
144
251
  * Checks that two values have the same value (deep equality). Currently supports
145
252
  * checking equality of Arrays, Sets, Maps, and nulls. Values inside arrays are
@@ -148,11 +255,13 @@ abstract class BaseExpectMatcher<T> {
148
255
  * `toBe()` rules.
149
256
  *
150
257
  * Like `toBe`, cross-type numeric comparisons follow AssemblyScript's own `==` operator
151
- * restrictions. Combinations where the float type lacks sufficient mantissa precision
152
- * for the integer type's range are rejected with an error (e.g. `f32` vs `i32`,
153
- * `f64` vs `i64`).
258
+ * restrictions. `toBeCloseTo()` is safer for any comparison involving a float and
259
+ * accurately handles precision-loss edge cases.
260
+ *
261
+ * ⚠️ IMPORTANT: Does not yet support user-defined object deep equality checking.
154
262
  *
155
- * Note: Does not yet support user-defined object deep equality checking.
263
+ * @throws When comparing float/integer types where the float's mantissa cannot losslessly
264
+ * represent the integer type's range (e.g. `f32` vs `i32`, `f64` vs `i64`).
156
265
  *
157
266
  * @example
158
267
  * expect([1, 2, 3]).toEqual([1, 2, 3]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-pool-assemblyscript",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "AssemblyScript testing with Vitest - Simple, fast, familiar, AS-native, with full coverage output",
5
5
  "author": "Matt Ritter <matthew.d.ritter@gmail.com>",
6
6
  "license": "MIT",