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

jest-collector

v2.0.0

Published

A tool for testing imported functions and or React components and its hooks or lifecycles.

Readme

Jest Collector

NPM Build Status Coverage Status

Know exactly what your React components did during a test.

Jest Collector records every render, every hook and every lifecycle method of your components - and of your plain functions and classes too - so you can assert on them like on any other value.

render(<UserProfile userId={1} />);

// it rendered once and never again
expect(collector.getCallCount(UserProfile.name)).toBe(1);

// the effect ran once and its deps are the ones you expect
expect(collector.getReactHooks(UserProfile.name)?.getHook("useEffect", 1)?.deps)
    .toEqual([1]);

Table of Contents

Why Jest Collector

Testing libraries answer the question "what is on the screen?". They cannot answer "how did it get there?".

That second question is where the bugs live:

  • A component re-renders on every keystroke because a prop is a new object each time. The screen looks perfect. The app is slow.
  • A useEffect fires twice because a dependency is not stable. The screen looks perfect. The API is called twice.
  • A useMemo is recomputed on every render because a dependency changes identity. The screen looks perfect. The memoization does nothing.
  • A Redux selector returns a new array literal, so the component re-renders on every unrelated action. The screen looks perfect.

None of these are visible through the DOM, and none of them fail a normal test. They surface as "the app feels slow" months later.

Jest Collector makes them assertable. It replaces the imports of your source files with recording wrappers, so for every function and component you get the number of calls, the arguments, the result, the parent tree and the complete hook data - without changing a single line of your production code.

What it is good for

  • Proving a component renders exactly as many times as it should.
  • Proving useEffect, useCallback and useMemo have the dependencies you think they have.
  • Proving React.memo, connect and observer actually bail out.
  • Test driven development of components where render count is a requirement.
  • Regression tests for performance fixes - so the fix stays fixed.

What it is not for

Unit tests of a single pure function. This is a tool for integration tests, where several components and hooks interact.

Getting Started

Install Jest Collector using npm:

npm install --save-dev jest-collector

or yarn:

yarn add --dev jest-collector

The collector has to run before all tests, because it uses jest.mock to record the imports. Point Jest at a setup file:

// jest.config.js
module.exports = {
    setupFilesAfterEnv: ["./jest.setup.js"]
};

and create the collector there with the root folder of your sources:

// jest.setup.js
const { createCollector } = require("jest-collector");

createCollector({ roots: ["src"] });

That is the whole setup. collector is now a global in every test.

With TypeScript, add the type of the global once:

// globals.ts
import { Collector } from "jest-collector";

declare global {
    var collector: Collector;
}

NOTE: Files ending with .test.ts, .test.tsx, .test.js or .test.jsx and all __tests__ folders are never mocked. Components coming from node_modules are not collected either - they are internals of a library, not the subject of your test.

It is recommended to reset the collector between the tests:

beforeEach(() => {
    collector.reset();
});

Examples

Catching an unnecessary re-render

The classic bug - the parent passes a new object on every render, so the memoized child re-renders anyway.

const Parent = () => {
    const [count, setCount] = React.useState(0);

    return (
        <>
            <button onClick={() => setCount(count + 1)}>{count}</button>
            {/* a new object literal on every render of Parent */}
            <Child config={{ theme: "dark" }} />
        </>
    );
};

const Child = React.memo(function Child({ config }: { config: Config }) {
    return <div>{config.theme}</div>;
});
render(<Parent />);

fireEvent.click(screen.getByRole("button"));

// Parent rendered twice, and so did Child - React.memo could not help,
// because `config` is a different object every time
expect(collector.getCallCount(Parent.name)).toBe(2);
expect(collector.getCallCount("Child")).toBe(2);

After wrapping config in a useMemo, the very same test proves the fix:

expect(collector.getCallCount(Parent.name)).toBe(2);
expect(collector.getCallCount("Child")).toBe(1);

Testing the dependencies of an effect

