cavy
Version:
An integration test framework for React Native.
241 lines (219 loc) • 7.71 kB
JavaScript
// Custom error returned when Cavy cannot find the component in the
// TestHookStore.
class ComponentNotFoundError extends Error {
constructor(message) {
super(message);
this.name = "ComponentNotFoundError";
}
}
// Custom error returned when a component has not been wrapped, but should have
// been.
class UnwrappedComponentError extends Error {
constructor(message) {
super(message);
this.name = "UnwrappedComponentError";
}
}
// Internal: TestScope is responsible for building up testCases to be run by
// the TestRunner, and includes all the functions available when writing these
// specs.
//
// component - the Tester component within which the app is wrapped.
// waitTime - length of time in ms that cavy should wait before giving up on
// finding a component in the testHookStore.
export default class TestScope {
constructor(component, waitTime) {
this.component = component;
this.testHooks = component.testHookStore;
this.testCases = [];
this.waitTime = waitTime;
}
// Public: Find a component by its test hook identifier. Waits
// this.waitTime for the component to appear before abandoning.
//
// Usually, you'll want to use `exists` instead.
//
// identifier - String, component identifier registered in the test hook store
// via `generateTestHook`.
//
// Example
//
// import { assert } from 'assert';
// const c = await spec.findComponent('MyScene.myComponent');
// assert(c, 'Component is missing');
//
// Returns a promise; use `await` when calling this function. Resolves the
// promise if the component is found, rejects the promise after
// this.waitTime if the component is never found in the test hook
// store.
findComponent(identifier) {
let promise = new Promise((resolve, reject) => {
let startTime = Date.now();
let loop = setInterval(() => {
const component = this.testHooks.get(identifier);
if (component) {
clearInterval(loop);
return resolve(component);
} else {
if (Date.now() - startTime >= this.waitTime) {
reject(
new ComponentNotFoundError(
`Could not find component with identifier ${identifier}`
)
);
clearInterval(loop);
}
}
}, 100);
});
return promise;
}
// Public: Build up a group of test cases.
//
// label - Label for these test cases.
// f - Callback function containing your tests cases defined with `it`.
// tag - (Optional) A string tag used to determine whether the group of
// tests should run. Defaults to null.
//
// Example
//
// // specs/MyFeatureSpec.js
// export default function(spec) {
// spec.describe('My Scene', function() {
// spec.it('Has a component', async function() {
// await spec.exists('MyScene.myComponent');
// }, 'focus');
// });
// }
//
// Returns undefined.
describe(label, f, tag = null) {
this.describeLabel = label;
this.tag = tag;
f.call(this);
}
// Public: Define a test case.
//
// label - Label for this test case. This is combined with the label from
// `describe` when Cavy outputs to the console.
// f - The test case.
// testTag - (Optional) A string tag used to determine whether the individual
// test should run. 'Inherits' a tag from its surrounding describe
// block (if present) or defaults to null.
//
// See example above.
it(label, f, testTag = null) {
const tag = this.tag || testTag;
this.testCases.push({ describeLabel: this.describeLabel, label, f, tag });
}
// Public: Runs a function before each test case.
//
// f - the function to run
beforeEach(f) {
this.beforeEach = f;
}
// ACTIONS
// Public: Fill in a `TextInput`-compatible component with a string value.
// Your component should respond to the property `onChangeText`.
//
// identifier - Identifier for the component.
// str - String to fill in.
//
// Returns a promise, use await when calling this function. Promise will be
// rejected if the component is not found.
async fillIn(identifier, str) {
const component = await this.findComponent(identifier);
component.props.onChangeText(str);
}
// Public: 'Press' a component (e.g. a `<Button />`).
// Your component should respond to the property `onPress`.
//
// identifier - Identifier for the component.
//
// Returns a promise, use await when calling this function. Promise will be
// rejected if the component is not found.
async press(identifier) {
const component = await this.findComponent(identifier);
component.props.onPress();
}
// Public: 'Focus' a component (e.g. a `<TextInput />`).
// Your component should respond to the property `onFocus`.
//
// identifier - Identifier for the component.
//
// Returns a promise, use await when calling this function. Promise will be
// rejected if the component is not found.
async focus(identifier) {
const component = await this.findComponent(identifier);
component.props.onFocus();
}
// Public: Pause the test for a specified length of time, perhaps to allow
// time for a request response to be received.
//
// time - Integer length of time to pause for (in milliseconds).
//
// Returns a promise, use await when calling this function.
async pause(time) {
let promise = new Promise((resolve, reject) => {
setTimeout(function() {
resolve();
}, time);
});
return promise;
}
// Public: Checks whether a component e.g. <Text> contains the text string
// as a child.
//
// identifier - Identifier for the component.
// text - String
//
// Returns a promise, use await when calling this function. Promise will be
// rejected if the component is not found.
async containsText(identifier, text) {
const component = await this.findComponent(identifier);
if (component.props === undefined) {
const msg = "Cannot read property 'children' of undefined.\n" +
"Are you using `containsText` with a React <Text> component?\n" +
"If so, you need to `wrap` the component first.\n" +
"See documentation for Cavy's `wrap` function:" +
"https://cavy.app/docs/api/test-hooks#example-3";
throw new UnwrappedComponentError(msg);
}
const stringifiedChildren = component.props.children.includes
? component.props.children
: String(component.props.children);
if (!stringifiedChildren.includes(text)) {
throw new Error(`Could not find text ${text}`);
}
}
// ASSERTIONS
// Public: Check a component exists.
//
// identifier - Identifier for the component.
//
// Returns a promise, use await when calling this function. Promise will be
// rejected if component is not found, otherwise will be resolved with
// `true`.
async exists(identifier) {
const component = await this.findComponent(identifier);
return !!component;
}
// Public: Check for the absence of a component. Will potentially halt your
// test for your maximum wait time.
//
// identifier - Identifier for the component.
//
// Returns a promise, use await when calling this function. Promise will be
// rejected if the component is not found.
async notExists(identifier) {
try {
await this.findComponent(identifier);
} catch (e) {
if (e.name == "ComponentNotFoundError") {
return true;
}
throw e;
}
throw new Error(`Component with identifier ${identifier} was present`);
}
}