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');
+ });
+});