diff --git a/.changeset/brown-boats-nail.md b/.changeset/brown-boats-nail.md new file mode 100644 index 00000000..0c413937 --- /dev/null +++ b/.changeset/brown-boats-nail.md @@ -0,0 +1,5 @@ +--- +'preact-render-to-string': patch +--- + +Fix `preact/debug` incorrectly throwing errors on text children diff --git a/package.json b/package.json index 46cb0d2f..1afbe951 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,10 @@ "transpile": "microbundle src/index.js -f es,umd --target web --external preact", "transpile:jsx": "microbundle src/jsx.js -o dist/jsx.js --target web --external preact && microbundle dist/jsx.js -o dist/jsx.js -f cjs --external preact", "copy-typescript-definition": "copyfiles -f src/*.d.ts dist", - "test": "eslint src test && tsc && npm run test:mocha && npm run test:mocha:compat && npm run bench", - "test:mocha": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js test/**/[!compat]*.test.js", + "test": "eslint src test && tsc && npm run test:mocha && npm run test:mocha:compat && npm run test:mocha:debug && npm run bench", + "test:mocha": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js test/**/[!compat][!debug]*.test.js", "test:mocha:compat": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js test/compat.test.js 'test/compat-*.test.js'", + "test:mocha:debug": "BABEL_ENV=test mocha -r @babel/register -r test/setup.js test/debug.test.js 'test/debug-*.test.js'", "format": "prettier src/**/*.{d.ts,js} test/**/*.js --write", "prepublishOnly": "npm run build", "release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" diff --git a/src/index.js b/src/index.js index bd596ba9..9d816d6d 100644 --- a/src/index.js +++ b/src/index.js @@ -165,6 +165,23 @@ function renderClassComponent(vnode, context) { return c.render(c.props, c.state, c.context); } +/** + * @param {any} vnode + * @returns {VNode} + */ +function normalizeVNode(vnode) { + if (vnode == null || typeof vnode == 'boolean') { + return null; + } else if ( + typeof vnode == 'string' || + typeof vnode == 'number' || + typeof vnode == 'bigint' + ) { + return h(null, null, vnode); + } + return vnode; +} + /** * @param {string} name * @param {boolean} isSvgMode @@ -237,6 +254,8 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) { rendered = rendered + _renderToString(vnode[i], context, isSvgMode, selectValue, parent); + + vnode[i] = normalizeVNode(vnode[i]); } return rendered; } @@ -372,6 +391,8 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) { vnode[CHILDREN] = children; for (let i = 0; i < children.length; i++) { let child = children[i]; + children[i] = normalizeVNode(child); + if (child != null && child !== false) { let childSvgMode = type === 'svg' || (type !== 'foreignObject' && isSvgMode); @@ -391,7 +412,7 @@ function _renderToString(vnode, context, isSvgMode, selectValue, parent) { } } } else if (children != null && children !== false && children !== true) { - vnode[CHILDREN] = [children]; + vnode[CHILDREN] = [normalizeVNode(children)]; let childSvgMode = type === 'svg' || (type !== 'foreignObject' && isSvgMode); let ret = _renderToString( diff --git a/test/debug.test.js b/test/debug.test.js new file mode 100644 index 00000000..d794a8ea --- /dev/null +++ b/test/debug.test.js @@ -0,0 +1,26 @@ +import 'preact/debug'; +import { render } from '../src'; +import { h } from 'preact'; +import { expect } from 'chai'; + +describe('debug', () => { + it('should not throw "Objects are not valid as a child" error', () => { + expect(() => render(

{'foo'}

)).not.to.throw(); + expect(() => render(

{2}

)).not.to.throw(); + expect(() => render(

{true}

)).not.to.throw(); + expect(() => render(

{false}

)).not.to.throw(); + expect(() => render(

{null}

)).not.to.throw(); + expect(() => render(

{undefined}

)).not.to.throw(); + }); + + it('should not throw "Objects are not valid as a child" error #2', () => { + function Str() { + return ['foo']; + } + expect(() => render()).not.to.throw(); + }); + + it('should not throw "Objects are not valid as a child" error #3', () => { + expect(() => render(

{'foo'}bar

)).not.to.throw(); + }); +});