npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-cavynext

v0.2.0

Published

A cross-platform integration test framework for React Native, written in TypeScript.

Readme

react-native-cavynext

The library half of react-native-cavynext. Add it to your app, hook the components you want to drive, and write specs against them.

npm install --save-dev react-native-cavynext

Migrating from Cavy? See MIGRATION.md.

Two ways to identify components

Specs are written the same either way; only the app side differs.

| Approach | App code needed | Notes | | --- | --- | --- | | testID | None | Components are found by searching the rendered tree for a matching testID. | | Test hooks | A ref per component, plus wrap() for function and host components | Cavy's original approach. Explicit, and doesn't depend on React internals. |

An explicitly hooked component always wins over a matching testID, so the two can be mixed and adding a testID can never change what an existing spec means.

Using testID

Nothing to import. Set testID as you normally would:

export default function LoginScreen({ onSubmit }) {
  return (
    <View testID="LoginScreen">
      <TextInput testID="LoginScreen.EmailInput" onChangeText={setEmail} />
      <Pressable testID="LoginScreen.Button" onPress={onSubmit}>
        <Text>Log in</Text>
      </Pressable>
    </View>
  );
}

Then refer to those IDs from a spec exactly as you would a hooked component:

await spec.fillIn('LoginScreen.EmailInput', '[email protected]');
await spec.press('LoginScreen.Button');

Turn it off with <Tester useTestIDs={false} ...>.

Caveats. This walks React's fiber tree, which is not a public API, so it's written defensively — if React's internals change, testID lookups degrade to "not found" and test hooks keep working. If the same testID appears more than once, the outermost match wins.

Hooking components

useCavyNext()

For function components. Returns generateTestHook, which you pass into a ref:

import { useCavyNext } from 'react-native-cavynext';

export default function SearchBar() {
  const generateTestHook = useCavyNext();
  return <TextInput ref={generateTestHook('SearchBar.TextInput')} />;
}

Your own ref is always preserved, either as a callback or as a useRef object:

<TextInput ref={generateTestHook('SearchBar.TextInput', inputRef)} />

useCavy is exported as an alias, for compatibility with Cavy.

hook(Component)

For class components. Supplies generateTestHook as a prop:

import { hook } from 'react-native-cavynext';

class MyComponent extends React.Component {
  render() {
    const { generateTestHook } = this.props;
    return <Button ref={generateTestHook('MyComponent.button')} title="Press me" />;
  }
}

export default hook(MyComponent);

wrap(Component)

Only needed for the ref-based API — a testID needs no wrapping.

Function components and host components like Text don't expose their props through a ref, so the spec helpers can't reach them. wrap fixes that:

import { Text } from 'react-native';
import { useCavyNext, wrap } from 'react-native-cavynext';

const TestableText = wrap(Text);

export default function Title({ title }) {
  const generateTestHook = useCavyNext();
  return <TestableText ref={generateTestHook('Title.text')}>{title}</TestableText>;
}

Class components need no wrapping.

The Tester component

Wrap your whole app in it from your test entry point.

| Prop | Type | Description | | --- | --- | --- | | store | TestHookStore | Required. The store holding your hooked components. | | specs | SpecFn[] | Required. Your spec functions. | | reporter | reporter, class or function | Defaults to WebSocketReporter, which reports to the CLI. | | waitTime | number | How long findComponent waits for a component, in ms. Defaults to 2000. | | startDelay | number | How long to wait before starting, in ms. Defaults to 0. | | only | string[] | Run only test cases carrying one of these tags. | | clearStorage | boolean | Clear storage between test cases. Defaults to false. | | storage | Storage | Your AsyncStorage implementation, required by clearStorage. | | useTestIDs | boolean | Allow specs to find components by testID. Defaults to true. |

import AsyncStorage from '@react-native-async-storage/async-storage';

<Tester specs={specs} store={store} clearStorage storage={AsyncStorage}>
  <App />
</Tester>;

Spec API

A spec is a function receiving a TestScope. Every helper returns a promise, so await them.

Structure

| Helper | Description | | --- | --- | | describe(label, fn, tag?) | Group test cases. May be nested. An optional tag can be matched by the Tester's only. | | it(label, fn, tag?) | Define a test case. Inherits its describe block's tag. | | xdescribe / xit | Skip a block or test; it is reported as skipped. | | fdescribe / fit | Focus a block or test; when anything is focused, only focused tests run. | | describeIf(cond, ...) / itIf(cond, ...) | Run only when the condition holds, e.g. spec.describeIf(spec.platform() !== 'web', ...); otherwise reported as skipped. | | describe.each(table)(...) / it.each(table)(...) | Parameterized blocks/tests, once per table entry - see below. Also on xdescribe, xit, describeIf and itIf. |

Parameterized tests

describe.each and it.each register a block or test once per table entry. Label templates support $prop (and $prop.path) resolved against the entry, $# for its index, and %s for the entry itself:

