Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ it.layer(ApiLive)("api", (it) => {
})
```

Property tests accept Effect `Schema` and FastCheck `Arbitrary` values. They shrink callbacks that return `false`, throw, or fail with a non-interruption cause.
Property tests accept Effect `Schema` values and `Arbitrary` values from `effect/unstable/arbitrary`. They shrink callbacks that return `false`, throw, or fail with a non-interruption cause.

```ts
import { Effect, Schema } from "effect"
Expand All @@ -66,7 +66,9 @@ it.effect.prop(
)
```

All three helpers accept both tuple and record inputs, mixing schemas and FastCheck arbitraries. Schemas are converted with `Schema.toArbitrary(schema)(FastCheck)`; FastCheck arbitraries are used directly. For example, a synchronous property can use `[Schema.Literal("schema"), FastCheck.integer()]` or `{ label: Schema.Literal("schema"), count: FastCheck.integer() }`. A schema must support arbitrary generation; this does not make every possible schema generatable.
All three helpers accept tuple and record inputs, mixing schemas and arbitraries. Schemas are converted with `Arbitrary.schema(schema)`; `Arbitrary` values are used directly. For example, a synchronous property can use `[Schema.Literal("schema"), Arbitrary.schema(Schema.Int)]`. A schema must support arbitrary generation. Pass check options as `{ arbitrary: { runs: 200, seed: "repro" } }` (see `Arbitrary.CheckOptions`).

Requires `effect` `4.0.0-rc.113` or later, which removed `effect/testing/FastCheck`. To migrate from 0.1.x, replace `FastCheck.*` inputs with schemas or `Arbitrary` values, and `{ fastCheck: { numRuns } }` with `{ arbitrary: { runs } }`.

Rstest modifiers remain available. Use `it.effect.concurrent`, `it.effect.sequential`, `it.effect.skip`, `it.effect.only`, `it.effect.fails`, `it.effect.skipIf`, and `it.effect.runIf` with Effect tests.

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@
},
"peerDependencies": {
"@rstest/core": ">=0.11.12 <0.12.0",
"effect": ">=4.0.0-rc.112 <5.0.0"
"effect": ">=4.0.0-rc.113 <5.0.0"
},
"devDependencies": {
"@rslib/core": "1.0.0",
"@rstest/core": "0.11.12",
"@types/node": "^26.4.1",
"effect": "4.0.0-rc.112",
"effect": "4.0.0-rc.117",
"tstyche": "^7.2.4",
"typescript": "^5.9.3"
}
Expand Down
113 changes: 5 additions & 108 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type * as Effect from "effect/Effect"
import type * as Layer from "effect/Layer"
import type * as Schema from "effect/Schema"
import type * as Scope from "effect/Scope"
import type { FastCheck as FC } from "effect/testing"
import type * as Arbitrary from "effect/unstable/arbitrary/Arbitrary"
import * as internal from "./internal/internal.ts"

/**
Expand Down Expand Up @@ -46,11 +46,11 @@ export namespace Rstest {
* @since 0.1.0
*/
export type Arbitraries =
| ReadonlyArray<Schema.Schema<any> | FC.Arbitrary<any>>
| { [K in string]: Schema.Schema<any> | FC.Arbitrary<any> }
| ReadonlyArray<Schema.Schema<any> | Arbitrary.Arbitrary<any>>
| { [K in string]: Schema.Schema<any> | Arbitrary.Arbitrary<any> }

type ArbitraryValue<A> = A extends Schema.Schema<infer T> ? T
: A extends FC.Arbitrary<infer T> ? T
: A extends Arbitrary.Arbitrary<infer T> ? T
: never

/**
Expand Down Expand Up @@ -102,7 +102,7 @@ export namespace Rstest {
timeout?:
| number
| R.TestOptions & {
fastCheck?: FC.Parameters<any>
arbitrary?: Arbitrary.CheckOptions
fails?: boolean
}
) => void
Expand Down Expand Up @@ -156,7 +156,7 @@ export namespace Rstest {
timeout?:
| number
| R.TestOptions & {
fastCheck?: FC.Parameters<any>
arbitrary?: Arbitrary.CheckOptions
fails?: boolean
}
) => void
Expand Down
46 changes: 26 additions & 20 deletions src/internal/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { flow, pipe } from "effect/Function"
import * as Layer from "effect/Layer"
import * as Predicate from "effect/Predicate"
import * as Schedule from "effect/Schedule"
import * as Schema from "effect/Schema"
import type * as Schema from "effect/Schema"
import * as Scope from "effect/Scope"
import { FastCheck as FC, TestClock, TestConsole } from "effect/testing"
import { TestClock, TestConsole } from "effect/testing"
import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary"
import type * as Rstest from "../index.ts"

const runPromise: <E, A>(
Expand Down Expand Up @@ -61,7 +62,7 @@ const hookTimeout = (timeout?: Duration.Input) =>
timeout === undefined ? undefined : Duration.toMillis(Duration.fromInputUnsafe(timeout))

type PropertyOptions = R.TestOptions & {
readonly fastCheck?: FC.Parameters<any> | undefined
readonly arbitrary?: Arbitrary.CheckOptions | undefined
readonly fails?: boolean | undefined
}

Expand All @@ -75,30 +76,30 @@ const testOptions = (timeout?: number | R.TestOptions | PropertyOptions): R.Test
if (timeout === undefined) {
return {}
}
const { fails: _, fastCheck: __, ...options } = timeout as PropertyOptions
const { arbitrary: __, fails: _, ...options } = timeout as PropertyOptions
return options
}

type ArbitraryInput = Schema.Schema<any> | FC.Arbitrary<unknown>
type ArbitraryInput = Schema.Schema<any> | Arbitrary.Arbitrary<unknown>

type Arbitraries = ReadonlyArray<ArbitraryInput> | { [K in string]: ArbitraryInput }

const propertyTestOptions = (
timeout: PropertyTimeout | undefined
): Exclude<PropertyTimeout, number> | undefined => typeof timeout === "number" ? undefined : timeout

const checkOptions = (timeout: PropertyTimeout | undefined): FC.Parameters<any> | undefined =>
propertyTestOptions(timeout)?.fastCheck
const checkOptions = (timeout: PropertyTimeout | undefined): Arbitrary.CheckOptions | undefined =>
propertyTestOptions(timeout)?.arbitrary

const compileArbitraryInput = (input: ArbitraryInput): FC.Arbitrary<any> =>
Schema.isSchema(input) ? Schema.toArbitrary(input)(FC) : input as FC.Arbitrary<any>
const compileArbitraryInput = (input: ArbitraryInput): Arbitrary.Arbitrary<any> =>
Arbitrary.isArbitrary(input) ? input : Arbitrary.schema(input)

const makeArbitrary = (arbitraries: Arbitraries): FC.Arbitrary<any> =>
Array.isArray(arbitraries)
? FC.tuple(...arbitraries.map(compileArbitraryInput))
: FC.record(Object.fromEntries(
Object.entries(arbitraries).map(([key, input]) => [key, compileArbitraryInput(input)])
))
const makeArbitrary = (arbitraries: Arbitraries): Arbitrary.Arbitrary<any> =>
Arbitrary.all(
Array.isArray(arbitraries)
? arbitraries.map(compileArbitraryInput)
: Object.fromEntries(Object.entries(arbitraries).map(([key, input]) => [key, compileArbitraryInput(input)]))
)

const normalizeProperty = <A, E, R>(
property: (value: A) => boolean | Effect.Effect<boolean, E, R>,
Expand Down Expand Up @@ -130,13 +131,18 @@ const normalizeSyncProperty = <A>(

const runCheck = <A, E>(
ctx: R.TestContext,
arbitrary: FC.Arbitrary<A>,
arbitrary: Arbitrary.Arbitrary<A>,
property: (value: A) => boolean | Effect.Effect<boolean, E>,
options: FC.Parameters<any> | undefined
options: Arbitrary.CheckOptions | undefined
): Promise<void> =>
FC.assert(
FC.asyncProperty(arbitrary, (value) => runTest(ctx)(normalizeProperty(property, value))),
options
runTest(ctx)(
Effect.flatMapEager(
Arbitrary.checkEffect(arbitrary, (value) => normalizeProperty(property, value), options),
(result) => {
const failure = Arbitrary.formatCheckFailure(result)
return failure === undefined ? Effect.void : Effect.die(new Error(failure))
}
)
)

type TestAPI = R.TestAPIs | R.TestAPIs["skip"]
Expand Down
Loading
Loading