From bcce235841366fe19a2d301377777192e383523a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 26 Mar 2025 11:09:42 -0700 Subject: [PATCH] Add basic tests for Event Timing API (#50289) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50289 Changelog: [internal] Adding basic Fantom tests for the new Event Timing API. Reviewed By: huntie Differential Revision: D71734778 fbshipit-source-id: b5da3162fade64eadede72346af50df68b4facd9 --- .../__tests__/EventTimingAPI-itest.js | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js new file mode 100644 index 00000000000..80684afa3b6 --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js @@ -0,0 +1,192 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall react_native + * @fantom_flags enableReportEventPaintTime:true + */ + +import 'react-native/Libraries/Core/InitializeCore'; + +import type {PerformanceObserverEntryList} from 'react-native/src/private/webapis/performance/PerformanceObserver'; + +import * as Fantom from '@react-native/fantom'; +import nullthrows from 'nullthrows'; +import {useState} from 'react'; +import {Text, View} from 'react-native'; +import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver'; +import {PerformanceEventTiming} from 'react-native/src/private/webapis/performance/EventTiming'; +import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver'; + +setUpPerformanceObserver(); + +function sleep(ms: number) { + const end = performance.now() + ms; + while (performance.now() < end) {} +} + +function ensurePerformanceEventTiming(value: mixed): PerformanceEventTiming { + if (!(value instanceof PerformanceEventTiming)) { + throw new Error( + `Expected instance of PerformanceEventTiming but got ${String(value)}`, + ); + } + + return value; +} + +describe('Event Timing API', () => { + it('reports events without event handlers or updates', () => { + const callback = jest.fn(); + + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['event']}); + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(callback).not.toHaveBeenCalled(); + + Fantom.dispatchNativeEvent(element, 'click'); + + expect(callback).toHaveBeenCalledTimes(1); + + const entryList = callback.mock.lastCall[0] as PerformanceObserverEntryList; + const entries = entryList.getEntries(); + + expect(entries.length).toBe(1); + + const entry = ensurePerformanceEventTiming(entries[0]); + + expect(entry.entryType).toBe('event'); + expect(entry.name).toBe('click'); + + expect(entry.startTime).toBeGreaterThanOrEqual(0); + expect(entry.processingStart).toBeGreaterThan(entry.startTime); + expect(entry.processingEnd).toBeGreaterThanOrEqual(entry.processingStart); + + expect(entry.duration).toBeGreaterThanOrEqual(0); + expect(entry.duration).toBeGreaterThan( + entry.processingEnd - entry.startTime, + ); + + expect(entry.interactionId).toBeGreaterThanOrEqual(0); + }); + + it('reports events with handlers but no updates', () => { + const callback = jest.fn(); + + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['event']}); + + const SIMULATED_PROCESSING_DELAY = 50; + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render( + { + sleep(SIMULATED_PROCESSING_DELAY); + }} + />, + ); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(callback).not.toHaveBeenCalled(); + + Fantom.dispatchNativeEvent(element, 'click'); + + expect(callback).toHaveBeenCalledTimes(1); + + const entryList = callback.mock.lastCall[0] as PerformanceObserverEntryList; + const entries = entryList.getEntries(); + + expect(entries.length).toBe(1); + + const entry = ensurePerformanceEventTiming(entries[0]); + + expect(entry.entryType).toBe('event'); + expect(entry.name).toBe('click'); + + expect(entry.startTime).toBeGreaterThanOrEqual(0); + expect(entry.processingStart).toBeGreaterThan(entry.startTime); + expect(entry.processingEnd).toBeGreaterThanOrEqual(entry.processingStart); + + expect(entry.processingEnd - entry.processingStart).toBeGreaterThanOrEqual( + SIMULATED_PROCESSING_DELAY, + ); + + expect(entry.duration).toBeGreaterThanOrEqual(0); + expect(entry.duration).toBeGreaterThan( + entry.processingEnd - entry.startTime, + ); + + expect(entry.interactionId).toBeGreaterThanOrEqual(0); + }); + + it('reports events with updates', () => { + const callback = jest.fn(); + + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['event']}); + + function MyComponent() { + const [count, setCount] = useState(0); + + return ( + { + setCount(count + 1); + }}> + {count} + + ); + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(callback).not.toHaveBeenCalled(); + + Fantom.dispatchNativeEvent(element, 'click'); + + expect(callback).toHaveBeenCalledTimes(1); + + const entryList = callback.mock.lastCall[0] as PerformanceObserverEntryList; + const entries = entryList.getEntries(); + + expect(entries.length).toBe(1); + + const entry = ensurePerformanceEventTiming(entries[0]); + + expect(entry.entryType).toBe('event'); + expect(entry.name).toBe('click'); + + expect(entry.startTime).toBeGreaterThanOrEqual(0); + expect(entry.processingStart).toBeGreaterThan(entry.startTime); + expect(entry.processingEnd).toBeGreaterThanOrEqual(entry.processingStart); + + expect(entry.duration).toBeGreaterThanOrEqual(0); + expect(entry.duration).toBeGreaterThan( + entry.processingEnd - entry.startTime, + ); + + // TODO: When Fantom provides structured data from mounting manager, add timestamp to operations and verify that the duration includes that. + + expect(entry.interactionId).toBeGreaterThanOrEqual(0); + }); +});