ReviewOS

also looking at this

craft-native/craft

feat(maps): add key-free native Android maps and shared map controls

#363
Merged chris wants to merge codex/wildloop-native-map-providers into main
28 files +1857 -481

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.github/workflows/mobile-e2e.ymlmodified+2-2
Changes to .github/workflows/mobile-e2e.yml
@@ -74,7 +74,7 @@ on:
7474 android_suite:
7575 description: Android native fixture coverage for the android-navigation scope
7676 type: choice
77 options: [focused, full, system-bars, maps]
77 options: [focused, full, system-bars, maps, accessibility]
7878 default: focused
7979 selectors:
8080 description: Optional XCTest selector for focused native iOS rendering or navigation
@@ -607,7 +607,7 @@ jobs:
607607 api-level: ${{ fromJSON((inputs.scope != 'android-navigation' || inputs.android_suite == 'full') && '[34,35]' || inputs.android_suite == 'system-bars' && '[35]' || '[34]') }}
608608 env:
609609 CRAFT_ANDROID_NATIVE_SUITE: ${{ inputs.scope != 'android-navigation' && 'full' || inputs.android_suite || 'focused' }}
610 CRAFT_ANDROID_TEST_CLASS: ${{ inputs.scope == 'android-navigation' && inputs.android_suite == 'maps' && 'dev.craft.navigationtest.NativeMapTest#keyFreeBasemapsKeepTheRecordedRouteAndDirectionSignsVisible,dev.craft.navigationtest.NativeMapTest#nativeMapMutationsUpdateThePinAndCameraWithoutReloadingTheStyle,dev.craft.navigationtest.NativeMapTest#showingTheLocationDotDoesNotRequestPermissionDuringRendering,dev.craft.navigationtest.NativeMapTest#controlledRegionEchoPreservesTheCameraAfterANativeDrag,dev.craft.navigationtest.NativeMapTest#backgroundAndResumeKeepTheNativeMapAndCamera' || inputs.scope == 'android-navigation' && inputs.android_suite == 'system-bars' && 'dev.craft.navigationtest.NativeNavigationTest#nativeScreenBackgroundOptionAppliesAndResets' || inputs.scope == 'android-navigation' && inputs.android_suite != 'full' && 'dev.craft.navigationtest.NativeNavigationTest#outgoingTabTapWinsOverSettlingSwipe,dev.craft.navigationtest.NativeNavigationTest#thirdTabTapWinsOverSettlingSwipe,dev.craft.navigationtest.NativeNavigationTest#cancelledSwipeTargetIsVisibleWhenSelectedLater,dev.craft.navigationtest.NativeNavigationTest#completedReverseSwipeKeepsRetainedTabVisible,dev.craft.navigationtest.NativeNavigationTest#nativePullToRefreshSurvivesViewReattachment,dev.craft.navigationtest.NativeNavigationTest#pushedScreenTabAccessoryOpensNativeRouteAndClearsOnBack,dev.craft.navigationtest.NativeFlatListTest#refreshAppearanceUsesTheScreenContrastFallback,dev.craft.navigationtest.NativeNavigationTest#nativeDatePickerKeepsFollowingControlsMounted' || '' }}
610 CRAFT_ANDROID_TEST_CLASS: ${{ inputs.scope == 'android-navigation' && inputs.android_suite == 'accessibility' && 'dev.craft.navigationtest.NativeNavigationTest#nativeTestIdsRemainQueryableWithoutReplacingVisibleLabels' || inputs.scope == 'android-navigation' && inputs.android_suite == 'maps' && 'dev.craft.navigationtest.NativeMapTest#keyFreeBasemapsKeepTheRecordedRouteAndDirectionSignsVisible,dev.craft.navigationtest.NativeMapTest#nativeMapMutationsUpdateThePinAndCameraWithoutReloadingTheStyle,dev.craft.navigationtest.NativeMapTest#showingTheLocationDotDoesNotRequestPermissionDuringRendering,dev.craft.navigationtest.NativeMapTest#controlledRegionEchoPreservesTheCameraAfterANativeDrag,dev.craft.navigationtest.NativeMapTest#backgroundAndResumeKeepTheNativeMapAndCamera' || inputs.scope == 'android-navigation' && inputs.android_suite == 'system-bars' && 'dev.craft.navigationtest.NativeNavigationTest#nativeScreenBackgroundOptionAppliesAndResets' || inputs.scope == 'android-navigation' && inputs.android_suite != 'full' && 'dev.craft.navigationtest.NativeNavigationTest#outgoingTabTapWinsOverSettlingSwipe,dev.craft.navigationtest.NativeNavigationTest#thirdTabTapWinsOverSettlingSwipe,dev.craft.navigationtest.NativeNavigationTest#cancelledSwipeTargetIsVisibleWhenSelectedLater,dev.craft.navigationtest.NativeNavigationTest#completedReverseSwipeKeepsRetainedTabVisible,dev.craft.navigationtest.NativeNavigationTest#nativePullToRefreshSurvivesViewReattachment,dev.craft.navigationtest.NativeNavigationTest#pushedScreenTabAccessoryOpensNativeRouteAndClearsOnBack,dev.craft.navigationtest.NativeFlatListTest#refreshAppearanceUsesTheScreenContrastFallback,dev.craft.navigationtest.NativeNavigationTest#nativeDatePickerKeepsFollowingControlsMounted' || '' }}
611611 if: ${{ inputs.scope != 'ios-navigation' && inputs.scope != 'ios-render' }}
612612 runs-on: ubuntu-latest
613613 timeout-minutes: 35
packages/android/fixtures/native-navigation/NativeNavigationTest.ktmodified+37-0
Changes to packages/android/fixtures/native-navigation/NativeNavigationTest.kt
@@ -602,6 +602,43 @@ class NativeNavigationTest {
602602 }
603603 }
604604
605 @Test
606 fun nativeTestIdsRemainQueryableWithoutReplacingVisibleLabels() {
607 val automation = instrumentation.uiAutomation
608 val previousServiceInfo = automation.serviceInfo
609 automation.serviceInfo = automation.serviceInfo.apply {
610 flags = flags or android.accessibilityservice.AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
611 }
612 val activity = instrumentation.startActivitySync(
613 Intent(instrumentation.targetContext, MainActivity::class.java)
614 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
615 ) as MainActivity
616 fun node(id: String): AccessibilityNodeInfo {
617 repeat(100) {
618 automation.rootInActiveWindow?.findAccessibilityNodeInfosByViewId(id)?.firstOrNull()?.let { return it }
619 SystemClock.sleep(100)
620 }
621 throw AssertionError("The native accessibility tree did not expose STX testID $id")
622 }
623 try {
624 awaitView(activity, "home-title")
625 val title = node("home-title")
626 assertEquals("Home", title.text.toString())
627 assertEquals("The test identity must not become a spoken label", null, title.contentDescription)
628 val toggle = node("toggle-safe-area")
629 assertEquals("Toggle safe edges", toggle.text.toString())
630 assertTrue("The identified native button did not accept its accessibility click",
631 toggle.performAction(AccessibilityNodeInfo.ACTION_CLICK))
632 awaitText(activity, "safe-area-status", "Safe edges: bottom")
633 assertEquals("The identity disappeared after native reconciliation", "Toggle safe edges",
634 node("toggle-safe-area").text.toString())
635 } finally {
636 instrumentation.runOnMainSync { activity.finishAndRemoveTask() }
637 automation.serviceInfo = previousServiceInfo
638 instrumentation.waitForIdleSync()
639 }
640 }
641
605642 @Test
606643 fun outgoingTabTapWinsOverSettlingSwipe() {
607644 assertTabTapInterruptsSettlingSwipe(0, "navigation-home-state", "Home state: Ada/1")
packages/android/templates/MainActivityNative.kt.templatemodified+4-0
Changes to packages/android/templates/MainActivityNative.kt.template
@@ -6575,6 +6575,7 @@ class MainActivity : AppCompatActivity() {
65756575 val label = craftNativeOptionalString(props.opt("accessibilityLabel"))
65766576 ?: craftNativeOptionalString(props.opt("alt"))?.takeIf { type == "Image" || type == "ImageBackground" }
65776577 val value = craftNativeOptionalString(props.opt("accessibilityValue"))
6578 val testId = craftNativeOptionalString(props.opt("testID"))
65786579 view.contentDescription = when {
65796580 label != null && value != null -> "$label, $value"
65806581 label != null -> label
@@ -6599,6 +6600,9 @@ class MainActivity : AppCompatActivity() {
65996600 ViewCompat.setAccessibilityDelegate(view, object : AccessibilityDelegateCompat() {
66006601 override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
66016602 super.onInitializeAccessibilityNodeInfo(host, info)
6603 // Expose STX test identities to Android's native automation
6604 // tree without replacing the label read by TalkBack.
6605 info.viewIdResourceName = testId
66026606 info.className = when (role) {
66036607 "button" -> Button::class.java.name
66046608 "image" -> ImageView::class.java.name