Repository: preactjs/preact-render-to-string (js)

Issue:
Bug: <option> elements have a duplicate selected attribute, resulting in HTML validation errors
**Description**

When using preact-render-to-string, `<option>` elements are given a duplicate `selected` attribute in the document source, resulting in HTML validation errors when run through a tool like https://validator.w3.org/.

**Steps to reproduce**

1. CodeSandbox demo: https://codesandbox.io/s/nextjs-swr-preact-forked-fmojde?file=/pages/index.js.
2. Navigate to the URL directly: https://fmojde.sse.codesandbox.io/.
3. View page source (e.g., `Ctrl+U` on Windows).
4. Search for `selected`.
5. Observe this HTML: `<option selected value="b" selected>b</option>`.
6. Visit https://validator.w3.org/nu/?doc=https%3A%2F%2Ffmojde.sse.codesandbox.io%2F to view the validation report. Observe that one of the errors is: `Duplicate attribute selected.`

**Environment**
- Nextjs version: 12.1.4
- preact-render-to-string: 5.1.20

**Additional context**

Culprit: https://github.com/preactjs/preact-render-to-string/blob/8582f23b6ad64a3e247904d711de9b60666e2f29/src/index.js#L300

Possibly related to https://github.com/preactjs/preact-render-to-string/issues/208 for similar reasons.

Happy to put in a PR myself to fix this

Pull request:
Don't add selected attribute to <option> if it already exists
Closes #214

Patch:
diff --git a/.changeset/twenty-weeks-fetch.md b/.changeset/twenty-weeks-fetch.md
new file mode 100644
index 0000000..641f335
--- /dev/null
+++ b/.changeset/twenty-weeks-fetch.md
@@ -0,0 +1,5 @@
+---
+'preact-render-to-string': patch
+---
+
+Don't add selected attribute to <option> elements if they already contain that attribute
diff --git a/src/index.js b/src/index.js
index 7714f36..2c6f682 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,8 +15,7 @@ const SHALLOW = { shallow: true };
 // components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
 const UNNAMED = [];
 
-const VOID_ELEMENTS =
-	/^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
+const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
 
 const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
 
@@ -296,7 +295,13 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
 					if (nodeName === 'select') {
 						selectValue = v;
 						continue;
-					} else if (nodeName === 'option' && selectValue == v) {
+					} else if (
+						// If we're looking at an <option> and it's the currently selected one
+						nodeName === 'option' &&
+						selectValue == v &&
+						// and the <option> doesn't already have a selected attribute on it
+						typeof props.selected === 'undefined'
+					) {
 						s += ` selected`;
 					}
 				}


Test patch:
diff --git a/test/render.test.js b/test/render.test.js
index 27a08f6..7f64c42 100644
--- a/test/render.test.js
+++ b/test/render.test.js
@@ -1139,6 +1139,20 @@ describe('render', () => {
 		);
 	});
 
+	it('should not add a selected attribute if one already exists', () => {
+		let res = render(
+			<select value="B">
+				<option value="A">A</option>
+				<option selected value="B">
+					B
+				</option>
+			</select>
+		);
+		expect(res).to.equal(
+			'<select><option value="A">A</option><option selected value="B">B</option></select>'
+		);
+	});
+
 	it('should render select value on option with a Fragment', () => {
 		let res = render(
 			<select value="B">