| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | * |
| 7 | * @emails react-core |
| 8 | */ |
| 9 | |
| 10 | 'use strict'; |
| 11 | |
| 12 | const React = require('react'); |
| 13 | const ReactDOM = require('react-dom'); |
| 14 | const ReactDOMClient = require('react-dom/client'); |
| 15 | const act = require('internal-test-utils').act; |
| 16 | const assertConsoleErrorDev = |
| 17 | require('internal-test-utils').assertConsoleErrorDev; |
| 18 | |
| 19 | describe('ReactMount', () => { |
| 20 | it('should destroy a react root upon request', async () => { |
| 21 | const mainContainerDiv = document.createElement('div'); |
| 22 | document.body.appendChild(mainContainerDiv); |
| 23 | |
| 24 | const instanceOne = <div className="firstReactDiv" />; |
| 25 | const firstRootDiv = document.createElement('div'); |
| 26 | mainContainerDiv.appendChild(firstRootDiv); |
| 27 | const firstRoot = ReactDOMClient.createRoot(firstRootDiv); |
| 28 | await act(() => { |
| 29 | firstRoot.render(instanceOne); |
| 30 | }); |
| 31 | |
| 32 | const instanceTwo = <div className="secondReactDiv" />; |
| 33 | const secondRootDiv = document.createElement('div'); |
| 34 | mainContainerDiv.appendChild(secondRootDiv); |
| 35 | const secondRoot = ReactDOMClient.createRoot(secondRootDiv); |
| 36 | await act(() => { |
| 37 | secondRoot.render(instanceTwo); |
| 38 | }); |
| 39 | |
| 40 | // Test that two react roots are rendered in isolation |
| 41 | expect(firstRootDiv.firstChild.className).toBe('firstReactDiv'); |
| 42 | expect(secondRootDiv.firstChild.className).toBe('secondReactDiv'); |
| 43 | |
| 44 | // Test that after unmounting each, they are no longer in the document. |
| 45 | await act(() => { |
| 46 | firstRoot.unmount(); |
| 47 | }); |
| 48 | expect(firstRootDiv.firstChild).toBeNull(); |
| 49 | await act(() => { |
| 50 | secondRoot.unmount(); |
| 51 | }); |
| 52 | }); |
| 53 | |
| 54 | // @gate !disableLegacyMode |
| 55 | it('should warn when unmounting a non-container root node', () => { |
| 56 | const mainContainerDiv = document.createElement('div'); |
| 57 | |
| 58 | const component = ( |
| 59 | <div> |
| 60 | <div /> |
| 61 | </div> |
| 62 | ); |
| 63 | // Cannot be migrated to createRoot until we remove unmountComponentAtNode i.e. remove this test. |
| 64 | ReactDOM.render(component, mainContainerDiv); |
| 65 | |
| 66 | // Test that unmounting at a root node gives a helpful warning |
| 67 | const rootDiv = mainContainerDiv.firstChild; |
| 68 | ReactDOM.unmountComponentAtNode(rootDiv); |
| 69 | assertConsoleErrorDev([ |
| 70 | "unmountComponentAtNode(): The node you're attempting to " + |
| 71 | 'unmount was rendered by React and is not a top-level container. You ' + |
| 72 | 'may have accidentally passed in a React root node instead of its ' + |
| 73 | 'container.', |
| 74 | ]); |
| 75 | }); |
| 76 | |
| 77 | // @gate !disableLegacyMode |
| 78 | it('should warn when unmounting a non-container, non-root node', () => { |
| 79 | const mainContainerDiv = document.createElement('div'); |
| 80 | |
| 81 | const component = ( |
| 82 | <div> |
| 83 | <div> |
| 84 | <div /> |
| 85 | </div> |
| 86 | </div> |
| 87 | ); |
| 88 | // Cannot be migrated to createRoot until we remove unmountComponentAtNode i.e. remove this test. |
| 89 | ReactDOM.render(component, mainContainerDiv); |
| 90 | |
| 91 | // Test that unmounting at a non-root node gives a different warning |
| 92 | const nonRootDiv = mainContainerDiv.firstChild.firstChild; |
| 93 | ReactDOM.unmountComponentAtNode(nonRootDiv); |
| 94 | assertConsoleErrorDev([ |
| 95 | "unmountComponentAtNode(): The node you're attempting to " + |
| 96 | 'unmount was rendered by React and is not a top-level container. ' + |
| 97 | 'Instead, have the parent component update its state and rerender in ' + |
| 98 | 'order to remove this component.', |
| 99 | ]); |
| 100 | }); |
| 101 | }); |