diff --git a/package.json b/package.json index 125488a..1967b2e 100644 --- a/package.json +++ b/package.json @@ -190,6 +190,5 @@ "console": {} }, "testEnvironment": "jsdom" - }, - "dependencies": {} + } } diff --git a/src/components/summit-dropdown/__tests__/summit-dropdown.test.js b/src/components/summit-dropdown/__tests__/summit-dropdown.test.js new file mode 100644 index 0000000..943eff1 --- /dev/null +++ b/src/components/summit-dropdown/__tests__/summit-dropdown.test.js @@ -0,0 +1,120 @@ +/** + * @jest-environment jsdom + */ +import React from 'react'; +import "@testing-library/jest-dom"; +import { render, screen, act } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import SummitDropdown from '..'; + +jest.mock('i18n-react/dist/i18n-react', () => ({ + __esModule: true, + default: { translate: (key) => key }, +})); + +const summits = [ + { id: 1, name: 'Summit A', start_date: 1000 }, + { id: 2, name: 'Summit B', start_date: 2000 }, +]; + +const defaultProps = { + summits, + actionLabel: 'Go', + actionClass: '', + onClick: jest.fn(), +}; + +function renderComponent(props = {}) { + return render(); +} + +describe('SummitDropdown summitValue state', () => { + beforeEach(() => { + defaultProps.onClick.mockClear(); + jest.clearAllMocks(); + }); + + test('summitValue is null on initial render', () => { + renderComponent(); + expect(screen.getByRole('button', { name: 'Go' })).toBeDisabled(); + }); + + test('handleChange sets summitValue when given a valid object', async () => { + const user = userEvent.setup(); + renderComponent(); + + await user.click(screen.getByRole('textbox')); + await user.click(screen.getByText('Summit A')); + + expect(screen.getByRole('button', { name: 'Go' })).toBeEnabled(); + }); + + test('handleChange does not set summitValue when given a non-object', () => { + const ref = React.createRef(); + render(); + + const option = { label: 'Summit A', value: 1 }; + const invalidOption = 'not-an-object'; + + act(() => { ref.current.handleChange(invalidOption); }); + expect(ref.current.state.summitValue).toBeNull(); + + act(() => { ref.current.handleChange(option); }); + expect(ref.current.state.summitValue).toEqual(option); + + act(() => { ref.current.handleChange(invalidOption); }); + expect(ref.current.state.summitValue).toEqual(option); + }); + + test('handleChange does not set summitValue when given null', () => { + const ref = React.createRef(); + render(); + + const option = { label: 'Summit A', value: 1 }; + + act(() => { ref.current.handleChange(null); }); + expect(ref.current.state.summitValue).toBeNull(); + + act(() => { ref.current.handleChange(option); }); + expect(ref.current.state.summitValue).toEqual(option); + + act(() => { ref.current.handleChange(null); }); + expect(ref.current.state.summitValue).toEqual(option); + }); + + test('handleClick does not call onClick when summitValue is null', async () => { + const user = userEvent.setup(); + renderComponent(); + + // button is disabled — click is a no-op, onClick must NOT be called + await user.click(screen.getByRole('button', { name: 'Go' })); + + expect(defaultProps.onClick).not.toHaveBeenCalled(); + }); + + test('handleClick calls onClick with summit id when summitValue is set', async () => { + const user = userEvent.setup(); + renderComponent(); + + await user.click(screen.getByRole('textbox')); + await user.click(screen.getByText('Summit A')); + await user.click(screen.getByRole('button', { name: 'Go' })); + + expect(defaultProps.onClick).toHaveBeenCalledWith(1); + }); + + test('button is disabled when summitValue is null', () => { + renderComponent(); + expect(screen.getByRole('button', { name: 'Go' })).toBeDisabled(); + }); + + test('button is enabled after selecting a summit', async () => { + const user = userEvent.setup(); + renderComponent(); + + await user.click(screen.getByRole('textbox')); + await user.click(screen.getByText('Summit A')); + + expect(screen.getByRole('button', { name: 'Go' })).toBeEnabled(); + }); +}); diff --git a/src/components/summit-dropdown/index.js b/src/components/summit-dropdown/index.js index 40cb2e5..7a2a4f4 100644 --- a/src/components/summit-dropdown/index.js +++ b/src/components/summit-dropdown/index.js @@ -30,24 +30,29 @@ export default class SummitDropdown extends React.Component { } handleChange(summit) { - this.setState({summitValue: summit}); + const summitValue = summit?.value !== undefined ? summit : this.state.summitValue; + + this.setState({ summitValue }); } handleClick(ev) { ev.preventDefault(); - this.props.onClick(this.state.summitValue.value); + if ( this.state?.summitValue?.value !== undefined ) + this.props.onClick(this.state.summitValue.value); } render() { - let {summits, actionLabel, actionClass} = this.props; - let summitOptions = summits + let { summits, actionLabel, actionClass } = this.props; + let summitOptions = [...summits] .sort( (a, b) => (a.start_date < b.start_date ? 1 : (a.start_date > b.start_date ? -1 : 0)) ).map(s => ({label: s.name, value: s.id})); let bigClass = this.props.hasOwnProperty('big') ? 'big' : ''; - const isDisabled = !this.state.summitValue; + const isDisabled = this.state.summitValue?.value === undefined; + + return (