const SearchResults = ({ query }: { query: string }) => {
    const [results, setResults] = React.useState<string[]>([]);

    React.useEffect(() => {
        fetchResults(query).then(setResults);
    }, [query]);

    return <ul>{/* ... */}</ul>;
};
const { rerender } = render(<SearchResults query="react" />);

const effect = collector.getReactHooks(SearchResults.name)?.getHook("useEffect", 1);

// the deps are exactly what you expect
expect(effect?.deps).toEqual(["react"]);
expect(effect?.action).toHaveBeenCalledTimes(1);

// the same query must not fetch again
rerender(<SearchResults query="react" />);
expect(effect?.action).toHaveBeenCalledTimes(1);

// a new query must
rerender(<SearchResults query="jest" />);
expect(effect?.action).toHaveBeenCalledTimes(2);

unmount is collected too, so cleanup is testable:

const { unmount } = render(<SearchResults query="react" />);

unmount();

expect(
    collector.getReactHooks(SearchResults.name)?.getHook("useEffect", 1)?.unmount
).toHaveBeenCalledTimes(1);

Testing referential stability

hasBeenChanged tells you whether the value returned by useCallback or useMemo is a new one - which is the whole point of using them.

render(<Form />);

const memo = collector.getReactHooks(Form.name)?.getHook("useMemo", 1);

// first render, nothing to compare with yet
expect(memo?.hasBeenChanged).toBeFalsy();

fireEvent.change(screen.getByRole("textbox"), { target: { value: "a" } });

// the component re-rendered, but the memoized value stayed the same
expect(collector.getCallCount(Form.name)).toBe(2);
expect(
    collector.getReactHooks(Form.name)?.getHook("useMemo", 1)?.hasBeenChanged
).toBeFalsy();

Following the state between the renders

getUseState and getUseReducer give you every value the state ever had.

render(<Counter />);

const state = collector.getReactHooks(Counter.name)?.getUseState(1);

expect(state?.getState(1)).toBe(0);

fireEvent.click(screen.getByRole("button"));
fireEvent.click(screen.getByRole("button"));

// every value since the first render
expect(state?.next()).toEqual([0, 1, 2]);

// and nothing new since the last call
expect(state?.next()).toEqual([]);

Telling identical components apart

Rendering the same component several times side by side is normally indistinguishable. The collector numbers them with nthChild, and a data-testid identifies them by name.

render(
    <List>
        <Row />
        <Row />
        <Row data-testid="last" />
    </List>
);
expect(collector.getCallCount(Row.name)).toBe(3);
expect(collector.getCallCount(Row.name, { nthChild: 1 })).toBe(1);
expect(collector.getCallCount(Row.name, { dataTestId: "last" })).toBe(1);

// or by the parent
expect(
    collector.getAllDataFor(Row.name, { parent: { name: List.name } }).length
).toBe(3);

Redux - does the store re-render too much

useSelector, useDispatch and connect are collected automatically when react-redux is installed. No configuration.

render(
    <Provider store={store}>
        <Counter />
        <UserName />
    </Provider>
);

fireEvent.click(screen.getByTestId("increment"));

const hooks = collector.getReactHooks(Counter.name);

// the action which was dispatched
expect(hooks?.getHook("useDispatch", 1)?.dispatch).toHaveBeenCalledWith({
    type: "increment"
});

// the value the selector returned on every render
expect(hooks?.getHook("useSelector", 1)?.result).toEqual([0, 1]);

// UserName selects another slice, so it must not have re-rendered
expect(collector.getCallCount(Counter.name)).toBe(2);
expect(collector.getCallCount(UserName.name)).toBe(1);

MobX - does the observer observe the right thing

observer, useLocalObservable and useLocalStore are collected automatically when mobx-react-lite or mobx-react is installed.

render(
    <>
        <Counter />
        <UserName />
    </>
);

act(() => store.increment());

// only the observer reading `count` re-rendered
expect(collector.getCallCount("Counter")).toBe(2);
expect(collector.getCallCount("UserName")).toBe(1);

Class components

Every lifecycle method the class implements is a jest.fn.

