Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/test-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ It is intentionally small, but each surface is dense with durable accessibility
- `Automation lab`: long-press, alert-result, app-event, app-state, appearance, orientation, permission-recovery, log canaries, a flattened (`accessible={true}`) text input, and an Apple Pay sheet hosted in `com.apple.PassbookUIService`
- `WebView accessibility`: a deterministic semantic fixture plus live websites with varied HTML for native accessibility snapshot verification
- `Perf lab`: seeded JavaScript render problems for `react-devtools profile`, one per scenario (see [Perf lab](#perf-lab))
- `Heavy feed`: a Bluesky-shaped home feed whose accessibility tree is deeper than 65 levels (see [Heavy feed](#heavy-feed))

Navigation uses Expo Router native bottom tabs, so the tab bar itself is also part of the test surface.

Expand Down Expand Up @@ -261,6 +262,23 @@ agent-device react-devtools profile stop

`src/screens/PerfLabScreen.tsx` imports only React and React Native, so it can be copied into a bare React Native app to check the profiler on other React Native versions.

## Heavy feed

**Settings → Open heavy feed** (`open-heavy-feed`) opens a JavaScript-only reproduction of a heavy
React Native home feed, shaped after Bluesky 1.136. Thirty nested containers stand in for its
navigation and provider layers. Below them a virtualized list renders posts with avatar, text,
image, link, quote, and quote-with-media embeds. Every level opts out of view flattening, so the
native accessibility tree is deeper than 65 levels and holds about 300 raw nodes.

On this screen the XCTest tree capture fails with `kAXErrorIllegalArgument` and recovers through
private AX, while the simulator accessibility bridge still answers. Use it to reproduce deep-tree
capture, recovery, and gesture-routing behavior without a third-party app. `wrappers` (default 30)
and `posts` (default 60) route parameters change the shape.

The stable ids are `heavy-feed-title`, `heavy-feed-liked-count`, `heavy-feed-last-action`, and per
post `heavy-post-<n>`, `heavy-post-<n>-like`, `-reply`, `-repost`, and `-more`. A like press updates
`Liked posts: <count>`, and a long press on a post sets `Last action: held post <n>`.

## Local Agent Device suites

The repo includes two local suites for iterating on the fixture app:
Expand Down
1 change: 1 addition & 0 deletions examples/test-app/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function SettingsRoute() {
onLoadDiagnostics={state.loadDiagnostics}
onOpenAccessorySetup={() => router.push('/accessory-setup')}
onOpenAutomationLab={() => router.push('/automation')}
onOpenHeavyFeed={() => router.push('/heavy-feed')}
onOpenInertSurface={() => router.push('/inert')}
onOpenSnapshotDepth={() => router.push('/snapshot-depth')}
onOpenWebViewLab={() => router.push('/webview')}
Expand Down
1 change: 1 addition & 0 deletions examples/test-app/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function RootLayoutContent() {
<Stack.Screen name="inert" options={{ presentation: 'fullScreenModal' }} />
<Stack.Screen name="snapshot-depth" />
<Stack.Screen name="deep-tree" />
<Stack.Screen name="heavy-feed" options={{ headerShown: true, title: 'Heavy feed' }} />
<Stack.Screen name="ticker" />
<Stack.Screen name="perf-lab" />
<Stack.Screen name="product/[productId]" />
Expand Down
5 changes: 5 additions & 0 deletions examples/test-app/app/heavy-feed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { HeavyFeedScreen } from '../src/screens/HeavyFeedScreen';

export default function HeavyFeedRoute() {
return <HeavyFeedScreen />;
}
10 changes: 10 additions & 0 deletions examples/test-app/src/route-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function clampInteger(
value: string | undefined,
fallback: number,
min: number,
max: number,
): number {
const parsed = Number.parseInt(value ?? '', 10);
if (!Number.isFinite(parsed)) return fallback;
return Math.min(max, Math.max(min, parsed));
}
7 changes: 1 addition & 6 deletions examples/test-app/src/screens/DeepTreeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useLocalSearchParams } from 'expo-router';
import { StyleSheet, Text, View } from 'react-native';

import { clampInteger } from '../route-params';
import { useAppColors, type AppColors } from '../theme';

/**
Expand Down Expand Up @@ -62,12 +63,6 @@ function DeepLevel({
);
}

function clampInteger(value: string | undefined, fallback: number, min: number, max: number) {
const parsed = Number.parseInt(value ?? '', 10);
if (!Number.isFinite(parsed)) return fallback;
return Math.min(max, Math.max(min, parsed));
}

function createStyles(colors: AppColors) {
return StyleSheet.create({
frame: {
Expand Down
Loading
Loading