diff --git a/packages/subapp-react/lib/framework-lib.js b/packages/subapp-react/lib/framework-lib.js
index 52c26ed04..5c34df629 100644
--- a/packages/subapp-react/lib/framework-lib.js
+++ b/packages/subapp-react/lib/framework-lib.js
@@ -141,7 +141,8 @@ class FrameworkLib {
assert(Provider, "subapp-web: react-redux Provider not available");
// finally render the element with Redux Provider and the store created
return await this.renderTo(
- React.createElement(Provider, { store: this.store }, this.createTopComponent())
+ React.createElement(Provider, { store: this.store }, this.createTopComponent()),
+ options
);
}
return "";
diff --git a/packages/subapp-react/package.json b/packages/subapp-react/package.json
index 81f26da61..9f7ff3aec 100644
--- a/packages/subapp-react/package.json
+++ b/packages/subapp-react/package.json
@@ -39,13 +39,15 @@
"@babel/register": "^7.7.7",
"babel-preset-minify": "^0.5.1",
"electrode-archetype-njs-module-dev": "^3.0.0",
+ "jsdom": "^15.2.1",
"react": "^16.8.3",
"react-async-ssr": "^0.6.0",
"react-dom": "^16.8.3",
"react-redux": "^6.0.1",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
- "redux": "^4.0.1"
+ "redux": "^4.0.1",
+ "run-verify": "^1.2.2"
},
"peerDependencies": {
"react": "*",
@@ -78,10 +80,10 @@
"**/.babelrc.js"
],
"check-coverage": true,
- "statements": 0,
- "branches": 0,
- "functions": 0,
- "lines": 0,
+ "statements": 100,
+ "branches": 100,
+ "functions": 100,
+ "lines": 100,
"cache": true
}
}
diff --git a/packages/subapp-react/src/fe-framework-lib.js b/packages/subapp-react/src/fe-framework-lib.js
index deacf817a..7cb9095c7 100644
--- a/packages/subapp-react/src/fe-framework-lib.js
+++ b/packages/subapp-react/src/fe-framework-lib.js
@@ -11,7 +11,6 @@ class FrameworkLib {
const props = { ...options._prepared, ...options.props };
const Component = subApp.info.StartComponent || subApp.info.Component;
-
if (element) {
if (options.serverSideRendering) {
hydrate(, element);
diff --git a/packages/subapp-react/src/index.js b/packages/subapp-react/src/index.js
index 91d0e89aa..117a801d6 100644
--- a/packages/subapp-react/src/index.js
+++ b/packages/subapp-react/src/index.js
@@ -9,3 +9,5 @@ export * from "subapp-web";
export { default as React } from "react";
export { default as AppContext } from "./app-context";
+
+export { FrameworkLib };
diff --git a/packages/subapp-react/test/spec/fe-framework.spec.js b/packages/subapp-react/test/spec/fe-framework.spec.js
new file mode 100644
index 000000000..884cd2461
--- /dev/null
+++ b/packages/subapp-react/test/spec/fe-framework.spec.js
@@ -0,0 +1,62 @@
+"use strict";
+
+const React = require("react"); // eslint-disable-line
+const feLib = require("../../src");
+const { JSDOM } = require("jsdom");
+
+describe("FE React framework", function() {
+ //
+ it("should setup FrameworkLib", () => {
+ expect(feLib.React).to.be.ok;
+ expect(feLib.AppContext).to.be.ok;
+ expect(feLib.loadSubApp).to.be.a("function");
+ expect(feLib.FrameworkLib).to.be.ok;
+ });
+
+ it("should render component into DOM element", () => {
+ const dom = new JSDOM(`
`);
+ global.window = dom.window;
+ const element = dom.window.document.getElementById("test");
+ const framework = new feLib.FrameworkLib({
+ subApp: {
+ info: {
+ Component: props => hello {props.foo}
+ }
+ },
+ element,
+ options: { props: { foo: "bar" } }
+ });
+ framework.renderStart();
+ expect(element.innerHTML).equals(`hello bar
`);
+ });
+
+ it("should hydrate render component into DOM element", () => {
+ const dom = new JSDOM(``);
+ global.window = dom.window;
+ const element = dom.window.document.getElementById("test");
+ const framework = new feLib.FrameworkLib({
+ subApp: {
+ info: {
+ Component: props => hello {props.foo}
+ }
+ },
+ element,
+ options: { props: { foo: "bar" }, serverSideRendering: true }
+ });
+ framework.renderStart();
+ expect(element.innerHTML).equals(`hello bar
`);
+ });
+
+ it("should just return the component without DOM element", () => {
+ const Component = props => hello {props.foo}
;
+
+ const framework = new feLib.FrameworkLib({
+ subApp: {
+ info: { Component }
+ },
+ options: { props: { foo: "bar" }, serverSideRendering: true }
+ });
+ const c = framework.renderStart();
+ expect(c.type).equals(Component);
+ });
+});
diff --git a/packages/subapp-react/test/spec/ssr-framework.spec.js b/packages/subapp-react/test/spec/ssr-framework.spec.js
index 919df0b9d..fa4a37176 100644
--- a/packages/subapp-react/test/spec/ssr-framework.spec.js
+++ b/packages/subapp-react/test/spec/ssr-framework.spec.js
@@ -5,6 +5,9 @@ const React = require("react"); // eslint-disable-line
const lib = require("../../lib");
const { withRouter } = require("react-router");
const { Route, Switch } = require("react-router-dom"); // eslint-disable-line
+const { asyncVerify } = require("run-verify");
+const Redux = require("redux");
+const { connect } = require("react-redux");
describe("SSR React framework", function() {
it("should setup React framework", () => {
@@ -24,6 +27,16 @@ describe("SSR React framework", function() {
expect(res).contains("has no StartComponent");
});
+ it("should not do SSR if serverSideRendering is not true", async () => {
+ const framework = new lib.FrameworkLib({
+ subApp: { Component: () => {} },
+ subAppServer: {},
+ options: { serverSideRendering: false }
+ });
+ const res = await framework.handleSSR();
+ expect(res).equals("");
+ });
+
it("should render subapp with w/o initial props if no prepare provided", async () => {
const framework = new lib.FrameworkLib({
subApp: {
@@ -59,6 +72,84 @@ describe("SSR React framework", function() {
expect(res).contains("Hello foo bar");
});
+ it("should render Component with streaming if enabled", () => {
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ prepare: () => ({ test: "foo bar" }),
+ Component: props => {
+ return Hello {props.test}
;
+ }
+ },
+ subAppServer: {},
+ options: { serverSideRendering: true, streaming: true },
+ context: {
+ user: {}
+ }
+ });
+ return asyncVerify(
+ () => framework.handleSSR(),
+ (stream, next) => {
+ let res = "";
+ stream.on("data", data => (res += data.toString()));
+ stream.on("end", () => next(null, res));
+ stream.on("error", next);
+ },
+ res => expect(res).contains("Hello foo bar")
+ );
+ });
+
+ it("should hydrate render Component with streaming if enabled", () => {
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ prepare: () => ({ test: "foo bar" }),
+ Component: props => {
+ return Hello {props.test}
;
+ }
+ },
+ subAppServer: {},
+ options: { serverSideRendering: true, streaming: true, hydrateServerData: true },
+ context: {
+ user: {}
+ }
+ });
+ return asyncVerify(
+ () => framework.handleSSR(),
+ (stream, next) => {
+ let res = "";
+ stream.on("data", data => (res += data.toString()));
+ stream.on("end", () => next(null, res));
+ stream.on("error", next);
+ },
+ res => expect(res).contains(`Hello foo bar
`)
+ );
+ });
+
+ it("should render Component from subapp with hydration info", async () => {
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ prepare: () => ({
+ test: "foo bar"
+ }),
+ Component: props => {
+ return Hello {props.test}
;
+ }
+ },
+ subAppServer: {},
+ options: {
+ serverSideRendering: true,
+ hydrateServerData: true
+ },
+ context: {
+ user: {}
+ }
+ });
+ // data-reactroot isn't getting created due to Context.Provider
+ // see https://github.com/facebook/react/issues/15012
+ const res = await framework.handleSSR();
+ // but the non-static renderToString adds a for some reason
+ expect(res).contains("Hello foo bar");
+ });
+
it("should render Component from subapp with initial props from server's prepare", async () => {
const framework = new lib.FrameworkLib({
subApp: {
@@ -78,6 +169,113 @@ describe("SSR React framework", function() {
expect(res).contains("Hello foo bar");
});
+ it("should init redux store and render Component", async () => {
+ const Component = connect(x => x)(props => Hello {props.test}
);
+
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ __redux: true,
+ Component,
+ reduxCreateStore: initState => Redux.createStore(x => x, initState),
+ prepare: () => ({ test: "foo bar" })
+ },
+ subAppServer: {},
+ options: { serverSideRendering: true },
+ context: {
+ user: {}
+ }
+ });
+ const res = await framework.handleSSR();
+ expect(res).contains("Hello foo bar");
+ expect(framework.initialStateStr).equals(`{"test":"foo bar"}`);
+ });
+
+ it("should init redux store and render Component but doesn't attach initial state", async () => {
+ const Component = connect(x => x)(props => Hello {props.test}
);
+
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ __redux: true,
+ Component,
+ reduxCreateStore: initState => Redux.createStore(x => x, initState),
+ prepare: () => ({ test: "foo bar" })
+ },
+ subAppServer: { attachInitialState: false },
+ options: { serverSideRendering: true },
+ context: {
+ user: {}
+ }
+ });
+ const res = await framework.handleSSR();
+ expect(res).contains("Hello foo bar");
+ expect(framework.initialStateStr).equals(undefined);
+ });
+
+ it("should init redux store but doesn't render Component if serverSideRendering is not true", async () => {
+ const Component = connect(x => x)(props => Hello {props.test}
);
+
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ __redux: true,
+ Component,
+ reduxCreateStore: initState => Redux.createStore(x => x, initState),
+ prepare: () => ({ test: "foo bar" })
+ },
+ subAppServer: { attachInitialState: false },
+ options: { serverSideRendering: false },
+ context: {
+ user: {}
+ }
+ });
+ const res = await framework.handleSSR();
+ expect(res).equals("");
+ expect(framework.initialStateStr).equals(undefined);
+ });
+
+ it("should init redux store with empty state without prepare and render Component", async () => {
+ const Component = connect(x => x)(props => Hello {props.test}
);
+
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ __redux: true,
+ Component,
+ reduxCreateStore: initState => Redux.createStore(x => x, initState)
+ },
+ subAppServer: {},
+ options: { serverSideRendering: true },
+ context: {
+ user: {}
+ }
+ });
+ const res = await framework.handleSSR();
+ expect(res).contains("Hello <");
+ expect(framework.initialStateStr).equals(`{}`);
+ });
+
+ it("should hydrate render Component with suspense using react-async-ssr", async () => {
+ const framework = new lib.FrameworkLib({
+ subApp: {
+ Component: props => {
+ return (
+ Loading...}>
+ Hello {props.test}
+
+ );
+ }
+ },
+ subAppServer: {
+ prepare: () => ({ test: "foo bar" })
+ },
+ options: { serverSideRendering: true, suspenseSsr: true, hydrateServerData: true },
+ context: {
+ user: {}
+ }
+ });
+ const res = await framework.handleSSR();
+ // react-async-ssr includes data-reactroot
+ expect(res).contains(`Hello foo bar
`);
+ });
+
it("should render Component with suspense using react-async-ssr", async () => {
const framework = new lib.FrameworkLib({
subApp: {