-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(native): Add toBeDisabled #140
base: main
Are you sure you want to change the base?
Changes from all commits
5a8f357
3dbcce9
4146337
512f251
190ce45
39ff2b3
3f36d19
7e87056
543b776
72efbf8
7dd25a0
89ba184
e0cb309
5fbe42a
2aa8928
177eefe
3b3ebc1
898721a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,5 +17,8 @@ node_modules/ | |
# VSCode | ||
.vscode/ | ||
|
||
# idea | ||
.idea/ | ||
|
||
# Packages | ||
*.tgz |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { Assertion, AssertionError } from "@assertive-ts/core"; | ||
import { get } from "dot-prop-immutable"; | ||
import { ReactTestInstance } from "react-test-renderer"; | ||
|
||
export class ElementAssertion extends Assertion<ReactTestInstance> { | ||
public constructor(actual: ReactTestInstance) { | ||
super(actual); | ||
} | ||
|
||
public override toString = (): string => { | ||
if (this.actual === null) { | ||
return "null"; | ||
} | ||
|
||
return `<${this.actual.type.toString()} testID="${this.actual.props.testID}"... />`; | ||
}; | ||
|
||
/** | ||
* Check if the component is disabled. | ||
* | ||
* @example | ||
* ``` | ||
* expect(component).toBeDisabled(); | ||
* ``` | ||
* | ||
* @returns the assertion instance | ||
*/ | ||
public toBeDisabled(): this { | ||
const error = new AssertionError({ | ||
actual: this.actual, | ||
message: `Expected element ${this.toString()} to be disabled.`, | ||
}); | ||
const invertedError = new AssertionError({ | ||
actual: this.actual, | ||
message: `Received element ${this.toString()} not to be disabled.`, | ||
}); | ||
|
||
return this.execute({ | ||
assertWhen: this.isElementDisabled(this.actual) || this.isAncestorDisabled(this.actual), | ||
error, | ||
invertedError, | ||
}); | ||
} | ||
|
||
/** | ||
* Check if the component is enabled. | ||
* | ||
* @example | ||
* ``` | ||
* expect(component).toBeEnabled(); | ||
* ``` | ||
* @returns the assertion instance | ||
*/ | ||
public toBeEnabled(): this { | ||
return this.not.toBeDisabled(); | ||
} | ||
|
||
private isElementDisabled(element: ReactTestInstance): boolean { | ||
const { type } = element; | ||
const elementType = type.toString(); | ||
if (elementType === "TextInput" && element?.props?.editable === false) { | ||
return true; | ||
} | ||
|
||
return ( | ||
get(element, "props.aria-disabled") | ||
|| get(element, "props.disabled", false) | ||
|| get(element, "props.accessibilityState.disabled", false) | ||
|| get<ReactTestInstance, string[]>(element, "props.accessibilityStates", []).includes("disabled") | ||
); | ||
} | ||
|
||
private isAncestorDisabled(element: ReactTestInstance): boolean { | ||
const { parent } = element; | ||
return parent !== null && (this.isElementDisabled(element) || this.isAncestorDisabled(parent)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Plugin } from "@assertive-ts/core"; | ||
import { ReactTestInstance } from "react-test-renderer"; | ||
|
||
import { ElementAssertion } from "./lib/ElementAssertion"; | ||
|
||
declare module "@assertive-ts/core" { | ||
|
||
export interface Expect { | ||
// eslint-disable-next-line @typescript-eslint/prefer-function-type | ||
(actual: ReactTestInstance): ElementAssertion; | ||
} | ||
} | ||
|
||
const ElementPlugin: Plugin<ReactTestInstance, ElementAssertion> = { | ||
Assertion: ElementAssertion, | ||
insertAt: "top", | ||
predicate: (actual): actual is ReactTestInstance => | ||
typeof actual === "object" | ||
&& actual !== null | ||
&& "instance" in actual | ||
&& typeof actual.instance === "object" | ||
&& "type" in actual | ||
&& typeof actual.type === "object" | ||
&& "props" in actual | ||
&& typeof actual.props === "object" | ||
&& "parent" in actual | ||
&& typeof actual.parent === "object" | ||
&& "children" in actual | ||
&& typeof actual.children === "object", | ||
Comment on lines
+26
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these props still present if the element does not have a parent or children? 🤔 |
||
}; | ||
|
||
export const NativePlugin = [ElementPlugin]; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're missing tests for |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { AssertionError, expect } from "@assertive-ts/core"; | ||
import { render } from "@testing-library/react-native"; | ||
import { | ||
View, | ||
TextInput, | ||
} from "react-native"; | ||
|
||
import { ElementAssertion } from "../../src/lib/ElementAssertion"; | ||
|
||
describe("[Unit] ElementAssertion.test.ts", () => { | ||
describe(".toBeDisabled", () => { | ||
context("when the element is TextInput", () => { | ||
context("and the element is not editable", () => { | ||
it("returns the assertion instance", () => { | ||
const element = render( | ||
<TextInput testID="id" editable={false} />, | ||
); | ||
const test = new ElementAssertion(element.getByTestId("id")); | ||
expect(test.toBeDisabled()).toBe(test); | ||
}); | ||
}); | ||
|
||
context("and the element is editable", () => { | ||
it("throws an error", () => { | ||
const reactElement = render(<TextInput editable={true} testID="id" />); | ||
const test = new ElementAssertion(reactElement.getByTestId("id")); | ||
|
||
expect(() => test.toBeDisabled()) | ||
.toThrowError(AssertionError) | ||
.toHaveMessage('Expected element <TextInput testID="id"... /> to be disabled.'); | ||
}); | ||
}); | ||
}); | ||
|
||
context("when the parent has property aria-disabled", () => { | ||
context("if parent aria-disabled = true", () => { | ||
it("returns assertion instance for parent and child element", () => { | ||
const element = render( | ||
<View aria-disabled={true} testID="parentId"> | ||
<View testID="childId"> | ||
<TextInput /> | ||
</View> | ||
</View>, | ||
); | ||
|
||
const parent = new ElementAssertion(element.getByTestId("parentId")); | ||
const child = new ElementAssertion(element.getByTestId("childId")); | ||
expect(parent.toBeDisabled()).toBe(parent); | ||
expect(child.toBeDisabled()).toBe(child); | ||
}); | ||
}); | ||
|
||
context("if parent aria-disabled = false", () => { | ||
it("throws an error for parent and child element", () => { | ||
const element = render( | ||
<View aria-disabled={false} testID="parentId"> | ||
<View testID="childId"> | ||
<TextInput /> | ||
</View> | ||
</View>, | ||
); | ||
|
||
const parent = new ElementAssertion(element.getByTestId("parentId")); | ||
const child = new ElementAssertion(element.getByTestId("childId")); | ||
|
||
expect(parent.toBeEnabled()).toBeEqual(parent); | ||
expect(() => parent.toBeDisabled()) | ||
.toThrowError(AssertionError) | ||
.toHaveMessage('Expected element <View testID="parentId"... /> to be disabled.'); | ||
expect(() => child.toBeDisabled()) | ||
.toThrowError(AssertionError) | ||
.toHaveMessage('Expected element <View testID="childId"... /> to be disabled.'); | ||
}); | ||
}); | ||
}); | ||
|
||
context("when the element contains property aria-disabled", () => { | ||
const element = render( | ||
<View testID="parentId"> | ||
<View aria-disabled={true} testID="childId"> | ||
<TextInput /> | ||
</View> | ||
</View>, | ||
); | ||
|
||
const parent = new ElementAssertion(element.getByTestId("parentId")); | ||
const child = new ElementAssertion(element.getByTestId("childId")); | ||
|
||
context("if child contains aria-disabled = true", () => { | ||
it("returns assertion instance for child element", () => { | ||
expect(child.toBeDisabled()).toBe(child); | ||
expect(() => child.toBeEnabled()) | ||
.toThrowError(AssertionError) | ||
.toHaveMessage("Received element <View testID=\"childId\"... /> not to be disabled."); | ||
}); | ||
|
||
it("returns error for parent element", () => { | ||
expect(parent.toBeEnabled()).toBe(parent); | ||
expect(() => parent.toBeDisabled()) | ||
.toThrowError(AssertionError) | ||
.toHaveMessage("Expected element <View testID=\"parentId\"... /> to be disabled."); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure about this? Not all elements will have a
testID
. Also, using the testID should be our last resource when using tools like testing-library.