Skip to content

Commit

Permalink
use newly merged schema
Browse files Browse the repository at this point in the history
  • Loading branch information
jessekelly881 committed Oct 16, 2024
1 parent 0875e02 commit 737109a
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 6 deletions.
31 changes: 31 additions & 0 deletions .changeset/young-boxes-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"@effect/vitest": patch
---

Adds property testing to @effect/vitest

```ts
import { Schema } from "effect"
import { it } from "@effect/vitest"

const realNumber = Schema.Finite.pipe(Schema.nonNaN())

it.prop("symmetry", [realNumber, realNumber], ([a, b]) => a + b === b + a)

it.effect.prop("symmetry", [realNumber, realNumber], ([a, b]) =>
Effect.gen(function* () {
yield* Effect.void
return a + b === b + a
})
)

it.scoped.prop(
"should detect the substring",
{ a: Schema.String, b: Schema.String, c: Schema.String },
({ a, b, c }) =>
Effect.gen(function* () {
yield* Effect.scope
return (a + b + c).includes(b)
})
)
```
60 changes: 59 additions & 1 deletion packages/vitest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import type * as Duration from "effect/Duration"
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 * as TestServices from "effect/TestServices"
import * as V from "vitest"
Expand Down Expand Up @@ -36,6 +37,11 @@ export namespace Vitest {
): void
}

/**
* @since 1.0.0
*/
export type SchemaObj<A, E, R> = Array<Schema.Schema<A, E, R>> | { [K in string]: Schema.Schema<A, E, R> }

/**
* @since 1.0.0
*/
Expand All @@ -47,6 +53,21 @@ export namespace Vitest {
each: <T>(
cases: ReadonlyArray<T>
) => <A, E>(name: string, self: TestFunction<A, E, R, Array<T>>, timeout?: number | V.TestOptions) => void

/**
* @since 1.0.0
*/
prop: <const S extends SchemaObj<any, any, any>, A, E>(
name: string,
schemas: S,
self: TestFunction<
A,
E,
R,
[{ [K in keyof S]: Schema.Schema.Type<S[K]> }, V.TaskContext<V.RunnerTestCase<{}>> & V.TestContext]
>,
timeout?: number | V.TestOptions
) => void
}

/**
Expand All @@ -67,6 +88,26 @@ export namespace Vitest {
(f: (it: Vitest.Methods<R | R2>) => void): void
(name: string, f: (it: Vitest.Methods<R | R2>) => void): void
}

/**
* @since 1.0.0
* @example
*
* import { Schema } from "@effect/schema"
* import { it } from "@effect/vitest"
*
* const realNumber = Schema.Finite.pipe(Schema.nonNaN())
* it.prop("symmetry", [realNumber, realNumber], ([a, b]) => a + b === b + a)
*/
readonly prop: <const S extends SchemaObj<any, any, any>>(
name: string,
schemas: S,
self: (
schemas: { [K in keyof S]: Schema.Schema.Type<S[K]> },
ctx: V.TaskContext<V.RunnerTestCase<{}>> & V.TestContext
) => void,
timeout?: number | V.TestOptions
) => void
}
}

Expand Down Expand Up @@ -151,8 +192,25 @@ export const flakyTest: <A, E, R>(
timeout?: Duration.DurationInput
) => Effect.Effect<A, never, R> = internal.flakyTest

/**
* @since 1.0.0
*/
export const prop: <const S extends Vitest.SchemaObj<any, any, any>>(
name: string,
schemas: S,
self: (
schemas: { [K in keyof S]: Schema.Schema.Type<S[K]> },
ctx: V.TaskContext<V.RunnerTestCase<{}>> & V.TestContext
) => void,
timeout?: number | V.TestOptions
) => void = internal.prop

/**
* @since 1.0.0
*/

/** @ignored */
const methods = { effect, live, flakyTest, scoped, scopedLive, layer } as const
const methods = { effect, live, flakyTest, scoped, scopedLive, layer, prop } as const

/**
* @since 1.0.0
Expand Down
64 changes: 60 additions & 4 deletions packages/vitest/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @since 1.0.0
*/
import type { Tester, TesterContext } from "@vitest/expect"
import * as Arbitrary from "effect/Arbitrary"
import * as Cause from "effect/Cause"
import * as Duration from "effect/Duration"
import * as Effect from "effect/Effect"
Expand All @@ -16,6 +17,7 @@ import * as Scope from "effect/Scope"
import * as TestEnvironment from "effect/TestContext"
import type * as TestServices from "effect/TestServices"
import * as Utils from "effect/Utils"
import fc from "fast-check"
import * as V from "vitest"
import type * as Vitest from "./index.js"

