mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00
tests(react): create tests for the react bindings to core (#17551)
* 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.
This commit is contained in:
1
react/__mocks__/@ionic/core/loader/index.js
Normal file
1
react/__mocks__/@ionic/core/loader/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
exports.defineCustomElements = () => {};
|
2
react/__mocks__/ionicons/index.js
Normal file
2
react/__mocks__/ionicons/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
exports.addIcons = () => {};
|
1
react/jest.setup.js
Normal file
1
react/jest.setup.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
global.crypto = require('@trust/webcrypto');
|
@ -24,7 +24,8 @@
|
|||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"compile": "npm run tsc",
|
"compile": "npm run tsc",
|
||||||
"deploy": "np --any-branch",
|
"deploy": "np --any-branch",
|
||||||
"tsc": "tsc -p ."
|
"tsc": "tsc -p .",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@ -33,25 +34,39 @@
|
|||||||
"dist/"
|
"dist/"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@trust/webcrypto": "^0.9.2",
|
||||||
"@types/jest": "23.3.9",
|
"@types/jest": "23.3.9",
|
||||||
"@types/node": "10.12.9",
|
"@types/node": "10.12.9",
|
||||||
"@types/react": "16.7.6",
|
"@types/react": "16.7.6",
|
||||||
"@types/react-dom": "16.0.9",
|
"@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": "^16.7.0",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"react-router": "^4.3.1",
|
"react-router": "^4.3.1",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"typescript": "^3.2.2",
|
"react-testing-library": "^5.5.3",
|
||||||
"np": "^3.1.0"
|
"ts-jest": "^23.10.5",
|
||||||
|
"typescript": "^3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ionic/core": "4.0.1"
|
"@ionic/core": "^4.0.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.7.0",
|
"react": "^16.7.0",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"react-router": "^4.3.1",
|
"react-router": "^4.3.1",
|
||||||
"react-router-dom": "^4.3.1"
|
"react-router-dom": "^4.3.1"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"setupTestFrameworkScriptFile": "<rootDir>/jest.setup.js",
|
||||||
|
"testPathIgnorePatterns": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ import { createControllerComponent } from './createControllerComponent';
|
|||||||
|
|
||||||
export type LoadingOptions = Components.IonLoadingAttributes;
|
export type LoadingOptions = Components.IonLoadingAttributes;
|
||||||
|
|
||||||
const IonActionSheet = createControllerComponent<LoadingOptions, HTMLIonLoadingElement, HTMLIonLoadingControllerElement>('ion-loading', 'ion-loading-controller')
|
const IonLoading = createControllerComponent<LoadingOptions, HTMLIonLoadingElement, HTMLIonLoadingControllerElement>('ion-loading', 'ion-loading-controller')
|
||||||
export default IonActionSheet;
|
export default IonLoading;
|
||||||
|
29
react/src/components/IonPage.tsx
Normal file
29
react/src/components/IonPage.tsx
Normal file
@ -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
react/src/components/__tests__/IonRouterOutlet.tsx
Normal file
40
react/src/components/__tests__/IonRouterOutlet.tsx
Normal file
@ -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
react/src/components/__tests__/createComponent.tsx
Normal file
49
react/src/components/__tests__/createComponent.tsx
Normal file
@ -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
react/src/components/__tests__/createControllerComponent.tsx
Normal file
120
react/src/components/__tests__/createControllerComponent.tsx
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
110
react/src/components/__tests__/createOverlayComponent.tsx
Normal file
110
react/src/components/__tests__/createOverlayComponent.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Components } from '@ionic/core'
|
||||||
|
import { createOverlayComponent } from '../createOverlayComponent';
|
||||||
|
import { render, waitForElement } from 'react-testing-library';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
import { createControllerUtils } from '../utils/controller-test-utils';
|
||||||
|
import 'jest-dom/extend-expect';
|
||||||
|
|
||||||
|
describe('createOverlayComponent - events', () => {
|
||||||
|
const { cleanupAfterController, createControllerElement, augmentController} = createControllerUtils('ion-action-sheet');
|
||||||
|
type ActionSheetOptions = Components.IonActionSheetAttributes;
|
||||||
|
const IonActionSheet = createOverlayComponent<ActionSheetOptions, HTMLIonActionSheetElement, HTMLIonActionSheetControllerElement>('ion-action-sheet', 'ion-action-sheet-controller');
|
||||||
|
|
||||||
|
afterEach(cleanupAfterController);
|
||||||
|
|
||||||
|
test('should set events on handler', async () => {
|
||||||
|
const onDismiss = jest.fn();
|
||||||
|
const { baseElement, container } = render(
|
||||||
|
<>
|
||||||
|
<IonActionSheet
|
||||||
|
isOpen={false}
|
||||||
|
onDidDismiss={onDismiss}
|
||||||
|
buttons={[]}
|
||||||
|
>
|
||||||
|
ButtonNameA
|
||||||
|
</IonActionSheet>
|
||||||
|
<span>ButtonNameA</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
expect(container).toContainHTML('<div><span>ButtonNameA</span></div>');
|
||||||
|
expect(baseElement.querySelector('ion-action-sheet-controller')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create component and attach props on opening', async () => {
|
||||||
|
const onDidDismiss = jest.fn();
|
||||||
|
const { baseElement, rerender, container } = render(
|
||||||
|
<IonActionSheet
|
||||||
|
isOpen={false}
|
||||||
|
onDidDismiss={onDidDismiss}
|
||||||
|
buttons={[]}
|
||||||
|
>
|
||||||
|
ButtonNameA
|
||||||
|
</IonActionSheet>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [element, presentFunction] = createControllerElement();
|
||||||
|
const actionSheetController = augmentController(baseElement, container, element);
|
||||||
|
|
||||||
|
const attachEventPropsSpy = jest.spyOn(utils, "attachEventProps");
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<IonActionSheet
|
||||||
|
isOpen={true}
|
||||||
|
onDidDismiss={onDidDismiss}
|
||||||
|
buttons={[]}
|
||||||
|
>
|
||||||
|
ButtonNameA
|
||||||
|
</IonActionSheet>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitForElement(() => container.querySelector('ion-action-sheet'));
|
||||||
|
|
||||||
|
expect((actionSheetController as any).create).toHaveBeenCalled();
|
||||||
|
expect(presentFunction).toHaveBeenCalled();
|
||||||
|
expect(attachEventPropsSpy).toHaveBeenCalledWith(element, {
|
||||||
|
buttons: [],
|
||||||
|
onIonActionSheetDidDismiss: onDidDismiss
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should dismiss component on hiding', async () => {
|
||||||
|
const [element, , dismissFunction] = createControllerElement();
|
||||||
|
|
||||||
|
const { baseElement, rerender, container } = render(
|
||||||
|
<IonActionSheet
|
||||||
|
isOpen={false}
|
||||||
|
onDidDismiss={jest.fn()}
|
||||||
|
buttons={[]}
|
||||||
|
>
|
||||||
|
ButtonNameA
|
||||||
|
</IonActionSheet>
|
||||||
|
);
|
||||||
|
|
||||||
|
augmentController(baseElement, container, element);
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<IonActionSheet
|
||||||
|
isOpen={true}
|
||||||
|
onDidDismiss={jest.fn()}
|
||||||
|
buttons={[]}
|
||||||
|
>
|
||||||
|
ButtonNameA
|
||||||
|
</IonActionSheet>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitForElement(() => container.querySelector('ion-action-sheet'));
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<IonActionSheet
|
||||||
|
isOpen={false}
|
||||||
|
onDidDismiss={jest.fn()}
|
||||||
|
buttons={[]}
|
||||||
|
>
|
||||||
|
ButtonNameA
|
||||||
|
</IonActionSheet>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(dismissFunction).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
88
react/src/components/__tests__/utils.ts
Normal file
88
react/src/components/__tests__/utils.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import * as utils from '../utils';
|
||||||
|
import 'jest-dom/extend-expect'
|
||||||
|
|
||||||
|
describe('isCoveredByReact', () => {
|
||||||
|
it('should identify standard events as covered by React', () => {
|
||||||
|
expect(utils.isCoveredByReact('click', document)).toEqual(true);
|
||||||
|
});
|
||||||
|
it('should identify custom events as not covered by React', () => {
|
||||||
|
expect(utils.isCoveredByReact('change', document)).toEqual(true);
|
||||||
|
expect(utils.isCoveredByReact('ionchange', document)).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('syncEvent', () => {
|
||||||
|
it('should add event on sync and readd on additional syncs', () => {
|
||||||
|
var div = document.createElement("div");
|
||||||
|
const addEventListener = jest.spyOn(div, "addEventListener");
|
||||||
|
const removeEventListener = jest.spyOn(div, "removeEventListener");
|
||||||
|
const ionClickCallback = jest.fn();
|
||||||
|
|
||||||
|
utils.syncEvent(div, 'ionClick', ionClickCallback);
|
||||||
|
expect(removeEventListener).not.toHaveBeenCalled();
|
||||||
|
expect(addEventListener).toHaveBeenCalledWith('ionClick', expect.any(Function));
|
||||||
|
|
||||||
|
utils.syncEvent(div, 'ionClick', ionClickCallback);
|
||||||
|
expect(removeEventListener).toHaveBeenCalledWith('ionClick', expect.any(Function));
|
||||||
|
expect(addEventListener).toHaveBeenCalledWith('ionClick', expect.any(Function));
|
||||||
|
|
||||||
|
const event = new CustomEvent('ionClick', { detail: 'test'});
|
||||||
|
div.dispatchEvent(event);
|
||||||
|
expect(ionClickCallback).toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ensureElementInBody', () => {
|
||||||
|
it('should return if exists', () => {
|
||||||
|
const element = document.createElement("some-random-thing");
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
const returnedElement = utils.ensureElementInBody('some-random-thing');
|
||||||
|
expect(returnedElement).toEqual(element);
|
||||||
|
expect(document.body.children.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create if it does not exist', () => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
|
||||||
|
const returnedElement = utils.ensureElementInBody('some-random-thing');
|
||||||
|
expect(returnedElement).toBeDefined();
|
||||||
|
expect(returnedElement.tagName).toEqual('SOME-RANDOM-THING');
|
||||||
|
expect(document.body.children.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('attachEventProps', () => {
|
||||||
|
it('should pass props to a dom node', () => {
|
||||||
|
const onIonClickCallback = () => {};
|
||||||
|
|
||||||
|
var div = document.createElement("div");
|
||||||
|
utils.attachEventProps(div, {
|
||||||
|
'children': [],
|
||||||
|
'style': 'color: red',
|
||||||
|
'ref': () => {},
|
||||||
|
'onClick': () => {},
|
||||||
|
'onIonClick': onIonClickCallback,
|
||||||
|
'testprop': ['red']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect((div as any).testprop).toEqual(['red']);
|
||||||
|
expect(div).toHaveStyle('');
|
||||||
|
expect(Object.keys((div as any).__events)).toEqual(['ionClick']);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateUniqueId', () => {
|
||||||
|
const uniqueRegexMatch = /^(\w){8}-(\w){4}-(\w){4}-(\w){4}-(\w){12}$/;
|
||||||
|
|
||||||
|
it('should generate a global unique id', () => {
|
||||||
|
const first = utils.generateUniqueId();
|
||||||
|
const second = utils.generateUniqueId();
|
||||||
|
|
||||||
|
expect(first).toMatch(uniqueRegexMatch);
|
||||||
|
expect(second).not.toEqual(first);
|
||||||
|
expect(second).toMatch(uniqueRegexMatch);
|
||||||
|
});
|
||||||
|
});
|
@ -33,11 +33,7 @@ export function createReactComponent<T extends object, E>(tagName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(props: InternalProps) {
|
componentWillReceiveProps(props: InternalProps) {
|
||||||
const node = ReactDOM.findDOMNode(this);
|
const node = ReactDOM.findDOMNode(this) as HTMLElement;
|
||||||
|
|
||||||
if (!(node instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachEventProps(node, props);
|
attachEventProps(node, props);
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,55 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { attachEventProps } from './utils'
|
import { OverlayEventDetail } from '@ionic/core';
|
||||||
import { ensureElementInBody, dashToPascalCase } from './utils';
|
import { attachEventProps, ensureElementInBody, dashToPascalCase, generateUniqueId } from './utils'
|
||||||
import { OverlayComponentElement, OverlayControllerComponentElement } from '../types';
|
import { OverlayComponentElement, OverlayControllerComponentElement } from '../types';
|
||||||
|
|
||||||
export function createControllerComponent<T extends object, E extends OverlayComponentElement, C extends OverlayControllerComponentElement<E>>(tagName: string, controllerTagName: string) {
|
export function createControllerComponent<T extends object, E extends OverlayComponentElement, C extends OverlayControllerComponentElement<E>>(tagName: string, controllerTagName: string) {
|
||||||
const displayName = dashToPascalCase(tagName);
|
const displayName = dashToPascalCase(tagName);
|
||||||
|
const dismissEventName = `on${displayName}DidDismiss`;
|
||||||
|
|
||||||
type ReactProps = {
|
type ReactProps = {
|
||||||
show: boolean;
|
isOpen: boolean;
|
||||||
|
onDidDismiss: (event: CustomEvent<OverlayEventDetail>) => void;
|
||||||
}
|
}
|
||||||
type Props = T & ReactProps;
|
type Props = T & ReactProps;
|
||||||
|
|
||||||
return class ReactControllerComponent extends React.Component<Props> {
|
return class ReactControllerComponent extends React.Component<Props> {
|
||||||
element: E;
|
element: E;
|
||||||
controllerElement: C;
|
controllerElement: C;
|
||||||
|
id: string;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.id = generateUniqueId();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get displayName() {
|
static get displayName() {
|
||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
this.controllerElement = ensureElementInBody<C>(controllerTagName);
|
this.controllerElement = ensureElementInBody<C>(controllerTagName);
|
||||||
await this.controllerElement.componentOnReady();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate(prevProps: Props) {
|
async componentDidUpdate(prevProps: Props) {
|
||||||
if (prevProps.show !== this.props.show && this.props.show === true) {
|
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
|
||||||
const { show, ...cProps} = this.props;
|
const { isOpen, onDidDismiss, ...cProps} = this.props;
|
||||||
|
const elementProps = {
|
||||||
|
...cProps,
|
||||||
|
[dismissEventName]: onDidDismiss
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.controllerElement.componentOnReady) {
|
||||||
|
await this.controllerElement.componentOnReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element = await this.controllerElement.create(elementProps);
|
||||||
|
attachEventProps(this.element, elementProps);
|
||||||
|
|
||||||
this.element = await this.controllerElement.create(cProps);
|
|
||||||
await this.element.present();
|
await this.element.present();
|
||||||
|
|
||||||
attachEventProps(this.element, cProps);
|
|
||||||
}
|
}
|
||||||
if (prevProps.show !== this.props.show && this.props.show === false) {
|
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
|
||||||
await this.element.dismiss();
|
await this.element.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { OverlayEventDetail } from '@ionic/core';
|
||||||
import { attachEventProps } from './utils'
|
import { attachEventProps } from './utils'
|
||||||
import { ensureElementInBody, dashToPascalCase } from './utils';
|
import { ensureElementInBody, dashToPascalCase } from './utils';
|
||||||
import { OverlayComponentElement, OverlayControllerComponentElement } from '../types';
|
import { OverlayComponentElement, OverlayControllerComponentElement } from '../types';
|
||||||
|
|
||||||
export function createOverlayComponent<T extends object, E extends OverlayComponentElement, C extends OverlayControllerComponentElement<E>>(tagName: string, controllerTagName: string) {
|
export function createOverlayComponent<T extends object, E extends OverlayComponentElement, C extends OverlayControllerComponentElement<E>>(tagName: string, controllerTagName: string) {
|
||||||
const displayName = dashToPascalCase(tagName);
|
const displayName = dashToPascalCase(tagName);
|
||||||
|
const dismissEventName = `on${displayName}DidDismiss`;
|
||||||
|
|
||||||
type ReactProps = {
|
type ReactProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
show: boolean;
|
isOpen: boolean;
|
||||||
|
onDidDismiss: (event: CustomEvent<OverlayEventDetail>) => void;
|
||||||
}
|
}
|
||||||
type Props = T & ReactProps;
|
type Props = T & ReactProps;
|
||||||
|
|
||||||
@ -28,25 +31,32 @@ export function createOverlayComponent<T extends object, E extends OverlayCompon
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
this.controllerElement = ensureElementInBody<C>(controllerTagName);
|
this.controllerElement = ensureElementInBody<C>(controllerTagName);
|
||||||
await this.controllerElement.componentOnReady();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate(prevProps: Props) {
|
async componentDidUpdate(prevProps: Props) {
|
||||||
if (prevProps.show !== this.props.show && this.props.show === true) {
|
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
|
||||||
const { children, show, ...cProps} = this.props;
|
const { children, isOpen, onDidDismiss, ...cProps} = this.props;
|
||||||
|
const elementProps = {
|
||||||
|
...cProps,
|
||||||
|
[dismissEventName]: onDidDismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.controllerElement.componentOnReady) {
|
||||||
|
await this.controllerElement.componentOnReady();
|
||||||
|
}
|
||||||
|
|
||||||
this.element = await this.controllerElement.create({
|
this.element = await this.controllerElement.create({
|
||||||
...cProps,
|
...elementProps,
|
||||||
component: this.el,
|
component: this.el,
|
||||||
componentProps: {}
|
componentProps: {}
|
||||||
});
|
});
|
||||||
await this.element.present();
|
attachEventProps(this.element, elementProps);
|
||||||
|
|
||||||
attachEventProps(this.element, cProps);
|
await this.element.present();
|
||||||
}
|
}
|
||||||
if (prevProps.show !== this.props.show && this.props.show === false) {
|
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
|
||||||
await this.element.dismiss();
|
await this.element.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,39 @@
|
|||||||
|
import { addIcons } from 'ionicons';
|
||||||
|
import { ICON_PATHS } from 'ionicons/icons';
|
||||||
|
import { defineCustomElements } from '@ionic/core/loader';
|
||||||
import { Components as IoniconsComponents } from 'ionicons';
|
import { Components as IoniconsComponents } from 'ionicons';
|
||||||
import { Components } from '@ionic/core';
|
import { Components } from '@ionic/core';
|
||||||
import { createReactComponent } from './createComponent';
|
import { createReactComponent } from './createComponent';
|
||||||
|
|
||||||
export { AlertButton, AlertInput } from '@ionic/core';
|
export { AlertButton, AlertInput } from '@ionic/core';
|
||||||
|
|
||||||
export { default as IonActionSheet } from './IonActionSheet';
|
// createControllerComponent
|
||||||
export { default as IonAlert } from './IonAlert';
|
export { default as IonAlert } from './IonAlert';
|
||||||
export { default as IonLoading } from './IonLoading';
|
export { default as IonLoading } from './IonLoading';
|
||||||
|
export { default as IonToast } from './IonToast';
|
||||||
|
|
||||||
|
// createOverlayComponent
|
||||||
|
export { default as IonActionSheet } from './IonActionSheet';
|
||||||
export { default as IonModal } from './IonModal';
|
export { default as IonModal } from './IonModal';
|
||||||
export { default as IonPopover } from './IonPopover';
|
export { default as IonPopover } from './IonPopover';
|
||||||
export { default as IonToast } from './IonToast';
|
|
||||||
|
// Custom Components
|
||||||
|
export { default as IonPage } from './IonPage';
|
||||||
export { default as IonTabs } from './navigation/IonTabs';
|
export { default as IonTabs } from './navigation/IonTabs';
|
||||||
export { default as IonTabBar } from './navigation/IonTabBar';
|
export { default as IonTabBar } from './navigation/IonTabBar';
|
||||||
export { IonRouterOutlet, IonBackButton } from './navigation/IonRouterOutlet';
|
export { IonRouterOutlet, IonBackButton } from './navigation/IonRouterOutlet';
|
||||||
|
|
||||||
|
addIcons(ICON_PATHS);
|
||||||
|
defineCustomElements(window);
|
||||||
|
|
||||||
|
// ionicons
|
||||||
|
export const IonIcon = createReactComponent<IoniconsComponents.IonIconAttributes, HTMLIonIconElement>('ion-icon');
|
||||||
|
|
||||||
|
// createReactComponent
|
||||||
export const IonTabBarInner = createReactComponent<Components.IonTabBarAttributes, HTMLIonTabBarElement>('ion-tab-bar');
|
export const IonTabBarInner = createReactComponent<Components.IonTabBarAttributes, HTMLIonTabBarElement>('ion-tab-bar');
|
||||||
export const IonRouterOutletInner = createReactComponent<Components.IonRouterOutletAttributes, HTMLIonRouterOutletElement>('ion-router-outlet');
|
export const IonRouterOutletInner = createReactComponent<Components.IonRouterOutletAttributes, HTMLIonRouterOutletElement>('ion-router-outlet');
|
||||||
export const IonBackButtonInner = createReactComponent<Components.IonBackButtonAttributes, HTMLIonBackButtonElement>('ion-back-button');
|
export const IonBackButtonInner = createReactComponent<Components.IonBackButtonAttributes, HTMLIonBackButtonElement>('ion-back-button');
|
||||||
export const IonTab = createReactComponent<Components.IonTabAttributes, HTMLIonTabElement>('ion-tab');
|
export const IonTab = createReactComponent<Components.IonTabAttributes, HTMLIonTabElement>('ion-tab');
|
||||||
export const IonTabButton = createReactComponent<Components.IonTabButtonAttributes, HTMLIonTabButtonElement>('ion-tab-button');
|
export const IonTabButton = createReactComponent<Components.IonTabButtonAttributes, HTMLIonTabButtonElement>('ion-tab-button');
|
||||||
|
|
||||||
export const IonAnchor = createReactComponent<Components.IonAnchorAttributes, HTMLIonAnchorElement>('ion-anchor');
|
export const IonAnchor = createReactComponent<Components.IonAnchorAttributes, HTMLIonAnchorElement>('ion-anchor');
|
||||||
export const IonApp = createReactComponent<Components.IonAppAttributes, HTMLIonAppElement>('ion-app');
|
export const IonApp = createReactComponent<Components.IonAppAttributes, HTMLIonAppElement>('ion-app');
|
||||||
export const IonAvatar = createReactComponent<Components.IonAvatarAttributes, HTMLIonAvatarElement>('ion-avatar');
|
export const IonAvatar = createReactComponent<Components.IonAvatarAttributes, HTMLIonAvatarElement>('ion-avatar');
|
||||||
@ -43,7 +57,6 @@ export const IonFabList = createReactComponent<Components.IonFabListAttributes,
|
|||||||
export const IonFooter = createReactComponent<Components.IonFooterAttributes, HTMLIonFooterElement>('ion-footer');
|
export const IonFooter = createReactComponent<Components.IonFooterAttributes, HTMLIonFooterElement>('ion-footer');
|
||||||
export const IonGrid = createReactComponent<Components.IonGridAttributes, HTMLIonGridElement>('ion-grid');
|
export const IonGrid = createReactComponent<Components.IonGridAttributes, HTMLIonGridElement>('ion-grid');
|
||||||
export const IonHeader = createReactComponent<Components.IonHeaderAttributes, HTMLIonHeaderElement>('ion-header');
|
export const IonHeader = createReactComponent<Components.IonHeaderAttributes, HTMLIonHeaderElement>('ion-header');
|
||||||
export const IonIcon = createReactComponent<IoniconsComponents.IonIconAttributes, HTMLIonIconElement>('ion-icon');
|
|
||||||
export const IonImg = createReactComponent<Components.IonImgAttributes, HTMLIonImgElement>('ion-img');
|
export const IonImg = createReactComponent<Components.IonImgAttributes, HTMLIonImgElement>('ion-img');
|
||||||
export const IonInfiniteScroll = createReactComponent<Components.IonInfiniteScrollAttributes, HTMLIonInfiniteScrollElement>('ion-infinite-scroll');
|
export const IonInfiniteScroll = createReactComponent<Components.IonInfiniteScrollAttributes, HTMLIonInfiniteScrollElement>('ion-infinite-scroll');
|
||||||
export const IonInput = createReactComponent<Components.IonInputAttributes, HTMLIonInputElement>('ion-input');
|
export const IonInput = createReactComponent<Components.IonInputAttributes, HTMLIonInputElement>('ion-input');
|
||||||
|
@ -4,6 +4,7 @@ import { Components } from '@ionic/core';
|
|||||||
import { generateUniqueId } from '../utils';
|
import { generateUniqueId } from '../utils';
|
||||||
import { Location } from 'history';
|
import { Location } from 'history';
|
||||||
import { IonBackButtonInner, IonRouterOutletInner } from '../index';
|
import { IonBackButtonInner, IonRouterOutletInner } from '../index';
|
||||||
|
import IonPage from '../IonPage';
|
||||||
|
|
||||||
type ChildProps = RouteProps & {
|
type ChildProps = RouteProps & {
|
||||||
computedMatch: match<any>
|
computedMatch: match<any>
|
||||||
@ -190,27 +191,27 @@ class RouterOutlet extends Component<Props, State> {
|
|||||||
props = {
|
props = {
|
||||||
'ref': this.leavingEl,
|
'ref': this.leavingEl,
|
||||||
'hidden': this.state.direction == null,
|
'hidden': this.state.direction == null,
|
||||||
'className': 'ion-page' + (this.state.direction == null ? ' ion-page-hidden' : '')
|
'className': (this.state.direction == null ? ' ion-page-hidden' : '')
|
||||||
};
|
};
|
||||||
} else if (item.id === this.state.activeId) {
|
} else if (item.id === this.state.activeId) {
|
||||||
props = {
|
props = {
|
||||||
'ref': this.enteringEl,
|
'ref': this.enteringEl,
|
||||||
'className': 'ion-page' + (this.state.direction != null ? ' ion-page-invisible' : '')
|
'className': (this.state.direction != null ? ' ion-page-invisible' : '')
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
props = {
|
props = {
|
||||||
'aria-hidden': true,
|
'aria-hidden': true,
|
||||||
'className': 'ion-page ion-page-hidden'
|
'className': 'ion-page-hidden'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<IonPage
|
||||||
{...props}
|
{...props}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
>
|
>
|
||||||
{ this.renderChild(item) }
|
{ this.renderChild(item) }
|
||||||
</div>
|
</IonPage>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
|
53
react/src/components/utils/controller-test-utils.ts
Normal file
53
react/src/components/utils/controller-test-utils.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { cleanup } from 'react-testing-library';
|
||||||
|
|
||||||
|
export function createControllerUtils(tagName: string) {
|
||||||
|
const elementTag = tagName;
|
||||||
|
const controllerTag = `${tagName}-controller`;
|
||||||
|
|
||||||
|
function cleanupAfterController() {
|
||||||
|
const controller = document.querySelector(controllerTag);
|
||||||
|
if (controller) {
|
||||||
|
controller.remove();
|
||||||
|
}
|
||||||
|
const element = document.querySelector(elementTag);
|
||||||
|
if (element) {
|
||||||
|
element.remove();
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createControllerElement(): [HTMLElement, jest.Mock, jest.Mock] {
|
||||||
|
const element = document.createElement(elementTag);
|
||||||
|
const presentFunction = jest.fn(() => {
|
||||||
|
element.setAttribute('active', 'true');
|
||||||
|
return Promise.resolve(true)
|
||||||
|
});
|
||||||
|
const dismissFunction = jest.fn(() => {
|
||||||
|
element.remove();
|
||||||
|
Promise.resolve(true)
|
||||||
|
});
|
||||||
|
(element as any).present = presentFunction;
|
||||||
|
(element as any).dismiss = dismissFunction;
|
||||||
|
|
||||||
|
return [element, presentFunction, dismissFunction];
|
||||||
|
}
|
||||||
|
|
||||||
|
function augmentController(baseElement: HTMLElement, container: HTMLElement, childElement: HTMLElement): HTMLElement {
|
||||||
|
const controller: HTMLElement = baseElement.querySelector(controllerTag);
|
||||||
|
(controller as any).componentOnReady = jest.fn(async () => {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
(controller as any).create = jest.fn(async () => {
|
||||||
|
container.append(childElement);
|
||||||
|
return childElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cleanupAfterController,
|
||||||
|
createControllerElement,
|
||||||
|
augmentController
|
||||||
|
};
|
||||||
|
}
|
@ -2,12 +2,12 @@
|
|||||||
* Checks if an event is supported in the current execution environment.
|
* Checks if an event is supported in the current execution environment.
|
||||||
* @license Modernizr 3.0.0pre (Custom Build) | MIT
|
* @license Modernizr 3.0.0pre (Custom Build) | MIT
|
||||||
*/
|
*/
|
||||||
function isCoveredByReact(eventNameSuffix: string) {
|
export function isCoveredByReact(eventNameSuffix: string, doc: Document = document) {
|
||||||
const eventName = 'on' + eventNameSuffix;
|
const eventName = 'on' + eventNameSuffix;
|
||||||
let isSupported = eventName in document;
|
let isSupported = eventName in doc;
|
||||||
|
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
const element = document.createElement('div');
|
const element = doc.createElement('div');
|
||||||
element.setAttribute(eventName, 'return;');
|
element.setAttribute(eventName, 'return;');
|
||||||
isSupported = typeof (<any>element)[eventName] === 'function';
|
isSupported = typeof (<any>element)[eventName] === 'function';
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ function isCoveredByReact(eventNameSuffix: string) {
|
|||||||
return isSupported;
|
return isSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncEvent(node: Element, eventName: string, newEventHandler: (e: Event) => any) {
|
export function syncEvent(node: Element, eventName: string, newEventHandler: (e: Event) => any) {
|
||||||
const eventStore = (node as any).__events || ((node as any).__events = {});
|
const eventStore = (node as any).__events || ((node as any).__events = {});
|
||||||
const oldEventHandler = eventStore[eventName];
|
const oldEventHandler = eventStore[eventName];
|
||||||
|
|
||||||
@ -25,11 +25,9 @@ function syncEvent(node: Element, eventName: string, newEventHandler: (e: Event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bind new listener.
|
// Bind new listener.
|
||||||
if (newEventHandler) {
|
node.addEventListener(eventName, eventStore[eventName] = function handler(e: Event) {
|
||||||
node.addEventListener(eventName, eventStore[eventName] = function handler(e: Event) {
|
newEventHandler.call(this, e);
|
||||||
newEventHandler.call(this, e);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dashToPascalCase = (str: string) => str.toLowerCase().split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join('');
|
export const dashToPascalCase = (str: string) => str.toLowerCase().split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join('');
|
||||||
@ -65,7 +63,7 @@ export function attachEventProps<E extends HTMLElement>(node: E, props: any) {
|
|||||||
|
|
||||||
export function generateUniqueId() {
|
export function generateUniqueId() {
|
||||||
return ([1e7].toString() + -1e3.toString() + -4e3.toString() + -8e3.toString() + -1e11.toString()).replace(/[018]/g, function(c: any) {
|
return ([1e7].toString() + -1e3.toString() + -4e3.toString() + -8e3.toString() + -1e11.toString()).replace(/[018]/g, function(c: any) {
|
||||||
const random = window.crypto.getRandomValues(new Uint8Array(1)) as Uint8Array;
|
const random = crypto.getRandomValues(new Uint8Array(1)) as Uint8Array;
|
||||||
return (c ^ random[0] & 15 >> c / 4).toString(16);
|
return (c ^ random[0] & 15 >> c / 4).toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -2,4 +2,4 @@ export * from './components';
|
|||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
export * from './register';
|
export { setupConfig } from '@ionic/core';
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { addIcons } from 'ionicons';
|
|
||||||
import { ICON_PATHS } from 'ionicons/icons';
|
|
||||||
import { IonicConfig } from '@ionic/core';
|
|
||||||
import { defineCustomElements } from '@ionic/core/loader';
|
|
||||||
import { IonicWindow } from './types';
|
|
||||||
|
|
||||||
export function registerIonic(config: IonicConfig = {}) {
|
|
||||||
const win: IonicWindow = window as any;
|
|
||||||
const Ionic = (win.Ionic = win.Ionic || {});
|
|
||||||
addIcons(ICON_PATHS);
|
|
||||||
|
|
||||||
Ionic.config = config;
|
|
||||||
defineCustomElements(window);
|
|
||||||
}
|
|
@ -5,6 +5,7 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"lib": ["dom", "es2015"],
|
"lib": ["dom", "es2015"],
|
||||||
"module": "es2015",
|
"module": "es2015",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
@ -22,6 +23,9 @@
|
|||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx"
|
"src/**/*.tsx"
|
||||||
],
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/__tests__/**"
|
||||||
|
],
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"buildOnSave": false
|
"buildOnSave": false
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user