Repository: remarkablemark/html-react-parser (ts)

Issue:
list value is not preserved while parsing and rendering in UI.
## Expected Behavior
When a list item has a value attribute, the value is not preserved parsing and rendering in UI.
 Ex:  <ol><li value="3">Item-3</li></ol>

## Actual Behavior
It should preserve the list value. 
1. Item-3

## Steps to Reproduce
Please check the below link

## Reproducible Demo
https://codesandbox.io/s/loving-violet-2gicem?file=/src/App.js

## Environment
- Version: 3.0.9


Pull request:
fix(attributes-to-props): don't convert non-uncontrolled component props
## What is the motivation for this pull request?

fix(attributes-to-props): don't convert non-uncontrolled component props

Fixes #839

## What is the current behavior?

Non-uncontrolled component (e.g., `<li>`) attributes like `value` are being converted to `defaultValue` which is incorrect

## What is the new behavior?

Only uncontrolled components have attributes `checked` and `value` converted:

- `<input>`
- `<select>`
- `<textarea>`

Non-uncontrolled components are ignored

## Checklist:

- [x] [Conventional Commits](https://www.conventionalcommits.org/)
- [x] Tests
- [ ] Documentation
- [ ] Types

Patch:
diff --git a/.size-limit.json b/.size-limit.json
index d33164e..43e1a68 100644
--- a/.size-limit.json
+++ b/.size-limit.json
@@ -1,6 +1,6 @@
 [
   {
     "path": "dist/html-react-parser.min.js",
-    "limit": "10.593 KB"
+    "limit": "10.6 KB"
   }
 ]
diff --git a/lib/attributes-to-props.js b/lib/attributes-to-props.js
index 0d5c8f7..b3a38bc 100644
--- a/lib/attributes-to-props.js
+++ b/lib/attributes-to-props.js
@@ -1,6 +1,16 @@
 var reactProperty = require('react-property');
 var utilities = require('./utilities');
 
+// https://reactjs.org/docs/uncontrolled-components.html
+// https://developer.mozilla.org/docs/Web/HTML/Attributes
+var UNCONTROLLED_COMPONENT_ATTRIBUTES = ['checked', 'value'];
+var UNCONTROLLED_COMPONENT_NAMES = ['input', 'select', 'textarea'];
+
+var VALUE_ONLY_INPUTS = {
+  reset: true,
+  submit: true
+};
+
 /**
  * Converts HTML/SVG DOM attributes to React props.
  *
@@ -11,18 +21,13 @@ var utilities = require('./utilities');
 module.exports = function attributesToProps(attributes, nodeName) {
   attributes = attributes || {};
 
-  var valueOnlyInputs = {
-    reset: true,
-    submit: true
-  };
-
   var attributeName;
   var attributeNameLowerCased;
   var attributeValue;
   var propName;
   var propertyInfo;
   var props = {};
-  var inputIsValueOnly = attributes.type && valueOnlyInputs[attributes.type];
+  var inputIsValueOnly = attributes.type && VALUE_ONLY_INPUTS[attributes.type];
 
   for (attributeName in attributes) {
     attributeValue = attributes[attributeName];
@@ -41,10 +46,9 @@ module.exports = function attributesToProps(attributes, nodeName) {
       propertyInfo = reactProperty.getPropertyInfo(propName);
 
       // convert attribute to uncontrolled component prop (e.g., `value` to `defaultValue`)
-      // https://reactjs.org/docs/uncontrolled-components.html
       if (
-        (propName === 'checked' || propName === 'value') &&
-        nodeName !== 'option' &&
+        UNCONTROLLED_COMPONENT_ATTRIBUTES.indexOf(propName) !== -1 &&
+        UNCONTROLLED_COMPONENT_NAMES.indexOf(nodeName) !== -1 &&
         !inputIsValueOnly
       ) {
         propName = getPropName('default' + attributeNameLowerCased);


Test patch:
diff --git a/test/attributes-to-props.test.js b/test/attributes-to-props.test.js
index ab9d164..7f31ddc 100644
--- a/test/attributes-to-props.test.js
+++ b/test/attributes-to-props.test.js
@@ -5,6 +5,30 @@ it('returns empty object is argument is undefined', () => {
   expect(attributesToProps()).toEqual({});
 });
 
+it.each(['input', 'select', 'textarea'])(
+  'converts uncontrolled component attributes',
+  nodeName => {
+    expect(
+      attributesToProps({ value: 'foo', checked: false }, nodeName)
+    ).toEqual({
+      defaultValue: 'foo',
+      defaultChecked: true
+    });
+  }
+);
+
+it.each(['button', 'data', 'li', 'meter', 'option', 'progress', 'param'])(
+  'converts non-uncontrolled component attributes',
+  nodeName => {
+    expect(
+      attributesToProps({ value: 'foo', checked: false }, nodeName)
+    ).toEqual({
+      value: 'foo',
+      checked: true
+    });
+  }
+);
+
 describe('attributesToProps with HTML attribute', () => {
   it('converts attributes to React props', () => {
     const attributes = {
@@ -128,7 +152,7 @@ describe('attributesToProps with HTML attribute', () => {
       selected: '',
       truespeed: ''
     };
-    expect(attributesToProps(attributes)).toMatchInlineSnapshot(`
+    expect(attributesToProps(attributes, 'select')).toMatchInlineSnapshot(`
       {
         "allowFullScreen": true,
         "allowpaymentrequest": "",
@@ -183,7 +207,7 @@ describe('attributesToProps with HTML attribute', () => {
   ])(
     'converts form attribute to uncontrolled component property',
     (attributes, props) => {
-      expect(attributesToProps(attributes)).toEqual(props);
+      expect(attributesToProps(attributes, 'input')).toEqual(props);
     }
   );
 
diff --git a/test/data/html.js b/test/data/html.js
index a3bc893..ffc1f14 100644
--- a/test/data/html.js
+++ b/test/data/html.js
@@ -17,5 +17,6 @@ module.exports = {
   customElement:
     '<custom-element class="myClass" custom-attribute="value" style="-o-transition: all .5s; line-height: 1;"></custom-element>',
   form: '<input type="text" value="foo" checked="checked">',
+  list: '<ol><li>One</li><li value="2">Two</li></ol>',
   template: '<template><article><p>Test</p></article></template>'
 };
diff --git a/test/index.test.js b/test/index.test.js
index aedc2ff..980559d 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -163,6 +163,21 @@ describe('HTMLReactParser', () => {
     `);
   });
 
+  it('parses list', () => {
+    expect(parse(html.list)).toMatchInlineSnapshot(`
+      <ol>
+        <li>
+          One
+        </li>
+        <li
+          value="2"
+        >
+          Two
+        </li>
+      </ol>
+    `);
+  });
+
   it('parses template', () => {
     expect(parse(html.template)).toMatchInlineSnapshot(`
       <template>