diff --git a/packages/probitas-expect/mixin/__snapshots__/object_value_mixin_test.ts.snap b/packages/probitas-expect/mixin/__snapshots__/object_value_mixin_test.ts.snap index f838f0e..c8627ed 100644 --- a/packages/probitas-expect/mixin/__snapshots__/object_value_mixin_test.ts.snap +++ b/packages/probitas-expect/mixin/__snapshots__/object_value_mixin_test.ts.snap @@ -109,10 +109,10 @@ Context (packages/probitas-expect/mixin/object_value_mixin_test.ts:236:5) 236│ await assertSnapshotWithoutColors( 237│ t, ┆ - 509│ t, - 510│ catchError(() => applied.toHaveUserMatching({ name: "Bob" })).message, + 558│ t, + 559│ catchError(() => applied.toHaveUserMatching({ name: "Bob" })).message, │ ^ - 511│ );' + 560│ );' `; snapshot[`createObjectValueMixin - toHaveUserMatching with source context 2`] = ` @@ -132,8 +132,8 @@ snapshot[`createObjectValueMixin - toHaveUserMatching with source context 2`] = 251│ const applied = mixin({ dummy: true }); 252│ await assertSnapshotWithoutColors(  ┆ - 526│ t, - 527│ catchError(() => applied.toHaveUserMatching({ name: "Bob" })).message, + 575│ t, + 576│ catchError(() => applied.toHaveUserMatching({ name: "Bob" })).message,  │ ^ - 528│ );' + 577│ );' `; diff --git a/packages/probitas-expect/mixin/object_value_mixin.ts b/packages/probitas-expect/mixin/object_value_mixin.ts index e5878c8..9c1c626 100644 --- a/packages/probitas-expect/mixin/object_value_mixin.ts +++ b/packages/probitas-expect/mixin/object_value_mixin.ts @@ -4,7 +4,6 @@ import { Any, buildMatchingExpected, buildPropertyExpected, - ensureNonNullish, formatValue, toPascalCase, tryOk, @@ -318,18 +317,9 @@ export function createObjectValueMixin< const obj = getter.call(this); let matcherError: Error | undefined; - let propertyExists = false; try { - // @std/expect mutates array keyPath, so we need to copy it - const keyPathCopy = Array.isArray(keyPath) ? [...keyPath] : keyPath; - stdExpect(obj).toHaveProperty(keyPathCopy); - propertyExists = true; - const keyPathStr = Array.isArray(keyPath) ? keyPath.join(".") : keyPath; - const value = ensureNonNullish( - getPropertyValue(obj, keyPath), - keyPathStr, - ); + const value = getPropertyValue(obj, keyPath); matcher(value); } catch (error) { if (error instanceof Error) { @@ -344,16 +334,6 @@ export function createObjectValueMixin< if (isNegated ? passes : !passes) { const keyPathStr = Array.isArray(keyPath) ? keyPath.join(".") : keyPath; - if (!propertyExists && !isNegated) { - throw createExpectationError({ - message: - `Expected ${valueName} property "${keyPathStr}" to exist and satisfy the matcher, but it does not exist`, - expectOrigin: config.expectOrigin, - theme: config.theme, - subject: config.subject, - }); - } - throw createExpectationError({ message: isNegated ? `Expected ${valueName} property "${keyPathStr}" to not satisfy the matcher, but it did` diff --git a/packages/probitas-expect/mixin/object_value_mixin_test.ts b/packages/probitas-expect/mixin/object_value_mixin_test.ts index 3fec7e6..ed446df 100644 --- a/packages/probitas-expect/mixin/object_value_mixin_test.ts +++ b/packages/probitas-expect/mixin/object_value_mixin_test.ts @@ -492,6 +492,55 @@ Deno.test("createObjectValueMixin - toHaveUserPropertySatisfying", async (t) => ).message, ); }); + + await t.step("success - null value", () => { + const mixin = createObjectValueMixin( + () => ({ value: null }), + () => false, + { valueName: "data" }, + ); + const applied = mixin({ dummy: true }); + assertEquals( + applied.toHaveDataPropertySatisfying("value", (v) => { + if (v !== null) throw new Error("Must be null"); + }), + applied, + ); + }); + + await t.step("success - nested path with null intermediate", () => { + const mixin = createObjectValueMixin( + () => ({ user: null }), + () => false, + { valueName: "data" }, + ); + const applied = mixin({ dummy: true }); + assertEquals( + applied.toHaveDataPropertySatisfying("user.profile.age", (v) => { + if (v !== undefined) { + throw new Error("Must be undefined when intermediate is null"); + } + }), + applied, + ); + }); + + await t.step("success - nested path with undefined intermediate", () => { + const mixin = createObjectValueMixin( + () => ({ user: { profile: undefined } }), + () => false, + { valueName: "data" }, + ); + const applied = mixin({ dummy: true }); + assertEquals( + applied.toHaveDataPropertySatisfying("user.profile.age", (v) => { + if (v !== undefined) { + throw new Error("Must be undefined when intermediate is undefined"); + } + }), + applied, + ); + }); }); Deno.test("createObjectValueMixin - toHaveUserMatching with source context", async (t) => {