From d2c42bbff312098a12fc5a7cb78a0b31f4d1a616 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 27 Feb 2024 10:49:43 +0100 Subject: [PATCH 01/21] migrate config.dev.js to TypeScript --- tests/e2e/{config.dev.js => config.dev.ts} | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename tests/e2e/{config.dev.js => config.dev.ts} (76%) diff --git a/tests/e2e/config.dev.js b/tests/e2e/config.dev.ts similarity index 76% rename from tests/e2e/config.dev.js rename to tests/e2e/config.dev.ts index 0e5d3dc01a95..a930c2e21f06 100644 --- a/tests/e2e/config.dev.js +++ b/tests/e2e/config.dev.ts @@ -1,7 +1,9 @@ const packageName = 'com.expensify.chat.dev'; const appPath = './android/app/build/outputs/apk/development/debug/app-development-debug.apk'; -export default { +type Config = Record; + +const config: Config = { MAIN_APP_PACKAGE: packageName, DELTA_APP_PACKAGE: packageName, MAIN_APP_PATH: appPath, @@ -9,3 +11,5 @@ export default { RUNS: 8, BOOT_COOL_DOWN: 5 * 1000, }; + +export default config; From e01aa68ec912676b32003de490b74de682a2f8f4 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 27 Feb 2024 10:50:17 +0100 Subject: [PATCH 02/21] migrate setupAfterEnv to TypeScript --- tests/perf-test/{setupAfterEnv.js => setupAfterEnv.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/perf-test/{setupAfterEnv.js => setupAfterEnv.ts} (100%) diff --git a/tests/perf-test/setupAfterEnv.js b/tests/perf-test/setupAfterEnv.ts similarity index 100% rename from tests/perf-test/setupAfterEnv.js rename to tests/perf-test/setupAfterEnv.ts From 04c50675dff8c0822efbe4ad94f00d8882a9bdd1 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 27 Feb 2024 10:51:37 +0100 Subject: [PATCH 03/21] migrate e2e server to TypeScript --- tests/e2e/server/{index.js => index.ts} | 58 ++++++++++++++--------- tests/e2e/server/{routes.js => routes.ts} | 2 +- 2 files changed, 36 insertions(+), 24 deletions(-) rename tests/e2e/server/{index.js => index.ts} (76%) rename tests/e2e/server/{routes.js => routes.ts} (98%) diff --git a/tests/e2e/server/index.js b/tests/e2e/server/index.ts similarity index 76% rename from tests/e2e/server/index.js rename to tests/e2e/server/index.ts index 82152245d8e2..e0c71c6cf54b 100644 --- a/tests/e2e/server/index.js +++ b/tests/e2e/server/index.ts @@ -1,13 +1,28 @@ +import type {IncomingMessage, ServerResponse} from 'http'; import {createServer} from 'http'; +import type {TestConfig} from '@libs/E2E/types'; import config from '../config'; import * as nativeCommands from '../nativeCommands'; import * as Logger from '../utils/logger'; import Routes from './routes'; -const PORT = process.env.PORT || config.SERVER_PORT; +const PORT = process.env.PORT ?? config.SERVER_PORT; + +type Response = ServerResponse & { + req: IncomingMessage; +}; + +type Listener = (args?: unknown) => void; + +type PostJSONRequestData = { + appInstanceId: string; + cache: Cache; + actionName: string; + payload: unknown; +}; // Gets the request data as a string -const getReqData = (req) => { +const getReqData = (req: IncomingMessage): Promise => { let data = ''; req.on('data', (chunk) => { data += chunk; @@ -21,7 +36,7 @@ const getReqData = (req) => { }; // Expects a POST request with JSON data. Returns parsed JSON data. -const getPostJSONRequestData = (req, res) => { +const getPostJSONRequestData = (req: IncomingMessage, res: Response): Promise | undefined => { if (req.method !== 'POST') { res.statusCode = 400; res.end('Unsupported method'); @@ -30,7 +45,7 @@ const getPostJSONRequestData = (req, res) => { return getReqData(req).then((data) => { try { - return JSON.parse(data); + return JSON.parse(data) as PostJSONRequestData; } catch (e) { Logger.info('❌ Failed to parse request data', data); res.statusCode = 400; @@ -39,9 +54,9 @@ const getPostJSONRequestData = (req, res) => { }); }; -const createListenerState = () => { - const listeners = []; - const addListener = (listener) => { +const createListenerState = (): [Listener[], (listener: Listener) => () => void] => { + const listeners: Listener[] = []; + const addListener = (listener: Listener) => { listeners.push(listener); return () => { const index = listeners.indexOf(listener); @@ -83,13 +98,10 @@ const createServerInstance = () => { const [testResultListeners, addTestResultListener] = createListenerState(); const [testDoneListeners, addTestDoneListener] = createListenerState(); - let activeTestConfig; - const networkCache = {}; + let activeTestConfig: TestConfig | null; + const networkCache: Record = {}; - /** - * @param {TestConfig} testConfig - */ - const setTestConfig = (testConfig) => { + const setTestConfig = (testConfig: TestConfig | null) => { activeTestConfig = testConfig; }; @@ -105,7 +117,7 @@ const createServerInstance = () => { } case Routes.testResults: { - getPostJSONRequestData(req, res).then((data) => { + getPostJSONRequestData(req, res)?.then((data) => { if (data == null) { // The getPostJSONRequestData function already handled the response return; @@ -129,8 +141,8 @@ const createServerInstance = () => { case Routes.testNativeCommand: { getPostJSONRequestData(req, res) - .then((data) => - nativeCommands.executeFromPayload(data.actionName, data.payload).then((status) => { + ?.then((data) => + nativeCommands.executeFromPayload(data?.actionName, data?.payload).then((status) => { if (status) { res.end('ok'); return; @@ -148,15 +160,15 @@ const createServerInstance = () => { } case Routes.testGetNetworkCache: { - getPostJSONRequestData(req, res).then((data) => { - const appInstanceId = data && data.appInstanceId; + getPostJSONRequestData(req, res)?.then((data) => { + const appInstanceId = data?.appInstanceId; if (!appInstanceId) { res.statusCode = 400; res.end('Invalid request missing appInstanceId'); return; } - const cachedData = networkCache[appInstanceId] || {}; + const cachedData = networkCache[appInstanceId] ?? {}; res.end(JSON.stringify(cachedData)); }); @@ -164,9 +176,9 @@ const createServerInstance = () => { } case Routes.testUpdateNetworkCache: { - getPostJSONRequestData(req, res).then((data) => { - const appInstanceId = data && data.appInstanceId; - const cache = data && data.cache; + getPostJSONRequestData(req, res)?.then((data) => { + const appInstanceId = data?.appInstanceId; + const cache = data?.cache; if (!appInstanceId || !cache) { res.statusCode = 400; res.end('Invalid request missing appInstanceId or cache'); @@ -191,7 +203,7 @@ const createServerInstance = () => { addTestStartedListener, addTestResultListener, addTestDoneListener, - start: () => new Promise((resolve) => server.listen(PORT, resolve)), + start: () => new Promise((resolve) => server.listen(PORT, resolve)), stop: () => new Promise((resolve) => server.close(resolve)), }; }; diff --git a/tests/e2e/server/routes.js b/tests/e2e/server/routes.ts similarity index 98% rename from tests/e2e/server/routes.js rename to tests/e2e/server/routes.ts index ffab3b01d297..d43fd2f0d301 100644 --- a/tests/e2e/server/routes.js +++ b/tests/e2e/server/routes.ts @@ -16,4 +16,4 @@ export default { // Gets the network cache testGetNetworkCache: '/test_get_network_cache', -}; +} as const; From a7accb5e5731679a9e65ee963ad7361cab9a9616 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 27 Feb 2024 13:43:25 +0100 Subject: [PATCH 04/21] migrate e2e server to TypeScript --- tests/e2e/server/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/server/index.ts b/tests/e2e/server/index.ts index e0c71c6cf54b..cde5083616c4 100644 --- a/tests/e2e/server/index.ts +++ b/tests/e2e/server/index.ts @@ -12,8 +12,6 @@ type Response = ServerResponse & { req: IncomingMessage; }; -type Listener = (args?: unknown) => void; - type PostJSONRequestData = { appInstanceId: string; cache: Cache; @@ -21,6 +19,8 @@ type PostJSONRequestData = { payload: unknown; }; +type Listener = (args?: unknown) => void; + // Gets the request data as a string const getReqData = (req: IncomingMessage): Promise => { let data = ''; From 4a9d9a3f6c44ae87db08f9437c3ff273d2152ec6 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 27 Feb 2024 13:43:41 +0100 Subject: [PATCH 05/21] start migrating testRunner to TypeScript --- package.json | 4 ++-- tests/e2e/{testRunner.js => testRunner.ts} | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) rename tests/e2e/{testRunner.js => testRunner.ts} (93%) diff --git a/package.json b/package.json index d79437b1e65b..3cef89266973 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "test:e2e": "ts-node tests/e2e/testRunner.js --config ./config.local.ts", - "test:e2e:dev": "ts-node tests/e2e/testRunner.js --config ./config.dev.js", + "test:e2e": "ts-node tests/e2e/testRunner.ts --config ./config.local.ts", + "test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.js", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.js", diff --git a/tests/e2e/testRunner.js b/tests/e2e/testRunner.ts similarity index 93% rename from tests/e2e/testRunner.js rename to tests/e2e/testRunner.ts index 77b2033dfece..3bb4c8c70fc9 100644 --- a/tests/e2e/testRunner.js +++ b/tests/e2e/testRunner.ts @@ -16,7 +16,6 @@ /* eslint-disable @lwc/lwc/no-async-await,no-restricted-syntax,no-await-in-loop */ import {execSync} from 'child_process'; import fs from 'fs'; -import _ from 'underscore'; import compare from './compare/compare'; import defaultConfig from './config'; import createServerInstance from './server'; @@ -30,7 +29,7 @@ import withFailTimeout from './utils/withFailTimeout'; // VARIABLE CONFIGURATION const args = process.argv.slice(2); -const getArg = (argName) => { +const getArg = (argName: string): string | undefined => { const argIndex = args.indexOf(argName); if (argIndex === -1) { return undefined; @@ -39,13 +38,13 @@ const getArg = (argName) => { }; let config = defaultConfig; -const setConfigPath = (configPathParam) => { +const setConfigPath = (configPathParam: string | undefined): void => { let configPath = configPathParam; - if (!configPath.startsWith('.')) { + if (!configPath?.startsWith('.')) { configPath = `./${configPath}`; } const customConfig = require(configPath).default; - config = _.extend(defaultConfig, customConfig); + config = Object.assign(defaultConfig, customConfig); }; if (args.includes('--config')) { @@ -54,8 +53,8 @@ if (args.includes('--config')) { } // Important: set app path only after correct config file has been loaded -const mainAppPath = getArg('--mainAppPath') || config.MAIN_APP_PATH; -const deltaAppPath = getArg('--deltaAppPath') || config.DELTA_APP_PATH; +const mainAppPath = getArg('--mainAppPath') ?? config.MAIN_APP_PATH; +const deltaAppPath = getArg('--deltaAppPath') ?? config.DELTA_APP_PATH; // Check if files exists: if (!fs.existsSync(mainAppPath)) { throw new Error(`Main app path does not exist: ${mainAppPath}`); @@ -117,7 +116,7 @@ const runTests = async () => { }); // Function to run a single test iteration - async function runTestIteration(appPackage, iterationText, launchArgs) { + async function runTestIteration(appPackage: string, iterationText: string, launchArgs: Record = {}) { Logger.info(iterationText); // Making sure the app is really killed (e.g. if a prior test run crashed) @@ -143,9 +142,9 @@ const runTests = async () => { } // Run the tests - const tests = _.values(config.TESTS_CONFIG); + const tests = Object.keys(config.TESTS_CONFIG); for (let testIndex = 0; testIndex < tests.length; testIndex++) { - const test = _.values(config.TESTS_CONFIG)[testIndex]; + const test = Object.values(config.TESTS_CONFIG)[testIndex]; // check if we want to skip the test if (args.includes('--includes')) { From c229a688c08f11f87c70f69e9acb22a659a72620 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 28 Feb 2024 07:54:04 +0100 Subject: [PATCH 06/21] add return type to promise and error --- tests/e2e/testRunner.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index 3bb4c8c70fc9..84d6429f8c65 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -127,7 +127,7 @@ const runTests = async () => { await launchApp('android', appPackage, config.ACTIVITY_PATH, launchArgs); await withFailTimeout( - new Promise((resolve) => { + new Promise((resolve) => { const cleanup = server.addTestDoneListener(() => { Logger.success(iterationText); cleanup(); @@ -181,7 +181,7 @@ const runTests = async () => { // We run each test multiple time to average out the results for (let testIteration = 0; testIteration < config.RUNS; testIteration++) { - const onError = (e) => { + const onError = (e: string) => { errorCountRef.errorCount += 1; if (testIteration === 0 || errorCountRef.errorCount === errorCountRef.allowedExceptions) { Logger.error("There was an error running the test and we've reached the maximum number of allowed exceptions. Stopping the test run."); @@ -236,7 +236,7 @@ const run = async () => { execSync(`cat ${config.LOG_FILE}`); try { - execSync(`cat ~/.android/avd/${process.env.AVD_NAME || 'test'}.avd/config.ini > ${config.OUTPUT_DIR}/emulator-config.ini`); + execSync(`cat ~/.android/avd/${process.env.AVD_NAME ?? 'test'}.avd/config.ini > ${config.OUTPUT_DIR}/emulator-config.ini`); } catch (ignoredError) { // the error is ignored, as the file might not exist if the test // run wasn't started with an emulator From 3d314f09e1bbd3e07f263c30575b9a0c8f3dbb70 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 28 Feb 2024 13:23:37 +0100 Subject: [PATCH 07/21] migrate testRunner to TypeScript --- src/libs/E2E/client.ts | 11 ++--------- src/libs/E2E/types.ts | 10 +++++++++- tests/e2e/server/index.ts | 5 +++-- tests/e2e/testRunner.ts | 28 ++++++++++++++++------------ 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/libs/E2E/client.ts b/src/libs/E2E/client.ts index 265c55c4a230..b926af6271d1 100644 --- a/src/libs/E2E/client.ts +++ b/src/libs/E2E/client.ts @@ -1,14 +1,6 @@ import Config from '../../../tests/e2e/config'; import Routes from '../../../tests/e2e/server/routes'; -import type {NetworkCacheMap, TestConfig} from './types'; - -type TestResult = { - name: string; - branch?: string; - duration?: number; - error?: string; - renderCount?: number; -}; +import type {NetworkCacheMap, TestConfig, TestResult} from './types'; type NativeCommandPayload = { text: string; @@ -113,3 +105,4 @@ export default { updateNetworkCache, getNetworkCache, }; +export type {TestResult}; diff --git a/src/libs/E2E/types.ts b/src/libs/E2E/types.ts index 2d48813fa115..fe2809c4772f 100644 --- a/src/libs/E2E/types.ts +++ b/src/libs/E2E/types.ts @@ -23,4 +23,12 @@ type TestConfig = { [key: string]: string; }; -export type {SigninParams, IsE2ETestSession, NetworkCacheMap, NetworkCacheEntry, TestConfig}; +type TestResult = { + name: string; + branch?: string; + duration?: number; + error?: string; + renderCount?: number; +}; + +export type {SigninParams, IsE2ETestSession, NetworkCacheMap, NetworkCacheEntry, TestConfig, TestResult}; diff --git a/tests/e2e/server/index.ts b/tests/e2e/server/index.ts index cde5083616c4..025d2a77ab7e 100644 --- a/tests/e2e/server/index.ts +++ b/tests/e2e/server/index.ts @@ -1,6 +1,6 @@ import type {IncomingMessage, ServerResponse} from 'http'; import {createServer} from 'http'; -import type {TestConfig} from '@libs/E2E/types'; +import type {TestConfig, TestResult} from '@libs/E2E/types'; import config from '../config'; import * as nativeCommands from '../nativeCommands'; import * as Logger from '../utils/logger'; @@ -19,7 +19,8 @@ type PostJSONRequestData = { payload: unknown; }; -type Listener = (args?: unknown) => void; +type ListenerArgs = (Partial & Partial) | null; +type Listener = (args?: ListenerArgs) => void; // Gets the request data as a string const getReqData = (req: IncomingMessage): Promise => { diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index 84d6429f8c65..3a4fa8ec6d36 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -75,7 +75,6 @@ try { } // START OF TEST CODE - const runTests = async () => { Logger.info('Installing apps and reversing port'); await installApp('android', config.MAIN_APP_PACKAGE, mainAppPath); @@ -87,32 +86,36 @@ const runTests = async () => { await server.start(); // Create a dict in which we will store the run durations for all tests - const results = {}; + const results: Record> = {}; // Collect results while tests are being executed - server.addTestResultListener((testResult) => { - if (testResult.error != null) { + // server.addTestResultListener((testResult) => { + server.addTestResultListener((res) => { + const testResult = res; + if (testResult?.error != null) { throw new Error(`Test '${testResult.name}' failed with error: ${testResult.error}`); } - let result = 0; + let result: number | undefined = 0; - if ('duration' in testResult) { + if (testResult?.duration) { if (testResult.duration < 0) { return; } result = testResult.duration; } - if ('renderCount' in testResult) { + if (testResult?.renderCount) { result = testResult.renderCount; } - Logger.log(`[LISTENER] Test '${testResult.name}' on '${testResult.branch}' measured ${result}`); + Logger.log(`[LISTENER] Test '${testResult?.name}' on '${testResult?.branch}' measured ${result}`); - if (!results[testResult.branch]) { + if (testResult?.branch && !results[testResult.branch]) { results[testResult.branch] = {}; } - results[testResult.branch][testResult.name] = (results[testResult.branch][testResult.name] || []).concat(result); + if (testResult?.branch && testResult?.name) { + results[testResult.branch][testResult.name] = (results[testResult.branch][testResult.name] || []).concat(result); + } }); // Function to run a single test iteration @@ -181,7 +184,7 @@ const runTests = async () => { // We run each test multiple time to average out the results for (let testIteration = 0; testIteration < config.RUNS; testIteration++) { - const onError = (e: string) => { + const onError = (e: Error) => { errorCountRef.errorCount += 1; if (testIteration === 0 || errorCountRef.errorCount === errorCountRef.allowedExceptions) { Logger.error("There was an error running the test and we've reached the maximum number of allowed exceptions. Stopping the test run."); @@ -190,6 +193,7 @@ const runTests = async () => { // maximum number of allowed exceptions, we should stop the test run. throw e; } + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.warn(`There was an error running the test. Continuing the test run. Error: ${e}`); }; @@ -207,7 +211,7 @@ const runTests = async () => { // Run the test on the delta app: await runTestIteration(config.DELTA_APP_PACKAGE, deltaIterationText, launchArgs); } catch (e) { - onError(e); + onError(e as Error); } } } From f1d7d6c26da60759e9dddc6d7a7582e3728c74b5 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 29 Feb 2024 09:33:20 +0100 Subject: [PATCH 08/21] start migrating UnreadIndicatorsTest and getIsUsingFakeTimers to TypeScript --- jest.config.js | 2 +- ...catorsTest.js => UnreadIndicatorsTest.tsx} | 75 ++++++++++--------- ...gFakeTimers.js => getIsUsingFakeTimers.ts} | 0 3 files changed, 42 insertions(+), 35 deletions(-) rename tests/ui/{UnreadIndicatorsTest.js => UnreadIndicatorsTest.tsx} (94%) rename tests/utils/{getIsUsingFakeTimers.js => getIsUsingFakeTimers.ts} (100%) diff --git a/jest.config.js b/jest.config.js index 441507af4228..c65e4aeb5ce2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -23,7 +23,7 @@ module.exports = { }, testEnvironment: 'jsdom', setupFiles: ['/jest/setup.ts', './node_modules/@react-native-google-signin/google-signin/jest/build/setup.js'], - setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect', '/jest/setupAfterEnv.ts', '/tests/perf-test/setupAfterEnv.js'], + setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect', '/jest/setupAfterEnv.ts', '/tests/perf-test/setupAfterEnv.ts'], cacheDirectory: '/.jest-cache', moduleNameMapper: { '\\.(lottie)$': '/__mocks__/fileMock.ts', diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.tsx similarity index 94% rename from tests/ui/UnreadIndicatorsTest.js rename to tests/ui/UnreadIndicatorsTest.tsx index 6051f04f570e..f42955e901e8 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -4,23 +4,24 @@ import {utcToZonedTime} from 'date-fns-tz'; import lodashGet from 'lodash/get'; import React from 'react'; import {AppState, DeviceEventEmitter, Linking} from 'react-native'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; +import * as CollectionUtils from '@libs/CollectionUtils'; +import DateUtils from '@libs/DateUtils'; +import * as Localize from '@libs/Localize'; +import LocalNotification from '@libs/Notification/LocalNotification'; +import * as NumberUtils from '@libs/NumberUtils'; +import * as Pusher from '@libs/Pusher/pusher'; +import PusherConnectionManager from '@libs/PusherConnectionManager'; import FontUtils from '@styles/utils/FontUtils'; -import App from '../../src/App'; -import CONFIG from '../../src/CONFIG'; -import CONST from '../../src/CONST'; -import * as AppActions from '../../src/libs/actions/App'; -import * as Report from '../../src/libs/actions/Report'; -import * as User from '../../src/libs/actions/User'; -import * as CollectionUtils from '../../src/libs/CollectionUtils'; -import DateUtils from '../../src/libs/DateUtils'; -import * as Localize from '../../src/libs/Localize'; -import LocalNotification from '../../src/libs/Notification/LocalNotification'; -import * as NumberUtils from '../../src/libs/NumberUtils'; -import * as Pusher from '../../src/libs/Pusher/pusher'; -import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; -import ONYXKEYS from '../../src/ONYXKEYS'; -import appSetup from '../../src/setup'; +import * as AppActions from '@userActions/App'; +import * as Report from '@userActions/Report'; +import * as User from '@userActions/User'; +import App from '@src/App'; +import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import appSetup from '@src/setup'; +import type {ReportAction, ReportActions} from '@src/types/onyx'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -34,6 +35,7 @@ jest.mock('../../src/components/ConfirmedRoute.tsx'); // Needed for: https://stackoverflow.com/questions/76903168/mocking-libraries-in-jest jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention __esModule: true, default: { ignoreLogs: jest.fn(), @@ -41,6 +43,7 @@ jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ }, })); +// eslint-disable-next-line @typescript-eslint/no-unsafe-return jest.mock('react-native-reanimated', () => ({ ...jest.requireActual('react-native-reanimated/mock'), createAnimatedPropAdapter: jest.fn, @@ -50,7 +53,7 @@ jest.mock('react-native-reanimated', () => ({ /** * We need to keep track of the transitionEnd callback so we can trigger it in our tests */ -let transitionEndCB; +let transitionEndCB: () => void | undefined; /** * This is a helper function to create a mock for the addListener function of the react-navigation library. @@ -59,7 +62,7 @@ let transitionEndCB; * * P.S: This can't be moved to a utils file because Jest wants any external function to stay in the scope. * - * @returns {Object} An object with two functions: triggerTransitionEnd and addListener + * @returns An object with two functions: triggerTransitionEnd and addListener */ const createAddListenerMock = () => { const transitionEndListeners = []; @@ -84,6 +87,7 @@ jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); const {triggerTransitionEnd, addListener} = createAddListenerMock(); transitionEndCB = triggerTransitionEnd; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return const useNavigation = () => ({ navigate: jest.fn(), ...actualNav.useNavigation, @@ -93,6 +97,7 @@ jest.mock('@react-navigation/native', () => { addListener, }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...actualNav, useNavigation, @@ -108,7 +113,7 @@ beforeAll(() => { // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. - global.fetch = TestHelper.getGlobalFetchMock(); + global.fetch = TestHelper.getGlobalFetchMock() as typeof global.fetch; Linking.setInitialURL('https://new.expensify.com/'); appSetup(); @@ -144,29 +149,31 @@ function scrollUpToRevealNewMessagesBadge() { } /** - * @return {Boolean} + * @return */ function isNewMessagesBadgeVisible() { const hintText = Localize.translateLocal('accessibilityHints.scrollToNewestMessages'); const badge = screen.queryByAccessibilityHint(hintText); - return Math.round(badge.props.style.transform[0].translateY) === -40; + return Math.round(badge?.props.style.transform[0].translateY) === -40; } /** - * @return {Promise} + * @return */ function navigateToSidebar() { const hintText = Localize.translateLocal('accessibilityHints.navigateToChatsList'); const reportHeaderBackButton = screen.queryByAccessibilityHint(hintText); - fireEvent(reportHeaderBackButton, 'press'); + if (reportHeaderBackButton) { + fireEvent(reportHeaderBackButton, 'press'); + } return waitForBatchedUpdates(); } /** - * @param {Number} index - * @return {Promise} + * @param index + * @return */ -async function navigateToSidebarOption(index) { +async function navigateToSidebarOption(index: number): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); const optionRows = screen.queryAllByAccessibilityHint(hintText); fireEvent(optionRows[index], 'press'); @@ -174,7 +181,7 @@ async function navigateToSidebarOption(index) { } /** - * @return {Boolean} + * @return */ function areYouOnChatListScreen() { const hintText = Localize.translateLocal('sidebarScreen.listOfChats'); @@ -195,7 +202,7 @@ let reportAction9CreatedDate; /** * Sets up a test with a logged in user that has one unread chat from another user. Returns the test instance. * - * @returns {Promise} + * @returns */ function signInAndGetAppWithUnreadChat() { // Render the App and sign in as a test user. @@ -302,7 +309,7 @@ describe('Unread Indicators', () => { return navigateToSidebarOption(0); }) .then(async () => { - await act(() => transitionEndCB && transitionEndCB()); + await act(() => transitionEndCB?.()); // That the report actions are visible along with the created action const welcomeMessageHintText = Localize.translateLocal('accessibilityHints.chatWelcomeMessage'); @@ -327,7 +334,7 @@ describe('Unread Indicators', () => { // Navigate to the unread chat from the sidebar .then(() => navigateToSidebarOption(0)) .then(async () => { - await act(() => transitionEndCB && transitionEndCB()); + await act(() => transitionEndCB?.()); // Verify the unread indicator is present const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); @@ -453,7 +460,7 @@ describe('Unread Indicators', () => { }) .then(waitForBatchedUpdates) .then(async () => { - await act(() => transitionEndCB && transitionEndCB()); + await act(() => transitionEndCB?.()); // Verify that report we navigated to appears in a "read" state while the original unread report still shows as unread const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); @@ -530,7 +537,7 @@ describe('Unread Indicators', () => { return navigateToSidebarOption(0); }) .then(async () => { - await act(() => transitionEndCB && transitionEndCB()); + await act(() => transitionEndCB?.()); const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); @@ -587,8 +594,8 @@ describe('Unread Indicators', () => { })); it('Displays the correct chat message preview in the LHN when a comment is added then deleted', () => { - let reportActions; - let lastReportAction; + let reportActions: Record; + let lastReportAction: ReportAction; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, callback: (val) => (reportActions = val), diff --git a/tests/utils/getIsUsingFakeTimers.js b/tests/utils/getIsUsingFakeTimers.ts similarity index 100% rename from tests/utils/getIsUsingFakeTimers.js rename to tests/utils/getIsUsingFakeTimers.ts From cab31a11a30530ede7aa17c927e05c1883f44d65 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 29 Feb 2024 11:28:59 +0100 Subject: [PATCH 09/21] type variables --- tests/ui/UnreadIndicatorsTest.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index f42955e901e8..53ab9db5b090 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -115,7 +115,7 @@ beforeAll(() => { // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. global.fetch = TestHelper.getGlobalFetchMock() as typeof global.fetch; - Linking.setInitialURL('https://new.expensify.com/'); + // Linking.setInitialURL('https://new.expensify.com/'); appSetup(); // Connect to Pusher @@ -129,7 +129,7 @@ beforeAll(() => { function scrollUpToRevealNewMessagesBadge() { const hintText = Localize.translateLocal('sidebarScreen.listOfChatMessages'); - fireEvent.scroll(screen.queryByLabelText(hintText), { + fireEvent.scroll(screen.getByLabelText(hintText), { nativeEvent: { contentOffset: { y: 250, @@ -196,8 +196,8 @@ const USER_B_ACCOUNT_ID = 2; const USER_B_EMAIL = 'user_b@test.com'; const USER_C_ACCOUNT_ID = 3; const USER_C_EMAIL = 'user_c@test.com'; -let reportAction3CreatedDate; -let reportAction9CreatedDate; +let reportAction3CreatedDate: string; +let reportAction9CreatedDate: string; /** * Sets up a test with a logged in user that has one unread chat from another user. Returns the test instance. From b99ed56b3433dee2167784bc77c4ebb0cdd23335 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 29 Feb 2024 13:42:40 +0100 Subject: [PATCH 10/21] remove lodashGet from UnreadIndicatorsTest --- tests/ui/UnreadIndicatorsTest.tsx | 46 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 53ab9db5b090..ecff42ad2eed 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -1,10 +1,10 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native'; import {addSeconds, format, subMinutes, subSeconds} from 'date-fns'; import {utcToZonedTime} from 'date-fns-tz'; -import lodashGet from 'lodash/get'; import React from 'react'; -import {AppState, DeviceEventEmitter, Linking} from 'react-native'; -import Onyx, {OnyxEntry} from 'react-native-onyx'; +import {AppState, DeviceEventEmitter} from 'react-native'; +import Onyx from 'react-native-onyx'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; import * as Localize from '@libs/Localize'; @@ -21,7 +21,7 @@ import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import appSetup from '@src/setup'; -import type {ReportAction, ReportActions} from '@src/types/onyx'; +import type {ReportAction} from '@src/types/onyx'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -35,7 +35,6 @@ jest.mock('../../src/components/ConfirmedRoute.tsx'); // Needed for: https://stackoverflow.com/questions/76903168/mocking-libraries-in-jest jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention __esModule: true, default: { ignoreLogs: jest.fn(), @@ -186,7 +185,8 @@ async function navigateToSidebarOption(index: number): Promise { function areYouOnChatListScreen() { const hintText = Localize.translateLocal('sidebarScreen.listOfChats'); const sidebarLinks = screen.queryAllByLabelText(hintText); - return !lodashGet(sidebarLinks, [0, 'props', 'accessibilityElementsHidden']); + + return !sidebarLinks?.[0]?.props?.accessibilityElementsHidden; } const REPORT_ID = '1'; @@ -274,7 +274,7 @@ function signInAndGetAppWithUnreadChat() { }); // We manually setting the sidebar as loaded since the onLayout event does not fire in tests - AppActions.setSidebarLoaded(true); + AppActions.setSidebarLoaded(); return waitForBatchedUpdatesWithAct(); }); } @@ -304,7 +304,7 @@ describe('Unread Indicators', () => { // And that the text is bold const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameText = screen.queryByLabelText(displayNameHintText); - expect(lodashGet(displayNameText, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold); + expect(displayNameText?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); return navigateToSidebarOption(0); }) @@ -323,7 +323,7 @@ describe('Unread Indicators', () => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); - const reportActionID = lodashGet(unreadIndicator, [0, 'props', 'data-action-id']); + const reportActionID = unreadIndicator?.[0]?.props?.['data-action-id']; expect(reportActionID).toBe('4'); // Scroll up and verify that the "New messages" badge appears scrollUpToRevealNewMessagesBadge(); @@ -383,7 +383,7 @@ describe('Unread Indicators', () => { const createdReportActionID = NumberUtils.rand64(); const commentReportActionID = NumberUtils.rand64(); const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${USER_A_ACCOUNT_ID}${CONFIG.PUSHER.SUFFIX}`); - channel.emit(Pusher.TYPE.MULTIPLE_EVENTS, [ + channel?.emit(Pusher.TYPE.MULTIPLE_EVENTS, [ { eventType: Pusher.TYPE.MULTIPLE_EVENT_TYPE.ONYX_API_UPDATE, data: [ @@ -448,12 +448,12 @@ describe('Unread Indicators', () => { const displayNameTexts = screen.queryAllByLabelText(displayNameHintTexts); expect(displayNameTexts).toHaveLength(2); const firstReportOption = displayNameTexts[0]; - expect(lodashGet(firstReportOption, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold); - expect(lodashGet(firstReportOption, ['props', 'children', 0])).toBe('C User'); + expect(firstReportOption?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); + expect(firstReportOption?.props?.children?.[0]).toBe('C User'); const secondReportOption = displayNameTexts[1]; - expect(lodashGet(secondReportOption, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold); - expect(lodashGet(secondReportOption, ['props', 'children', 0])).toBe('B User'); + expect(secondReportOption?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); + expect(secondReportOption?.props?.children?.[0]).toBe('B User'); // Tap the new report option and navigate back to the sidebar again via the back button return navigateToSidebarOption(0); @@ -465,10 +465,10 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(2); - expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined); - expect(lodashGet(displayNameTexts[0], ['props', 'children', 0])).toBe('C User'); - expect(lodashGet(displayNameTexts[1], ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold); - expect(lodashGet(displayNameTexts[1], ['props', 'children', 0])).toBe('B User'); + expect(displayNameTexts?.[0]?.props?.style?.fontWeight).toBe(undefined); + expect(displayNameTexts?.[0]?.props?.children?.[0]).toBe('C User'); + expect(displayNameTexts?.[1]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); + expect(displayNameTexts?.[1]?.props?.children?.[0]).toBe('B User'); })); xit('Manually marking a chat message as unread shows the new line indicator and updates the LHN', () => @@ -486,7 +486,7 @@ describe('Unread Indicators', () => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); - const reportActionID = lodashGet(unreadIndicator, [0, 'props', 'data-action-id']); + const reportActionID = unreadIndicator?.[0]?.props?.['data-action-id']; expect(reportActionID).toBe('3'); // Scroll up and verify the new messages badge appears scrollUpToRevealNewMessagesBadge(); @@ -499,8 +499,8 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(1); - expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold); - expect(lodashGet(displayNameTexts[0], ['props', 'children', 0])).toBe('B User'); + expect(displayNameTexts?.[0]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); + expect(displayNameTexts?.[0]?.props?.children?.[0]).toBe('B User'); // Navigate to the report again and back to the sidebar return navigateToSidebarOption(0); @@ -511,8 +511,8 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(1); - expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined); - expect(lodashGet(displayNameTexts[0], ['props', 'children', 0])).toBe('B User'); + expect(displayNameTexts?.[0]?.props?.style?.fontWeight).toBe(undefined); + expect(displayNameTexts?.[0]?.props?.children?.[0]).toBe('B User'); // Navigate to the report again and verify the new line indicator is missing return navigateToSidebarOption(0); From 6d759f10335f3b87c854153a0c804a0bbc3c9fd0 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 29 Feb 2024 14:09:31 +0100 Subject: [PATCH 11/21] add emitCurrentTestState and setInitialURL to react-native module declaration --- src/types/modules/react-native.d.ts | 8 ++++++++ tests/ui/UnreadIndicatorsTest.tsx | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 21f1d620b14f..effbdd9d28fa 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -330,6 +330,14 @@ declare module 'react-native' { } interface PressableProps extends WebPressableProps {} + interface AppStateStatic { + emitCurrentTestState: (status: string) => void; + } + + interface LinkingStatic { + setInitialURL: (url: string) => void; + } + /** * Styles */ diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index ecff42ad2eed..2b7f62279c00 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -3,7 +3,7 @@ import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-na import {addSeconds, format, subMinutes, subSeconds} from 'date-fns'; import {utcToZonedTime} from 'date-fns-tz'; import React from 'react'; -import {AppState, DeviceEventEmitter} from 'react-native'; +import {AppState, DeviceEventEmitter, Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; @@ -64,7 +64,7 @@ let transitionEndCB: () => void | undefined; * @returns An object with two functions: triggerTransitionEnd and addListener */ const createAddListenerMock = () => { - const transitionEndListeners = []; + const transitionEndListeners: Array<() => void> = []; const triggerTransitionEnd = () => { transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); }; @@ -114,7 +114,7 @@ beforeAll(() => { // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. global.fetch = TestHelper.getGlobalFetchMock() as typeof global.fetch; - // Linking.setInitialURL('https://new.expensify.com/'); + Linking.setInitialURL('https://new.expensify.com/'); appSetup(); // Connect to Pusher From c5feb25f86ee93b7b13d2145eeb140cc16d0ea48 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 29 Feb 2024 15:04:11 +0100 Subject: [PATCH 12/21] migrate UnreadIndicatorsTest to TypeScript --- tests/ui/UnreadIndicatorsTest.tsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 2b7f62279c00..3d7f15b15eb6 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -4,6 +4,7 @@ import {addSeconds, format, subMinutes, subSeconds} from 'date-fns'; import {utcToZonedTime} from 'date-fns-tz'; import React from 'react'; import {AppState, DeviceEventEmitter, Linking} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; @@ -21,7 +22,7 @@ import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import appSetup from '@src/setup'; -import type {ReportAction} from '@src/types/onyx'; +import type {ReportAction, ReportActions} from '@src/types/onyx'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -289,7 +290,7 @@ describe('Unread Indicators', () => { signInAndGetAppWithUnreadChat() .then(() => { // Verify no notifications are created for these older messages - expect(LocalNotification.showCommentNotification.mock.calls).toHaveLength(0); + expect((LocalNotification.showCommentNotification as jest.Mock).mock.calls).toHaveLength(0); // Verify the sidebar links are rendered const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); @@ -594,8 +595,9 @@ describe('Unread Indicators', () => { })); it('Displays the correct chat message preview in the LHN when a comment is added then deleted', () => { - let reportActions: Record; - let lastReportAction: ReportAction; + // let reportActions: Record; + let reportActions: OnyxEntry; + let lastReportAction: ReportAction | undefined; Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, callback: (val) => (reportActions = val), @@ -611,11 +613,11 @@ describe('Unread Indicators', () => { }) .then(() => { // Simulate the response from the server so that the comment can be deleted in this test - lastReportAction = {...CollectionUtils.lastItem(reportActions)}; + lastReportAction = reportActions ? CollectionUtils.lastItem(reportActions) : undefined; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { - lastMessageText: lastReportAction.message[0].text, - lastVisibleActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), - lastActorAccountID: lastReportAction.actorAccountID, + lastMessageText: lastReportAction?.message?.[0].text, + lastVisibleActionCreated: DateUtils.getDBTime(lastReportAction?.timestamp), + lastActorAccountID: lastReportAction?.actorAccountID, reportID: REPORT_ID, }); return waitForBatchedUpdates(); @@ -627,7 +629,9 @@ describe('Unread Indicators', () => { expect(alternateText).toHaveLength(1); expect(alternateText[0].props.children).toBe('Current User Comment 1'); - Report.deleteReportComment(REPORT_ID, lastReportAction); + if (lastReportAction) { + Report.deleteReportComment(REPORT_ID, lastReportAction); + } return waitForBatchedUpdates(); }) .then(() => { From 3b96a8d61aa6fcc2713b2d56e45be7a0e357ed4d Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 29 Feb 2024 16:36:31 +0100 Subject: [PATCH 13/21] migrate getIsUsingFakeTimers to TypeScript --- tests/e2e/testRunner.ts | 3 +-- tests/utils/getIsUsingFakeTimers.ts | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index 3a4fa8ec6d36..5db5cc67b8df 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -131,9 +131,8 @@ const runTests = async () => { await withFailTimeout( new Promise((resolve) => { - const cleanup = server.addTestDoneListener(() => { + server.addTestDoneListener(() => { Logger.success(iterationText); - cleanup(); resolve(); }); }), diff --git a/tests/utils/getIsUsingFakeTimers.ts b/tests/utils/getIsUsingFakeTimers.ts index 376312ac6c06..52138276928c 100644 --- a/tests/utils/getIsUsingFakeTimers.ts +++ b/tests/utils/getIsUsingFakeTimers.ts @@ -1 +1,3 @@ -export default () => Boolean(global.setTimeout.mock || global.setTimeout.clock); +type SetTimeout = typeof global.setTimeout & jest.Mock & typeof jasmine; + +export default () => Boolean((global.setTimeout as SetTimeout).mock || (global.setTimeout as SetTimeout).clock); From ccf0050945457d7c67fe2c206026f4e6e9c80203 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Fri, 1 Mar 2024 09:03:28 +0100 Subject: [PATCH 14/21] cleanup the code --- tests/e2e/testRunner.ts | 11 +++--- tests/ui/UnreadIndicatorsTest.tsx | 57 +++++++++++++------------------ 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index 5db5cc67b8df..d82dc9263f7c 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -75,7 +75,7 @@ try { } // START OF TEST CODE -const runTests = async () => { +const runTests = async (): Promise => { Logger.info('Installing apps and reversing port'); await installApp('android', config.MAIN_APP_PACKAGE, mainAppPath); await installApp('android', config.DELTA_APP_PACKAGE, deltaAppPath); @@ -90,8 +90,7 @@ const runTests = async () => { // Collect results while tests are being executed // server.addTestResultListener((testResult) => { - server.addTestResultListener((res) => { - const testResult = res; + server.addTestResultListener((testResult) => { if (testResult?.error != null) { throw new Error(`Test '${testResult.name}' failed with error: ${testResult.error}`); } @@ -114,12 +113,12 @@ const runTests = async () => { } if (testResult?.branch && testResult?.name) { - results[testResult.branch][testResult.name] = (results[testResult.branch][testResult.name] || []).concat(result); + results[testResult.branch][testResult.name] = (results[testResult.branch][testResult.name] ?? []).concat(result); } }); // Function to run a single test iteration - async function runTestIteration(appPackage: string, iterationText: string, launchArgs: Record = {}) { + async function runTestIteration(appPackage: string, iterationText: string, launchArgs: Record = {}): Promise { Logger.info(iterationText); // Making sure the app is really killed (e.g. if a prior test run crashed) @@ -183,7 +182,7 @@ const runTests = async () => { // We run each test multiple time to average out the results for (let testIteration = 0; testIteration < config.RUNS; testIteration++) { - const onError = (e: Error) => { + const onError = (e: Error): void => { errorCountRef.errorCount += 1; if (testIteration === 0 || errorCountRef.errorCount === errorCountRef.allowedExceptions) { Logger.error("There was an error running the test and we've reached the maximum number of allowed exceptions. Stopping the test run."); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 3d7f15b15eb6..9394db8a021c 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import type * as NativeNavigation from '@react-navigation/native'; import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native'; import {addSeconds, format, subMinutes, subSeconds} from 'date-fns'; import {utcToZonedTime} from 'date-fns-tz'; @@ -55,6 +56,11 @@ jest.mock('react-native-reanimated', () => ({ */ let transitionEndCB: () => void | undefined; +type ListenerMock = { + triggerTransitionEnd: () => void; + addListener: jest.Mock; +}; + /** * This is a helper function to create a mock for the addListener function of the react-navigation library. * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate @@ -64,13 +70,13 @@ let transitionEndCB: () => void | undefined; * * @returns An object with two functions: triggerTransitionEnd and addListener */ -const createAddListenerMock = () => { +const createAddListenerMock = (): ListenerMock => { const transitionEndListeners: Array<() => void> = []; const triggerTransitionEnd = () => { transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); }; - const addListener = jest.fn().mockImplementation((listener, callback) => { + const addListener: jest.Mock = jest.fn().mockImplementation((listener, callback) => { if (listener === 'transitionEnd') { transitionEndListeners.push(callback); } @@ -87,24 +93,24 @@ jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); const {triggerTransitionEnd, addListener} = createAddListenerMock(); transitionEndCB = triggerTransitionEnd; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - const useNavigation = () => ({ - navigate: jest.fn(), - ...actualNav.useNavigation, - getState: () => ({ - routes: [], - }), - addListener, - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const useNavigation = () => + ({ + navigate: jest.fn(), + ...actualNav.useNavigation, + getState: () => ({ + routes: [], + }), + addListener, + } as typeof NativeNavigation.useNavigation); + return { ...actualNav, useNavigation, getState: () => ({ routes: [], }), - }; + } as typeof NativeNavigation; }); beforeAll(() => { @@ -127,7 +133,7 @@ beforeAll(() => { }); }); -function scrollUpToRevealNewMessagesBadge() { +function scrollUpToRevealNewMessagesBadge(): void { const hintText = Localize.translateLocal('sidebarScreen.listOfChatMessages'); fireEvent.scroll(screen.getByLabelText(hintText), { nativeEvent: { @@ -148,19 +154,13 @@ function scrollUpToRevealNewMessagesBadge() { }); } -/** - * @return - */ -function isNewMessagesBadgeVisible() { +function isNewMessagesBadgeVisible(): boolean { const hintText = Localize.translateLocal('accessibilityHints.scrollToNewestMessages'); const badge = screen.queryByAccessibilityHint(hintText); return Math.round(badge?.props.style.transform[0].translateY) === -40; } -/** - * @return - */ -function navigateToSidebar() { +function navigateToSidebar(): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigateToChatsList'); const reportHeaderBackButton = screen.queryByAccessibilityHint(hintText); if (reportHeaderBackButton) { @@ -169,10 +169,6 @@ function navigateToSidebar() { return waitForBatchedUpdates(); } -/** - * @param index - * @return - */ async function navigateToSidebarOption(index: number): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); const optionRows = screen.queryAllByAccessibilityHint(hintText); @@ -180,10 +176,7 @@ async function navigateToSidebarOption(index: number): Promise { await waitForBatchedUpdatesWithAct(); } -/** - * @return - */ -function areYouOnChatListScreen() { +function areYouOnChatListScreen(): boolean { const hintText = Localize.translateLocal('sidebarScreen.listOfChats'); const sidebarLinks = screen.queryAllByLabelText(hintText); @@ -202,10 +195,8 @@ let reportAction9CreatedDate: string; /** * Sets up a test with a logged in user that has one unread chat from another user. Returns the test instance. - * - * @returns */ -function signInAndGetAppWithUnreadChat() { +function signInAndGetAppWithUnreadChat(): Promise { // Render the App and sign in as a test user. render(); return waitForBatchedUpdatesWithAct() From 4e3f77f3596a6a3d42e9b933b7dd7ab6d7e72c7e Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Fri, 1 Mar 2024 09:26:31 +0100 Subject: [PATCH 15/21] update file extensions in docs and config --- package.json | 2 +- tests/e2e/README.md | 2 +- tests/e2e/TestSpec.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bf4753f29ce2..569b7b4fe3e0 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", "test:e2e": "ts-node tests/e2e/testRunner.ts --config ./config.local.ts", - "test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.js", + "test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.ts", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.js", diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 5f124f20e872..ea36172a52ff 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -117,7 +117,7 @@ components: - Orchestrates the test suite. - Runs the app with the tests on a device - Responsible for gathering and comparing results - - Located in `e2e/testRunner.js`. + - Located in `e2e/testRunner.ts`. - Test server: - A nodeJS application that starts an HTTP server. diff --git a/tests/e2e/TestSpec.yml b/tests/e2e/TestSpec.yml index e0dcd2b9b66d..7745140d89c5 100644 --- a/tests/e2e/TestSpec.yml +++ b/tests/e2e/TestSpec.yml @@ -22,7 +22,7 @@ phases: commands: - cd zip - npm install underscore ts-node typescript - - npx ts-node e2e/testRunner.js -- --mainAppPath app-e2eRelease.apk --deltaAppPath app-e2edeltaRelease.apk + - npx ts-node e2e/testRunner.ts -- --mainAppPath app-e2eRelease.apk --deltaAppPath app-e2edeltaRelease.apk artifacts: - $WORKING_DIRECTORY From 96d86cb384155c701639618771861ee6efd7900d Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 15:15:42 +0100 Subject: [PATCH 16/21] apply suggested changes --- src/libs/E2E/client.ts | 19 +------------------ src/libs/E2E/types.ts | 9 +++++++++ tests/e2e/config.dev.ts | 4 ++-- tests/e2e/config.local.ts | 1 + tests/e2e/testRunner.ts | 10 +++++----- tests/ui/UnreadIndicatorsTest.tsx | 30 +++++++++++++++--------------- 6 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/libs/E2E/client.ts b/src/libs/E2E/client.ts index f76bdf2ed9a5..4c0e572cc9b2 100644 --- a/src/libs/E2E/client.ts +++ b/src/libs/E2E/client.ts @@ -1,23 +1,6 @@ import Config from '../../../tests/e2e/config'; import Routes from '../../../tests/e2e/server/routes'; -import type {NetworkCacheMap, TestConfig} from './types'; - -type TestResult = { - /** Name of the test */ - name: string; - - /** The branch where test were running */ - branch?: string; - - /** Duration in milliseconds */ - duration?: number; - - /** Optional, if set indicates that the test run failed and has no valid results. */ - error?: string; - - /** Render count */ - renderCount?: number; -}; +import type {NetworkCacheMap, TestConfig, TestResult} from './types'; type NativeCommandPayload = { text: string; diff --git a/src/libs/E2E/types.ts b/src/libs/E2E/types.ts index cb5bae9ee4b3..3c4d4680c77e 100644 --- a/src/libs/E2E/types.ts +++ b/src/libs/E2E/types.ts @@ -24,10 +24,19 @@ type TestConfig = { }; type TestResult = { + /** Name of the test */ name: string; + + /** The branch where test were running */ branch?: string; + + /** Duration in milliseconds */ duration?: number; + + /** Optional, if set indicates that the test run failed and has no valid results. */ error?: string; + + /** Render count */ renderCount?: number; }; diff --git a/tests/e2e/config.dev.ts b/tests/e2e/config.dev.ts index a930c2e21f06..cdd7bce756c8 100644 --- a/tests/e2e/config.dev.ts +++ b/tests/e2e/config.dev.ts @@ -1,8 +1,8 @@ +import type {Config} from './config.local'; + const packageName = 'com.expensify.chat.dev'; const appPath = './android/app/build/outputs/apk/development/debug/app-development-debug.apk'; -type Config = Record; - const config: Config = { MAIN_APP_PACKAGE: packageName, DELTA_APP_PACKAGE: packageName, diff --git a/tests/e2e/config.local.ts b/tests/e2e/config.local.ts index 40f7afde3985..8e90da9d3423 100644 --- a/tests/e2e/config.local.ts +++ b/tests/e2e/config.local.ts @@ -10,3 +10,4 @@ const config: Config = { }; export default config; +export type {Config}; diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index d82dc9263f7c..5d81dceee531 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -85,24 +85,24 @@ const runTests = async (): Promise => { const server = createServerInstance(); await server.start(); + type Result = Record; // Create a dict in which we will store the run durations for all tests - const results: Record> = {}; + const results: Record = {}; // Collect results while tests are being executed - // server.addTestResultListener((testResult) => { server.addTestResultListener((testResult) => { if (testResult?.error != null) { throw new Error(`Test '${testResult.name}' failed with error: ${testResult.error}`); } - let result: number | undefined = 0; + let result = 0; - if (testResult?.duration) { + if (testResult?.duration !== undefined) { if (testResult.duration < 0) { return; } result = testResult.duration; } - if (testResult?.renderCount) { + if (testResult?.renderCount !== undefined) { result = testResult.renderCount; } diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 9394db8a021c..9a84ba31410a 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -7,6 +7,7 @@ import React from 'react'; import {AppState, DeviceEventEmitter, Linking} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import type Animated from 'react-native-reanimated'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; import * as Localize from '@libs/Localize'; @@ -44,9 +45,8 @@ jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ }, })); -// eslint-disable-next-line @typescript-eslint/no-unsafe-return jest.mock('react-native-reanimated', () => ({ - ...jest.requireActual('react-native-reanimated/mock'), + ...jest.requireActual('react-native-reanimated/mock'), createAnimatedPropAdapter: jest.fn, useReducedMotion: jest.fn, })); @@ -54,7 +54,7 @@ jest.mock('react-native-reanimated', () => ({ /** * We need to keep track of the transitionEnd callback so we can trigger it in our tests */ -let transitionEndCB: () => void | undefined; +let transitionEndCB: () => void; type ListenerMock = { triggerTransitionEnd: () => void; @@ -119,7 +119,8 @@ beforeAll(() => { // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. - global.fetch = TestHelper.getGlobalFetchMock() as typeof global.fetch; + // @ts-expect-error -- TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated + global.fetch = TestHelper.getGlobalFetchMock(); Linking.setInitialURL('https://new.expensify.com/'); appSetup(); @@ -315,7 +316,7 @@ describe('Unread Indicators', () => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); - const reportActionID = unreadIndicator?.[0]?.props?.['data-action-id']; + const reportActionID = unreadIndicator[0]?.props?.['data-action-id']; expect(reportActionID).toBe('4'); // Scroll up and verify that the "New messages" badge appears scrollUpToRevealNewMessagesBadge(); @@ -457,10 +458,10 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(2); - expect(displayNameTexts?.[0]?.props?.style?.fontWeight).toBe(undefined); - expect(displayNameTexts?.[0]?.props?.children?.[0]).toBe('C User'); - expect(displayNameTexts?.[1]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); - expect(displayNameTexts?.[1]?.props?.children?.[0]).toBe('B User'); + expect(displayNameTexts[0]?.props?.style?.fontWeight).toBe(undefined); + expect(displayNameTexts[0]?.props?.children?.[0]).toBe('C User'); + expect(displayNameTexts[1]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); + expect(displayNameTexts[1]?.props?.children?.[0]).toBe('B User'); })); xit('Manually marking a chat message as unread shows the new line indicator and updates the LHN', () => @@ -478,7 +479,7 @@ describe('Unread Indicators', () => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText); expect(unreadIndicator).toHaveLength(1); - const reportActionID = unreadIndicator?.[0]?.props?.['data-action-id']; + const reportActionID = unreadIndicator[0]?.props?.['data-action-id']; expect(reportActionID).toBe('3'); // Scroll up and verify the new messages badge appears scrollUpToRevealNewMessagesBadge(); @@ -491,8 +492,8 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(1); - expect(displayNameTexts?.[0]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); - expect(displayNameTexts?.[0]?.props?.children?.[0]).toBe('B User'); + expect(displayNameTexts[0]?.props?.style?.fontWeight).toBe(FontUtils.fontWeight.bold); + expect(displayNameTexts[0]?.props?.children?.[0]).toBe('B User'); // Navigate to the report again and back to the sidebar return navigateToSidebarOption(0); @@ -503,8 +504,8 @@ describe('Unread Indicators', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameTexts = screen.queryAllByLabelText(hintText); expect(displayNameTexts).toHaveLength(1); - expect(displayNameTexts?.[0]?.props?.style?.fontWeight).toBe(undefined); - expect(displayNameTexts?.[0]?.props?.children?.[0]).toBe('B User'); + expect(displayNameTexts[0]?.props?.style?.fontWeight).toBe(undefined); + expect(displayNameTexts[0]?.props?.children?.[0]).toBe('B User'); // Navigate to the report again and verify the new line indicator is missing return navigateToSidebarOption(0); @@ -586,7 +587,6 @@ describe('Unread Indicators', () => { })); it('Displays the correct chat message preview in the LHN when a comment is added then deleted', () => { - // let reportActions: Record; let reportActions: OnyxEntry; let lastReportAction: ReportAction | undefined; Onyx.connect({ From 2bda50d6e919252f7b87b386ed041c9777b13847 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 4 Mar 2024 15:19:20 +0100 Subject: [PATCH 17/21] update scripts to reference testRunner.ts --- .github/workflows/e2ePerformanceTests.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 8a47ea4bb220..fd814ad69a7c 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -183,7 +183,7 @@ jobs: run: npm run e2e-test-runner-build - name: Copy e2e code into zip folder - run: cp tests/e2e/dist/index.js zip/testRunner.js + run: cp tests/e2e/dist/index.js zip/testRunner.ts - name: Zip everything in the zip directory up run: zip -qr App.zip ./zip diff --git a/package.json b/package.json index 76a970bada4b..7f5987803379 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", "workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.js", "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1", - "e2e-test-runner-build": "ncc build tests/e2e/testRunner.js -o tests/e2e/dist/" + "e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/" }, "dependencies": { "@dotlottie/react-player": "^1.6.3", From f1f2c06cf7d91e0791d62189a45a52cdfccb97cb Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 5 Mar 2024 09:00:59 +0100 Subject: [PATCH 18/21] apply suggested changes --- tests/e2e/testRunner.ts | 7 ++++--- tests/ui/UnreadIndicatorsTest.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index 5d81dceee531..ac636c3abd6a 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -27,6 +27,8 @@ import * as Logger from './utils/logger'; import sleep from './utils/sleep'; import withFailTimeout from './utils/withFailTimeout'; +type Result = Record; + // VARIABLE CONFIGURATION const args = process.argv.slice(2); const getArg = (argName: string): string | undefined => { @@ -38,7 +40,7 @@ const getArg = (argName: string): string | undefined => { }; let config = defaultConfig; -const setConfigPath = (configPathParam: string | undefined): void => { +const setConfigPath = (configPathParam: string | undefined) => { let configPath = configPathParam; if (!configPath?.startsWith('.')) { configPath = `./${configPath}`; @@ -85,7 +87,6 @@ const runTests = async (): Promise => { const server = createServerInstance(); await server.start(); - type Result = Record; // Create a dict in which we will store the run durations for all tests const results: Record = {}; @@ -182,7 +183,7 @@ const runTests = async (): Promise => { // We run each test multiple time to average out the results for (let testIteration = 0; testIteration < config.RUNS; testIteration++) { - const onError = (e: Error): void => { + const onError = (e: Error) => { errorCountRef.errorCount += 1; if (testIteration === 0 || errorCountRef.errorCount === errorCountRef.allowedExceptions) { Logger.error("There was an error running the test and we've reached the maximum number of allowed exceptions. Stopping the test run."); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 9a84ba31410a..5cdd3766cb3e 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -134,7 +134,7 @@ beforeAll(() => { }); }); -function scrollUpToRevealNewMessagesBadge(): void { +function scrollUpToRevealNewMessagesBadge() { const hintText = Localize.translateLocal('sidebarScreen.listOfChatMessages'); fireEvent.scroll(screen.getByLabelText(hintText), { nativeEvent: { From b4e35039426772a89d249077d266d25e2b8dfe34 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 5 Mar 2024 09:08:51 +0100 Subject: [PATCH 19/21] remove unknown return types --- tests/ui/UnreadIndicatorsTest.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 5cdd3766cb3e..9b723260d375 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -161,7 +161,7 @@ function isNewMessagesBadgeVisible(): boolean { return Math.round(badge?.props.style.transform[0].translateY) === -40; } -function navigateToSidebar(): Promise { +function navigateToSidebar(): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigateToChatsList'); const reportHeaderBackButton = screen.queryByAccessibilityHint(hintText); if (reportHeaderBackButton) { @@ -197,7 +197,7 @@ let reportAction9CreatedDate: string; /** * Sets up a test with a logged in user that has one unread chat from another user. Returns the test instance. */ -function signInAndGetAppWithUnreadChat(): Promise { +function signInAndGetAppWithUnreadChat(): Promise { // Render the App and sign in as a test user. render(); return waitForBatchedUpdatesWithAct() From 5bfb9f810ccd460ae6e61e560148be1ba6c2b9a5 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 6 Mar 2024 13:34:39 +0100 Subject: [PATCH 20/21] add type csting to testRunner.ts --- tests/e2e/testRunner.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index e58b6d02248f..b21033566ea8 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -16,6 +16,7 @@ /* eslint-disable @lwc/lwc/no-async-await,no-restricted-syntax,no-await-in-loop */ import {execSync} from 'child_process'; import fs from 'fs'; +import type {TestConfig} from '@libs/E2E/types'; import compare from './compare/compare'; import defaultConfig from './config'; import createServerInstance from './server'; @@ -165,7 +166,7 @@ const runTests = async (): Promise => { Logger.info(`Cooling down for ${config.BOOT_COOL_DOWN / 1000}s`); await sleep(config.BOOT_COOL_DOWN); - server.setTestConfig(test); + server.setTestConfig(test as TestConfig); const warmupText = `Warmup for test '${test.name}' [${testIndex + 1}/${tests.length}]`; From a2ea06c34037b7c65cdd510c29034fb95c78390b Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 11 Mar 2024 08:39:06 +0100 Subject: [PATCH 21/21] fix typescipt error in logger --- tests/e2e/testRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/testRunner.ts b/tests/e2e/testRunner.ts index b21033566ea8..5edc8c068229 100644 --- a/tests/e2e/testRunner.ts +++ b/tests/e2e/testRunner.ts @@ -231,7 +231,7 @@ const run = async () => { process.exit(0); } catch (e) { - Logger.info('\n\nE2E test suite failed due to error:', e, '\nPrinting full logs:\n\n'); + Logger.info('\n\nE2E test suite failed due to error:', e as string, '\nPrinting full logs:\n\n'); // Write logcat, meminfo, emulator info to file as well: execSync(`adb logcat -d > ${config.OUTPUT_DIR}/logcat.txt`);