with one click
review-and-test
// Review a FlashList PR or branch, run unit tests, test on iOS simulator, and verify RTL/LTR behavior. Shared context with fix-github-issue skill.
// Review a FlashList PR or branch, run unit tests, test on iOS simulator, and verify RTL/LTR behavior. Shared context with fix-github-issue skill.
| name | review-and-test |
| description | Review a FlashList PR or branch, run unit tests, test on iOS simulator, and verify RTL/LTR behavior. Shared context with fix-github-issue skill. |
which agent-device && which gh && yarn test --version
Ensure Metro is running from fixture/react-native/:
curl -s http://localhost:8081/status
gh pr view <pr-number> --repo Shopify/flash-list
gh pr diff <pr-number> --repo Shopify/flash-list
gh pr view <pr-number> --repo Shopify/flash-list --comments
git log main..HEAD --oneline
git diff main...HEAD
Identify:
All three must pass:
yarn test
yarn type-check
yarn lint
Root yarn type-check only covers src/ — the fixture has a separate tsconfig.json. Root yarn lint does cover fixture files.
If any files in fixture/react-native/ were modified, also run:
git diff main...HEAD --name-only | grep 'fixture/react-native/'
# If matches found:
cd fixture/react-native && yarn build # tsc -b — catches type errors in fixture code
Run E2E tests if any of these changed in the PR/branch:
*.e2e.*)fixture/react-native/src/# Check if e2e-relevant files were changed
git diff main...HEAD --name-only | grep -E '\.e2e\.|fixture/react-native/src/'
# If yes, run e2e tests on iOS
yarn e2e:ios
E2E tests use Detox. The yarn e2e:ios script handles both build and test.
Warning: E2E builds a release app that replaces the debug app on the simulator. After running E2E, rebuild debug to continue interactive testing:
cd fixture/react-native && yarn react-native run-ios
If tests fail, investigate before proceeding to device testing.
CRITICAL: The fixture app uses compiled output from dist/. Source changes in src/ have NO effect until built.
yarn build # runs tsc -b
Before building the native app, check if it's already installed on the simulator. Only build if it's not installed:
xcrun simctl get_app_container booted org.reactjs.native.example.FlatListPro 2>/dev/null
run-ios — just relaunch it.cd fixture/react-native && yarn react-native run-ios
After yarn build, relaunch the app (kill + reopen) so Metro serves the new bundle:
agent-device close --platform ios
xcrun simctl launch --terminate-running-process <UDID> org.reactjs.native.example.FlatListPro
agent-device open "FlatListPro" --platform ios
Find the simulator UDID with:
xcrun simctl list devices | grep Booted
Use the agent-device skill to navigate and take screenshots. Screens are listed on the Examples page. Common ones:
| Screen | What to check |
|---|---|
| Sticky Header Example | Headers pin correctly, no duplicate overlays |
| Horizontal List | Items scroll, header reachable, initialScrollIndex works |
| Grid | Multi-column layout correct |
| Masonry | Variable heights, columns balanced |
| Chat | Bottom-rendering, prepend behavior |
| Contacts | Section headers, fast scrolling |
| Grid with Separator | Last row items same height, separators between rows only |
Navigate to each affected screen, scroll through content, verify no visual regressions.
If an Android emulator is available (e.g., on the android-agent CI runner), test there too. Android uses a native RecyclerView bridge so behavior can differ from iOS.
yarn build
cd fixture/react-native && yarn react-native run-android
Use agent-device with --platform android --session droid:
agent-device snapshot -i -c --json --session droid
agent-device press <x> <y> --session droid
agent-device screenshot /tmp/android-screen.png --session droid
onEndReached / onStartReached fire at the same thresholds as iOSThe ONLY reliable method: change fixture/react-native/index.js:
// Change this:
I18nManager.forceRTL(false);
// To this:
I18nManager.forceRTL(true);
Then kill and relaunch the app (a JS reload is NOT sufficient):
agent-device close --platform ios
xcrun simctl launch --terminate-running-process <UDID> org.reactjs.native.example.FlatListPro
agent-device open "FlatListPro" --platform ios
Do NOT use:
DevSettings.reload() — does not properly apply RTLIn RTL, the scroll direction is reversed:
Use agent-device swipe with coordinates at the list center:
# Swipe right-to-left at y=30% (list center on HorizontalList screen)
agent-device swipe 350 256 50 256 --platform ios
I18nManager.forceRTL(false);
firstItemOffset Values (for layout/measurement changes)If the PR touches measureLayout, measureParentSize, firstItemOffset, or RecyclerView.tsx layout logic, verify the actual runtime values match expected values.
| Scenario | firstItemOffset (adjusted) |
|---|---|
| Vertical, no header | 0 |
| Vertical, with ListHeaderComponent | headerHeight |
| Vertical, content above FlashList | 0 (NOT the parent's y position) |
| Horizontal LTR, with header | headerWidth |
| Horizontal RTL, with header (1st render) | headerWidth |
| Horizontal RTL, with header (stable) | headerWidth |
console.log does NOT work on Fabric/Hermes (output goes to CDP debugger, not Metro).
Use a local HTTP server instead:
# Terminal 1: start server
node -e "
const http=require('http'),fs=require('fs');
http.createServer((q,r)=>{
if(q.method==='POST'){let b='';q.on('data',c=>b+=c);q.on('end',()=>{
fs.appendFileSync('/tmp/rv-debug.log',b+'\n');console.log(b);r.end('ok');})}
else r.end('ok');
}).listen(9876,()=>console.log('on :9876'));
"
Add to source (remember to remove after):
try {
fetch("http://localhost:9876", {
method: "POST",
body: JSON.stringify({ tag: "myDebug", ...values }),
}).catch(() => {});
} catch (e) {}
Then rebuild (yarn build), relaunch, navigate, and read /tmp/rv-debug.log.
measureParentSize x/y behaviorOn RN 0.84 Fabric, view.measureLayout(view) returns x=0, y=0 (the Fabric self-measurement bug from #2017 does NOT reproduce on this version). The defensive fix that strips x/y is a no-op here but protects other RN versions.
After testing, provide:
firstItemOffset verification (if applicable): actual vs expected valuesRun through relevant entries after any fix or review. This is the single source of truth for edge case checklists — the fix-github-issue skill references this.
stickyHeaderOffset > 0 combined with content above FlashListmeasureParentSize x/y values — on RN 0.84 Fabric these are (0,0), but on other RN versions they may be non-zeronumColumns > 1 with ItemSeparatorComponent — last row items must have same height (no separator height mismatch)numColumns > 1 with ItemSeparatorComponent and overrideItemLayout (variable spans) — separator suppression must use layout y-coordinates, not index arithmeticHorizontalList in RTL — items right-to-left, header reachable by swiping right-to-leftGrid in RTL — column ordering reversedStickyHeaderExample in RTL — header must pin to correct edgeChat in RTL — messages align correctlyMasonry in RTLstickyHeaderOffset > 0maintainVisibleContentPosition / startRenderingFromBottomhideRelatedCell: true — overlay hides original cell without jumpingmeasureLayout/measureParentSize call — verify behaviour matches PaperfirstItemOffset after fix — confirm it equals ListHeaderComponent height/widthmeasureParentSize(view) returns x=0, y=0 on RN 0.84 Fabric — the #2017 bug may only manifest on other RN versionsManualBenchmarkExample)yarn build and relaunch? The dist/ folder may be staledist/ is NOT rebuilt on branch switch. You MUST run yarn build after every git checkout. Verify with grep in dist/ that the expected code is present before testing.forceRTL(true) in index.js and do a full kill+relaunch?agent-device swipe gives "drag" error — delete ~/.agent-device/ios-runner/derived/ and re-run agent-device open to rebuild the iOS runnerlsof -p $(lsof -ti :8081) | grep "1w" to find where Metro stdout goes (often /private/tmp/metro_fixture.log)agent-device swipe 350 256 50 256. To scroll toward higher items, swipe left-to-right.org.reactjs.native.example.FlatListPro. Use with xcrun simctl launch.estimatedItemSize does not exist — this FlashList does NOT have this prop. Do not use it.Cmd+D → "Configure Bundler" → set host localhost and the correct port. Then reload.snapshot -i -c --json as the primary method for finding elements (returns exact coordinates). Fall back to screenshot + percentage-based press when elements aren't in the accessibility tree. See the agent-device skill for details.main, build (yarn build), relaunch, and reproduce the bugyarn build), relaunch, and verify the fixgrep "expected_code" dist/path/to/file.js — if the old code is still there, the build didn't run or didn't pick up the changeWithout step 1, you can't confirm the fix actually changed anything. Without verifying dist, you might test stale code on both branches and conclude "no difference" incorrectly.
When testing callback-based behavior (e.g., onStartReached, onEndReached, onViewableItemsChanged), add a visible counter to the fixture screen:
const [count, setCount] = useState(0);
// In the callback:
onStartReached={() => setCount(c => c + 1)}
// In the UI:
<View style={{ backgroundColor: count > 0 ? '#ff4444' : '#cccccc', ... }}>
<Text>Start: {count}</Text>
</View>
This gives immediate visual feedback in screenshots without needing console.log or debug servers.
Any masonry layout PR that adds Array.sort() or sorted copies is a red flag for performance. getVisibleLayouts runs on every scroll frame (60fps). O(N log N) sorting per layout change is expensive. Items within each column are naturally sorted — per-column binary search is O(log N) without sorting.
This skill is the single source of truth for testing knowledge, edge cases, and debug techniques. The fix-github-issue skill delegates here for Steps 6-7.
After each session, update this file with:
Interact with iOS simulator or Android emulator/device using snapshot-based coordinates. Uses accessibility tree snapshots for precise element targeting, with screenshot verification as fallback. Use when navigating the app on a simulator/emulator.
Analyze agent feedback artifacts from GitHub Actions workflow runs, extract actionable learnings, and incorporate them into skill files and CLAUDE.md. Tracks scan progress to avoid re-processing.
Full workflow for fixing a GitHub issue - understand the problem, reproduce, diagnose root cause, fix, test on iOS/Android simulators, review, and raise a PR
Create a GitHub PR for FlashList. Ensures no AI/Claude attribution in commits or PR body, follows repo conventions for title, description, and test plan.
Triage a GitHub issue — classify priority (P0/P1/P2), search for duplicates, and apply labels.
Upgrade the React Native fixture app to a new version. Covers JS deps, Android (Gradle, Kotlin, SDK), iOS (Podfile, pbxproj), Metro config, and third-party libraries.