const { unmount } = render(<Modal title="Hello" />);

const lifecycle = collector.getReactLifecycle(Modal.name);

expect(lifecycle?.componentDidMount).toHaveBeenCalledTimes(1);
expect(lifecycle?.render).toHaveBeenCalledTimes(1);
expect(lifecycle?.setState).not.toHaveBeenCalled();

fireEvent.click(screen.getByRole("button"));

expect(lifecycle?.setState).toHaveBeenCalledTimes(1);
expect(lifecycle?.shouldComponentUpdate).toHaveBeenCalledTimes(1);
expect(lifecycle?.render).toHaveBeenCalledTimes(2);

unmount();

expect(lifecycle?.componentWillUnmount).toHaveBeenCalledTimes(1);

Every instance is collected separately, so two modals on the screen never mix their statistics.

Plain functions and class methods

The collector is not limited to components.

formatPrice(1000, "EUR");

expect(collector.getCallCount(formatPrice.name)).toBe(1);
expect(collector.getDataFor(formatPrice.name)?.calls[0].args).toEqual([
    1000,
    "EUR"
]);
expect(collector.getDataFor(formatPrice.name)?.calls[0].result).toBe("€1,000");

With mockClassMethods enabled, the methods of your classes are collected as well - including which method called which:

new Cart().addItem(item);

expect(collector.getCallCount("addItem")).toBe(1);
expect(
    collector.hasRegistered("recalculate", { parent: { name: "addItem" } })
).toBeTruthy();

Performance

The collector runs on every render of every component, so its own cost matters. Measured with npm run benchmark on the suite in this repository:

| Work | Overhead of the collector | | --- | --- | | One component, per render | < 0.1 ms | | A component with 5 hooks, per render | ~ 0.2 ms | | A list of 500 siblings | ~ 25 ms total | | Reading the data of 500 registrations | ~ 1 ms |

The numbers depend on the machine - run the benchmark yourself to get yours.

What that means in practice: a test rendering a few dozen components pays a couple of milliseconds. The collector is not something you will notice, and the test suite of this repository - 306 tests over four Jest projects - runs in about 8 seconds.

Resolving the identity of a component is the hot path, and it is optimized for it: the stack is captured unformatted and only the frames really needed are formatted, the result is cached per call site, sibling identification is linear in the number of siblings, and the file patterns are compiled once instead of per file.

The collector never distorts what it measures - in particular React.memo, connect and observer still bail out of a render exactly as they would without it.

Supported Versions

| Dependency | Supported | | --- | --- | | React | 16.8 - 19 | | Jest | 26 - 30 | | react-redux | 7 - 9 | | mobx | 6 | | mobx-react-lite / mobx-react | 3 - 4 / 7 - 9 |

The peer ranges are capped at the newest verified major (react-redux is >=7 <10, not >=7). A major which has not been run through the tests is not promised to work, therefore a new one widens the range only after its version set passes.

Every supported version set is verified in CI by the whole test suite of src - see compatibility. The repository runs it against the newest versions, one folder per older set runs the identical files against React 16 with react-redux 7, React 17 and React 18.

Both JSX runtimes work - the classic one (React.createElement, "jsx": "react") and the automatic one (react/jsx-runtime, "jsx": "react-jsx"), which is the default of every modern setup. Components wrapped in React.memo and React.forwardRef are collected as well.

NOTE: React calls a functional component with a second internal argument - the legacy context until React 18, undefined since React 19. It is part of calls[].args. Assert on args[0] to keep a test working on all versions.

List of Collected Hooks

useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState

With react-redux installed, useDispatch and useSelector are collected as well. With mobx-react-lite or mobx-react installed, useLocalObservable and its predecessor useLocalStore are collected too.

Create Collector Options

exclude

[an array of matches]

The collector is created for each test by default. To exclude a test from being processed, use exclude with a match pattern.

// it will exclude "custom.test.ts" directly under "src"
createCollector({
    exclude: ["src/custom.test.ts"],
    roots: ["src"]
});

excludeImports

