diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index 4dbabd6..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 * ``` @@ -94,6 +94,33 @@ export class ElementAssertion extends Assertion { }); } + /** + * Check if the element is visible and has not been hidden by an ancestor. + * + * @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)); + } } diff --git a/packages/native/test/lib/ElementAssertion.test.tsx b/packages/native/test/lib/ElementAssertion.test.tsx index ce3b441..05b8743 100644 --- a/packages/native/test/lib/ElementAssertion.test.tsx +++ b/packages/native/test/lib/ElementAssertion.test.tsx @@ -1,13 +1,38 @@ 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, ReactElement } from "react"; import { View, TextInput, Text, + Modal, + Button, } from "react-native"; import { ElementAssertion } from "../../src/lib/ElementAssertion"; +function SimpleToggleText(): ReactElement { + const [isVisible, setIsVisible] = useState(true); + + const handleToggle = useCallback((): void => { + setIsVisible(prev => !prev); + }, []); + + return ( + + + {"Toggle me!"} + +