Skip to content

Commit

Permalink
moved prop testing to dedicated module
Browse files Browse the repository at this point in the history
  • Loading branch information
jessekelly881 committed Oct 10, 2024
1 parent 8a69754 commit b8cca68
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 7 deletions.
31 changes: 31 additions & 0 deletions .changeset/tame-bags-know.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/schema"
import { prop } from "@effect/vitest/Schema"

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

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

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

prop.scoped(
"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)
})
)
```
2 changes: 2 additions & 0 deletions packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
},
"peerDependencies": {
"effect": "workspace:^",
"@effect/schema": "workspace:^",
"vitest": "^2.0.5"
},
"devDependencies": {
"effect": "workspace:^",
"@effect/schema": "workspace:^",
"vitest": "^2.0.5"
}
}
118 changes: 118 additions & 0 deletions packages/vitest/src/Schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as Arbitrary from "@effect/schema/Arbitrary"
import type * as Schema from "@effect/schema/Schema"
import * as Effect from "effect/Effect"
import type * as Scope from "effect/Scope"
import type { TestServices } from "effect/TestServices"
import fc from "fast-check"
import * as V from "vitest"
import * as internal from "./internal.js"

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

/**
* @since 1.0.0
*/
export type TestFn<S extends SchemaObj<any, any, any>, R> = (
schemas: { [K in keyof S]: Schema.Schema.Type<S[K]> },
ctx: V.TaskContext<V.RunnerTestCase<{}>> & V.TestContext
) => R

/**
* @internal
*/
const makePropTester = () => {
const f = <S extends SchemaObj<any, any, any>>(
name: string,
schemaObj: S,
fn: TestFn<S, boolean | void | Promise<boolean | void>>,
timeout?: number | V.TestOptions
) => {
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) => fn(as as any, 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.asyncProperty(arbs, (...as) => fn(as[0] as any, ctx))),
timeout
)
}

const effect = <S extends SchemaObj<any, any, any>>(
name: string,
schemaObj: S,
fn: TestFn<S, Effect.Effect<boolean | void, never, TestServices>>,
timeout?: number | V.TestOptions
) =>
f(
name,
schemaObj,
(obj, ctx) => Effect.runPromise(fn(obj, ctx).pipe(Effect.provide(internal.TestEnv))),
timeout
)

const live = <S extends SchemaObj<any, any, any>>(
name: string,
schemaObj: S,
fn: TestFn<S, Effect.Effect<boolean | void>>,
timeout?: number | V.TestOptions
) =>
f(
name,
schemaObj,
(obj, ctx) => Effect.runPromise(fn(obj, ctx)),
timeout
)

const scoped = <S extends SchemaObj<any, any, any>>(
name: string,
schemaObj: S,
fn: TestFn<S, Effect.Effect<boolean | void, never, Scope.Scope | TestServices>>,
timeout?: number | V.TestOptions
) =>
f(
name,
schemaObj,
(obj, ctx) => Effect.runPromise(fn(obj, ctx).pipe(Effect.scoped, Effect.provide(internal.TestEnv))),
timeout
)

const scopedLive = <S extends SchemaObj<any, any, any>>(
name: string,
schemaObj: S,
fn: TestFn<S, Effect.Effect<boolean | void, never, Scope.Scope>>,
timeout?: number | V.TestOptions
) =>
f(
name,
schemaObj,
(obj, ctx) => Effect.runPromise(fn(obj, ctx).pipe(Effect.scoped)),
timeout
)

return Object.assign(f, {
effect,
scoped,
live,
scopedLive
})
}

export const prop = makePropTester()
8 changes: 4 additions & 4 deletions packages/vitest/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as Utils from "effect/Utils"
import * as V from "vitest"
import type * as Vitest from "./index.js"

/** @internal */
const runPromise = (ctx?: Vitest.TaskContext) => <E, A>(effect: Effect.Effect<A, E>) =>
Effect.gen(function*() {
const exitFiber = yield* Effect.fork(Effect.exit(effect))
Expand All @@ -45,11 +46,10 @@ 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))
export const runTest = (ctx?: Vitest.TaskContext) => <E, A>(effect: Effect.Effect<A, E>) => runPromise(ctx)(effect)

/** @internal */
const TestEnv = TestEnvironment.TestContext.pipe(
export const TestEnv = TestEnvironment.TestContext.pipe(
Layer.provide(Logger.remove(Logger.defaultLogger))
)

Expand Down Expand Up @@ -93,7 +93,7 @@ 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 })
Expand Down
25 changes: 25 additions & 0 deletions packages/vitest/test/Schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Schema } from "@effect/schema"
import { Effect } from "effect"
import { prop } from "@effect/vitest/Schema"

// property testing

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

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

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

prop.scoped(
"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)
})
)
3 changes: 2 additions & 1 deletion packages/vitest/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "./tsconfig.src.json",
"references": [
{ "path": "../effect/tsconfig.build.json" }
{ "path": "../effect/tsconfig.build.json" },
{ "path": "../schema/tsconfig.build.json" }
],
"compilerOptions": {
"tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo",
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/tsconfig.src.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"references": [{ "path": "../effect" }],
"references": [{ "path": "../effect/tsconfig.src.json" }, { "path": "../schema/tsconfig.build.json" }],
"compilerOptions": {
"outDir": "build/src",
"tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

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

3 changes: 2 additions & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@
"@effect/typeclass": ["./packages/typeclass/src/index.js"],
"@effect/typeclass/*": ["./packages/typeclass/src/*.js"],
"@effect/typeclass/test/*": ["./packages/typeclass/test/*.js"],
"@effect/vitest": ["./packages/vitest/src/index.js"]
"@effect/vitest": ["./packages/vitest/src/index.js"],
"@effect/vitest/*": ["./packages/vitest/src/*.js"]
}
}
}

0 comments on commit b8cca68

Please sign in to comment.