Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A naive `renderToString()` that mimics `React.renderToStaticMarkup()`
/* globals describe, it */
import React from 'react';
import { expect } from 'chai';
// Using React.createElement works too!
import { renderToString, createElement } from './renderer';
describe( 'renderToString()', function() {
it( 'returns a string with a tag wrapping plain text for a component with one text child', function() {
const component = createElement( 'p', null, 'hi' );
const out = renderToString( component );
expect( out ).to.equal( '<p>hi</p>' );
} );
it( 'returns a string with a tag wrapping plain text for a function component', function() {
const MyEm = ( { children } ) => createElement( 'em', null, children );
const component = createElement( MyEm, null, 'hi' );
const out = renderToString( component );
expect( out ).to.equal( '<em>hi</em>' );
} );
it( 'returns a string with a tag wrapping a function component for a function component', function() {
const MyEm = ( { children } ) => createElement( 'em', null, children );
const component = createElement( MyEm, null, createElement( MyEm, null, 'hi' ) );
const out = renderToString( component );
expect( out ).to.equal( '<em><em>hi</em></em>' );
} );
it( 'returns a string with a tag wrapping plain text for an object component', function() {
class MyStrong extends React.Component {
render() {
return createElement( 'strong', null, this.props.children );
}
}
const component = createElement( MyStrong, null, 'hi' );
const out = renderToString( component );
expect( out ).to.equal( '<strong>hi</strong>' );
} );
it( 'returns a string with a tag wrapping joined text for a component with many text children', function() {
const component = createElement( 'p', null, [ 'hello', 'world' ] );
const out = renderToString( component );
expect( out ).to.equal( '<p>helloworld</p>' );
} );
it( 'returns a string with a tag wrapping another tag for a component with one component child', function() {
const child = createElement( 'em', null, 'yo' );
const component = createElement( 'p', null, child );
const out = renderToString( component );
expect( out ).to.equal( '<p><em>yo</em></p>' );
} );
it( 'returns a string with a tag wrapping other tags for a component with many component children', function() {
const child = createElement( 'em', null, 'yo' );
const text = createElement( 'span', null, 'there' );
const component = createElement( 'p', null, [ child, text ] );
const out = renderToString( component );
expect( out ).to.equal( '<p><em>yo</em><span>there</span></p>' );
} );
it( 'returns a string with a tag wrapping other tags and text for a component with component and text children', function() {
const child = createElement( 'em', null, 'yo' );
const component = createElement( 'p', null, [ child, ' there' ] );
const out = renderToString( component );
expect( out ).to.equal( '<p><em>yo</em> there</p>' );
} );
it( 'returns a string with a tag that expands its props to attributes for a text component', function() {
const component = createElement( 'a', { href: 'foo', target: 'top' }, 'link' );
const out = renderToString( component );
expect( out ).to.equal( '<a href="foo" target="top">link</a>' );
} );
it( 'returns a string with a tag that expands its className prop to a class attribute for a text component', function() {
const component = createElement( 'a', { className: 'cool' }, 'link' );
const out = renderToString( component );
expect( out ).to.equal( '<a class="cool">link</a>' );
} );
it( 'returns a string with a tag that uses its props to create a child for a function component', function() {
const MyEm = ( { name } ) => createElement( 'em', null, name );
const component = createElement( MyEm, { name: 'hi' } );
const out = renderToString( component );
expect( out ).to.equal( '<em>hi</em>' );
} );
} );
export function createElement( type, props = null, children = null ) {
if ( ! props ) {
props = {};
}
if ( children ) {
props.children = children;
}
return { type, props };
}
export function renderToString( component ) {
if ( component.length ) {
return component;
}
return renderNonText( component );
}
function renderNonText( component ) {
if ( typeof component.type !== 'function' ) {
return renderHtmlTag( component.type, component.props, component.props.children );
}
if ( component.type.prototype.render ) {
return renderObject( component.type, component.props );
}
return renderFunction( component.type, component.props );
}
function renderChildren( children ) {
return getChildrenArray( children ).map( child => renderToString( child ) ).join( '' );
}
function renderFunction( func, props ) {
return renderToString( func( props ) );
}
function renderObject( Type, props ) {
const instance = new Type( props );
return renderToString( instance.render() );
}
function renderHtmlTag( type, props, children ) {
return children ? `<${ type + renderPropsAsAttrs( props ) }>${ renderChildren( children ) }</${ type }>` : `<${ type + renderPropsAsAttrs( props ) } />`;
}
function renderPropsAsAttrs( props ) {
if ( ! props ) {
props = {};
}
const exclude = [ 'children' ];
const keys = Object.keys( props ).filter( key => exclude.indexOf( key ) === -1 );
if ( keys.length < 1 ) {
return '';
}
return ' ' + keys.map( attr => renderPropAsAttr( attr, props[ attr ] ) ).join( ' ' );
}
function renderPropAsAttr( prop, value ) {
const attr = prop === 'className' ? 'class' : prop;
return `${ attr }="${ value }"`;
}
function getChildrenArray( children = [] ) {
return Array.isArray( children ) ? children : [ children ];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment