Skip to content

Instantly share code, notes, and snippets.

@mainfraame
Last active November 2, 2020 16:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mainfraame/850648724022850217e092244961184f to your computer and use it in GitHub Desktop.
Save mainfraame/850648724022850217e092244961184f to your computer and use it in GitHub Desktop.
A base class to extend component functionality wrappers with
import {isEqual} from "lodash";
import {outdent} from "outdent";
import {act} from "react-dom/test-utils";
import {fireEvent} from "@testing-library/react";
export type ElementProps = {
id?: string;
root?: string;
selector?: string;
};
export class Element<T = HTMLDivElement> {
component: string;
_element: T;
_id: string;
_rootSelector: string;
_selector: string;
constructor(props: ElementProps) {
const root = props.root ? `${props.root} ` : "";
const selector =
props.selector || (props.id ? `[data-test-id="${props.id}"]` : "");
this._id = props.id;
this._rootSelector = root;
this._selector = selector;
}
private element(): T {
if (
// ensure the element is still valid
this._element?.isConnected &&
this._element["__proto__"]
) {
return this._element;
}
this._element = document.querySelector(this.selector());
return this._element;
}
click(): void {
act(() => {
this.element().click();
});
}
getAttribute<T>(attr: string): T {
return this.element().getAttribute(attr) as T;
}
getDataAttrs(pick?: string[]): { [index: string]: any } {
const dataset = this.element().dataset;
return Object.keys(dataset)
.filter((key) => !pick || pick.includes(key))
.reduce(
(acc, key) => ({
...acc,
[key]: dataset[key],
}),
{}
);
}
getDataAttr<T>(attr: string): T {
return this.element().dataset[attr] as T;
}
getValue(): any {
return this.element().value;
}
hasAttribute(attr: string): boolean {
return this.element().hasAttribute(attr);
}
isDisabled(): boolean {
return !!this.hasAttribute("disabled") === true;
}
isEnabled(): boolean {
return !this.isDisabled();
}
isInDom(): boolean {
return !!this.element();
}
innerText(): string {
// for some reason, i've found that some third party components will
// have their innerText only available in the textContent property
return (this.element().innerText || this.element().textContent)
.trim()
.replace(/\n/g, "");
}
keyDown(key: string): void {
act(() => {
fireEvent.keyDown(this.element(), {key});
});
}
outerHTML(): string {
return this.element().outerHTML.trim();
}
scrollTo(coordinates: { top?: number; left?: number }): void {
const $el = this.element();
if (typeof coordinates.top === "number") {
act(() => {
$el.scrollTop = coordinates.top;
});
}
if (typeof coordinates.left === "number") {
act(() => {
$el.scrollLeft = coordinates.left;
});
}
act(() => {
fireEvent.scroll(this.element());
});
}
selector(selector?: string, trim?: boolean): string {
return `${this._rootSelector}${this._selector}${
selector ? `${trim || !this._selector ? "" : " "}${selector}` : ""
}`;
}
setValue(value: string): void {
if (isEqual(value, this.getValue())) {
return;
}
act(() => {
fireEvent.change(this.element(), {
bubbles: true,
currentTarget: {
value: value,
rawValue: value,
},
target: {
value: value,
rawValue: value,
},
});
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment