Skip to content

Commit

Permalink
tests(react): create tests for the react bindings to core (#17551)
Browse files Browse the repository at this point in the history
* Add tests.

* updates after API meeting.

* test(): add tests for create controller components.

* correct testing for controller component

* Ensure tests properly cleanup after each run.

* create common test utils for overlay and controllers.

* initial tests for ion router outlet

* simple update.

* add mocks for jest tests.
  • Loading branch information
jthoms1 authored Feb 20, 2019
1 parent cf60732 commit d9aa318
Show file tree
Hide file tree
Showing 22 changed files with 595 additions and 67 deletions.
1 change: 1 addition & 0 deletions react/__mocks__/@ionic/core/loader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.defineCustomElements = () => {};
File renamed without changes.
2 changes: 2 additions & 0 deletions react/__mocks__/ionicons/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

exports.addIcons = () => {};
1 change: 1 addition & 0 deletions react/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global.crypto = require('@trust/webcrypto');
25 changes: 20 additions & 5 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"clean": "rm -rf dist",
"compile": "npm run tsc",
"deploy": "np --any-branch",
"tsc": "tsc -p ."
"tsc": "tsc -p .",
"test": "jest"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
Expand All @@ -33,25 +34,39 @@
"dist/"
],
"devDependencies": {
"@trust/webcrypto": "^0.9.2",
"@types/jest": "23.3.9",
"@types/node": "10.12.9",
"@types/react": "16.7.6",
"@types/react-dom": "16.0.9",
"@types/react-router": "^4.4.3",
"@types/react-router": "^4.4.4",
"@types/react-router-dom": "^4.3.1",
"jest": "^23.0.0",
"jest-dom": "^3.0.2",
"np": "^3.1.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"typescript": "^3.2.2",
"np": "^3.1.0"
"react-testing-library": "^5.5.3",
"ts-jest": "^23.10.5",
"typescript": "^3.2.2"
},
"dependencies": {
"@ionic/core": "4.0.1"
"@ionic/core": "^4.0.2"
},
"peerDependencies": {
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1"
},
"jest": {
"preset": "ts-jest",
"setupTestFrameworkScriptFile": "<rootDir>/jest.setup.js",
"testPathIgnorePatterns": [
"node_modules",
"dist"
]
}
}
4 changes: 2 additions & 2 deletions react/src/components/IonLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { createControllerComponent } from './createControllerComponent';

export type LoadingOptions = Components.IonLoadingAttributes;

const IonActionSheet = createControllerComponent<LoadingOptions, HTMLIonLoadingElement, HTMLIonLoadingControllerElement>('ion-loading', 'ion-loading-controller')
export default IonActionSheet;
const IonLoading = createControllerComponent<LoadingOptions, HTMLIonLoadingElement, HTMLIonLoadingControllerElement>('ion-loading', 'ion-loading-controller')
export default IonLoading;
29 changes: 29 additions & 0 deletions react/src/components/IonPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';


type Props = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;

type InternalProps = Props & {
forwardedRef?: React.RefObject<HTMLDivElement>
};

type ExternalProps = Props & {
ref?: React.RefObject<HTMLDivElement>
};

const IonPage: React.SFC<InternalProps> = ({ children, forwardedRef, className, ...props }) => (
<div
className={className ? `ion-page ${className}` : 'ion-page'}
ref={forwardedRef}
{...props}
>
{children}
</div>
);

function forwardRef(props: InternalProps, ref: React.RefObject<HTMLDivElement>) {
return <IonPage {...props} forwardedRef={ref} />;
}
forwardRef.displayName = 'IonPage';

export default React.forwardRef<HTMLDivElement, ExternalProps>(forwardRef);
40 changes: 40 additions & 0 deletions react/src/components/__tests__/IonRouterOutlet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { SFC, ReactElement } from 'react';
import { Route, Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { render, cleanup } from 'react-testing-library';
import { IonRouterOutlet } from '../navigation/IonRouterOutlet';

afterEach(cleanup)

function createReactPage(text: string) {
const ReactPage: SFC = () => <span>{text}</span>;
return ReactPage;
}

function renderWithRouter(
ui: ReactElement<any>,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
} = {}
) {
return {
...render(<Router history={history}>{ui}</Router>),
history
}
}

test('landing on a bad page', () => {
const { container } = renderWithRouter(
<IonRouterOutlet>
<Route path="/:tab(schedule)" component={createReactPage('Schedule Home')} exact={true} />
<Route path="/:tab(speakers)" component={createReactPage('Speakers Home')} exact={true} />
<Route path="/:tab(speakers)/speaker/:id" component={createReactPage('Speaker Detail')} />
<Route path="/:tab(schedule|speakers)/sessions/:id" component={createReactPage('Session Detail')} />
</IonRouterOutlet>
, {
route: '/schedule',
});

expect(container.innerHTML).toContain('<ion-router-outlet><div class="ion-page"><span>Schedule Home</span></div></ion-router-outlet>');
})
49 changes: 49 additions & 0 deletions react/src/components/__tests__/createComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { Components } from '@ionic/core'
import { createReactComponent } from '../createComponent';
import { render, fireEvent, cleanup } from 'react-testing-library';