[an array of matches]

The collector mocks every file and its exports by default. To exclude a file, use excludeImports with a match pattern.

NOTE: Excluded files are not considered in the parent tree.

// it will not mock "custom.ts" directly under "src"
createCollector({
    excludeImports: ["src/custom.ts"],
    roots: ["src"]
});

extensions

[an array of file extensions]

Defaults to .ts and .tsx. A new set replaces the default one. An empty array means the collector tries to mock every file matching the other options.

createCollector({
    extensions: [".ts", ".js"],
    roots: ["src"]
});

include

[an array of matches]

The collector is created for each test by default. With include, only the matching tests are processed. A test matching both include and exclude is not processed.

createCollector({
    include: ["src/custom.test.ts"],
    roots: ["src"]
});

includeImports

[an array of matches]

With includeImports, only the matching files are mocked. A file matching both includeImports and excludeImports is not mocked.

createCollector({
    includeImports: ["src/custom.ts"],
    roots: ["src"]
});

mockClassMethods

[a boolean]

Disabled by default. When enabled, every method of every class exported from a mocked file - on the prototype as well as the static ones - is collected, so you can assert how many times a method was called, with which arguments and what it returned.

Accessors are never mocked, because reading a property would become a collected call and change the behaviour of the class.

createCollector({
    mockClassMethods: true,
    roots: ["src"]
});
const calculator = new Calculator(10);

calculator.add(5);

expect(collector.getCallCount("add")).toBe(1);
expect(collector.getDataFor("add")?.calls[0].args).toEqual([5]);
expect(collector.getDataFor("add")?.calls[0].result).toBe(15);

roots

[an array of folders]

Required. At least one root folder of your sources, relative to the Jest process. All files matching the other options are mocked and processed.

createCollector({ roots: ["admin", "src"] });

// an inner folder works too
createCollector({ roots: ["src/inner-folder"] });

Matches

// any file in any folder under "src/utils"
"src/utils/**/*";

// "custom.ts" directly under "src"
"src/custom.ts";

// all files in any folder under "src" ending with ".exclude.test.ts"
"src/**/*.exclude.test.ts";

// the same, for ".exclude.test.ts" or ".exclude.test.tsx"
"src/**/*.exclude.test.(ts|tsx)";

// all files named "utils.ts" in every folder
"**/*/utils.ts";

// all files named "utils.ts" in the first folder level under "src" -
// it matches "src/folder/utils.ts" but not "src/utils.ts"
"src/**/utils.ts";

// all files directly under "src" ending with ".utils.ts"
"src/*.utils.ts";

NOTE: Matches are case sensitive. Always use the UNIX file system style, even on Windows.

API

Most methods take an options object which narrows down which registration you mean. See Options.

enableDataTestIdInheritance

// inheritance is disabled by default
enableDataTestIdInheritance(excludeNotMockedElements?: boolean): void

Each component should have a data-testid to be easily identified. If you do not want to set one on every component, enable inheritance and every component without an own data-testid inherits the one of its parent.

IMPORTANT: When testing, a mocked component must be at the highest level to inherit the data-testid correctly.

collector.enableDataTestIdInheritance();

render(
    <MockedComponent data-testid="test-id">
        <SimpleComponent />
    </MockedComponent>
);

expect(
    collector.hasComponent(SimpleComponent.name, { dataTestId: "test-id" })
).toBeTruthy();

It works through not mocked elements as well:

collector.enableDataTestIdInheritance();

render(
    <MockedComponent>
        <div data-testid="test-id">
            <SimpleComponent />
        </div>
    </MockedComponent>
);

expect(
    collector.hasComponent(SimpleComponent.name, { dataTestId: "test-id" })
).toBeTruthy();

Call it with true to let only mocked components pass their data-testid down:

collector.enableDataTestIdInheritance(true);

render(
    <Component data-testid="test-id-1">
        <div data-testid="test-id-2">
            <SimpleComponent />
        </div>
    </Component>
);

