Coder Social home page Coder Social logo

ionic-react-test-utils's Introduction

Deprecation Notice

Note: This project has been deprecated and is no longer actively maintained.

See the Ionic Testing Library documentation for instructions on testing your Ionic React app.

Ionic React Test Utils

This is a set of helper methods to make testing easier in Ionic React with React Testing Library and Jest.

Installation

yarn add -D @ionic/react-test-utils

or

npm install --dev @ionic/react-test-utils

Custom ionFireEvent

ionFireEvent extends Testing Library's fireEvent by adding the custom ion* events. This can be used as a drop in replacement for fireEvent or used in conjunction with.

import { ionFireEvent as fireEvent } from '@ionic/react-test-utils';

...


fireEvent.ionChange(element, 'my text');

mockIonicReact

This method mocks out certain Ionic components that have issues rendering in JSDOM. To use it, open up setupTests.ts and add this to the file:

import { mockIonicReact } from '@ionic/react-test-utils';
mockIonicReact();

If you are using Ionic v6, you will also need to call setupIonicReact:

import { mockIonicReact } from '@ionic/react-test-utils';
import { setupIonicReact } from '@ionic/react';

setupIonicReact();
mockIonicReact();

waitForIonicReact

This function waits for Ionic React to be fully initialized. Use this in any test that renders Ionic components, to ensure the rendered markup has all classes etc. that Ionic adds at runtime.

import { render } from '@testing-library/react';
import { waitForIonicReact } from '@ionic/react-test-utils';
import MyComponent from './MyComponent';

describe('<MyComponent />', () => {
  it('renders consistently', async () => {
    const { baseElement } = render(<MyComponent/>);
    await waitForIonicReact();
    expect(baseElement).toMatchSnapshot();
  });
});

waitForIonicReact waits for the DOM to be stable, meaning no markup has changed for a certain period of time. This period is determined by the first parameter, timeout, which defaults to 750 milliseconds. There is also a global timeout determined by the second parameter, maxWaitTime. This defaults to 5000 milliseconds (five seconds) and, if hit, will cause the function to fail early. If you find your tests are running too slow or not detecting all Ionic behaviors, try tweaking these values.

ionic-react-test-utils's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ionic-react-test-utils's Issues

Using with Vitest gives ReferenceError: jest is not defined

Can we use it with Vitest?

I get this error:
Screenshot 2023-06-08 at 10 37 08 AM

ReferenceError: jest is not defined
 ❯ mockIonicReact node_modules/@ionic/react-test-utils/src/mocks/mockIonicReact.ts:6:3
 ❯ src/setupTests.ts:19:1
     17| expect.extend(matchers);
     18| 
     19| mockIonicReact();
       | ^

"@ionic/react": "^7.0.10",
"vite": "^4.3.9",
"@vitejs/plugin-react": "^4.0.0",
"jsdom": "^22.1.0",
"vitest": "^0.32.0",
"vitest-fetch-mock": "^0.2.2"

`mockIonCheckbox` throws an error when `onIonFocus` or `onIonBlur` is provided

When executing tests that use the IonToggle component mockIonCheckbox logs the following error:

console.error
      Warning: Unknown event handler property `onIonFocus`. It will be ignored.
          at input
          at Object.<anonymous>.exports.mockIonCheckbox (/node_modules/@ionic/react-test-utils/src/mocks/mockIonCheckbox.tsx:7:7)

console.error
      Warning: Unknown event handler property `onIonBlur`. It will be ignored.
          at input
          at Object.<anonymous>.exports.mockIonCheckbox (/node_modules/@ionic/react-test-utils/src/mocks/mockIonCheckbox.tsx:7:7)

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:67:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:43:5)
      at validateProperty$1 (node_modules/react-dom/cjs/react-dom.development.js:3448:9)
      at warnUnknownProperties (node_modules/react-dom/cjs/react-dom.development.js:3559:21)
      at validateProperties$2 (node_modules/react-dom/cjs/react-dom.development.js:3583:3)
      at validatePropertiesInDevelopment (node_modules/react-dom/cjs/react-dom.development.js:8765:5)
      at setInitialProperties (node_modules/react-dom/cjs/react-dom.development.js:9041:5)
      at finalizeInitialChildren (node_modules/react-dom/cjs/react-dom.development.js:10201:3)

IonicTabs need mocking

