diff --git a/src/index.d.ts b/src/index.d.ts
index c69419c6..2ade666c 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -5,6 +5,7 @@ declare module render {
shallow:boolean;
xml:boolean;
pretty:boolean;
+ alwaysRenderedComponents: Array[String];
}
function render(vnode:VNode, context?:any, options?:Options):string;
diff --git a/src/index.js b/src/index.js
index b2de14cd..aebbd4b9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -34,6 +34,7 @@ const VOID_ELEMENTS = [
* @param {Boolean} [options.shallow=false] If `true`, renders nested Components as HTML elements (``).
* @param {Boolean} [options.xml=false] If `true`, uses self-closing tags for elements without children.
* @param {Boolean} [options.pretty=false] If `true`, adds whitespace for readability
+ * @param {string[]} [options.alwaysRenderedComponents=[]] List of components that should be rendered with shallow rendering
*/
renderToString.render = renderToString;
@@ -47,13 +48,13 @@ renderToString.render = renderToString;
*/
let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
-
/** The default export is an alias of `render()`. */
export default function renderToString(vnode, context, opts, inner, isSvgMode) {
let { nodeName, attributes, children } = vnode || EMPTY,
isComponent = false;
context = context || {};
opts = opts || {};
+ opts.alwaysRenderedComponents = opts.alwaysRenderedComponents || [];
let pretty = opts.pretty,
indentChar = typeof pretty==='string' ? pretty : '\t';
@@ -69,9 +70,12 @@ export default function renderToString(vnode, context, opts, inner, isSvgMode) {
// components
if (typeof nodeName==='function') {
+ let componentName = getComponentName(nodeName);
isComponent = true;
- if (opts.shallow && (inner || opts.renderRootComponent===false)) {
- nodeName = getComponentName(nodeName);
+ if (opts.shallow &&
+ (inner || opts.renderRootComponent===false) &&
+ opts.alwaysRenderedComponents.indexOf(componentName)===-1) {
+ nodeName = componentName;
}
else {
let props = getNodeProps(vnode),
diff --git a/test/mixedRender.js b/test/mixedRender.js
new file mode 100644
index 00000000..24b6f322
--- /dev/null
+++ b/test/mixedRender.js
@@ -0,0 +1,85 @@
+import { render } from '../src';
+import {h, Component} from 'preact';
+import chai, { expect } from 'chai';
+import { spy, match } from 'sinon';
+import sinonChai from 'sinon-chai';
+
+chai.use(sinonChai);
+
+describe('mixedRender()', () => {
+ it('should not render nested components when not white listed', () => {
+ let Test = spy(({ foo, children }) =>
test child{children}
);
+ Test.displayName = 'Test';
+
+ let rendered = render(
+
+ , {}, { shallow: true, alwaysRenderedComponents: [] });
+
+ expect(rendered).to.equal(``);
+ expect(Test).not.to.have.been.called;
+ });
+
+ it('should always render root component', () => {
+ let Test = spy(({ foo, children }) => test child{children}
);
+ Test.displayName = 'Test';
+
+ let rendered = render(
+
+ asdf
+
+ , {}, { shallow: true, alwaysRenderedComponents: [] });
+
+ expect(rendered).to.equal(`test childasdf
`);
+ expect(Test).to.have.been.calledOnce;
+ });
+
+ it('should render nested components when they are white listed', () => {
+ let Test = spy(({ foo, children }) => test child{children}
);
+ Test.displayName = 'Test';
+
+ let rendered = render(
+
+ , undefined, { alwaysRenderedComponents: ['Test'] });
+
+ expect(rendered).to.equal(``);
+ expect(Test).to.have.been.called;
+ });
+
+ it('should not render nested components inside a whitelisted component', () => {
+ let Test = spy(({ foo, children }) => test child{children}
);
+ let Ignored = spy(({ title, children }) => This {title} should not be rendered
);
+ Test.displayName = 'Test';
+ Ignored.displayName = 'Ignored';
+
+ let rendered = render(
+
+ , {}, { shallow: true, alwaysRenderedComponents: ['Test'] });
+
+ expect(rendered).to.equal(``);
+ expect(Test).to.have.been.called;
+ expect(Ignored).to.not.have.been.called;
+ });
+
+ it('should render deeply nested components when all are white listed', () => {
+ let Test = spy(({ foo, children }) => test child{children}
);
+ let Ignored = spy(({ title, children }) => This {title} should be rendered
);
+ Test.displayName = 'Test';
+ Ignored.displayName = 'Ignored';
+
+ let rendered = render(
+
+ , {}, { shallow: true, alwaysRenderedComponents: ['Test', 'Ignored'] });
+
+ expect(rendered).to.equal(`test child
This FooBarTitle should be rendered
`);
+ expect(Test).to.have.been.called;
+ expect(Ignored).to.have.been.called;
+ });
+});
diff --git a/test/render.js b/test/render.js
index 2fa9c5ed..bfc1e878 100644
--- a/test/render.js
+++ b/test/render.js
@@ -500,7 +500,7 @@ describe('render', () => {
expect(renderXml()).to.equal(``);
});
});
-
+
describe('state locking', () => {
it('should set _disable and __x to true', () => {
let inst;
@@ -513,9 +513,9 @@ describe('render', () => {
return ;
}
}
-
+
expect(render()).to.equal('');
-
+
expect(inst).to.have.property('_disable', true);
expect(inst).to.have.property('__x', true);
});
@@ -533,9 +533,9 @@ describe('render', () => {
return ;
}
}
-
+
expect(render()).to.equal('');
-
+
expect(Bar).to.have.been.calledOnce.and.calledWithMatch({ count: 1 });
});
});