Files
meteor/packages/server-render/server-render-tests.js
Ben Newman fb73388ce3 Make server-render API more flexible and isomorph-ish.
Render callbacks can now inject HTML content into multiple different
elements, and may also append content to the <head> or <body> elements, on
both the client and the server.

This new API was inspired by trying to use the styled-components npm
package on the server, which involves not only rendering and injecting
static HTML somewhere in the <body>, but also appending the resulting
<style> tag(s) into the <head>:

  import { onPageLoad } from "meteor/server-render";
  import { renderToString } from "react-dom/server";
  import { ServerStyleSheet } from "styled-components";

  onPageLoad(sink => {
    const sheet = new ServerStyleSheet();
    const html = renderToString(sheet.collectStyles(
      <App location={sink.request.url} />
    ));

    sink.renderIntoElementById("app", html);
    sink.appendToHead(sheet.getStyleTags());
  });

Note that the server-render package now exports an onPageLoad function,
rather than the old renderIntoElementById function. The functionality of
renderIntoElementById is now exposed by the {Client,Server}Sink API.

I say the client-side version of this API is 'isomorphish' to the
server-side version, because ClientSink methods can accept DOM nodes in
addition to raw HTML strings, whereas DOM nodes don't really make sense on
the server.
2017-06-29 15:08:32 -04:00

105 lines
3.0 KiB
JavaScript

import { Tinytest } from "meteor/tinytest";
import { WebAppInternals } from "meteor/webapp";
import { onPageLoad } from "meteor/server-render";
import { parse } from "parse5";
const skeleton = `
<h1>Look, Ma... static HTML!</h1>
<div id="container-2"></div>
<p>
<div id="container-1">
</div>
</p>`;
Tinytest.add('server-render - boilerplate', function (test) {
// This test is not a very good demonstration of the server-render
// abstraction. In normal usage, you would call renderIntoElementById
// and not think about the rest of this stuff. The extra complexity owes
// to the trickiness of testing this package without using a real
// browser to parse the resulting HTTP response.
const realCallback =
// Use the underlying abstraction to set the static HTML skeleton.
WebAppInternals.registerBoilerplateDataCallback(
"meteor/server-render",
(request, data, arch) => {
if (request.isServerRenderTest) {
test.equal(arch, "web.browser");
test.equal(request.url, "/server-render/test");
data.body = skeleton;
}
return realCallback.call(this, request, data, arch);
}
);
const callback1 = onPageLoad(sink => {
sink.renderIntoElementById("container-1", "<oyez/>");
});
// This callback is async, and that's fine because
// WebAppInternals.getBoilerplate is able to yield. Internally the
// webapp package uses a function called getBoilerplateAsync, so the
// Fiber power-tools need not be involved in typical requests.
const callback2 = onPageLoad(async sink => {
sink.renderIntoElementById(
"container-2",
(await "oy") + (await "ez")
);
});
try {
const boilerplate = WebAppInternals.getBoilerplate({
isServerRenderTest: true,
browser: { name: "fake" },
url: "/server-render/test"
}, "web.browser");
const ids = [];
const seen = new Set;
function walk(node) {
if (node && ! seen.has(node)) {
seen.add(node);
if (node.nodeName === "div" && node.attrs) {
node.attrs.some(attr => {
if (attr.name === "id") {
const id = attr.value;
if (id === "container-1") {
test.equal(node.childNodes[0].nodeName, "oyez");
ids.push(id);
} else if (id === "container-2") {
const child = node.childNodes[0];
test.equal(child.nodeName, "#text");
test.equal(child.value.trim(), "oyez");
ids.push(id);
}
return true;
}
});
}
if (node.childNodes) {
node.childNodes.forEach(walk)
}
}
}
walk(parse(boilerplate));
test.equal(ids, ["container-2", "container-1"]);
} finally {
// Cleanup to minimize interference with other tests:
WebAppInternals.registerBoilerplateDataCallback(
"meteor/server-render",
realCallback
);
onPageLoad.remove(callback1);
onPageLoad.remove(callback2);
}
});