Example #1
0
  it('supports hydration', async () => {
    const markup = await new Promise(resolve =>
      resolve(
        ReactDOMServer.renderToString(
          <div>
            <span className="extra" />
          </div>,
        ),
      ),
    );

    // Does not hydrate by default
    const container1 = document.createElement('div');
    container1.innerHTML = markup;
    const root1 = ReactDOM.unstable_createRoot(container1);
    root1.render(
      <div>
        <span />
      </div>,
    );
    Scheduler.flushAll();

    // Accepts `hydrate` option
    const container2 = document.createElement('div');
    container2.innerHTML = markup;
    const root2 = ReactDOM.unstable_createRoot(container2, {hydrate: true});
    root2.render(
      <div>
        <span />
      </div>,
    );
    expect(() => Scheduler.flushAll()).toWarnDev('Extra attributes', {
      withoutStack: true,
    });
  });
Example #2
0
 it('warns when unmounting with legacy API (no previous content)', () => {
   const root = ReactDOM.unstable_createRoot(container);
   root.render(<div>Hi</div>);
   Scheduler.flushAll();
   expect(container.textContent).toEqual('Hi');
   let unmounted = false;
   expect(() => {
     unmounted = ReactDOM.unmountComponentAtNode(container);
   }).toWarnDev(
     [
       // We care about this warning:
       'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
         'passed to ReactDOM.unstable_createRoot(). This is not supported. Did you mean to call root.unmount()?',
       // This is more of a symptom but restructuring the code to avoid it isn't worth it:
       "The node you're attempting to unmount was rendered by React and is not a top-level container.",
     ],
     {withoutStack: true},
   );
   expect(unmounted).toBe(false);
   Scheduler.flushAll();
   expect(container.textContent).toEqual('Hi');
   root.unmount();
   Scheduler.flushAll();
   expect(container.textContent).toEqual('');
 });
Example #3
0
  it('applies setState in componentDidMount synchronously in a batch', done => {
    class App extends React.Component {
      state = {mounted: false};
      componentDidMount() {
        this.setState({
          mounted: true,
        });
      }
      render() {
        return this.state.mounted ? 'Hi' : 'Bye';
      }
    }

    const root = ReactDOM.unstable_createRoot(container);
    const batch = root.createBatch();
    batch.render(<App />);

    Scheduler.flushAll();

    // Hasn't updated yet
    expect(container.textContent).toEqual('');

    let ops = [];
    batch.then(() => {
      // Still hasn't updated
      ops.push(container.textContent);

      // Should synchronously commit
      batch.commit();
      ops.push(container.textContent);

      expect(ops).toEqual(['', 'Hi']);
      done();
    });
  });
Example #4
0
it('works with createRoot().render combo', () => {
  const root = ReactDOM.unstable_createRoot(document.createElement('div'));
  TestRenderer.act(() => {
    root.render(<App />);
  });
  confirmWarning();
});
Example #5
0
 it('unmounts children', () => {
   const root = ReactDOM.unstable_createRoot(container);
   root.render(<div>Hi</div>);
   Scheduler.flushAll();
   expect(container.textContent).toEqual('Hi');
   root.unmount();
   Scheduler.flushAll();
   expect(container.textContent).toEqual('');
 });
Example #6
0
 it('unmounts children', () => {
   const root = ReactDOM.unstable_createRoot(container);
   root.render(<div>Hi</div>);
   jest.runAllTimers();
   expect(container.textContent).toEqual('Hi');
   root.unmount();
   jest.runAllTimers();
   expect(container.textContent).toEqual('');
 });
Example #7
0
 it('can defer a commit by batching it', () => {
   const root = ReactDOM.unstable_createRoot(container);
   const batch = root.createBatch();
   batch.render(<div>Hi</div>);
   // Hasn't committed yet
   expect(container.textContent).toEqual('');
   // Commit
   batch.commit();
   expect(container.textContent).toEqual('Hi');
 });
Example #8
0
 it('handles fatal errors triggered by batch.commit()', () => {
   const root = ReactDOM.unstable_createRoot(container);
   const batch = root.createBatch();
   const InvalidType = undefined;
   expect(() => batch.render(<InvalidType />)).toWarnDev(
     ['React.createElement: type is invalid'],
     {withoutStack: true},
   );
   expect(() => batch.commit()).toThrow('Element type is invalid');
 });