// the id of the not mocked div is ignored
expect(
    collector.hasComponent(SimpleComponent.name, { dataTestId: "test-id-1" })
).toBeTruthy();
expect(
    collector.hasComponent(SimpleComponent.name, { dataTestId: "test-id-2" })
).toBeFalsy();

disableDataTestIdInheritance

disableDataTestIdInheritance(): void

Disables a previously enabled inheritance. reset does it too.

getCallCount

getCallCount(name: string, options?: Options): number | undefined

The number of calls of a function or component, or undefined when it is not registered. For a React class component it is the number of render calls.

render(
    <>
        <SimpleComponent />
        <SimpleComponent />
    </>
);

// both are directly under the root and have no data-testid, therefore
// they cannot be told apart and count as one registration
expect(collector.getCallCount(SimpleComponent.name)).toBe(2);
// under a mocked parent they are numbered
render(
    <MockedComponent>
        <SimpleComponent />
        <SimpleComponent />
    </MockedComponent>
);

expect(collector.getCallCount(SimpleComponent.name, { nthChild: 1 })).toBe(1);
expect(collector.getCallCount(SimpleComponent.name, { nthChild: 2 })).toBe(1);
expect(collector.getCallCount(SimpleComponent.name, { nthChild: 3 })).toBeUndefined();
// or filtered by the parent
expect(
    collector.getCallCount(SimpleComponent.name, {
        parent: { name: MockedComponent.name }
    })
).toBe(2);
expect(collector.getCallCount(SimpleComponent.name, { parent: null })).toBe(1);

getComponentData

getComponentData(componentName: string, options?: Options): RegisteredFunction | undefined

The same as getDataFor, named for readability when you work with components.

getAllDataFor

getAllDataFor(name: string, options?: Options): RegisteredFunction[]
getAllDataFor(options: OptionsWithName): RegisteredFunction[]

interface OptionsWithName extends Options {
    name?: string;
}

All registrations matching the name and options. Unlike getDataFor it never logs a warning when there is more than one result.

collector.getAllDataFor(SimpleComponent.name);

// everything the collector knows about
collector.getAllDataFor({});

getDataFor

getDataFor(name: string, options?: Options): RegisteredFunction | undefined

The data of one function or component. If more than one matches, a warning is logged - narrow it down with relativePath, parent, dataTestId or nthChild, or pass ignoreWarning: true.

The returned object always has calls, current, jestFn and parent. A React functional component also has hooks, a React class component also has lifecycle.

  • calls - one entry per call
    • args - the arguments the function was called with
    • stats - time is how long the call took in milliseconds. For a React component this is the component itself, without its children, because React executes them separately
    • result - whatever the function returned
  • current - the identity of the registration
    • dataTestId - the data-testid if there was one
    • name - the name of the function or component
    • nthChild - the position among identical siblings, see getCallCount
    • originMock - true when the function was mocked by createCollector, false when it was mocked during the render to identify it and its children
    • relativePath - the file, always with forward slashes
  • hooks - see getReactHooks
  • jestFn - a jest.fn recording the same calls, useful for the jest matchers
  • lifecycle - see getReactLifecycle
  • parent - the parent registration, or null

getReactHooks

getReactHooks(componentName: string, options?: Options): {
    getAll(): ReactHooks | undefined;
    getAll(hookType: HookType): ReactHooks[typeof hookType][] | undefined;
    getHook(hookType: HookType, sequence: number): ReactHooks[typeof hookType] | undefined;
    getHooksByType(hookType: HookType): {
        get(sequence: number): ReactHooks[typeof hookType] | undefined;
    };
    getUseReducer(sequence: number): GetState;
    getUseState(sequence: number): GetState;
} | undefined

sequence is the order of the hook during the render and always starts at 1.

  • getAll - every registered hook as an object, or an array of one hook type when you pass a name.
  • getHook - one hook by type and sequence, undefined when it does not exist.
  • getHooksByType - an object with get(sequence), handy when you test several hooks of the same type.
  • getUseReducer / getUseState - helpers for the state:
    • getState(stateSequence) - the state of the n-th render.
    • next() - every state since the last call of next.
    • reset() - makes the next next() start from the beginning again.

