Skip to content

Commit 5ca5b7d

Browse files
committed
fix: SummitDropdown | Type Error - null is not an object (evaluating 'this.state.summitValue.value')
1 parent f14ec6e commit 5ca5b7d

3 files changed

Lines changed: 108 additions & 6 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
import React from 'react';
5+
import Enzyme, { mount } from 'enzyme';
6+
import Adapter from 'enzyme-adapter-react-16';
7+
import SummitDropdown from '..';
8+
9+
Enzyme.configure({ adapter: new Adapter() });
10+
11+
const summits = [
12+
{ id: 1, name: 'Summit A', start_date: 1000 },
13+
{ id: 2, name: 'Summit B', start_date: 2000 },
14+
];
15+
16+
const defaultProps = {
17+
summits,
18+
actionLabel: 'Go',
19+
actionClass: '',
20+
onClick: jest.fn(),
21+
};
22+
23+
function render(props = {}) {
24+
return mount(<SummitDropdown {...defaultProps} {...props} />);
25+
}
26+
27+
describe('SummitDropdown summitValue state', () => {
28+
beforeEach(() => {
29+
defaultProps.onClick.mockClear();
30+
});
31+
32+
test('summitValue is null on initial render', () => {
33+
const wrapper = render();
34+
expect(wrapper.instance().state.summitValue).toBeNull();
35+
});
36+
37+
test('handleChange sets summitValue when given a valid object', () => {
38+
const wrapper = render();
39+
const option = { label: 'Summit A', value: 1 };
40+
41+
wrapper.instance().handleChange(option);
42+
43+
expect(wrapper.instance().state.summitValue).toEqual(option);
44+
});
45+
46+
test('handleChange does not set summitValue when given a non-object', () => {
47+
const wrapper = render();
48+
49+
wrapper.instance().handleChange('not-an-object');
50+
51+
expect(wrapper.instance().state.summitValue).toBeNull();
52+
});
53+
54+
test('handleChange does not set summitValue when given null', () => {
55+
const wrapper = render();
56+
57+
wrapper.instance().handleChange(null);
58+
59+
expect(wrapper.instance().state.summitValue).toBeNull();
60+
});
61+
62+
test('handleClick does not call onClick when summitValue is null', () => {
63+
const wrapper = render();
64+
const fakeEvent = { preventDefault: jest.fn() };
65+
66+
// summitValue starts as null — onClick must NOT be called
67+
wrapper.instance().handleClick(fakeEvent);
68+
69+
expect(defaultProps.onClick).not.toHaveBeenCalled();
70+
});
71+
72+
test('handleClick calls onClick with summit id when summitValue is set', () => {
73+
const wrapper = render();
74+
const option = { label: 'Summit A', value: 1 };
75+
const fakeEvent = { preventDefault: jest.fn() };
76+
77+
wrapper.instance().handleChange(option);
78+
wrapper.instance().handleClick(fakeEvent);
79+
80+
expect(defaultProps.onClick).toHaveBeenCalledWith(1);
81+
});
82+
83+
test('button is disabled when summitValue is null', () => {
84+
const wrapper = render();
85+
expect(wrapper.find('button').prop('disabled')).toBe(true);
86+
});
87+
88+
test('button is enabled after selecting a summit', () => {
89+
const wrapper = render();
90+
wrapper.instance().handleChange({ label: 'Summit A', value: 1 });
91+
wrapper.update();
92+
93+
expect(wrapper.find('button').prop('disabled')).toBe(false);
94+
});
95+
});

src/components/summit-dropdown/index.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import React from 'react';
1515
import './summit-dropdown.less';
1616
import Select from 'react-select';
1717
import T from 'i18n-react/dist/i18n-react';
18+
import { OBJECT_TYPEOF, UNDEFINED_TYPEOF } from '../../utils/constants';
1819

1920
export default class SummitDropdown extends React.Component {
2021

@@ -30,25 +31,28 @@ export default class SummitDropdown extends React.Component {
3031
}
3132

3233
handleChange(summit) {
33-
this.setState({summitValue: summit});
34+
if (typeof summit === OBJECT_TYPEOF)
35+
this.setState({summitValue: summit});
3436
}
3537

3638
handleClick(ev) {
3739
ev.preventDefault();
38-
if (this.state.summitValue)
39-
this.props.onClick(this.state.summitValue.value);
40+
if (
41+
typeof this.state.summitValue === OBJECT_TYPEOF &&
42+
typeof this.state.summitValue?.value !== UNDEFINED_TYPEOF
43+
)
44+
this.props.onClick(this.state.summitValue.value);
4045
}
4146

4247
render() {
4348

44-
let {summits, actionLabel, actionClass} = this.props;
49+
let {summits, actionLabel, actionClass, big: bigClass = "" } = this.props;
4550
let summitOptions = summits
4651
.sort(
4752
(a, b) => (a.start_date < b.start_date ? 1 : (a.start_date > b.start_date ? -1 : 0))
4853
).map(s => ({label: s.name, value: s.id}));
4954

50-
let bigClass = this.props.hasOwnProperty('big') ? 'big' : '';
51-
const isDisabled = !this.state.summitValue;
55+
const isDisabled = typeof this.state.summitValue !== OBJECT_TYPEOF || this.state.summitValue === null;
5256

5357
return (
5458
<div className={"summit-dropdown btn-group " + bigClass}>

src/utils/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ export const TWO_DECIMAL_PLACES = 2;
33
export const THREE_DECIMAL_PLACES = 3;
44
export const ONE_CENT = 1n;
55
export const ZERO_INT = 0;
6+
7+
export const OBJECT_TYPEOF = 'object';
8+
export const UNDEFINED_TYPEOF = 'undefined';

0 commit comments

Comments
 (0)