@instructure/ui-simple-select 10.2.3-snapshot-5 → 10.2.3-snapshot-7

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -3,9 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [10.2.3-snapshot-5](https://github.com/instructure/instructure-ui/compare/v10.2.2...v10.2.3-snapshot-5) (2024-09-24)
6
+ ## [10.2.3-snapshot-7](https://github.com/instructure/instructure-ui/compare/v10.2.2...v10.2.3-snapshot-7) (2024-09-27)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-simple-select
8
+
9
+ ### Bug Fixes
10
+
11
+ * **ui-simple-select:** ensure input value updates correctly when options change ([4dc7cb2](https://github.com/instructure/instructure-ui/commit/4dc7cb2cfde69d28baaaced32a5e63aea9a48ee1))
9
12
 
10
13
 
11
14
 
@@ -22,7 +22,7 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
  import React from 'react';
25
- import { render } from '@testing-library/react';
25
+ import { render, fireEvent, screen } from '@testing-library/react';
26
26
  import { vi } from 'vitest';
27
27
  import '@testing-library/jest-dom';
28
28
  import SimpleSelect from '../index';
@@ -63,4 +63,52 @@ describe('<SimpleSelect />', () => {
63
63
  const input = container.querySelector('input');
64
64
  expect(input).toHaveAttribute('role', 'combobox');
65
65
  });
66
+ describe('children', () => {
67
+ const initialOptions = ['foo', 'bar'];
68
+ const updatedOptions = ['bar', 'baz'];
69
+ const getOptions = options => options.map(opt => /*#__PURE__*/React.createElement(SimpleSelect.Option, {
70
+ id: opt,
71
+ key: opt,
72
+ value: opt
73
+ }, opt));
74
+ const renderSimpleSelect = options => {
75
+ return render( /*#__PURE__*/React.createElement(SimpleSelect, {
76
+ renderLabel: "Choose an option"
77
+ }, getOptions(options)));
78
+ };
79
+ it('should clear selection if selected option does not exist in updated options', () => {
80
+ const _renderSimpleSelect = renderSimpleSelect(initialOptions),
81
+ rerender = _renderSimpleSelect.rerender;
82
+ const input = screen.getByRole('combobox', {
83
+ name: 'Choose an option'
84
+ });
85
+ fireEvent.click(input);
86
+ const fooOption = screen.getByRole('option', {
87
+ name: 'foo'
88
+ });
89
+ fireEvent.click(fooOption);
90
+ expect(input).toHaveValue('foo');
91
+ rerender( /*#__PURE__*/React.createElement(SimpleSelect, {
92
+ renderLabel: "Choose an option"
93
+ }, getOptions(updatedOptions)));
94
+ expect(input).toHaveValue('');
95
+ });
96
+ it('should persist selected option if it exists in updated options', () => {
97
+ const _renderSimpleSelect2 = renderSimpleSelect(initialOptions),
98
+ rerender = _renderSimpleSelect2.rerender;
99
+ const input = screen.getByRole('combobox', {
100
+ name: 'Choose an option'
101
+ });
102
+ fireEvent.click(input);
103
+ const barOption = screen.getByRole('option', {
104
+ name: 'bar'
105
+ });
106
+ fireEvent.click(barOption);
107
+ expect(input).toHaveValue('bar');
108
+ rerender( /*#__PURE__*/React.createElement(SimpleSelect, {
109
+ renderLabel: "Choose an option"
110
+ }, getOptions(updatedOptions)));
111
+ expect(input).toHaveValue('bar');
112
+ });
113
+ });
66
114
  });
@@ -176,7 +176,25 @@ let SimpleSelect = (_dec = withDeterministicId(), _dec2 = testable(), _dec(_clas
176
176
  props: this.props
177
177
  });
178
178
  }
