mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 17:42:15 +08:00
fix(react): ensure tabs are resilient to optional tabs. (#17862)
This commit is contained in:
102
react/src/components/__tests__/IonTabs.spec.tsx
Normal file
102
react/src/components/__tests__/IonTabs.spec.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React, { ReactElement } from 'react';
|
||||||
|
import { Router } from 'react-router-dom';
|
||||||
|
import { createMemoryHistory } from 'history'
|
||||||
|
import { IonTabs, IonTab, IonTabBar, IonTabButton, IonLabel, IonIcon} from '../index';
|
||||||
|
import { render, cleanup } from 'react-testing-library';
|
||||||
|
|
||||||
|
afterEach(cleanup)
|
||||||
|
|
||||||
|
function renderWithRouter(
|
||||||
|
ui: ReactElement<any>,
|
||||||
|
{
|
||||||
|
route = '/',
|
||||||
|
history = createMemoryHistory({ initialEntries: [route] }),
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...render(<Router history={history}>{ui}</Router>),
|
||||||
|
history
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('IonTabs', () => {
|
||||||
|
test('should render happy path', () => {
|
||||||
|
const { container } = renderWithRouter(
|
||||||
|
<IonTabs>
|
||||||
|
<IonTab tab="schedule">Schedule Content</IonTab>
|
||||||
|
<IonTab tab="speakers">Speakers Content</IonTab>
|
||||||
|
<IonTab tab="map">Map Content</IonTab>
|
||||||
|
<IonTab tab="about">About Content</IonTab>
|
||||||
|
|
||||||
|
<IonTabBar slot="bottom">
|
||||||
|
<IonTabButton tab="schedule">
|
||||||
|
<IonLabel>Schedule</IonLabel>
|
||||||
|
<IonIcon name="schedule"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
<IonTabButton tab="speakers">
|
||||||
|
<IonLabel>Speakers</IonLabel>
|
||||||
|
<IonIcon name="speakers"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
<IonTabButton tab="map">
|
||||||
|
<IonLabel>Map</IonLabel>
|
||||||
|
<IonIcon name="map"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
<IonTabButton tab="about">
|
||||||
|
<IonLabel>About</IonLabel>
|
||||||
|
<IonIcon name="about"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
</IonTabBar>
|
||||||
|
</IonTabs>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.children[0].children.length).toEqual(2);
|
||||||
|
expect(container.children[0].children[0].tagName).toEqual('DIV');
|
||||||
|
expect(container.children[0].children[0].className).toEqual('tabs-inner');
|
||||||
|
|
||||||
|
expect(container.children[0].children[1].tagName).toEqual('ION-TAB-BAR');
|
||||||
|
expect(container.children[0].children[1].children.length).toEqual(4);
|
||||||
|
expect(Array.from(container.children[0].children[1].children).map(c => c.tagName)).toEqual(['ION-TAB-BUTTON', 'ION-TAB-BUTTON', 'ION-TAB-BUTTON', 'ION-TAB-BUTTON']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow for conditional children', () => {
|
||||||
|
const { container } = renderWithRouter(
|
||||||
|
<IonTabs>
|
||||||
|
{false &&
|
||||||
|
<IonTab tab="schedule">Schedule Content</IonTab>
|
||||||
|
}
|
||||||
|
<IonTab tab="speakers">Speakers Content</IonTab>
|
||||||
|
<IonTab tab="map">Map Content</IonTab>
|
||||||
|
<IonTab tab="about">About Content</IonTab>
|
||||||
|
|
||||||
|
<IonTabBar slot="bottom">
|
||||||
|
{false &&
|
||||||
|
<IonTabButton tab="schedule">
|
||||||
|
<IonLabel>Schedule</IonLabel>
|
||||||
|
<IonIcon name="schedule"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
}
|
||||||
|
<IonTabButton tab="speakers">
|
||||||
|
<IonLabel>Speakers</IonLabel>
|
||||||
|
<IonIcon name="speakers"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
<IonTabButton tab="map">
|
||||||
|
<IonLabel>Map</IonLabel>
|
||||||
|
<IonIcon name="map"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
<IonTabButton tab="about">
|
||||||
|
<IonLabel>About</IonLabel>
|
||||||
|
<IonIcon name="about"></IonIcon>
|
||||||
|
</IonTabButton>
|
||||||
|
</IonTabBar>
|
||||||
|
</IonTabs>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.children[0].children.length).toEqual(2);
|
||||||
|
expect(container.children[0].children[0].tagName).toEqual('DIV');
|
||||||
|
expect(container.children[0].children[0].className).toEqual('tabs-inner');
|
||||||
|
|
||||||
|
expect(container.children[0].children[1].tagName).toEqual('ION-TAB-BAR');
|
||||||
|
expect(container.children[0].children[1].children.length).toEqual(3);
|
||||||
|
expect(Array.from(container.children[0].children[1].children).map(c => c.tagName)).toEqual(['ION-TAB-BUTTON', 'ION-TAB-BUTTON', 'ION-TAB-BUTTON']);
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Components } from '@ionic/core'
|
import { Components } from '@ionic/core';
|
||||||
import { createReactComponent } from '../createComponent';
|
import { createReactComponent } from '../createComponent';
|
||||||
import { render, fireEvent, cleanup } from 'react-testing-library';
|
import { render, fireEvent, cleanup } from 'react-testing-library';
|
||||||
|
|
@ -25,7 +25,7 @@ class IonTabBar extends Component<Props, State> {
|
|||||||
const tabActiveUrls: { [key: string]: Tab } = {};
|
const tabActiveUrls: { [key: string]: Tab } = {};
|
||||||
|
|
||||||
React.Children.forEach(this.props.children, (child) => {
|
React.Children.forEach(this.props.children, (child) => {
|
||||||
if (typeof child === 'object' && child.type === IonTabButton) {
|
if (child != null && typeof child === 'object' && child.props && child.type === IonTabButton) {
|
||||||
tabActiveUrls[child.props.tab] = {
|
tabActiveUrls[child.props.tab] = {
|
||||||
originalHref: child.props.href,
|
originalHref: child.props.href,
|
||||||
currentHref: child.props.href
|
currentHref: child.props.href
|
||||||
@ -72,12 +72,15 @@ class IonTabBar extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderChild = (activeTab: string) => (child: React.ReactElement<Components.IonTabButtonAttributes & { onIonTabButtonClick: (e: CustomEvent) => void }>) => {
|
renderChild = (activeTab: string) => (child: React.ReactElement<Components.IonTabButtonAttributes & { onIonTabButtonClick: (e: CustomEvent) => void }>) => {
|
||||||
const href = (child.props.tab === activeTab) ? this.props.location.pathname : (this.state.tabs[child.props.tab].currentHref);
|
if (child != null && typeof child === 'object' && child.props && child.type === IonTabButton) {
|
||||||
|
const href = (child.props.tab === activeTab) ? this.props.location.pathname : (this.state.tabs[child.props.tab].currentHref);
|
||||||
|
|
||||||
return React.cloneElement(child, {
|
return React.cloneElement(child, {
|
||||||
href,
|
href,
|
||||||
onIonTabButtonClick: this.onTabButtonClick
|
onIonTabButtonClick: this.onTabButtonClick
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -32,10 +32,13 @@ export default class IonTabs extends Component<Props> {
|
|||||||
let tabBar: React.ReactElement<{ slot: 'bottom' | 'top' }>;
|
let tabBar: React.ReactElement<{ slot: 'bottom' | 'top' }>;
|
||||||
|
|
||||||
React.Children.forEach(this.props.children, child => {
|
React.Children.forEach(this.props.children, child => {
|
||||||
if (typeof child === 'object' && child.type === IonRouterOutlet) {
|
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (child.type === IonRouterOutlet) {
|
||||||
outlet = child;
|
outlet = child;
|
||||||
}
|
}
|
||||||
if (typeof child === 'object' && child.type === IonTabBar) {
|
if (child.type === IonTabBar) {
|
||||||
tabBar = child;
|
tabBar = child;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user