Expand Down Expand Up @@ -45,8 +47,7 @@ const runPromise = (ctx?: Vitest.TaskContext) => <E, A>(effect: Effect.Effect<A,
}).pipe(Effect.runPromise).then((f) => f())

/** @internal */
const runTest = (ctx?: Vitest.TaskContext) => <E, A>(effect: Effect.Effect<A, E>) =>
runPromise(ctx)(Effect.asVoid(effect))
const runTest = (ctx?: Vitest.TaskContext) => <E, A>(effect: Effect.Effect<A, E>) => runPromise(ctx)(effect)

/** @internal */
const TestEnv = TestEnvironment.TestContext.pipe(
Expand Down Expand Up @@ -93,10 +94,62 @@ const makeTester = <R>(
V.it.for(cases)(
name,
typeof timeout === "number" ? { timeout } : timeout ?? {},
(args, ctx) => run(ctx, [args], self)
(args, ctx) => run(ctx, [args], self) as any
)

return Object.assign(f, { skip, skipIf, runIf, only, each })
const prop: Vitest.Vitest.Tester<R>["prop"] = (name, schemaObj, self, timeout) => {
if (Array.isArray(schemaObj)) {
const arbs = schemaObj.map((schema) => Arbitrary.make(schema))
return V.it(
name,
// @ts-ignore
(ctx) => fc.assert(fc.asyncProperty(...arbs, (...as) => run(ctx, [as as any, ctx], self))),
timeout
)
}

const arbs = fc.record(
Object.keys(schemaObj).reduce(function(result, key) {
result[key] = Arbitrary.make(schemaObj[key])
return result
}, {} as Record<string, fc.Arbitrary<any>>)
)

return V.it(
name,
// @ts-ignore
(ctx) => fc.assert(fc.asyncProperty(arbs, (...as) => run(ctx, [as[0] as any, ctx], self))),
timeout
)
}

return Object.assign(f, { skip, skipIf, runIf, only, each, prop })
}

export const prop: Vitest.Vitest.Methods["prop"] = (name, schemaObj, self, timeout) => {
if (Array.isArray(schemaObj)) {
const arbs = schemaObj.map((schema) => Arbitrary.make(schema))
return V.it(
name,
// @ts-ignore
(ctx) => fc.assert(fc.property(...arbs, (...as) => self(as, ctx))),
timeout
)
}

const arbs = fc.record(
Object.keys(schemaObj).reduce(function(result, key) {
result[key] = Arbitrary.make(schemaObj[key])
return result
}, {} as Record<string, fc.Arbitrary<any>>)
)

return V.it(
name,
// @ts-ignore
(ctx) => fc.assert(fc.property(arbs, (...as) => self(as[0], ctx))),
timeout
)
}

/** @internal */
Expand Down Expand Up @@ -127,6 +180,9 @@ export const layer = <R, E>(layer_: Layer.Layer<R, E>, options?: {
Effect.provide(TestEnv)
))
),

prop,

scoped: makeTester<TestServices.TestServices | Scope.Scope | R>((effect) =>
Effect.flatMap(runtimeEffect, (runtime) =>
effect.pipe(
Expand Down
31 changes: 30 additions & 1 deletion packages/vitest/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterAll, describe, expect, it, layer } from "@effect/vitest"
import { Context, Effect, Layer } from "effect"
import { Context, Effect, Layer, Schema } from "effect"

it.live(
"live %s",
Expand Down Expand Up @@ -146,5 +146,34 @@ layer(Foo.Live)("layer", (it) => {
expect(scoped).toEqual("scoped")
}))
})

it.effect.prop("adds context", [Schema.Number], ([num]) =>
Effect.gen(function*() {
const foo = yield* Foo
expect(foo).toEqual("foo")
return num === num
}))
})
})

// property testing

const realNumber = Schema.Finite.pipe(Schema.nonNaN())

it.prop("symmetry", [realNumber, realNumber], ([a, b]) => a + b === b + a)

it.effect.prop("symmetry", [realNumber, realNumber], ([a, b]) =>
Effect.gen(function*() {
yield* Effect.void
return a + b === b + a
}))

it.scoped.prop(
"should detect the substring",
{ a: Schema.String, b: Schema.String, c: Schema.String },
({ a, b, c }) =>
Effect.gen(function*() {
yield* Effect.scope
return (a + b + c).includes(b)
})
)

0 comments on commit 737109a

Please sign in to comment.