179
+ hasOptionsChanged(prevChildren, currentChildren) {
180
+ const getValues = children => React.Children.map(children, child => {
181
+ if ( /*#__PURE__*/React.isValidElement(child)) {
182
+ return child.props.value;
183
+ }
184
+ return null;
185
+ });
186
+ const prevValues = getValues(prevChildren);
187
+ const currentValues = getValues(currentChildren);
188
+ return JSON.stringify(prevValues) !== JSON.stringify(currentValues);
189
+ }
179
190
  componentDidUpdate(prevProps) {
191
+ if (this.hasOptionsChanged(prevProps.children, this.props.children)) {
192
+ const option = this.getOption('value', this.state.inputValue);
193
+ this.setState({
194
+ inputValue: option ? option.props.children : void 0,
195
+ selectedOptionId: option ? option.props.id : ''
196
+ });
197
+ }
180
198
  if (this.props.value !== prevProps.value) {
181
199
  let option = this.getOption('value', this.props.value);
182
200
  if (typeof this.props.value === 'undefined') {
@@ -68,4 +68,52 @@ describe('<SimpleSelect />', () => {
68
68
  const input = container.querySelector('input');
69
69
  expect(input).toHaveAttribute('role', 'combobox');
70
70
  });
71
+ describe('children', () => {
72
+ const initialOptions = ['foo', 'bar'];
73
+ const updatedOptions = ['bar', 'baz'];
74
+ const getOptions = options => options.map(opt => /*#__PURE__*/_react.default.createElement(_index.default.Option, {
75
+ id: opt,
76
+ key: opt,
77
+ value: opt
78
+ }, opt));
79
+ const renderSimpleSelect = options => {
80
+ return (0, _react2.render)( /*#__PURE__*/_react.default.createElement(_index.default, {
81
+ renderLabel: "Choose an option"
82
+ }, getOptions(options)));
83
+ };
84
+ it('should clear selection if selected option does not exist in updated options', () => {
85
+ const _renderSimpleSelect = renderSimpleSelect(initialOptions),
86
+ rerender = _renderSimpleSelect.rerender;
87
+ const input = _react2.screen.getByRole('combobox', {
88
+ name: 'Choose an option'
89
+ });
90
+ _react2.fireEvent.click(input);
91
+ const fooOption = _react2.screen.getByRole('option', {
92
+ name: 'foo'
93
+ });
94
+ _react2.fireEvent.click(fooOption);
95
+ expect(input).toHaveValue('foo');
96
+ rerender( /*#__PURE__*/_react.default.createElement(_index.default, {
97
+ renderLabel: "Choose an option"
98
+ }, getOptions(updatedOptions)));
99
+ expect(input).toHaveValue('');
100
+ });
101
+ it('should persist selected option if it exists in updated options', () => {
102
+ const _renderSimpleSelect2 = renderSimpleSelect(initialOptions),
103
+ rerender = _renderSimpleSelect2.rerender;
104
+ const input = _react2.screen.getByRole('combobox', {
105
+ name: 'Choose an option'
106
+ });
107
+ _react2.fireEvent.click(input);
108
+ const barOption = _react2.screen.getByRole('option', {
109
+ name: 'bar'
110
+ });
111
+ _react2.fireEvent.click(barOption);
112
+ expect(input).toHaveValue('bar');
113
+ rerender( /*#__PURE__*/_react.default.createElement(_index.default, {
114
+ renderLabel: "Choose an option"
115
+ }, getOptions(updatedOptions)));
116
+ expect(input).toHaveValue('bar');
117
+ });
118
+ });
71
119
  });
@@ -187,7 +187,25 @@ let SimpleSelect = exports.SimpleSelect = (_dec = (0, _withDeterministicId.withD
187
187
  props: this.props
188
188
  });
189
189
  }
