Skip to content

Commit

Permalink
Convert ChangeEventPlugin to createRoot
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Silbermann committed Jan 25, 2024
1 parent 7f11d79 commit 808ac42
Showing 1 changed file with 118 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,92 +83,106 @@ describe('ChangeEventPlugin', () => {
// keep track of the "current" value and only fire events when it changes.
// See https://github.com/facebook/react/pull/5746.

it('should consider initial text value to be current', () => {
it('should consider initial text value to be current', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<input type="text" onChange={cb} defaultValue="foo" />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="text" onChange={cb} defaultValue="foo" />);
});

const node = container.firstChild;
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));

// There should be no React change events because the value stayed the same.
expect(called).toBe(0);
});

it('should consider initial text value to be current (capture)', () => {
it('should consider initial text value to be current (capture)', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<input type="text" onChangeCapture={cb} defaultValue="foo" />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<input type="text" onChangeCapture={cb} defaultValue="foo" />,
);
});

const node = container.firstChild;
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));

// There should be no React change events because the value stayed the same.
expect(called).toBe(0);
});

it('should not invoke a change event for textarea same value', () => {
it('should not invoke a change event for textarea same value', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<textarea onChange={cb} defaultValue="initial" />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<textarea onChange={cb} defaultValue="initial" />);
});

const node = container.firstChild;
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
// There should be no React change events because the value stayed the same.
expect(called).toBe(0);
});

it('should not invoke a change event for textarea same value (capture)', () => {
it('should not invoke a change event for textarea same value (capture)', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<textarea onChangeCapture={cb} defaultValue="initial" />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<textarea onChangeCapture={cb} defaultValue="initial" />);
});

const node = container.firstChild;
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
// There should be no React change events because the value stayed the same.
expect(called).toBe(0);
});

it('should consider initial checkbox checked=true to be current', () => {
it('should consider initial checkbox checked=true to be current', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<input type="checkbox" onChange={cb} defaultChecked={true} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<input type="checkbox" onChange={cb} defaultChecked={true} />,
);
});

const node = container.firstChild;

// Secretly, set `checked` to false, so that dispatching the `click` will
// make it `true` again. Thus, at the time of the event, React should not
Expand All @@ -181,18 +195,22 @@ describe('ChangeEventPlugin', () => {
expect(called).toBe(0);
});

it('should consider initial checkbox checked=false to be current', () => {
it('should consider initial checkbox checked=false to be current', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<input type="checkbox" onChange={cb} defaultChecked={false} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<input type="checkbox" onChange={cb} defaultChecked={false} />,
);
});

const node = container.firstChild;

// Secretly, set `checked` to true, so that dispatching the `click` will
// make it `false` again. Thus, at the time of the event, React should not
Expand All @@ -205,18 +223,20 @@ describe('ChangeEventPlugin', () => {
expect(called).toBe(0);
});

it('should fire change for checkbox input', () => {
it('should fire change for checkbox input', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const node = ReactDOM.render(
<input type="checkbox" onChange={cb} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="checkbox" onChange={cb} />);
});

const node = container.firstChild;

expect(node.checked).toBe(false);
node.dispatchEvent(
Expand All @@ -235,18 +255,20 @@ describe('ChangeEventPlugin', () => {
expect(called).toBe(2);
});

it('should not fire change setting the value programmatically', () => {
it('should not fire change setting the value programmatically', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const input = ReactDOM.render(
<input type="text" defaultValue="foo" onChange={cb} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="text" defaultValue="foo" onChange={cb} />);
});

const input = container.firstChild;

// Set it programmatically.
input.value = 'bar';
Expand All @@ -271,18 +293,20 @@ describe('ChangeEventPlugin', () => {
expect(called).toBe(1);
});

it('should not distinguish equal string and number values', () => {
it('should not distinguish equal string and number values', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const input = ReactDOM.render(
<input type="text" defaultValue="42" onChange={cb} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="text" defaultValue="42" onChange={cb} />);
});

const input = container.firstChild;

// When we set `value` as a property, React updates the "current" value
// that it tracks internally. The "current" value is later used to determine
Expand All @@ -296,18 +320,22 @@ describe('ChangeEventPlugin', () => {
});

// See a similar input test above for a detailed description of why.
it('should not fire change when setting checked programmatically', () => {
it('should not fire change when setting checked programmatically', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const input = ReactDOM.render(
<input type="checkbox" onChange={cb} defaultChecked={false} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<input type="checkbox" onChange={cb} defaultChecked={false} />,
);
});

const input = container.firstChild;

// Set the value, updating the "current" value that React tracks to true.
input.checked = true;
Expand All @@ -326,32 +354,39 @@ describe('ChangeEventPlugin', () => {
expect(called).toBe(1);
});

it('should unmount', () => {
const input = ReactDOM.render(<input />, container);
it('should unmount', async () => {
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input />);
});

const input = container.firstChild;

ReactDOM.unmountComponentAtNode(container);
});

it('should only fire change for checked radio button once', () => {
it('should only fire change for checked radio button once', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const input = ReactDOM.render(
<input type="radio" onChange={cb} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="radio" onChange={cb} />);
});

const input = container.firstChild;

setUntrackedChecked.call(input, true);
input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
input.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
expect(called).toBe(1);
});

it('should track radio button cousins in a group', () => {
it('should track radio button cousins in a group', async () => {
let called1 = 0;
let called2 = 0;

Expand All @@ -365,13 +400,17 @@ describe('ChangeEventPlugin', () => {
expect(e.type).toBe('change');
}

const div = ReactDOM.render(
<div>
<input type="radio" name="group" onChange={cb1} />
<input type="radio" name="group" onChange={cb2} />
</div>,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<div>
<input type="radio" name="group" onChange={cb1} />
<input type="radio" name="group" onChange={cb2} />
</div>,
);
});

const div = container.firstChild;
const option1 = div.childNodes[0];
const option2 = div.childNodes[1];

Expand Down Expand Up @@ -455,18 +494,20 @@ describe('ChangeEventPlugin', () => {
});
});

it('should listen for both change and input events when supported', () => {
it('should listen for both change and input events when supported', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const input = ReactDOM.render(
<input type="range" onChange={cb} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="range" onChange={cb} />);
});

const input = container.firstChild;

setUntrackedValue.call(input, 10);
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
Expand All @@ -477,18 +518,20 @@ describe('ChangeEventPlugin', () => {
expect(called).toBe(2);
});

it('should only fire events when the value changes for range inputs', () => {
it('should only fire events when the value changes for range inputs', async () => {
let called = 0;

function cb(e) {
called++;
expect(e.type).toBe('change');
}

const input = ReactDOM.render(
<input type="range" onChange={cb} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<input type="range" onChange={cb} />);
});

const input = container.firstChild;
setUntrackedValue.call(input, '40');
input.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
Expand Down

0 comments on commit 808ac42

Please sign in to comment.