diff --git a/.github/workflows/npm-screens-publish-nightly.yml b/.github/workflows/npm-screens-publish-nightly.yml index a62be55a95..de5432b0dd 100644 --- a/.github/workflows/npm-screens-publish-nightly.yml +++ b/.github/workflows/npm-screens-publish-nightly.yml @@ -24,7 +24,7 @@ jobs: node-version: 22 cache: 'yarn' registry-url: https://registry.npmjs.org/ - + # Ensure npm 11.5.1 or later is installed for OIDC - name: Update npm run: npm install -g npm@latest diff --git a/Example/.gitignore b/Example/.gitignore index ec11000081..4b8d0da1f8 100644 --- a/Example/.gitignore +++ b/Example/.gitignore @@ -65,7 +65,7 @@ yarn-error.log # testing /coverage - + # Yarn .yarn/* !.yarn/patches diff --git a/Example/android/app/build.gradle b/Example/android/app/build.gradle index 654bea0a75..5b8c959a3e 100644 --- a/Example/android/app/build.gradle +++ b/Example/android/app/build.gradle @@ -49,7 +49,7 @@ react { // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] - + /* Autolinking */ autolinkLibrariesWithApp() } diff --git a/Example/e2e/examplesTests/bottomTabs.e2e.ts b/Example/e2e/examplesTests/bottomTabs.e2e.ts index 4a0585867d..029770e97f 100644 --- a/Example/e2e/examplesTests/bottomTabs.e2e.ts +++ b/Example/e2e/examplesTests/bottomTabs.e2e.ts @@ -2,7 +2,7 @@ import { device, expect, element, by } from 'detox'; describe('Bottom tabs and native stack', () => { beforeEach(async () => { - await device.reloadReactNative(); + await device.launchApp({ newInstance: true }); }); it('should go to main screen and back', async () => { diff --git a/Example/e2e/examplesTests/events.e2e.ts b/Example/e2e/examplesTests/events.e2e.ts index 8fb755d192..811f27ab57 100644 --- a/Example/e2e/examplesTests/events.e2e.ts +++ b/Example/e2e/examplesTests/events.e2e.ts @@ -11,43 +11,32 @@ const pressBack = async () => { }; const awaitClassicalEventBehavior = async () => { - if (device.getPlatform() === 'ios') { - await expect( - element(by.text('9. Chats | transitionStart | closing')), - ).toExist(); - await expect( - element(by.text('10. Privacy | transitionStart | closing')), - ).toExist(); - await expect( - element(by.text('11. Main | transitionStart | opening')), - ).toExist(); - await expect( - element(by.text('12. Chats | transitionEnd | closing')), - ).toExist(); - await expect( - element(by.text('13. Privacy | transitionEnd | closing')), - ).toExist(); - await expect(element(by.text('14. Privacy | beforeRemove'))).toExist(); - await expect(element(by.text('15. Chats | beforeRemove'))).toExist(); - await expect( - element(by.text('16. Main | transitionEnd | opening')), - ).toExist(); - } else { - await expect(element(by.text('9. Privacy | beforeRemove'))).toExist(); - await expect(element(by.text('10. Chats | beforeRemove'))).toExist(); - await expect( - element(by.text('11. Main | transitionStart | opening')), - ).toExist(); - await expect( - element(by.text('12. Main | transitionEnd | opening')), - ).toExist(); + const expectedEvents = + device.getPlatform() === 'ios' + ? [ + '9. Chats | transitionStart | closing', + '10. Privacy | transitionStart | closing', + '11. Main | transitionStart | opening', + '12. Chats | transitionEnd | closing', + '13. Privacy | transitionEnd | closing', + '14. Privacy | beforeRemove', + '15. Chats | beforeRemove', + '16. Main | transitionEnd | opening', + ] + : [ + '9. Privacy | beforeRemove', + '10. Chats | beforeRemove', + '11. Main | transitionStart | opening', + '12. Main | transitionEnd | opening', + ]; + for (const expectedEventNotification of expectedEvents) { + await expect(element(by.text(expectedEventNotification))).toExist(); } }; describe('Events', () => { beforeEach(async () => { - await device.reloadReactNative(); - // await device.launchApp({ newInstance: true }); + await device.launchApp({ newInstance: true }); await waitFor(element(by.id('root-screen-playground-Events'))) .toBeVisible() diff --git a/FabricExample/e2e/component-objects/back-button.ts b/FabricExample/e2e/component-objects/back-button.ts new file mode 100644 index 0000000000..1bb3ccce6a --- /dev/null +++ b/FabricExample/e2e/component-objects/back-button.ts @@ -0,0 +1,35 @@ +import { device, element, by } from 'detox'; +import { getIOSVersion } from '../../../scripts/e2e/ios-devices.js'; +import semverSatisfies from 'semver/functions/satisfies'; +import semverCoerce from 'semver/functions/coerce'; + +const IOS_BAR_BUTTON_TYPE = '_UIButtonBarButton'; +const backButtonElement = element(by.id('BackButton')); + +export async function tapBarBackButton() { + const platform = device.getPlatform(); + if (platform === 'ios') { + return (await getIOSBackButton()).tap(); + } else if (platform === 'android') { + return backButtonElement.tap(); + } else throw new Error(`Platform "${platform}" not supported`); +} +async function getIOSBackButton() { + const iosVersion = semverCoerce(getIOSVersion().replace('iOS', ''))!; + if (semverSatisfies(iosVersion, '>=26.0')) { + const elementsByAttributes = + (await backButtonElement.getAttributes()) as unknown as { + elements: { className: string }[]; + }; + const elements = elementsByAttributes.elements; + if (Array.isArray(elements)) { + const uiBarButtonIndex = elements.findIndex( + elem => elem.className === IOS_BAR_BUTTON_TYPE, + ); + if (uiBarButtonIndex !== -1) { + return backButtonElement.atIndex(uiBarButtonIndex); + } + } + } + return backButtonElement; +} diff --git a/FabricExample/e2e/e2e-utils.ts b/FabricExample/e2e/e2e-utils.ts index 6e8cbf8fba..5fba9293b5 100644 --- a/FabricExample/e2e/e2e-utils.ts +++ b/FabricExample/e2e/e2e-utils.ts @@ -18,7 +18,10 @@ export async function selectTestScreen(screenName: string) { // More details: https://github.com/software-mansion/react-native-screens/pull/2919 await device.pressBack(); } else { - await element(by.type('UISearchBarTextField')).replaceText(screenName); + await waitFor(element(by.id('root-screen-tests-' + screenName))) + .toBeVisible() + .whileElement(by.id('root-screen-examples-scrollview')) + .scroll(600, 'down', Number.NaN, 0.85); } await expect(element(by.id(`root-screen-tests-${screenName}`))).toBeVisible(); diff --git a/FabricExample/e2e/helpers/disableStylus.ts b/FabricExample/e2e/helpers/disableStylus.ts new file mode 100644 index 0000000000..3f912b8029 --- /dev/null +++ b/FabricExample/e2e/helpers/disableStylus.ts @@ -0,0 +1,11 @@ +import { getCommandLineResponse } from "react-native-screens/scripts/e2e/command-line-helpers"; +import { device } from 'detox'; + +export function disableStylusPopupOnAndroid() { + if (device.getPlatform() === 'ios') return; + try { + getCommandLineResponse(`adb -s ${device.id} shell settings put secure stylus_handwriting_enabled 0`); + } catch { + console.warn('Failed to disable stylus setting.'); + } +} diff --git a/FabricExample/e2e/issuesTests/Test2926.e2e.ts b/FabricExample/e2e/issuesTests/Test2926.e2e.ts index 9649497f05..6ccfe078b4 100644 --- a/FabricExample/e2e/issuesTests/Test2926.e2e.ts +++ b/FabricExample/e2e/issuesTests/Test2926.e2e.ts @@ -1,5 +1,6 @@ import { device, expect, element, by } from 'detox'; import { describeIfiOS, selectTestScreen } from '../e2e-utils'; +import { tapBarBackButton } from '../component-objects/back-button'; // PR related to iOS search bar describeIfiOS('Test2926', () => { @@ -26,7 +27,7 @@ describeIfiOS('Test2926', () => { await element(by.type('UISearchBarTextField')).replaceText('Item 2'); await element(by.id('home-button-open-second')).tap(); - await element(by.id('BackButton')).tap(); + await tapBarBackButton(); await expect(element(by.type('UISearchBarTextField'))).toBeVisible(); await expect(element(by.type('UISearchBarTextField'))).toHaveText('Item 2'); diff --git a/FabricExample/e2e/issuesTests/Test432.e2e.ts b/FabricExample/e2e/issuesTests/Test432.e2e.ts index ee083827e5..de824fee75 100644 --- a/FabricExample/e2e/issuesTests/Test432.e2e.ts +++ b/FabricExample/e2e/issuesTests/Test432.e2e.ts @@ -1,5 +1,6 @@ import { device, expect, element, by } from 'detox'; import { selectTestScreen } from '../e2e-utils'; +import { tapBarBackButton } from '../component-objects/back-button'; describe('Test432', () => { beforeAll(async () => { @@ -26,7 +27,7 @@ describe('Test432', () => { await expect(element(by.id('details-headerRight-red'))).toBeVisible(100); if (device.getPlatform() === 'ios') { - await element(by.id('BackButton')).tap(); + await tapBarBackButton(); } else { await device.pressBack(); } @@ -47,7 +48,7 @@ describe('Test432', () => { waitFor(element(by.id('info-headerRight-green-1'))).toBeVisible(100); if (device.getPlatform() === 'ios') { - await element(by.id('BackButton')).tap(); + await tapBarBackButton(); } else { await device.pressBack(); } diff --git a/FabricExample/e2e/issuesTests/Test528.e2e.ts b/FabricExample/e2e/issuesTests/Test528.e2e.ts index 59030e10e3..644d06d5a0 100644 --- a/FabricExample/e2e/issuesTests/Test528.e2e.ts +++ b/FabricExample/e2e/issuesTests/Test528.e2e.ts @@ -1,5 +1,6 @@ import { device, expect, element, by } from 'detox'; import { describeIfiOS, selectTestScreen } from '../e2e-utils'; +import { tapBarBackButton } from '../component-objects/back-button'; // Detox currently supports orientation only on iOS describeIfiOS('Test528', () => { @@ -22,7 +23,8 @@ describeIfiOS('Test528', () => { it('headerRight button should be visible after coming back from horizontal screen', async () => { await element(by.text('Go to Screen 2')).tap(); await device.setOrientation('landscape'); - await element(by.id('BackButton')).tap(); + + await tapBarBackButton(); await expect(element(by.text('Custom Button'))).toBeVisible(100); await device.setOrientation('portrait'); await expect(element(by.text('Custom Button'))).toBeVisible(100); diff --git a/FabricExample/e2e/jest.setup.ts b/FabricExample/e2e/jest.setup.ts index b7d849b9b1..35c0aa0b6d 100644 --- a/FabricExample/e2e/jest.setup.ts +++ b/FabricExample/e2e/jest.setup.ts @@ -1,7 +1,9 @@ import { device } from 'detox'; +import { disableStylusPopupOnAndroid } from './helpers/disableStylus'; beforeAll(async () => { await device.launchApp(); + disableStylusPopupOnAndroid(); }); afterAll(async () => { diff --git a/FabricExample/package.json b/FabricExample/package.json index 91199b09f3..02660fa5c9 100644 --- a/FabricExample/package.json +++ b/FabricExample/package.json @@ -51,6 +51,7 @@ "@types/jest": "^29.5.13", "@types/react": "^19.1.1", "@types/react-test-renderer": "^19.1.0", + "@types/semver": "^7", "babel-jest": "^29.6.3", "detox": "^20.45.1", "eslint": "^8.19.0", @@ -58,6 +59,7 @@ "patch-package": "^8.0.0", "prettier": "2.8.8", "react-test-renderer": "19.1.1", + "semver": "^7.7.3", "ts-jest": "^29.0.3", "typescript": "5.0.4" }, diff --git a/FabricExample/tsconfig.json b/FabricExample/tsconfig.json index 3c43903cfd..8ff28ba3a4 100644 --- a/FabricExample/tsconfig.json +++ b/FabricExample/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "../tsconfig.json" + "extends": "../tsconfig.json", + "compilerOptions": { + "allowJs": true, + }, } diff --git a/FabricExample/yarn.lock b/FabricExample/yarn.lock index 80cff62243..32a5430ed2 100644 --- a/FabricExample/yarn.lock +++ b/FabricExample/yarn.lock @@ -3076,6 +3076,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7": + version: 7.7.1 + resolution: "@types/semver@npm:7.7.1" + checksum: 10c0/c938aef3bf79a73f0f3f6037c16e2e759ff40c54122ddf0b2583703393d8d3127130823facb880e694caa324eb6845628186aac1997ee8b31dc2d18fafe26268 + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -3316,6 +3323,7 @@ __metadata: "@types/jest": "npm:^29.5.13" "@types/react": "npm:^19.1.1" "@types/react-test-renderer": "npm:^19.1.0" + "@types/semver": "npm:^7" babel-jest: "npm:^29.6.3" detox: "npm:^20.45.1" eslint: "npm:^8.19.0" @@ -3333,6 +3341,7 @@ __metadata: react-native-screens: "link:../" react-native-worklets: "npm:~0.6.0" react-test-renderer: "npm:19.1.1" + semver: "npm:^7.7.3" ts-jest: "npm:^29.0.3" typescript: "npm:5.0.4" languageName: unknown @@ -9464,6 +9473,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.7.3": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" diff --git a/apps/src/tests/Test3265.tsx b/apps/src/tests/Test3265.tsx index 1e831bb714..2f23f5f342 100644 --- a/apps/src/tests/Test3265.tsx +++ b/apps/src/tests/Test3265.tsx @@ -18,7 +18,7 @@ export default function TestScrollViewHorizontal() {