Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions packages/native/src/lib/ElementAssertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,47 @@ export class ElementAssertion extends Assertion<ReactTestInstance> {
});
}

/**
* 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)}.`,
});

return this.execute({
assertWhen: this.isElementContained(this.actual, element),
error,
invertedError,
});
}

private 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
);
}

private isElementDisabled(element: ReactTestInstance): boolean {
const { type } = element;
const elementType = type.toString();
Expand Down
20 changes: 19 additions & 1 deletion packages/native/src/lib/helpers/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import { ReactTestInstance } from "react-test-renderer";

/**
* 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(value)) {
return value.length === 0;
}

return false;
}

/**
* Converts a ReactTestInstance to a string representation.
*
* @param instance The ReactTestInstance to convert.
* @param instance - The ReactTestInstance to convert.
* @returns A string representation of the instance.
*/
export function instanceToString(instance: ReactTestInstance | null): string {
Expand Down
60 changes: 60 additions & 0 deletions packages/native/test/lib/ElementAssertion.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,4 +311,64 @@ describe("[Unit] ElementAssertion.test.ts", () => {
});
});
});

describe (".toContainElement", () => {
const element = render(
Comment on lines +315 to +316
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a good practice, elements should be rendered on each test it(..) function, so the cleanup function cleans what's rendered before each test, avoid flaky tests, etc.

<View testID="grandParentId">
<View testID="parentId">
<View testID="childId" />
</View>
<Text testID="textId" />
</View>,
Comment on lines +317 to +322
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use <Text> elements so that we can avoid the testIDs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im trying to do this, but im not getting good results, would you like to pair?

);

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");
const textElementAssertion = new ElementAssertion(text);

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 <View ... /> NOT to contain element <View ... />.");
expect(() => containerElementAssertion.not.toContainElement(text))
.toThrowError(AssertionError)
.toHaveMessage("Expected element <View ... /> NOT to contain element <Text ... />.");
});
});

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 <View ... /> to contain element <Text ... />.");
});

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 element does NOT have children", () => {
it("throws an error", () => {
expect(() => textElementAssertion.toContainElement(parent))
.toThrowError(AssertionError)
.toHaveMessage("Expected element <Text ... /> to contain element <View ... />.");
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we please add some tests for when there are no child nor parent elements 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the recommendation! To take this into account, I've changed the structure of the tests so they're clearer and easier to read 🚀

});