Hi, I'm trying to add tests for what happens when a user clicks a given IonTabButton but it seems impossible to trigger any kind of reaction.

Any hope for getting some mocks testable versions of <IonTabBar, <IonTabButton> and <IonTabLabel> ?

React does not recognize the `backdropDismiss` prop

Thank you Ionic team for providing a useful library for @ionic/react!

I faced the following warning when I tested a modal component.

 ● Console

    console.error node_modules/react-dom/cjs/react-dom.development.js:530
      Warning: React does not recognize the `backdropDismiss` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `backdropdismiss` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
          in div (created by MockControllerInner)
          in MockControllerInner (created by ForwardRef)
          in ForwardRef
          in Unknown
import React, { FunctionComponent } from 'react';
import { IonModal } from '@ionic/react';

type Props = {
  show: boolean;
  onDidDismiss?: () => void;
};

export const Modal: FunctionComponent<Props> = props => {
  const { show, onDidDismiss, children } = props;

  return (
    <IonModal isOpen={show} backdropDismiss={false} onDidDismiss={onDidDismiss}>
      {show && children}
    </IonModal>
  );
};
import React from 'react';
import { render } from '@testing-library/react';
import { Modal } from './Modal';

describe('Modal', () => {
  it('render', () => {
    const { asFragment } = render(
      <Modal show>
        <p>content</p>
      </Modal>
    );

    expect(asFragment()).toMatchSnapshot();
  });
});

Is it because of missing backdropDismiss prop in mockController?.

Imposibility of testing IonInput

Hi folks!

Currently i'm trying to test an IonInput but seems impossible.

test('input to be writable', () => {
    render(
    <Input title="input xd" value="this is a value" placeholder="ingrese"/>
    )

    const ionInput = screen.getByTitle('input xd');
    
    expect(ionInput).toBeInTheDocument();

    fireEvent.ionFocus(ionInput);
    fireEvent.ionChange(ionInput, {target: {value: "new value"}})

    expect(ionInput.value).toBe("new value"); <- ionInput.value always undefined
})

If this is because the value is stored on the <input /> then is impossible to get because jest does not know that there is an input on the dom

this -> const input = screen.getByRole('input') always return an error

bug: ionic-react-test-utils not compatible with vitest

Prerequisites

Ionic Framework Version

  • v6.x

Current Behavior

Unit Tests with vitest fail due to the following issue:

 FAIL  src/components/Header.test.tsx [ src/components/Header.test.tsx ]
SyntaxError: Unexpected token 'export'
 ❯ Object.compileFunction https:/ionic-react-vitest-example.w.staticblitz.com/blitz.bbb482e7415e406cfc62838751ca694c1dc24cf6.js:6:341311
 ❯ wrapSafe https:/ionic-react-vitest-example.w.staticblitz.com/blitz.bbb482e7415e406cfc62838751ca694c1dc24cf6.js:6:218270
 ❯ Module._compile https:/ionic-react-vitest-example.w.staticblitz.com/blitz.bbb482e7415e406cfc62838751ca694c1dc24cf6.js:6:218638
 ❯ Module._extensions..js https:/ionic-react-vitest-example.w.staticblitz.com/blitz.bbb482e7415e406cfc62838751ca694c1dc24cf6.js:6:219666
 ❯ Module.load https:/ionic-react-vitest-example.w.staticblitz.com/blitz.bbb482e7415e406cfc62838751ca694c1dc24cf6.js:6:217692
 ❯ Module._load https:/ionic-react-vitest-example.w.staticblitz.com/blitz.bbb482e7415e406cfc62838751ca694c1dc24cf6.js:6:215263

Module /home/projects/ionic-react-vitest-example/node_modules/@ionic/core/components/index.js:4 seems to be an ES Module but shipped in a CommonJS package. You might want to create an issue to the package "@ionic/core" asking them to ship the file in .mjs extension or add "type": "module" in their package.json.

As a temporary workaround you can try to inline the package by updating your config:

// vitest.config.js
export default {
  test: {
    deps: {
      inline: [
        "@ionic/core"
      ]
    }
  }
}

Expected Behavior

Expect @ionic/react-test-utils to be compatible with vitest

Steps to Reproduce

Following the Stackblitz example:

  1. Wait until web container is initialized and tests are successful run
  2. Open the test/setup.ts file and switch the comments block to use mockIonicReact
  3. Rerun tests with npm run test or npm run test:ui -> Tests failing
  4. Recommended workaround with deps: { inline: ["@ionic/core"] } remains broken