Example #9
0
 it('resolves `work.then` callback synchronously if the work already committed', () => {
   const root = ReactDOM.unstable_createRoot(container);
   const work = root.render(<AsyncMode>Hi</AsyncMode>);
   jest.runAllTimers();
   let ops = [];
   work.then(() => {
     ops.push('inside callback');
   });
   expect(ops).toEqual(['inside callback']);
 });
Example #10
0
 it('resolves `work.then` callback synchronously if the work already committed', () => {
   const root = ReactDOM.unstable_createRoot(container);
   const work = root.render('Hi');
   Scheduler.flushAll();
   let ops = [];
   work.then(() => {
     ops.push('inside callback');
   });
   expect(ops).toEqual(['inside callback']);
 });
    it('is async for non-input events', () => {
      const root = ReactDOM.unstable_createRoot(container);
      let input;

      let ops = [];

      class ControlledInput extends React.Component {
        state = {value: 'initial'};
        onChange = event => this.setState({value: event.target.value});
        reset = () => {
          this.setState({value: ''});
        };
        render() {
          ops.push(`render: ${this.state.value}`);
          const controlledValue =
            this.state.value === 'changed' ? 'changed [!]' : this.state.value;
          return (
            <input
              ref={el => (input = el)}
              type="text"
              value={controlledValue}
              onChange={this.onChange}
              onClick={this.reset}
            />
          );
        }
      }

      // Initial mount. Test that this is async.
      root.render(<ControlledInput />);
      // Should not have flushed yet.
      expect(ops).toEqual([]);
      expect(input).toBe(undefined);
      // Flush callbacks.
      jest.runAllTimers();
      expect(ops).toEqual(['render: initial']);
      expect(input.value).toBe('initial');

      ops = [];

      // Trigger a click event
      input.dispatchEvent(
        new Event('click', {bubbles: true, cancelable: true}),
      );
      // Nothing should have changed
      expect(ops).toEqual([]);
      expect(input.value).toBe('initial');

      // Flush callbacks.
      jest.runAllTimers();
      // Now the click update has flushed.
      expect(ops).toEqual(['render: ']);
      expect(input.value).toBe('');
    });
    it('parent of input', () => {
      const root = ReactDOM.unstable_createRoot(container);
      let input;

      let ops = [];

      class ControlledInput extends React.Component {
        state = {value: 'initial'};
        onChange = event => this.setState({value: event.target.value});
        render() {
          ops.push(`render: ${this.state.value}`);
          const controlledValue =
            this.state.value === 'changed' ? 'changed [!]' : this.state.value;
          return (
            <div onChange={this.onChange}>
              <input
                ref={el => (input = el)}
                type="text"
                value={controlledValue}
                onChange={() => {
                  // Does nothing. Parent handler is reponsible for updating.
                }}
              />
            </div>
          );
        }
      }

      // Initial mount. Test that this is async.
      root.render(<ControlledInput />);
      // Should not have flushed yet.
      expect(ops).toEqual([]);
      expect(input).toBe(undefined);
      // Flush callbacks.
      jest.runAllTimers();
      expect(ops).toEqual(['render: initial']);
      expect(input.value).toBe('initial');

      ops = [];

      // Trigger a change event.
      setUntrackedValue.call(input, 'changed');
      input.dispatchEvent(
        new Event('input', {bubbles: true, cancelable: true}),
      );
      // Change should synchronously flush
      expect(ops).toEqual(['render: changed']);
      // Value should be the controlled value, not the original one
      expect(input.value).toBe('changed [!]');
    });
Example #13
0
  it('can commit an empty batch', () => {
    const root = ReactDOM.unstable_createRoot(container);
    root.render(<AsyncMode>1</AsyncMode>);

    advanceCurrentTime(2000);
    // This batch has a later expiration time than the earlier update.
    const batch = root.createBatch();

    // This should not flush the earlier update.
    batch.commit();
    expect(container.textContent).toEqual('');

    jest.runAllTimers();
    expect(container.textContent).toEqual('1');
  });
