diff --git a/src/utils/resolveRenderArgs.js b/src/utils/resolveRenderArgs.js index 4ff0cc11..18efb0cd 100644 --- a/src/utils/resolveRenderArgs.js +++ b/src/utils/resolveRenderArgs.js @@ -48,7 +48,7 @@ export default async function* resolveRenderArgs({ for await (const elements of resolver.resolveElements(augmentedMatch)) { yield { ...augmentedMatch, - elements: foldElements([...elements], match.routeIndices), + elements: elements && foldElements([...elements], match.routeIndices), }; } } catch (e) { diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 00000000..e0070366 --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,25 @@ +import resolver from '../src/resolver'; + +export function timeout(delay) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} + +export class InstrumentedResolver { + constructor() { + // This should be a rejected promise to prevent awaiting on done before + // trying to resolve, but Node doesn't like naked unresolved promises. + this.done = new Promise(() => {}); + } + + async * resolveElements(match) { + let resolveDone; + this.done = new Promise((resolve) => { + resolveDone = resolve; + }); + + yield* resolver.resolveElements(match); + resolveDone(); + } +} diff --git a/test/render.test.js b/test/render.test.js index 0bf3154e..38831ab0 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -1,27 +1,23 @@ +import ServerProtocol from 'farce/lib/ServerProtocol'; import React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; +import createFarceRouter from '../src/createFarceRouter'; import createRender from '../src/createRender'; -import getFarceResult from '../src/server/getFarceResult'; -async function render(url, routeConfig) { - const { element } = await getFarceResult({ - url, - routeConfig, - render: createRender({}), - }); - - return ReactTestUtils.renderIntoDocument(element); -} +import { timeout, InstrumentedResolver } from './helpers'; describe('render', () => { it('should support nested routes', async () => { - const instance = await render( - '/foo/baz/a', - [ + const Router = createFarceRouter({ + historyProtocol: new ServerProtocol('/foo/baz/a'), + routeConfig: [ { path: 'foo', - Component: ({ children }) =>
{children}
, + getComponent: async () => { + await timeout(20); + return ({ children }) =>
{children}
; + }, children: [ { path: 'bar', @@ -36,8 +32,24 @@ describe('render', () => { ], }, ], + + render: createRender({ + renderPending: () =>
, + }), + }); + + const resolver = new InstrumentedResolver(); + const instance = ReactTestUtils.renderIntoDocument( + , ); + // Initial pending render is asynchronous. + await timeout(10); + + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'pending'); + + await resolver.done; + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'foo'); expect( ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, 'bar'), @@ -50,17 +62,20 @@ describe('render', () => { }); it('should support named child routes', async () => { - const instance = await render( - '/foo/bar/qux/a', - [ + const Router = createFarceRouter({ + historyProtocol: new ServerProtocol('/foo/bar/qux/a'), + routeConfig: [ { path: 'foo', - Component: ({ nav, main }) => ( -
- {nav} - {main} -
- ), + getComponent: async () => { + await timeout(20); + return ({ nav, main }) => ( +
+ {nav} + {main} +
+ ); + }, children: [ { path: 'bar', @@ -88,8 +103,24 @@ describe('render', () => { ], }, ], + + render: createRender({ + renderPending: () =>
, + }), + }); + + const resolver = new InstrumentedResolver(); + const instance = ReactTestUtils.renderIntoDocument( + , ); + // Initial pending render is asynchronous. + await timeout(10); + + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'pending'); + + await resolver.done; + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'foo'); ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'bar-nav'); diff --git a/test/server/getFarceResult.test.js b/test/server/getFarceResult.test.js new file mode 100644 index 00000000..85a11357 --- /dev/null +++ b/test/server/getFarceResult.test.js @@ -0,0 +1,105 @@ +import React from 'react'; +import ReactTestUtils from 'react-dom/test-utils'; + +import createRender from '../../src/createRender'; +import getFarceResult from '../../src/server/getFarceResult'; + +async function render(url, routeConfig) { + const { element } = await getFarceResult({ + url, + routeConfig, + render: createRender({}), + }); + + return ReactTestUtils.renderIntoDocument(element); +} + +describe('getFarceResult', () => { + it('should support nested routes', async () => { + const instance = await render( + '/foo/baz/a', + [ + { + path: 'foo', + Component: ({ children }) =>
{children}
, + children: [ + { + path: 'bar', + Component: () =>
, + }, + { + path: 'baz/:qux', + Component: ({ params }) => ( +
{params.qux}
+ ), + }, + ], + }, + ], + ); + + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'foo'); + expect( + ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, 'bar'), + ).toHaveLength(0); + + const bazNode = ReactTestUtils.findRenderedDOMComponentWithClass( + instance, 'baz', + ); + expect(bazNode.textContent).toBe('a'); + }); + + it('should support named child routes', async () => { + const instance = await render( + '/foo/bar/qux/a', + [ + { + path: 'foo', + Component: ({ nav, main }) => ( +
+ {nav} + {main} +
+ ), + children: [ + { + path: 'bar', + children: { + nav: [ + { + path: '(.*)?', + Component: () =>
, + }, + ], + main: [ + { + path: 'baz', + Component: () =>
, + }, + { + path: 'qux/:quux', + Component: ({ params }) => ( +
{params.quux}
+ ), + }, + ], + }, + }, + ], + }, + ], + ); + + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'foo'); + ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'bar-nav'); + + expect( + ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, 'baz'), + ).toHaveLength(0); + + const quxNode = ReactTestUtils.findRenderedDOMComponentWithClass( + instance, 'qux', + ); + expect(quxNode.textContent).toBe('a'); + }); +});