Last active
December 23, 2016 20:43
-
-
Save sirbrillig/736da249f7619624bb1d570cc026bfcb to your computer and use it in GitHub Desktop.
A naive `renderToString()` that mimics `React.renderToStaticMarkup()`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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>' ); | |
} ); | |
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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