Example #14
0
  it('can commit an empty batch', () => {
    const root = ReactDOM.unstable_createRoot(container);
    root.render(1);

    Scheduler.advanceTime(2000);
    // This batch has a later expiration time than the earlier update.
    const batch = root.createBatch();

    // This should not flush the earlier update.
    batch.commit();
    expect(container.textContent).toEqual('');

    Scheduler.flushAll();
    expect(container.textContent).toEqual('1');
  });
Example #15
0
  it('two batches created simultaneously are committed separately', () => {
    // (In other words, they have distinct expiration times)
    const root = ReactDOM.unstable_createRoot(container);
    const batch1 = root.createBatch();
    batch1.render(1);
    const batch2 = root.createBatch();
    batch2.render(2);

    expect(container.textContent).toEqual('');

    batch1.commit();
    expect(container.textContent).toEqual('1');

    batch2.commit();
    expect(container.textContent).toEqual('2');
  });
Example #16
0
 it('`root.render` returns a thenable work object', () => {
   const root = ReactDOM.unstable_createRoot(container);
   const work = root.render('Hi');
   let ops = [];
   work.then(() => {
     ops.push('inside callback: ' + container.textContent);
   });
   ops.push('before committing: ' + container.textContent);
   Scheduler.flushAll();
   ops.push('after committing: ' + container.textContent);
   expect(ops).toEqual([
     'before committing: ',
     // `then` callback should fire during commit phase
     'inside callback: Hi',
     'after committing: Hi',
   ]);
 });
Example #17
0
  it('commits an earlier batch without committing a later batch', () => {
    const root = ReactDOM.unstable_createRoot(container);
    const batch1 = root.createBatch();
    batch1.render(1);

    // This batch has a later expiration time
    Scheduler.advanceTime(2000);
    const batch2 = root.createBatch();
    batch2.render(2);

    expect(container.textContent).toEqual('');

    batch1.commit();
    expect(container.textContent).toEqual('1');

    batch2.commit();
    expect(container.textContent).toEqual('2');
  });
Example #18
0
 it('warns when unmounting with legacy API (has previous content)', () => {
   // Currently createRoot().render() doesn't clear this.
   container.appendChild(document.createElement('div'));
   // The rest is the same as test above.
   const root = ReactDOM.unstable_createRoot(container);
   root.render(<div>Hi</div>);
   Scheduler.flushAll();
   expect(container.textContent).toEqual('Hi');
   let unmounted = false;
   expect(() => {
     unmounted = ReactDOM.unmountComponentAtNode(container);
   }).toWarnDev('Did you mean to call root.unmount()?', {withoutStack: true});
   expect(unmounted).toBe(false);
   Scheduler.flushAll();
   expect(container.textContent).toEqual('Hi');
   root.unmount();
   Scheduler.flushAll();
   expect(container.textContent).toEqual('');
 });
Example #19
0
 it('warns when hydrating with legacy API into createRoot() container', () => {
   const root = ReactDOM.unstable_createRoot(container);
   root.render(<div>Hi</div>);
   Scheduler.flushAll();
   expect(container.textContent).toEqual('Hi');
   expect(() => {
     ReactDOM.hydrate(<div>Hi</div>, container);
   }).toWarnDev(
     [
       // We care about this warning:
       'You are calling ReactDOM.hydrate() on a container that was previously ' +
         'passed to ReactDOM.unstable_createRoot(). This is not supported. ' +
         'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
       // This is more of a symptom but restructuring the code to avoid it isn't worth it:
       'Replacing React-rendered children with a new root component.',
     ],
     {withoutStack: true},
   );
 });
Example #20
0
 it('does not clear existing children', async () => {
   container.innerHTML = '<div>a</div><div>b</div>';
   const root = ReactDOM.unstable_createRoot(container);
   root.render(
     <div>
       <span>c</span>
       <span>d</span>
     </div>,
   );
   Scheduler.flushAll();
   expect(container.textContent).toEqual('abcd');
   root.render(
     <div>
       <span>d</span>
       <span>c</span>
     </div>,
   );
   Scheduler.flushAll();
   expect(container.textContent).toEqual('abdc');
 });
