Skip to content

Commit

Permalink
Merge pull request #567 from travi/alpha
Browse files Browse the repository at this point in the history
Alpha
  • Loading branch information
travi authored Dec 17, 2018
2 parents df852f1 + 86298c3 commit 45c4ef0
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ deploy:
provider: script
skip_cleanup: true
script: npx semantic-release@beta
on:
all_branches: true
env:
global:
- FORCE_COLOR=1
Expand Down
2 changes: 1 addition & 1 deletion example/layout.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="wrap"><div>{{{ renderedContent }}}</div></div>
<div id="wrap"><div>{{{ renderedContent.html }}}</div></div>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"> </script>
</body>
</html>
11 changes: 11 additions & 0 deletions src/default-render-factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import {renderToString} from 'react-dom/server';
import {RouterContext} from 'react-router';

export default function (request, store, renderProps, Root) {
return otherProps => renderToString(
<Root request={request} store={store} {...otherProps}>
<RouterContext {...renderProps} />
</Root>
);
}
1 change: 1 addition & 0 deletions src/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const plugin = {
method: 'GET',
path: '/html',
handler: (request, h) => renderThroughReactRouter(request, h, {
render: options.render,
routes: options.routes,
respond: options.respond,
Root: options.Root,
Expand Down
14 changes: 5 additions & 9 deletions src/router-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react';
import {renderToString} from 'react-dom/server';
import {RouterContext} from 'react-router';
import Boom from 'boom';
import {MOVED_PERMANENTLY, MOVED_TEMPORARILY} from 'http-status-codes';
import matchRoute from './route-matcher';
import fetchData from './data-fetcher';
import defaultRenderFactory from './default-render-factory';

export default async function renderThroughReactRouter(request, h, {routes, respond, Root, store}) {
export default async function renderThroughReactRouter(request, h, {render, routes, respond, Root, store}) {
try {
const {renderProps, status, redirectLocation} = await matchRoute(request.raw.req.url, routes);

Expand All @@ -24,14 +22,12 @@ export default async function renderThroughReactRouter(request, h, {routes, resp
} else {
await fetchData({renderProps, store});

const defaultRender = defaultRenderFactory(request, store, renderProps, Root);

return respond(h, {
store,
status,
renderedContent: renderToString((
<Root request={request} store={store}>
<RouterContext {...renderProps} />
</Root>
))
renderedContent: render ? render(defaultRender) : {html: defaultRender()}
});
}
} catch (e) {
Expand Down
6 changes: 3 additions & 3 deletions test/integration/features/step_definitions/render-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import {OK} from 'http-status-codes';
import {assert} from 'chai';
import {When, Then} from 'cucumber';

When('a request is made for an existing route', function () {
When(/^a request is made for an existing route$/, function () {
return this.makeRequest({url: '/existing-route'});
});

Then('the route is rendered successfully', function (callback) {
Then(/^the route is rendered successfully$/, function (callback) {
assert.equal(this.serverResponse.statusCode, OK);
assert.equal(this.serverResponse.headers['content-type'], 'text/html; charset=utf-8');

callback();
});

Then('asynchronously fetched data is included in the page', function (callback) {
Then(/^asynchronously fetched data is included in the page$/, function (callback) {
assert.include(this.serverResponse.payload, this.dataPoint);

callback();
Expand Down
47 changes: 47 additions & 0 deletions test/unit/default-render-factory-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import domServer from 'react-dom/server';
import {RouterContext} from 'react-router';
import sinon from 'sinon';
import any from '@travi/any';
import {assert} from 'chai';
import defaultRenderFactory from '../../src/default-render-factory';

suite('default-render factory', () => {
let sandbox;
const Root = any.simpleObject();
const store = any.simpleObject();

setup(() => {
sandbox = sinon.createSandbox();

sandbox.stub(React, 'createElement');
sandbox.stub(domServer, 'renderToString');
});

teardown(() => sandbox.restore());

test('that the router-context is rendered within the provided root component', () => {
const renderProps = any.simpleObject();
const rootComponent = any.simpleObject();
const html = any.string();
const request = any.simpleObject();
React.createElement.withArgs(RouterContext, renderProps).returns(context);
React.createElement.withArgs(Root, {request, store}, context).returns(rootComponent);
domServer.renderToString.withArgs(rootComponent).returns(html);

assert.equal(defaultRenderFactory(request, store, renderProps, Root)(), html);
});

test('that the additional props are passed to the root component, when provided', () => {
const renderProps = any.simpleObject();
const rootComponent = any.simpleObject();
const html = any.string();
const request = any.simpleObject();
const otherProps = any.simpleObject();
React.createElement.withArgs(RouterContext, renderProps).returns(context);
React.createElement.withArgs(Root, {request, store, ...otherProps}, context).returns(rootComponent);
domServer.renderToString.withArgs(rootComponent).returns(html);

assert.equal(defaultRenderFactory(request, store, renderProps, Root)(otherProps), html);
});
});
5 changes: 3 additions & 2 deletions test/unit/route-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ suite('route', () => {
});

test('that the request for html is handled', async () => {
const render = () => undefined;
const route = sinon.stub();
const respond = sinon.spy();
const routes = sinon.spy();
Expand All @@ -32,7 +33,7 @@ suite('route', () => {
const configureStore = sinon.stub();
configureStore.withArgs({session: {auth: auth.credentials}, server}).returns(store);

await plugin.register(server, {respond, routes, Root, configureStore});
await plugin.register(server, {render, respond, routes, Root, configureStore});

assert.calledWith(route, sinon.match({
method: 'GET',
Expand All @@ -41,6 +42,6 @@ suite('route', () => {

route.yieldTo('handler', request, reply);

assert.calledWith(routerWrapper.default, request, reply, {routes, respond, Root, store});
assert.calledWith(routerWrapper.default, request, reply, {render, routes, respond, Root, store});
});
});
39 changes: 26 additions & 13 deletions test/unit/router-wrapper-test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react';
import {RouterContext} from 'react-router';
import domServer from 'react-dom/server';
import {MOVED_TEMPORARILY, MOVED_PERMANENTLY} from 'http-status-codes';
import {MOVED_PERMANENTLY, MOVED_TEMPORARILY} from 'http-status-codes';
import sinon from 'sinon';
import {assert} from 'chai';
import any from '@travi/any';
import Boom from 'boom';
import renderThroughReactRouter from '../../src/router-wrapper';
import * as defaultRenderFactory from '../../src/default-render-factory';
import * as routeMatcher from '../../src/route-matcher';
import * as dataFetcher from '../../src/data-fetcher';

Expand All @@ -25,8 +23,7 @@ suite('router-wrapper', () => {
sandbox.stub(routeMatcher, 'default');
sandbox.stub(dataFetcher, 'default');
sandbox.stub(Boom, 'wrap');
sandbox.stub(React, 'createElement');
sandbox.stub(domServer, 'renderToString');
sandbox.stub(defaultRenderFactory, 'default');
});

teardown(() => sandbox.restore());
Expand All @@ -36,20 +33,36 @@ suite('router-wrapper', () => {
const reply = sinon.spy();
const renderProps = any.simpleObject();
const status = any.integer();
const context = any.simpleObject();
const rootComponent = any.simpleObject();
const renderedContent = any.string();
const html = any.string();
const response = any.string();
const defaultRender = sinon.stub();
routeMatcher.default.withArgs(url, routes).resolves({renderProps, status});
dataFetcher.default.withArgs({renderProps, store, status}).resolves({renderProps, status});
React.createElement.withArgs(RouterContext, sinon.match(renderProps)).returns(context);
React.createElement.withArgs(Root, {request, store}).returns(rootComponent);
domServer.renderToString.withArgs(rootComponent).returns(renderedContent);
respond.withArgs(reply, {renderedContent, store, status}).returns(response);
defaultRender.returns(html);
defaultRenderFactory.default.withArgs(request, store, renderProps, Root).returns(defaultRender);
respond.withArgs(reply, {renderedContent: {html}, store, status}).returns(response);

return assert.becomes(renderThroughReactRouter(request, reply, {routes, respond, Root, store}), response);
});

test('that response contains the custom-rendered content when a custom renderer is provided', async () => {
const respond = sinon.stub();
const reply = sinon.spy();
const renderProps = any.simpleObject();
const status = any.integer();
const response = any.string();
const render = sinon.stub();
const renderedContent = any.simpleObject();
const defaultRender = () => undefined;
routeMatcher.default.withArgs(url, routes).resolves({renderProps, status});
dataFetcher.default.withArgs({renderProps, store, status}).resolves({renderProps, status});
respond.withArgs(reply, {renderedContent, store, status}).returns(response);
defaultRenderFactory.default.withArgs(request, store, renderProps, Root).returns(defaultRender);
render.withArgs(defaultRender).returns(renderedContent);

assert.equal(await renderThroughReactRouter(request, reply, {render, routes, respond, Root, store}), response);
});

test('that a temporary redirect results when a redirectLocation is defined with a 302 status', () => {
const respond = sinon.stub();
const redirect = sinon.stub();
Expand Down

0 comments on commit 45c4ef0

Please sign in to comment.