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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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') {