afterEach(cleanup);

describe('createComponent - events', () => {
test('should set events on handler', () => {
const FakeOnClick = jest.fn((e) => e);
const IonButton = createReactComponent<Components.IonButtonAttributes, HTMLIonButtonElement>('ion-button');

const { getByText } = render(
<IonButton onClick={FakeOnClick}>
ButtonNameA
</IonButton>
);
fireEvent.click(getByText('ButtonNameA'));
expect(FakeOnClick).toBeCalledTimes(1);
});

test('should add custom events', () => {
const FakeIonFocus = jest.fn((e) => e);
const IonInput = createReactComponent<Components.IonInputAttributes, HTMLIonInputElement>('ion-input');

const { getByText } = render(
<IonInput onIonFocus={FakeIonFocus}>
ButtonNameA
</IonInput>
);
const ionInputItem = getByText('ButtonNameA');
expect(Object.keys((ionInputItem as any).__events)).toEqual(['ionFocus']);
});
});

describe('createComponent - ref', () => {
test('should pass ref on to web component instance', () => {
const ionButtonRef: React.RefObject<any> = React.createRef();
const IonButton = createReactComponent<Components.IonButtonAttributes, HTMLIonButtonElement>('ion-button');

const { getByText } = render(
<IonButton ref={ionButtonRef}>
ButtonNameA
</IonButton>
)
const ionButtonItem = getByText('ButtonNameA');
expect(ionButtonRef.current).toEqual(ionButtonItem);
});
});
120 changes: 120 additions & 0 deletions react/src/components/__tests__/createControllerComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from 'react';
import { Components } from '@ionic/core';
import { createControllerComponent } from '../createControllerComponent';
import { render, waitForElement, wait } from 'react-testing-library';
import * as utils from '../utils';
import { createControllerUtils } from '../utils/controller-test-utils';
import 'jest-dom/extend-expect';

describe('createControllerComponent - events', () => {
const { cleanupAfterController, createControllerElement, augmentController} = createControllerUtils('ion-loading');
type LoadingOptions = Components.IonLoadingAttributes;
const IonLoading = createControllerComponent<LoadingOptions, HTMLIonLoadingElement, HTMLIonLoadingControllerElement>('ion-loading', 'ion-loading-controller')

afterEach(cleanupAfterController);

test('should create controller component outside of the react component', async () => {
const { container, baseElement } = render(
<>
<IonLoading
isOpen={false}
onDidDismiss={jest.fn()}
duration={2000}
>
</IonLoading>
<span>ButtonNameA</span>
</>
);
expect(container).toContainHTML('<div><span>ButtonNameA</span></div>');
expect(baseElement.querySelector('ion-loading-controller')).toBeInTheDocument();
});

test('should create component and attach props on opening', async () => {
const onDidDismiss = jest.fn();
const { baseElement, container, rerender } = render(
<IonLoading
isOpen={false}
onDidDismiss={onDidDismiss}
duration={2000}
>
ButtonNameA
</IonLoading>
);

const [element, presentFunction] = createControllerElement();
const loadingController = augmentController(baseElement, container, element);

const attachEventPropsSpy = jest.spyOn(utils, "attachEventProps");

rerender(
<IonLoading
isOpen={true}
onDidDismiss={onDidDismiss}
duration={2000}
>
ButtonNameA
</IonLoading>
);

await waitForElement(() => container.querySelector('ion-loading'));

expect((loadingController as any).create).toHaveBeenCalledWith({
duration: 2000,
children: 'ButtonNameA',
onIonLoadingDidDismiss: onDidDismiss
});
expect(presentFunction).toHaveBeenCalled();
expect(attachEventPropsSpy).toHaveBeenCalledWith(element, {
duration: 2000,
children: 'ButtonNameA',
onIonLoadingDidDismiss: onDidDismiss
});
});

test('should dismiss component on hiding', async () => {
const { container, baseElement, rerender } = render(
<IonLoading
isOpen={false}
onDidDismiss={jest.fn()}
duration={2000}
>
ButtonNameA
</IonLoading>
);

const [element, , dismissFunction] = createControllerElement();
augmentController(baseElement, container, element);

rerender(
<IonLoading
isOpen={true}
onDidDismiss={jest.fn()}
duration={2000}
>
ButtonNameA
</IonLoading>
);

await waitForElement(() => container.querySelector('ion-loading'));

rerender(
<IonLoading
isOpen={false}
onDidDismiss={jest.fn()}
duration={2000}
>
ButtonNameA
</IonLoading>
);

await wait(() => {
const item = container.querySelector('ion-loading');
if (item) {
throw new Error();
}
});

expect(dismissFunction).toHaveBeenCalled();
});

});
Loading

0 comments on commit d9aa318

Please sign in to comment.