Skip to content

Commit

Permalink
fix: vitest custom tester with toMatchObject (rebase) (#2732)
Browse files Browse the repository at this point in the history
Co-authored-by: Milan Suk <Milansuk@email.cz>
  • Loading branch information
mikearnaldi and sukovanej authored May 12, 2024
1 parent 50b8bd5 commit e41e911
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 71 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-lizards-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Call Equal.equals internally in order inputs were passed.
5 changes: 5 additions & 0 deletions .changeset/shiny-crabs-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/vitest": patch
---

Fix usage of `toMatchObject`.
8 changes: 7 additions & 1 deletion packages/effect/src/Equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ function compareBoth(self: unknown, that: unknown): boolean {
if (selfType === "object" || selfType === "function") {
if (self !== null && that !== null) {
if (isEqual(self) && isEqual(that)) {
return Hash.hash(self) === Hash.hash(that) && self[symbol](that)
if (Hash.hash(self) === Hash.hash(that) && self[symbol](that)) {
return true
} else {
return structuralRegionState.enabled && structuralRegionState.tester
? structuralRegionState.tester(self, that)
: false
}
}
}
if (structuralRegionState.enabled) {
Expand Down
46 changes: 17 additions & 29 deletions packages/effect/src/Hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export interface Hash {
* @category hashing
*/
export const hash: <A>(self: A) => number = <A>(self: A) => {
if (structuralRegionState.enabled === true) {
return 0
}

switch (typeof self) {
case "number":
return number(self)
Expand Down Expand Up @@ -72,9 +76,6 @@ export const hash: <A>(self: A) => number = <A>(self: A) => {
* @category hashing
*/
export const random: <A extends object>(self: A) => number = (self) => {
if (structuralRegionState.enabled === true) {
return 0
}
if (!randomHashCache.has(self)) {
randomHashCache.set(self, number(pcgr.integer(Number.MAX_SAFE_INTEGER)))
}
Expand Down Expand Up @@ -171,36 +172,23 @@ export const cached: {
if (arguments.length === 1) {
const self = arguments[0] as object
return function(hash: number) {
// @ts-expect-error
const original = self[symbol].bind(self)
if (structuralRegionState.enabled === false) {
Object.defineProperty(self, symbol, {
value() {
if (structuralRegionState.enabled === true) {
return original()
}
return hash
},
enumerable: false
})
}
Object.defineProperty(self, symbol, {
value() {
return hash
},
enumerable: false
})
return hash
} as any
}
const self = arguments[0] as object
const hash = arguments[1] as number
// @ts-expect-error
const original = self[symbol].bind(self)
if (structuralRegionState.enabled === false) {
Object.defineProperty(self, symbol, {
value() {
if (structuralRegionState.enabled === true) {
return original()
}
return hash
},
enumerable: false
})
}
Object.defineProperty(self, symbol, {
value() {
return hash
},
enumerable: false
})

return hash
}
4 changes: 2 additions & 2 deletions packages/effect/src/internal/either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const RightProto = Object.assign(Object.create(CommonProto), {
_tag: "Right",
_op: "Right",
[Equal.symbol]<L, R>(this: Either.Right<L, R>, that: unknown): boolean {
return isEither(that) && isRight(that) && Equal.equals(that.right, this.right)
return isEither(that) && isRight(that) && Equal.equals(this.right, that.right)
},
[Hash.symbol]<L, R>(this: Either.Right<L, R>) {
return Hash.combine(Hash.hash(this._tag))(Hash.hash(this.right))
Expand All @@ -52,7 +52,7 @@ const LeftProto = Object.assign(Object.create(CommonProto), {
_tag: "Left",
_op: "Left",
[Equal.symbol]<L, R>(this: Either.Left<L, R>, that: unknown): boolean {
return isEither(that) && isLeft(that) && Equal.equals(that.left, this.left)
return isEither(that) && isLeft(that) && Equal.equals(this.left, that.left)
},
[Hash.symbol]<L, R>(this: Either.Left<L, R>) {
return Hash.combine(Hash.hash(this._tag))(Hash.hash(this.left))
Expand Down
2 changes: 1 addition & 1 deletion packages/effect/src/internal/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const SomeProto = Object.assign(Object.create(CommonProto), {
_tag: "Some",
_op: "Some",
[Equal.symbol]<A>(this: Option.Some<A>, that: unknown): boolean {
return isOption(that) && isSome(that) && Equal.equals(that.value, this.value)
return isOption(that) && isSome(that) && Equal.equals(this.value, that.value)
},
[Hash.symbol]<A>(this: Option.Some<A>) {
return Hash.cached(this, Hash.combine(Hash.hash(this._tag))(Hash.hash(this.value)))
Expand Down
10 changes: 0 additions & 10 deletions packages/effect/test/Either.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,6 @@ describe("Either", () => {
Util.deepStrictEqual(pipe(Either.left("a"), Either.orElse(() => Either.left("b"))), Either.left("b"))
})

it("vitest equality", () => {
expect(Either.right(1)).toStrictEqual(Either.right(1))
expect(Either.left(1)).toStrictEqual(Either.left(1))

expect(Either.right(2)).not.toStrictEqual(Either.right(1))
expect(Either.left(2)).not.toStrictEqual(Either.left(1))
expect(Either.left(1)).not.toStrictEqual(Either.right(1))
expect(Either.left(1)).not.toStrictEqual(Either.right(2))
})

describe("do notation", () => {
it("Do", () => {
expectRight(Either.Do, {})
Expand Down
15 changes: 0 additions & 15 deletions packages/effect/test/Exit.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as Cause from "effect/Cause"
import * as Exit from "effect/Exit"
import { describe, expect, it } from "vitest"

Expand Down Expand Up @@ -74,18 +73,4 @@ describe("Exit", () => {
}`)
})
})

it("vitest equality", () => {
expect(Exit.succeed(1)).toEqual(Exit.succeed(1))
expect(Exit.fail("failure")).toEqual(Exit.fail("failure"))
expect(Exit.die("defect")).toEqual(Exit.die("defect"))

expect(Exit.succeed(1)).not.toEqual(Exit.succeed(2))
expect(Exit.fail("failure")).not.toEqual(Exit.fail("failure1"))
expect(Exit.die("failure")).not.toEqual(Exit.fail("failure1"))
expect(Exit.die("failure")).not.toEqual(Exit.fail("failure1"))
expect(Exit.failCause(Cause.sequential(Cause.fail("f1"), Cause.fail("f2")))).not.toEqual(
Exit.failCause(Cause.sequential(Cause.fail("f1"), Cause.fail("f3")))
)
})
})
8 changes: 0 additions & 8 deletions packages/effect/test/Option.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,14 +499,6 @@ describe("Option", () => {
expect(f(Option.some(1), Option.some(2))).toStrictEqual(Option.some(3))
})

it("vitest equality", () => {
expect(Option.some(2)).toStrictEqual(Option.some(2))
expect(Option.none()).toStrictEqual(Option.none())

expect(Option.some(2)).not.toStrictEqual(Option.some(1))
expect(Option.none()).not.toStrictEqual(Option.some(1))
})

describe("do notation", () => {
it("Do", () => {
expectSome(Option.Do, {})
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/test/Schema/Class/Class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe("Class", () => {
const schema = S.suspend(() => string)
class A extends S.Class<A>("A")({ a: S.optional(schema) }) {}
const string = S.String
await Util.expectDecodeUnknownSuccess(A, { a: "a" })
await Util.expectDecodeUnknownSuccess(A, new A({ a: "a" }))
})

it("should be a Schema", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/test/Schema/Class/extend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe("extend", () => {
S.Struct(fields).pipe(S.filter(({ a, b }) => a === b ? undefined : "a should be equal to b"))
) {}
Util.expectFields(A.fields, { ...baseFields, ...fields })
await Util.expectDecodeUnknownSuccess(A, { base: "base", a: 1, b: 1 })
await Util.expectDecodeUnknownSuccess(A, new A({ base: "base", a: 1, b: 1 }))
await Util.expectDecodeUnknownFailure(
A,
{ base: "base", a: 1, b: 2 },
Expand Down
13 changes: 10 additions & 3 deletions packages/vitest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @since 1.0.0
*/
import type { TesterContext } from "@vitest/expect"
import type { Tester, TesterContext } from "@vitest/expect"
import * as Duration from "effect/Duration"
import * as Effect from "effect/Effect"
import * as Equal from "effect/Equal"
Expand All @@ -24,9 +24,16 @@ export type API = TestAPI<{}>
const TestEnv = TestEnvironment.TestContext.pipe(
Layer.provide(Logger.remove(Logger.defaultLogger))
)

/** @internal */
function customTester(this: TesterContext, a: unknown, b: unknown) {
return Utils.structuralRegion(() => Equal.equals(a, b), (x, y) => this.equals(x, y))
function customTester(this: TesterContext, a: unknown, b: unknown, customTesters: Array<Tester>) {
if (!Equal.isEqual(a) || !Equal.isEqual(b)) {
return undefined
}
return Utils.structuralRegion(
() => Equal.equals(a, b),
(x, y) => this.equals(x, y, customTesters.filter((t) => t !== customTester))
)
}

/**
Expand Down
79 changes: 79 additions & 0 deletions packages/vitest/test/equality-tester.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as Cause from "effect/Cause"
import * as Data from "effect/Data"
import * as Either from "effect/Either"
import * as Exit from "effect/Exit"
import * as Option from "effect/Option"
import { describe, expect, it } from "vitest"

describe("toMatchObject", () => {
it("plain objects", () => {
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 })
})

it("Data.struct", () => {
const alice = Data.struct({ name: "Alice", age: 30 })

expect(alice).toMatchObject(Data.struct({ name: "Alice" }))
})

it("option", () => {
expect(Option.some({ a: 1, b: 2 })).toMatchObject(Option.some({ a: 1 }))
expect(Option.none()).toMatchObject(Option.none())
expect({ x: Option.some({ a: 1, b: 2 }), y: Option.none() }).toMatchObject({ x: Option.some({ a: 1 }) })

expect(Option.none()).not.toMatchObject(Option.some({ a: 1 }))
expect(Option.some({ b: 1 })).not.toMatchObject(Option.some({ a: 1 }))
expect({ x: Option.some({ a: 1, b: 2 }), y: Option.none() }).not.toMatchObject({ x: Option.some({ b: 1 }) })
expect({ x: Option.none(), y: Option.none() }).not.toMatchObject({ x: Option.some({}) })
})

it("either", () => {
expect(Either.right({ a: 1, b: 2 })).toMatchObject(Either.right({ a: 1 }))
expect(Either.left({ a: 1, b: 2 })).toMatchObject(Either.left({ a: 1 }))

expect(Either.right({ a: 1, b: 2 })).not.toMatchObject(Either.left({ a: 1 }))
expect(Either.left({ a: 1, b: 2 })).not.toMatchObject(Either.right({ a: 1 }))
})

it("either", () => {
expect(Either.right({ a: 1, b: 2 })).toMatchObject(Either.right({ a: 1 }))
expect(Either.left({ a: 1, b: 2 })).toMatchObject(Either.left({ a: 1 }))

expect(Either.right({ a: 1, b: 2 })).not.toMatchObject(Either.left({ a: 1 }))
expect(Either.left({ a: 1, b: 2 })).not.toMatchObject(Either.right({ a: 1 }))
})
})

describe.each(["toStrictEqual", "toEqual"] as const)("%s", (matcher) => {
it("either", () => {
expect(Either.right(1))[matcher](Either.right(1))
expect(Either.left(1))[matcher](Either.left(1))

expect(Either.right(2)).not[matcher](Either.right(1))
expect(Either.left(2)).not[matcher](Either.left(1))
expect(Either.left(1)).not[matcher](Either.right(1))
expect(Either.left(1)).not[matcher](Either.right(2))
})

it("exit", () => {
expect(Exit.succeed(1))[matcher](Exit.succeed(1))
expect(Exit.fail("failure"))[matcher](Exit.fail("failure"))
expect(Exit.die("defect"))[matcher](Exit.die("defect"))

expect(Exit.succeed(1)).not[matcher](Exit.succeed(2))
expect(Exit.fail("failure")).not[matcher](Exit.fail("failure1"))
expect(Exit.die("failure")).not[matcher](Exit.fail("failure1"))
expect(Exit.die("failure")).not[matcher](Exit.fail("failure1"))
expect(Exit.failCause(Cause.sequential(Cause.fail("f1"), Cause.fail("f2")))).not[matcher](
Exit.failCause(Cause.sequential(Cause.fail("f1"), Cause.fail("f3")))
)
})

it("option", () => {
expect(Option.some(2))[matcher](Option.some(2))
expect(Option.none())[matcher](Option.none())

expect(Option.some(2)).not[matcher](Option.some(1))
expect(Option.none()).not[matcher](Option.some(1))
})
})

0 comments on commit e41e911

Please sign in to comment.