Hook properties

Every function below is a jest.fn.

useCallback - action, deps, hasBeenChanged

useContext - args, context

useDispatch (react-redux) - dispatch, recording every dispatched action

useEffect - action, deps, unmount

useLocalObservable (MobX) - initializer, store

useMemo - deps, hasBeenChanged, result

useReducer - dispatch, initialState, reducer, state

useRef - args, hasBeenChanged, ref

useSelector (react-redux) - equalityFn, hasBeenChanged, result, selector

useState - initialState, setState, state

A complete example

const deps = [];
const action = jest.fn();
const unmountAction = jest.fn();

const Component = () => {
    const [state, setState] = React.useState(10);

    React.useEffect(() => {
        action();

        return unmountAction;
    }, deps);

    return <button onClick={() => setState(state + 1)}>Increase</button>;
};

// MockedComponent must be a component mocked by createCollector
const { unmount } = render(
    <MockedComponent>
        <Component />
    </MockedComponent>
);

const reactHooks = collector.getReactHooks(Component.name);
const useEffectHooks = reactHooks?.getHooksByType("useEffect");

expect(useEffectHooks?.get(1)?.action).toHaveBeenCalledTimes(1);
expect(useEffectHooks?.get(2)).toBeUndefined();
expect(useEffectHooks?.get(1)?.unmount).not.toHaveBeenCalled();

// both ways of getting a hook are equivalent
expect(reactHooks?.getHook("useEffect", 1)).toEqual(useEffectHooks?.get(1));

const firstUseState = reactHooks?.getUseState(1);

expect(firstUseState?.getState(1)).toEqual(10);

fireEvent.click(screen.getByRole("button"));

// the effect did not run again, its deps did not change
expect(useEffectHooks?.get(1)?.action).toHaveBeenCalledTimes(1);
expect(firstUseState?.getState(2)).toEqual(11);

// every state since the first render
expect(firstUseState?.next()).toEqual([10, 11]);
// and nothing new since the last call
expect(firstUseState?.next()).toEqual([]);

fireEvent.click(screen.getByRole("button"));

expect(firstUseState?.next()).toEqual([12]);

firstUseState?.reset();

expect(firstUseState?.next()).toEqual([10, 11, 12]);

unmount();

expect(useEffectHooks?.get(1)?.unmount).toHaveBeenCalledTimes(1);

getReactLifecycle

getReactLifecycle(componentName: string, options?: Options): ReactClassLifecycle | undefined

Every lifecycle method the class implements, as a jest.fn. The mocks are created per registration, so two instances of the same class never share their statistics.

Collected instance methods: UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps, UNSAFE_componentWillUpdate, componentDidCatch, componentDidMount, componentDidUpdate, componentWillUnmount, forceUpdate, getSnapshotBeforeUpdate, render, setState, shouldComponentUpdate.

Collected static methods: getDerivedStateFromError, getDerivedStateFromProps. They belong to the class, so the mock is shared by all registrations of that class.

IMPORTANT: A method the class does not implement stays undefined. Adding it would change how React renders the component - a shouldComponentUpdate returning undefined would stop every re-render.

getStats

getStats(): Stats[]
getStats(options?: GetStatsOptions): Stats[]
getStats(name: string, options?: GetStatsOptions): Stats[] | Stats | undefined

interface GetStatsOptions extends Options {
    excludeTime?: boolean;
}

Statistics for the whole collector or for one function. Use excludeTime for snapshot tests, because the time is different on every run.

// everything
collector.getStats();

// everything, without the time
collector.getStats({ excludeTime: true });

// everything rendered at the top of the tree
collector.getStats({ parent: null });

// one component - an object when there is one result, an array when more
collector.getStats(SimpleComponent.name);
collector.getStats(SimpleComponent.name, { dataTestId: "test-id" });

hasComponent

hasComponent(componentName: string, options?: Options): boolean

The same as hasRegistered, named for readability when you work with components.