190
+ hasOptionsChanged(prevChildren, currentChildren) {
191
+ const getValues = children => _react.default.Children.map(children, child => {
192
+ if ( /*#__PURE__*/_react.default.isValidElement(child)) {
193
+ return child.props.value;
194
+ }
195
+ return null;
196
+ });
197
+ const prevValues = getValues(prevChildren);
198
+ const currentValues = getValues(currentChildren);
199
+ return JSON.stringify(prevValues) !== JSON.stringify(currentValues);
200
+ }
190
201
  componentDidUpdate(prevProps) {
202
+ if (this.hasOptionsChanged(prevProps.children, this.props.children)) {
203
+ const option = this.getOption('value', this.state.inputValue);
204
+ this.setState({
205
+ inputValue: option ? option.props.children : void 0,
206
+ selectedOptionId: option ? option.props.id : ''
207
+ });
208
+ }
191
209
  if (this.props.value !== prevProps.value) {
192
210
  let option = this.getOption('value', this.props.value);
193
211
  if (typeof this.props.value === 'undefined') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-simple-select",
3
- "version": "10.2.3-snapshot-5",
3
+ "version": "10.2.3-snapshot-7",
4
4
  "description": "A component for standard select element behavior.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -24,23 +24,23 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.24.5",
27
- "@instructure/console": "10.2.3-snapshot-5",
28
- "@instructure/shared-types": "10.2.3-snapshot-5",
29
- "@instructure/ui-form-field": "10.2.3-snapshot-5",
30
- "@instructure/ui-position": "10.2.3-snapshot-5",
31
- "@instructure/ui-prop-types": "10.2.3-snapshot-5",
32
- "@instructure/ui-react-utils": "10.2.3-snapshot-5",
33
- "@instructure/ui-select": "10.2.3-snapshot-5",
34
- "@instructure/ui-testable": "10.2.3-snapshot-5",
27
+ "@instructure/console": "10.2.3-snapshot-7",
28
+ "@instructure/shared-types": "10.2.3-snapshot-7",
29
+ "@instructure/ui-form-field": "10.2.3-snapshot-7",
30
+ "@instructure/ui-position": "10.2.3-snapshot-7",
31
+ "@instructure/ui-prop-types": "10.2.3-snapshot-7",
32
+ "@instructure/ui-react-utils": "10.2.3-snapshot-7",
33
+ "@instructure/ui-select": "10.2.3-snapshot-7",
34
+ "@instructure/ui-testable": "10.2.3-snapshot-7",
35
35
  "prop-types": "^15.8.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@instructure/ui-babel-preset": "10.2.3-snapshot-5",
39
- "@instructure/ui-color-utils": "10.2.3-snapshot-5",
40
- "@instructure/ui-icons": "10.2.3-snapshot-5",
41
- "@instructure/ui-test-locator": "10.2.3-snapshot-5",
42
- "@instructure/ui-test-utils": "10.2.3-snapshot-5",
43
- "@instructure/ui-utils": "10.2.3-snapshot-5",
38
+ "@instructure/ui-babel-preset": "10.2.3-snapshot-7",
39
+ "@instructure/ui-color-utils": "10.2.3-snapshot-7",
40
+ "@instructure/ui-icons": "10.2.3-snapshot-7",
41
+ "@instructure/ui-test-locator": "10.2.3-snapshot-7",
42
+ "@instructure/ui-test-utils": "10.2.3-snapshot-7",
43
+ "@instructure/ui-utils": "10.2.3-snapshot-7",
44
44
  "@testing-library/jest-dom": "^6.4.6",
45
45
  "@testing-library/react": "^15.0.7",
46
46
  "vitest": "^2.0.2"
@@ -22,7 +22,7 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
  import React from 'react'
25
- import { render } from '@testing-library/react'
25
+ import { render, fireEvent, screen } from '@testing-library/react'
26
26
  import { vi } from 'vitest'
27
27
  import '@testing-library/jest-dom'
28
28
  import SimpleSelect from '../index'
@@ -73,4 +73,64 @@ describe('<SimpleSelect />', () => {
73
73
  const input = container.querySelector('input')
74
74
  expect(input).toHaveAttribute('role', 'combobox')
75
75
  })
76
+
77
+ describe('children', () => {
78
+ const initialOptions: ExampleOption[] = ['foo', 'bar']
79
+ const updatedOptions: ExampleOption[] = ['bar', 'baz']
80
+
81
+ const getOptions = (options: string[]) =>
82
+ options.map((opt) => (
83
+ <SimpleSelect.Option id={opt} key={opt} value={opt}>
84
+ {opt}
85
+ </SimpleSelect.Option>
86
+ ))
87
+
88
+ const renderSimpleSelect = (options: ExampleOption[]) => {
89
+ return render(
90
+ <SimpleSelect renderLabel="Choose an option">
91
+ {getOptions(options)}
92
+ </SimpleSelect>
93
+ )
94
+ }
95
+
96
+ it('should clear selection if selected option does not exist in updated options', () => {
97
+ const { rerender } = renderSimpleSelect(initialOptions)
98
+
99
+ const input = screen.getByRole('combobox', { name: 'Choose an option' })
100
+ fireEvent.click(input)
101
+
102
+ const fooOption = screen.getByRole('option', { name: 'foo' })
103
+ fireEvent.click(fooOption)
104
+
105
+ expect(input).toHaveValue('foo')
106
+
107
+ rerender(
108
+ <SimpleSelect renderLabel="Choose an option">
109
+ {getOptions(updatedOptions)}
110
+ </SimpleSelect>
111
+ )
112
+
113
+ expect(input).toHaveValue('')
114
+ })
115
+
116
+ it('should persist selected option if it exists in updated options', () => {
117
+ const { rerender } = renderSimpleSelect(initialOptions)
118
+
119
+ const input = screen.getByRole('combobox', { name: 'Choose an option' })
120
+ fireEvent.click(input)
121
+
122
+ const barOption = screen.getByRole('option', { name: 'bar' })
123
+ fireEvent.click(barOption)
124
+
125
+ expect(input).toHaveValue('bar')
126
+
127
+ rerender(
128
+ <SimpleSelect renderLabel="Choose an option">
129
+ {getOptions(updatedOptions)}
130
+ </SimpleSelect>
131
+ )
132
+
133
+ expect(input).toHaveValue('bar')
134
+ })
135
+ })
76
136
  })
@@ -130,7 +130,33 @@ class SimpleSelect extends Component<SimpleSelectProps, SimpleSelectState> {
130
130
  return getInteraction({ props: this.props })
131
131
  }
132
132
 
133
+ hasOptionsChanged(
134
+ prevChildren: SimpleSelectProps['children'],
135
+ currentChildren: SimpleSelectProps['children']
136
+ ) {
137
+ const getValues = (children: SimpleSelectProps['children']) =>
138
+ React.Children.map(children, (child) => {
139
+ if (React.isValidElement(child)) {
140
+ return child.props.value
141
+ }
142
+ return null
143
+ })
144
+
145
+ const prevValues = getValues(prevChildren)
146
+ const currentValues = getValues(currentChildren)
147
+
148
+ return JSON.stringify(prevValues) !== JSON.stringify(currentValues)
149
+ }
150
+
133
151
  componentDidUpdate(prevProps: SimpleSelectProps) {
152
+ if (this.hasOptionsChanged(prevProps.children, this.props.children)) {
153
+ const option = this.getOption('value', this.state.inputValue)
154
+ this.setState({
155
+ inputValue: option ? option.props.children : undefined,
156
+ selectedOptionId: option ? option.props.id : ''
157
+ })
158
+ }
159
+
134
160
  if (this.props.value !== prevProps.value) {
135
161
  let option = this.getOption('value', this.props.value)
136
162
  if (typeof this.props.value === 'undefined') {