Skip to content

Commit

Permalink
WIP(ui-tabs): add tests and a workaround for element key
Browse files Browse the repository at this point in the history
  • Loading branch information
joyenjoyer committed Oct 19, 2023
1 parent 4201f96 commit 7f32b7d
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 11 deletions.
6 changes: 5 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/ui-tabs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"@instructure/ui-color-utils": "8.46.1",
"@instructure/ui-test-locator": "8.46.1",
"@instructure/ui-test-utils": "8.46.1",
"@instructure/ui-themes": "8.46.1"
"@instructure/ui-themes": "8.46.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0"
},
"dependencies": {
"@babel/runtime": "^7.22.15",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-tabs/src/Tabs/Panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Panel extends Component<TabsPanelProps> {
variant: 'default',
isSelected: false,
padding: 'small',
active: 'false'
active: false
}

componentDidMount() {
Expand Down
85 changes: 85 additions & 0 deletions packages/ui-tabs/src/Tabs/__new-tests__/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 - present Instructure, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import React from 'react'

import { Tabs } from '../index'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'

describe('<Tabs />', () => {
it('should render the correct number of panels', () => {
const { container } = render(
<Tabs>
<Tabs.Panel renderTitle="First Tab">Tab 1 content</Tabs.Panel>
<Tabs.Panel renderTitle="Second Tab">Tab 2 content</Tabs.Panel>
<Tabs.Panel renderTitle="Third Tab" isDisabled>
Tab 3 content
</Tabs.Panel>
</Tabs>
)

expect(container.firstChild).toBeInTheDocument()
})

it('should render same content for other tabs as for the active one', () => {
const { container } = render(
<Tabs>
<Tabs.Panel renderTitle="First Tab" active>
CONTENT
</Tabs.Panel>
<Tabs.Panel id="secondTab" renderTitle="Second Tab" isSelected />
<Tabs.Panel renderTitle="Third Tab" />
</Tabs>
)

const tabPanel = screen.getByText('CONTENT')

expect(container).toBeInTheDocument()
expect(tabPanel).toBeInTheDocument()
})

it('should warn if multiple active tabs exist', () => {
const consoleMock = jest.spyOn(console, 'error').mockImplementation()
const { container } = render(
<Tabs>
<Tabs.Panel renderTitle="First Tab" active>
Tab 1 content
</Tabs.Panel>
<Tabs.Panel renderTitle="Second Tab" active>
Tab 2 content
</Tabs.Panel>
<Tabs.Panel renderTitle="Third Tab" isDisabled>
Tab 3 content
</Tabs.Panel>
</Tabs>
)

expect(container.firstChild).toBeInTheDocument()

expect(consoleMock.mock.calls[0][0]).toEqual(
'Warning: [Tabs] Only one Panel can be marked as active.'
)
})
})
39 changes: 31 additions & 8 deletions packages/ui-tabs/src/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import React, {
Component,
ComponentClass,
ComponentElement,
createElement
createElement,
ReactElement
} from 'react'

import keycode from 'keycode'
Expand Down Expand Up @@ -359,24 +360,44 @@ class Tabs extends Component<TabsProps, TabsState> {
_index: number,
generatedId: string,
selected: boolean,
panel: PanelChild
panel: PanelChild,
activePanel?: PanelChild
) {
const id = panel.props.id || generatedId

// fixHeight can be 0, so simply `fixheight` could return falsy value
const hasFixedHeight = typeof this.props.fixHeight !== 'undefined'

return safeCloneElement(panel, {
const commonProps = {
id: panel.props.id || `panel-${id}`,
labelledBy: `tab-${id}`,
isSelected: selected,
key: panel.props.id || `panel-${id}`,
variant: this.props.variant,
padding: panel.props.padding || this.props.padding,
textAlign: panel.props.textAlign || this.props.textAlign,
maxHeight: !hasFixedHeight ? this.props.maxHeight : undefined,
minHeight: !hasFixedHeight ? this.props.minHeight : '100%'
} as TabsPanelProps & { key: string }) as PanelChild
}

let activePanelClone = null
if (activePanel !== undefined) {
// cloning active panel with a proper custom key as a workaround because
// safeCloneElement overwrites it with the key from the original element
activePanelClone = React.cloneElement(activePanel as ReactElement, {
key: panel.props.id || `panel-${id}`
})

return safeCloneElement(activePanelClone, {
padding: activePanelClone.props.padding || this.props.padding,
textAlign: activePanelClone.props.textAlign || this.props.textAlign,
...commonProps
} as TabsPanelProps & { key: string }) as PanelChild
} else {
return safeCloneElement(panel, {
key: panel.props.id || `panel-${id}`,
padding: panel.props.padding || this.props.padding,
textAlign: panel.props.textAlign || this.props.textAlign,
...commonProps
} as TabsPanelProps & { key: string }) as PanelChild
}
}

handleFocusableRef = (el: Focusable | null) => {
Expand Down Expand Up @@ -456,7 +477,9 @@ class Tabs extends Component<TabsProps, TabsState> {

tabs.push(this.createTab(index, id, selected, child))
if (activePanels.length === 1) {
panels.push(this.clonePanel(index, id, selected, activePanels[0]))
panels.push(
this.clonePanel(index, id, selected, child, activePanels[0])
)
} else {
panels.push(this.clonePanel(index, id, selected, child))
}
Expand Down

0 comments on commit 7f32b7d

Please sign in to comment.