/** * Copyright (c) Meta Platforms, Inc. and 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 ReactDOMClient; let Scheduler; let act; let waitForAll; let waitForDiscrete; let assertLog; const setUntrackedChecked = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'checked', ).set; const setUntrackedValue = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'value', ).set; const setUntrackedTextareaValue = Object.getOwnPropertyDescriptor( HTMLTextAreaElement.prototype, 'value', ).set; describe('ChangeEventPlugin', () => { let container; beforeEach(() => { jest.resetModules(); // TODO pull this into helper method, reduce repetition. // mock the browser APIs which are used in schedule: // - calling 'window.postMessage' should actually fire postmessage handlers const originalAddEventListener = global.addEventListener; let postMessageCallback; global.addEventListener = function (eventName, callback, useCapture) { if (eventName === 'message') { postMessageCallback = callback; } else { originalAddEventListener(eventName, callback, useCapture); } }; global.postMessage = function (messageKey, targetOrigin) { const postMessageEvent = {source: window, data: messageKey}; if (postMessageCallback) { postMessageCallback(postMessageEvent); } }; React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); act = require('internal-test-utils').act; Scheduler = require('scheduler'); const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; waitForDiscrete = InternalTestUtils.waitForDiscrete; assertLog = InternalTestUtils.assertLog; container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { document.body.removeChild(container); container = null; }); // We try to avoid firing "duplicate" React change events. // However, to tell which events are "duplicates" and should be ignored, // we are tracking the "current" input value, and only respect events // that occur after it changes. In most of these tests, we verify that we // 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', async () => { let called = 0; function cb(e) { called++; expect(e.type).toBe('change'); } const root = ReactDOMClient.createRoot(container); await act(() => { root.render(); }); 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)', async () => { let called = 0; function cb(e) { called++; expect(e.type).toBe('change'); } const root = ReactDOMClient.createRoot(container); await act(() => { root.render( , ); }); 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', async () => { let called = 0; function cb(e) { called++; expect(e.type).toBe('change'); } const root = ReactDOMClient.createRoot(container); await act(() => { root.render(