From c8a4e557a676c2250f080b334a390f19ed943c13 Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Fri, 13 May 2022 10:58:28 -0400 Subject: [PATCH] Add UI tests for /utils and /components (#23456) * Add UI tests for /utils and /components * add test for Table * Address PR feedback * Fix window prompt var * Fix TaskName test from rebase * fix lint errors (cherry picked from commit 694e380e52a1b2c9ad74148e1dda2914a0496b70) --- airflow/www/static/js/grid/LegendRow.jsx | 2 +- .../js/grid/{ => components}/Clipboard.jsx | 2 +- .../js/grid/components/Clipboard.test.jsx | 39 ++++ .../grid/{ => components}/InstanceTooltip.jsx | 4 +- .../grid/components/InstanceTooltip.test.jsx | 86 +++++++ .../js/grid/{ => components}/StatusBox.jsx | 4 +- .../static/js/grid/{ => components}/Table.jsx | 6 +- .../static/js/grid/components/Table.test.jsx | 211 ++++++++++++++++++ .../js/grid/{ => components}/TaskName.jsx | 2 +- .../js/grid/components/TaskName.test.jsx | 53 +++++ .../static/js/grid/{ => components}/Time.jsx | 5 +- .../static/js/grid/components/Time.test.jsx | 83 +++++++ .../www/static/js/grid/context/timezone.jsx | 5 +- airflow/www/static/js/grid/dagRuns/Bar.jsx | 2 +- .../www/static/js/grid/dagRuns/Tooltip.jsx | 2 +- airflow/www/static/js/grid/details/Header.jsx | 2 +- .../static/js/grid/details/content/Dag.jsx | 4 +- .../js/grid/details/content/dagRun/index.jsx | 6 +- .../details/content/taskInstance/Details.jsx | 6 +- .../content/taskInstance/MappedInstances.jsx | 6 +- airflow/www/static/js/grid/renderTaskRows.jsx | 4 +- .../www/static/js/grid/utils/gridData.test.js | 47 ++++ .../www/static/js/grid/utils/testUtils.jsx | 6 + .../www/static/js/grid/utils/useErrorToast.js | 4 +- .../js/grid/utils/useErrorToast.test.jsx | 52 +++++ .../www/static/js/grid/utils/useSelection.js | 16 +- .../js/grid/utils/useSelection.test.jsx | 70 ++++++ 27 files changed, 694 insertions(+), 35 deletions(-) rename airflow/www/static/js/grid/{ => components}/Clipboard.jsx (97%) create mode 100644 airflow/www/static/js/grid/components/Clipboard.test.jsx rename airflow/www/static/js/grid/{ => components}/InstanceTooltip.jsx (95%) create mode 100644 airflow/www/static/js/grid/components/InstanceTooltip.test.jsx rename airflow/www/static/js/grid/{ => components}/StatusBox.jsx (96%) rename airflow/www/static/js/grid/{ => components}/Table.jsx (97%) create mode 100644 airflow/www/static/js/grid/components/Table.test.jsx rename airflow/www/static/js/grid/{ => components}/TaskName.jsx (95%) create mode 100644 airflow/www/static/js/grid/components/TaskName.test.jsx rename airflow/www/static/js/grid/{ => components}/Time.jsx (92%) create mode 100644 airflow/www/static/js/grid/components/Time.test.jsx create mode 100644 airflow/www/static/js/grid/utils/gridData.test.js create mode 100644 airflow/www/static/js/grid/utils/useErrorToast.test.jsx create mode 100644 airflow/www/static/js/grid/utils/useSelection.test.jsx diff --git a/airflow/www/static/js/grid/LegendRow.jsx b/airflow/www/static/js/grid/LegendRow.jsx index 75fba14358413..b155c1ba22e2e 100644 --- a/airflow/www/static/js/grid/LegendRow.jsx +++ b/airflow/www/static/js/grid/LegendRow.jsx @@ -24,7 +24,7 @@ import { Text, } from '@chakra-ui/react'; import React from 'react'; -import { SimpleStatus } from './StatusBox'; +import { SimpleStatus } from './components/StatusBox'; const LegendRow = () => ( diff --git a/airflow/www/static/js/grid/Clipboard.jsx b/airflow/www/static/js/grid/components/Clipboard.jsx similarity index 97% rename from airflow/www/static/js/grid/Clipboard.jsx rename to airflow/www/static/js/grid/components/Clipboard.jsx index 5fa645e61a7ac..794e363fa0b0e 100644 --- a/airflow/www/static/js/grid/Clipboard.jsx +++ b/airflow/www/static/js/grid/components/Clipboard.jsx @@ -27,7 +27,7 @@ import { } from '@chakra-ui/react'; import { FiCopy } from 'react-icons/fi'; -import { useContainerRef } from './context/containerRef'; +import { useContainerRef } from '../context/containerRef'; export const ClipboardButton = forwardRef( ( diff --git a/airflow/www/static/js/grid/components/Clipboard.test.jsx b/airflow/www/static/js/grid/components/Clipboard.test.jsx new file mode 100644 index 0000000000000..a27cdf16ceaaa --- /dev/null +++ b/airflow/www/static/js/grid/components/Clipboard.test.jsx @@ -0,0 +1,39 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* global describe, test, expect, jest, window */ + +import React from 'react'; +import '@testing-library/jest-dom'; +import { render, fireEvent } from '@testing-library/react'; + +import { ClipboardButton } from './Clipboard'; + +describe('ClipboardButton', () => { + test('Loads button', async () => { + const windowPrompt = window.prompt; + window.prompt = jest.fn(); + const { getByText } = render(); + + const button = getByText(/copy/i); + fireEvent.click(button); + expect(window.prompt).toHaveBeenCalledWith('Copy to clipboard: Ctrl+C, Enter', 'lorem ipsum'); + window.prompt = windowPrompt; + }); +}); diff --git a/airflow/www/static/js/grid/InstanceTooltip.jsx b/airflow/www/static/js/grid/components/InstanceTooltip.jsx similarity index 95% rename from airflow/www/static/js/grid/InstanceTooltip.jsx rename to airflow/www/static/js/grid/components/InstanceTooltip.jsx index 49d24124db7de..ebcecc5341b32 100644 --- a/airflow/www/static/js/grid/InstanceTooltip.jsx +++ b/airflow/www/static/js/grid/components/InstanceTooltip.jsx @@ -20,8 +20,8 @@ import React from 'react'; import { Box, Text } from '@chakra-ui/react'; -import { finalStatesMap } from '../utils'; -import { formatDuration, getDuration } from '../datetime_utils'; +import { finalStatesMap } from '../../utils'; +import { formatDuration, getDuration } from '../../datetime_utils'; import Time from './Time'; const InstanceTooltip = ({ diff --git a/airflow/www/static/js/grid/components/InstanceTooltip.test.jsx b/airflow/www/static/js/grid/components/InstanceTooltip.test.jsx new file mode 100644 index 0000000000000..fc6ab848c958d --- /dev/null +++ b/airflow/www/static/js/grid/components/InstanceTooltip.test.jsx @@ -0,0 +1,86 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* global describe, test, expect */ + +import React from 'react'; +import { render } from '@testing-library/react'; + +import InstanceTooltip from './InstanceTooltip'; +import { Wrapper } from '../utils/testUtils'; + +const instance = { + startDate: new Date(), + endDate: new Date(), + state: 'success', + runId: 'run', +}; + +describe('Test Task InstanceTooltip', () => { + test('Displays a normal task', () => { + const { getByText } = render( + , + { wrapper: Wrapper }, + ); + + expect(getByText('Status: success')).toBeDefined(); + }); + + test('Displays a mapped task with overall status', () => { + const { getByText } = render( + , + { wrapper: Wrapper }, + ); + + expect(getByText('Overall Status: success')).toBeDefined(); + expect(getByText('2 mapped tasks')).toBeDefined(); + expect(getByText('success: 2')).toBeDefined(); + }); + + test('Displays a task group with overall status', () => { + const { getByText, queryByText } = render( + , + { wrapper: Wrapper }, + ); + + expect(getByText('Overall Status: success')).toBeDefined(); + expect(queryByText('mapped task')).toBeNull(); + expect(getByText('success: 1')).toBeDefined(); + }); +}); diff --git a/airflow/www/static/js/grid/StatusBox.jsx b/airflow/www/static/js/grid/components/StatusBox.jsx similarity index 96% rename from airflow/www/static/js/grid/StatusBox.jsx rename to airflow/www/static/js/grid/components/StatusBox.jsx index 14831562c031e..2868e8727c23b 100644 --- a/airflow/www/static/js/grid/StatusBox.jsx +++ b/airflow/www/static/js/grid/components/StatusBox.jsx @@ -28,8 +28,8 @@ import { } from '@chakra-ui/react'; import InstanceTooltip from './InstanceTooltip'; -import { useContainerRef } from './context/containerRef'; -import useFilters from './utils/useFilters'; +import { useContainerRef } from '../context/containerRef'; +import useFilters from '../utils/useFilters'; export const boxSize = 10; export const boxSizePx = `${boxSize}px`; diff --git a/airflow/www/static/js/grid/Table.jsx b/airflow/www/static/js/grid/components/Table.jsx similarity index 97% rename from airflow/www/static/js/grid/Table.jsx rename to airflow/www/static/js/grid/components/Table.jsx index 6f8e351d862aa..570becf12aa9e 100644 --- a/airflow/www/static/js/grid/Table.jsx +++ b/airflow/www/static/js/grid/components/Table.jsx @@ -187,13 +187,14 @@ const Table = ({ })} - {totalEntries > data.length && ( + {(canPreviousPage || canNextPage) && ( } /> } /> @@ -208,7 +210,7 @@ const Table = ({ - {upperCount} {' of '} - {totalEntries} + {totalEntries || data.length} )} diff --git a/airflow/www/static/js/grid/components/Table.test.jsx b/airflow/www/static/js/grid/components/Table.test.jsx new file mode 100644 index 0000000000000..dd6cea13efed6 --- /dev/null +++ b/airflow/www/static/js/grid/components/Table.test.jsx @@ -0,0 +1,211 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* global describe, test, expect */ + +import React from 'react'; +import '@testing-library/jest-dom'; +import { render, fireEvent, within } from '@testing-library/react'; +import { sortBy } from 'lodash'; + +import Table from './Table'; +import { ChakraWrapper } from '../utils/testUtils'; + +const data = [ + { firstName: 'Lamont', lastName: 'Grimes', country: 'United States' }, + { firstName: 'Alysa', lastName: 'Armstrong', country: 'Spain' }, + { firstName: 'Petra', lastName: 'Blick', country: 'France' }, + { firstName: 'Jeromy', lastName: 'Herman', country: 'Mexico' }, + { firstName: 'Eleonore', lastName: 'Rohan', country: 'Nigeria' }, +]; + +const columns = [ + { + Header: 'First Name', + accessor: 'firstName', + }, + { + Header: 'Last Name', + accessor: 'lastName', + }, + { + Header: 'Country', + accessor: 'country', + }, +]; + +describe('Test Table', () => { + test('Displays correct data', async () => { + const { getAllByRole, getByText, queryByTitle } = render( + , + { wrapper: ChakraWrapper }, + ); + + const rows = getAllByRole('row'); + const name1 = getByText(data[0].firstName); + const name2 = getByText(data[1].firstName); + const name3 = getByText(data[2].firstName); + const name4 = getByText(data[3].firstName); + const name5 = getByText(data[4].firstName); + const previous = queryByTitle('Previous Page'); + const next = queryByTitle('Next Page'); + + // table header is a row so add 1 to expected amount + expect(rows).toHaveLength(6); + + expect(name1).toBeInTheDocument(); + expect(name2).toBeInTheDocument(); + expect(name3).toBeInTheDocument(); + expect(name4).toBeInTheDocument(); + expect(name5).toBeInTheDocument(); + + // expect pagination to de hidden when fewer results than default pageSize (24) + expect(previous).toBeNull(); + expect(next).toBeNull(); + }); + + test('Shows empty state', async () => { + const { getAllByRole, getByText } = render( +
, + { wrapper: ChakraWrapper }, + ); + + const rows = getAllByRole('row'); + + // table header is a row so add 1 to expected amount + expect(rows).toHaveLength(2); + expect(getByText('No Data found.')).toBeInTheDocument(); + }); + + test('With pagination', async () => { + const { getAllByRole, queryByText, getByTitle } = render( +
, + { wrapper: ChakraWrapper }, + ); + + const name1 = data[0].firstName; + const name2 = data[1].firstName; + const name3 = data[2].firstName; + const name4 = data[3].firstName; + const name5 = data[4].firstName; + const previous = getByTitle('Previous Page'); + const next = getByTitle('Next Page'); + + /// // PAGE ONE // /// + // table header is a row so add 1 to expected amount + expect(getAllByRole('row')).toHaveLength(3); + + expect(queryByText(name1)).toBeInTheDocument(); + expect(queryByText(name2)).toBeInTheDocument(); + expect(queryByText(name3)).toBeNull(); + expect(queryByText(name4)).toBeNull(); + expect(queryByText(name5)).toBeNull(); + + // expect only pagination next button to be functional on 1st page + expect(previous).toBeDisabled(); + expect(next).toBeEnabled(); + + fireEvent.click(next); + + /// // PAGE TWO // /// + expect(getAllByRole('row')).toHaveLength(3); + + expect(queryByText(name1)).toBeNull(); + expect(queryByText(name2)).toBeNull(); + expect(queryByText(name3)).toBeInTheDocument(); + expect(queryByText(name4)).toBeInTheDocument(); + expect(queryByText(name5)).toBeNull(); + + // expect both pagination buttons to be functional on 2nd page + expect(previous).toBeEnabled(); + expect(next).toBeEnabled(); + + fireEvent.click(next); + + /// // PAGE THREE // /// + expect(getAllByRole('row')).toHaveLength(2); + + expect(queryByText(name1)).toBeNull(); + expect(queryByText(name2)).toBeNull(); + expect(queryByText(name3)).toBeNull(); + expect(queryByText(name4)).toBeNull(); + expect(queryByText(name5)).toBeInTheDocument(); + + // expect only pagination previous button to be functional on last page + expect(previous).toBeEnabled(); + expect(next).toBeDisabled(); + }); + + test('With sorting', async () => { + const { getAllByRole } = render(
, { + wrapper: ChakraWrapper, + }); + + // Default order matches original data order // + const firstNameHeader = getAllByRole('columnheader')[0]; + const rows = getAllByRole('row'); + + const firstRowName = within(rows[1]).queryByText(data[0].firstName); + const lastRowName = within(rows[5]).queryByText(data[4].firstName); + expect(firstRowName).toBeInTheDocument(); + expect(lastRowName).toBeInTheDocument(); + + fireEvent.click(firstNameHeader); + + /// // ASCENDING SORT // /// + const ascendingRows = getAllByRole('row'); + const ascendingData = sortBy(data, [(o) => o.firstName]); + + const ascendingFirstRowName = within(ascendingRows[1]).queryByText(ascendingData[0].firstName); + const ascendingLastRowName = within(ascendingRows[5]).queryByText(ascendingData[4].firstName); + expect(ascendingFirstRowName).toBeInTheDocument(); + expect(ascendingLastRowName).toBeInTheDocument(); + + fireEvent.click(firstNameHeader); + + /// // DESCENDING SORT // /// + const descendingRows = getAllByRole('row'); + const descendingData = sortBy(data, [(o) => o.firstName]).reverse(); + + const descendingFirstRowName = within(descendingRows[1]).queryByText( + descendingData[0].firstName, + ); + const descendingLastRowName = within(descendingRows[5]).queryByText( + descendingData[4].firstName, + ); + expect(descendingFirstRowName).toBeInTheDocument(); + expect(descendingLastRowName).toBeInTheDocument(); + }); + + test('Shows checkboxes', async () => { + const { getAllByTitle } = render( +
{}} />, + { wrapper: ChakraWrapper }, + ); + + const checkboxes = getAllByTitle('Toggle Row Selected'); + expect(checkboxes).toHaveLength(data.length); + + const checkbox1 = checkboxes[1]; + + fireEvent.click(checkbox1); + + expect(checkbox1).toHaveAttribute('data-checked'); + }); +}); diff --git a/airflow/www/static/js/grid/TaskName.jsx b/airflow/www/static/js/grid/components/TaskName.jsx similarity index 95% rename from airflow/www/static/js/grid/TaskName.jsx rename to airflow/www/static/js/grid/components/TaskName.jsx index 24c80b863e469..316dd89e956bd 100644 --- a/airflow/www/static/js/grid/TaskName.jsx +++ b/airflow/www/static/js/grid/components/TaskName.jsx @@ -25,7 +25,7 @@ import { import { FiChevronUp, FiChevronDown } from 'react-icons/fi'; const TaskName = ({ - isGroup = false, isMapped = false, onToggle, isOpen, level, label, + isGroup = false, isMapped = false, onToggle, isOpen = false, level = 0, label, }) => ( { + test('Displays a normal task name', () => { + const { getByText } = render( + , { wrapper: ChakraWrapper }, + ); + + expect(getByText('test')).toBeDefined(); + }); + + test('Displays a mapped task name', () => { + const { getByText } = render( + , { wrapper: ChakraWrapper }, + ); + + expect(getByText('test [ ]')).toBeDefined(); + }); + + test('Displays a group task name', () => { + const { getByText, getByTestId } = render( + , { wrapper: ChakraWrapper }, + ); + + expect(getByText('test')).toBeDefined(); + expect(getByTestId('closed-group')).toBeDefined(); + }); +}); diff --git a/airflow/www/static/js/grid/Time.jsx b/airflow/www/static/js/grid/components/Time.jsx similarity index 92% rename from airflow/www/static/js/grid/Time.jsx rename to airflow/www/static/js/grid/components/Time.jsx index 2cd940b10e9d7..571374be48bf2 100644 --- a/airflow/www/static/js/grid/Time.jsx +++ b/airflow/www/static/js/grid/components/Time.jsx @@ -20,8 +20,8 @@ /* global moment */ import React from 'react'; -import { useTimezone } from './context/timezone'; -import { defaultFormatWithTZ } from '../datetime_utils'; +import { useTimezone } from '../context/timezone'; +import { defaultFormatWithTZ } from '../../datetime_utils'; const Time = ({ dateTime, format = defaultFormatWithTZ }) => { const { timezone } = useTimezone(); @@ -32,6 +32,7 @@ const Time = ({ dateTime, format = defaultFormatWithTZ }) => { const formattedTime = time.tz(timezone).format(format); const utcTime = time.tz('UTC').format(defaultFormatWithTZ); + return (
diff --git a/airflow/www/static/js/grid/utils/useErrorToast.js b/airflow/www/static/js/grid/utils/useErrorToast.js index 02d3195f6cfea..842eb53810ba4 100644 --- a/airflow/www/static/js/grid/utils/useErrorToast.js +++ b/airflow/www/static/js/grid/utils/useErrorToast.js @@ -19,8 +19,8 @@ import { useToast } from '@chakra-ui/react'; -const getErrorDescription = (error, fallbackMessage) => { - if (error.response && error.response.data) { +export const getErrorDescription = (error, fallbackMessage) => { + if (error && error.response && error.response.data) { return error.response.data; } if (error instanceof Error) return error.message; diff --git a/airflow/www/static/js/grid/utils/useErrorToast.test.jsx b/airflow/www/static/js/grid/utils/useErrorToast.test.jsx new file mode 100644 index 0000000000000..39256b3d30320 --- /dev/null +++ b/airflow/www/static/js/grid/utils/useErrorToast.test.jsx @@ -0,0 +1,52 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* global describe, test, expect */ + +import { getErrorDescription } from './useErrorToast'; + +describe('Test getErrorDescription()', () => { + test('Returns expected results', () => { + let description; + + // is response.data is defined + description = getErrorDescription({ response: { data: 'uh oh' } }); + expect(description).toBe('uh oh'); + + // if it is not, use default message + description = getErrorDescription({ response: { data: '' } }); + expect(description).toBe('Something went wrong.'); + + // if error object, return the message + description = getErrorDescription(new Error('no no')); + expect(description).toBe('no no'); + + // if string, return the string + description = getErrorDescription('error!'); + expect(description).toBe('error!'); + + // if it's undefined, use a fallback + description = getErrorDescription(null, 'fallback'); + expect(description).toBe('fallback'); + + // use default if nothing is defined + description = getErrorDescription(); + expect(description).toBe('Something went wrong.'); + }); +}); diff --git a/airflow/www/static/js/grid/utils/useSelection.js b/airflow/www/static/js/grid/utils/useSelection.js index c7220b4f215a5..c90578837a2e1 100644 --- a/airflow/www/static/js/grid/utils/useSelection.js +++ b/airflow/www/static/js/grid/utils/useSelection.js @@ -32,13 +32,13 @@ const useSelection = () => { setSearchParams(searchParams); }; - const onSelect = (payload) => { + const onSelect = ({ runId, taskId }) => { const params = new URLSearchParams(searchParams); - if (payload.runId) params.set(RUN_ID, payload.runId); + if (runId) params.set(RUN_ID, runId); else params.delete(RUN_ID); - if (payload.taskId) params.set(TASK_ID, payload.taskId); + if (taskId) params.set(TASK_ID, taskId); else params.delete(TASK_ID); setSearchParams(params); @@ -46,9 +46,15 @@ const useSelection = () => { const runId = searchParams.get(RUN_ID); const taskId = searchParams.get(TASK_ID); - const selected = { runId, taskId }; - return { selected, clearSelection, onSelect }; + return { + selected: { + runId, + taskId, + }, + clearSelection, + onSelect, + }; }; export default useSelection; diff --git a/airflow/www/static/js/grid/utils/useSelection.test.jsx b/airflow/www/static/js/grid/utils/useSelection.test.jsx new file mode 100644 index 0000000000000..2d2eeeb4db742 --- /dev/null +++ b/airflow/www/static/js/grid/utils/useSelection.test.jsx @@ -0,0 +1,70 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* global describe, test, expect */ + +import React from 'react'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { MemoryRouter } from 'react-router-dom'; + +import useSelection from './useSelection'; + +const Wrapper = ({ children }) => ( + + {children} + +); + +describe('Test useSelection hook', () => { + test('Initial values', async () => { + const { result } = renderHook(() => useSelection(), { wrapper: Wrapper }); + const { + selected: { + runId, + taskId, + }, + } = result.current; + + expect(runId).toBeNull(); + expect(taskId).toBeNull(); + }); + + test.each([ + { taskId: 'task_1', runId: 'run_1' }, + { taskId: null, runId: 'run_1' }, + { taskId: 'task_1', runId: null }, + ])('Test onSelect() and clearSelection()', async (selected) => { + const { result } = renderHook(() => useSelection(), { wrapper: Wrapper }); + + await act(async () => { + result.current.onSelect(selected); + }); + + expect(result.current.selected.taskId).toBe(selected.taskId); + expect(result.current.selected.runId).toBe(selected.runId); + + // clearSelection + await act(async () => { + result.current.clearSelection(); + }); + + expect(result.current.selected.taskId).toBeNull(); + expect(result.current.selected.runId).toBeNull(); + }); +});