From c7ca0a436bfe99ff9d8d15dbad6b111cae40f799 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 27 Apr 2021 00:31:28 +0200 Subject: [PATCH] Fix crash when passing array children to preact-iso --- .changeset/thirty-poets-fry.md | 5 ++++ packages/preact-iso/router.js | 4 +-- packages/preact-iso/test/router.test.js | 34 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .changeset/thirty-poets-fry.md diff --git a/.changeset/thirty-poets-fry.md b/.changeset/thirty-poets-fry.md new file mode 100644 index 000000000..444de6289 --- /dev/null +++ b/.changeset/thirty-poets-fry.md @@ -0,0 +1,5 @@ +--- +'preact-iso': patch +--- + +Fix crash when passing dynamic arrays as children. This was caused by missing children normalization. diff --git a/packages/preact-iso/router.js b/packages/preact-iso/router.js index a44f1c9b5..91476e353 100644 --- a/packages/preact-iso/router.js +++ b/packages/preact-iso/router.js @@ -1,4 +1,4 @@ -import { h, createContext, cloneElement } from 'preact'; +import { h, createContext, cloneElement, toChildArray } from 'preact'; import { useContext, useMemo, useReducer, useEffect, useLayoutEffect, useRef } from 'preact/hooks'; let push; @@ -106,7 +106,7 @@ export function Router(props) { prev.current = cur.current; let p, d, m; - [].concat(props.children || []).some(vnode => { + toChildArray(props.children).some(vnode => { const matches = exec(path, vnode.props.path, (m = { path, query })); if (matches) return (p = cloneElement(vnode, m)); if (vnode.props.default) d = cloneElement(vnode, m); diff --git a/packages/preact-iso/test/router.test.js b/packages/preact-iso/test/router.test.js index b79a5a9ef..7d774deaa 100644 --- a/packages/preact-iso/test/router.test.js +++ b/packages/preact-iso/test/router.test.js @@ -399,4 +399,38 @@ describe('Router', () => { pushState.mockRestore(); }); + + it('should normalize children', async () => { + let loc; + const pushState = jest.spyOn(history, 'pushState'); + const Route = jest.fn(() => html`foo`); + + const routes = ['/foo', '/bar']; + render( + html` + <${LocationProvider}> + <${Router}> + ${routes.map(route => html`<${Route} path=${route} />`)} + <${Route} default /> + + <${() => { + loc = useLocation(); + }} /> + + `, + scratch + ); + + expect(Route).toHaveBeenCalledTimes(1); + Route.mockClear(); + await sleep(20); + + scratch.querySelector('a[href="/foo#foo"]').click(); + await sleep(100); + expect(Route).toHaveBeenCalledTimes(1); + expect(loc).toMatchObject({ url: '/foo#foo', path: '/foo' }); + expect(pushState).toHaveBeenCalled(); + + pushState.mockRestore(); + }); });