Code Reproduction URL

https://stackblitz.com/edit/ionic-react-vitest-example?file=src%2Ftest%2Fsetup.ts&initialPath=__vitest__

Additional Information

Could be related to the use of CommonJS and ES Module, which is already described in this ticket: ionic-team/ionic-framework#25104

onWillDissmiss() is not called in MockController

MockController calls onDidDismiss() but it does not call onWillDismiss(). it's probably a less used property, but in order for me to test my <IonActionSheet /> component, it needs to call onWillDismiss() for me to test the cancel button.

Here's the test I'm trying to write:

const Settings: React.FC = () => {
    const [logoutIntent, setLogoutIntent] = useState<boolean>(false)

    return (
        <IonPage>        
            <IonContent>
                <IonList>
                    <IonItem button onClick={() => setLogoutIntent(true)}>
                        <IonLabel>Log Out</IonLabel>
                    </IonItem>
                </IonList>
            </IonContent>
            <IonActionSheet
                header={'Are you sure you want to log out?'}
                isOpen={logoutIntent}
                onWillDismiss={() => {
                    setLogoutIntent(false)
                }}
                buttons={[{
                    text: 'Log Out',
                    role: 'destructive',
                    icon: logOut,
                    handler: () => {
                        signOut()
                    },
                }, {
                    text: 'Cancel',
                    icon: close,
                    role: 'cancel',
                }]}
            />
        </IonPage>
    )
}

// the test
    it('should cancel the log out when the Cancel confirm button is clicked', () => {

        render(<Settings />)
        const logoutItem = screen.getByText('Log Out', {selector: 'ion-label'})
        fireEvent.click(logoutItem)
        screen.getByText('Are you sure you want to log out?')

        const cancelButton = screen.getByText('Cancel', {selector: 'button'})

        fireEvent.click(cancelButton)
       
        expect(screen.queryByText('Are you sure you want to log out?')).not.toBeDefined()

    })

bug: onDidPresent not handled by MockController

onDidPresent is not handled in the mockController and is instead passed onto the wrapping <div /> through ...rest.

This results in the following error when attempting to test a component that implements this callback:
image

Expected result:
onDidPresent should be called when isOpen is switched from false to true

ionChange not firing correctly on IonRange

I'm currently trying to test the range component and it doesn't seem to be firing the event correctly.

Test.tsx

const Test: React.FC = () => (
  <IonRange 
    min={5}
    max={50}
    pin={true}
    snaps={true}
    ticks={false}
    onIonChange={(e: any) => console.log(e)} 
  />
);

Test.test.tsx

test('Range should be updated', async () => {
  const {  container } = render(<Test/>);
  const rangeSlider = container.querySelector('ion-range')
  ionFireEvent.ionChange(rangeSlider, 10)
});

Expected Console

console.log src/pages/Test.tsx
  CustomEvent { 
    isTrusted: [Getter] 
    detail {
      value: 10
    }
  }

Actual Console

console.log src/pages/Test.tsx
  CustomEvent { isTrusted: [Getter] }

Any ideas would be much appreciated!

mockIonCheckbox throws an unhelpful error when no onIonChange is defined.

The TypeError: onIonChange is not a function is thrown when an IonCheckbox is the used component with no handler for IonChange. For example:

<IonCheckbox
    checked={item.checked}
    onClick={() => handleClick(index)}
    title="Check this item"
/>

The stack trace does not make it easy to find what is the issue in that case, as the there is no mention of a checkbox in there.

This setup above works in the production code so it should be handled by the mock.

If it's not going to be handled it would be helpful to throw a meaningful error in this case.

ionChange doesn't work on searchBar

Have an issue with IonSearchbar onIonChange fireevent

Home.tsx

const Home: React.FC = () => {
 const handleSearch = (even: any) => {
  console.log(JSON.stringify(event)):
  }
  return <IonSearchbar title="searchbar" onIonChange={handleSearch} />
};

Home.test.tsx

test('page should do search', async () => {
  const {  findByTitle } = render(<Home />);
  const searchbar = await findByTitle('searchbar');
  fireEvent.ionChange(searchbar, 'sdf');
  debug();
});

In the console

 console.log src/pages/Home.tsx:17
      {"isTrusted":false}

Is this package using older RTL that causes previous unit test's render leaking into next

