diff --git a/packages/electrode-redux-router-engine/lib/redux-router-engine.js b/packages/electrode-redux-router-engine/lib/redux-router-engine.js index 35a1efc89..c73e734b4 100644 --- a/packages/electrode-redux-router-engine/lib/redux-router-engine.js +++ b/packages/electrode-redux-router-engine/lib/redux-router-engine.js @@ -4,6 +4,7 @@ const Path = require("path"); const assert = require("assert"); +const Url = require("url"); const optionalRequire = require("optional-require")(require); const React = optionalRequire("react"); const ReactDomServer = optionalRequire("react-dom/server"); @@ -63,8 +64,8 @@ class ReduxRouterEngine { this._routesComponent = renderRoutes(this._routes); } - async render(req, options) { - const location = req.path || (req.url && req.url.path); + async render(req, options = {}) { + const location = options.location || req.url || Url.parse(req.path); try { const match = this._matchRoute(req, this._routes, location); @@ -72,23 +73,23 @@ class ReduxRouterEngine { if (match.length === 0) { return { status: 404, - message: `${pkg.name}: Path ${location} not found` + message: `${pkg.name}: Path ${location.path} not found` }; } const methods = match[0].methods || "get"; if (methods.toLowerCase().indexOf(req.method.toLowerCase()) < 0) { - throw new Error(`${pkg.name}: ${location} doesn't allow request method ${req.method}`); + throw new Error(`${pkg.name}: ${location.path} doesn't allow request method ${req.method}`); } - return await this._handleRender(req, location, match, options || {}); + return await this._handleRender(req, location, match, options); } catch (err) { this.options.logError.call(this, req, err); return { status: err.status || 500, // eslint-disable-line message: err.message, - path: err.path || location, + path: err.path || location.path, _err: err }; } @@ -96,7 +97,7 @@ class ReduxRouterEngine { // _matchRoute(req, routes, location) { - return matchRoutes(routes, location); + return matchRoutes(routes, location.pathname); } async _handleRender(req, location, match, options) { diff --git a/packages/electrode-redux-router-engine/package.json b/packages/electrode-redux-router-engine/package.json index c882894fd..544f31fa1 100644 --- a/packages/electrode-redux-router-engine/package.json +++ b/packages/electrode-redux-router-engine/package.json @@ -35,6 +35,7 @@ "babel-preset-stage-0": "^6.0.15", "babel-register": "^6.5.2", "electrode-archetype-njs-module-dev": "^2.1.0", + "electrode-server": "^1.5.1", "react": "^16.0.0 || ^15.3.1 || ^0.14.8", "react-dom": "^16.0.0 || ^15.3.1 || ^0.14.8", "test-init-nm": "./test/nm/test-init-nm", diff --git a/packages/electrode-redux-router-engine/test/routes.jsx b/packages/electrode-redux-router-engine/test/routes.jsx index b9743bffb..2946c019b 100644 --- a/packages/electrode-redux-router-engine/test/routes.jsx +++ b/packages/electrode-redux-router-engine/test/routes.jsx @@ -6,7 +6,9 @@ import { renderFlatRoutes } from "./render-flat-routes"; class Home extends React.Component { render() { - return
Home
; + const { search } = this.props.location; + const query = search ? ` - Query: ${search}` : ""; + return
Home{query}
; } } diff --git a/packages/electrode-redux-router-engine/test/spec/electrode-server.integration.spec.js b/packages/electrode-redux-router-engine/test/spec/electrode-server.integration.spec.js new file mode 100644 index 000000000..5b2260038 --- /dev/null +++ b/packages/electrode-redux-router-engine/test/spec/electrode-server.integration.spec.js @@ -0,0 +1,54 @@ +"use strict"; + +const ReduxRouterEngine = require("../.."); +const expect = require("chai").expect; +const electrodeServer = require("electrode-server"); + +require("babel-register"); + +const routes = require("../routes.jsx").default; + +describe("electrode server (Hapi) integration", function() { + let server; + let engine; + + before(async () => { + server = await electrodeServer({ + electrode: { logLevel: "none" }, + connections: { default: { port: 0 } } + }); + server.route({ + method: "get", + path: "/test", + handler: async (request, reply) => { + if (!engine) engine = new ReduxRouterEngine({ routes }); + const result = await engine.render(request); + reply(result); + } + }); + }); + + after(done => { + server.stop(done); + }); + + it("should render basic test route", () => { + return server.inject("/test").then(resp => { + expect(resp.result).to.deep.equal({ + status: 200, + html: "
Page
Home
", + prefetch: "window.__PRELOADED_STATE__ = {};" + }); + }); + }); + + it("should render with URL query", () => { + return server.inject("/test?foo=bar").then(resp => { + expect(resp.result).to.deep.equal({ + status: 200, + html: "
Page
Home - Query: ?foo=bar
", + prefetch: "window.__PRELOADED_STATE__ = {};" + }); + }); + }); +}); diff --git a/packages/electrode-redux-router-engine/test/spec/redux-router-engine.spec.js b/packages/electrode-redux-router-engine/test/spec/redux-router-engine.spec.js index 3bd93ccba..8e0db96b0 100644 --- a/packages/electrode-redux-router-engine/test/spec/redux-router-engine.spec.js +++ b/packages/electrode-redux-router-engine/test/spec/redux-router-engine.spec.js @@ -3,6 +3,7 @@ const Path = require("path"); const ReduxRouterEngine = require("../.."); const xstdout = require("xstdout"); +const Url = require("url"); const expect = require("chai").expect; @@ -17,14 +18,13 @@ describe("redux-router-engine", function() { testReq = { method: "get", log: () => {}, - app: {}, - url: {} + app: {} }; }); it("should return 404 for unknown index route", () => { const engine = new ReduxRouterEngine({ routes }); - testReq.url.path = "/oop/blah"; + testReq.url = Url.parse("/oop/blah"); return engine.render(testReq).then(result => { expect(result.status).to.equal(404); @@ -34,7 +34,7 @@ describe("redux-router-engine", function() { it("should return string error", () => { const intercept = xstdout.intercept(true); const engine = new ReduxRouterEngine({ routes }); - testReq.url.path = "/test/init-not-found"; + testReq.url = Url.parse("/test/init-not-found"); return engine.render(testReq).then(result => { intercept.restore(); @@ -46,7 +46,7 @@ describe("redux-router-engine", function() { it("should return Error error", () => { const intercept = xstdout.intercept(true); const engine = new ReduxRouterEngine({ routes, routesHandlerPath: Path.join(__dirname, "..") }); - testReq.url.path = "/throw-error"; + testReq.url = Url.parse("/throw-error"); return engine.render(testReq).then(result => { intercept.restore(); @@ -61,7 +61,7 @@ describe("redux-router-engine", function() { routes, renderToString: () => Promise.resolve(testHtml) }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq).then(result => { expect(result.html).to.equal(testHtml); @@ -70,7 +70,7 @@ describe("redux-router-engine", function() { it("should resolve index route", () => { const engine = new ReduxRouterEngine({ routes, routesHandlerPath: "test" }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq).then(result => { expect(result.status).to.equal(200); @@ -79,9 +79,31 @@ describe("redux-router-engine", function() { }); }); + it("should parse req.path if req.url is missing", () => { + const engine = new ReduxRouterEngine({ routes, routesHandlerPath: "test" }); + testReq.path = "/test?foo=bar"; + + return engine.render(testReq).then(result => { + expect(result.status).to.equal(200); + expect(result.html).to.equal("
Page
Home - Query: ?foo=bar
"); + expect(result.prefetch).to.equal("window.__PRELOADED_STATE__ = {};"); + }); + }); + + it("should use options.location", () => { + const engine = new ReduxRouterEngine({ routes, routesHandlerPath: "test" }); + testReq.url = Url.parse("/test?foo=bar"); + + return engine.render(testReq, { location: Url.parse("/test?a=1") }).then(result => { + expect(result.status).to.equal(200); + expect(result.html).to.equal("
Page
Home - Query: ?a=1
"); + expect(result.prefetch).to.equal("window.__PRELOADED_STATE__ = {};"); + }); + }); + it("should load init without leading . from node_modules", () => { const engine = new ReduxRouterEngine({ routes, routesHandlerPath: "test" }); - testReq.url.path = "/test-init-nm"; + testReq.url = Url.parse("/test-init-nm"); return engine.render(testReq).then(result => { expect(result.status).to.equal(200); @@ -95,7 +117,7 @@ describe("redux-router-engine", function() { routes: Path.resolve(__dirname, "../routes"), routesHandlerPath: "./test" }); - testReq.url.path = "/top-reducer/init"; + testReq.url = Url.parse("/top-reducer/init"); return engine.render(testReq).then(result => { expect(result.status).to.equal(200); @@ -108,7 +130,7 @@ describe("redux-router-engine", function() { it("should take routes as name of module", () => { const engine = new ReduxRouterEngine({ routes: "./test/routes" }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq).then(result => { expect(result.status).to.equal(200); @@ -119,7 +141,7 @@ describe("redux-router-engine", function() { it("should resolve skip SSR if disabled", () => { const engine = new ReduxRouterEngine({ routes }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); testReq.app.disableSSR = true; return engine.render(testReq).then(result => { @@ -129,7 +151,7 @@ describe("redux-router-engine", function() { it("escapes troublesome characters in the state", () => { const engine = new ReduxRouterEngine({ routes, routesHandlerPath: Path.join(__dirname, "..") }); - testReq.url.path = "/escape-chars"; + testReq.url = Url.parse("/escape-chars"); return engine.render(testReq).then(result => { expect(result.prefetch).to.contain( @@ -142,7 +164,7 @@ describe("redux-router-engine", function() { it("should return 302 for router Redirect component", () => { const engine = new ReduxRouterEngine({ routes, componentRedirect: true }); - testReq.url.path = "/test/component-redirect"; + testReq.url = Url.parse("/test/component-redirect"); return engine.render(testReq).then(result => { expect(result.status).to.equal(302); @@ -154,7 +176,7 @@ describe("redux-router-engine", function() { it("should return 500 for invalid component", () => { const intercept = xstdout.intercept(true); const engine = new ReduxRouterEngine({ routes }); - testReq.url.path = "/invalid-component"; + testReq.url = Url.parse("/invalid-component"); return engine.render(testReq).then(result => { intercept.restore(); @@ -166,7 +188,7 @@ describe("redux-router-engine", function() { it("should return 404 if component throws 404", () => { const intercept = xstdout.intercept(true); const engine = new ReduxRouterEngine({ routes }); - testReq.url.path = "/error-component"; + testReq.url = Url.parse("/error-component"); return engine.render(testReq).then(result => { intercept.restore(); @@ -177,7 +199,7 @@ describe("redux-router-engine", function() { it("should populate react-id when requested", () => { const engine = new ReduxRouterEngine({ routes, withIds: true }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq).then(result => { expect(result.html).to.contain("data-reactroot"); @@ -186,7 +208,7 @@ describe("redux-router-engine", function() { it("should not populate react-id by default", () => { const engine = new ReduxRouterEngine({ routes }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq).then(result => { expect(result.html).to.not.contain("data-reactroot"); @@ -199,7 +221,7 @@ describe("redux-router-engine", function() { stringifyPreloadedState: () => `window.__TEST_STATE__`, renderToString: () => "test" }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq).then(result => { expect(result.prefetch).to.equal(`window.__TEST_STATE__`); @@ -208,7 +230,7 @@ describe("redux-router-engine", function() { it("should use optional logError", () => { let error; - testReq.url.path = "/test/init-not-found"; + testReq.url = Url.parse("/test/init-not-found"); return new ReduxRouterEngine({ routes, @@ -225,7 +247,7 @@ describe("redux-router-engine", function() { it("render options should override constructor options", () => { const engine = new ReduxRouterEngine({ routes, withIds: true }); - testReq.url.path = "/test"; + testReq.url = Url.parse("/test"); return engine.render(testReq, { withIds: false }).then(result => { expect(result.html).to.not.contain("data-reactroot"); @@ -239,7 +261,7 @@ describe("redux-router-engine", function() { method: "post", log: () => {}, app: {}, - url: {} + url: Url.parse("/post-only") }; const engine = new ReduxRouterEngine({ routes }); @@ -257,7 +279,7 @@ describe("redux-router-engine", function() { method: "get", log: () => {}, app: {}, - url: {} + url: Url.parse("/test/init") }; const engine = new ReduxRouterEngine({ @@ -277,7 +299,7 @@ describe("redux-router-engine", function() { method: "get", log: () => {}, app: {}, - url: {} + url: Url.parse("/top-wait/init") }; const engine = new ReduxRouterEngine({ @@ -309,7 +331,7 @@ describe("redux-router-engine", function() { method: "get", log: () => {}, app: {}, - url: {} + url: Url.parse("/test-init2") }; const engine = new ReduxRouterEngine({ @@ -329,7 +351,7 @@ describe("redux-router-engine", function() { method: "get", log: () => {}, app: {}, - url: {} + url: Url.parse("/test/redux") }; const engine = new ReduxRouterEngine({