const sources = [
  { description: 'DASH', url: 'stream.mpd' },
  { description: 'HLS', url: 'stream.m3u8' },
];

spec.describe.each(sources)('Play a $description stream', (source) => {
  spec.it('starts', async () => {
    await startPlayback(source.url);
  });
});

spec.it.each([0, 30, 60])('seeks to %s seconds', async (time) => {
  await seekTo(time);
});

In the describeIf.each / itIf.each forms the condition may also be a predicate evaluated per entry:

spec.describeIf.each(sources)((s) => s.live, 'Live: $description', (source) => { ... });

| beforeEach(fn) / afterEach(fn) | Run something before/after every test case in the spec. Callable multiple times. | | beforeAll(fn) / afterAll(fn) | Run something once before the first / after the last test of the spec. | | platform() | The platform under test: 'ios', 'android', 'web', ... |

Selectors

Every helper accepts either a test hook identifier (string) or a by.* selector:

import { by } from 'react-native-cavynext';

await spec.press(by.id('LoginScreen.Button'));
await spec.exists(by.text('Welcome back'));       // string or RegExp
await spec.fillIn(by.label('Email address'), '[email protected]');
await spec.press(by.role('button'));

Actions

| Helper | Description | | --- | --- | | fillIn(id, str) | Calls onChangeText. Also available as changeText / replaceText. | | typeText(id, str) | Appends to the current value, then calls onChangeText. | | clearText(id) | Calls onChangeText(''). | | press(id) | Calls onPress. Also available as tap. | | doublePress(id) | Calls onPress twice. | | longPress(id) | Calls onLongPress. | | focus(id) / blur(id) | Calls onFocus / onBlur. | | toggleSwitch(id) | Calls onValueChange with the opposite of value. | | setValue(id, value) | Calls onValueChange with the value. | | scrollTo(id, { x, y }) | Scrolls a ScrollView/FlatList, preferring its imperative scrollTo. | | scrollToEnd(id) | Scrolls to the end, preferring the imperative scrollToEnd. | | pause(ms) | Waits for a fixed time. | | waitFor(predicate, opts?) | Polls until a condition holds. opts is a timeout or { timeout, interval }. | | waitForElementToBeRemoved(id, opts?) | Polls until the component disappears. |

Assertions

| Helper | Description | | --- | --- | | exists(id) | Resolves true if the component is present. | | notExists(id) | Resolves true if it is absent. Waits the full waitTime. | | expectVisible(id) | Also fails if the component is hidden via display: 'none' or opacity: 0. | | containsText(id, text) | Checks the component's children contain text. | | findComponent(id) | Returns the hooked component itself, for anything custom. | | expectComponent(id) | Component-specific matchers - see below. |

expect - value matchers

A Jest-style expect for plain values, with .not, .resolves and .rejects:

import { expect } from 'react-native-cavynext';

expect(2 + 2).toBe(4);
expect({ a: 1 }).toEqual({ a: 1 });
expect([1, 2, 3]).toContain(2);
expect('hello').toMatch(/^h/);
expect(() => JSON.parse('nope')).toThrow();
expect(value).not.toBeNull();
await expect(fetchUser()).resolves.toHaveProperty('name');
await expect(failingCall()).rejects.toBeInstanceOf(Error);

Matchers: toBe, toEqual, toStrictEqual, toBeTruthy, toBeFalsy, toBeNull, toBeUndefined, toBeDefined, toBeNaN, toContain, toContainEqual, toHaveLength, toHaveProperty, toMatch, toMatchObject, toBeGreaterThan(OrEqual), toBeLessThan(OrEqual), toBeCloseTo, toBeInstanceOf, toThrow.

expectComponent - component matchers

Async matchers that wait for the component (up to waitTime) before asserting. All support .not:

await spec.expectComponent('Login.error').toBeVisible();
await spec.expectComponent('Login.email').toHaveValue('[email protected]');
await spec.expectComponent(by.text('Welcome')).toExist();
await spec.expectComponent('Login.submit').not.toBeDisabled();

Matchers: toExist, toBeVisible, toHaveText, toContainText, toHaveProp, toHaveValue, toBeEnabled, toBeDisabled, toBeChecked, toHaveStyle, toHaveAccessibilityLabel.

Errors are typed, so you can tell them apart: ComponentNotFoundError, MissingPropError, UnwrappedComponentError, TimeoutError, AssertionError.

Custom reporters

The simplest reporter is a function receiving the final report:

<Tester specs={specs} store={store} reporter={(report) => upload(report)} />

For results as they happen, implement RealtimeReporter:

import type { RealtimeReporter } from 'react-native-cavynext';

const reporter: RealtimeReporter = {
  type: 'realtime',
  onStart: () => openConnection(),
  send: (result) => stream(result),
  onFinish: (report) => finish(report),
};

DeferredReporter (type: 'deferred') receives only the final report.

License

MIT