Skip to content

Commit

Permalink
fix(react): ensure tabs are resilient to optional tabs. (#17862)
Browse files Browse the repository at this point in the history
  • Loading branch information
jthoms1 committed Mar 22, 2019
1 parent 424879d commit c29f5a6
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 9 deletions.
102 changes: 102 additions & 0 deletions react/src/components/__tests__/IonTabs.spec.tsx
@@ -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 { Components } from '@ionic/core'
import { Components } from '@ionic/core';
import { createReactComponent } from '../createComponent';
import { render, fireEvent, cleanup } from 'react-testing-library';

Expand Down
File renamed without changes.
15 changes: 9 additions & 6 deletions react/src/components/navigation/IonTabBar.tsx
Expand Up @@ -25,7 +25,7 @@ class IonTabBar extends Component<Props, State> {
const tabActiveUrls: { [key: string]: Tab } = {};

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] = {
originalHref: child.props.href,
currentHref: child.props.href
Expand Down Expand Up @@ -72,12 +72,15 @@ class IonTabBar extends Component<Props, State> {
}

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, {
href,
onIonTabButtonClick: this.onTabButtonClick
})
return React.cloneElement(child, {
href,
onIonTabButtonClick: this.onTabButtonClick
});
}
return null;
}

render() {
Expand Down
7 changes: 5 additions & 2 deletions react/src/components/navigation/IonTabs.tsx
Expand Up @@ -32,10 +32,13 @@ export default class IonTabs extends Component<Props> {
let tabBar: React.ReactElement<{ slot: 'bottom' | 'top' }>;

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;
}
if (typeof child === 'object' && child.type === IonTabBar) {
if (child.type === IonTabBar) {
tabBar = child;
}
});
Expand Down

0 comments on commit c29f5a6

Please sign in to comment.