From c8cc4f9c8b9fa091728bfdefcbfd2630ea80dd52 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 12 Aug 2024 13:50:11 +0200 Subject: [PATCH] chore(ui): update to react 18 (#32079) Part of https://github.com/microsoft/playwright/issues/31863. Updates most of our React usage to React 18. `recorder` doesn't seem to like it yet. I suspect that some of our code isn't compatible with concurrent mode, i've investigated that in https://github.com/microsoft/playwright/pull/32101. --------- Signed-off-by: Simon Knott Co-authored-by: Max Schmitt --- packages/html-reporter/src/index.tsx | 4 ++-- packages/trace-viewer/src/embedded.tsx | 4 ++-- packages/trace-viewer/src/index.tsx | 4 ++-- .../src/ui/uiModeTestListView.tsx | 4 ++-- packages/trace-viewer/src/ui/uiModeView.tsx | 19 ++++++++++--------- packages/trace-viewer/src/uiMode.tsx | 4 ++-- .../playwright-test/ui-mode-test-run.spec.ts | 4 +++- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/html-reporter/src/index.tsx b/packages/html-reporter/src/index.tsx index 3fedf4938af14..d34aa2b6400d1 100644 --- a/packages/html-reporter/src/index.tsx +++ b/packages/html-reporter/src/index.tsx @@ -19,7 +19,7 @@ import type zip from '@zip.js/zip.js'; // @ts-ignore import * as zipImport from '@zip.js/zip.js/lib/zip-no-worker-inflate.js'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import './colors.css'; import type { LoadedReport } from './loadedReport'; import { ReportView } from './reportView'; @@ -44,7 +44,7 @@ const ReportLoader: React.FC = () => { }; window.onload = () => { - ReactDOM.render(, document.querySelector('#root')); + ReactDOM.createRoot(document.querySelector('#root')!).render(); }; class ZipReport implements LoadedReport { diff --git a/packages/trace-viewer/src/embedded.tsx b/packages/trace-viewer/src/embedded.tsx index c916d295c1214..4f1503dcf27fe 100644 --- a/packages/trace-viewer/src/embedded.tsx +++ b/packages/trace-viewer/src/embedded.tsx @@ -17,7 +17,7 @@ import '@web/common.css'; import { applyTheme } from '@web/theme'; import '@web/third_party/vscode/codicon.css'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { EmbeddedWorkbenchLoader } from './ui/embeddedWorkbenchLoader'; (async () => { @@ -56,5 +56,5 @@ import { EmbeddedWorkbenchLoader } from './ui/embeddedWorkbenchLoader'; setInterval(function() { fetch('ping'); }, 10000); } - ReactDOM.render(, document.querySelector('#root')); + ReactDOM.createRoot(document.querySelector('#root')!).render(); })(); diff --git a/packages/trace-viewer/src/index.tsx b/packages/trace-viewer/src/index.tsx index 993b90a23d8d7..a737d9017f76c 100644 --- a/packages/trace-viewer/src/index.tsx +++ b/packages/trace-viewer/src/index.tsx @@ -17,7 +17,7 @@ import '@web/common.css'; import { applyTheme } from '@web/theme'; import '@web/third_party/vscode/codicon.css'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { WorkbenchLoader } from './ui/workbenchLoader'; (async () => { @@ -38,5 +38,5 @@ import { WorkbenchLoader } from './ui/workbenchLoader'; setInterval(function() { fetch('ping'); }, 10000); } - ReactDOM.render(, document.querySelector('#root')); + ReactDOM.createRoot(document.querySelector('#root')!).render(); })(); diff --git a/packages/trace-viewer/src/ui/uiModeTestListView.tsx b/packages/trace-viewer/src/ui/uiModeTestListView.tsx index 239de7d4b4693..b7a9d8e1d1d2c 100644 --- a/packages/trace-viewer/src/ui/uiModeTestListView.tsx +++ b/packages/trace-viewer/src/ui/uiModeTestListView.tsx @@ -40,7 +40,7 @@ export const TestListView: React.FC<{ testServerConnection: TestServerConnection | undefined, testModel?: TestModel, runTests: (mode: 'bounce-if-busy' | 'queue-if-busy', testIds: Set) => void, - runningState?: { testIds: Set, itemSelectedByUser?: boolean }, + runningState?: { testIds: Set, itemSelectedByUser?: boolean, completed?: boolean }, watchAll: boolean, watchedTreeIds: { value: Set }, setWatchedTreeIds: (ids: { value: Set }) => void, @@ -154,7 +154,7 @@ export const TestListView: React.FC<{ {!!treeItem.duration && treeItem.status !== 'skipped' &&
{msToString(treeItem.duration)}
} - runTreeItem(treeItem)} disabled={!!runningState}> + runTreeItem(treeItem)} disabled={!!runningState && !runningState.completed}> {!watchAll && { if (watchedTreeIds.value.has(treeItem.id)) diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 244a25ca5e2e7..b12617c3005ef 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -85,7 +85,9 @@ export const UIModeView: React.FC<{}> = ({ const [selectedItem, setSelectedItem] = React.useState<{ treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase }>({}); const [visibleTestIds, setVisibleTestIds] = React.useState>(new Set()); const [isLoading, setIsLoading] = React.useState(false); - const [runningState, setRunningState] = React.useState<{ testIds: Set, itemSelectedByUser?: boolean } | undefined>(); + const [runningState, setRunningState] = React.useState<{ testIds: Set, itemSelectedByUser?: boolean, completed?: boolean } | undefined>(); + const isRunningTest = runningState && !runningState.completed; + const [watchAll, setWatchAll] = useSetting('watch-all', false); const [watchedTreeIds, setWatchedTreeIds] = React.useState<{ value: Set }>({ value: new Set() }); const commandQueue = React.useRef(Promise.resolve()); @@ -251,29 +253,29 @@ export const UIModeView: React.FC<{}> = ({ // Update progress. React.useEffect(() => { - if (runningState && testModel?.progress) + if (isRunningTest && testModel?.progress) setProgress(testModel.progress); else if (!testModel) setProgress(undefined); - }, [testModel, runningState]); + }, [testModel, isRunningTest]); // Test tree is built from the model and filters. const { testTree } = React.useMemo(() => { if (!testModel) return { testTree: new TestTree('', new TeleSuite('', 'root'), [], projectFilters, pathSeparator) }; const testTree = new TestTree('', testModel.rootSuite, testModel.loadErrors, projectFilters, pathSeparator); - testTree.filterTree(filterText, statusFilters, runningState?.testIds); + testTree.filterTree(filterText, statusFilters, isRunningTest ? runningState?.testIds : undefined); testTree.sortAndPropagateStatus(); testTree.shortenRoot(); testTree.flattenForSingleProject(); setVisibleTestIds(testTree.testIds()); return { testTree }; - }, [filterText, testModel, statusFilters, projectFilters, setVisibleTestIds, runningState]); + }, [filterText, testModel, statusFilters, projectFilters, setVisibleTestIds, runningState, isRunningTest]); const runTests = React.useCallback((mode: 'queue-if-busy' | 'bounce-if-busy', testIds: Set) => { if (!testServerConnection || !testModel) return; - if (mode === 'bounce-if-busy' && runningState) + if (mode === 'bounce-if-busy' && isRunningTest) return; runTestBacklog.current = new Set([...runTestBacklog.current, ...testIds]); @@ -320,9 +322,9 @@ export const UIModeView: React.FC<{}> = ({ test.results = []; } setTestModel({ ...testModel }); - setRunningState(undefined); + setRunningState(oldState => oldState ? ({ ...oldState, completed: true }) : undefined); }); - }, [projectFilters, runningState, testModel, testServerConnection, runWorkers, runHeaded, runUpdateSnapshots]); + }, [projectFilters, isRunningTest, testModel, testServerConnection, runWorkers, runHeaded, runUpdateSnapshots]); React.useEffect(() => { if (!testServerConnection || !teleSuiteUpdater) @@ -396,7 +398,6 @@ export const UIModeView: React.FC<{}> = ({ }; }, [runTests, reloadTests, testServerConnection, visibleTestIds, isShowingOutput]); - const isRunningTest = !!runningState; const dialogRef = React.useRef(null); const openInstallDialog = React.useCallback((e: React.MouseEvent) => { e.preventDefault(); diff --git a/packages/trace-viewer/src/uiMode.tsx b/packages/trace-viewer/src/uiMode.tsx index f2f846a1126af..5dac2082e81ee 100644 --- a/packages/trace-viewer/src/uiMode.tsx +++ b/packages/trace-viewer/src/uiMode.tsx @@ -17,7 +17,7 @@ import '@web/common.css'; import { applyTheme } from '@web/theme'; import '@web/third_party/vscode/codicon.css'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { UIModeView } from './ui/uiModeView'; (async () => { @@ -38,5 +38,5 @@ import { UIModeView } from './ui/uiModeView'; setInterval(function() { fetch('ping'); }, 10000); } - ReactDOM.render(, document.querySelector('#root')); + ReactDOM.createRoot(document.querySelector('#root')!).render(); })(); diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts index 2d41aa18d435e..9c1120ea04b7d 100644 --- a/tests/playwright-test/ui-mode-test-run.spec.ts +++ b/tests/playwright-test/ui-mode-test-run.spec.ts @@ -174,7 +174,9 @@ test('should run by project', async ({ runUITest }) => { await expect.poll(dumpTestTree(page)).toBe(` ▼ ❌ a.test.ts ► ◯ passes - ► ❌ fails <= + ▼ ❌ fails + ❌ foo <= + ◯ bar ► ❌ suite ▼ ❌ b.test.ts ► ◯ passes