From 41d0f97fc51a080ed60576c335e8927a02378ff6 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 17 Mar 2025 10:47:13 -0500 Subject: [PATCH 01/14] WIP: toBeEmpty function --- .../src/lib/ToBeEmptyElementAssertion.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/native/src/lib/ToBeEmptyElementAssertion.ts diff --git a/packages/native/src/lib/ToBeEmptyElementAssertion.ts b/packages/native/src/lib/ToBeEmptyElementAssertion.ts new file mode 100644 index 0000000..3b59632 --- /dev/null +++ b/packages/native/src/lib/ToBeEmptyElementAssertion.ts @@ -0,0 +1,51 @@ +import { Assertion, AssertionError } from "@assertive-ts/core"; +import { ReactTestInstance } from "react-test-renderer"; + +export class ToBeEmptyElementAssertion extends Assertion { + public constructor(actual: ReactTestInstance) { + super(actual); + } + + public override toString = (): string => { + if (this.actual === null) { + return "null"; + } + + return `<${this.actual.type.toString()} ... />`; + }; + + /** + * Check if the element is empty. + * + * @example + * ``` + * expect(element).toBeEmptyElement(); + * ``` + * + * @returns the assertion instance + */ + public toBeEmptyElement(): this { + const error = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} to be empty.`, + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} to NOT be empty.`, + }); + + return this.execute({ + assertWhen: this.isEmpty(this.actual), + error, + invertedError, + }); + } + + private isEmpty(element: ReactTestInstance): boolean { + if(!element?.props?.children) { + return true; + } + + return element?.props?.children.length === 0; + } +} \ No newline at end of file From 6dfdd21db7f766971de9024499f662ca890d4443 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 17 Mar 2025 10:47:33 -0500 Subject: [PATCH 02/14] toBeEmpty tests --- .../lib/ToBeEmptyElementAssertion.test.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx diff --git a/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx b/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx new file mode 100644 index 0000000..e28a680 --- /dev/null +++ b/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx @@ -0,0 +1,37 @@ +import { AssertionError, expect } from "@assertive-ts/core"; +import { render } from "@testing-library/react-native"; +import { View, Text } from "react-native"; + +import { ToBeEmptyElementAssertion } from "../../src/lib/ToBeEmptyElementAssertion"; + +describe("[Unit] ToBeEmptyElementAssertion.test.ts", () => { + describe(".toBeEmptyElement", () => { + context("when the element is empty", () => { + it("returns the assertion instance", () => { + const element = render(); + const test = new ToBeEmptyElementAssertion(element.getByTestId("id")); + + expect(test.toBeEmptyElement()).toBe(test); + expect(() => test.not.toBeEmptyElement()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to NOT be empty."); + }); + }); + + context("when the element is NOT empty", () => { + it("throws an error", () => { + const element = render( + + {"Not empty"} + , + ); + const test = new ToBeEmptyElementAssertion(element.getByTestId("id")); + + expect(test.not.toBeEmptyElement()).toBeEqual(test); + expect(() => test.toBeEmptyElement()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to be empty."); + }); + }); + }); +}); From debfd8ae11f4eb9277c0626354a4274d697539f6 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 17 Mar 2025 13:01:15 -0500 Subject: [PATCH 03/14] Fix: use element 'children' --- packages/native/src/lib/ToBeEmptyElementAssertion.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/native/src/lib/ToBeEmptyElementAssertion.ts b/packages/native/src/lib/ToBeEmptyElementAssertion.ts index 3b59632..ab0f2fd 100644 --- a/packages/native/src/lib/ToBeEmptyElementAssertion.ts +++ b/packages/native/src/lib/ToBeEmptyElementAssertion.ts @@ -42,10 +42,16 @@ export class ToBeEmptyElementAssertion extends Assertion { } private isEmpty(element: ReactTestInstance): boolean { - if(!element?.props?.children) { + const children = element?.children; + + if (!children) { return true; } - return element?.props?.children.length === 0; + if (Array.isArray(children)) { + return children.length === 0; + } + + return false; } -} \ No newline at end of file +} From 496edc59a1a507808cb2f6429cc6542dad361354 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 17 Mar 2025 15:45:03 -0500 Subject: [PATCH 04/14] CR: Move isEmpty to a helper file --- .../src/lib/ToBeEmptyElementAssertion.ts | 21 ++++++------------- packages/native/src/lib/helpers/helpers.ts | 15 +++++++++++++ 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 packages/native/src/lib/helpers/helpers.ts diff --git a/packages/native/src/lib/ToBeEmptyElementAssertion.ts b/packages/native/src/lib/ToBeEmptyElementAssertion.ts index ab0f2fd..b6bede4 100644 --- a/packages/native/src/lib/ToBeEmptyElementAssertion.ts +++ b/packages/native/src/lib/ToBeEmptyElementAssertion.ts @@ -1,6 +1,11 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; import { ReactTestInstance } from "react-test-renderer"; +import { isEmpty } from "./helpers/helpers"; + +/** + * Assertion for checking if a React element is empty. + */ export class ToBeEmptyElementAssertion extends Assertion { public constructor(actual: ReactTestInstance) { super(actual); @@ -35,23 +40,9 @@ export class ToBeEmptyElementAssertion extends Assertion { }); return this.execute({ - assertWhen: this.isEmpty(this.actual), + assertWhen: isEmpty(this.actual), error, invertedError, }); } - - private isEmpty(element: ReactTestInstance): boolean { - const children = element?.children; - - if (!children) { - return true; - } - - if (Array.isArray(children)) { - return children.length === 0; - } - - return false; - } } diff --git a/packages/native/src/lib/helpers/helpers.ts b/packages/native/src/lib/helpers/helpers.ts new file mode 100644 index 0000000..afaf4d0 --- /dev/null +++ b/packages/native/src/lib/helpers/helpers.ts @@ -0,0 +1,15 @@ +import { ReactTestInstance } from "react-test-renderer"; + +export function isEmpty(element: ReactTestInstance): boolean { + const children = element?.children; + + if (!children) { + return true; + } + + if (Array.isArray(children)) { + return children.length === 0; + } + + return false; +} From 1f782720dc31584f1393299a46601fc39da39a9a Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 17 Mar 2025 15:55:33 -0500 Subject: [PATCH 05/14] Add isEmpty doc. --- .../src/lib/ToBeEmptyElementAssertion.ts | 2 +- packages/native/src/lib/helpers/helpers.ts | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/native/src/lib/ToBeEmptyElementAssertion.ts b/packages/native/src/lib/ToBeEmptyElementAssertion.ts index b6bede4..43d71b2 100644 --- a/packages/native/src/lib/ToBeEmptyElementAssertion.ts +++ b/packages/native/src/lib/ToBeEmptyElementAssertion.ts @@ -40,7 +40,7 @@ export class ToBeEmptyElementAssertion extends Assertion { }); return this.execute({ - assertWhen: isEmpty(this.actual), + assertWhen: isEmpty(this.actual.children), error, invertedError, }); diff --git a/packages/native/src/lib/helpers/helpers.ts b/packages/native/src/lib/helpers/helpers.ts index afaf4d0..76e6f2a 100644 --- a/packages/native/src/lib/helpers/helpers.ts +++ b/packages/native/src/lib/helpers/helpers.ts @@ -1,14 +1,16 @@ -import { ReactTestInstance } from "react-test-renderer"; - -export function isEmpty(element: ReactTestInstance): boolean { - const children = element?.children; - - if (!children) { +/** + * Checks if a value is empty. + * + * @param value - The value to check. + * @returns `true` if the value is empty, `false` otherwise. + */ +export function isEmpty(value: unknown): boolean { + if (!value) { return true; } - if (Array.isArray(children)) { - return children.length === 0; + if (Array.isArray(value)) { + return value.length === 0; } return false; From f305bb4cf72237a2e321a472fc620fec508b37db Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Tue, 18 Mar 2025 13:01:54 -0500 Subject: [PATCH 06/14] Update: CR comments --- packages/native/src/lib/ElementAssertion.ts | 12 +++++------ ...mentAssertion.ts => ToBeEmptyAssertion.ts} | 16 ++++++--------- packages/native/src/lib/helpers/helpers.ts | 16 +++++++++++++++ .../native/test/lib/ElementAssertion.test.tsx | 12 +++++------ .../lib/ToBeEmptyElementAssertion.test.tsx | 20 +++++++++---------- 5 files changed, 43 insertions(+), 33 deletions(-) rename packages/native/src/lib/{ToBeEmptyElementAssertion.ts => ToBeEmptyAssertion.ts} (67%) diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index d1350b9..aa9d680 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -2,17 +2,15 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; import { get } from "dot-prop-immutable"; import { ReactTestInstance } from "react-test-renderer"; +import { instanceToString } from "./helpers/helpers"; + export class ElementAssertion extends Assertion { public constructor(actual: ReactTestInstance) { super(actual); } public override toString = (): string => { - if (this.actual === null) { - return "null"; - } - - return `<${this.actual.type.toString()} ... />`; + return instanceToString(this.actual); }; /** @@ -32,7 +30,7 @@ export class ElementAssertion extends Assertion { }); const invertedError = new AssertionError({ actual: this.actual, - message: `Expected element ${this.toString()} to NOT be disabled.`, + message: `Expected element ${this.toString()} NOT to be disabled.`, }); return this.execute({ @@ -58,7 +56,7 @@ export class ElementAssertion extends Assertion { }); const invertedError = new AssertionError({ actual: this.actual, - message: `Expected element ${this.toString()} to NOT be enabled.`, + message: `Expected element ${this.toString()} NOT to be enabled.`, }); return this.execute({ diff --git a/packages/native/src/lib/ToBeEmptyElementAssertion.ts b/packages/native/src/lib/ToBeEmptyAssertion.ts similarity index 67% rename from packages/native/src/lib/ToBeEmptyElementAssertion.ts rename to packages/native/src/lib/ToBeEmptyAssertion.ts index 43d71b2..b6bd603 100644 --- a/packages/native/src/lib/ToBeEmptyElementAssertion.ts +++ b/packages/native/src/lib/ToBeEmptyAssertion.ts @@ -1,22 +1,18 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; import { ReactTestInstance } from "react-test-renderer"; -import { isEmpty } from "./helpers/helpers"; +import { instanceToString, isEmpty } from "./helpers/helpers"; /** * Assertion for checking if a React element is empty. */ -export class ToBeEmptyElementAssertion extends Assertion { +export class ToBeEmptyAssertion extends Assertion { public constructor(actual: ReactTestInstance) { super(actual); } public override toString = (): string => { - if (this.actual === null) { - return "null"; - } - - return `<${this.actual.type.toString()} ... />`; + return instanceToString(this.actual); }; /** @@ -24,19 +20,19 @@ export class ToBeEmptyElementAssertion extends Assertion { * * @example * ``` - * expect(element).toBeEmptyElement(); + * expect(element).toBeEmpty(); * ``` * * @returns the assertion instance */ - public toBeEmptyElement(): this { + public toBeEmpty(): this { const error = new AssertionError({ actual: this.actual, message: `Expected element ${this.toString()} to be empty.`, }); const invertedError = new AssertionError({ actual: this.actual, - message: `Expected element ${this.toString()} to NOT be empty.`, + message: `Expected element ${this.toString()} NOT to be empty.`, }); return this.execute({ diff --git a/packages/native/src/lib/helpers/helpers.ts b/packages/native/src/lib/helpers/helpers.ts index 76e6f2a..6ab4db1 100644 --- a/packages/native/src/lib/helpers/helpers.ts +++ b/packages/native/src/lib/helpers/helpers.ts @@ -1,3 +1,5 @@ +import { ReactTestInstance } from "react-test-renderer"; + /** * Checks if a value is empty. * @@ -15,3 +17,17 @@ export function isEmpty(value: unknown): boolean { return false; } + +/** + * Converts a ReactTestInstance to a string representation. + * + * @param instance - The ReactTestInstance to convert. + * @returns A string representation of the instance. + */ +export function instanceToString(instance: ReactTestInstance | null): string { + if (instance === null) { + return "null"; + } + + return `<${instance.type.toString()} ... />`; +} diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index 6db8aba..bdaabb4 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -34,7 +34,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { .toHaveMessage("Expected element to be disabled."); expect(() => test.not.toBeEnabled()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be enabled."); + .toHaveMessage("Expected element NOT to be enabled."); }); }); }); @@ -59,7 +59,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { .toHaveMessage("Expected element to be enabled."); expect(() => parent.not.toBeDisabled()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be disabled."); + .toHaveMessage("Expected element NOT to be disabled."); }); }); @@ -83,13 +83,13 @@ describe("[Unit] ElementAssertion.test.ts", () => { .toHaveMessage("Expected element to be disabled."); expect(() => parent.not.toBeEnabled()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be enabled."); + .toHaveMessage("Expected element NOT to be enabled."); expect(() => child.toBeDisabled()) .toThrowError(AssertionError) .toHaveMessage("Expected element to be disabled."); expect(() => child.not.toBeEnabled()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be enabled."); + .toHaveMessage("Expected element NOT to be enabled."); }); }); }); @@ -114,7 +114,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { .toHaveMessage("Expected element to be enabled."); expect(() => child.not.toBeDisabled()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be disabled."); + .toHaveMessage("Expected element NOT to be disabled."); }); it("returns error for parent element", () => { @@ -124,7 +124,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { .toHaveMessage("Expected element to be disabled."); expect(() => parent.not.toBeEnabled()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be enabled."); + .toHaveMessage("Expected element NOT to be enabled."); }); }); }); diff --git a/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx b/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx index e28a680..86fcb21 100644 --- a/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx +++ b/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx @@ -2,19 +2,19 @@ import { AssertionError, expect } from "@assertive-ts/core"; import { render } from "@testing-library/react-native"; import { View, Text } from "react-native"; -import { ToBeEmptyElementAssertion } from "../../src/lib/ToBeEmptyElementAssertion"; +import { ToBeEmptyAssertion } from "../../src/lib/ToBeEmptyAssertion"; -describe("[Unit] ToBeEmptyElementAssertion.test.ts", () => { - describe(".toBeEmptyElement", () => { +describe("[Unit] toBeEmptyAssertion.test.ts", () => { + describe(".toBeEmpty", () => { context("when the element is empty", () => { it("returns the assertion instance", () => { const element = render(); - const test = new ToBeEmptyElementAssertion(element.getByTestId("id")); + const test = new ToBeEmptyAssertion(element.getByTestId("id")); - expect(test.toBeEmptyElement()).toBe(test); - expect(() => test.not.toBeEmptyElement()) + expect(test.toBeEmpty()).toBe(test); + expect(() => test.not.toBeEmpty()) .toThrowError(AssertionError) - .toHaveMessage("Expected element to NOT be empty."); + .toHaveMessage("Expected element NOT to be empty."); }); }); @@ -25,10 +25,10 @@ describe("[Unit] ToBeEmptyElementAssertion.test.ts", () => { {"Not empty"} , ); - const test = new ToBeEmptyElementAssertion(element.getByTestId("id")); + const test = new ToBeEmptyAssertion(element.getByTestId("id")); - expect(test.not.toBeEmptyElement()).toBeEqual(test); - expect(() => test.toBeEmptyElement()) + expect(test.not.toBeEmpty()).toBeEqual(test); + expect(() => test.toBeEmpty()) .toThrowError(AssertionError) .toHaveMessage("Expected element to be empty."); }); From aed8344bb10a1626210ab13fbff4808067322f13 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Thu, 20 Mar 2025 12:53:04 -0500 Subject: [PATCH 07/14] Move toBeEmptyAssertion to the element assertions --- packages/native/src/lib/ElementAssertion.ts | 29 +++++++++++- packages/native/src/lib/ToBeEmptyAssertion.ts | 44 ------------------- .../native/test/lib/ElementAssertion.test.tsx | 31 +++++++++++++ .../lib/ToBeEmptyElementAssertion.test.tsx | 37 ---------------- 4 files changed, 59 insertions(+), 82 deletions(-) delete mode 100644 packages/native/src/lib/ToBeEmptyAssertion.ts delete mode 100644 packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index aa9d680..283516c 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -2,7 +2,7 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; import { get } from "dot-prop-immutable"; import { ReactTestInstance } from "react-test-renderer"; -import { instanceToString } from "./helpers/helpers"; +import { instanceToString, isEmpty } from "./helpers/helpers"; export class ElementAssertion extends Assertion { public constructor(actual: ReactTestInstance) { @@ -66,6 +66,33 @@ export class ElementAssertion extends Assertion { }); } + /** + * Check if the element is empty. + * + * @example + * ``` + * expect(element).toBeEmpty(); + * ``` + * + * @returns the assertion instance + */ + public toBeEmpty(): this { + const error = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} to be empty.`, + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} NOT to be empty.`, + }); + + return this.execute({ + assertWhen: isEmpty(this.actual.children), + error, + invertedError, + }); + } + private isElementDisabled(element: ReactTestInstance): boolean { const { type } = element; const elementType = type.toString(); diff --git a/packages/native/src/lib/ToBeEmptyAssertion.ts b/packages/native/src/lib/ToBeEmptyAssertion.ts deleted file mode 100644 index b6bd603..0000000 --- a/packages/native/src/lib/ToBeEmptyAssertion.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Assertion, AssertionError } from "@assertive-ts/core"; -import { ReactTestInstance } from "react-test-renderer"; - -import { instanceToString, isEmpty } from "./helpers/helpers"; - -/** - * Assertion for checking if a React element is empty. - */ -export class ToBeEmptyAssertion extends Assertion { - public constructor(actual: ReactTestInstance) { - super(actual); - } - - public override toString = (): string => { - return instanceToString(this.actual); - }; - - /** - * Check if the element is empty. - * - * @example - * ``` - * expect(element).toBeEmpty(); - * ``` - * - * @returns the assertion instance - */ - public toBeEmpty(): this { - const error = new AssertionError({ - actual: this.actual, - message: `Expected element ${this.toString()} to be empty.`, - }); - const invertedError = new AssertionError({ - actual: this.actual, - message: `Expected element ${this.toString()} NOT to be empty.`, - }); - - return this.execute({ - assertWhen: isEmpty(this.actual.children), - error, - invertedError, - }); - } -} diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index bdaabb4..ce3b441 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -3,6 +3,7 @@ import { render } from "@testing-library/react-native"; import { View, TextInput, + Text, } from "react-native"; import { ElementAssertion } from "../../src/lib/ElementAssertion"; @@ -129,4 +130,34 @@ describe("[Unit] ElementAssertion.test.ts", () => { }); }); }); + + describe(".toBeEmpty", () => { + context("when the element is empty", () => { + it("returns the assertion instance", () => { + const element = render(); + const test = new ElementAssertion(element.getByTestId("id")); + + expect(test.toBeEmpty()).toBe(test); + expect(() => test.not.toBeEmpty()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to be empty."); + }); + }); + + context("when the element is NOT empty", () => { + it("throws an error", () => { + const element = render( + + {"Not empty"} + , + ); + const test = new ElementAssertion(element.getByTestId("id")); + + expect(test.not.toBeEmpty()).toBeEqual(test); + expect(() => test.toBeEmpty()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to be empty."); + }); + }); + }); }); diff --git a/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx b/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx deleted file mode 100644 index 86fcb21..0000000 --- a/packages/native/test/lib/ToBeEmptyElementAssertion.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { AssertionError, expect } from "@assertive-ts/core"; -import { render } from "@testing-library/react-native"; -import { View, Text } from "react-native"; - -import { ToBeEmptyAssertion } from "../../src/lib/ToBeEmptyAssertion"; - -describe("[Unit] toBeEmptyAssertion.test.ts", () => { - describe(".toBeEmpty", () => { - context("when the element is empty", () => { - it("returns the assertion instance", () => { - const element = render(); - const test = new ToBeEmptyAssertion(element.getByTestId("id")); - - expect(test.toBeEmpty()).toBe(test); - expect(() => test.not.toBeEmpty()) - .toThrowError(AssertionError) - .toHaveMessage("Expected element NOT to be empty."); - }); - }); - - context("when the element is NOT empty", () => { - it("throws an error", () => { - const element = render( - - {"Not empty"} - , - ); - const test = new ToBeEmptyAssertion(element.getByTestId("id")); - - expect(test.not.toBeEmpty()).toBeEqual(test); - expect(() => test.toBeEmpty()) - .toThrowError(AssertionError) - .toHaveMessage("Expected element to be empty."); - }); - }); - }); -}); From 22e54c9f23033d912534aa53c4843848dc8d139b Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Fri, 21 Mar 2025 17:11:32 -0500 Subject: [PATCH 08/14] Add toBeVisible matcher --- packages/native/src/lib/ElementAssertion.ts | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index 283516c..0872de1 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -93,6 +93,33 @@ export class ElementAssertion extends Assertion { }); } + /** + * Check if the element is visible. + * + * @example + * ``` + * expect(element).toBeVisible(); + * ``` + * + * @returns the assertion instance + */ + public toBeVisible(): this { + const error = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} to be visible.`, + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} NOT to be visible.`, + }); + + return this.execute({ + assertWhen: this.isElementVisible(this.actual) && !this.isAncestorNotVisible(this.actual), + error, + invertedError, + }); + } + private isElementDisabled(element: ReactTestInstance): boolean { const { type } = element; const elementType = type.toString(); @@ -112,4 +139,24 @@ export class ElementAssertion extends Assertion { const { parent } = element; return parent !== null && (this.isElementDisabled(element) || this.isAncestorDisabled(parent)); } + + private isElementVisible(element: ReactTestInstance): boolean { + const { type } = element; + const elementType = type.toString(); + if (elementType === "Modal" && !element?.props?.visible === true) { + return false; + } + + return ( + get(element, "props.style.display") !== "none" + && get(element, "props.style.opacity") !== 0 + && get(element, "props.accessibilityElementsHidden") !== true + && get(element, "props.importantForAccessibility") !== "no-hide-descendants" + ); + } + + private isAncestorNotVisible(element: ReactTestInstance): boolean { + const { parent } = element; + return parent !== null && (!this.isElementVisible(element) || this.isAncestorNotVisible(parent)); + } } From 05a57e916a45852e622d330a5b126c72591fb4c1 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Fri, 21 Mar 2025 17:11:42 -0500 Subject: [PATCH 09/14] Add tests for toBeVisible --- .../native/test/lib/ElementAssertion.test.tsx | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index ce3b441..0b42a3a 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -4,6 +4,7 @@ import { View, TextInput, Text, + Modal, } from "react-native"; import { ElementAssertion } from "../../src/lib/ElementAssertion"; @@ -160,4 +161,114 @@ describe("[Unit] ElementAssertion.test.ts", () => { }); }); }); + + describe (".toBeVisible", () => { + context("when the modal is visible", () => { + it("returns the assertion instance", () => { + const element = render( + , + ); + const test = new ElementAssertion(element.getByTestId("id")); + + expect(test.toBeVisible()).toBe(test); + expect(() => test.not.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to be visible."); + }); + }); + + context("when the element contains 'display' property", () => { + it("returns the assertion instance", () => { + const element = render( + , + ); + const test = new ElementAssertion(element.getByTestId("id")); + + expect(test.toBeVisible()).toBe(test); + expect(() => test.not.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to be visible."); + }); + }); + + context("when the element contains 'accessibilityElementsHidden' property", () => { + it("returns the assertion instance", () => { + const element = render( + , + ); + const test = new ElementAssertion(element.getByTestId("id")); + + expect(test.toBeVisible()).toBe(test); + expect(() => test.not.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to be visible."); + }); + }); + + context("when the element contains 'importantForAccessibility' property", () => { + it("returns the assertion instance", () => { + const element = render( + , + ); + const test = new ElementAssertion(element.getByTestId("id")); + + expect(test.toBeVisible()).toBe(test); + expect(() => test.not.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to be visible."); + }); + }); + + context("when the parent element contains 'opacity' property", () => { + context("if parent opacity = 0", () => { + const element = render( + + + , + ); + + const parent = new ElementAssertion(element.getByTestId("parentId")); + const child = new ElementAssertion(element.getByTestId("childId")); + + it("returns assertion instance for NOT visible elements", () => { + expect(parent.not.toBeVisible()).toBeEqual(parent); + expect(child.not.toBeVisible()).toBeEqual(child); + }); + + it("throws an error for visible elements", () => { + expect(() => parent.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to be visible."); + expect(() => child.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to be visible."); + }); + }); + + context("if child opacity = 0", () => { + const element = render( + + + , + ); + + const parent = new ElementAssertion(element.getByTestId("parentId")); + const child = new ElementAssertion(element.getByTestId("childId")); + + it("returns assertion instance for NOT visible elements", () => { + expect(parent.toBeVisible()).toBeEqual(parent); + expect(child.not.toBeVisible()).toBeEqual(child); + }); + + it("throws an error for visible elements", () => { + expect(() => parent.not.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to be visible."); + expect(() => child.toBeVisible()) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to be visible."); + }); + }); + }); + }); }); From abf480acce5901a7ff480c48ecdae625e09463e9 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Fri, 21 Mar 2025 17:14:51 -0500 Subject: [PATCH 10/14] Fix: Update JSDocs --- packages/native/src/lib/ElementAssertion.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index 0872de1..4a0570e 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -41,7 +41,7 @@ export class ElementAssertion extends Assertion { } /** - * Check if the component is enabled. + * Check if the component is enabled and has not been disabled by an ancestor. * * @example * ``` @@ -94,7 +94,7 @@ export class ElementAssertion extends Assertion { } /** - * Check if the element is visible. + * Check if the element is visible and has not been hidden by an ancestor. * * @example * ``` From 0c30feea875359bbce9cb6d917b2b01d3db291c1 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 24 Mar 2025 15:30:55 -0500 Subject: [PATCH 11/14] toContainElement() assertion --- packages/native/src/lib/ElementAssertion.ts | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index 4a0570e..4abdb73 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -120,6 +120,50 @@ export class ElementAssertion extends Assertion { }); } + /** + * Check if an element is contained within another element. + * + * @example + * ``` + * expect(parent).toContainElement(child); + * ``` + * + * @param element - The element to check for. + * @returns the assertion instance + */ + public toContainElement(element: ReactTestInstance): this { + const error = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} to contain element ${instanceToString(element)}.`, + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected element ${this.toString()} NOT to contain element ${instanceToString(element)}.`, + }); + + const isElementContained = ( + parentElement: ReactTestInstance, + childElement: ReactTestInstance, + ): boolean => { + if (parentElement === null || childElement === null) { + return false; + } + + return ( + parentElement.findAll( + node => + node.type === childElement.type && node.props === childElement.props, + ).length > 0 + ); + }; + + return this.execute({ + assertWhen: isElementContained(this.actual, element), + error, + invertedError, + }); + } + private isElementDisabled(element: ReactTestInstance): boolean { const { type } = element; const elementType = type.toString(); From 05ef646a2501c84b63dcb5c533f3ef0d0b4fb4e2 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 24 Mar 2025 15:36:10 -0500 Subject: [PATCH 12/14] Add tests for toContainElement() --- .../native/test/lib/ElementAssertion.test.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index 0b42a3a..5cadae1 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -271,4 +271,50 @@ describe("[Unit] ElementAssertion.test.ts", () => { }); }); }); + + describe (".toContainElement", () => { + const element = render( + + + + + + , + ); + + const container = element.getByTestId("grandParentId"); + const containerElementAssertion = new ElementAssertion(container); + const parent = element.getByTestId("parentId"); + const parentElementAssertion = new ElementAssertion(parent); + const child = element.getByTestId("childId"); + const text = element.getByTestId("textId"); + + context("when the container element contains the target element", () => { + it("returns the assertion instance", () => { + expect(containerElementAssertion.toContainElement(parent)).toBe(containerElementAssertion); + expect(containerElementAssertion.toContainElement(child)).toBe(containerElementAssertion); + expect(containerElementAssertion.toContainElement(text)).toBe(containerElementAssertion); + expect(parentElementAssertion.toContainElement(child)).toBe(parentElementAssertion); + }); + + it("returns the assertion instance for negated assertions when the target element is not contained", () => { + expect(parentElementAssertion.not.toContainElement(text)).toBe(parentElementAssertion); + expect(parentElementAssertion.not.toContainElement(container)).toBe(parentElementAssertion); + }); + }); + + context("when the container element does NOT contain the target element", () => { + it("throws an error", () => { + expect(() => containerElementAssertion.not.toContainElement(parent)) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to contain element ."); + expect(() => containerElementAssertion.not.toContainElement(text)) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to contain element ."); + expect(() => parentElementAssertion.toContainElement(text)) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to contain element ."); + }); + }); + }); }); From fda2999e3a85cfb67c34dc28593f7c9d5a74f8df Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Mon, 24 Mar 2025 15:49:32 -0500 Subject: [PATCH 13/14] fix: assertions on tests --- packages/native/test/lib/ElementAssertion.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index 5cadae1..b657d36 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -298,8 +298,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { }); it("returns the assertion instance for negated assertions when the target element is not contained", () => { - expect(parentElementAssertion.not.toContainElement(text)).toBe(parentElementAssertion); - expect(parentElementAssertion.not.toContainElement(container)).toBe(parentElementAssertion); + expect(parentElementAssertion.not.toContainElement(text)).toBeEqual(parentElementAssertion); + expect(parentElementAssertion.not.toContainElement(container)).toBeEqual(parentElementAssertion); }); }); From 1942629be4b8cbd891fc71f00c9554eb0a2d4eac Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Tue, 22 Apr 2025 15:14:14 -0500 Subject: [PATCH 14/14] (CR): Address Code Review comments --- .../native/test/lib/ElementAssertion.test.tsx | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index b657d36..cfb4b97 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -288,32 +288,46 @@ describe("[Unit] ElementAssertion.test.ts", () => { const parentElementAssertion = new ElementAssertion(parent); const child = element.getByTestId("childId"); const text = element.getByTestId("textId"); + const textElementAssertion = new ElementAssertion(text); - context("when the container element contains the target element", () => { - it("returns the assertion instance", () => { - expect(containerElementAssertion.toContainElement(parent)).toBe(containerElementAssertion); - expect(containerElementAssertion.toContainElement(child)).toBe(containerElementAssertion); - expect(containerElementAssertion.toContainElement(text)).toBe(containerElementAssertion); - expect(parentElementAssertion.toContainElement(child)).toBe(parentElementAssertion); + context("when the element has children", () => { + context("and the target element is found in the children's element", () => { + it("returns the assertion instance", () => { + expect(containerElementAssertion.toContainElement(parent)).toBe(containerElementAssertion); + expect(containerElementAssertion.toContainElement(child)).toBe(containerElementAssertion); + expect(containerElementAssertion.toContainElement(text)).toBe(containerElementAssertion); + expect(parentElementAssertion.toContainElement(child)).toBe(parentElementAssertion); + }); + + it("throws an error for negative assertion", () => { + expect(() => containerElementAssertion.not.toContainElement(parent)) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to contain element ."); + expect(() => containerElementAssertion.not.toContainElement(text)) + .toThrowError(AssertionError) + .toHaveMessage("Expected element NOT to contain element ."); + }); }); - it("returns the assertion instance for negated assertions when the target element is not contained", () => { - expect(parentElementAssertion.not.toContainElement(text)).toBeEqual(parentElementAssertion); - expect(parentElementAssertion.not.toContainElement(container)).toBeEqual(parentElementAssertion); + context("and the target element is NOT found in the children's element", () => { + it("throws an error", () => { + expect(() => parentElementAssertion.toContainElement(text)) + .toThrowError(AssertionError) + .toHaveMessage("Expected element to contain element ."); + }); + + it("returns the assertion instance for negative assertion", () => { + expect(parentElementAssertion.not.toContainElement(text)).toBeEqual(parentElementAssertion); + expect(parentElementAssertion.not.toContainElement(container)).toBeEqual(parentElementAssertion); + }); }); }); - context("when the container element does NOT contain the target element", () => { + context("when the element does NOT have children", () => { it("throws an error", () => { - expect(() => containerElementAssertion.not.toContainElement(parent)) - .toThrowError(AssertionError) - .toHaveMessage("Expected element NOT to contain element ."); - expect(() => containerElementAssertion.not.toContainElement(text)) - .toThrowError(AssertionError) - .toHaveMessage("Expected element NOT to contain element ."); - expect(() => parentElementAssertion.toContainElement(text)) + expect(() => textElementAssertion.toContainElement(parent)) .toThrowError(AssertionError) - .toHaveMessage("Expected element to contain element ."); + .toHaveMessage("Expected element to contain element ."); }); }); });