Skip to content

Commit 576353e

Browse files
ElliotLuke Bowerman
andauthored
Action List: Sorting (#668)
* Added sorting capabilities to the ActionList component * Added util functions to provide automatic sorting behavior * Modified existing ActionList test to include sort check * Added unit tests to test util functions Co-authored-by: Luke Bowerman <[email protected]>
1 parent b36bb7a commit 576353e

File tree

16 files changed

+745
-137
lines changed

16 files changed

+745
-137
lines changed

packages/components/src/ActionList/ActionList.test.tsx

Lines changed: 113 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838

3939
const columns: ActionListColumns = [
4040
{
41+
canSort: true,
4142
children: 'ID',
4243
id: 'id',
4344
primaryKey: true,
@@ -68,9 +69,9 @@ const data = [
6869

6970
const header = (
7071
<>
71-
<ActionListHeaderColumn>Foo</ActionListHeaderColumn>
72-
<ActionListHeaderColumn>Bar</ActionListHeaderColumn>
73-
<ActionListHeaderColumn>FooBar</ActionListHeaderColumn>
72+
<ActionListHeaderColumn id="id">Foo</ActionListHeaderColumn>
73+
<ActionListHeaderColumn id="name">Bar</ActionListHeaderColumn>
74+
<ActionListHeaderColumn id="type">FooBar</ActionListHeaderColumn>
7475
</>
7576
)
7677

@@ -106,7 +107,7 @@ const actionListWithNoHeader = (
106107
</ActionList>
107108
)
108109

109-
describe('<ActionList /> : General Layout', () => {
110+
describe('ActionList', () => {
110111
let rafSpy: jest.SpyInstance<number, [FrameRequestCallback]>
111112

112113
beforeEach(() => {
@@ -119,104 +120,135 @@ describe('<ActionList /> : General Layout', () => {
119120
rafSpy.mockRestore()
120121
})
121122

122-
test('Renders a generated header and list item', () => {
123-
const { getByText } = renderWithTheme(actionListWithGeneratedHeader)
123+
describe('General Layout', () => {
124+
test('Renders a generated header and list item', () => {
125+
const { getByText } = renderWithTheme(actionListWithGeneratedHeader)
124126

125-
expect(getByText('ID')).toBeInTheDocument()
126-
expect(getByText('Name')).toBeInTheDocument()
127-
expect(getByText('Role')).toBeInTheDocument()
127+
expect(getByText('ID')).toBeInTheDocument()
128+
expect(getByText('Name')).toBeInTheDocument()
129+
expect(getByText('Role')).toBeInTheDocument()
128130

129-
expect(getByText('1')).toBeInTheDocument()
130-
expect(getByText('Richard Garfield')).toBeInTheDocument()
131-
expect(getByText('Game Designer')).toBeInTheDocument()
132-
})
131+
expect(getByText('1')).toBeInTheDocument()
132+
expect(getByText('Richard Garfield')).toBeInTheDocument()
133+
expect(getByText('Game Designer')).toBeInTheDocument()
134+
})
133135

134-
test('Renders a provided header and list item', () => {
135-
const { getByText, queryByText } = renderWithTheme(
136-
actionListWithProvidedHeader
137-
)
136+
test('Renders a provided header and list item', () => {
137+
const { getByText, queryByText } = renderWithTheme(
138+
actionListWithProvidedHeader
139+
)
138140

139-
expect(queryByText('ID')).not.toBeInTheDocument()
140-
expect(queryByText('Name')).not.toBeInTheDocument()
141-
expect(queryByText('Role')).not.toBeInTheDocument()
141+
expect(queryByText('ID')).not.toBeInTheDocument()
142+
expect(queryByText('Name')).not.toBeInTheDocument()
143+
expect(queryByText('Role')).not.toBeInTheDocument()
142144

143-
expect(getByText('Foo')).toBeInTheDocument()
144-
expect(getByText('Bar')).toBeInTheDocument()
145-
expect(getByText('FooBar')).toBeInTheDocument()
145+
expect(getByText('Foo')).toBeInTheDocument()
146+
expect(getByText('Bar')).toBeInTheDocument()
147+
expect(getByText('FooBar')).toBeInTheDocument()
146148

147-
expect(getByText('1')).toBeInTheDocument()
148-
expect(getByText('Richard Garfield')).toBeInTheDocument()
149-
expect(getByText('Game Designer')).toBeInTheDocument()
150-
})
149+
expect(getByText('1')).toBeInTheDocument()
150+
expect(getByText('Richard Garfield')).toBeInTheDocument()
151+
expect(getByText('Game Designer')).toBeInTheDocument()
152+
})
151153

152-
test('Renders no header if header prop value is false', () => {
153-
const { getByText, queryByText } = renderWithTheme(actionListWithNoHeader)
154+
test('Renders no header if header prop value is false', () => {
155+
const { getByText, queryByText } = renderWithTheme(actionListWithNoHeader)
154156

155-
expect(queryByText('ID')).not.toBeInTheDocument()
156-
expect(queryByText('Name')).not.toBeInTheDocument()
157-
expect(queryByText('Role')).not.toBeInTheDocument()
157+
expect(queryByText('ID')).not.toBeInTheDocument()
158+
expect(queryByText('Name')).not.toBeInTheDocument()
159+
expect(queryByText('Role')).not.toBeInTheDocument()
158160

159-
expect(getByText('1')).toBeInTheDocument()
160-
expect(getByText('Richard Garfield')).toBeInTheDocument()
161-
expect(getByText('Game Designer')).toBeInTheDocument()
162-
})
161+
expect(getByText('1')).toBeInTheDocument()
162+
expect(getByText('Richard Garfield')).toBeInTheDocument()
163+
expect(getByText('Game Designer')).toBeInTheDocument()
164+
})
163165

164-
test('Renders action menu on button click and handles clicks on list item and action', () => {
165-
const handleActionClick = jest.fn()
166-
const handleListItemClick = jest.fn()
167-
168-
const clickableItems = data.map(({ id, name, type }) => {
169-
const availableActions = (
170-
<>
171-
<ActionListItemAction onClick={handleActionClick}>
172-
View Profile
173-
</ActionListItemAction>
174-
</>
166+
test('Renders action menu on button click and handles clicks on list item and action', () => {
167+
const handleActionClick = jest.fn()
168+
const handleListItemClick = jest.fn()
169+
170+
const clickableItems = data.map(({ id, name, type }) => {
171+
const availableActions = (
172+
<>
173+
<ActionListItemAction onClick={handleActionClick}>
174+
View Profile
175+
</ActionListItemAction>
176+
</>
177+
)
178+
179+
return (
180+
<ActionListItem
181+
key={id}
182+
actions={availableActions}
183+
onClick={handleListItemClick}
184+
>
185+
<ActionListItemColumn>{id}</ActionListItemColumn>
186+
<ActionListItemColumn>{name}</ActionListItemColumn>
187+
<ActionListItemColumn>{type}</ActionListItemColumn>
188+
</ActionListItem>
189+
)
190+
})
191+
192+
const { getByRole, getByText, queryByText } = renderWithTheme(
193+
<ActionList columns={columns}>{clickableItems}</ActionList>
175194
)
176195

177-
return (
178-
<ActionListItem
179-
key={id}
180-
actions={availableActions}
181-
onClick={handleListItemClick}
182-
>
183-
<ActionListItemColumn>{id}</ActionListItemColumn>
184-
<ActionListItemColumn>{name}</ActionListItemColumn>
185-
<ActionListItemColumn>{type}</ActionListItemColumn>
186-
</ActionListItem>
196+
const listItemId = getByText('1')
197+
198+
expect(handleListItemClick.mock.calls.length).toBe(0)
199+
fireEvent.click(listItemId)
200+
expect(handleListItemClick.mock.calls.length).toBe(1)
201+
202+
fireEvent(
203+
listItemId,
204+
new MouseEvent('mouseenter', {
205+
bubbles: true,
206+
cancelable: true,
207+
})
187208
)
188-
})
189209

190-
const { getByRole, getByText, queryByText } = renderWithTheme(
191-
<ActionList columns={columns}>{clickableItems}</ActionList>
192-
)
210+
const listItemButton = getByRole('button')
211+
expect(queryByText('View Profile')).not.toBeInTheDocument()
193212

194-
const listItemId = getByText('1')
213+
fireEvent.click(listItemButton)
214+
const viewProfileAction = getByText('View Profile')
215+
expect(viewProfileAction).toBeInTheDocument()
195216

196-
expect(handleListItemClick.mock.calls.length).toBe(0)
197-
fireEvent.click(listItemId)
198-
expect(handleListItemClick.mock.calls.length).toBe(1)
217+
expect(handleActionClick.mock.calls.length).toBe(0)
218+
fireEvent.click(viewProfileAction)
219+
expect(handleActionClick.mock.calls.length).toBe(1)
199220

200-
fireEvent(
201-
listItemId,
202-
new MouseEvent('mouseenter', {
203-
bubbles: true,
204-
cancelable: true,
205-
})
221+
fireEvent.click(listItemButton)
222+
expect(queryByText('View Profile')).not.toBeInTheDocument()
223+
})
224+
})
225+
226+
describe('Sorting', () => {
227+
const doSort = jest.fn()
228+
const actionListWithSort = (
229+
<ActionList columns={columns} doSort={doSort}>
230+
{items}
231+
</ActionList>
206232
)
207233

208-
const listItemButton = getByRole('button')
209-
expect(queryByText('View Profile')).not.toBeInTheDocument()
234+
test('Calls doSort if canSort property is true', () => {
235+
const { getByText } = renderWithTheme(actionListWithSort)
236+
237+
const idColumnHeader = getByText('ID')
238+
fireEvent.click(idColumnHeader)
239+
expect(doSort.mock.calls.length).toBe(1)
210240

211-
fireEvent.click(listItemButton)
212-
const viewProfileAction = getByText('View Profile')
213-
expect(viewProfileAction).toBeInTheDocument()
241+
doSort.mockClear()
242+
})
214243

215-
expect(handleActionClick.mock.calls.length).toBe(0)
216-
fireEvent.click(viewProfileAction)
217-
expect(handleActionClick.mock.calls.length).toBe(1)
244+
test('Does not call doSort if canSort property is false', () => {
245+
const { getByText } = renderWithTheme(actionListWithSort)
218246

219-
fireEvent.click(listItemButton)
220-
expect(queryByText('View Profile')).not.toBeInTheDocument()
247+
const nameColumnHeader = getByText('Name')
248+
fireEvent.click(nameColumnHeader)
249+
expect(doSort.mock.calls.length).toBe(0)
250+
251+
doSort.mockClear()
252+
})
221253
})
222254
})

0 commit comments

Comments
 (0)