Example #21
0
  it('can wait for a batch to finish', () => {
    const root = ReactDOM.unstable_createRoot(container);
    const batch = root.createBatch();
    batch.render('Foo');

    Scheduler.flushAll();

    // Hasn't updated yet
    expect(container.textContent).toEqual('');

    let ops = [];
    batch.then(() => {
      // Still hasn't updated
      ops.push(container.textContent);
      // Should synchronously commit
      batch.commit();
      ops.push(container.textContent);
    });

    expect(ops).toEqual(['', 'Foo']);
  });
Example #22
0
  it('does not restart a completed batch when committing if there were no intervening updates', () => {
    let ops = [];
    function Foo(props) {
      ops.push('Foo');
      return props.children;
    }
    const root = ReactDOM.unstable_createRoot(container);
    const batch = root.createBatch();
    batch.render(<Foo>Hi</Foo>);
    // Flush all async work.
    Scheduler.flushAll();
    // Root should complete without committing.
    expect(ops).toEqual(['Foo']);
    expect(container.textContent).toEqual('');

    ops = [];

    // Commit. Shouldn't re-render Foo.
    batch.commit();
    expect(ops).toEqual([]);
    expect(container.textContent).toEqual('Hi');
  });
import React, { Placeholder } from "react";
import "./lib/index.css";
import App from "./App.start";
import { unstable_createRoot } from "react-dom";

unstable_createRoot(document.getElementById("root")).render(
  <Placeholder delayMs={1000} fallback={<div>Loading...</div>}>
    <App />
  </Placeholder>
);
Example #24
0
 expect(() => {
   ReactDOM.unstable_createRoot(<div>Hi</div>);
 }).toThrow(
Example #25
0
                      width: progress + '%',
                      position: 'absolute',
                      left: 0,
                      top: 0,
                      backgroundColor:
                        progress !== 100 ? '#61dafb' : 'lightgreen',
                      zIndex: -1,
                      opacity: 0.8,
                    }}
                  />
                  <div
                    style={{
                      fontFamily: 'monospace',
                      fontWeight: 'bold',
                      color: 'black',
                    }}>
                    {url}
                  </div>
                </div>
              );
            })}
        </div>
      </Draggable>
    );
  }
}

unstable_createRoot(document.getElementById('root')).render(<Shell />);

render(<Debugger />, document.getElementById('debugger'));
    it('checkbox input', () => {
      const root = ReactDOM.unstable_createRoot(container);
      let input;

      let ops = [];

      class ControlledInput extends React.Component {
        state = {checked: false};
        onChange = event => {
          this.setState({checked: event.target.checked});
        };
        render() {
          ops.push(`render: ${this.state.checked}`);
          const controlledValue = this.props.reverse
            ? !this.state.checked
            : this.state.checked;
          return (
            <input
              ref={el => (input = el)}
              type="checkbox"
              checked={controlledValue}
              onChange={this.onChange}
            />
          );
        }
      }

      // Initial mount. Test that this is async.
      root.render(<ControlledInput reverse={false} />);
      // Should not have flushed yet.
      expect(ops).toEqual([]);
      expect(input).toBe(undefined);
      // Flush callbacks.
      jest.runAllTimers();
      expect(ops).toEqual(['render: false']);
      expect(input.checked).toBe(false);

      ops = [];

      // Trigger a change event.
      input.dispatchEvent(
        new MouseEvent('click', {bubbles: true, cancelable: true}),
      );
      // Change should synchronously flush
      expect(ops).toEqual(['render: true']);
      expect(input.checked).toBe(true);

      // Now let's make sure we're using the controlled value.
      root.render(<ControlledInput reverse={true} />);
      jest.runAllTimers();

      ops = [];

      // Trigger another change event.
      input.dispatchEvent(
        new MouseEvent('click', {bubbles: true, cancelable: true}),
      );
      // Change should synchronously flush
      expect(ops).toEqual(['render: true']);
      expect(input.checked).toBe(false);
    });
Example #27
0
 expect(() => {
   ReactDOM.unstable_createRoot(container);
 }).toWarnDev(