diff --git a/.argent/flows/nested-touchables-test.yaml b/.argent/flows/nested-touchables-test.yaml new file mode 100644 index 0000000000..4195b3d357 --- /dev/null +++ b/.argent/flows/nested-touchables-test.yaml @@ -0,0 +1,85 @@ +steps: + - echo: Launching Expo Example + - launch: com.example.ExpoExample + - await: { visible: { text: "Empty Example" }, timeout: 15000 } + - echo: "On the Home list, search for the nested touchables example" + - type: { text: "nested touchables", into: { id: "search-examples" } } + - echo: "Tap the example to open it" + - tap: { text: "Nested touchables" } + - await: { visible: { id: "level-4" } } + - await: { idle: true } + + - echo: "Clear console" + - await: { visible: { id: "open-console-button" } } + - tap: { id: "open-console-button" } + - await: { visible: { id: "clear-console-button" } } + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "Level 1 - tap the outer tap gesture" + - tap: { id: "level-1" } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - echo: "Level 1 activates and no other level logs" + - assert: { visible: { text: "1. L1 onActivate" } } + - assert: { hidden: { text: "L2 on" } } + - assert: { hidden: { text: "L3 on" } } + - assert: { hidden: { text: "L4 on" } } + - assert: { hidden: { text: "2. L" } } + + - echo: "Clear console" + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "Level 2 - tap the touchable nested in the outer tap gesture" + - tap: { id: "level-2" } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - echo: "Level 2 presses in order and no other level logs" + - assert: { visible: { text: "2. L2 onPressIn" } } + - assert: { visible: { text: "3. L2 onPressOut" } } + - assert: { visible: { text: "4. L2 onPress" } } + - assert: { hidden: { text: "L1 on" } } + - assert: { hidden: { text: "L3 on" } } + - assert: { hidden: { text: "L4 on" } } + - assert: { hidden: { text: "5. L" } } + + - echo: "Clear console" + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "Level 3 - tap the tap gesture nested in the touchable" + - tap: { id: "level-3" } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - echo: "Level 3 activates and no other level logs" + - assert: { visible: { text: "5. L3 onActivate" } } + - assert: { hidden: { text: "L1 on" } } + - assert: { hidden: { text: "L2 on" } } + - assert: { hidden: { text: "L4 on" } } + - assert: { hidden: { text: "6. L" } } + + - echo: "Clear console" + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "Level 4 - tap the innermost touchable" + - tap: { id: "level-4" } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - echo: "Level 4 presses in order and no other level logs" + - assert: { visible: { text: "6. L4 onPressIn" } } + - assert: { visible: { text: "7. L4 onPressOut" } } + - assert: { visible: { text: "8. L4 onPress" } } + - assert: { hidden: { text: "L1 on" } } + - assert: { hidden: { text: "L2 on" } } + - assert: { hidden: { text: "L3 on" } } + - assert: { hidden: { text: "9. L" } } diff --git a/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx b/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx index 72da4e2c31..288167edbd 100644 --- a/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx +++ b/apps/common-app/src/new_api/tests/nestedTouchables/index.tsx @@ -1,84 +1,154 @@ -import React, { useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; +import type { ViewStyle } from 'react-native'; import { StyleSheet, Text, View } from 'react-native'; import { GestureDetector, Touchable, useTapGesture, } from 'react-native-gesture-handler'; +import type { AnimatedStyle } from 'react-native-reanimated'; +import Animated, { + interpolateColor, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; +import { scheduleOnRN } from 'react-native-worklets'; -import { COLORS } from '../../../common'; +import { COLORS, useIndexedLogger } from '../../../common'; + +type LevelEvent = { name: string; count: number }; + +function useLevel(token: string, log: (message: string) => void) { + const [lastEvent, setLastEvent] = useState(null); + const counts = useRef>({}); + const flash = useSharedValue(0); + + const updateLastEvent = useCallback((name: string) => { + const count = (counts.current[name] ?? 0) + 1; + counts.current[name] = count; + setLastEvent({ name, count }); + }, []); + + const report = useCallback( + (name: string) => { + 'worklet'; + log(`${token} ${name}`); + // Snap to the highlight color, then fade back to the resting one. + flash.value = 1; + flash.value = withTiming(0, { duration: 700 }); + scheduleOnRN(updateLastEvent, name); + }, + [flash, log, token, updateLastEvent] + ); + + const flashStyle = useAnimatedStyle(() => ({ + backgroundColor: interpolateColor( + flash.value, + [0, 1], + [COLORS.offWhite, COLORS.PURPLE] + ), + })); + + return { report, lastEvent, flashStyle }; +} + +type LevelBandProps = { + testID: string; + label: string; + event: LevelEvent | null; + flashStyle: AnimatedStyle; +}; + +// Keep the level tokens (L1..L4) out of on-screen text. The screen stays in the +// native tree behind the console modal, and nested-touchables-test.yaml asserts +// those tokens are hidden while the modal covers it. +function LevelBand({ testID, label, event, flashStyle }: LevelBandProps) { + return ( + + {label} + + {event ? `${event.name} x${event.count}` : 'no events'} + + + ); +} export default function NestedTouchablesExample() { - const [log, setLog] = useState([]); - - const pushLog = (message: string) => { - console.log(message); - setLog((prev) => - [...prev, `[${new Date().toLocaleTimeString()}] ${message}`].slice(-6) - ); - }; - - const outerTap = useTapGesture({ - runOnJS: true, - onActivate: () => pushLog('outer tap gesture'), - testID: 'outer-tap', + const log = useIndexedLogger(); + + const level1 = useLevel('L1', log); + const level2 = useLevel('L2', log); + const level3 = useLevel('L3', log); + const level4 = useLevel('L4', log); + + const { report: reportLevel1 } = level1; + const { report: reportLevel3 } = level3; + + const level1Tap = useTapGesture({ + onActivate: () => reportLevel1('onActivate'), }); - const innerTap = useTapGesture({ - runOnJS: true, - onActivate: () => pushLog('inner tap gesture'), - testID: 'inner-tap', + const level3Tap = useTapGesture({ + onActivate: () => reportLevel3('onActivate'), }); return ( Nested gestures & touchables - Tap each colored layer. Every level fires its own handler. + Tap the band of a level. Only that band should flash and update. - - - Outer tap gesture + + + pushLog('outer press in')} - onPressOut={() => pushLog('outer press out')} - onLongPress={() => pushLog('outer long press')} - onPress={() => pushLog('outer Touchable')}> - Outer Touchable + onPressIn={() => level2.report('onPressIn')} + onPressOut={() => level2.report('onPressOut')} + onPress={() => level2.report('onPress')}> + - - - Inner tap gesture + + + pushLog('inner press in')} - onPressOut={() => pushLog('inner press out')} - onLongPress={() => pushLog('inner long press')} - onPress={() => pushLog('inner Touchable')}> - Inner Touchable + onPressIn={() => level4.report('onPressIn')} + onPressOut={() => level4.report('onPressOut')} + onPress={() => level4.report('onPress')}> + - - - Event log - {Array.from({ length: 6 }).map((_, index) => ( - - {log[index] ?? ' '} - - ))} - ); } @@ -88,7 +158,7 @@ const styles = StyleSheet.create({ flex: 1, alignItems: 'center', justifyContent: 'center', - padding: 24, + padding: 16, gap: 16, }, title: { @@ -100,48 +170,47 @@ const styles = StyleSheet.create({ opacity: 0.6, fontSize: 14, }, - layer: { + level: { alignItems: 'center', justifyContent: 'flex-start', borderRadius: 12, - padding: 16, - gap: 12, + padding: 8, + gap: 8, }, - outerLayer: { - width: 300, + level1: { + width: '100%', + maxWidth: 340, backgroundColor: COLORS.KINDA_YELLOW, }, - outerTouchable: { - width: 260, + level2: { + alignSelf: 'stretch', backgroundColor: COLORS.YELLOW, }, - innerLayer: { - width: 220, + level3: { + alignSelf: 'stretch', backgroundColor: COLORS.KINDA_GREEN, }, - innerTouchable: { - width: 180, + level4: { + alignSelf: 'stretch', backgroundColor: COLORS.KINDA_BLUE, }, - layerLabel: { - fontSize: 14, - fontWeight: '600', - color: COLORS.NAVY, - }, - logBox: { - width: '100%', - padding: 12, + band: { + alignSelf: 'stretch', + height: 44, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 10, borderRadius: 8, - backgroundColor: COLORS.offWhite, - gap: 4, }, - logTitle: { + bandLabel: { fontSize: 13, - fontWeight: '700', - marginBottom: 4, + fontWeight: '600', + color: COLORS.NAVY, }, - logEntry: { + bandEvent: { fontSize: 12, - fontFamily: 'Menlo', + fontWeight: '700', + color: COLORS.NAVY, }, });