UNPKG

react-oc

Version:

A React component that allows OpenComponents to operate within a react application.

308 lines (260 loc) 14.1 kB
import 'jest-plugin-console-matchers/setup'; import React from 'react'; import ReactDOM from 'react-dom'; import ReactDOMServer from 'react-dom/server'; import jQuery from 'jquery'; import Promise from 'bluebird'; import { renderAsync } from './__test__/react-helpers' import { OpenComponentsContext } from "./OpenComponentsContext"; import { OCContext } from "./OCContext"; import { OpenComponent } from "./OpenComponent"; describe('<OpenComponent />', () => { const callCountScript = 'window.callCount = window.callCount || 0; window.callCount++;' beforeEach(() => { delete window.callCount; document.body.innerHTML = ''; }); describe('When not mounted within a <ComponentContext />', () => { it('throws an error', () => { const node = document.createElement('div'); return expect(renderAsync(<OpenComponent.Prefetched prefetchKey='my-component' />, node)).rejects .toThrow(/must be nested within a <ComponentContext \/>/); }); }); let baseContext, baseContextServer; beforeEach(() => { baseContext = { getElement: jest.fn(() => {}), getHtml: jest.fn(() => 'hello world'), saveElement: jest.fn(() => {}), oc: { renderNestedComponent: jest.fn((_, cb) => cb()), $: jQuery, } }; baseContextServer = { getElement: baseContext.getElement, getHtml: baseContext.getHtml, saveElement: baseContext.saveElement, }; }); it('throws an error if prefetchKey prop was not defined', () => { const node = document.createElement('div'); return expect(renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched /> </OCContext.Provider>, node) ).rejects.toThrow(/Mandatory prop 'prefetchKey' was not provided/); }); it('should apply the given id to a container div', async () => { const node = document.createElement('div'); await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched id='my-unique-id' prefetchKey='my-component' /> </OCContext.Provider>, node); expect(node.childNodes[0].id).toBe('my-unique-id'); }); it('should apply the given className to a container div', async () => { const node = document.createElement('div'); await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched className='my-class' prefetchKey='my-component' /> </OCContext.Provider>, node); expect(node.childNodes[0].className).toBe('my-class'); }); it('renders an empty div if no html is provided by context.getHtml', async () => { const node = document.createElement('div'); baseContext.getHtml.mockImplementation(() => undefined) await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node); expect(node.innerHTML).toBe('<div></div>'); }); it('renders the fallback if no html is provided by context.getHtml and fallback is provided', async () => { const node = document.createElement('div'); baseContext.getHtml.mockImplementation(() => undefined) await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched prefetchKey='my-component' fallback={`<span>Hello world</span>`} /> </OCContext.Provider>, node); expect(node.innerHTML).toBe('<div><span>Hello world</span></div>'); }); it('should render markup from context.getHtml with prefetchKey in container div', async () => { const node = document.createElement('div'); const fakeHtml = `<h1>Hello world</h1>`; baseContext.getHtml.mockImplementation((key) => key === 'my-component' ? fakeHtml : undefined ) await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node); expect(node.innerHTML).toContain(fakeHtml); }); it('should run any scripts from context.getHtml with prefetchKey in container div', async () => { const node = document.createElement('div'); document.body.append(node); const fakeHtml = `<h1>Hello world</h1><script>${callCountScript}</script>`; baseContext.getHtml.mockImplementation((key) => key === 'my-component' ? fakeHtml : undefined ) await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node); expect(window.callCount).toBe(1); }); describe('when getHtml returns an unrendered oc-component tag', () => { const fakeHtml = `<oc-component data-rendered="false" name="my-component"></oc-component>`; it('should call oc.renderNestedComponent', async () => { const node = document.createElement('div'); baseContext.getHtml.mockImplementation(() => fakeHtml) await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node); expect(baseContext.oc.renderNestedComponent).toBeCalledWith(expect.objectContaining({ jquery: expect.anything(), 0: expect.objectContaining({ outerHTML: fakeHtml }) }), expect.anything()); }) }); it('should render markup from context.getHtml with prefetchKey in container div', async () => { const node = document.createElement('div'); const fakeHtml = `<h1>Hello world</h1>`; baseContext.getHtml.mockImplementation((key) => key === 'my-component' ? fakeHtml : undefined ) await renderAsync( <OCContext.Provider value={{...baseContext}}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node); expect(node.innerHTML).toContain(fakeHtml); }); describe('when server side rendering', () => { it('should render markup from context.getHtml with prefetchKey in container div', () => { const fakeHtml = `<h1>Hello world</h1>`; baseContextServer.getHtml.mockImplementation((key) => key === 'my-component' ? fakeHtml : undefined ) const render = ReactDOMServer.renderToString( <OCContext.Provider value={{...baseContextServer}}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>); expect(render).toContain(fakeHtml); }); }); describe('when hydrating over server side rendered markup', () => { it('should not change markup', async () => { const node = document.createElement('div'); node.innerHTML = ReactDOMServer.renderToString(<OCContext.Provider value={{ ...baseContextServer, getHtml: () => '<h1>Hello</h1><p>World</p>' }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>); const existingMarkup = node.innerHTML; await new Promise((resolve) => ReactDOM.hydrate( <OCContext.Provider value={{...baseContext, getHtml: () => undefined }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node, resolve)); expect(node.innerHTML).toBe(existingMarkup); }); it('should warn if getHtml provides a different value than server', async () => { const node = document.createElement('div'); node.innerHTML = ReactDOMServer.renderToString(<OCContext.Provider value={{ ...baseContextServer, getHtml: () => '<h1>Hello</h1><p>World</p>' }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>); const existingMarkup = node.innerHTML; const expectedWarning = /Warning: Prop `[^`]+` did not match./ suppress.console('error', expectedWarning); await new Promise((resolve) => ReactDOM.hydrate( <OCContext.Provider value={{...baseContext, getHtml: () => '<h1>Goodbye</h1><p>Universe</p>' }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node, resolve)); expect(console.error).toHaveBeenCalledWith( expect.stringMatching(expectedWarning), 'dangerouslySetInnerHTML', expect.anything(), expect.anything(), ); }); it('should not run script tags again', async () => { const node = document.createElement('div'); document.body.append(node); node.innerHTML = ReactDOMServer.renderToString(<OCContext.Provider value={{ ...baseContextServer, getHtml: () => `<h1>Hello</h1><p>World</p><script>${callCountScript}</script>` }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>); // simulate browser called script tag. eval(callCountScript); const existingMarkup = node.innerHTML; await new Promise((resolve) => ReactDOM.hydrate( <OCContext.Provider value={{...baseContext, getHtml: () => undefined }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node, resolve)); expect(window.callCount).toBe(1); }); it('should run script tags again if getHtml returns the same markup during hydration', async () => { const node = document.createElement('div'); document.body.append(node); const fakeMarkup = `<h1>Hello</h1><p>World</p><script>${callCountScript}</script>`; node.innerHTML = ReactDOMServer.renderToString(<OCContext.Provider value={{ ...baseContextServer, getHtml: () => fakeMarkup }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>); // simulate browser called script tag. eval(callCountScript); const existingMarkup = node.innerHTML; await new Promise((resolve) => ReactDOM.hydrate( <OCContext.Provider value={{...baseContext, getHtml: () => fakeMarkup }}> <OpenComponent.Prefetched prefetchKey='my-component' /> </OCContext.Provider>, node, resolve)); expect(window.callCount).toBe(2); }); }); describe('when given a captureAs prop', () => { it('calls saveElements on context with the captureAs value and elements from getHtml', async () => { const node = document.createElement('div'); const html = '<div>hello</div><h1>world</h1>'; const saveElements = jest.fn(); const getElements = jest.fn(); await renderAsync( <OCContext.Provider value={{...baseContext, saveElements, getElements, getHtml: () => html}}> <OpenComponent.Prefetched prefetchKey='my-component' captureAs='my-component-1' /> </OCContext.Provider>, node); expect(saveElements).toBeCalledWith('my-component-1', [ expect.objectContaining({ innerHTML: 'hello', tagName: 'DIV' }), expect.objectContaining({ innerHTML: 'world', tagName: 'H1' }), ]); }); describe('when context.getElements returns a html element', () => { it('calls getElements with the captureAs key', async () => { const node = document.createElement('div'); const element = document.createElement('span'); const getElements = jest.fn().mockImplementation((key) => element); await renderAsync( <OCContext.Provider value={{...baseContext, getElements}}> <OpenComponent.Prefetched prefetchKey='my-component' captureAs='my-component-1' /> </OCContext.Provider>, node); expect(getElements).toBeCalledWith('my-component-1'); }); it('injects the elements into the container', async () => { const node = document.createElement('div'); const prevNode = document.createElement('span'); prevNode.innerHTML = '<span>span</span>hello<div>div</div>'; const elements = [...prevNode.childNodes]; const getElements = jest.fn().mockImplementation((key) => elements) await renderAsync( <OCContext.Provider value={{...baseContext, getElements}}> <OpenComponent.Prefetched prefetchKey='my-component' captureAs='my-component-1' /> </OCContext.Provider>, node); const div = node.childNodes[0]; expect(div.childNodes.length).toBe(elements.length); for(var i = 0; i < elements.length; i++) { // using toBe ensures that it is the same element with // all event handlers etc. in-tact. expect(div.childNodes[i]).toBe(elements[i]); } }); }); }); });