UNPKG

arkit

Version:

Visualises JavaScript, TypeScript and Flow codebases as meaningful and committable architecture diagrams

513 lines (460 loc) 15.7 kB
/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ 'use strict'; let React; let ReactDOM; let ReactDOMServer; let ReactTestUtils; describe('ReactDOM', () => { beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); }); it('should bubble onSubmit', function() { const container = document.createElement('div'); let count = 0; let buttonRef; function Parent() { return ( <div onSubmit={event => { event.preventDefault(); count++; }}> <Child /> </div> ); } function Child() { return ( <form> <input type="submit" ref={button => (buttonRef = button)} /> </form> ); } document.body.appendChild(container); try { ReactDOM.render(<Parent />, container); buttonRef.click(); expect(count).toBe(1); } finally { document.body.removeChild(container); } }); it('allows a DOM element to be used with a string', () => { const element = React.createElement('div', {className: 'foo'}); const node = ReactTestUtils.renderIntoDocument(element); expect(node.tagName).toBe('DIV'); }); it('should allow children to be passed as an argument', () => { const argNode = ReactTestUtils.renderIntoDocument( React.createElement('div', null, 'child'), ); expect(argNode.innerHTML).toBe('child'); }); it('should overwrite props.children with children argument', () => { const conflictNode = ReactTestUtils.renderIntoDocument( React.createElement('div', {children: 'fakechild'}, 'child'), ); expect(conflictNode.innerHTML).toBe('child'); }); /** * We need to make sure that updates occur to the actual node that's in the * DOM, instead of a stale cache. */ it('should purge the DOM cache when removing nodes', () => { let myDiv = ReactTestUtils.renderIntoDocument( <div> <div key="theDog" className="dog" />, <div key="theBird" className="bird" /> </div>, ); // Warm the cache with theDog myDiv = ReactTestUtils.renderIntoDocument( <div> <div key="theDog" className="dogbeforedelete" />, <div key="theBird" className="bird" />, </div>, ); // Remove theDog - this should purge the cache myDiv = ReactTestUtils.renderIntoDocument( <div> <div key="theBird" className="bird" />, </div>, ); // Now, put theDog back. It's now a different DOM node. myDiv = ReactTestUtils.renderIntoDocument( <div> <div key="theDog" className="dog" />, <div key="theBird" className="bird" />, </div>, ); // Change the className of theDog. It will use the same element myDiv = ReactTestUtils.renderIntoDocument( <div> <div key="theDog" className="bigdog" />, <div key="theBird" className="bird" />, </div>, ); const dog = myDiv.childNodes[0]; expect(dog.className).toBe('bigdog'); }); it('throws in render() if the mount callback is not a function', () => { function Foo() { this.a = 1; this.b = 2; } class A extends React.Component { state = {}; render() { return <div />; } } const myDiv = document.createElement('div'); expect(() => { expect(() => { ReactDOM.render(<A />, myDiv, 'no'); }).toWarnDev( 'render(...): Expected the last optional `callback` argument to be ' + 'a function. Instead received: no.', ); }).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: no', ); expect(() => { expect(() => { ReactDOM.render(<A />, myDiv, {foo: 'bar'}); }).toWarnDev( 'render(...): Expected the last optional `callback` argument to be ' + 'a function. Instead received: [object Object].', ); }).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); expect(() => { expect(() => { ReactDOM.render(<A />, myDiv, new Foo()); }).toWarnDev( 'render(...): Expected the last optional `callback` argument to be ' + 'a function. Instead received: [object Object].', ); }).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); }); it('throws in render() if the update callback is not a function', () => { function Foo() { this.a = 1; this.b = 2; } class A extends React.Component { state = {}; render() { return <div />; } } const myDiv = document.createElement('div'); ReactDOM.render(<A />, myDiv); expect(() => { expect(() => { ReactDOM.render(<A />, myDiv, 'no'); }).toWarnDev( 'render(...): Expected the last optional `callback` argument to be ' + 'a function. Instead received: no.', ); }).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: no', ); ReactDOM.render(<A />, myDiv); // Re-mount expect(() => { expect(() => { ReactDOM.render(<A />, myDiv, {foo: 'bar'}); }).toWarnDev( 'render(...): Expected the last optional `callback` argument to be ' + 'a function. Instead received: [object Object].', ); }).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); ReactDOM.render(<A />, myDiv); // Re-mount expect(() => { expect(() => { ReactDOM.render(<A />, myDiv, new Foo()); }).toWarnDev( 'render(...): Expected the last optional `callback` argument to be ' + 'a function. Instead received: [object Object].', ); }).toThrowError( 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: [object Object]', ); }); it('preserves focus', () => { let input; let input2; class A extends React.Component { render() { return ( <div> <input id="one" ref={r => (input = input || r)} /> {this.props.showTwo && ( <input id="two" ref={r => (input2 = input2 || r)} /> )} </div> ); } componentDidUpdate() { // Focus should have been restored to the original input expect(document.activeElement.id).toBe('one'); input2.focus(); expect(document.activeElement.id).toBe('two'); log.push('input2 focused'); } } const log = []; const container = document.createElement('div'); document.body.appendChild(container); try { ReactDOM.render(<A showTwo={false} />, container); input.focus(); // When the second input is added, let's simulate losing focus, which is // something that could happen when manipulating DOM nodes (but is hard to // deterministically force without relying intensely on React DOM // implementation details) const div = container.firstChild; ['appendChild', 'insertBefore'].forEach(name => { const mutator = div[name]; div[name] = function() { if (input) { input.blur(); expect(document.activeElement.tagName).toBe('BODY'); log.push('input2 inserted'); } return mutator.apply(this, arguments); }; }); expect(document.activeElement.id).toBe('one'); ReactDOM.render(<A showTwo={true} />, container); // input2 gets added, which causes input to get blurred. Then // componentDidUpdate focuses input2 and that should make it down to here, // not get overwritten by focus restoration. expect(document.activeElement.id).toBe('two'); expect(log).toEqual(['input2 inserted', 'input2 focused']); } finally { document.body.removeChild(container); } }); it('calls focus() on autoFocus elements after they have been mounted to the DOM', () => { const originalFocus = HTMLElement.prototype.focus; try { let focusedElement; let inputFocusedAfterMount = false; // This test needs to determine that focus is called after mount. // Can't check document.activeElement because PhantomJS is too permissive; // It doesn't require element to be in the DOM to be focused. HTMLElement.prototype.focus = function() { focusedElement = this; inputFocusedAfterMount = !!this.parentNode; }; const container = document.createElement('div'); document.body.appendChild(container); ReactDOM.render( <div> <h1>Auto-focus Test</h1> <input autoFocus={true} /> <p>The above input should be focused after mount.</p> </div>, container, ); expect(inputFocusedAfterMount).toBe(true); expect(focusedElement.tagName).toBe('INPUT'); } finally { HTMLElement.prototype.focus = originalFocus; } }); it("shouldn't fire duplicate event handler while handling other nested dispatch", () => { const actual = []; class Wrapper extends React.Component { componentDidMount() { this.ref1.click(); } render() { return ( <div> <div onClick={() => { actual.push('1st node clicked'); this.ref2.click(); }} ref={ref => (this.ref1 = ref)} /> <div onClick={ref => { actual.push("2nd node clicked imperatively from 1st's handler"); }} ref={ref => (this.ref2 = ref)} /> </div> ); } } const container = document.createElement('div'); document.body.appendChild(container); try { ReactDOM.render(<Wrapper />, container); const expected = [ '1st node clicked', "2nd node clicked imperatively from 1st's handler", ]; expect(actual).toEqual(expected); } finally { document.body.removeChild(container); } }); it('should not crash with devtools installed', () => { try { global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { inject: function() {}, onCommitFiberRoot: function() {}, onCommitFiberUnmount: function() {}, supportsFiber: true, }; jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); class Component extends React.Component { render() { return <div />; } } ReactDOM.render(<Component />, document.createElement('container')); } finally { delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__; } }); it('should not crash calling findDOMNode inside a function component', () => { const container = document.createElement('div'); class Component extends React.Component { render() { return <div />; } } const instance = ReactTestUtils.renderIntoDocument(<Component />); const App = () => { ReactDOM.findDOMNode(instance); return <div />; }; if (__DEV__) { ReactDOM.render(<App />, container); } }); it('throws in DEV if jsdom is destroyed by the time setState() is called', () => { class App extends React.Component { state = {x: 1}; componentDidUpdate() {} render() { return <div />; } } const container = document.createElement('div'); const instance = ReactDOM.render(<App />, container); const documentDescriptor = Object.getOwnPropertyDescriptor( global, 'document', ); try { // Emulate jsdom environment cleanup. // This is roughly what happens if the test finished and then // an asynchronous callback tried to setState() after this. delete global.document; // The error we're interested in is thrown by invokeGuardedCallback, which // in DEV is used 1) to replay a failed begin phase, or 2) when calling // lifecycle methods. We're triggering the second case here. const fn = () => instance.setState({x: 2}); if (__DEV__) { expect(fn).toThrow( 'The `document` global was defined when React was initialized, but is not ' + 'defined anymore. This can happen in a test environment if a component ' + 'schedules an update from an asynchronous callback, but the test has already ' + 'finished running. To solve this, you can either unmount the component at ' + 'the end of your test (and ensure that any asynchronous operations get ' + 'canceled in `componentWillUnmount`), or you can change the test itself ' + 'to be asynchronous.', ); } else { expect(fn).not.toThrow(); } } finally { // Don't break other tests. Object.defineProperty(global, 'document', documentDescriptor); } }); it('reports stacks with re-entrant renderToString() calls on the client', () => { function Child2(props) { return <span ariaTypo3="no">{props.children}</span>; } function App2() { return ( <Child2> {ReactDOMServer.renderToString(<blink ariaTypo2="no" />)} </Child2> ); } function Child() { return ( <span ariaTypo4="no">{ReactDOMServer.renderToString(<App2 />)}</span> ); } function ServerEntry() { return ReactDOMServer.renderToString(<Child />); } function App() { return ( <div> <span ariaTypo="no" /> <ServerEntry /> <font ariaTypo5="no" /> </div> ); } const container = document.createElement('div'); expect(() => ReactDOM.render(<App />, container)).toWarnDev([ // ReactDOM(App > div > span) 'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + ' in div (at **)\n' + ' in App (at **)', // ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink) 'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in blink (at **)', // ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2 > Child2 > span) 'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + ' in Child2 (at **)\n' + ' in App2 (at **)', // ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child > span) 'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in span (at **)\n' + ' in Child (at **)', // ReactDOM(App > div > font) 'Invalid ARIA attribute `ariaTypo5`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' + ' in font (at **)\n' + ' in div (at **)\n' + ' in App (at **)', ]); }); });