diff --git a/.changeset/khaki-apples-retire.md b/.changeset/khaki-apples-retire.md new file mode 100644 index 00000000000..d116392de85 --- /dev/null +++ b/.changeset/khaki-apples-retire.md @@ -0,0 +1,6 @@ +--- +"effect": minor +"@effect/vitest": minor +--- + +Improve custom matchers diff --git a/packages/effect/src/Equal.ts b/packages/effect/src/Equal.ts index 440e36c9fa7..f6d7ac66b1d 100644 --- a/packages/effect/src/Equal.ts +++ b/packages/effect/src/Equal.ts @@ -4,7 +4,6 @@ import type { Equivalence } from "./Equivalence.js" import * as Hash from "./Hash.js" import { hasProperty } from "./Predicate.js" -import { structuralRegionState } from "./Utils.js" /** * @since 2.0.0 @@ -44,41 +43,14 @@ function compareBoth(self: unknown, that: unknown): boolean { if (selfType === "object" || selfType === "function") { if (self !== null && that !== null) { if (isEqual(self) && isEqual(that)) { - if (Hash.hash(self) === Hash.hash(that) && self[symbol](that)) { - return true - } else { - return structuralRegionState.enabled && structuralRegionState.tester - ? structuralRegionState.tester(self, that) - : false - } + return Hash.hash(self) === Hash.hash(that) && self[symbol](that) } else if (self instanceof Date && that instanceof Date) { return self.toISOString() === that.toISOString() } } - if (structuralRegionState.enabled) { - if (Array.isArray(self) && Array.isArray(that)) { - return self.length === that.length && self.every((v, i) => compareBoth(v, that[i])) - } - if (Object.getPrototypeOf(self) === Object.prototype && Object.getPrototypeOf(self) === Object.prototype) { - const keysSelf = Object.keys(self as any) - const keysThat = Object.keys(that as any) - if (keysSelf.length === keysThat.length) { - for (const key of keysSelf) { - // @ts-expect-error - if (!(key in that && compareBoth(self[key], that[key]))) { - return structuralRegionState.tester ? structuralRegionState.tester(self, that) : false - } - } - return true - } - } - return structuralRegionState.tester ? structuralRegionState.tester(self, that) : false - } } - return structuralRegionState.enabled && structuralRegionState.tester - ? structuralRegionState.tester(self, that) - : false + return false } /** diff --git a/packages/effect/src/Hash.ts b/packages/effect/src/Hash.ts index ec81836bafa..531f2ac70ec 100644 --- a/packages/effect/src/Hash.ts +++ b/packages/effect/src/Hash.ts @@ -4,7 +4,6 @@ import { pipe } from "./Function.js" import { globalValue } from "./GlobalValue.js" import { hasProperty } from "./Predicate.js" -import { structuralRegionState } from "./Utils.js" /** @internal */ const randomHashCache = globalValue( @@ -31,10 +30,6 @@ export interface Hash { * @category hashing */ export const hash: (self: A) => number = (self: A) => { - if (structuralRegionState.enabled === true) { - return 0 - } - switch (typeof self) { case "number": return number(self) diff --git a/packages/effect/src/Utils.ts b/packages/effect/src/Utils.ts index 618d0a6d7d6..003970d42fd 100644 --- a/packages/effect/src/Utils.ts +++ b/packages/effect/src/Utils.ts @@ -2,7 +2,6 @@ * @since 2.0.0 */ import { identity } from "./Function.js" -import { globalValue } from "./GlobalValue.js" import type { Kind, TypeLambda } from "./HKT.js" import { getBugErrorMessage } from "./internal/errors.js" import { isNullable, isObject } from "./Predicate.js" @@ -751,43 +750,6 @@ export function yieldWrapGet(self: YieldWrap): T { throw new Error(getBugErrorMessage("yieldWrapGet")) } -/** - * Note: this is an experimental feature made available to allow custom matchers in tests, not to be directly used yet in user code - * - * @since 3.1.1 - * @status experimental - * @category modifiers - */ -export const structuralRegionState = globalValue( - "effect/Utils/isStructuralRegion", - (): { enabled: boolean; tester: ((a: unknown, b: unknown) => boolean) | undefined } => ({ - enabled: false, - tester: undefined - }) -) - -/** - * Note: this is an experimental feature made available to allow custom matchers in tests, not to be directly used yet in user code - * - * @since 3.1.1 - * @status experimental - * @category modifiers - */ -export const structuralRegion = (body: () => A, tester?: (a: unknown, b: unknown) => boolean): A => { - const current = structuralRegionState.enabled - const currentTester = structuralRegionState.tester - structuralRegionState.enabled = true - if (tester) { - structuralRegionState.tester = tester - } - try { - return body() - } finally { - structuralRegionState.enabled = current - structuralRegionState.tester = currentTester - } -} - const tracingFunction = (name: string) => { const wrap = { [name](body: () => A) { diff --git a/packages/effect/test/Hash.test.ts b/packages/effect/test/Hash.test.ts index 3da943e6215..1ab9a62799c 100644 --- a/packages/effect/test/Hash.test.ts +++ b/packages/effect/test/Hash.test.ts @@ -1,36 +1,9 @@ -import * as Equal from "effect/Equal" import { absurd, identity } from "effect/Function" import * as Hash from "effect/Hash" import * as HashSet from "effect/HashSet" -import * as Option from "effect/Option" -import * as Utils from "effect/Utils" import { describe, expect, it } from "vitest" describe("Hash", () => { - it("structural", () => { - const a = { foo: { bar: "ok", baz: { arr: [0, 1, 2] } } } - const b = { foo: { bar: "ok", baz: { arr: [0, 1, 2] } } } - expect(Hash.hash(a)).not.toBe(Hash.hash(b)) - expect(Equal.equals(a, b)).toBe(false) - Utils.structuralRegion(() => { - expect(Hash.hash(a)).toBe(Hash.hash(b)) - expect(Equal.equals(a, b)).toBe(true) - }) - expect(Hash.hash(a)).not.toBe(Hash.hash(b)) - expect(Equal.equals(a, b)).toBe(false) - }) - it("structural cached", () => { - const a = Option.some({ foo: { bar: "ok", baz: { arr: [0, 1, 2] } } }) - const b = Option.some({ foo: { bar: "ok", baz: { arr: [0, 1, 2] } } }) - expect(Hash.hash(a)).not.toBe(Hash.hash(b)) - expect(Equal.equals(a, b)).toBe(false) - Utils.structuralRegion(() => { - expect(Hash.hash(a)).toBe(Hash.hash(b)) - expect(Equal.equals(a, b)).toBe(true) - }) - expect(Hash.hash(a)).not.toBe(Hash.hash(b)) - expect(Equal.equals(a, b)).toBe(false) - }) it("exports", () => { expect(Hash.string).exist expect(Hash.structureKeys).exist diff --git a/packages/vitest/src/internal.ts b/packages/vitest/src/internal.ts index 1b8cb9658a8..d4e138e0b00 100644 --- a/packages/vitest/src/internal.ts +++ b/packages/vitest/src/internal.ts @@ -3,18 +3,20 @@ */ import type { Tester, TesterContext } from "@vitest/expect" import * as Cause from "effect/Cause" +import * as Chunk from "effect/Chunk" import * as Duration from "effect/Duration" import * as Effect from "effect/Effect" -import * as Equal from "effect/Equal" +import * as Either from "effect/Either" import * as Exit from "effect/Exit" import { flow, identity, pipe } from "effect/Function" +import * as HashSet from "effect/HashSet" import * as Layer from "effect/Layer" import * as Logger from "effect/Logger" +import * as Option from "effect/Option" import * as Schedule from "effect/Schedule" import type * 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 * as V from "vitest" import type * as Vitest from "./index.js" @@ -41,14 +43,26 @@ const TestEnv = TestEnvironment.TestContext.pipe( ) /** @internal */ -function customTester(this: TesterContext, a: unknown, b: unknown, customTesters: Array) { - if (!Equal.isEqual(a) || !Equal.isEqual(b)) { - return undefined +function customTester(this: TesterContext, _a: unknown, _b: unknown, _customTesters: Array) { + if (Chunk.isChunk(_a) && Chunk.isChunk(_b)) { + return this.equals(Array.from(_a), Array.from(_b), _customTesters) } - return Utils.structuralRegion( - () => Equal.equals(a, b), - (x, y) => this.equals(x, y, customTesters.filter((t) => t !== customTester)) - ) + if (Option.isOption(_a) && Option.isOption(_b)) { + return _a._tag === _b._tag && (_a._tag === "None" || this.equals(_a.value, (_b as any).value, _customTesters)) + } + if (Either.isEither(_a) && Either.isEither(_b)) { + return (Either.isLeft(_a) && Either.isLeft(_b) && this.equals(_a.left, _b.left, _customTesters)) || + (Either.isRight(_a) && Either.isRight(_b) && this.equals(_a.right, _b.right, _customTesters)) + } + if (Cause.isCause(_a) && Cause.isCause(_b)) { + return this.equals(Cause.failures(_a), Cause.failures(_b), _customTesters) && + this.equals(Cause.defects(_a), Cause.defects(_b), _customTesters) && + this.equals(Cause.interruptors(_a), Cause.interruptors(_b), _customTesters) + } + if (HashSet.isHashSet(_a) && HashSet.isHashSet(_b)) { + return this.equals(new Set(_a), new Set(_b), _customTesters) + } + return undefined } /** @internal */