From 6e9b6db004233d4b7da305ed07f7cbd4ff36ed51 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Fri, 21 Mar 2025 17:11:32 -0500 Subject: [PATCH 1/5] 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 4dbabd6..a3b80d2 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -94,6 +94,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(); @@ -113,4 +140,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 260b9190499f4a5c7ebe5ac961651d32b1a93a36 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Fri, 21 Mar 2025 17:11:42 -0500 Subject: [PATCH 2/5] 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 6c2cfc16cc1ec8a93d1a0dad61e65c237e24fb26 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Fri, 21 Mar 2025 17:14:51 -0500 Subject: [PATCH 3/5] 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 a3b80d2..efdef13 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -42,7 +42,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 * ``` @@ -95,7 +95,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 311d4f03cba0f02035af1a941c444b172951a2e4 Mon Sep 17 00:00:00 2001 From: Karla Quistanchala Date: Thu, 3 Apr 2025 15:48:20 -0500 Subject: [PATCH 4/5] (CR): Address Code Review comments --- .../native/test/lib/ElementAssertion.test.tsx | 72 +++++++++++++++---- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index 0b42a3a..b38a0a9 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -1,14 +1,40 @@ import { AssertionError, expect } from "@assertive-ts/core"; -import { render } from "@testing-library/react-native"; +import { fireEvent, render } from "@testing-library/react-native"; +import { useState, useCallback } from "react"; import { View, TextInput, Text, Modal, + Button, } from "react-native"; import { ElementAssertion } from "../../src/lib/ElementAssertion"; +const SimpleToggleText: React.FC = () => { + const [isVisible, setIsVisible] = useState(true); + + const handleToggle = useCallback((): void => { + setIsVisible((prev: boolean) => !prev); + }, []); + + return ( + + + {"Toggle me!"} + +