Repository: decaporg/decap-cms (JavaScript)

Issue:
Select widget becomes empty when selecting an option with value 0
**Describe the bug**
The `select` widget allows true numbers (not numeric strings) to be stored by passing an array of `{ label: string, value: number }` objects as the  `options` parameter.
However, when an option with a value of `0` is selected in the UI, the field becomes empty, likely due to some bug related to input validation. Since the field becomes empty, if the `select` is `required`, the entry cannot be saved, otherwise the value saves as `null`.

**To Reproduce**
Create a `select` widget and add an option which has any label and a value of `0`.
Attempt to select that option via the UI

**Expected behavior**
The option with a value of `0` is selected and its label is displayed

**Applicable Versions:**
<!--You can find the CMS version by checking your web browser's developer tools console while in the CMS. -->
 - Netlify CMS version: 2.10.192



Discussion:
i would like to work on this issue. please assign it to me. 
Hi @martinjagodic , if this issue is still open for contribution, I'd like to work on it.



Patch:
diff --git a/packages/decap-cms-widget-select/src/SelectControl.js b/packages/decap-cms-widget-select/src/SelectControl.js
index 816a10f39ec0..90a22aa10675 100644
--- a/packages/decap-cms-widget-select/src/SelectControl.js
+++ b/packages/decap-cms-widget-select/src/SelectControl.js
@@ -8,7 +8,9 @@ import { reactSelectStyles } from 'decap-cms-ui-default';
 import { validations } from 'decap-cms-lib-widgets';
 
 function optionToString(option) {
-  return option && option.value ? option.value : null;
+  return option && (typeof option.value === 'number' || typeof option.value === 'string')
+    ? option.value
+    : null;
 }
 
 function convertToOption(raw) {
@@ -47,9 +49,10 @@ export default class SelectControl extends React.Component {
       options: ImmutablePropTypes.listOf(
         PropTypes.oneOfType([
           PropTypes.string,
+          PropTypes.number,
           ImmutablePropTypes.contains({
             label: PropTypes.string.isRequired,
-            value: PropTypes.string.isRequired,
+            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
           }),
         ]),
       ).isRequired,


Test patch:
diff --git a/packages/decap-cms-widget-select/src/__tests__/select.spec.js b/packages/decap-cms-widget-select/src/__tests__/select.spec.js
index 778d1d9c4184..448d87bcae08 100644
--- a/packages/decap-cms-widget-select/src/__tests__/select.spec.js
+++ b/packages/decap-cms-widget-select/src/__tests__/select.spec.js
@@ -12,6 +12,11 @@ const options = [
   { value: 'baz', label: 'Baz' },
 ];
 const stringOptions = ['foo', 'bar', 'baz'];
+const numberOptions = [
+  { value: 0, label: 'Foo' },
+  { value: 1, label: 'Bar' },
+  { value: 2, label: 'Baz' },
+];
 
 class SelectController extends React.Component {
   state = {
@@ -134,6 +139,18 @@ describe('Select widget', () => {
     expect(getByText('baz')).toBeInTheDocument();
   });
 
+  it('should call onChange with correct selectedItem when value is number 0', () => {
+    const field = fromJS({ options: numberOptions });
+    const { getByText, input, onChangeSpy } = setup({ field });
+
+    fireEvent.focus(input);
+    fireEvent.keyDown(input, { key: 'ArrowDown' });
+    fireEvent.click(getByText('Foo'));
+
+    expect(onChangeSpy).toHaveBeenCalledTimes(1);
+    expect(onChangeSpy).toHaveBeenCalledWith(numberOptions[0].value);
+  });
+
   describe('with multiple', () => {
     it('should call onChange with correct selectedItem', () => {
       const field = fromJS({ options, multiple: true });
@@ -248,6 +265,22 @@ describe('Select widget', () => {
       expect(getByText('bar')).toBeInTheDocument();
       expect(getByText('baz')).toBeInTheDocument();
     });
+
+    it('should call onChange with correct selectedItem when values are numbers including 0', () => {
+      const field = fromJS({ options: numberOptions, multiple: true });
+      const { getByText, input, onChangeSpy } = setup({ field });
+
+      fireEvent.keyDown(input, { key: 'ArrowDown' });
+      fireEvent.click(getByText('Foo'));
+      fireEvent.keyDown(input, { key: 'ArrowDown' });
+      fireEvent.click(getByText('Baz'));
+
+      expect(onChangeSpy).toHaveBeenCalledTimes(2);
+      expect(onChangeSpy).toHaveBeenCalledWith(fromJS([numberOptions[0].value]));
+      expect(onChangeSpy).toHaveBeenCalledWith(
+        fromJS([numberOptions[0].value, numberOptions[2].value]),
+      );
+    });
   });
   describe('validation', () => {
     function validate(setupOpts) {