I wanted to make use of the ionChange event of fireEvent in my unit test to test that the callback I provide to the IonSegment component gets called when its onIonChange event is triggered. For this I added this library in my project, but when I do that I have the below problem that make my other tests to fail, which were not failing before adding this package.

Rendering the component from the previous test does not get cleaned up by itself when I render the component again in the next test. As a result, I get error such as TestingLibraryElementError: Found multiple elements by: because the same component is now rendered twice, once in each test.

it('renders without error', async () => {
    render(<IonSegment {...mockProps} />);
  });

it(`onChange called when segment's on change event is triggered, () => {
    const { queryByTestId } = render(<IonSegment {...mockProps} />);
    const component = queryByTestId('ion-segment');
    fireEvent.ionChange(component, {
      target: { value: 'selected' }
    });
    expect(mockProps.onChange).toBeCalled();
  });

I verified this by changing the sequence of the tests or keeping just one test and the error is avoided. FYI I am also calling setupIonicReact() and mockIonicReact() before each test, but that did not help resolve this error. As a temporary fix, I resolved this by calling cleanup after each test as below but this should not ideally be required.

 afterEach(() => {
    cleanup();
  });

The newer react testing library versions do not have this problem, is this package using the older version? And if so, is there a permanent solution to this?

bug: Error in mockIonicReact: "Invalid variable access: react_1"

My setupTests.ts file looks like this:

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import { mockIonicReact } from '@ionic/react-test-utils';
mockIonicReact();

Nothing too fancy going on there, mainly just added mockIonicReact(). But when I upgraded to version ^0.1.1, all tests are now failing with this error message:

  ● Test suite failed to run

    ReferenceError: <REDACTED>\node_modules\@ionic\react-test-utils\dist\mocks\mockIonicReact.js: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
    Invalid variable access: react_1
    Allowed objects: AbortController, AbortSignal, AggregateError, Array, ArrayBuffer, Atomics, BigInt, BigInt64Array, BigUint64Array, Boolean, Buffer, DataView, Date, Error, EvalError, Event, EventTarget, FinalizationRegistry, Float32Array, Float64Array, Function, Generator, GeneratorFunction, Infinity, Int16Array, Int32Array, Int8Array, InternalError, Intl, JSON, Map, Math, MessageChannel, MessageEvent, MessagePort, NaN, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect, RegExp, Set, SharedArrayBuffer, String, Symbol, SyntaxError, TextDecoder, TextEncoder, TypeError, URIError, URL, URLSearchParams, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray, WeakMap, WeakRef, WeakSet, WebAssembly, arguments, clearImmediate, clearInterval, clearTimeout, console, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, escape, eval, expect, global, globalThis, isFinite, isNaN, jest, parseFloat, parseInt, process, queueMicrotask, require, setImmediate, setInterval, setTimeout, undefined, unescape.
    Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with `mock` (case insensitive) are permitted.

       7 |     jest.mock('@ionic/react', () => {
       8 |         const rest = jest.requireActual('@ionic/react');
    >  9 |         return Object.assign(Object.assign({}, rest), { IonActionSheet: mockController_1.mockController, IonAlert: mockController_1.mockController, IonCheckbox: mockIonCheckbox_1.mockIonCheckbox, IonDatetime: react_1.IonInput, IonLoading: mockController_1.mockController, IonPicker: mockController_1.mockController, IonPopover: mockController_1.mockController, IonToast: mockController_1.mockController, IonModal: mockController_1.mockController, IonToggle: mockIonCheckbox_1.mockIonCheckbox });
         |                                                                                                                                                                                                                  ^^^^^^^
      10 |     });
      11 | }
      12 | exports.mockIonicReact = mockIonicReact;

      at File.buildCodeFrameError (node_modules/@babel/core/lib/transformation/file/file.js:250:12)
      at NodePath.buildCodeFrameError (node_modules/@babel/traverse/lib/path/index.js:138:21)
      at newFn (node_modules/@babel/traverse/lib/visitors.js:175:21)
      at NodePath._call (node_modules/@babel/traverse/lib/path/context.js:55:20)
      at NodePath.call (node_modules/@babel/traverse/lib/path/context.js:42:17)
      at NodePath.visit (node_modules/@babel/traverse/lib/path/context.js:92:31)
      at TraversalContext.visitQueue (node_modules/@babel/traverse/lib/context.js:116:16)

This does not happen if I downgrade back to version 0.0.3.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.