EdgeAIG's picture
download
raw
14.4 kB
/**
* Provides helpers for testing Schema behavior.
*
* These utilities assert how schemas construct values, decode input, encode
* output, generate arbitrary values, and round-trip between encoded and decoded
* forms. The `Asserts` class groups the common checks for one schema, while
* `Decoding` and `Encoding` can be used directly when a test only needs one
* direction.
*
* @since 4.0.0
*/
import * as assert from "node:assert";
import * as Effect from "../Effect.js";
import * as Record from "../Record.js";
import * as Result from "../Result.js";
import * as Schema from "../Schema.js";
import * as SchemaAST from "../SchemaAST.js";
import * as SchemaParser from "../SchemaParser.js";
import * as FastCheck from "../testing/FastCheck.js";
/**
* Provides schema test assertions for decoding, encoding, make, arbitrary generation, and round-trip verification.
*
* **When to use**
*
* Use when writing schema unit tests for decoding, encoding, construction, property-based round-trip, or generation behavior.
*
* **Example** (Decoding and encoding a struct)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const schema = Schema.Struct({ name: Schema.String })
* const asserts = new TestSchema.Asserts(schema)
*
* // decoding
* await asserts.decoding().succeed({ name: "Alice" })
*
* // encoding
* await asserts.encoding().succeed({ name: "Alice" })
* ```
*
* @see {@link Decoding}
* @see {@link Encoding}
* @category testing
* @since 4.0.0
*/
export class Asserts {
/**
* Static helpers for comparing schema AST structures.
*
* **When to use**
*
* Use to assert that two schema field or tuple element definitions produce
* the same AST structure.
*
* **Details**
*
* `ast.fields.equals(a, b)` compares struct field ASTs via `assert.deepStrictEqual`. `ast.elements.equals(a, b)` compares tuple element ASTs via `assert.deepStrictEqual`.
*
* **Example** (Comparing struct fields)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const fieldsA = { name: Schema.String }
* const fieldsB = { name: Schema.String }
* TestSchema.Asserts.ast.fields.equals(fieldsA, fieldsB) // no error
* ```
*/
static ast = {
fields: {
equals: (a, b) => {
assert.deepStrictEqual(Record.map(a, SchemaAST.getAST), Record.map(b, SchemaAST.getAST));
}
},
elements: {
equals: (a, b) => {
assert.deepStrictEqual(a.map(SchemaAST.getAST), b.map(SchemaAST.getAST));
}
}
};
schema;
constructor(schema) {
this.schema = schema;
}
/**
* Returns an object with `succeed` and `fail` helpers for testing the schema's `make` operation.
*
* **When to use**
*
* Use to assert how `Schema.make` accepts, transforms, or rejects
* construction input for this schema.
*
* **Details**
*
* `succeed(input)` asserts make returns the input unchanged. `succeed(input, expected)` asserts make returns `expected`. `fail(input, message)` asserts make fails with `message`.
*
* **Example** (Testing make)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const schema = Schema.String
* const asserts = new TestSchema.Asserts(schema)
* await asserts.make().succeed("hello")
* ```
*
* @see {@link decoding} for assertions against decoded input
* @see {@link encoding} for assertions against encoded output
*/
make(options) {
const makeEffect = SchemaParser.makeEffect(this.schema);
async function succeed(input, expected) {
const r = await Effect.runPromise(makeEffect(input, options).pipe(Effect.mapErrorEager(issue => issue.toString()), Effect.result));
expected = arguments.length === 1 ? input : expected;
assert.deepStrictEqual(r, Result.succeed(expected));
}
return {
succeed,
async fail(input, message) {
const r = await Effect.runPromise(makeEffect(input, options).pipe(Effect.mapErrorEager(issue => issue.toString()), Effect.result));
assert.deepStrictEqual(r, Result.fail(message));
}
};
}
/**
* Runs a property-based test that encodes arbitrary values and then decodes them, asserting the decoded value equals the original.
*
* **When to use**
*
* Use to verify that generated schema values survive an encode-then-decode
* round trip.
*
* **Details**
*
* FastCheck generates arbitrary values matching the schema's `Type`. The assertion fails if any generated value does not round-trip. Pass `options.params` to control FastCheck parameters such as `numRuns`.
*
* **Example** (Round-trip verification)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const asserts = new TestSchema.Asserts(Schema.NumberFromString)
* await asserts.verifyLosslessTransformation()
* ```
*
* @see {@link arbitrary} for checking that generated values satisfy the schema
*/
verifyLosslessTransformation(options) {
const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(this.schema);
const encodeEffect = SchemaParser.encodeEffect(this.schema);
const arbitrary = Schema.toArbitrary(this.schema);
return FastCheck.assert(FastCheck.asyncProperty(arbitrary, async t => {
const r = await Effect.runPromise(encodeEffect(t).pipe(Effect.flatMapEager(e => decodeUnknownEffect(e)), Effect.mapErrorEager(issue => issue.toString()), Effect.result));
assert.deepStrictEqual(r, Result.succeed(t));
}), options?.params);
}
/**
* Returns a {@link Decoding} instance for this schema with helpers for decoding assertions.
*
* **When to use**
*
* Use to test how unknown input is decoded into the schema's type.
*
* **Details**
*
* Pass `parseOptions` to control error reporting, for example `{ errors: "all" }`.
*
* **Example** (Decoding assertions)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const asserts = new TestSchema.Asserts(Schema.NumberFromString)
* const decoding = asserts.decoding()
* await decoding.succeed("42", 42)
* await decoding.fail(null, "Expected string, got null")
* ```
*
* @see {@link Decoding}
* @see {@link encoding} for assertions in the opposite direction
*/
decoding(options) {
return new Decoding(this.schema, options);
}
/**
* Returns an {@link Encoding} instance for this schema with helpers for encoding assertions.
*
* **When to use**
*
* Use to test how schema values are encoded into their external form.
*
* **Details**
*
* Pass `parseOptions` to control error reporting, for example `{ errors: "all" }`.
*
* **Example** (Encoding assertions)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const asserts = new TestSchema.Asserts(Schema.NumberFromString)
* const encoding = asserts.encoding()
* await encoding.succeed(42, "42")
* ```
*
* @see {@link Encoding}
* @see {@link decoding} for assertions in the opposite direction
*/
encoding(options) {
return new Encoding(this.schema, options);
}
/**
* Returns an object with property-based testing helpers for the schema's arbitrary generator.
*
* **When to use**
*
* Use to verify that arbitrary values generated for this schema satisfy the
* schema's predicate.
*
* **Details**
*
* `verifyGeneration()` generates arbitrary values and asserts each value satisfies the schema's `is` predicate. It defaults to 20 runs. Pass `options.params` to override FastCheck parameters.
*
* **Example** (Verifying arbitrary generation)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const asserts = new TestSchema.Asserts(Schema.String)
* asserts.arbitrary().verifyGeneration()
* ```
*
* @see {@link verifyLosslessTransformation} for property-based round-trip checks
*/
arbitrary() {
const schema = this.schema;
return {
verifyGeneration(options) {
const params = options?.params;
const is = Schema.is(schema);
const arb = Schema.toArbitrary(schema);
FastCheck.assert(FastCheck.property(arb, a => is(a)), {
numRuns: 20,
...params
});
}
};
}
}
/**
* Provides decoding test assertions through `succeed` and `fail` methods that run the schema's decoder and compare the result.
*
* **When to use**
*
* Use when you want to assert that specific inputs decode to expected values, invalid inputs produce specific error messages, or schemas receive required decoding services.
*
* **Details**
*
* All assertions are async and use `assert.deepStrictEqual` internally. `succeed(input)` asserts the decoded output equals `input`; `succeed(input, expected)` asserts it equals `expected`; `fail(input, message)` asserts decoding fails and the stringified issue equals `message`. `provide(key, impl)` returns a new `Decoding` with the service injected into the decoding context.
*
* **Example** (Decoding with service provision)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const asserts = new TestSchema.Asserts(Schema.String)
* const decoding = asserts.decoding()
* await decoding.succeed("hello")
* ```
*
* @see {@link Asserts}
* @see {@link Encoding}
* @category testing
* @since 4.0.0
*/
export class Decoding {
schema;
decodeUnknownEffect;
options;
constructor(schema, options) {
this.schema = schema;
this.decodeUnknownEffect = SchemaParser.decodeUnknownEffect(schema);
this.options = options;
}
async succeed(input, expected) {
const r = await Effect.runPromise(this.decodeUnknownEffect(input, this.options?.parseOptions).pipe(Effect.mapErrorEager(issue => issue.toString()), Effect.result));
expected = arguments.length === 1 ? input : expected;
assert.deepStrictEqual(r, Result.succeed(expected));
}
/**
* Asserts that decoding `input` fails and the stringified issue equals
* `message`.
*
* **When to use**
*
* Use to verify that invalid decoding input produces the expected issue text.
*
* **Example** (Asserting a decoding failure)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const decoding = new TestSchema.Asserts(Schema.String).decoding()
* await decoding.fail(42, "Expected string, got 42")
* ```
*
* @see {@link succeed} for asserting successful decoding
*/
async fail(input, message) {
const r = await Effect.runPromise(this.decodeUnknownEffect(input, this.options?.parseOptions).pipe(Effect.mapErrorEager(issue => issue.toString()), Effect.result));
assert.deepStrictEqual(r, Result.fail(message));
}
/**
* Returns a new {@link Decoding} instance with the given service injected into the decoding effect context.
*
* **When to use**
*
* Use when the schema's decoder requires a service dependency.
*
* @see {@link Encoding.provide}
*/
provide(service, implementation) {
return new Decoding(this.schema.pipe(Schema.middlewareDecoding(Effect.provideService(service, implementation))), this.options);
}
}
/**
* Provides encoding test assertions through `succeed` and `fail` methods that run the schema's encoder and compare the result.
*
* **When to use**
*
* Use when you want to assert that specific values encode to expected outputs, invalid inputs produce specific error messages, or schemas receive required encoding services.
*
* **Details**
*
* All assertions are async and use `assert.deepStrictEqual` internally. `succeed(input)` asserts the encoded output equals `input`; `succeed(input, expected)` asserts it equals `expected`; `fail(input, message)` asserts encoding fails and the stringified issue equals `message`. `provide(key, impl)` returns a new `Encoding` with the service injected into the encoding context.
*
* **Example** (Encoding assertions)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const encoding = new TestSchema.Asserts(Schema.NumberFromString).encoding()
* await encoding.succeed(42, "42")
* ```
*
* @see {@link Asserts}
* @see {@link Decoding}
*
* @category testing
* @since 4.0.0
*/
export class Encoding {
schema;
encodeUnknownEffect;
options;
constructor(schema, options) {
this.schema = schema;
this.encodeUnknownEffect = SchemaParser.encodeUnknownEffect(schema);
this.options = options;
}
async succeed(input, expected) {
const r = await Effect.runPromise(this.encodeUnknownEffect(input, this.options?.parseOptions).pipe(Effect.mapErrorEager(issue => issue.toString()), Effect.result));
expected = arguments.length === 1 ? input : expected;
assert.deepStrictEqual(r, Result.succeed(expected));
}
/**
* Asserts that encoding `input` fails and the stringified issue equals
* `message`.
*
* **When to use**
*
* Use to verify that invalid encoding input produces the expected issue text.
*
* **Example** (Asserting an encoding failure)
*
* ```ts
* import { Schema } from "effect"
* import { TestSchema } from "effect/testing"
*
* const encoding = new TestSchema.Asserts(Schema.NumberFromString).encoding()
* await encoding.fail("not-a-number", "Expected number, got \"not-a-number\"")
* ```
*
* @see {@link succeed} for asserting successful encoding
*/
async fail(input, message) {
const r = await Effect.runPromise(this.encodeUnknownEffect(input, this.options?.parseOptions).pipe(Effect.mapErrorEager(issue => issue.toString()), Effect.result));
assert.deepStrictEqual(r, Result.fail(message));
}
/**
* Returns a new {@link Encoding} instance with the given service injected into the encoding effect context.
*
* **When to use**
*
* Use when the schema's encoder requires a service dependency.
*
* @see {@link Decoding.provide}
*/
provide(service, implementation) {
return new Encoding(this.schema.pipe(Schema.middlewareEncoding(Effect.provideService(service, implementation))), this.options);
}
}
//# sourceMappingURL=TestSchema.js.map

Xet Storage Details

Size:
14.4 kB
·
Xet hash:
651c8257406bc8c5056d07bd285c469367902afecd015f845c864d62f87106ed

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.