hasRegistered

hasRegistered(name: string, options?: Options): boolean

Whether the function or component is registered.

render(<SimpleComponent />);

expect(collector.hasRegistered(SimpleComponent.name)).toBeTruthy();
expect(collector.hasComponent(SimpleComponent.name)).toBeTruthy();

reset

reset(): void
reset(name: string, options?: Options): void

Without arguments it clears everything and puts the collector back to its defaults. With a name it removes only that registration, which stays gone until the next render.

beforeEach(() => {
    collector.reset();
});

// or only one component
collector.reset(SimpleComponent.name);

Interfaces

Call

interface Call {
    args: unknown[];
    stats: CallStats;
    result?: unknown;
}

CallStats

interface CallStats {
    time?: number;
}

Identity

interface Identity {
    dataTestId: string | null;
    name: string;
    nthChild?: number;
    originMock: boolean;
    relativePath: string;
}

Options

interface Options {
    dataTestId?: string | null;
    ignoreWarning?: true;
    nthChild?: number;
    parent?: Parent | null;
    relativePath?: string;
}

Parent

interface Parent {
    dataTestId?: string | null;
    name?: string;
    nthChild?: number;
    originMock?: boolean;
    parent?: Parent | null;
    relativePath?: string;
}

ReactClassLifecycle

Only the methods the class really implements are defined.

interface ReactClassLifecycle {
    UNSAFE_componentWillMount?: jest.Mock;
    UNSAFE_componentWillReceiveProps?: jest.Mock;
    UNSAFE_componentWillUpdate?: jest.Mock;
    componentDidCatch?: jest.Mock;
    componentDidMount?: jest.Mock;
    componentDidUpdate?: jest.Mock;
    componentWillUnmount?: jest.Mock;
    forceUpdate?: jest.Mock;
    getDerivedStateFromError?: jest.Mock; // static, shared by the class
    getDerivedStateFromProps?: jest.Mock; // static, shared by the class
    getSnapshotBeforeUpdate?: jest.Mock;
    render?: jest.Mock;
    setState?: jest.Mock;
    shouldComponentUpdate?: jest.Mock;
}

ReactHooks

interface ReactHooks {
    useCallback: {
        action: jest.Mock;
        deps: unknown[];
        hasBeenChanged: boolean;
    }[];
    useContext: {
        args: unknown[];
        context: unknown;
    }[];
    // react-redux
    useDispatch: {
        dispatch: jest.Mock;
    }[];
    useEffect: {
        action: jest.Mock;
        deps: unknown[];
        unmount?: jest.Mock;
    }[];
    // mobx-react-lite / mobx-react
    useLocalObservable: {
        initializer: jest.Mock;
        store: unknown;
    }[];
    useMemo: {
        deps: unknown[];
        hasBeenChanged: boolean;
        result: jest.Mock | unknown;
    }[];
    useRef: {
        args: unknown[];
        hasBeenChanged: boolean;
        ref: {
            current?: unknown;
        };
    }[];
    useReducer: {
        dispatch: jest.Mock;
        initialState: unknown;
        reducer: jest.Mock;
        state: unknown[];
    }[];
    // react-redux
    useSelector: {
        equalityFn?: unknown;
        hasBeenChanged: boolean;
        result: unknown[];
        selector: jest.Mock;
    }[];
    useState: {
        initialState: unknown;
        setState: jest.Mock;
        state: unknown[];
    }[];
}

RegisteredFunction

interface RegisteredFunction {
    calls: Call[];
    current: Identity;
    hooks?: ReactHooks; // React functional components only
    jestFn: jest.Mock;
    lifecycle?: ReactClassLifecycle; // React class components only
    parent: RegisteredFunction | null;
}

Stats

interface Stats {
    calls: Call[];
    dataTestId: string | null;
    name: string;
    nthChild?: number;
    numberOfCalls: number;
    parent: Parent | null;
    relativePath: string;
}

More Examples

A runnable project is in examples.

License

Jest Collector is MIT licensed.