From ec63982840f9ec0d7df2400b90f125ec2e1f6cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Wed, 17 Jul 2024 12:12:17 +0200 Subject: [PATCH] refactor: improve UX --- .../RuntimeTestsRunner.tsx | 263 +++++++++++++----- .../RuntimeTests/RuntimeTestsExample.tsx | 18 +- 2 files changed, 196 insertions(+), 85 deletions(-) diff --git a/apps/common-app/src/examples/RuntimeTests/ReanimatedRuntimeTestsRunner/RuntimeTestsRunner.tsx b/apps/common-app/src/examples/RuntimeTests/ReanimatedRuntimeTestsRunner/RuntimeTestsRunner.tsx index 162b810cf75..0a98617e7c8 100644 --- a/apps/common-app/src/examples/RuntimeTests/ReanimatedRuntimeTestsRunner/RuntimeTestsRunner.tsx +++ b/apps/common-app/src/examples/RuntimeTests/ReanimatedRuntimeTestsRunner/RuntimeTestsRunner.tsx @@ -1,16 +1,9 @@ -import { View, TouchableOpacity, StyleSheet, Text } from 'react-native'; +import { View, StyleSheet, Text, Pressable } from 'react-native'; import type { ReactNode } from 'react'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { runTests, configure } from './RuntimeTestsApi'; import { RenderLock } from './SyncUIRunner'; -interface ImportButton { - testSuiteName: string; - importTest: () => void; - skipByDefault?: boolean; -} - -let renderLock: RenderLock = new RenderLock(); export class ErrorBoundary extends React.Component< { children: React.JSX.Element | Array }, { hasError: boolean } @@ -34,77 +27,51 @@ export class ErrorBoundary extends React.Component< } } -function ImportButtons({ importButtons }: { importButtons: Array }) { - const [importedTests, setImportedTests] = useState>([]); - const [importedAll, setImportedAll] = useState(false); - - const handleImportAllClick = () => { - setImportedAll(true); - const newImportedTests = importedTests; - for (const button of importButtons) { - if (!button.skipByDefault) { - button.importTest(); - if (!importedTests.includes(button.testSuiteName)) { - newImportedTests.push(button.testSuiteName); - } - } - } - setImportedTests(newImportedTests); - }; +let renderLock: RenderLock = new RenderLock(); - const handleImportClick = (button: ImportButton) => { - button.importTest(); - if (!importedTests.includes(button.testSuiteName)) { - setImportedTests([...importedTests, button.testSuiteName]); - } - }; - return ( - - - Import all reanimated tests - - - - {importButtons.map(importButton => { - const { testSuiteName } = importButton; - return ( - handleImportClick(importButton)} - style={[styles.importButton, importedTests.includes(testSuiteName) ? styles.importButtonImported : {}]}> - {testSuiteName} - - ); - })} - - - ); +interface TestData { + testSuiteName: string; + importTest: () => void; + skipByDefault?: boolean; } -export default function RuntimeTestsRunner({ importButtons }: { importButtons: Array }) { +interface RuntimeTestRunnerProps { + tests: TestData[]; +} + +export default function RuntimeTestsRunner({ tests }: RuntimeTestRunnerProps) { const [component, setComponent] = useState(null); const [started, setStarted] = useState(false); + const testSelectionCallbacks = useRef void>>(new Set()); useEffect(() => { if (renderLock) { renderLock.unlock(); } }, [component]); + + async function run() { + renderLock = configure({ render: setComponent }); + await runTests(); + } + + function handleStartClick() { + testSelectionCallbacks.current.forEach(callback => callback()); + setStarted(true); + // eslint-disable-next-line no-void + void run(); + } + return ( - {started ? null : } - {started ? null : ( - { - setStarted(true); - renderLock = configure({ render: setComponent }); - await runTests(); - }} - style={styles.button}> - Run tests - + {started ? ( + Reload the app to run the tests again + ) : ( + <> + + + Run tests + + )} {/* Don't render anything if component is undefined to prevent blinking */} @@ -113,23 +80,142 @@ export default function RuntimeTestsRunner({ importButtons }: { importButtons: A ); } +interface TestSelectorProps { + tests: Array; + testSelectionCallbacks: React.RefObject void>>; +} + +function TestSelector({ tests, testSelectionCallbacks }: TestSelectorProps) { + const [selectedTests, setSelectedTests] = useState>( + tests.reduce((acc, testData) => { + acc.set(testData.testSuiteName, !testData.skipByDefault); + return acc; + }, new Map()), + ); + + function selectAllClick(select: boolean) { + tests.forEach(button => { + setSelectedTests(selectedTests => new Map(selectedTests.set(button.testSuiteName, select))); + if (select) { + testSelectionCallbacks.current!.add(button.importTest); + } else { + testSelectionCallbacks.current!.delete(button.importTest); + } + }); + } + + function selectClick(button: TestData) { + setSelectedTests(new Map(selectedTests.set(button.testSuiteName, !selectedTests.get(button.testSuiteName)))); + if (testSelectionCallbacks.current!.has(button.importTest)) { + testSelectionCallbacks.current!.delete(button.importTest); + } else { + testSelectionCallbacks.current!.add(button.importTest); + } + } + + return ( + + + + + + {tests.map(testData => { + return ( + selectClick(testData)} + selectedTests={selectedTests} + /> + ); + })} + + + ); +} + +interface SelectTestProps { + testSuiteName: string; + selectClick: () => void; + selectedTests: Map; +} + +function SelectTest({ testSuiteName, selectClick, selectedTests }: SelectTestProps) { + const [isPressed, setIsPressed] = useState(false); + + function handleSelectClickIn() { + setIsPressed(true); + } + + function handleSelectClickOut() { + selectClick(); + setIsPressed(false); + } + + return ( + handleSelectClickIn()} + onPressOut={() => handleSelectClickOut()}> + + + {testSuiteName} + + + ); +} + +interface SelectAllButtonProps { + handleSelectAllClick: (select: boolean) => void; + select: boolean; +} + +function SelectAllButtonProps({ handleSelectAllClick, select }: SelectAllButtonProps) { + const [isPressed, setIsPressed] = useState(false); + + function handleSelectAllClickIn() { + setIsPressed(true); + } + + function handleSelectAllClickOut() { + handleSelectAllClick(select); + setIsPressed(false); + } + + return ( + handleSelectAllClickOut()} + style={[styles.selectAllButton, isPressed ? styles.pressedButton : {}]}> + {select ? 'Select all' : 'Deselect all'} + + ); +} + const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'column', }, - importAllButton: { + selectAllButton: { + marginVertical: 5, marginHorizontal: 20, - marginTop: 20, + height: 40, + borderWidth: 2, + borderRadius: 10, + backgroundColor: 'white', + borderColor: 'navy', + justifyContent: 'center', + alignItems: 'center', }, - importButtonsFrame: { + selectButtonsFrame: { borderRadius: 10, backgroundColor: 'lightblue', margin: 20, - paddingHorizontal: 40, + paddingHorizontal: 10, paddingVertical: 10, }, - importButton: { + selectButton: { height: 40, borderWidth: 2, marginVertical: 5, @@ -138,15 +224,14 @@ const styles = StyleSheet.create({ borderColor: 'navy', justifyContent: 'center', alignItems: 'center', - }, - importButtonImported: { - backgroundColor: 'pink', + flex: 1, }, button: { height: 40, backgroundColor: 'navy', justifyContent: 'center', alignItems: 'center', + zIndex: 1, }, buttonText: { fontSize: 20, @@ -156,4 +241,30 @@ const styles = StyleSheet.create({ fontSize: 20, color: 'white', }, + buttonWrapper: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 10, + }, + checkbox: { + width: 20, + height: 20, + marginRight: 10, + borderWidth: 2, + backgroundColor: 'white', + }, + checkedCheckbox: { + backgroundColor: 'navy', + }, + reloadText: { + fontSize: 20, + color: 'navy', + alignSelf: 'center', + }, + pressedButton: { + zIndex: 2, + backgroundColor: '#FFFA', + borderRadius: 10, + borderColor: '#FFFF', + }, }); diff --git a/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx b/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx index 6df171e77c2..97a3a00961d 100644 --- a/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx +++ b/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx @@ -5,14 +5,7 @@ import { describe } from './ReanimatedRuntimeTestsRunner/RuntimeTestsApi'; export default function RuntimeTestsExample() { return ( { - require('./tests/TestsOfTestingFramework.test'); - }, - }, + tests={[ { testSuiteName: 'animations', importTest: () => { @@ -69,12 +62,19 @@ export default function RuntimeTestsExample() { }, }, { - testSuiteName: 'advancedAPI', + testSuiteName: 'advanced API', importTest: () => { require('./tests/advancedAPI/useFrameCallback.test'); // require('./tests/advancedAPI/measure.test'); // crash on Android }, }, + { + skipByDefault: true, + testSuiteName: 'self-tests', + importTest: () => { + require('./tests/TestsOfTestingFramework.test'); + }, + }, ]} /> );