diff --git a/.argent/flows/bench-part1.template.yaml b/.argent/flows/bench-part1.template.yaml new file mode 100644 index 00000000000..1c48139dd03 --- /dev/null +++ b/.argent/flows/bench-part1.template.yaml @@ -0,0 +1,130 @@ +steps: + - echo: On rooms list — open bench-roomview-hooks channel (top row) + - tool: await-ui-element + args: + condition: visible + selector: + text: bench-roomview-hooks + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.218 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: exists + selector: + text: e2e_admin + timeoutMs: 15000 + - echo: In room — scroll up through history until load-more pagination fires + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: await-ui-element + args: + condition: exists + selector: + text: seed message 1 + timeoutMs: 15000 + pollIntervalMs: 500 + - echo: Pagination loaded. Tap composer, type run-tagged message, send + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench RUNID msg1 + delayMs: 150 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-send + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5778 + - echo: Dismiss keyboard (swipe down), jump to last message, verify send + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID msg1 + timeoutMs: 15000 +executionPrerequisite: Rooms list visible, bench channel top row. RUNID replaced. diff --git a/.argent/flows/bench-part1.yaml b/.argent/flows/bench-part1.yaml new file mode 100644 index 00000000000..f9b6d5d9398 --- /dev/null +++ b/.argent/flows/bench-part1.yaml @@ -0,0 +1,130 @@ +steps: + - echo: On rooms list — open bench-roomview-hooks channel (top row) + - tool: await-ui-element + args: + condition: visible + selector: + text: bench-roomview-hooks + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.218 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: exists + selector: + text: e2e_admin + timeoutMs: 15000 + - echo: In room — scroll up through history until load-more pagination fires + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: await-ui-element + args: + condition: exists + selector: + text: seed message 1 + timeoutMs: 15000 + pollIntervalMs: 500 + - echo: Pagination loaded. Tap composer, type run-tagged message, send + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench a2 msg1 + delayMs: 150 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-send + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5778 + - echo: Dismiss keyboard (swipe down), jump to last message, verify send + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 msg1 + timeoutMs: 15000 +executionPrerequisite: Rooms list visible, bench channel top row. a2 replaced. diff --git a/.argent/flows/bench-part2.template.yaml b/.argent/flows/bench-part2.template.yaml new file mode 100644 index 00000000000..8c04dde71e7 --- /dev/null +++ b/.argent/flows/bench-part2.template.yaml @@ -0,0 +1,52 @@ +steps: + - tool: await-ui-element + args: + condition: visible + selector: + text: Edit + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.586 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 800 + - tool: keyboard + args: + text: ' edited' + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID msg1 edited + timeoutMs: 15000 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID msg1 edited + timeoutMs: 15000 +executionPrerequisite: Action sheet open on Bench RUNID msg1 (long-pressed). diff --git a/.argent/flows/bench-part2.yaml b/.argent/flows/bench-part2.yaml new file mode 100644 index 00000000000..c681039e155 --- /dev/null +++ b/.argent/flows/bench-part2.yaml @@ -0,0 +1,52 @@ +steps: + - tool: await-ui-element + args: + condition: visible + selector: + text: Edit + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.586 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 800 + - tool: keyboard + args: + text: ' edited' + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 msg1 edited + timeoutMs: 15000 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 msg1 edited + timeoutMs: 15000 +executionPrerequisite: Action sheet open on Bench a2 msg1 (long-pressed). diff --git a/.argent/flows/bench-part3.template.yaml b/.argent/flows/bench-part3.template.yaml new file mode 100644 index 00000000000..f2e90518167 --- /dev/null +++ b/.argent/flows/bench-part3.template.yaml @@ -0,0 +1,52 @@ +steps: + - tool: await-ui-element + args: + condition: visible + selector: + text: Quote + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.64 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 800 + - tool: keyboard + args: + text: Bench RUNID quote1 + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID quote1 + timeoutMs: 15000 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID quote1 + timeoutMs: 15000 +executionPrerequisite: Action sheet open on Bench RUNID msg1 edited (long-pressed). diff --git a/.argent/flows/bench-part3.yaml b/.argent/flows/bench-part3.yaml new file mode 100644 index 00000000000..1161531a706 --- /dev/null +++ b/.argent/flows/bench-part3.yaml @@ -0,0 +1,52 @@ +steps: + - tool: await-ui-element + args: + condition: visible + selector: + text: Quote + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.64 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 800 + - tool: keyboard + args: + text: Bench a2 quote1 + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 quote1 + timeoutMs: 15000 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 quote1 + timeoutMs: 15000 +executionPrerequisite: Action sheet open on Bench a2 msg1 edited (long-pressed). diff --git a/.argent/flows/bench-part4.template.yaml b/.argent/flows/bench-part4.template.yaml new file mode 100644 index 00000000000..3764abba82d --- /dev/null +++ b/.argent/flows/bench-part4.template.yaml @@ -0,0 +1,65 @@ +steps: + - tool: await-ui-element + args: + condition: visible + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.749 + - tool: await-ui-element + args: + condition: visible + selector: + text: Add thread reply + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench RUNID reply1 + delayMs: 150 + - wait: 800 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5366 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID reply1 + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.054 + y: 0.097 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID reply1 + timeoutMs: 15000 +executionPrerequisite: Action sheet open on Bench RUNID quote1 (long-pressed). diff --git a/.argent/flows/bench-part4.yaml b/.argent/flows/bench-part4.yaml new file mode 100644 index 00000000000..90d947d3926 --- /dev/null +++ b/.argent/flows/bench-part4.yaml @@ -0,0 +1,65 @@ +steps: + - tool: await-ui-element + args: + condition: visible + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.749 + - tool: await-ui-element + args: + condition: visible + selector: + text: Add thread reply + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench a2 reply1 + delayMs: 150 + - wait: 800 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5366 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 reply1 + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.054 + y: 0.097 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a2 reply1 + timeoutMs: 15000 +executionPrerequisite: Action sheet open on Bench a2 quote1 (long-pressed). diff --git a/.argent/flows/longpress-test.yaml b/.argent/flows/longpress-test.yaml new file mode 100644 index 00000000000..df431d8e4cc --- /dev/null +++ b/.argent/flows/longpress-test.yaml @@ -0,0 +1,27 @@ +steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z msg1 + timeoutMs: 15000 + - long-press: { on: 'Bench a1z msg1', duration: 900 } + - tool: await-ui-element + args: + condition: visible + selector: + text: Edit + timeoutMs: 10000 +executionPrerequisite: In bench room, keyboard open, Bench a1z msg1 sent. diff --git a/.argent/flows/perf-7findings-send-journey.yaml b/.argent/flows/perf-7findings-send-journey.yaml new file mode 100644 index 00000000000..20f8e6c1354 --- /dev/null +++ b/.argent/flows/perf-7findings-send-journey.yaml @@ -0,0 +1,65 @@ +executionPrerequisite: 'iPhone 16 Pro sim (UDID + DCDEBDE5-D487-4115-B93C-2B30CD482C49) booted; app chat.rocket.ios running, + RCT_jsLocation pinned to 127.0.0.1:8083 (this worktree''s Metro — verify with + `defaults read chat.rocket.ios RCT_jsLocation`); logged in as diego.mello2 + (display "Diego Mello") on open.rocket.chat; rooms list visible (E2EE banner + "Digite a senha E2EE" above it) and — VERIFY WITH describe EVERY RUN — the + "Diego Mello" DM row at frame (0, 0.261, 1, 0.086), i.e. row 2 below + react-native-notifications. The list reorders by last activity and WILL drift: + react-native-notifications receives Rocket.Cat webhook posts every few minutes, + and this flow''s own send bumps the DM to row 1 (frame 0, 0.175, 1, 0.086). If + the frame differs, edit the first gesture-tap step''s y to the row''s current + center before running; the DM''s composer draft is + empty; sim keyboard autocorrection AND autocapitalization disabled (`xcrun simctl + spawn defaults write com.apple.Preferences KeyboardAutocorrection -bool + false` + same for KeyboardAutocapitalization, then restart the app) — otherwise + autocorrect rewrites the payload (observed: perf → Please); the prediction bar + stays ON (turning it off shrinks the keyboard and shifts the send button''s y). + For profiling runs: do a + fresh Metro reload (port 8083), let the app settle to idle on the rooms list, + THEN start the profiler, THEN run this flow — reload must NOT happen after + profiler attach.' +steps: + - echo: 'On rooms list — tapping "Diego Mello" DM row (frame 0,0.261,1,0.086 per + prerequisite check; baseline commit #5 cold room open starts here)' + - tool: gesture-tap + args: + udid: DCDEBDE5-D487-4115-B93C-2B30CD482C49 + x: 0.5 + y: 0.304 + - echo: In "Diego Mello" DM at bottom — settling to idle 20s (pre-send baseline; + covers the finding-4 background-burst window ~16-18s after room open) + - tool: screenshot + args: + udid: DCDEBDE5-D487-4115-B93C-2B30CD482C49 + delayMs: 20000 + - echo: Focusing composer "Message @Diego Mello" (frame 0.129,0.906,0.741,0.055) + - tool: gesture-tap + args: + udid: DCDEBDE5-D487-4115-B93C-2B30CD482C49 + x: 0.5 + y: 0.934 + - echo: Typing payload perf-profile-7126 (same text as baseline so the parse-cache + key is comparable) + - tool: keyboard + args: + udid: DCDEBDE5-D487-4115-B93C-2B30CD482C49 + text: perf-profile-7126 + delayMs: 1000 + - echo: Tapping Send "Enviar mensagem" (frame 0.900,0.564,0.060,0.027 with + keyboard open; mic button swaps to send once text present) — starts the + re-profile window's send event + - tool: gesture-tap + args: + udid: DCDEBDE5-D487-4115-B93C-2B30CD482C49 + x: 0.93 + y: 0.578 + delayMs: 1000 + - echo: 'Sent — waiting 2s then screenshot (re-profile window tail: baseline hot + commits land +0.5s and +1.7s after send; expect own message + "Perf-profile-7126" at DM bottom — sim autocap uppercases the P, + deterministic)' + - tool: screenshot + args: + udid: DCDEBDE5-D487-4115-B93C-2B30CD482C49 + delayMs: 2000 diff --git a/.argent/flows/roomopen-ab.yaml b/.argent/flows/roomopen-ab.yaml new file mode 100644 index 00000000000..acdd189723d --- /dev/null +++ b/.argent/flows/roomopen-ab.yaml @@ -0,0 +1,29 @@ +executionPrerequisite: RoomsList visible, logged in as diego.mello2 on + open.rocket.chat, message-variations-tester row at ~y=0.27 +steps: + - tool: gesture-tap + args: + udid: emulator-5554 + x: 0.5 + y: 0.274 + - tool: await-ui-element + args: + udid: emulator-5554 + condition: visible + selector: + identifier: room-view-messages + timeoutMs: 12000 + - tool: gesture-tap + args: + udid: emulator-5554 + x: 0.053 + y: 0.071 + delayMs: 2500 + - tool: await-ui-element + args: + udid: emulator-5554 + condition: visible + selector: + identifier: rooms-list-view-search + timeoutMs: 12000 + delayMs: 1500 diff --git a/.argent/flows/roomview-bench.template.yaml b/.argent/flows/roomview-bench.template.yaml new file mode 100644 index 00000000000..14808522296 --- /dev/null +++ b/.argent/flows/roomview-bench.template.yaml @@ -0,0 +1,329 @@ +steps: + - echo: On rooms list — open bench-roomview-hooks channel (top row) + - tool: await-ui-element + args: + condition: visible + selector: + text: bench-roomview-hooks + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.218 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: exists + selector: + text: e2e_admin + timeoutMs: 15000 + - echo: In room — scroll up through history until load-more pagination fires + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: await-ui-element + args: + condition: exists + selector: + text: seed message 1 + timeoutMs: 15000 + pollIntervalMs: 500 + - echo: Pagination loaded. Tap composer, type run-tagged message, send + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench RUNID msg1 + delayMs: 150 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-send + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5778 + - echo: Dismiss keyboard (swipe down), jump to last message, verify send + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID msg1 + timeoutMs: 15000 + - echo: Edit own message (long-press last message, bottom anchor) + - tool: gesture-custom + args: + events: + - type: Down + x: 0.5 + y: 0.822 + - type: Up + x: 0.5 + y: 0.822 + delayMs: 900 + - tool: await-ui-element + args: + condition: visible + selector: + text: Edit + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.586 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - tool: keyboard + args: + text: ' edited' + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID msg1 edited + timeoutMs: 15000 + - echo: Quote the edited message (dismiss keyboard, jump, long-press last) + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID msg1 edited + timeoutMs: 15000 + - tool: gesture-custom + args: + events: + - type: Down + x: 0.5 + y: 0.822 + - type: Up + x: 0.5 + y: 0.822 + delayMs: 900 + - tool: await-ui-element + args: + condition: visible + selector: + text: Quote + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.64 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 800 + - tool: keyboard + args: + text: Bench RUNID quote1 + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID quote1 + timeoutMs: 15000 + - echo: Reply in thread on the quote message (dismiss keyboard, jump, long-press last) + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID quote1 + timeoutMs: 15000 + - tool: gesture-custom + args: + events: + - type: Down + x: 0.5 + y: 0.822 + - type: Up + x: 0.5 + y: 0.822 + delayMs: 900 + - tool: await-ui-element + args: + condition: visible + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.749 + - tool: await-ui-element + args: + condition: visible + selector: + text: Add thread reply + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench RUNID reply1 + delayMs: 150 + - wait: 800 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5366 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID reply1 + timeoutMs: 15000 + - echo: Back to room, verify reply landed in channel (also-send-to-channel) + - tool: gesture-tap + args: + x: 0.054 + y: 0.097 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench RUNID reply1 + timeoutMs: 15000 +executionPrerequisite: Logged in as e2e_admin on mobile.qa.rocket.chat, rooms + list visible, channel bench-roomview-hooks present with 70+ seed messages and + most recent activity (top row). Placeholder RUNID in flow YAML must be + sed-replaced with a unique run id before execution. diff --git a/.argent/flows/roomview-bench.yaml b/.argent/flows/roomview-bench.yaml new file mode 100644 index 00000000000..8ef3d94cb47 --- /dev/null +++ b/.argent/flows/roomview-bench.yaml @@ -0,0 +1,329 @@ +steps: + - echo: On rooms list — open bench-roomview-hooks channel (top row) + - tool: await-ui-element + args: + condition: visible + selector: + text: bench-roomview-hooks + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.218 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: exists + selector: + text: e2e_admin + timeoutMs: 15000 + - echo: In room — scroll up through history until load-more pagination fires + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: run-sequence + args: + steps: + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.25, toX: 0.5, toY: 0.75, settle: true } + delayMs: 400 + - tool: await-ui-element + args: + condition: exists + selector: + text: seed message 1 + timeoutMs: 15000 + pollIntervalMs: 500 + - echo: Pagination loaded. Tap composer, type run-tagged message, send + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.933 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench a1z msg1 + delayMs: 150 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-send + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5778 + - echo: Dismiss keyboard (swipe down), jump to last message, verify send + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z msg1 + timeoutMs: 15000 + - echo: Edit own message (long-press last message, bottom anchor) + - tool: gesture-custom + args: + events: + - type: Down + x: 0.5 + y: 0.822 + - type: Up + x: 0.5 + y: 0.822 + delayMs: 900 + - tool: await-ui-element + args: + condition: visible + selector: + text: Edit + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.586 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - tool: keyboard + args: + text: ' edited' + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z msg1 edited + timeoutMs: 15000 + - echo: Quote the edited message (dismiss keyboard, jump, long-press last) + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z msg1 edited + timeoutMs: 15000 + - tool: gesture-custom + args: + events: + - type: Down + x: 0.5 + y: 0.822 + - type: Up + x: 0.5 + y: 0.822 + delayMs: 900 + - tool: await-ui-element + args: + condition: visible + selector: + text: Quote + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.64 + - tool: await-ui-element + args: + condition: hidden + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 800 + - tool: keyboard + args: + text: Bench a1z quote1 + delayMs: 150 + - tool: gesture-tap + args: + x: 0.93 + y: 0.578 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z quote1 + timeoutMs: 15000 + - echo: Reply in thread on the quote message (dismiss keyboard, jump, long-press last) + - tool: gesture-swipe + args: { fromX: 0.5, fromY: 0.4, toX: 0.5, toY: 0.85, settle: true } + - tool: await-ui-element + args: + condition: visible + selector: + text: Jump to last message + timeoutMs: 10000 + - tool: gesture-tap + args: + x: 0.9 + y: 0.805 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z quote1 + timeoutMs: 15000 + - tool: gesture-custom + args: + events: + - type: Down + x: 0.5 + y: 0.822 + - type: Up + x: 0.5 + y: 0.822 + delayMs: 900 + - tool: await-ui-element + args: + condition: visible + selector: + text: Reply in thread + timeoutMs: 10000 + - wait: 400 + - tool: gesture-tap + args: + x: 0.5 + y: 0.749 + - tool: await-ui-element + args: + condition: visible + selector: + text: Add thread reply + timeoutMs: 15000 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - wait: 700 + - tool: gesture-tap + args: + x: 0.5 + y: 0.892 + - tool: await-ui-element + args: + condition: visible + selector: + text: return + timeoutMs: 10000 + - tool: keyboard + args: + text: Bench a1z reply1 + delayMs: 150 + - wait: 800 + - tool: gesture-tap + args: + x: 0.9303 + y: 0.5366 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z reply1 + timeoutMs: 15000 + - echo: Back to room, verify reply landed in channel (also-send-to-channel) + - tool: gesture-tap + args: + x: 0.054 + y: 0.097 + - tool: await-ui-element + args: + condition: visible + selector: + identifier: message-composer-actions + timeoutMs: 15000 + - tool: await-ui-element + args: + condition: visible + selector: + text: Bench a1z reply1 + timeoutMs: 15000 +executionPrerequisite: Logged in as e2e_admin on mobile.qa.rocket.chat, rooms + list visible, channel bench-roomview-hooks present with 70+ seed messages and + most recent activity (top row). Placeholder a1z in flow YAML must be + sed-replaced with a unique run id before execution. diff --git a/.argent/flows/roomview-first-mount.yaml b/.argent/flows/roomview-first-mount.yaml new file mode 100644 index 00000000000..60aa7e160f1 --- /dev/null +++ b/.argent/flows/roomview-first-mount.yaml @@ -0,0 +1,16 @@ +executionPrerequisite: RoomsList visible on open.rocket.chat, logged in as + diego.mello, drawer closed. Flow opens the `support` public channel + (non-E2EE), lets RoomView first-mount settle, then navigates back to + RoomsList. Repeat via flow-execute for A/B first-mount profiling. +steps: + - tool: gesture-tap + args: + udid: emulator-5554 + x: 0.5 + y: 0.359 + - tool: gesture-tap + args: + udid: emulator-5554 + x: 0.053 + y: 0.071 + delayMs: 3000 diff --git a/.maestro/tests/room/threads.yaml b/.maestro/tests/room/threads.yaml index 20261bdc51c..f45f17151b4 100644 --- a/.maestro/tests/room/threads.yaml +++ b/.maestro/tests/room/threads.yaml @@ -294,6 +294,14 @@ tags: timeout: 60000 - tapOn: id: 'header-back' +- extendedWaitUntil: + notVisible: + id: 'room-view-title-thread' + timeout: 60000 +- extendedWaitUntil: + visible: + id: 'room-view-title-${output.room.name}' + timeout: 60000 # should create thread delete the message and the thread show the correct number of messages - runFlow: diff --git a/CONTEXT.md b/CONTEXT.md index 1399e8e8b1d..c2bb35fc25c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -2,18 +2,21 @@ ## Rooms & Conversations -| Term | Definition | Aliases to avoid | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | -| **Room** | A server-side conversation container with shared state (name, type, settings) | Chat, conversation | -| **Subscription** | A user's personal relationship to a Room, holding per-user state (unread count, favorite, muted, open) | Membership, room entry | -| **Channel** | A public Room (type `'c'`) visible to all server users | Public room | -| **Group** | A private Room (type `'p'`) visible only to invited members | Private room, private channel | -| **Direct Message** | A 1-on-1 private Room (type `'d'`) between two users | DM, PM, private message | -| **Thread** | A branched conversation spawned from a single Message, identified by `tmid` (thread message id) | Reply chain | -| **Discussion** | A separate Room spawned from a parent Room, identified by `prid` (parent room id) — unlike Threads, Discussions are full Rooms | Sub-room, sub-channel | -| **Team** | An organizational container that groups multiple Channels and users under a single entity | Workspace (ambiguous) | -| **Broadcast Room** | A Room where only authorized users can send Messages; other users can only Reply Broadcast to existing Messages | Broadcast channel | -| **Reply Broadcast** | The action of replying to a Message in a Broadcast Room when the current user cannot send regular Messages | Broadcast reply | +| Term | Definition | Aliases to avoid | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| **Room** | A server-side conversation container with shared state (name, type, settings) | Chat, conversation | +| **Subscription** | A user's personal relationship to a Room, holding per-user state (unread count, favorite, muted, open) | Membership, room entry | +| **Subscribed Room** | A Room the user has joined, backed by a persisted Subscription model. Detected by the presence of a Subscription record. | Joined room | +| **Preview Mode** | Viewing a Room without joining: no Subscription record exists and the Room data comes from navigation params or a REST lookup. Persists until the user joins. Detected by the absence of a Subscription. | — | +| **Invited** | A Room where a Subscription exists but its status is `invited` and not yet accepted. Detected by a Subscription whose status is `invited`. | — | +| **Channel** | A public Room (type `'c'`) visible to all server users | Public room | +| **Group** | A private Room (type `'p'`) visible only to invited members | Private room, private channel | +| **Direct Message** | A 1-on-1 private Room (type `'d'`) between two users | DM, PM, private message | +| **Thread** | A branched conversation spawned from a single Message, identified by `tmid` (thread message id) | Reply chain | +| **Discussion** | A separate Room spawned from a parent Room, identified by `prid` (parent room id) — unlike Threads, Discussions are full Rooms | Sub-room, sub-channel | +| **Team** | An organizational container that groups multiple Channels and users under a single entity | Workspace (ambiguous) | +| **Broadcast Room** | A Room where only authorized users can send Messages; other users can only Reply Broadcast to existing Messages | Broadcast channel | +| **Reply Broadcast** | The action of replying to a Message in a Broadcast Room when the current user cannot send regular Messages | Broadcast reply | ## Messages @@ -108,10 +111,12 @@ Independent boolean markers on a Message, orthogonal to its Status — a Message Two distinct kinds of transient per-Room state drive how the Room view renders Messages. Keep them apart. -| Term | Definition | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------- | -| **Message Action State** | The active Message Action in the Room (Quote, Edit, or React) and its target Message(s); null when none is active | -| **Positional State** | Which Message is highlighted and the jump or scroll position | +| Term | Definition | Owner | Scope | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------- | +| **Message Action State** | The active Message Action in the Room (Quote, Edit, or React) and its target Message(s); null when none is active | A per-Room MessageActionStore | Migrated to the per-Room store as part of the Message row work | +| **Positional State** | Umbrella for which Message is highlighted and the jump or scroll position. Splits across two owners (below). | — | — | +| **Jump orchestration** | Deciding to jump to a Message, resolving its anchor, and requesting the jump | RoomView (`useJumpToMessage`) | Owned by RoomView as part of the room-screen hooks migration | +| **Scroll and highlight execution** | Performing the scroll onto the target Message and rendering its highlight | The List component | Already shipped in the List component | ### Message Actions diff --git a/app/containers/MessageComposer/MessageComposer.test.tsx b/app/containers/MessageComposer/MessageComposer.test.tsx index fdcf1a973e0..dda236c6eac 100644 --- a/app/containers/MessageComposer/MessageComposer.test.tsx +++ b/app/containers/MessageComposer/MessageComposer.test.tsx @@ -12,7 +12,8 @@ import { mockedStore } from '../../reducers/mockedStore'; import { type IPermissionsState } from '../../reducers/permissions'; import { type IMessage, type IShareAttachment, type TMessageActionState } from '../../definitions'; import { colors } from '../../lib/constants/colors'; -import { type IRoomContext, RoomContext } from '../../views/RoomView/context'; +import { type ComposerState } from '../../views/RoomView/definitions'; +import { ComposerProvider } from '../../views/RoomView/stores/ComposerStore'; import { MessageActionProvider } from '../message/stores/MessageActionStore'; import * as EmojiKeyboardHook from './hooks/useEmojiKeyboard'; import { initStore } from '../../lib/store/auxStore'; @@ -122,20 +123,20 @@ const Render = ({ action, children }: { - context?: Partial; + context?: Partial; action?: TMessageActionState; children?: ReactElement; }) => ( - + <> {children} - + ); diff --git a/app/containers/MessageComposer/MessageComposer.tsx b/app/containers/MessageComposer/MessageComposer.tsx index 40ee12556aa..1ed299b705a 100644 --- a/app/containers/MessageComposer/MessageComposer.tsx +++ b/app/containers/MessageComposer/MessageComposer.tsx @@ -4,8 +4,15 @@ import { useBackHandler } from '@react-native-community/hooks'; import { Q } from '@nozbe/watermelondb'; import Animated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; -import { useRoomContext } from '../../views/RoomView/context'; -import { useMessageAction } from '../message/stores/MessageActionStore'; +import { + useComposerRid, + useComposerSharing, + useComposerTmid, + useEditRequest, + useOnSendMessage, + useSetQuotesAndText +} from '../../views/RoomView/stores/ComposerStore'; +import { useEditingMessageId, useMessageAction, useQuotedMessageIds } from '../message/stores/MessageActionStore'; import { Autocomplete } from './components'; import { MIN_HEIGHT } from './constants'; import { @@ -55,8 +62,15 @@ export const MessageComposer = ({ }); const contentHeight = useSharedValue(MIN_HEIGHT); useCloseKeyboardWhenOrientationChanges(); - const { rid, tmid, sharing, editRequest, onSendMessage, setQuotesAndText } = useRoomContext(); + const rid = useComposerRid(); + const tmid = useComposerTmid(); + const sharing = useComposerSharing(); + const editRequest = useEditRequest(); + const onSendMessage = useOnSendMessage(); + const setQuotesAndText = useSetQuotesAndText(); const action = useMessageAction(); + const editingMessageId = useEditingMessageId(); + const quotedMessageIds = useQuotedMessageIds(); const alsoSendThreadToChannel = useAlsoSendThreadToChannel(); const { showEmojiKeyboard, showEmojiSearchbar, openEmojiSearchbar, resetKeyboard, keyboardHeight } = useEmojiKeyboard(); const { setAlsoSendThreadToChannel, setAutocompleteParams, clearAttachments } = useMessageComposerApi(); @@ -110,13 +124,13 @@ export const MessageComposer = ({ const textFromInput = composerInputComponentRef.current.getTextAndClear(); - if (action?.kind === 'edit') { + if (editingMessageId) { const updatedAttachments = attachments.length ? attachments.map(({ description, altText, fileId, filename }) => altTextSupported ? { description: altText || '', fileId, filename } : { description: description || '' } ) : undefined; - editRequest?.({ id: action.messageId, msg: textFromInput, rid, attachments: updatedAttachments }); + editRequest?.({ id: editingMessageId, msg: textFromInput, rid, attachments: updatedAttachments }); clearAttachments(); return; } @@ -124,8 +138,8 @@ export const MessageComposer = ({ if (attachments.length) { let quotedMessage: string | undefined; - if (action?.kind === 'quote') { - quotedMessage = await prepareQuoteMessage(textFromInput, action.messageIds); + if (quotedMessageIds.length) { + quotedMessage = await prepareQuoteMessage(textFromInput, quotedMessageIds); } try { @@ -148,8 +162,8 @@ export const MessageComposer = ({ } } - if (action?.kind === 'quote') { - const quoteMessage = await prepareQuoteMessage(textFromInput, action.messageIds); + if (quotedMessageIds.length) { + const quoteMessage = await prepareQuoteMessage(textFromInput, quotedMessageIds); onSendMessage?.(quoteMessage); return; } diff --git a/app/containers/MessageComposer/MessageComposerContainer.tsx b/app/containers/MessageComposer/MessageComposerContainer.tsx index eb100e1c510..35fe7936120 100644 --- a/app/containers/MessageComposer/MessageComposerContainer.tsx +++ b/app/containers/MessageComposer/MessageComposerContainer.tsx @@ -4,9 +4,10 @@ import { MessageComposerProvider } from './context'; import { type IMessageComposerContainerProps, type IMessageComposerRef } from './interfaces'; import { MessageComposer } from './MessageComposer'; import { EmojiKeyboardProvider } from './hooks/useEmojiKeyboard'; +import { ComposerAttachments } from './components/Attachments/ComposerAttachments'; export const MessageComposerContainer = forwardRef( - ({ children }, ref): ReactElement => { + ({ children = }, ref): ReactElement => { 'use memo'; return ( diff --git a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap index ab660f0a8df..675b7d0e7bb 100644 --- a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap +++ b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap @@ -9488,7 +9488,7 @@ exports[`MessageComposer Toolbar tap emoji 1`] = ` borderless={true} collapsable={false} delayLongPress={600} - handlerTag={11} + handlerTag={-1} handlerType="NativeViewGestureHandler" hitSlop={ { diff --git a/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx b/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx index 176c8406a39..4bf01a9186b 100644 --- a/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx +++ b/app/containers/MessageComposer/components/Autocomplete/Autocomplete.tsx @@ -8,7 +8,8 @@ import { AutocompleteItem } from './AutocompleteItem'; import { useAutocomplete } from '../../hooks'; import { type IAutocompleteItemProps } from '../../interfaces'; import { AutocompletePreview } from './AutocompletePreview'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useComposerRid, useUpdateAutocompleteVisible } from '../../../../views/RoomView/stores/ComposerStore'; +import { useAutocompleteA11yAnnounce } from './useAutocompleteA11yAnnounce'; import { useStyle } from './styles'; export const Autocomplete = ({ @@ -22,7 +23,10 @@ export const Autocomplete = ({ }): ReactElement | null => { 'use memo'; - const { rid, updateAutocompleteVisible } = useRoomContext(); + useAutocompleteA11yAnnounce(); + + const rid = useComposerRid(); + const updateAutocompleteVisible = useUpdateAutocompleteVisible(); const { text, type, params } = useAutocompleteParams(); const items = useAutocomplete({ rid, diff --git a/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.test.tsx b/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.test.tsx new file mode 100644 index 00000000000..4cd3e646db2 --- /dev/null +++ b/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.test.tsx @@ -0,0 +1,75 @@ +import { act, renderHook } from '@testing-library/react-native'; +import { AccessibilityInfo } from 'react-native'; +import { type ReactNode } from 'react'; + +import { useAutocompleteA11yAnnounce } from './useAutocompleteA11yAnnounce'; +import { createComposerStore, ComposerStoreContext } from '../../../../views/RoomView/stores/ComposerStore'; +import { type ComposerStore } from '../../../../views/RoomView/definitions'; + +const externalState = { + rid: 'rid-1', + t: 'c', + tmid: undefined, + room: { rid: 'rid-1', t: 'c' }, + sharing: false, + editCancel: jest.fn(), + editRequest: jest.fn(() => Promise.resolve()), + onRemoveQuoteMessage: jest.fn(), + onSendMessage: jest.fn(), + setQuotesAndText: jest.fn(), + getText: jest.fn(() => '') +}; + +const setup = () => { + const store: ComposerStore = createComposerStore(externalState); + const wrapper = ({ children }: { children: ReactNode }) => ( + {children} + ); + const view = renderHook(() => useAutocompleteA11yAnnounce(), { wrapper }); + return { store, ...view }; +}; + +describe('useAutocompleteA11yAnnounce', () => { + let announceSpy: jest.SpyInstance; + + beforeEach(() => { + jest.useFakeTimers(); + announceSpy = jest.spyOn(AccessibilityInfo, 'announceForAccessibility').mockImplementation(() => {}); + }); + + afterEach(() => { + announceSpy.mockRestore(); + jest.useRealTimers(); + }); + + it('announces 800ms after a false→true transition', () => { + const { store } = setup(); + + act(() => store.getState().updateAutocompleteVisible(true)); + expect(announceSpy).not.toHaveBeenCalled(); + + act(() => jest.advanceTimersByTime(800)); + expect(announceSpy).toHaveBeenCalledTimes(1); + }); + + it('clears the pending timeout when visibility flips back before 800ms', () => { + const { store } = setup(); + + act(() => store.getState().updateAutocompleteVisible(true)); + act(() => jest.advanceTimersByTime(400)); + act(() => store.getState().updateAutocompleteVisible(false)); + + act(() => jest.advanceTimersByTime(800)); + expect(announceSpy).not.toHaveBeenCalled(); + }); + + it('clears the pending timeout on unmount', () => { + const { store, unmount } = setup(); + + act(() => store.getState().updateAutocompleteVisible(true)); + unmount(); + + act(() => jest.advanceTimersByTime(800)); + expect(announceSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts b/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts new file mode 100644 index 00000000000..30dadfa9d1f --- /dev/null +++ b/app/containers/MessageComposer/components/Autocomplete/useAutocompleteA11yAnnounce.ts @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; +import { AccessibilityInfo } from 'react-native'; + +import I18n from '../../../../i18n'; +import { useIsAutocompleteVisible } from '../../../../views/RoomView/stores/ComposerStore'; + +export const useAutocompleteA11yAnnounce = (): void => { + const isAutocompleteVisible = useIsAutocompleteVisible(); + + useEffect(() => { + if (!isAutocompleteVisible) { + return; + } + + // timeout to prevent conflict with default keyboard announcement. + const timeout = setTimeout(() => { + AccessibilityInfo.announceForAccessibility(I18n.t('The_autocomplete_options_are_available_above_the_input_composer')); + }, 800); + + return () => clearTimeout(timeout); + }, [isAutocompleteVisible]); +}; diff --git a/app/containers/MessageComposer/components/Buttons/ActionsButton.tsx b/app/containers/MessageComposer/components/Buttons/ActionsButton.tsx index d9212d13cd2..22484b47af4 100644 --- a/app/containers/MessageComposer/components/Buttons/ActionsButton.tsx +++ b/app/containers/MessageComposer/components/Buttons/ActionsButton.tsx @@ -9,12 +9,14 @@ import Navigation from '../../../../lib/navigation/appNavigation'; import { useMasterDetail } from '../../../../lib/hooks/useMasterDetail'; import { usePermissions } from '../../../../lib/hooks/usePermissions'; import { useCanUploadFile, useChooseMedia } from '../../hooks'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useComposerRid, useComposerTmid, useComposerType } from '../../../../views/RoomView/stores/ComposerStore'; export const ActionsButton = () => { 'use memo'; - const { rid, tmid, t } = useRoomContext(); + const rid = useComposerRid(); + const tmid = useComposerTmid(); + const t = useComposerType(); const { closeEmojiKeyboardAndAction } = useContext(MessageInnerContext); const permissionToUpload = useCanUploadFile(rid); const [permissionToViewCannedResponses] = usePermissions(['view-canned-responses'], rid); diff --git a/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx b/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx index c5a06567f94..792b250a1c4 100644 --- a/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx +++ b/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx @@ -7,7 +7,7 @@ import i18n from '../../../../i18n'; import { useAppSelector } from '../../../../lib/hooks/useAppSelector'; import { openAppSettings } from '../../../../lib/methods/helpers/openAppSettings'; import { useTheme } from '../../../../theme'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useComposerRid, useComposerSharing } from '../../../../views/RoomView/stores/ComposerStore'; import { MessageInnerContext, useComposerAttachments, useMessageComposerApi, useMicOrSend } from '../../context'; import { useCanUploadFile } from '../../hooks'; import { BaseButton } from './BaseButton'; @@ -15,7 +15,8 @@ import { BaseButton } from './BaseButton'; export const MicOrSendButton = (): ReactElement | null => { 'use memo'; - const { rid, sharing } = useRoomContext(); + const rid = useComposerRid(); + const sharing = useComposerSharing(); const micOrSend = useMicOrSend(); const attachments = useComposerAttachments(); const { sendMessage } = useContext(MessageInnerContext); diff --git a/app/containers/MessageComposer/components/CancelEdit.tsx b/app/containers/MessageComposer/components/CancelEdit.tsx index 4b776a86fc9..a0e05db8af8 100644 --- a/app/containers/MessageComposer/components/CancelEdit.tsx +++ b/app/containers/MessageComposer/components/CancelEdit.tsx @@ -1,12 +1,12 @@ import { BaseButton } from './Buttons'; -import { useRoomContext } from '../../../views/RoomView/context'; +import { useEditCancel } from '../../../views/RoomView/stores/ComposerStore'; import { useMessageAction } from '../../message/stores/MessageActionStore'; import { Gap } from './Gap'; export const CancelEdit = () => { 'use memo'; - const { editCancel } = useRoomContext(); + const editCancel = useEditCancel(); const action = useMessageAction(); if (action?.kind !== 'edit') { diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx index a0547bab0a6..e3d32a4f8f6 100644 --- a/app/containers/MessageComposer/components/ComposerInput.tsx +++ b/app/containers/MessageComposer/components/ComposerInput.tsx @@ -1,4 +1,4 @@ -import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef } from 'react'; +import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; import { TextInput, StyleSheet, type TextInputProps, InteractionManager } from 'react-native'; import { useDebouncedCallback } from 'use-debounce'; import { useDispatch } from 'react-redux'; @@ -32,7 +32,13 @@ import { import database from '../../../lib/database'; import Navigation from '../../../lib/navigation/appNavigation'; import { emitter } from '../../../lib/methods/helpers/emitter'; -import { useRoomContext } from '../../../views/RoomView/context'; +import { + useComposerRid, + useComposerRoom, + useComposerSharing, + useComposerTmid, + useSetQuotesAndText +} from '../../../views/RoomView/stores/ComposerStore'; import { useMessageAction } from '../../message/stores/MessageActionStore'; import { getMessageById } from '../../../lib/database/services/Message'; import { generateTriggerId } from '../../../lib/methods/actions'; @@ -48,352 +54,354 @@ import { isExternalKeyboardConnected } from '../../../lib/methods/helpers/extern const defaultSelection: IInputSelection = { start: 0, end: 0 }; -export const ComposerInput = memo( - forwardRef(({ inputRef }, ref) => { - const { colors, theme } = useTheme(); - const { rid, tmid, sharing, setQuotesAndText, room } = useRoomContext(); - const action = useMessageAction(); - const focused = useFocused(); - const { setFocused, setMicOrSend, setAutocompleteParams } = useMessageComposerApi(); - const autocompleteType = useAutocompleteParams()?.type; - const textRef = useRef(''); - const firstRender = useRef(true); - const selectionRef = useRef(defaultSelection); - const dispatch = useDispatch(); - const isMasterDetail = useMasterDetail(); - const altTextSupported = useAltTextSupported(); - let placeholder = tmid ? I18n.t('Add_thread_reply') : ''; - if (room && !tmid) { - placeholder = I18n.t('Message_roomname', { roomName: (room.t === 'd' ? '@' : '#') + getRoomTitle(room) }); - if (!isTablet && placeholder.length > COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH) { - placeholder = `${placeholder.slice(0, COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH)}...`; - } +export const ComposerInput = forwardRef(({ inputRef }, ref) => { + 'use memo'; + + const { colors, theme } = useTheme(); + const rid = useComposerRid(); + const tmid = useComposerTmid(); + const sharing = useComposerSharing(); + const setQuotesAndText = useSetQuotesAndText(); + const room = useComposerRoom(); + const action = useMessageAction(); + const focused = useFocused(); + const { setFocused, setMicOrSend, setAutocompleteParams } = useMessageComposerApi(); + const autocompleteType = useAutocompleteParams()?.type; + const textRef = useRef(''); + const firstRender = useRef(true); + const selectionRef = useRef(defaultSelection); + const dispatch = useDispatch(); + const isMasterDetail = useMasterDetail(); + const altTextSupported = useAltTextSupported(); + let placeholder = tmid ? I18n.t('Add_thread_reply') : ''; + if (room && !tmid) { + placeholder = I18n.t('Message_roomname', { roomName: (room.t === 'd' ? '@' : '#') + getRoomTitle(room) }); + if (!isTablet && placeholder.length > COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH) { + placeholder = `${placeholder.slice(0, COMPOSER_INPUT_PLACEHOLDER_MAX_LENGTH)}...`; } - const route = useRoute>(); - const usedCannedResponse = route.params?.usedCannedResponse; - const prevAction = usePrevious(action); - - // subscribe to changes on mic state to update draft after a message is sent - useMicOrSend(); - const { saveMessageDraft } = useAutoSaveDraft(textRef.current); - - // workaround to handle issues with iOS back swipe navigation - const { iOSBackSwipe } = useIOSBackSwipeHandler(); - - // Draft/Canned Responses - useEffect(() => { - const setDraftMessage = async () => { - const draftMessage = await loadDraftMessage({ rid, tmid }); - if (draftMessage) { - const parsedDraft = parseJson(draftMessage); - if (parsedDraft?.msg || parsedDraft?.quotes) { - setQuotesAndText?.(parsedDraft.msg, parsedDraft.quotes); - } else { - setInput(draftMessage); - } + } + const route = useRoute>(); + const usedCannedResponse = route.params?.usedCannedResponse; + const prevAction = usePrevious(action); + + // subscribe to changes on mic state to update draft after a message is sent + useMicOrSend(); + const { saveMessageDraft } = useAutoSaveDraft(textRef.current); + + // workaround to handle issues with iOS back swipe navigation + const { iOSBackSwipe } = useIOSBackSwipeHandler(); + + // Draft/Canned Responses + useEffect(() => { + const setDraftMessage = async () => { + const draftMessage = await loadDraftMessage({ rid, tmid }); + if (draftMessage) { + const parsedDraft = parseJson(draftMessage); + if (parsedDraft?.msg || parsedDraft?.quotes) { + setQuotesAndText?.(parsedDraft.msg, parsedDraft.quotes); + } else { + setInput(draftMessage); } - }; - - if (action?.kind !== 'edit' && firstRender.current) { - firstRender.current = false; - setDraftMessage(); } - if (sharing) return; - if (usedCannedResponse) setInput(usedCannedResponse); - }, [action?.kind, rid, tmid, usedCannedResponse]); - - // Edit/quote - useEffect(() => { - const fetchMessageAndSetInput = async (messageId: string) => { - const message = await getMessageById(messageId); - if (message) { - setInput(message?.msg || (altTextSupported ? '' : message?.attachments?.[0]?.description || '')); - } - }; - - if (sharing) return; + }; - if (prevAction?.kind === 'edit' && action?.kind !== 'edit') { - setInput(''); - return; - } - if (action?.kind === 'edit') { - focus(); - fetchMessageAndSetInput(action.messageId); - return; - } - if (action?.kind === 'quote' && action.messageIds.length) { - focus(); - } - }, [action]); - - useFocusEffect( - useCallback(() => { - const task = InteractionManager.runAfterInteractions(() => { - emitter.on('addMarkdown', ({ style }) => { - const { start, end } = selectionRef.current; - const text = textRef.current; - const markdown = MARKDOWN_STYLES[style]; - const newText = `${text.substr(0, start)}${markdown}${text.substr(start, end - start)}${markdown}${text.substr(end)}`; - setInput(newText, { - start: start + markdown.length, - end: start === end ? start + markdown.length : end + markdown.length - }); - }); - emitter.on('toolbarMention', () => { - if (autocompleteType) { - return; - } - const { start, end } = selectionRef.current; - const text = textRef.current; - const newText = `${text.substr(0, start)}@${text.substr(start, end - start)}${text.substr(end)}`; - setInput(newText, { start: start + 1, end: start === end ? start + 1 : end + 1 }); - setAutocompleteParams({ text: '', type: '@' }); - }); - }); - return () => { - emitter.off('addMarkdown'); - emitter.off('toolbarMention'); - task?.cancel(); - }; - }, [rid, tmid, autocompleteType]) - ); - - useImperativeHandle(ref, () => ({ - getTextAndClear: () => { - const text = textRef.current; - setInput('', undefined, true); - return text; - }, - getText: () => textRef.current, - getSelection: () => selectionRef.current, - setInput, - onAutocompleteItemSelected, - focus - })); - - const setInput: TSetInput = (text, selection, forceUpdateDraftMessage) => { - const message = text.trim(); - textRef.current = message; - - if (forceUpdateDraftMessage) { - saveMessageDraft(''); + if (action?.kind !== 'edit' && firstRender.current) { + firstRender.current = false; + setDraftMessage(); + } + if (sharing) return; + if (usedCannedResponse) setInput(usedCannedResponse); + }, [action?.kind, rid, tmid, usedCannedResponse]); + + // Edit/quote + useEffect(() => { + const fetchMessageAndSetInput = async (messageId: string) => { + const message = await getMessageById(messageId); + if (message) { + setInput(message?.msg || (altTextSupported ? '' : message?.attachments?.[0]?.description || '')); } + }; - inputRef.current?.setNativeProps?.({ text }); + if (sharing) return; - if (selection) { - // setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set - setTimeout(() => { - inputRef.current?.setSelection?.(selection.start, selection.end); - selectionRef.current = selection; - }, 50); - } - setMicOrSend(message.length === 0 ? 'mic' : 'send'); - }; + if (prevAction?.kind === 'edit' && action?.kind !== 'edit') { + setInput(''); + return; + } + if (action?.kind === 'edit') { + focus(); + fetchMessageAndSetInput(action.messageId); + return; + } + if (action?.kind === 'quote' && action.messageIds.length) { + focus(); + } + }, [action]); - const focus = () => { - setTimeout(() => { - if (inputRef.current) { - inputRef.current.focus(); + useFocusEffect(() => { + const task = InteractionManager.runAfterInteractions(() => { + emitter.on('addMarkdown', ({ style }) => { + const { start, end } = selectionRef.current; + const text = textRef.current; + const markdown = MARKDOWN_STYLES[style]; + const newText = `${text.substr(0, start)}${markdown}${text.substr(start, end - start)}${markdown}${text.substr(end)}`; + setInput(newText, { + start: start + markdown.length, + end: start === end ? start + markdown.length : end + markdown.length + }); + }); + emitter.on('toolbarMention', () => { + if (autocompleteType) { + return; } - }, 300); - }; - - const onChangeText: TextInputProps['onChangeText'] = text => { - textRef.current = text; - debouncedOnChangeText(text); - setInput(text); + const { start, end } = selectionRef.current; + const text = textRef.current; + const newText = `${text.substr(0, start)}@${text.substr(start, end - start)}${text.substr(end)}`; + setInput(newText, { start: start + 1, end: start === end ? start + 1 : end + 1 }); + setAutocompleteParams({ text: '', type: '@' }); + }); + }); + return () => { + emitter.off('addMarkdown'); + emitter.off('toolbarMention'); + task?.cancel(); }; + }); - const onSelectionChange: TextInputProps['onSelectionChange'] = e => { - selectionRef.current = e.nativeEvent.selection; - }; + useImperativeHandle(ref, () => ({ + getTextAndClear: () => { + const text = textRef.current; + setInput('', undefined, true); + return text; + }, + getText: () => textRef.current, + getSelection: () => selectionRef.current, + setInput, + onAutocompleteItemSelected, + focus + })); + + const setInput: TSetInput = (text, selection, forceUpdateDraftMessage) => { + const message = text.trim(); + textRef.current = message; + + if (forceUpdateDraftMessage) { + saveMessageDraft(''); + } - const onFocus: TextInputProps['onFocus'] = () => { - setFocused(true); - }; + inputRef.current?.setNativeProps?.({ text }); - const onTouchStart: TextInputProps['onTouchStart'] = () => { - setFocused(true); - }; + if (selection) { + // setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set + setTimeout(() => { + inputRef.current?.setSelection?.(selection.start, selection.end); + selectionRef.current = selection; + }, 50); + } + setMicOrSend(message.length === 0 ? 'mic' : 'send'); + }; - const onBlur: TextInputProps['onBlur'] = () => { - if (!iOSBackSwipe.current && !isExternalKeyboardConnected()) { - setFocused(false); - stopAutocomplete(); + const focus = () => { + setTimeout(() => { + if (inputRef.current) { + inputRef.current.focus(); } - }; + }, 300); + }; + + const onChangeText: TextInputProps['onChangeText'] = text => { + textRef.current = text; + debouncedOnChangeText(text); + setInput(text); + }; + + const onSelectionChange: TextInputProps['onSelectionChange'] = e => { + selectionRef.current = e.nativeEvent.selection; + }; + + const onFocus: TextInputProps['onFocus'] = () => { + setFocused(true); + }; + + const onTouchStart: TextInputProps['onTouchStart'] = () => { + setFocused(true); + }; + + const onBlur: TextInputProps['onBlur'] = () => { + if (!iOSBackSwipe.current && !isExternalKeyboardConnected()) { + setFocused(false); + stopAutocomplete(); + } + }; - const onAutocompleteItemSelected: IAutocompleteItemProps['onPress'] = async item => { - if (item.type === 'loading') { - return null; - } + const onAutocompleteItemSelected: IAutocompleteItemProps['onPress'] = async item => { + if (item.type === 'loading') { + return null; + } - // If it's slash command preview, we need to execute the command - if (item.type === '/preview') { - try { - if (!rid) return; - const db = database.active; - const commandsCollection = db.get('slash_commands'); - const commandRecord = await commandsCollection.find(item.text); - const { appId } = commandRecord; - const triggerId = generateTriggerId(appId); - executeCommandPreview(item.text, item.params, rid, item.preview, triggerId, tmid); - } catch (e) { - log(e); - } - requestAnimationFrame(() => { - stopAutocomplete(); - setInput('', { start: 0, end: 0 }); - }); - return; + // If it's slash command preview, we need to execute the command + if (item.type === '/preview') { + try { + if (!rid) return; + const db = database.active; + const commandsCollection = db.get('slash_commands'); + const commandRecord = await commandsCollection.find(item.text); + const { appId } = commandRecord; + const triggerId = generateTriggerId(appId); + executeCommandPreview(item.text, item.params, rid, item.preview, triggerId, tmid); + } catch (e) { + log(e); } - - // If it's canned response, but there's no canned responses, we open the canned responses view - if (item.type === '!' && item.id === NO_CANNED_RESPONSES) { - const params = { rid }; - if (isMasterDetail) { - Navigation.navigate('ModalStackNavigator', { screen: 'CannedResponsesListView', params }); - } else { - Navigation.navigate('CannedResponsesListView', params); - } + requestAnimationFrame(() => { stopAutocomplete(); - return; - } + setInput('', { start: 0, end: 0 }); + }); + return; + } - const text = textRef.current; - const { start, end } = selectionRef.current; - const cursor = Math.max(start, end); - const regexp = getMentionRegexp(); - let result = text.substr(0, cursor).replace(regexp, ''); - // Remove the ! after select the canned response - if (item.type === '!') { - const lastIndexOfExclamation = text.lastIndexOf('!', cursor); - result = text.substr(0, lastIndexOfExclamation).replace(regexp, ''); + // If it's canned response, but there's no canned responses, we open the canned responses view + if (item.type === '!' && item.id === NO_CANNED_RESPONSES) { + const params = { rid }; + if (isMasterDetail) { + Navigation.navigate('ModalStackNavigator', { screen: 'CannedResponsesListView', params }); + } else { + Navigation.navigate('CannedResponsesListView', params); } - let mention = ''; - switch (item.type) { - case '@': - mention = fetchIsAllOrHere(item) ? item.title : item.subtitle || item.title; - break; - case '#': - mention = item.subtitle ? item.subtitle : ''; - break; - case ':': - mention = `${typeof item.emoji === 'string' ? item.emoji : item.emoji.name}:`; - break; - case '/': - mention = item.title; - break; - case '!': - mention = item.subtitle ? item.subtitle : ''; - break; - default: - mention = ''; - } - const newText = `${result}${mention} ${text.slice(cursor)}`; + stopAutocomplete(); + return; + } - const newCursor = result.length + mention.length + 1; - setInput(newText, { start: newCursor, end: newCursor }); - focus(); - requestAnimationFrame(() => { - stopAutocomplete(); - }); - }; + const text = textRef.current; + const { start, end } = selectionRef.current; + const cursor = Math.max(start, end); + const regexp = getMentionRegexp(); + let result = text.substr(0, cursor).replace(regexp, ''); + // Remove the ! after select the canned response + if (item.type === '!') { + const lastIndexOfExclamation = text.lastIndexOf('!', cursor); + result = text.substr(0, lastIndexOfExclamation).replace(regexp, ''); + } + let mention = ''; + switch (item.type) { + case '@': + mention = fetchIsAllOrHere(item) ? item.title : item.subtitle || item.title; + break; + case '#': + mention = item.subtitle ? item.subtitle : ''; + break; + case ':': + mention = `${typeof item.emoji === 'string' ? item.emoji : item.emoji.name}:`; + break; + case '/': + mention = item.title; + break; + case '!': + mention = item.subtitle ? item.subtitle : ''; + break; + default: + mention = ''; + } + const newText = `${result}${mention} ${text.slice(cursor)}`; - const stopAutocomplete = () => { - setAutocompleteParams({ text: '', type: null, params: '' }); - }; + const newCursor = result.length + mention.length + 1; + setInput(newText, { start: newCursor, end: newCursor }); + focus(); + requestAnimationFrame(() => { + stopAutocomplete(); + }); + }; - const debouncedOnChangeText = useDebouncedCallback(async (text: string) => { - const isTextEmpty = text.length === 0; - handleTyping(!isTextEmpty); - if (isTextEmpty || !focused) { - stopAutocomplete(); - return; - } - const { start, end } = selectionRef.current; - const cursor = Math.max(start, end); - const whiteSpaceOrBreakLineRegex = /[\s\n]+/; - const txt = - cursor < text.length ? text.substr(0, cursor).split(whiteSpaceOrBreakLineRegex) : text.split(whiteSpaceOrBreakLineRegex); - const lastWord = txt[txt.length - 1]; - const autocompleteText = lastWord.substring(1); - - if (!lastWord) { - stopAutocomplete(); - return; - } - if (!sharing && text.match(/^\//)) { - const commandParameter = text.match(/^\/([a-z0-9._-]+) (.+)/im); - if (commandParameter) { - const db = database.active; - const [, command, params] = commandParameter; - const commandsCollection = db.get('slash_commands'); - try { - const commandRecord = await commandsCollection.find(command); - if (commandRecord.providesPreview) { - setAutocompleteParams({ params, text: command, type: '/preview' }); - } - return; - } catch (e) { - // do nothing + const stopAutocomplete = () => { + setAutocompleteParams({ text: '', type: null, params: '' }); + }; + + const debouncedOnChangeText = useDebouncedCallback(async (text: string) => { + const isTextEmpty = text.length === 0; + handleTyping(!isTextEmpty); + if (isTextEmpty || !focused) { + stopAutocomplete(); + return; + } + const { start, end } = selectionRef.current; + const cursor = Math.max(start, end); + const whiteSpaceOrBreakLineRegex = /[\s\n]+/; + const txt = + cursor < text.length ? text.substr(0, cursor).split(whiteSpaceOrBreakLineRegex) : text.split(whiteSpaceOrBreakLineRegex); + const lastWord = txt[txt.length - 1]; + const autocompleteText = lastWord.substring(1); + + if (!lastWord) { + stopAutocomplete(); + return; + } + if (!sharing && text.match(/^\//)) { + const commandParameter = text.match(/^\/([a-z0-9._-]+) (.+)/im); + if (commandParameter) { + const db = database.active; + const [, command, params] = commandParameter; + const commandsCollection = db.get('slash_commands'); + try { + const commandRecord = await commandsCollection.find(command); + if (commandRecord.providesPreview) { + setAutocompleteParams({ params, text: command, type: '/preview' }); } + return; + } catch (e) { + // do nothing } - setAutocompleteParams({ text: autocompleteText, type: '/' }); - return; - } - if (lastWord.match(/^#/)) { - setAutocompleteParams({ text: autocompleteText, type: '#' }); - return; - } - if (lastWord.match(/^@/)) { - setAutocompleteParams({ text: autocompleteText, type: '@' }); - return; - } - if (lastWord.match(/^:/)) { - setAutocompleteParams({ text: autocompleteText, type: ':' }); - return; } - if (lastWord.match(/^!/) && room?.t === 'l') { - setAutocompleteParams({ text: autocompleteText, type: '!' }); - return; - } - - stopAutocomplete(); - }, textInputDebounceTime); - - const handleTyping = (isTyping: boolean) => { - if (sharing || !rid) return; - dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {})); - }; + setAutocompleteParams({ text: autocompleteText, type: '/' }); + return; + } + if (lastWord.match(/^#/)) { + setAutocompleteParams({ text: autocompleteText, type: '#' }); + return; + } + if (lastWord.match(/^@/)) { + setAutocompleteParams({ text: autocompleteText, type: '@' }); + return; + } + if (lastWord.match(/^:/)) { + setAutocompleteParams({ text: autocompleteText, type: ':' }); + return; + } + if (lastWord.match(/^!/) && room?.t === 'l') { + setAutocompleteParams({ text: autocompleteText, type: '!' }); + return; + } - return ( - { - inputRef.current = component; - }} - blurOnSubmit={false} - onChangeText={onChangeText} - onTouchStart={onTouchStart} - onSelectionChange={onSelectionChange} - onFocus={onFocus} - onBlur={onBlur} - underlineColorAndroid='transparent' - defaultValue='' - multiline - {...(autocompleteType ? { autoComplete: 'off', autoCorrect: false, autoCapitalize: 'none' } : {})} - keyboardAppearance={theme === 'light' ? 'light' : 'dark'} - // eslint-disable-next-line no-nested-ternary - testID={`message-composer-input${tmid ? '-thread' : sharing ? '-share' : ''}`} - /> - ); - }) -); + stopAutocomplete(); + }, textInputDebounceTime); + + const handleTyping = (isTyping: boolean) => { + if (sharing || !rid) return; + dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {})); + }; + + return ( + { + inputRef.current = component; + }} + blurOnSubmit={false} + onChangeText={onChangeText} + onTouchStart={onTouchStart} + onSelectionChange={onSelectionChange} + onFocus={onFocus} + onBlur={onBlur} + underlineColorAndroid='transparent' + defaultValue='' + multiline + {...(autocompleteType ? { autoComplete: 'off', autoCorrect: false, autoCapitalize: 'none' } : {})} + keyboardAppearance={theme === 'light' ? 'light' : 'dark'} + // eslint-disable-next-line no-nested-ternary + testID={`message-composer-input${tmid ? '-thread' : sharing ? '-share' : ''}`} + /> + ); +}); const styles = StyleSheet.create({ textInput: { diff --git a/app/containers/MessageComposer/components/Quotes/Quote.tsx b/app/containers/MessageComposer/components/Quotes/Quote.tsx index 4c28dbc27c0..15d18419d7b 100644 --- a/app/containers/MessageComposer/components/Quotes/Quote.tsx +++ b/app/containers/MessageComposer/components/Quotes/Quote.tsx @@ -3,7 +3,7 @@ import { View, Text } from 'react-native'; import dayjs from '../../../../lib/dayjs'; import { useTheme } from '../../../../theme'; import sharedStyles from '../../../../views/Styles'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useOnRemoveQuoteMessage } from '../../../../views/RoomView/stores/ComposerStore'; import { BaseButton } from '../Buttons'; import { useMessage } from '../../hooks'; import { useAppSelector } from '../../../../lib/hooks/useAppSelector'; @@ -15,7 +15,7 @@ export const Quote = ({ messageId }: { messageId: string }) => { const [styles, colors] = useStyle(); const message = useMessage(messageId); const useRealName = useAppSelector(({ settings }) => settings.UI_Use_Real_Name); - const { onRemoveQuoteMessage } = useRoomContext(); + const onRemoveQuoteMessage = useOnRemoveQuoteMessage(); let username = ''; let msg = ''; diff --git a/app/containers/MessageComposer/components/Quotes/Quotes.tsx b/app/containers/MessageComposer/components/Quotes/Quotes.tsx index e8a54af866d..6819d9da644 100644 --- a/app/containers/MessageComposer/components/Quotes/Quotes.tsx +++ b/app/containers/MessageComposer/components/Quotes/Quotes.tsx @@ -2,13 +2,12 @@ import { useEffect, useRef, type ReactElement } from 'react'; import { FlatList } from 'react-native'; import { Quote } from './Quote'; -import { useMessageAction } from '../../../message/stores/MessageActionStore'; +import { useQuotedMessageIds } from '../../../message/stores/MessageActionStore'; export const Quotes = (): ReactElement | null => { 'use memo'; - const action = useMessageAction(); - const selectedMessages = action?.kind === 'quote' ? action.messageIds : []; + const selectedMessages = useQuotedMessageIds(); const nQuotesRef = useRef(0); const listRef = useRef(null); @@ -21,7 +20,7 @@ export const Quotes = (): ReactElement | null => { nQuotesRef.current = selectedMessages.length; }, [selectedMessages.length]); - if (action?.kind !== 'quote') { + if (!selectedMessages.length) { return null; } diff --git a/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx b/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx index 296505efd85..0e602c5f0e3 100644 --- a/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx +++ b/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx @@ -16,7 +16,7 @@ import { RECORDING_EXTENSION, RECORDING_MODE, RECORDING_SETTINGS } from '../../. import { useAppSelector } from '../../../../lib/hooks/useAppSelector'; import log from '../../../../lib/methods/helpers/log'; import { type IUpload } from '../../../../definitions'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useComposerRid, useComposerTmid } from '../../../../views/RoomView/stores/ComposerStore'; import { useCanUploadFile } from '../../hooks'; import { Duration, type IDurationRef } from './Duration'; import AudioPlayer from '../../../AudioPlayer'; @@ -30,7 +30,8 @@ export const RecordAudio = (): ReactElement | null => { const numberOfTriesRef = useRef(0); const [status, setStatus] = useState<'recording' | 'reviewing'>('recording'); const { setRecordingAudio } = useMessageComposerApi(); - const { rid, tmid } = useRoomContext(); + const rid = useComposerRid(); + const tmid = useComposerTmid(); const server = useAppSelector(state => state.server.server); const user = useAppSelector(state => ({ id: state.login.user.id, token: state.login.user.token }), shallowEqual); const permissionToUpload = useCanUploadFile(rid); diff --git a/app/containers/MessageComposer/components/SendThreadToChannel.tsx b/app/containers/MessageComposer/components/SendThreadToChannel.tsx index b9e32127836..23157bf997a 100644 --- a/app/containers/MessageComposer/components/SendThreadToChannel.tsx +++ b/app/containers/MessageComposer/components/SendThreadToChannel.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef, type ReactElement } from 'react'; import { type Subscription } from 'rxjs'; import { Q } from '@nozbe/watermelondb'; -import { useRoomContext } from '../../../views/RoomView/context'; +import { useComposerTmid } from '../../../views/RoomView/stores/ComposerStore'; import { useAlsoSendThreadToChannel, useMessageComposerApi } from '../context'; import { CustomIcon } from '../../CustomIcon'; import { useTheme } from '../../../theme'; @@ -21,7 +21,7 @@ export const SendThreadToChannel = (): ReactElement | null => { const alsoSendThreadToChannel = useAlsoSendThreadToChannel(); const { setAlsoSendThreadToChannel } = useMessageComposerApi(); const { showEmojiSearchbar } = useEmojiKeyboard(); - const { tmid } = useRoomContext(); + const tmid = useComposerTmid(); const { colors } = useTheme(); const subscription = useRef(null); const alsoSendThreadToChannelUserPref = useAppSelector(state => state.login.user.alsoSendThreadToChannel); diff --git a/app/containers/MessageComposer/components/Toolbar/Default.tsx b/app/containers/MessageComposer/components/Toolbar/Default.tsx index ef9907f9745..52ce203f78c 100644 --- a/app/containers/MessageComposer/components/Toolbar/Default.tsx +++ b/app/containers/MessageComposer/components/Toolbar/Default.tsx @@ -5,13 +5,13 @@ import { ActionsButton, BaseButton } from '..'; import { useMessageComposerApi } from '../../context'; import { Gap } from '../Gap'; import { emitter } from '../../../../lib/methods/helpers/emitter'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useComposerSharing } from '../../../../views/RoomView/stores/ComposerStore'; import { useEmojiKeyboard } from '../../hooks/useEmojiKeyboard'; export const Default = (): ReactElement | null => { 'use memo'; - const { sharing } = useRoomContext(); + const sharing = useComposerSharing(); const { setMarkdownToolbar } = useMessageComposerApi(); const { openEmojiKeyboard } = useEmojiKeyboard(); diff --git a/app/containers/MessageComposer/components/Unfocused/Left.tsx b/app/containers/MessageComposer/components/Unfocused/Left.tsx index 5ad9107b0da..a41c60cbd08 100644 --- a/app/containers/MessageComposer/components/Unfocused/Left.tsx +++ b/app/containers/MessageComposer/components/Unfocused/Left.tsx @@ -5,12 +5,12 @@ import { useFocused } from '../../context'; import { useEmojiKeyboard } from '../../hooks/useEmojiKeyboard'; import { ActionsButton } from '../Buttons'; import { MIN_HEIGHT } from '../../constants'; -import { useRoomContext } from '../../../../views/RoomView/context'; +import { useComposerSharing } from '../../../../views/RoomView/stores/ComposerStore'; export const Left = (): ReactElement | null => { 'use memo'; - const { sharing } = useRoomContext(); + const sharing = useComposerSharing(); const focused = useFocused(); const { showEmojiKeyboard, showEmojiSearchbar } = useEmojiKeyboard(); if (focused || showEmojiKeyboard || showEmojiSearchbar || sharing) { diff --git a/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts b/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts index 4c3d538c704..dbea6f24c2e 100644 --- a/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts +++ b/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts @@ -2,7 +2,7 @@ import { useRoute } from '@react-navigation/native'; import { useCallback, useEffect, useRef } from 'react'; import { saveDraftMessage } from '../../../lib/methods/draftMessage'; -import { useRoomContext } from '../../../views/RoomView/context'; +import { useComposerRid, useComposerTmid } from '../../../views/RoomView/stores/ComposerStore'; import { useMessageAction } from '../../message/stores/MessageActionStore'; import { useFocused } from '../context'; @@ -10,7 +10,8 @@ export const useAutoSaveDraft = (text = '') => { 'use memo'; const route = useRoute(); - const { rid, tmid } = useRoomContext(); + const rid = useComposerRid(); + const tmid = useComposerTmid(); const action = useMessageAction(); const focused = useFocused(); const oldText = useRef(''); diff --git a/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx b/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx index da1ddb62bae..ce9220ae973 100644 --- a/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx +++ b/app/containers/MessageComposer/hooks/useChooseMedia.test.tsx @@ -14,13 +14,21 @@ jest.mock('../context', () => ({ useMessageComposerApi: jest.fn() })); -jest.mock('../../../views/RoomView/context', () => ({ - useRoomContext: jest.fn() +jest.mock('../../../views/RoomView/stores/ComposerStore', () => ({ + useSetQuotesAndText: jest.fn(), + useGetText: jest.fn() })); -jest.mock('../../message/stores/MessageActionStore', () => ({ - useMessageAction: jest.fn() -})); +jest.mock('../../message/stores/MessageActionStore', () => { + const useMessageAction = jest.fn(); + return { + useMessageAction, + useQuotedMessageIds: () => { + const action = useMessageAction(); + return action?.kind === 'quote' ? action.messageIds : []; + } + }; +}); jest.mock('../../../lib/hooks/useAltTextSupported', () => ({ useAltTextSupported: jest.fn() @@ -49,7 +57,8 @@ jest.mock('../../../lib/methods/helpers/ImagePicker/ImagePicker', () => ({ const mockGetDocumentAsync = require('expo-document-picker').getDocumentAsync as jest.Mock; const mockUseAppSelector = require('../../../lib/hooks/useAppSelector').useAppSelector as jest.Mock; const mockUseMessageComposerApi = require('../context').useMessageComposerApi as jest.Mock; -const mockUseRoomContext = require('../../../views/RoomView/context').useRoomContext as jest.Mock; +const mockUseSetQuotesAndText = require('../../../views/RoomView/stores/ComposerStore').useSetQuotesAndText as jest.Mock; +const mockUseGetText = require('../../../views/RoomView/stores/ComposerStore').useGetText as jest.Mock; const mockUseMessageAction = require('../../message/stores/MessageActionStore').useMessageAction as jest.Mock; const mockUseAltTextSupported = require('../../../lib/hooks/useAltTextSupported').useAltTextSupported as jest.Mock; const mockGetSubscriptionByRoomId = require('../../../lib/database/services/Subscription').getSubscriptionByRoomId as jest.Mock; @@ -71,10 +80,8 @@ describe('useChooseMedia', () => { }) ); mockUseMessageComposerApi.mockReturnValue({ addAttachments }); - mockUseRoomContext.mockReturnValue({ - setQuotesAndText: jest.fn(), - getText: jest.fn(() => 'draft') - }); + mockUseSetQuotesAndText.mockReturnValue(jest.fn()); + mockUseGetText.mockReturnValue(jest.fn(() => 'draft')); mockUseMessageAction.mockReturnValue(null); mockGetSubscriptionByRoomId.mockResolvedValue({ rid: 'room-id', t: 'c' }); mockGetThreadById.mockResolvedValue({ id: 'thread-id' }); diff --git a/app/containers/MessageComposer/hooks/useChooseMedia.ts b/app/containers/MessageComposer/hooks/useChooseMedia.ts index 626737fd53f..e95e3e5ad44 100644 --- a/app/containers/MessageComposer/hooks/useChooseMedia.ts +++ b/app/containers/MessageComposer/hooks/useChooseMedia.ts @@ -9,8 +9,8 @@ import { getSubscriptionByRoomId } from '../../../lib/database/services/Subscrip import { getThreadById } from '../../../lib/database/services/Thread'; import Navigation from '../../../lib/navigation/appNavigation'; import { useAppSelector } from '../../../lib/hooks/useAppSelector'; -import { useRoomContext } from '../../../views/RoomView/context'; -import { useMessageAction } from '../../message/stores/MessageActionStore'; +import { useGetText, useSetQuotesAndText } from '../../../views/RoomView/stores/ComposerStore'; +import { useMessageAction, useQuotedMessageIds } from '../../message/stores/MessageActionStore'; import { type IShareAttachment } from '../../../definitions'; import ImagePicker, { type ImageOrVideo } from '../../../lib/methods/helpers/ImagePicker/ImagePicker'; import { useMessageComposerApi } from '../context'; @@ -32,8 +32,10 @@ export const useChooseMedia = ({ const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = useAppSelector(state => state.settings); const { addAttachments } = useMessageComposerApi(); - const { setQuotesAndText, getText } = useRoomContext(); + const setQuotesAndText = useSetQuotesAndText(); + const getText = useGetText(); const action = useMessageAction(); + const quotedMessageIds = useQuotedMessageIds(); const altTextSupported = useAltTextSupported(); const allowList = FileUpload_MediaTypeWhiteList as string; const maxFileSize = FileUpload_MaxFileSize as number; @@ -96,9 +98,8 @@ export const useChooseMedia = ({ const startShareView = () => { const text = getText?.() || ''; - const selectedMessages = action?.kind === 'quote' ? action.messageIds : []; return { - selectedMessages, + selectedMessages: quotedMessageIds, text }; }; diff --git a/app/containers/MessageComposer/index.tsx b/app/containers/MessageComposer/index.tsx index 08eb216d93f..e4096f60c34 100644 --- a/app/containers/MessageComposer/index.tsx +++ b/app/containers/MessageComposer/index.tsx @@ -1,3 +1,2 @@ export * from './interfaces'; export * from './MessageComposerContainer'; -export { ComposerAttachments } from './components/Attachments/ComposerAttachments'; diff --git a/app/containers/UIKit/UiKitModal.stories.tsx b/app/containers/UIKit/UiKitModal.stories.tsx index adee3d2d256..74b60d5a771 100644 --- a/app/containers/UIKit/UiKitModal.stories.tsx +++ b/app/containers/UIKit/UiKitModal.stories.tsx @@ -11,7 +11,7 @@ export default { title: 'UIKit/UiKitModal', decorators: [ (Story: any) => ( - + diff --git a/app/containers/message/components/Attachments/Attachments.tsx b/app/containers/message/components/Attachments/Attachments.tsx index 0eb95137c5d..897dae671e7 100644 --- a/app/containers/message/components/Attachments/Attachments.tsx +++ b/app/containers/message/components/Attachments/Attachments.tsx @@ -7,8 +7,8 @@ import Video from './Video'; import CollapsibleQuote from './CollapsibleQuote'; import AttachedActions from './AttachedActions'; import Reply from './Reply'; -import { useShowAttachment } from '../../stores/MessageRoomStore'; import { useMessageField, useTranslateLanguage } from '../../stores/MessageStore'; +import { useShowAttachment } from '../../stores/MessageRoomStore'; import { type IMessageAttachments } from '../../interfaces'; import { getMessageFromAttachment } from '../../utils'; import { getAttachmentKey, isContentAttachment } from './utils'; diff --git a/app/containers/message/components/Blocks.ts b/app/containers/message/components/Blocks.ts index f8897a04a56..ecc4e284184 100644 --- a/app/containers/message/components/Blocks.ts +++ b/app/containers/message/components/Blocks.ts @@ -1,7 +1,7 @@ import { createElement } from 'react'; import { messageBlockWithContext } from '../../UIKit/MessageBlock'; -import { useBlockAction, useRid } from '../stores/MessageRoomStore'; +import { useRid, useBlockAction } from '../stores/MessageRoomStore'; import { useBlocks } from '../stores/MessageStore'; const Blocks = () => { diff --git a/app/containers/message/components/MessageA11yIndex.tsx b/app/containers/message/components/MessageA11yIndex.tsx new file mode 100644 index 00000000000..c6125829950 --- /dev/null +++ b/app/containers/message/components/MessageA11yIndex.tsx @@ -0,0 +1,44 @@ +import { type ReactElement, type ReactNode } from 'react'; +import { View, type StyleProp, type ViewStyle } from 'react-native'; +import { A11y } from 'react-native-a11y-order'; + +import { useA11yGate } from '../stores/A11yGate'; + +interface IMessageA11yIndexProps { + index: number; + accessible?: boolean; + accessibilityLabel?: string; + accessibilityLanguage?: string; + style?: StyleProp; + children: ReactNode; +} + +const MessageA11yIndex = ({ + index, + accessible, + accessibilityLabel, + accessibilityLanguage, + style, + children +}: IMessageA11yIndexProps): ReactElement => { + 'use memo'; + + const enabled = useA11yGate(); + + if (!enabled) { + return style ? {children} : <>{children}; + } + + return ( + + {children} + + ); +}; + +export default MessageA11yIndex; diff --git a/app/containers/message/components/MessageA11yOrder.tsx b/app/containers/message/components/MessageA11yOrder.tsx new file mode 100644 index 00000000000..3e2b6531a03 --- /dev/null +++ b/app/containers/message/components/MessageA11yOrder.tsx @@ -0,0 +1,16 @@ +import { type ReactElement, type ReactNode } from 'react'; +import { A11y } from 'react-native-a11y-order'; + +import { useA11yGate } from '../stores/A11yGate'; + +const MessageA11yOrder = ({ children }: { children: ReactNode }): ReactElement => { + 'use memo'; + + const enabled = useA11yGate(); + + if (!enabled) return <>{children}; + + return {children}; +}; + +export default MessageA11yOrder; diff --git a/app/containers/message/components/MessageAccessibleIndex.tsx b/app/containers/message/components/MessageAccessibleIndex.tsx index cc1fd812da0..6d3e8a8c784 100644 --- a/app/containers/message/components/MessageAccessibleIndex.tsx +++ b/app/containers/message/components/MessageAccessibleIndex.tsx @@ -1,9 +1,9 @@ import { type ReactNode } from 'react'; import { type StyleProp, type ViewStyle } from 'react-native'; -import { A11y } from 'react-native-a11y-order'; import { useMessageText } from '../stores/MessageStore'; import { useAutoTranslate } from '../stores/MessageRoomStore'; +import MessageA11yIndex from './MessageA11yIndex'; const MessageAccessibleIndex = ({ style, children }: { style?: StyleProp; children: ReactNode }) => { 'use memo'; @@ -12,14 +12,14 @@ const MessageAccessibleIndex = ({ style, children }: { style?: StyleProp {children} - + ); }; diff --git a/app/containers/message/components/Preview.tsx b/app/containers/message/components/Preview.tsx index 7e3c22910be..5d2f7db30ff 100644 --- a/app/containers/message/components/Preview.tsx +++ b/app/containers/message/components/Preview.tsx @@ -1,26 +1,17 @@ -import { shallowEqual } from 'react-redux'; - import Message from '../index'; import { MessageRoomProvider } from '../stores/MessageRoomStore'; -import { useAppSelector } from '../../../lib/hooks/useAppSelector'; -import { getUserSelector } from '../../../selectors/login'; +import { A11yGateProvider } from '../stores/A11yGate'; import { type TAnyMessageModel } from '../../../definitions'; const MessagePreview = ({ message }: { message: TAnyMessageModel }) => { 'use memo'; - const { user, baseUrl } = useAppSelector( - state => ({ - user: getUserSelector(state), - baseUrl: state.server.server - }), - shallowEqual - ); - return ( - - - + + + + + ); }; diff --git a/app/containers/message/components/Reactions.tsx b/app/containers/message/components/Reactions.tsx index 9129bc17e17..d4a204da3bd 100644 --- a/app/containers/message/components/Reactions.tsx +++ b/app/containers/message/components/Reactions.tsx @@ -8,7 +8,7 @@ import Emoji from './Emoji'; import { BUTTON_HIT_SLOP } from '../utils'; import { useTheme } from '../../../theme'; import { useMessageId, useMessageItem, useReactions } from '../stores/MessageStore'; -import { useMessageUser, useOnReactionLongPress, useOnReactionPress, useReactionInit } from '../stores/MessageRoomStore'; +import { useMessageUser, useReactionInit, useOnReactionPress, useOnReactionLongPress } from '../stores/MessageRoomStore'; interface IReaction { _id: string; diff --git a/app/containers/message/components/Thread.tsx b/app/containers/message/components/Thread.tsx index fdfbd0d8360..10f1bc187ba 100644 --- a/app/containers/message/components/Thread.tsx +++ b/app/containers/message/components/Thread.tsx @@ -15,10 +15,10 @@ const Thread = () => { const item = useMessageItem(); const isThreadRoom = useIsThreadRoom(); const threadBadgeColor = useThreadBadgeColor(); + const onThreadPress = useOnThreadPress(); const toggleFollowThread = useToggleFollowThread(); const user = useMessageUser(); const replies = useReplies(); - const onThreadPress = useOnThreadPress(); const { tcount, tlm, id } = useThreadData(); const { messageText } = useMessageText(); diff --git a/app/containers/message/components/Touchable/MessageTouchable.tsx b/app/containers/message/components/Touchable/MessageTouchable.tsx index e4e39c43855..d675b369639 100644 --- a/app/containers/message/components/Touchable/MessageTouchable.tsx +++ b/app/containers/message/components/Touchable/MessageTouchable.tsx @@ -1,6 +1,6 @@ -import { A11y } from 'react-native-a11y-order'; - import { useTheme } from '../../../../theme'; +import MessageA11yOrder from '../MessageA11yOrder'; +import MessageA11yIndex from '../MessageA11yIndex'; import Touch from './Touch'; import Message, { type TMessageProps } from '../Message/Message'; import { useLastFocusedMessageRef } from '../../../../lib/a11y/useLastFocusedMessageRef'; @@ -10,7 +10,7 @@ import { useMessageAccessibilityHint } from '../../hooks/useMessageAccessibility import { useIsBeingEdited } from '../../stores/MessageActionStore'; import { useIsInfoMessage, - useMessageField, + useMessageId, useMessageLongPress, useMessagePress, useMessageStatus, @@ -25,7 +25,7 @@ const MessageTouchable = (props: TMessageProps) => { const isInfo = useIsInfoMessage(); const { hasError } = useMessageStatus(); const { tappable } = useMessageTouchable(); - const id = useMessageField(item => item.id); + const id = useMessageId(); const isBeingEdited = useIsBeingEdited(id); const onPressAction = useMessagePress(); const onLongPress = useMessageLongPress(); @@ -43,9 +43,9 @@ const MessageTouchable = (props: TMessageProps) => { if (hasError || isInfo) { return ( - + - + ); } @@ -55,8 +55,8 @@ const MessageTouchable = (props: TMessageProps) => { }; return ( - - + + { }}> - - + + ); }; diff --git a/app/containers/message/components/__tests__/Blocks.test.tsx b/app/containers/message/components/__tests__/Blocks.test.tsx index 4c1b7595635..077800c5a9a 100644 --- a/app/containers/message/components/__tests__/Blocks.test.tsx +++ b/app/containers/message/components/__tests__/Blocks.test.tsx @@ -56,7 +56,7 @@ describe('Blocks', () => { it("calls blockAction with the wired params and rid defaulted to '' when absent", async () => { const blockAction = jest.fn(); - renderBlocks([{ appId: 'app-1' }] as TAnyMessageModel['blocks'], { blockAction }); + renderBlocks([{ appId: 'app-1' }] as TAnyMessageModel['blocks'], { handlers: { blockAction } }); const { action } = messageBlockWithContext.mock.calls[0][0]; await action({ actionId: 'submit', value: 'v', blockId: 'block-1' }); diff --git a/app/containers/message/components/__tests__/CollapsibleQuote.test.tsx b/app/containers/message/components/__tests__/CollapsibleQuote.test.tsx index c2f1a9ba02a..79549c29ffb 100644 --- a/app/containers/message/components/__tests__/CollapsibleQuote.test.tsx +++ b/app/containers/message/components/__tests__/CollapsibleQuote.test.tsx @@ -38,7 +38,6 @@ const initialMockedStoreState = () => { initialMockedStoreState(); const contextValue: Partial = { - user: { username: 'Marcos' }, timeFormat: 'LT' }; diff --git a/app/containers/message/components/__tests__/Content.test.tsx b/app/containers/message/components/__tests__/Content.test.tsx index 6289edc307c..36258c00c88 100644 --- a/app/containers/message/components/__tests__/Content.test.tsx +++ b/app/containers/message/components/__tests__/Content.test.tsx @@ -53,7 +53,6 @@ const buildItem = ( const tree = (overrides: TOverrides) => { const { msg, attachments, isEncrypted, autoTranslateLanguage, tmid, isIgnored } = overrides; const contextValue: Partial = { - user: { username: 'john' }, navToRoomInfo: jest.fn(), autoTranslateLanguage }; diff --git a/app/containers/message/components/__tests__/ImageContainer.test.tsx b/app/containers/message/components/__tests__/ImageContainer.test.tsx index 0483324a1c5..1a4ee89d7b9 100644 --- a/app/containers/message/components/__tests__/ImageContainer.test.tsx +++ b/app/containers/message/components/__tests__/ImageContainer.test.tsx @@ -36,10 +36,7 @@ jest.mock('../../../../lib/hooks/useAltTextSupported', () => ({ const renderImageContainer = (props?: Partial>) => { const id = 'message-id'; - const contextValue: Partial = { - baseUrl: 'https://open.rocket.chat', - user: { id: 'user-id', username: 'rocket.cat', token: 'token' } - }; + const contextValue: Partial = {}; const item = { id } as unknown as TAnyMessageModel; return render( diff --git a/app/containers/message/components/__tests__/JitsiLayout.test.tsx b/app/containers/message/components/__tests__/JitsiLayout.test.tsx index bfaae1594a8..7021d3ae203 100644 --- a/app/containers/message/components/__tests__/JitsiLayout.test.tsx +++ b/app/containers/message/components/__tests__/JitsiLayout.test.tsx @@ -3,7 +3,7 @@ import { render } from '@testing-library/react-native'; import JitsiLayout from '../Layout/JitsiLayout'; import { MessageProvider } from '../../stores/MessageStore'; -import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; +import { MessageRoomProvider } from '../../stores/MessageRoomStore'; import { mockedStore } from '../../../../reducers/mockedStore'; import { type TAnyMessageModel } from '../../../../definitions'; import I18n from '../../../../i18n'; @@ -15,18 +15,16 @@ const item = { u: { _id: 'author-id', username: 'diego.mello' } } as unknown as TAnyMessageModel; -const renderJitsiLayout = () => { - const room: Partial = { handleEnterCall: jest.fn() }; - return render( +const renderJitsiLayout = () => + render( - + ); -}; describe('JitsiLayout', () => { test('renders the localized "started a call" line AND the join button for a jitsi_call_started message', () => { diff --git a/app/containers/message/components/__tests__/MessageA11yGate.test.tsx b/app/containers/message/components/__tests__/MessageA11yGate.test.tsx new file mode 100644 index 00000000000..2dc4690a335 --- /dev/null +++ b/app/containers/message/components/__tests__/MessageA11yGate.test.tsx @@ -0,0 +1,133 @@ +import { Text } from 'react-native'; +import { render } from '@testing-library/react-native'; + +import { type TAnyMessageModel } from '../../../../definitions'; +import { A11yGateContext } from '../../stores/A11yGate'; +import { type MessageRoomState } from '../../stores/MessageRoomStore'; +import MessageTouchable from '../Touchable/MessageTouchable'; +import MessageAccessibleIndex from '../MessageAccessibleIndex'; +import { MessageProviders } from '../../__tests__/testHelpers'; + +// Render the react-native-a11y-order Fabric views as plain testID-bearing Views so the gating +// seam is observable from the tree. This is the only thing mocked; the gate value is driven purely +// through A11yGateContext, never by mocking useIsAccessibilityNavigationEnabled. +jest.mock('react-native-a11y-order', () => { + const { View: RNView } = require('react-native'); + return { + A11y: { + Order: ({ children, ...props }: any) => ( + + {children} + + ), + Index: ({ children, index, ...props }: any) => ( + + {children} + + ) + } + }; +}); + +// Touch pulls in gesture-handler internals irrelevant to the a11y seam; render a plain wrapper instead. +jest.mock('../Touchable/Touch', () => { + const { forwardRef } = require('react'); + const { View: RNView } = require('react-native'); + return forwardRef(({ children, ...props }: any, ref: any) => ( + + {children} + + )); +}); + +const createMockMessage = (overrides: Record = {}): TAnyMessageModel => + ({ + id: 'msg-1', + msg: 'Hello World', + t: undefined, + ts: new Date(), + u: { _id: 'user-1', username: 'testuser', name: 'Test User' }, + groupable: true, + experimentalSubscribe: jest.fn(() => jest.fn()), + ...overrides + } as unknown as TAnyMessageModel); + +const room: Partial = { rid: 'room-1' }; + +const renderTouchable = (gate: boolean, itemOverrides: Record = {}) => + render( + + + + + + ); + +describe('per-row a11y wrapper gating', () => { + describe('MessageTouchable — normal message', () => { + it('gate false: no A11y.Order / A11y.Index, message still renders', () => { + const { queryByTestId } = renderTouchable(false); + expect(queryByTestId('a11y-order')).toBeNull(); + expect(queryByTestId('a11y-index-1')).toBeNull(); + expect(queryByTestId('message-msg-1')).toBeTruthy(); + }); + + it('gate true: wraps in A11y.Order and A11y.Index index=1', () => { + const { getByTestId } = renderTouchable(true); + expect(getByTestId('a11y-order')).toBeTruthy(); + expect(getByTestId('a11y-index-1')).toBeTruthy(); + expect(getByTestId('message-msg-1')).toBeTruthy(); + }); + }); + + describe('MessageTouchable — info message (Order-only branch)', () => { + it('gate false: no A11y.Order', () => { + const { queryByTestId } = renderTouchable(false, { t: 'uj' }); + expect(queryByTestId('a11y-order')).toBeNull(); + expect(queryByTestId('a11y-index-1')).toBeNull(); + }); + + it('gate true: A11y.Order present, no A11y.Index', () => { + const { getByTestId, queryByTestId } = renderTouchable(true, { t: 'uj' }); + expect(getByTestId('a11y-order')).toBeTruthy(); + expect(queryByTestId('a11y-index-1')).toBeNull(); + }); + }); + + describe('MessageAccessibleIndex — index=2', () => { + const renderIndex = (gate: boolean, style?: object) => + render( + + + + child + + + + ); + + it('gate false with style: plain View keeps the style, no A11y.Index', () => { + const { queryByTestId, getByTestId, toJSON } = renderIndex(false, { flex: 1 }); + expect(queryByTestId('a11y-index-2')).toBeNull(); + expect(getByTestId('index-child')).toBeTruthy(); + expect(toJSON()).toMatchObject({ type: 'View', props: { style: { flex: 1 } } }); + }); + + it('gate false unstyled: child renders directly, no wrapper', () => { + const { queryByTestId, getByTestId } = renderIndex(false); + expect(queryByTestId('a11y-index-2')).toBeNull(); + expect(getByTestId('index-child')).toBeTruthy(); + }); + + it('gate true: A11y.Index index=2 forwards translation-aware props and style', () => { + const { getByTestId } = renderIndex(true, { flex: 1 }); + const index = getByTestId('a11y-index-2'); + expect(index.props.accessibilityLanguage).toBe('pt-BR'); + expect(typeof index.props.accessible).toBe('boolean'); + expect('accessibilityLabel' in index.props).toBe(true); + expect(index.props.style).toEqual({ flex: 1 }); + }); + }); +}); diff --git a/app/containers/message/components/__tests__/Quote.test.tsx b/app/containers/message/components/__tests__/Quote.test.tsx index d565fcfc5d6..f9750afb373 100644 --- a/app/containers/message/components/__tests__/Quote.test.tsx +++ b/app/containers/message/components/__tests__/Quote.test.tsx @@ -45,8 +45,6 @@ const buildItem = () => ({ id: 'msg-1' } as unknown as TAnyMessageModel); const renderQuote = (attachments: IAttachment[], ctx: Partial = {}) => { const contextValue: Partial = { - user: { id: 'user-1', username: 'john', token: 'token' }, - baseUrl: 'https://open.rocket.chat', timeFormat: 'HH:mm', ...ctx }; @@ -79,10 +77,7 @@ describe('Quote', () => { rerender( - + diff --git a/app/containers/message/components/__tests__/Reactions.test.tsx b/app/containers/message/components/__tests__/Reactions.test.tsx index 33ad050df72..3fecfb0252d 100644 --- a/app/containers/message/components/__tests__/Reactions.test.tsx +++ b/app/containers/message/components/__tests__/Reactions.test.tsx @@ -4,7 +4,7 @@ import Reactions from '../Reactions'; import { MessageProviders } from '../../__tests__/testHelpers'; import { setUser } from '../../../../actions/login'; import { mockedStore } from '../../../../reducers/mockedStore'; -import { type IReaction, type TAnyMessageModel } from '../../../../definitions'; +import { type IReaction, type IUseRoomMessageHandlersResult, type TAnyMessageModel } from '../../../../definitions'; const initialMockedStoreState = () => { mockedStore.dispatch( @@ -53,10 +53,11 @@ const TestWrapper = ({ item, onReactionPress }: { item: FakeItem; onReactionPres diff --git a/app/containers/message/components/__tests__/RepliedThread.test.tsx b/app/containers/message/components/__tests__/RepliedThread.test.tsx index ec30e1cbb1d..7d80abcad1d 100644 --- a/app/containers/message/components/__tests__/RepliedThread.test.tsx +++ b/app/containers/message/components/__tests__/RepliedThread.test.tsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux'; import RepliedThread from '../RepliedThread'; import { MessageProvider } from '../../stores/MessageStore'; -import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; +import { MessageRoomProvider } from '../../stores/MessageRoomStore'; import { type TAnyMessageModel } from '../../../../definitions'; import { mockedStore } from '../../../../reducers/mockedStore'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../../../lib/constants/keys'; @@ -11,10 +11,10 @@ import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../../../lib/constants/keys'; const buildItem = (overrides: Partial = {}): TAnyMessageModel => ({ id: 'msg1', tmid: 'thread1', tmsg: 'original reply', ...overrides } as unknown as TAnyMessageModel); -const renderRepliedThread = (item: TAnyMessageModel, roomState: Partial = {}) => +const renderRepliedThread = (item: TAnyMessageModel, fetchThreadName?: jest.Mock) => render( - + @@ -34,7 +34,7 @@ describe('RepliedThread', () => { test('fetches and renders the thread name when tmsg is missing', async () => { const fetchThreadName = jest.fn().mockResolvedValue('fetched thread name'); const item = buildItem({ tmsg: undefined }); - const { getByTestId } = renderRepliedThread(item, { fetchThreadName }); + const { getByTestId } = renderRepliedThread(item, fetchThreadName); await waitFor(() => expect(getByTestId('message-thread-replied-on-fetched thread name')).toBeTruthy()); expect(fetchThreadName).toHaveBeenCalledWith('thread1', 'msg1'); @@ -43,14 +43,14 @@ describe('RepliedThread', () => { test('re-fetches when tmid/id change without remounting', async () => { const fetchThreadName = jest.fn().mockResolvedValueOnce('first thread name').mockResolvedValueOnce('second thread name'); const item = buildItem({ tmid: 'thread1', id: 'msg1', tmsg: undefined }); - const { getByTestId, rerender } = renderRepliedThread(item, { fetchThreadName }); + const { getByTestId, rerender } = renderRepliedThread(item, fetchThreadName); await waitFor(() => expect(getByTestId('message-thread-replied-on-first thread name')).toBeTruthy()); const updatedItem = buildItem({ tmid: 'thread2', id: 'msg2', tmsg: undefined }); rerender( - + diff --git a/app/containers/message/components/__tests__/Reply.test.tsx b/app/containers/message/components/__tests__/Reply.test.tsx index 28ab3b8ff79..8deebb0c2c3 100644 --- a/app/containers/message/components/__tests__/Reply.test.tsx +++ b/app/containers/message/components/__tests__/Reply.test.tsx @@ -5,6 +5,8 @@ import Reply from '../Attachments/Reply'; import { MessageProvider } from '../../stores/MessageStore'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { mockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; +import { selectServerSuccess } from '../../../../actions/server'; import { type IAttachment, type TAnyMessageModel } from '../../../../definitions'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../../../lib/constants/keys'; import { fileDownloadAndPreview } from '../../../../lib/methods/helpers'; @@ -64,8 +66,6 @@ const renderReply = ({ ctx?: Partial; }) => { const contextValue: Partial = { - user: { id: 'user-1', username: 'john', token: 'token' }, - baseUrl: 'https://open.rocket.chat', timeFormat: 'HH:mm', ...ctx }; @@ -80,6 +80,9 @@ const renderReply = ({ ); }; +mockedStore.dispatch(setUser({ id: 'user-1', username: 'john', token: 'token' })); +mockedStore.dispatch(selectServerSuccess({ server: 'https://open.rocket.chat', version: '', name: '' })); + describe('Reply', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/app/containers/message/components/__tests__/Thread.test.tsx b/app/containers/message/components/__tests__/Thread.test.tsx index 806b86f3e02..468ed8f9cd4 100644 --- a/app/containers/message/components/__tests__/Thread.test.tsx +++ b/app/containers/message/components/__tests__/Thread.test.tsx @@ -8,9 +8,7 @@ import { type TAnyMessageModel } from '../../../../definitions'; import { mockedStore } from '../../../../reducers/mockedStore'; const baseContextValue: Partial = { - toggleFollowThread: jest.fn(), - user: { id: 'user1', username: 'user1' }, - onThreadPress: jest.fn() + handlers: { toggleFollowThread: jest.fn(), onThreadPress: jest.fn() } }; const buildItem = (overrides: Partial = {}): TAnyMessageModel => diff --git a/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap b/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap index 4b0bda15861..a52fe2b9b62 100644 --- a/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap +++ b/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap @@ -9,456 +9,436 @@ exports[`Story Snapshots: Archived should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + This message is inside an archived room - - - - - - - - - - This message is inside an archived room - - - - - - + + - + - - - + + + `; @@ -472,456 +452,436 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + This message is inside an archived room - - - - - 10:00 AM + - - - - - - - This message is inside an archived room - - - - - - - + - - - + + + `; @@ -935,1540 +895,749 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - diego.mello - - - 10:00 AM - - - - - - - - - - - - - Rocket.Chat - - - - - - - Rocket.Chat - - - , the best open source chat - - - - - - - - - - - - + Rocket.Chat + + + + - + -  + { + "color": "#095AD2", + }, + ] + } + > + Rocket.Chat + + + , the best open source chat + - - + - - - - - - - - - - -`; - -exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapshot 1`] = ` - - - - - - - - - - - + - - + - - - - - - - - - - + /> - diego.mello +  - - 10:00 AM - - - - - - - - - - Rocket.Chat - - - - - - - Rocket.Chat - - - , the best open source chat - - - - - - - - - - - - - - - - -  - - - - - - - - + - - - + + + `; -exports[`Story Snapshots: Avatar should match snapshot 1`] = ` +exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapshot 1`] = ` - - - + - - + - - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + diego.mello + + + + 10:00 AM + + + - - - no.avatar - - - 10:00 AM - - + }, + { + "borderColor": "#764FA5", + }, + ] + } + testID="reply-undefined-Rocket.Chat, the best open source chat" + > - - - - + Rocket.Chat + + + + - This message has no avatar. + + Rocket.Chat + + + , the best open source chat + - + - - - - - - - - - - - - - - - - - + - - + - - + - - - - - - - - - - - diego.mello - + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > - 10:00 AM +  - - - - - - - This message uses the user's profile picture as the avatar. - - - - - - - + - - - - + + + + +`; + +exports[`Story Snapshots: Avatar should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - 😄 - - - - - + > + no.avatar + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + This message has no avatar. - - - - - - - - - - This message uses a Unicode emoji as the avatar. - - - - - - + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + This message uses the user's profile picture as the avatar. - - - - - - - - - - This message uses a custom animated emoji as the avatar. - - - - - - + + - + - - - - + + + - - + - - + + + + + + 😄 + + + + + + + + + - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + This message uses a Unicode emoji as the avatar. - - - - - - - - - - This message uses a custom static emoji as the avatar. - - - - - - + + - + - - - - - -`; - -exports[`Story Snapshots: Basic should match snapshot 1`] = ` - - - + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + This message uses a custom animated emoji as the avatar. - - - - - - - - - - Message - - - - - - + + - + - - - - + + + - - + + + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM + This message uses a custom static emoji as the avatar. - - - - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: Basic should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello + Message - - - - - 10:00 AM + - - - - - - - Message - - - - - - - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - - 10:00 AM + - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - - - - + - - - + + + `; -exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` +exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` - - - + - - + + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Message - - - - - - - - - - - - - Testing block quote - - - - - - - - + + - + - - - - + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + diego.mello + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - - - - - - - - - - Testing block quote - - - - - - - - - Testing block quote - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + + - diego.mello - - - - - - 10:00 AM - - - - - - - - - - - Testing block quote - - - - - - + Testing block quote + + + - + - - - - + + + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + + - diego.mello + + + Testing block quote + + - - 10:00 AM - - - - - - - - - - - Testing block quote - - - - - - - - - Testing block quote - - - - - - + Testing block quote + + + - + - - - + + + `; -exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` +exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + + - diego.mello - - - 10:00 AM + } + } + > + + Testing block quote + + - - - - - - - - - Broadcasted message - - - - - - - - - -  - - - Reply - - - - - + - - - - - -`; - -exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + + - diego.mello + + + Testing block quote + + - - 10:00 AM - - - - - - - - Broadcasted message - - - - - - - - - -  - - - Reply - - - - - + Testing block quote + + + - + - - - + + + `; -exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` +exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello + Broadcasted message - + + + + + + + - 10:00 AM - - - - - - - - - - - Message - - - - - - - + - - - - - Title collapsed - - - - - -  - - - - - + "color": "#FFFFFF", + }, + ] + } + > + Reply + - + - - - - + + + + +`; + +exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Broadcasted message - - 10:00 AM - - - - + + + + + + - - - - - - - - Title collapsed - - - - - - - Title collapsed - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - - - - - - + - - - - Message - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "600", + "lineHeight": 16, + "textAlign": "left", + }, + { + "color": "#FFFFFF", + }, + ] + } + > + Reply + - + - - - + + + `; -exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` - - - + - - + - - + + - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + - - + - diego.mello + Message - - - - - 10:00 AM + + + + + @@ -11750,881 +10055,774 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` "backgroundColor": "transparent", "fontFamily": "Inter", "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, + "fontWeight": "500", "textAlign": "left", }, { - "color": "#2F343D", + "color": "#6C727A", }, ] } > - - - Message - - + Title collapsed - - - - - - Title collapsed - - - - - -  - - - - +  + - + - - - - + + + - - + - - + - - + + + + + + + + + + + + + - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - - - - 10:00 AM - - + + Title collapsed + + + + + + + Title collapsed + + + + + + + Field 1 + - - - Title collapsed - - - - - Title collapsed - + Value 1 - - - + + + + + Field 2 + + + + - - Field 1 - - - - - - Value 1 - - - - - - - Field 2 + Value 2 - - - - - Value 2 - - - - - - + + - + + + + + + - - - - - Message - - - - - - + Message + + + - + - - - + + + `; -exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1`] = ` +exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - + diego.mello + + + + + + 10:00 AM + + + + + - - - - - - - + "color": "#2F343D", + }, + ] + } + > + Message + + + - - - diego.mello - - - 10:00 AM - - - - - @@ -13185,1037 +11450,549 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` "backgroundColor": "transparent", "fontFamily": "Inter", "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, + "fontWeight": "500", "textAlign": "left", }, { - "color": "#2F343D", + "color": "#6C727A", }, ] } > - - - This is the main message body. - - + Title collapsed - - - - - - Collapsed attachment block - - - - - -  - - - - +  + - + - - - - + + + - - + + + + + + + + + + + + + - - - + diego.mello + + + + + + 10:00 AM + + + + + - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - Collapsed attachment block - - - - - - - This attachment text should NOT appear as plain text above the message or duplicate before the block. - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - Long field - - - - - - This field value could also contribute to duplicate text when expanded. - - - - - - - - + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "500", + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + Title collapsed + - Attachment description that might also leak as duplicate plain text. + Title collapsed - - - - - - + + Field 1 + + + + + + Value 1 + + + + + + - This is the main message body. + Field 2 - - + + + + + Value 2 + + + + + + + + + + + + Message + + + + + - + - - - + + + `; -exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - - - - - + "color": "#2F343D", + }, + ] + } + > + This is the main message body. + + + - + - - - diego.mello - - - - - 10:00 AM - - - @@ -14763,1037 +12847,549 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn "backgroundColor": "transparent", "fontFamily": "Inter", "fontSize": 16, - "fontWeight": "400", - "lineHeight": 22, + "fontWeight": "500", "textAlign": "left", }, { - "color": "#2F343D", + "color": "#6C727A", }, ] } > - - - This is the main message body. - - + Collapsed attachment block - - - - - - Collapsed attachment block - - - - - -  - - - - +  + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - - - - 10:00 AM - - - - - - - Collapsed attachment block - - - - - - - This attachment text should NOT appear as plain text above the message or duplicate before the block. - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - Long field - - - - - - This field value could also contribute to duplicate text when expanded. - - - - - - - - + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "500", + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + Collapsed attachment block + - Attachment description that might also leak as duplicate plain text. + This attachment text should NOT appear as plain text above the message or duplicate before the block. - - - - - - - This is the main message body. + Field 1 - - - - - - - - - - - - - - - - - -`; - -exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` - - - - - - - - - - - - - + + + + Value 1 + + + + + + + + Field 2 + + + + + + Value 2 + + + + + + + + Long field + + + + + + This field value could also contribute to duplicate text when expanded. + + + + + + + + + + - - - - - - - + > + Attachment description that might also leak as duplicate plain text. + + + + - - + - diego.mello - - - 10:00 AM + This is the main message body. - - + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + + + - - + + + + + + + + + + + diego.mello + + + + + + 10:00 AM + + + + + + + + This is the main message body. + + + + + + + + - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - - + Collapsed attachment block + - + - + - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - - - - - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - - - +  + - + - - - - - -`; - -exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - + + Collapsed attachment block + + + + + + + This attachment text should NOT appear as plain text above the message or duplicate before the block. + + + + + + + Field 1 + - - - - Field 1 + Value 1 - - - - - Value 1 - - - - - - + + + + + + Field 2 + + + + - Field 2 + Value 2 - - - - - Value 2 - - - - - - + + - - - - - + - + - + - - - Field 1 - - - - - - Value 1 - - - - - - - Field 2 + This field value could also contribute to duplicate text when expanded. - - - - - Value 2 - - - - - - + + - + + + + - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - - - - + Attachment description that might also leak as duplicate plain text. + + + + + + + + + This is the main message body. + + + + + - + - - - + + + `; -exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` +exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` - - - + - - - + + + + + + + + + + + + + + - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - 10:00 AM - - - - - + + + Field 1 + - - rocket.cat + + Value 1 + - - + + + + + Field 2 + + + - - Custom fields - + Value 2 - - + + + + + + + + + + + + + + + + Field 1 + + + - - Field 1 + Value 1 - - - - - Value 1 - - - - - - + + + + + + Field 2 + + + + - Field 2 + Value 2 - - - - - Value 2 - - - - - - + + + + + + - + - + + Field 1 + + + + + + Value 1 + + + + + + - Message + Field 2 - - + + + + + Value 2 + + + + + + - + - - - - - - + + + + + +`; + +exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - 10:00 AM - - - - - + + + Field 1 + - - - rocket.cat - - - - - Custom fields - + Value 1 - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - Field 3 - - - - - - Value 3 - - - - - - + + + + + Field 2 + + + + - - Field 4 - - - - - - Value 4 - - - - - - - Field 5 + Value 2 - - - - - Value 5 - - - - - - + + + + + + - + - - Message + Field 1 - - - - - - - - - - - - - - - - - -`; - -exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - + + + + Value 1 + + + + + + + > + + Field 2 + + + + + + Value 2 + + + + + + - - + + - - - - - diego.mello - - - - - - 10:00 AM - - + + + Field 1 + - - - rocket.cat - - - - - - Custom fields - - - - - - - Field 1 + Value 1 - - - - - Value 1 - - - - - - + + + + + + Field 2 + + + + - Field 2 + Value 2 - - - - - Value 2 - - - - - - + + - - - - - - Message - - - - - - + - - - - + + + + +`; + +exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - + - - diego.mello - - - - - - 10:00 AM - - - - + + rocket.cat + + + + + + + Custom fields + + + + + + + Field 1 + - - - rocket.cat - - - - - - Custom fields - - - - - - - Field 1 + Value 1 - - - - - Value 1 - - - - - - + + + + + + Field 2 + + + + - - Field 2 - - - - - - Value 2 - - - - - - - Field 3 + Value 2 - - - - - Value 3 - - - - - - - - Field 4 - - - - - - Value 4 - - - - - - - - Field 5 - - - - - - Value 5 - - - - - - + + - + + + + + + - - - - - Message - - - - - - + Message + + + - + - - - - - -`; - -exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - + - rocket.cat - - - 10:00 AM - - - - - - - - + + rocket.cat + + + - Fourth message + + Custom fields + - - - - - - - - - - - - - - - - - unread - - - - November 10, 2017 - - - - - - - - - - - - - - - - - + - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - Third message + Field 1 - - + + + + + Value 1 + + + + + + + + Field 2 + + + + + + Value 2 + + + + + + + + Field 3 + + + + + + Value 3 + + + + + + + + Field 4 + + + + + + Value 4 + + + + + + + + Field 5 + + + + + + Value 5 + + + + + + + + + + + + Message + + + + + - + - - - - + + + + +`; + +exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` + + + - - unread - - - - - - - - + - - - + + - - - - - - Second message - - - - - - + transition={null} + width={46.800000000000004} + /> - - - - + + + - - - - - - - - - - - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - rocket.cat - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-rocket.cat-Custom fields" + > - - - - + rocket.cat + + + + + + + Custom fields + + + + + + - Second message + Field 1 - - - - - - - - - - - - - - - - + + + + Value 1 + + + + + + + + Field 2 + + + + + + Value 2 + + + + + + + + + + + + + + + + + Message + + + + + + + + + + + + + + - - November 10, 2017 - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - + - diego.mello - - - 10:00 AM - - - - - - - - + + rocket.cat + + + - First message + + Custom fields + - - - - - - - - - - - - - - - - -`; - -exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - + - - - - - - - - - - - rocket.cat - - - - - - 10:00 AM - - - - - - - Fourth message + Field 1 - - - - - - - - - - - - - - - - - - unread - - + + + + Value 1 + + + + + + + + Field 2 + + + + + + Value 2 + + + + + + + + Field 3 + + + + + + Value 3 + + + + + + + + Field 4 + + + + + + Value 4 + + + + + + + + Field 5 + + + + + + Value 5 + + + + + + + + + + + + + + + + + Message + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` + + + + - - November 10, 2017 - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - + > + rocket.cat + + + 10:00 AM + + + + - - + - diego.mello + Fourth message - - - - - 10:00 AM + - - - - - - - Third message - - - - - - - + - - - + + + @@ -25446,220 +22007,385 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot { "backgroundColor": "#EC0D2A", }, + { + "marginHorizontal": 14, + }, ] } /> + + November 10, 2017 + - - - + + + + + + + + + + + + + - - - - + - - - - - - Second message - - - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "400", + "lineHeight": 18, + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + > + 10:00 AM + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - rocket.cat + Third message - - - - - 10:00 AM + - - - - - - - Second message - - - - - - - + - - - + + + - - November 10, 2017 + unread - - - + + - - + - - - - - - - + Second message + + + - + + + + + + + + + + + + + + + + + - - - - diego.mello - - - - - + + - 10:00 AM - - - - - - - - First message - - - - - - + transition={null} + width={36} + /> - - - + + + - - - - - - - -`; - -exports[`Story Snapshots: Discussion should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + rocket.cat + + + 10:00 AM + + + + - - + - diego.mello + Second message - - 10:00 AM - - - - - - - Started a discussion: - - - This is a discussion - - - - -  - - - No messages yet - - - - - - + - + - - - - + + + - + - + + + + + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + First message - - - - - - Started a discussion: - - - This is a discussion - - - - -  - - - 1 message - - - - - November 10, 2017 - - - + - + - - - - + + + + +`; + +exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + rocket.cat + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Fourth message - - - - - - Started a discussion: - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - -  - - - 10 messages - - - - - November 10, 2017 - - - + - + - - - - + + + - - + + + November 10, 2017 + + + + + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Third message - - 10:00 AM - - - - - + + + + + + + + + + + + + + unread + + + + + + + + + + + + + + + - Started a discussion: - - - This is a discussion - - - - - -  - - - +999 messages - - - - November 10, 2017 + Second message - - + + - + + - - - - - -`; - -exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - diego.mello - - - - - + rocket.cat + + + + + + 10:00 AM + + + + + - 10:00 AM - - - - Started a discussion: - - This is a discussion - - - - - -  - - - No messages yet - - - - - + > + Second message + + + - + - - - - + + + - + - + + + + + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - - diego.mello - - - - - 10:00 AM - - - - Started a discussion: - - This is a discussion - - - - - -  - - - 1 message - - - - November 10, 2017 + First message - - + + - + - - - - + + + + +`; + +exports[`Story Snapshots: Discussion should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + + Started a discussion: + + + This is a discussion + + - - - - diego.mello - - - - - - 10:00 AM - + "justifyContent": "center", + "paddingHorizontal": 12, + "paddingVertical": 8, + }, + { + "backgroundColor": "#1D74F5", + }, + ] + } + > - Started a discussion: +  - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + No messages yet - - - - -  - - - 10 messages - - - - - November 10, 2017 - - + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + + Started a discussion: + + + This is a discussion + + - - - - diego.mello - - - - - - 10:00 AM - + "justifyContent": "center", + "paddingHorizontal": 12, + "paddingVertical": 8, + }, + { + "backgroundColor": "#1D74F5", + }, + ] + } + > - Started a discussion: +  - This is a discussion + 1 message - - - - -  - - - +999 messages - - - - - November 10, 2017 - - + + November 10, 2017 + - + - - - - - -`; - -exports[`Story Snapshots: Edited should match snapshot 1`] = ` - - - + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + Started a discussion: + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - diego.mello - - - 10:00 AM - - - - + - -  - - - - - - - - - - - Message header - - - - - + 10 messages + + + November 10, 2017 + - + - - - - + + + - - + - - + - - - + + - - - - - - Message without header - - - - - - + "borderRadius": 4, + "height": 36, + "width": 36, + } + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + Started a discussion: + + + This is a discussion + + - + +  + + -  - + { + "color": "#FFFFFF", + }, + ] + } + > + +999 messages + + + + November 10, 2017 + - + - - - + + + `; -exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + + Started a discussion: + + + This is a discussion + + - - - diego.mello - - - - + - -  - - - - - - 10:00 AM - - - - - - - - Message header - - - - - + No messages yet + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - Message without header - - - - - - - + diego.mello + + + + 10:00 AM + + + + Started a discussion: + + + This is a discussion + + - + -  - + [ + { + "lineHeight": 20.8, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + 1 message + + + + November 10, 2017 + - + - - - - - -`; - -exports[`Story Snapshots: Editing should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - + - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + - - - diego.mello - - - 10:00 AM - - - - + } + accessibilityValue={ + { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } + accessible={true} + collapsable={false} + focusable={true} + hitSlop={ + { + "bottom": 4, + "left": 4, + "right": 4, + "top": 4, + } + } + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + [ + { + "alignItems": "center", + "borderRadius": 4, + "flexDirection": "row", + "justifyContent": "center", + "paddingHorizontal": 12, + "paddingVertical": 8, + }, + { + "backgroundColor": "#1D74F5", + }, + ] + } + > - - + - - - - Message being edited - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "600", + "lineHeight": 16, + "textAlign": "left", + }, + { + "color": "#FFFFFF", + }, + ] + } + > + 10 messages + + + November 10, 2017 + - + - - - - - -`; - -exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + - + + + 10:00 AM + + + - + Started a discussion: + + + This is a discussion + + + + - - - diego.mello - - - - - - 10:00 AM - - - - + - - - - Message being edited - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "600", + "lineHeight": 16, + "textAlign": "left", + }, + { + "color": "#FFFFFF", + }, + ] + } + > + +999 messages + + + November 10, 2017 + - + - - - + + + `; -exports[`Story Snapshots: Emojis should match snapshot 1`] = ` +exports[`Story Snapshots: Edited should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + - - +  + + + + + + + + + - diego.mello - - - 10:00 AM + Message header - - - - - - - - - 👊 - - - 🤙👏 - - - - - + + - + - - - - + + + - - + + - - - - - - - - - - - - - - - + - diego.mello - - - 10:00 AM + Message without header - - - - - - - - - 👏 - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + +`; + +exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` + + + + + + + + + + + - - - - diego.mello - - - 10:00 AM - - - + + - - - - - - - - - - - - - - - - - - + transition={null} + width={46.800000000000004} + /> - - - + + + - - - - - - - - - - - - - - - - - - - - - + > + diego.mello + - - +  + + + + + + 10:00 AM + + + + + + - diego.mello - - - 10:00 AM + Message header - - - - - - - - - - - - - - + + - + - - - - + + + - - + + - - - - - - - - - - - - - - - + - diego.mello - - - 10:00 AM + Message without header - - - - - - - - - 🤙 - - - - - - - - + + - + - - - - - - - - - - - - - + + + + + + + + + +`; + +exports[`Story Snapshots: Editing should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Message being edited - - - - - - - - - - 🤙 - - - :react_rocket: - - - 🤙 - - - 🤙 - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` - - - + + style={ + { + "flexDirection": "row", + } + } + > + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Message being edited - - - - - 10:00 AM + - - - - - - 👊 - - - 🤙👏 - - - - - - + - - - - + + + + +`; + +exports[`Story Snapshots: Emojis should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + "backgroundColor": "transparent", + "flexShrink": 1, + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600", + "lineHeight": 22, + "textAlign": "left", + }, + { + "color": "#1F2329", + }, + ] + } + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - + - - - 10:00 AM - - - - - - - 👏 - - - - + 🤙👏 + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - - - - - - - - + 👏 + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - diego.mello - + style={ + { + "height": 30, + "width": 30, + } + } + transition={null} + width={30} + /> - - - 10:00 AM - - - - - - - - - - + } + transition={null} + width={30} + /> + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - diego.mello - - - - - - 10:00 AM - - - - - - - 🤙 - - - - - - + transition={null} + width={30} + /> - + - - - - + + + - - + - - + + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - + 🤙 + + - - diego.mello - - - - - - 10:00 AM - - - - - - - - 🤙 - - - :react_rocket: - - - 🤙 - - - 🤙 - - - - + transition={null} + width={30} + /> - + - - - - - -`; - -exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` - - - + + + - - + - - + - + + - - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + - - + + 🤙 + + - diego.mello + :react_rocket: - 10:00 AM + 🤙 - - - - -  - - - - - - - Encrypted message + 🤙 - - + + - + - - - - + + + + +`; + +exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` + + + - - + - - + - - - + + - - - Encrypted message - - - + transition={null} + width={46.800000000000004} + /> - + + + + + + + - + diego.mello + + + + + + 10:00 AM + + + + - + -  - + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 30, + "fontWeight": "400", + "lineHeight": 43, + "textAlign": "left", + }, + ] + } + > + 👊 + + + 🤙👏 + + - + - - - - + + + - - + + + + + + + + + + + + + - - - + diego.mello + + + + + + 10:00 AM + + + + + - - - - - - - + 👏 + + - + + + + + + + + + + + + + + + + - + + - - - diego.mello - - - 10:00 AM - - - - - -  - - - - + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + + diego.mello + + + + + + 10:00 AM + + + + - - - Encrypted message - - - - - - 😂 - - - 1 - - - - + + - - - - 1 - - - - + - - - 🤔 - - - 1 - - - - + + + - - -  - - - + } + transition={null} + width={30} + /> - + - - - - + + + - - + + + + + + + + + + + + + - + + diego.mello + + + -  - + /> - Encrypted message + 10:00 AM - -  - - - - - - + - - - - - - - - - - - Encrypted message - + - + - - - - + + + - - + + + + + + + + + + + + + - - - + diego.mello + + + + + + 10:00 AM + + + + + - - - - + + - - - + } + transition={null} + width={30} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + + 10:00 AM + + + - - + + 🤙 + + - diego.mello + :react_rocket: - 10:00 AM + 🤙 - - - - -  - - - - - - - Encrypted message + 🤙 - - + + - + - - - - + + + + +`; + +exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` + + + - - + - - + - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - + + - - -  - - - - - - - - Encrypted message - - - + transition={null} + width={36} + /> - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - diego.mello - - - 10:00 AM - - - @@ -43071,14 +38557,6 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -43090,859 +38568,60 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello - - -  + 10:00 AM - - - - - - Encrypted message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - -  - - - -  - - - - - - - Encrypted message - - - - - - - - - - - - - - - - - - - - - - - - - - - - Encrypted message - - - - - - + + + + -  + Encrypted message - + - - - - - -`; - -exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` - - - + + + - - + - - + + + - - - - - - - - - - + testID="message-encrypted" + > + Encrypted message + - + + + + + +  + + + + + + + + + + + + + + + + + - - - - diego.mello - - - - - -  - - - - - + + - 10:00 AM - - - - - Encrypted message - - - + transition={null} + width={36} + /> - - - + + + - - - - - - - - - - - - - - + - - - Encrypted message - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "400", + "lineHeight": 18, + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + > + 10:00 AM + - - - - - - - - - - - - - - + + + Encrypted message + + - + - + 😂 + + - - - - - - + 1 + + - - - + + 1 + + + + + - - diego.mello - - - + - + - -  - - - + "color": "#1D74F5", + }, + ] + } + > + 1 + - + + - 10:00 AM - - - - - Encrypted message - - - - - - - 😂 - - - 1 - - - - - - - - 1 - - - - - - - 🤔 - - - 1 - - - - - - -  - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - - + + + - - + + +  + + + - + + - + + + + + + - -  - - - + + + + + + + + + - Encrypted message - + } + > -  + Encrypted message + + + + + + + + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + + + + diego.mello + + + 10:00 AM + + - Encrypted message +  - + + + + + Encrypted message + + + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + - - - - diego.mello - - - - - -  - - - - + } + accessible={true} + collapsable={false} + focusable={true} + hitSlop={ + { + "bottom": 4, + "left": 4, + "right": 4, + "top": 4, + } + } + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + { + "paddingLeft": 5, + } + } + > - 10:00 AM +  - - - - Encrypted message - - - - + + + + Encrypted message + + + + - - - - + + + - - + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + +  + + + + +  + + + + + + + + Encrypted message + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - diego.mello - - - - - -  - - - - - + + - 10:00 AM - - - - - Encrypted message - - - + transition={null} + width={36} + /> - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - diego.mello - - - @@ -47476,14 +42020,6 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -47495,878 +42031,60 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello - - -  + 10:00 AM - - - - 10:00 AM - - - - - Encrypted message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diego.mello - - - - - -  - - - -  - - - - - 10:00 AM - - - - - Encrypted message - - - - - - - - - - - - - - - - - - - - - - - - - - - - Encrypted message - - - - - - - + + + + Encrypted message + + + + - - - - - -`; - -exports[`Story Snapshots: Error should match snapshot 1`] = ` - - - + + + - + - + - - - + + testID="message-encrypted" + > + Encrypted message + - - + + + + + + +  + + + +  + + + + + + + +`; + +exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` + + + + + + - + - - diego.mello - - - 10:00 AM - - + "backgroundColor": "#E4E7EA", + "borderBottomLeftRadius": undefined, + "borderBottomRightRadius": undefined, + "borderRadius": undefined, + "borderTopLeftRadius": undefined, + "borderTopRightRadius": undefined, + "bottom": 0, + "left": 0, + "opacity": 0, + "position": "absolute", + "right": 0, + "top": 0, + } + } + /> + + + + + + + + + @@ -48805,14 +42811,6 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -48824,62 +42822,148 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello + + + +  + + + - - + > + 10:00 AM + - - - This message has error - - + Encrypted message @@ -48932,735 +42988,523 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` - + - - + - + - + - - - + + testID="message-encrypted" + > + Encrypted message + - - + + - - - + - - diego.mello - - - 10:00 AM - - - - - -  - - - - - - - - - - - This message has error too - - - - - +  + - + - - - -`; - -exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` - - - + - - + - + - - - + - - - + + + + + + - - - - diego.mello - - - @@ -49685,14 +43529,6 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -49704,81 +43540,148 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello + + + +  + + + - - - 10:00 AM - - + > + 10:00 AM + - - - This message has error - - + Encrypted message - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - diego.mello - - - - - -  - - - - - - 10:00 AM - - - - + + 😂 + + + 1 + + + + - - + - This message has error too + 1 - - - - - - - - - - - - - -`; - -exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] = ` - - - - - - - - - - - + + - + - + 🤔 + + - - - - - - + 1 + + - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - - - test.py - - - - test.py - - - - - - - - - - - - Check out this Python file - - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - - + + + - - + + +  + + + + Encrypted message + + - - + + + + + + - - + + - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - - - Component.tsx - - - - Component.tsx - - - - - - - - - - - - TypeScript component - - - - - - + transition={null} + width={26} + /> - + + + + + + + + + Encrypted message + - + - - - - + + + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - - - config.json - - - - config.json - - - - - - - - - - - - Configuration file - - - - - - +  + - + + 10:00 AM + + + + + Encrypted message + + + + - - - - + + + - - + - - + - - + + - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - - - main.go - - - - - - - This is the - - - - main - - - - entry point for the application - - - - - - - - - - - - - - - File with description - - - - - - + transition={null} + width={46.800000000000004} + /> - - - + + + - - - - - - - - - - - + testID="username-header-diego.mello" + > + + diego.mello + + - - - - - - - - - document.pdf - - - - document.pdf - - - - - - - - - - - - image.png - - - - image.png - - - - - - - + "lineHeight": 20.8, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + + 10:00 AM + + + } + > + + + Encrypted message + + - + - - - - - -`; - -exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + + + diego.mello + + - - - - - - - - - - - - - - - + + + - - - - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - test.py - - - - test.py - - - - - - - - - - - - Check out this Python file - - - - - - - - - - + "lineHeight": 20.8, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + + 10:00 AM + + + + + Encrypted message + - - - - + + + - - + + + + + + + + + + + + + - - - + diego.mello + + + + + - - - - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - - - - - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - Component.tsx - - - - Component.tsx - - - - - - - - - - - - TypeScript component - - - - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + testID="read-receipt-read" + > +  + + + + + 10:00 AM + + + + + Encrypted message + - + - - - - + + + - - + + - - - - - - - - - - - - - - - - - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - config.json - - - - config.json - - - - - - - - - - - - Configuration file - - - - - - - + Encrypted message + - + + + + + +  + + + +  + - - - - + + + + +`; + +exports[`Story Snapshots: Error should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + - - + + 10:00 AM + + + + - + - - - - - - - - - - - - +  + + + + + + + + + - - - - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - main.go - - - - - - - This is the - - - - main - - - - entry point for the application - - - - - - - - - - - - - - - File with description - - - - - - - - - - + "color": "#2F343D", + }, + ] + } + > + This message has error + + + + - - - - + + + - - - - - - - + + - + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + +  + + + + + + + + + - - - - - - - - - - document.pdf - - - - document.pdf - - - - - - - - - - - - image.png - - - - image.png - - - - - - - - - - - - + > + This message has error too + + + + - - - - + + + + +`; + +exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + - - - + diego.mello + + + + + +  + + + + + + 10:00 AM + + + + + + - - - - - - - - - - - - + This message has error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + + + +  + + + + + + 10:00 AM + + + + + + + + This message has error too + + + + + + + + + + + + +`; + +exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] = ` + + + + + + + + + + + - - + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - File.pdf - - - - - - - - - - - - - - - - - - File.pdf - - - - - - + test.py + - - - - - - - multi file - - + test.py - - - - - - - - - - -`; - -exports[`Story Snapshots: FullName should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - + - Diego Mello - - - 10:00 AM + Check out this Python file - - - - - - - - - - Message - - - - - - + + - + - - - - - -`; - -exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - Diego Mello - - - - - - 10:00 AM - - + + + Component.tsx + + - - - Message - - + Component.tsx + + + + + + TypeScript component + + + + + - + - - - - - -`; - -exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - + + config.json + + + + testID="markdown-preview-config.json" + > + config.json + - - + + - - + - diego.mello - - - 10:00 AM + Configuration file - - - - - - - - - - ... - - - - - - + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-undefined-This is the **main** entry point for the application" + > - - - - + main.go + + + + - Different user + + This is the + + + + main + + + + entry point for the application + - + + + + + + + File with description + + + + + - + - - - - + + + - - + + - - + + + document.pdf + + - - - This is the third message - - + document.pdf - - - - - - - - - - - - - - - - - - - - + + + image.png + + - - - This is the second message - - + image.png - - + + - - - - + + + + +`; + +exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match snapshot 1`] = ` + + + - - + - - + + + + + + + + + + + + + - - + diego.mello + + + + + + 10:00 AM + + + + + - - - + + test.py + + + + testID="markdown-preview-test.py" + > + test.py + - - + + - - + - diego.mello - - - 10:00 AM + Check out this Python file - - - - - - - - - - This is the first message - - - - - - + + - + - - - - - -`; - -exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` - - - + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - + + + Component.tsx + + - - - ... - - + Component.tsx + + + + + + TypeScript component + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - + diego.mello + + + + + + 10:00 AM + + + + + - - - + + config.json + + + + testID="markdown-preview-config.json" + > + config.json + - - + + - - + - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Configuration file - - - - - 10:00 AM + - - - - - - - Different user - - - - - - - + - - - - + + + - - + + + + + + + + + + + + + - + + + diego.mello + + + + + 10:00 AM + + - + main.go + + + + - This is the third message + + This is the + + + + main + + + + entry point for the application + - + + + > + + + + File with description + + + + + - + - - - - + + + - - + + - - + + + document.pdf + + + document.pdf + + + + + + + + + + + image.png + + + - - This is the second message - - + }, + { + "color": "#2F343D", + "lineHeight": undefined, + }, + ] + } + testID="markdown-preview-image.png" + > + image.png - - + + - - - - + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + diego.mello + + + + 10:00 AM + + + - + - - diego.mello - + + + + + + File.pdf + + + + + - - + + - 10:00 AM - - - + - This is the first message + + File.pdf + - + + + + + + + multi file + + + + + - + - - - + + + `; -exports[`Story Snapshots: Ignored should match snapshot 1`] = ` +exports[`Story Snapshots: FullName should match snapshot 1`] = ` - - - + - + - - + - + + - - - - - - - - - + + + + + + + + + + + Diego Mello + + + 10:00 AM + + + + + - Message ignored. Tap to display it. + + + Message + + - + - - - + + + `; -exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` - - - + - + - - + - - - - - - - - - - - + /> - + - Message ignored. Tap to display it. - + style={ + { + "borderRadius": 4, + "height": 46.800000000000004, + "width": 46.800000000000004, + } + } + transition={null} + width={46.800000000000004} + /> - - + + - - - - - - - -`; - -exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + Diego Mello + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Message - - - - - - - - - - xˆ2 + yˆ2 - zˆ2 - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - + - diego.mello + ... - - - - - 10:00 AM + - - - - - - - xˆ2 + yˆ2 - zˆ2 - - - - - - - + - - - - - -`; - -exports[`Story Snapshots: JitsiCall should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Different user - - - - - Call started by diego.mello + - - - - -  - - - Click to join! - - - - - + - - - - - -`; - -exports[`Story Snapshots: JitsiCallLargeFont should match snapshot 1`] = ` - - - + + + - - + + - - - - - - - - - - - - - - - + - diego.mello + This is the third message - - - - - Call started by diego.mello - - - - - -  - - - Click to join! - - - - - - 10:00 AM + - + + - - - - - -`; - -exports[`Story Snapshots: Katex should match snapshot 1`] = ` - - - + + + - - + + - - + - - - - - - - - - - - - - + - diego.mello - - - 10:00 AM + This is the second message - - - - - - - - - - + + - + + - - - - - -`; - -exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + This is the first message - - - - - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + ... - - - - - 10:00 AM + - - - - - - - - + - - - - - -`; - -exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` - - - + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + + 10:00 AM + + + - - + - diego.mello + Different user - - - + + + + + + + + + + + + + + + + + + + + + + + - 10:00 AM - - - - - - - - + This is the third message + + + - + + - - - - - -`; - -exports[`Story Snapshots: Lists should match snapshot 1`] = ` - - - + + + - - + + - - + - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - • - - - - - Dogs - - - - - - - • - - - - - cats - - - - - - - - - • - - - - - cats - - - - - - - - + "color": "#2F343D", + }, + ] + } + > + This is the second message + + + - + + - - - - + + + - - + - + + + + + + + + + + + + - - + diego.mello + + + + + + 10:00 AM + + + + + - - - - - - - - - - - - - + - diego.mello - - - 10:00 AM + This is the first message - - - - - - - - - - 1 - . - - - - - Dogs - - - - - - - 2 - . - - - - - Cats - - - - - - - - + + - + - - - - + + + + +`; + +exports[`Story Snapshots: Ignored should match snapshot 1`] = ` + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={20} + /> - - + + + + + + + - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - 1 - . - - - - - Dogs - - - - - - - - - - + Message ignored. Tap to display it. + - + - - - - + + + + +`; + +exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` + + + - - + - - + - - - + + - - - - - - 2 - . - - - - - Cats - - - - - - - - + transition={null} + width={26} + /> - - + + + + + + + + testID="message-ignored-undefined" + > + Message ignored. Tap to display it. + - + - - - + + + `; -exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` - - - + - - + - - + + - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + - - - - diego.mello - - - - - 10:00 AM - - - - - - - - • - - - - - Dogs - - - - - - - • - - - - - cats - - - - - - - - - • - - - - - cats - - - - - - - - + + xˆ2 + yˆ2 - zˆ2 + + + - + - - - - + + + + +`; + +exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` + + + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - - diego.mello - - - - - 10:00 AM - - - - - - - - 1 - . - - - - - Dogs - - - - - - - 2 - . - - - - - Cats - - - - - - - - + + xˆ2 + yˆ2 - zˆ2 + + + - + - - - - + + + + +`; + +exports[`Story Snapshots: JitsiCall should match snapshot 1`] = ` + + + - - + - - + + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + Call started by diego.mello + + + - - - - diego.mello - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - 10:00 AM + Click to join! - - - - - - - 1 - . - - - - - Dogs - - - - - - - - - + - - - - + + + + +`; + +exports[`Story Snapshots: JitsiCallLargeFont should match snapshot 1`] = ` + + + - - + - - + + + + + + + + + + + + + + testID="username-header-diego.mello" + > + + diego.mello + + + + + Call started by diego.mello + + + - - - - - - - 2 - . - - - - - Cats - - - - - - - - +  + + + Click to join! + - - + + 10:00 AM + + - - - + + + `; -exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` +exports[`Story Snapshots: Katex should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - - - - - - - - - - this is a normal message - - - - - - + - + - - - - + + + + +`; + +exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - - - - -  - - - - - - - - - - - Edited message - - - - - - + - + - - - - + + + + +`; + +exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` + + + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - + + + + + + + + + + + + +`; + +exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + + + - + + + + + + + + + + + diego.mello + + + + + + 10:00 AM + + + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: Lists should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + • - 10:00 AM + + + Dogs + + - + • + + + -  + { + "color": "#2F343D", + }, + ] + } + > + cats + - + - + - - + + - - Translated message - + cats - + - + - - - - + + + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + 1 + . - 10:00 AM + + + Dogs + + - - -  - - - - - - + 2 + . + - Encrypted message + + + Cats + + - + - - - - + + + - - + - + - - - + - - - + + + + + + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - - @@ -72104,14 +64882,6 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -72123,468 +64893,354 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello - - - - - - - + + + + + + - + + - Error message - - - + + 1 + . + + + + + Dogs + + + + + + - + - - + - - + + - - - - - - - - - - - - - - + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + 2 + . - 10:00 AM - - - - -  - - - - - - - - Message with read receipt - + Cats - + - + + - - - - + + + + +`; + +exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + • - 10:00 AM + + + Dogs + + - - -  - - -  + • - - - - - Encrypted message + + + cats + + + + + + + + + • + + + + + cats + + - + - - - - + + + - - + - + - - - + - - - + + + + + + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - - @@ -73562,14 +66242,6 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -73581,542 +66253,539 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello - -  - - - + + - -  - - - - -  - - - -  - - - - + > + 10:00 AM + - - Encrypted message - - - - - - - - - - - - - - - - - - - - + > + + + + 1 + . + + + + + Dogs + + + + + + + 2 + . + + + + + Cats + + + + + + - - + + + + + + + + + + - + - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - + "backgroundColor": "#E4E7EA", + "borderBottomLeftRadius": undefined, + "borderBottomRightRadius": undefined, + "borderRadius": undefined, + "borderTopLeftRadius": undefined, + "borderTopRightRadius": undefined, + "bottom": 0, + "left": 0, + "opacity": 0, + "position": "absolute", + "right": 0, + "top": 0, + } + } + /> + + + + + + + + + @@ -74141,14 +66810,6 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -74160,542 +66821,712 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-diego.mello" > -  + diego.mello - -  - - - + + - -  - - - - -  - - - -  - - - - + > + 10:00 AM + - - Encrypted message - + + + + + 1 + . + + + + + Dogs + + + + + + + - + - - + - + - + - - - + + > + + + + 2 + . + + + + + Cats + + + + + + - - + + + + + + + + + +`; + +exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` + + + + + + - + - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - + "backgroundColor": "#E4E7EA", + "borderBottomLeftRadius": undefined, + "borderBottomRightRadius": undefined, + "borderRadius": undefined, + "borderTopLeftRadius": undefined, + "borderTopRightRadius": undefined, + "bottom": 0, + "left": 0, + "opacity": 0, + "position": "absolute", + "right": 0, + "top": 0, + } + } + /> + + + + + + + + + @@ -74720,14 +67551,6 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -74739,2552 +67562,1814 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername" > -  + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - -  + 10:00 AM + + + + - + -  - - - - -  - + ] + } + > + + + this is a normal message + + + + - -  - + + + + + + + + + + + + - + - - Encrypted message - - + + + + + + - - - - - - - -`; - -exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + - - + + + + + + + + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + Edited message - - - - - 10:00 AM + - - - - - - - this is a normal message - - - - - - - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + - - + + + + + + + + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + Translated message - - - - -  - - - - - - 10:00 AM + - - - - - - - Edited message - - - - - - - + - - - - + + + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - - -  - - - - - 10:00 AM +  - - - - - - - Translated message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - - -  - - - - - - 10:00 AM - - - - - Encrypted message - - - - + Encrypted message + - + - - - - + + + - - - - + - + + - - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - - -  - - - - + "color": "#1F2329", + }, + ] + } + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + 10:00 AM + + - - - + + + + + + + + + - - - Error message - - + Error message - - + + - + - - + - - + - + + + + + + + + + + + + - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + + +  + + + + + - - + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - -  + Message with read receipt - - - - 10:00 AM + - - - - - - - Message with read receipt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - - -  - - - -  - - - - - 10:00 AM - - - - - Encrypted message - - - - + - - - - + + + - - + - + - - - + - - - + + + + + + - - - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - @@ -78691,14 +70310,6 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` accessible={true} collapsable={false} focusable={true} - hitSlop={ - { - "bottom": 4, - "left": 4, - "right": 4, - "top": 4, - } - } onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -78710,130 +70321,150 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onStartShouldSetResponder={[Function]} style={ { - "paddingLeft": 5, + "alignItems": "center", + "flexDirection": "row", + "flexShrink": 1, + "gap": 4, } } + testID="username-header-johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername" > -  + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - -  + 10:00 AM + + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + { + "paddingLeft": 5, + } + } + > + +  + + -  +  - + + -  + Encrypted message - -  - - - - - 10:00 AM - - - - - Encrypted message - - + - - + - - - - + - + + - - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + + + - +  + + + + - + + + +  + + + + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - - -  - - - - -  - - - + + - -  - - - - -  - - - -  - - - + }, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + testID="read-receipt-read" + > +  + + + + + - 10:00 AM + Encrypted message - - - - Encrypted message - - - - + - - + - - - - + - + + - - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + - - + + 10:00 AM + + + + - + +  + + + + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - - - -  - - - - -  - - - - -  - - - + + + - -  - - - +  + + + + -  - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + +  + + + + + + - 10:00 AM + Encrypted message - - - - Encrypted message - - - - + - - - -`; - -exports[`Story Snapshots: Mentions should match snapshot 1`] = ` - - - + - - + + + + + + + + + + + + - - + + 10:00 AM + + + + - + - +  + + + + +  + + + + +  + + + + +  + + + +  + + + + + + + Encrypted message + + + + + + + + + +`; + +exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + this is a normal message - - - - - - - - - - rocket.cat - - - - - - diego.mello - - - - - - all - - - - - - here - - - - - - general - - - - - - team - - - - - - + + - + - - - - + + + - - + + + + + + + + + + + + + - - - + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + + + - - - - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Edited message - - - - - - - - - - rocket.cat - - - Lorem ipsum dolor - - - diego.mello - - - sit amet, - - - all - - - consectetur adipiscing - - - here - - - elit, sed do eiusmod tempor - - - general - - - incididunt ut labore et dolore magna aliqua. - - - - - - + + - + - - - - - -`; - -exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + + + - - - - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + + 10:00 AM + + + - - + - diego.mello + Translated message - - - - - 10:00 AM + - - - - - - - rocket.cat - - - - - - diego.mello - - - - - - all - - - - - - here - - - - - - general - - - - - - team - - - - - - - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + - - - - diego.mello - - - - + } + accessible={true} + collapsable={false} + focusable={true} + hitSlop={ + { + "bottom": 4, + "left": 4, + "right": 4, + "top": 4, + } + } + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + { + "paddingLeft": 5, + } + } + > - 10:00 AM +  - - - - - - - rocket.cat - - - Lorem ipsum dolor - - - diego.mello - - - sit amet, - - - all - - - consectetur adipiscing - - - here - - - elit, sed do eiusmod tempor - - - general - - - incididunt ut labore et dolore magna aliqua. - - - - - - - + + 10:00 AM + + + + + Encrypted message + + + + - - - - - -`; - -exports[`Story Snapshots: Message should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + - - + + + + - - - - - - - - - - - - - + + + + + 10:00 AM + + + + + + - - - - - diego.mello - - - 10:00 AM - - - - - - - - - + "color": "#2F343D", + }, + ] + } + > + Error message + + + + - - - - - -`; - -exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + +  + + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Message with read receipt - - - - - - - - - - Forwarded message with file inside - - - - - - - - - - - - - rocket.cat - - - 10:00 AM - - - - - - - - - - user - - - document.pdf - - - - document.pdf - - - - - - - - - - - - + + - + - - - - + + + - - + + + + + + + + + + + + + - - - + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + + - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - Forwarded message with nested image - - - - - - - - - - - - - rocket.cat - - - - - - - - - Nested image from forwarded message - - - - - - - - - - -  - - - - - - - - - - - - - +  + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + testID="read-receipt-read" + > +  + - + + 10:00 AM + + + - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - Forwarded message with nested audio - - - - - - - - - - - - - rocket.cat - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - + "color": "#6C727A", + }, + ] + } + testID="message-encrypted" + > + Encrypted message + - + - - - - + + + - - - - - - + + - - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - Forwarded message with nested video - - - - - - - - - - - - - rocket.cat - - - - - - - - -  - - - - - - - - - - - - - - - - - + transition={null} + width={46.800000000000004} + /> + + + - - - - - - + + + + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + - - - + + + - +  + + + + +  + + + + +  + + + +  + + + + + 10:00 AM + + + + + Encrypted message + + + + + + + + + + + + + + + + + + + + + + + + + + + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + + + +  + + + + +  + + + + +  + + + + +  + + + +  + + + + + 10:00 AM + + + + + Encrypted message + + + + + + + + + + + + + + + + + + + + + + + + + + + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + + + +  + + + + +  + + + + +  + + + + +  + + + +  + + + + + 10:00 AM + + + + + Encrypted message + + + + + + + + + +`; + +exports[`Story Snapshots: Mentions should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + rocket.cat + + + + + + diego.mello + + + + + + all + + + + + + here + + + + + + general + + + + + + team + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + rocket.cat + + + Lorem ipsum dolor + + + diego.mello + + + sit amet, + + + all + + + consectetur adipiscing + + + here + + + elit, sed do eiusmod tempor + + + general + + + incididunt ut labore et dolore magna aliqua. + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + + + + 10:00 AM + + + + + + + + rocket.cat + + + + + + diego.mello + + + + + + all + + + + + + here + + + + + + general + + + + + + team + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + + + + 10:00 AM + + + + + + + + rocket.cat + + + Lorem ipsum dolor + + + diego.mello + + + sit amet, + + + all + + + consectetur adipiscing + + + here + + + elit, sed do eiusmod tempor + + + general + + + incididunt ut labore et dolore magna aliqua. + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: Message should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + Forwarded message with file inside + + + + + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + + + + user + + + document.pdf + + + + document.pdf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + Forwarded message with nested image + + + + + + + + + + + + + rocket.cat + + + + + + + + + Nested image from forwarded message + + + + + + + + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + Forwarded message with nested audio + + + + + + + + + + + + + rocket.cat + + + + + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + - + 10:00 AM + + + + + + + + - + + Forwarded message with nested video + + + + + + + + + - - + + rocket.cat + + + + > + + + + + +  + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Message forwarded twice with file inside - - - + + + + + + - - - - - - Message forwarded twice with file inside - - - - - + + rocket.cat + + + 10:00 AM + + + - - - rocket.cat - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-rocket.cat-" + > + + rocket.cat + + + 10:00 AM + + + - - - rocket.cat - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-user-undefined" + > + + user + + + document.pdf + + + - - - - - user - - - document.pdf - - - - document.pdf - - - - + document.pdf + @@ -87065,13 +83919,13 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` - + - - - + + + `; @@ -87085,705 +83939,1066 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + +  + + + + + - - + - diego.mello - - - 10:00 AM + I'm fine! - - + + + + + + + + + + + + + + + + + + + + + + + + -  + I'm fine! - - - - - - - - - I'm fine! - - - - - - + + - + + + + +  + - - - - + + + - - - + + + + + + + + + + + + + + - - - - + - - - - - - I'm fine! - - - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "400", + "lineHeight": 18, + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + > + 10:00 AM + -  +  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - -  - - - - - - - - - - I'm fine! - - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 22, + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + + + I'm fine! + + + - + - - - - + + + - - + + - - - - - - - - - I'm fine! - - - - - - + I'm fine! + + + - - -  - - - + + + + +  + - - - + + + `; @@ -88590,705 +85364,1047 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + +  + + + + + 10:00 AM + + + - - + - diego.mello - - - - -  + I'm fine! - - + + + + + + + + + + + + + + + + + + + + + + + - 10:00 AM - - - - - - - - I'm fine! - - - - - - + I'm fine! + + + - + + + + +  + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - I'm fine! - - - - - - - + "backgroundColor": "transparent", + "flexShrink": 1, + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600", + "lineHeight": 22, + "textAlign": "left", + }, + { + "color": "#1F2329", + }, + ] + } + > + diego.mello + -  +  - - - - - - - - - - - - - - + + 10:00 AM + - - - - - - - - - - - - - - + - diego.mello - - - - -  + I'm fine! - - - - 10:00 AM + - - - - - - - I'm fine! - - - - - - - + - - - - + + + - - + + - - - - - - - - - I'm fine! - - - - - - - - - - -  - + ] + } + > + + + I'm fine! + + + + - + + + + +  + - - - + + + `; @@ -90095,1444 +86789,1305 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - 10:00 AM - - - - - - - - - - I'm a very long long title and I'll break - - - - - - - How are you? - - - - - - - - - - - - + I'm a very long long title and I'll break + + + - I'm fine! + + How are you? + - + + + + + + + I'm fine! + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - 10:00 AM - - - - - + + rocket.cat + + + - - - - - rocket.cat - - + How are you? + - - - - How are you? - - - - - - + } + transition={null} + width={15} + /> - - + + + + + + + + - - - - - rocket.cat - - - - - - - How are you? - - - - - - - - - + rocket.cat + - Reply attachment can have a description + How are you? + + + - + + - - - - I'm fine! - - + Reply attachment can have a description - - + + + + + + + + I'm fine! + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Looks cool! - - - + + + + + + - - - - - - Looks cool! - - - - - + + rocket.cat + + + + + + + What you think about this one? + + + + + - + + - rocket.cat - - - + /> - - - - - What you think about this one? - - - - - - - - - - -  - - - - - +  + + @@ -92462,864 +89096,844 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Yes, I am - - - + + + + + + - - - - - - Yes, I am - - - - - + + rocket.cat + + + - - rocket.cat - - - - + + Are you seeing this mario + - - - - Are you seeing this mario - - - - - - ? - - - - - + + + ? + + + + + + + - + - - - - -  - - - + + - - + "lineHeight": 54, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + @@ -93328,586 +89942,532 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` - - - + + + + + + + + + + + +`; + +exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + - - - - - - - -`; - -exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - 10:00 AM - - - - - - - + - - - rocket.cat - - - script.sh - - - - script.sh - - - + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "500", + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + script.sh + - - - - - - - Here is the file - - + script.sh + + + + + + Here is the file + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - 10:00 AM - - - - - - - + - - - rocket.cat - - - config.yaml - - - - - - - This is a configuration file with - - - - important - - - - settings - - - - - - + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "500", + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + config.yaml + - - - - - - Got it! + + This is a configuration file with + + + + important + + + + settings + - + + + + + + + Got it! + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - 10:00 AM - - - - - + + rocket.cat + + + index.ts + + + - - - - - rocket.cat - - - index.ts - - - - index.ts - - - - + index.ts + + + + + + + - - + - - - rocket.cat - - - styles.css - - - - styles.css - - - + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "500", + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + styles.css + - - - - - - - Multiple files - - + styles.css + + + + + + Multiple files + + + + + - + - - - + + + `; @@ -95442,564 +91996,510 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - - - + - - - rocket.cat - - - script.sh - - - - script.sh - - - + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "500", + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + ] + } + > + script.sh + - - - - - - - Here is the file - - + script.sh + + + + + + Here is the file + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - + + rocket.cat + + + config.yaml + + + - - - - - rocket.cat - - - config.yaml - - - + - - - This is a configuration file with - - - - important - - - - settings - - + important - - - - - - - - - - - - Got it! + + + settings + - + + + + + + + Got it! + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - - - - - - rocket.cat - - - index.ts - - - - index.ts - - - - - - - + + rocket.cat + + + index.ts + + + - - - - - rocket.cat - - - styles.css - - - - styles.css - - - - + index.ts + + + + + + + + rocket.cat + + + styles.css + + - - - Multiple files - - + styles.css + + + + + + Multiple files + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Forwarded message with nested audio - - + + + + + + - - - 10:00 AM - - - - - - - - Forwarded message with nested audio - - - - - + + rocket.cat + + + - rocket.cat +  + @@ -98098,230 +94745,57 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot style={ [ { - "alignItems": "center", - "borderRadius": 4, - "borderWidth": 1, - "flex": 1, - "flexDirection": "row", - "height": 56, + "borderRadius": 2, + "height": 4, + "width": "100%", }, { - "backgroundColor": "#FFFFFF", - "borderColor": "#EBECEF", + "backgroundColor": "#CBCED1", }, ] } > - -  - - - + + - - - - - - - - - + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> @@ -98333,707 +94807,687 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot - + - - - - + + + - - + + + + + + + + + + + + + - + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Forwarded message with nested video - - + + + + + + - - - 10:00 AM - - - - - - - - Forwarded message with nested video - - - - - + + rocket.cat + + + + - rocket.cat +  - - - - - - -  - - - - - @@ -99043,879 +95497,859 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Message forwarded twice with file inside - - + + + + + + - - - 10:00 AM - - - - - - - - Message forwarded twice with file inside - - - - - + + rocket.cat + + + 10:00 AM + + + - - - rocket.cat - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-rocket.cat-" + > + + rocket.cat + + + 10:00 AM + + + - - - rocket.cat - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-user-undefined" + > + + user + + + document.pdf + + + - - - - - user - - - document.pdf - - - - document.pdf - - - - + document.pdf + @@ -99932,13 +96366,13 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot - + - - - + + + `; @@ -99952,2968 +96386,2040 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = } > - - - + - - + - - - - - - - - - - - - - - - - - diego.mello - - - - - + + - 10:00 AM - - - - - - - - - - I'm a very long long title and I'll break - - - - - - - How are you? - - - - - - - - - - - - - - - I'm fine! - - - - - - + } + transition={null} + width={46.800000000000004} + /> - - - + + + - - - - - - - - - - - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - - - - - - rocket.cat - - - - - - - How are you? - - - - - - - - - + I'm a very long long title and I'll break + - - - - - - I'm fine! + + How are you? + - + + + + + + + I'm fine! + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10:00 AM - - - - - - - - Looks cool! - - - - - + + rocket.cat + + + - - + + How are you? + - - rocket.cat - - - - - - - - - What you think about this one? - - - - - - - - - - -  - - - - - - + transition={null} + width={15} + /> - - + + + + + + + + I'm fine! + + + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Looks cool! - - + + + + + + - - - 10:00 AM - - - - - - - - Yes, I am - - - - - + + rocket.cat + + + + + + + What you think about this one? + + + + + - - rocket.cat - - - + + /> - - - - - Are you seeing this mario - - - - - - ? - - - - - - - - - - -  - - - - - +  + + @@ -102924,820 +98430,847 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = - + - - - - - -`; - -exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello - - - 10:00 AM + Yes, I am - - - + + + + + + - - - - - - How are you? - - - - - - - View thread - - - + "gap": 4, + } + } + > + + rocket.cat + + - -  - - - 1 - - - - -  - - - 0 - - - - - + + + Are you seeing this mario + + + + + + ? + + + + + - - - - + + -  - + +  + + + - - + + @@ -103745,3946 +99278,4594 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` - + - - - - + + + + +`; + +exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` + + + - - + - + - -  - - - + + + + + + + + + - How are you? - + } + > - + + diego.mello + + + 10:00 AM + + + -  - + /> - - + + + + + How are you? + + + + + + - + - + View thread + + + + - - +  + + + 1 + + + + +  + + + > + 0 + - - - - - - - - + - I'm fine! - + + + + + + +  + + + + + - + - - - - + + + - - + + +  + + + - + - + - -  - - - - Thread with emoji :) 😂 - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - - - - - - - - - - - + /> - + - I'm fine! - + style={ + { + "borderRadius": 4, + "height": 20, + "width": 20, + } + } + transition={null} + width={20} + /> - - + + - - - - - - - - - - -  + I'm fine! - + + + + + + + + + + + + + +  + + + + Thread with emoji :) 😂 + + + - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + I'm fine! + + - - - - + + + - - + + + + +  + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + +  + + + + - - + - -  - - - + + + + + + + + + - How are you? - + } + > -  + I'm fine! - + + + + + + + + + + + + - + + + How are you? + + + +  + + + + + + + - - + - + + - - - - - - - - - + + + + + + + + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + + + +  + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + +  + + + + - - -  - - - + - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - -  - - + + + + + + + + - - - - - - - - - - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Thread with attachment - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - - - - - - - - - - - + /> - + - This is a description :nyan_rocket: - + style={ + { + "borderRadius": 4, + "height": 20, + "width": 20, + } + } + transition={null} + width={20} + /> - - + + - - - - - - - - - - -  + This is a description :nyan_rocket: - + + + + + + + + + + + + + +  + + + + Thread with image attachment + + + - Thread with image attachment - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - example.png - - - - + example.png + + - - - - + + + - - + + + + +  + + + + Thread with file attachment + + + +  + + + + - - - -  - - - + - Thread with file attachment - - - -  - - + + + + + + + + - - - - - - - - - - - - - - - - presentation.pptx - - - - + presentation.pptx + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Thread with multiple attachments - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - - - - - - - - - - - - - + + - first-file.pdf - + style={ + { + "borderRadius": 4, + "height": 20, + "width": 20, + } + } + transition={null} + width={20} + /> - - + + + + + + + + + first-file.pdf + + - - - + + + `; @@ -107698,4280 +103879,4969 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + How are you? - - - + + + + + + - 10:00 AM + View thread + + - - - - - - How are you? - - - - - + } + > - +  + + - + + + - View thread - - - +  + + - + + + + + + - - -  - - - 1 - - - - -  - - - 0 - - - + "backgroundColor": undefined, + "borderRadius": undefined, + "margin": undefined, + "marginBottom": undefined, + "marginEnd": undefined, + "marginHorizontal": undefined, + "marginLeft": undefined, + "marginRight": undefined, + "marginStart": undefined, + "marginTop": undefined, + "marginVertical": undefined, + }, + { + "cursor": undefined, + }, + ] + } + underlayColor="#E4E7EA" + > + - - - - - - -  - - - - +  + - - + + - + + + + + + + + + + + + + + +  + + + + How are you? + + + +  + + + + + + + + + + + + + + + + + + + + + I'm fine! + + + - - - - + + + - - + + +  + + + - + + - + + + + + + - -  - - - + + + + + + + + + - How are you? - + } + > -  + I'm fine! - + + + + + + + + + + + + - + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + +  + + + + + + + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + [ + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "lineHeight": 22, + "textAlign": "left", + }, + { + "color": "#2F343D", + "lineHeight": undefined, + }, + ] + } + testID="message-preview-I'm fine!" + > + I'm fine! + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Thread with emoji :) 😂 - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + + +  + + + - + - + - -  - - - - How are you? - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + This is a description :nyan_rocket: + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + example.png + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Thread with attachment - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - This is a description :nyan_rocket: - - - - + presentation.pptx + + - - - - + + + - - + + +  + + + - + - + - -  - - - - Thread with image attachment - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - example.png - - - - + first-file.pdf + + - - - - + + + + +`; + +exports[`Story Snapshots: Names should match snapshot 1`] = ` + + + - - - + + - - -  - - - + - Thread with file attachment - - - -  - - + + + + + + + + - + - - - - - - - + JD + + + 10:00 AM + + - - presentation.pptx + + + Small name (2 chars) + + - + - - - - + + + - - + + + + + + + + + + + + + + + JD + + + 10:00 AM + + + +  + + + +  + + + + +  + + - - -  - - +  + + + + - Thread with multiple attachments +  - - -  - - - - - - - - - - - - - - - - - - - - first-file.pdf - - - - - +  + - - - - - - -`; - -exports[`Story Snapshots: Names should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - JD - - - 10:00 AM - - - - - - - - - - - Small name (2 chars) - - - - - - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + testID="message-encrypted" + > + Encrypted message + - - - - + + + - - + - + - - - + - - - + + + + + + - - - - JD - - - 10:00 AM - - - - -  - - - -  - - - - -  - - -  + John Doe - - -  - - - -  - - - - - - - Encrypted message - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "400", + "lineHeight": 18, + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + > + 10:00 AM + + + + - - + - John Doe - - - 10:00 AM + Medium name (8 chars) - - - - - - - - - - Medium name (8 chars) - - - - - - + + - + - - - - + + + - + + + + + + + + + + + + - + + John Doe + + + 10:00 AM + + + + +  + + - - + + + - - - - - + "lineHeight": 16, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + +  + + + + +  + + + +  + + + + + + + Encrypted message + + + + + + + + + - + - - John Doe - - - 10:00 AM - - + "backgroundColor": "#E4E7EA", + "borderBottomLeftRadius": undefined, + "borderBottomRightRadius": undefined, + "borderRadius": undefined, + "borderTopLeftRadius": undefined, + "borderTopRightRadius": undefined, + "bottom": 0, + "left": 0, + "opacity": 0, + "position": "absolute", + "right": 0, + "top": 0, + } + } + /> + + + + + + + + + - -  - -  + John Doe With An Extremely Long Display Name That Should Definitely Be Truncated In Any Reasonable User Interface Layout - - -  + 10:00 AM + + + - -  - - - - -  - + { + "color": "#2F343D", + }, + ] + } + > + + + Long name (22 chars) - should truncate + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + John Doe With An Extremely Long Display Name That Should Definitely Be Truncated In Any Reasonable User Interface Layout + + + 10:00 AM + + + + +  + + + -  - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - + + + +  + + + + - Encrypted message - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + +  + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - John Doe With An Extremely Long Display Name That Should Definitely Be Truncated In Any Reasonable User Interface Layout - - - 10:00 AM - - - - - - - - - - - Long name (22 chars) - should truncate - - - - - - - - - - + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "400", + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + testID="message-encrypted" + > + Encrypted message + - - - - + + + + +`; + +exports[`Story Snapshots: Pinned should match snapshot 1`] = ` + + + - - + - + - - - + - - - + + + + + + - - - - John Doe With An Extremely Long Display Name That Should Definitely Be Truncated In Any Reasonable User Interface Layout - - - 10:00 AM - - - - -  - -  + diego.mello - - -  + 10:00 AM @@ -115520,48 +111937,16 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` style={ [ { - "color": "#EC0D2A", + "color": "#2F343D", "fontSize": 16, }, [ { "lineHeight": 16, }, - undefined, - ], - { - "fontFamily": "custom", - "fontStyle": "normal", - "fontWeight": "normal", - }, - {}, - ] - } - > -  - - - - -  +  - -  - - - - - Encrypted message - + + + + + Message header + + + + + - + - - - -`; - -exports[`Story Snapshots: Pinned should match snapshot 1`] = ` - - - + - - + + - - - - - - - - - - - - - - - + + - diego.mello - - - 10:00 AM - - - - -  + Message without header - - - - - - - - - Message header - - - - - - + + - + + + + +  + - - - - + + + + +`; + +exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - - - - - - - - Message without header - - - - - - - + diego.mello + - + + 10:00 AM + + + + + + + + Message header + + + + + + + - - - - - -`; - -exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` - - - + + + - - + + - - - - - - - - - - - - - - - + - diego.mello - - - - -  + Message without header - - - - 10:00 AM + - - - - - - - Message header - - - - - - - + + + + +  + - - - - + + + + +`; + +exports[`Story Snapshots: Reactions should match snapshot 1`] = ` + + + - - + - - + - - - + + - - - - - - Message without header - - - - - - + transition={null} + width={36} + /> - + + + + + + + -  + diego.mello + + + 10:00 AM + - - - - - - - - - -`; - -exports[`Story Snapshots: Reactions should match snapshot 1`] = ` - - - - - - - - - + + + + + + Reactions + + + + + - + - + 😂 + + - - - - - - + 1 + + - - - + + 99 + + + + + + + 🤔 + + + 999 + + + + + + - - diego.mello - - - 10:00 AM - - - + + > + 9999 + + + - - - - - - Reactions - - - - - - - - - - 😂 - - - 1 - - - - - - - - 99 - - - - - - - 🤔 - - - 999 - - - - + - - - 🤔 - - - 9999 - - - - - - -  - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - - + + + - - + - - + - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - + + - - - - - - - - Multiple Reactions - - - - - - - - - - - 1 - - - - - - - - 1 - - - - + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + - - - - 1 - - - - + + + + + + + + + - - - ❤️ - - - 1 - - - - + 1 + + + + + + + - - - 🐶 - - - 1 - - - - + 1 + + + + + + + - - - 😀 - - - 1 - - - - + 1 + + + + + + + ❤️ + + + 1 + + + + + + - - - 😬 - - - 1 - - - - + 🐶 + + + 1 + + + + + + + 😀 + + - - - 😁 - - - 1 - - - - + 1 + + + + + + + 😬 + + + 1 + + + + + + + 😁 + + + 1 + + + + + + - - -  - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - + + + `; @@ -119650,2841 +115436,3015 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Reactions - - + + + + + + + + > + 😂 + + + 1 + - + + - 10:00 AM - - - - - - - - Reactions - - - - - - - - - - 😂 - - - 1 - - - - - - - - 99 - - - - + - - - 🤔 - - - 999 - - - - + 99 + + + + + + + 🤔 + + + 999 + + + + + + - - - 🤔 - - - 9999 - - - - + 🤔 + + + 9999 + + + + + + - - -  - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + Multiple Reactions - - - - + + + + + + - 10:00 AM - - - - - - - - Multiple Reactions - - - - - - - - - - - 1 - - - - - - - - 1 - - - - + - - - - 1 - - - - + 1 + + + + + + + - - - ❤️ - - - 1 - - - - + 1 + + + + + + + - - - 🐶 - - - 1 - - - - + 1 + + + + + + + ❤️ + + + 1 + + + + + + - - - 😀 - - - 1 - - - - + 🐶 + + + 1 + + + + + + + 😀 + + - - - 😬 - - - 1 - - - - + 1 + + + + + + + 😬 + + + 1 + + + + + + - - - 😁 - - - 1 - - - - + 😁 + + + 1 + + + + + + - - -  - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - - + + + + + + + + + + + +`; + +exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + - - - - - - - -`; - -exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should match snapshot 1`] = ` - - - - - - - - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - - - - - + "color": "#2F343D", + }, + ] + } + > + How are you? + + + + + View thread + + + - diego.mello +  - 10:00 AM + 1 - - - - - - - - How are you? - - - - - - - +  + + - - View thread - - - + + + + + + - - -  - - - 1 - - - - -  - - - 0 - - - + /> - - - - - - -  - - - - +  + - - + + - + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + I'm fine! + + - - - - + + + - - + - - - - - - - - - - - - - + - - + + - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - + style={ + { + "borderRadius": 4, + "height": 20, + "width": 20, + } + } + transition={null} + width={20} + /> - - + + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - This is a description - - - - + This is a description + + - - - + + + `; @@ -123904,1774 +119579,1703 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + How are you? - - - + + + + + + - 10:00 AM + View thread + + - - - - - How are you? - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + 1 + - +  + + - - View thread - - - + + + + + + - - -  - - - 1 - - - - -  - - - 0 - - - + /> - - - - - - -  - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - + + - + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + I'm fine! + + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - This is a description - - - - + This is a description + + - - - + + + `; @@ -125685,1403 +121289,1335 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma } > - - - + + +  + + + - + - + - -  - - - - How are you? - - - -  - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + I'm fine! + + - - - - + + + - - + - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Cool! - - - - + Cool! + + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - This is a description - - - - + This is a description + + - - - + + + `; @@ -127095,2523 +122631,2327 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont } > - - - + + +  + + + - + + - + + + + + + - -  - - - + + + + + + + + + - How are you? - + } + > -  + I'm fine! + + + + + + + + + + + + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - I'm fine! - - - - + Cool! + + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Cool! - - - - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + - - - - + + + - - + - + - - + - + + - - - - - - - - - + + + + + + + + - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - + This is a description + + - - - - + + + + +`; + +exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` + + + - - + - + - - + - - - - - - - - - - - + /> - + - This is a description - + style={ + { + "borderRadius": 4, + "height": 36, + "width": 36, + } + } + transition={null} + width={36} + /> - - + + - - - - - - - -`; - -exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Test Button - - - + + + + - + - - - - - Test Button - - - - - - - - Text button - - - - - + "color": "#FFFFFF", + "fontSize": 16, + }, + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontWeight": "500", + "textAlign": "center", + }, + undefined, + ] + } + > + Text button + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello + 🥑 - 10:00 AM + - - - - - - - - - 🥑 - - - - - - - Message with markdown - - - - - - - - - - Some text - - - - - - - - - This is a test - - + Message with markdown - - - + + + + + + + } + > - Text button + Some text - - - + + + + + + + + This is a test + + + + + + + Text button + + - + - - - + + + `; @@ -129745,1051 +125173,985 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - - diego.mello - - - - - 10:00 AM - - - - - - - - Test Button - - - - - + - - - Text button - - - - + Test Button + + + + + + + Text button + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - - diego.mello - - - - - 10:00 AM - - - - - - - - 🥑 - - - - - - - Message with markdown - - - - - + + 🥑 + + + + + + } + > - - - - Some text - - - + Message with markdown - + + + + + + + } + > - - - This is a test - - + Some text - - + + + + + + - - - Text button - - - - + This is a test + + + + + + + Text button + + - + - - - + + + `; @@ -130985,464 +126373,444 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` } > - - - + - - + - - + + - - - - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Message - - - - - - - - - - Message - - - - - - + + - + - - - + + + `; @@ -131456,464 +126824,444 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - - diego.mello - - - - - 10:00 AM - - - - - - - - Message - - - - - - + Message + + + - + - - - + + + `; @@ -131927,6309 +127275,5837 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` } > - - - - - + + - - - - - - + + + - - - - - diego.mello - - - - message removed - - - - - - - - - - + - - - - - - - - - - - + + - - - diego.mello - - - - joined the channel - + > + message removed - - + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - Pinned a message: - + > + diego.mello - - - + + + joined the channel + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - left the channel - + > + diego.mello - - + + + Pinned a message: + + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed rocket.cat - + > + diego.mello - - + + + left the channel + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - added rocket.cat - + > + diego.mello - - + + + removed rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - - - - diego.mello - - - - muted rocket.cat - + "marginLeft": 10, + } + } + > + + + diego.mello - - + + + added rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - unmuted rocket.cat - + > + diego.mello - - + + + muted rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - defined rocket.cat as admin - + > + diego.mello - - + + + unmuted rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed rocket.cat as admin - + > + diego.mello - - + + + defined rocket.cat as admin + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room name to: New name - + > + diego.mello - - + + + removed rocket.cat as admin + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room description to: new description - + > + diego.mello - - + + + changed room name to: New name + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room announcement to: new announcement - + > + diego.mello - - + + + changed room description to: new description + + + - - + - - - - + + - - - - - - + + + - + + - - - - diego.mello - - - - changed room topic to: new topic - + } + > + + + diego.mello - - + + + changed room announcement to: new announcement + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room to public - + > + diego.mello - - + + + changed room topic to: new topic + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - disabled E2E encryption for this room - + > + diego.mello - - + + + changed room to public + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - enabled E2E encryption for this room - + > + diego.mello - - + + + disabled E2E encryption for this room + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed @rocket.cat from this team - + > + diego.mello - - + + + enabled E2E encryption for this room + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - added @rocket.cat to this team - + > + diego.mello - - + + + removed @rocket.cat from this team + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - added #channel-name to this team - + > + diego.mello - - + + + added @rocket.cat to this team + + + - - + - - - - + + - - - - - - + + + - + + - - - - diego.mello - - - - converted #channel-name to a team - + + + diego.mello - - + + + added #channel-name to this team + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - converted #channel-name to channel - + > + diego.mello - - + + + converted #channel-name to a team + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - deleted #channel-name - + > + diego.mello - - + + + converted #channel-name to channel + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed #channel-name from this team - + > + diego.mello - - + + + deleted #channel-name + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - was removed by ABAC - + > + diego.mello - - + + + removed #channel-name from this team + + + - - + - - - - + + - - - - - - + + + - + + - + + > + diego.mello + + - Unsupported system message + was removed by ABAC - - + + - - - -`; - -exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` - - - + - - - - + + - - - - - - + + + - + + - - - - diego.mello - - - - message removed - - - - + Unsupported system message + + - - + + +`; + +exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` + + + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - joined the channel - + > + diego.mello - - + + + message removed + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - Pinned a message: - + > + diego.mello - - - + + + joined the channel + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - left the channel - + > + diego.mello - - + + + Pinned a message: + + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed rocket.cat - + > + diego.mello - - + + + left the channel + + + - - + - - - - - - + + - - - - - - - - - - diego.mello - - - - added rocket.cat - + "borderRadius": 4, + "height": 26, + "width": 26, + } + } + transition={null} + width={26} + /> + + + + + + + + + + diego.mello - - + + + removed rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - muted rocket.cat - + > + diego.mello - - + + + added rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - unmuted rocket.cat - + > + diego.mello - - + + + muted rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - defined rocket.cat as admin - + > + diego.mello - - + + + unmuted rocket.cat + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed rocket.cat as admin - + > + diego.mello - - + + + defined rocket.cat as admin + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room name to: New name - + > + diego.mello - - + + + removed rocket.cat as admin + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room description to: new description - + > + diego.mello - - + + + changed room name to: New name + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room announcement to: new announcement - + > + diego.mello - - + + + changed room description to: new description + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room topic to: new topic - + > + diego.mello - - + + + changed room announcement to: new announcement + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - changed room to public - + > + diego.mello - - + + + changed room topic to: new topic + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - disabled E2E encryption for this room - + > + diego.mello - - + + + changed room to public + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - enabled E2E encryption for this room - + > + diego.mello - - + + + disabled E2E encryption for this room + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed @rocket.cat from this team - + > + diego.mello - - + + + enabled E2E encryption for this room + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - added @rocket.cat to this team - + > + diego.mello - - + + + removed @rocket.cat from this team + + + - - + - - - - + + - - - - - - + + + - + + - + + diego.mello + + + - - - diego.mello - - - - added #channel-name to this team - + > + added @rocket.cat to this team - - + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - converted #channel-name to a team - + > + diego.mello - - + + + added #channel-name to this team + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - converted #channel-name to channel - + > + diego.mello - - + + + converted #channel-name to a team + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - deleted #channel-name - + > + diego.mello - - + + + converted #channel-name to channel + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - removed #channel-name from this team - + > + diego.mello - - + + + deleted #channel-name + + + - - + - - - - + + - - - - - - + + + - + + - + - - - diego.mello - - - - was removed by ABAC - + > + diego.mello - - + + + removed #channel-name from this team + + + - - + - - - - + + - - - - - - + + + - + + - + + > + diego.mello + + - Unsupported system message + was removed by ABAC - - + + - - - -`; - -exports[`Story Snapshots: Temp should match snapshot 1`] = ` - - - + - - - - - - - - - - - - - - - - - - - + + + - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - Temp message - - - - - - - - - - - - + } + transition={null} + width={26} + /> + + + + + + + + + Unsupported system message + - - - + + + `; -exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: Temp should match snapshot 1`] = ` - - - + - - + - - - - - - - - - - - - - - - - - diego.mello - - - - - + + - 10:00 AM - - - - - - - - Temp message - - - - - - - - - - + transition={null} + width={36} + /> + + + + - - - - - - - -`; - -exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Temp message - - - - - - - - - - - - Title - - - - - - - Image text - - - - - - - - - - - - - - - - this is a thumbnail - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` - - - - + + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - - diego.mello - - - - - - 10:00 AM - - - - - - - - - - Title - - - - - - - Image text - - - - - - - - - - - - - - - - this is a thumbnail - - - - - - + > + + + + Temp message + + + - + - - - + + + `; -exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` +exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - + + Title + + + + > + + + + Image text + + + + - - + + + - - + - diego.mello - - - 10 November 2017 - - - - - - - - - - - Testing - - - - - - + this is a thumbnail + + + - + - - - + + + `; -exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - - - - 10 November 2017 - - - + Title + + + + - Testing + + Image text + - + + + + + + + + this is a thumbnail + + + + + - + - - - + + + `; -exports[`Story Snapshots: Translated should match snapshot 1`] = ` +exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` - - - + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + 10 November 2017 + + + + - - + - other.user - - - 10:00 AM + Testing - - - - -  - - - - - - - - - - - Message translated - - - - - - + + - + - - - + + + `; -exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` - - - - + + - + + + + + + + + + + + + - - - - - - - - - - + > + diego.mello + + + + 10 November 2017 + + + - - + - other.user + Testing - - - - -  - - - - - - 10:00 AM + - - - - - - - Message translated - - - - - - - + - - - + + + `; -exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot 1`] = ` +exports[`Story Snapshots: Translated should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - + - - - - - - - - - - - + > + other.user + + + 10:00 AM + - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - - - rocket.cat - - - - - - - Custom fields - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - - - - + + + + + + + + - + - - - - - rocket.cat - - - - - - - Custom fields 2 - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - - Value 2 - - - - - - - - - - - - - + Message translated + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + other.user + + + + + +  + + + + + + 10:00 AM + + + + + + - - - - - Message - - - - - - + Message translated + + + - + - - - + + + `; -exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - diego.mello - - - - - - 10:00 AM - - + + rocket.cat + + + - - - - - rocket.cat - - - - - - - Custom fields - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - - - Field 2 - - - - - - Value 2 - - - - - - - - + Custom fields + + + - - + + Field 1 + - - - rocket.cat - - - - - - - Custom fields 2 - - - - - - - - Field 1 - - - - - - Value 1 - - - - - - + - Field 2 + Value 1 - - - - - - Value 2 - - - - - - - + + - - - - - - - - Message + Field 2 - - + + + + + Value 2 + + + + + + - - - - - - - - - - - -`; - -exports[`Story Snapshots: URL should match snapshot 1`] = ` - - - - - - - - - - - + - - - - + + rocket.cat + + + + + + + Custom fields 2 + + + + + + > + + + Field 1 + + + + + + Value 1 + + + + + + + + Field 2 + + + + + + + Value 2 + + + + + + + - - + + - - + - diego.mello - - - 10:00 AM + Message - - - - - - - - Rocket.Chat - Free, Open Source, Enterprise Team Chat - - - Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting. - - - - - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - + + - + - - - - + + + + +`; + +exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match snapshot 1`] = ` + + + - - + - - + - - + + - - - - - - - + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - 10:00 AM - - + }, + { + "borderColor": "#CBCED1", + }, + ] + } + testID="reply-rocket.cat-Custom fields" + > - - - - + rocket.cat + + + + + + + Custom fields + + + + + + - Message + Field 1 - + + > + + Value 1 + + + - - - - - - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - - - - - - - - - - - - - - - - - - - - - + + + + Field 2 + + + + + + Value 2 + + + + + + + + + + - - Google - - + rocket.cat + + + - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - - - - - - - - - - - - - - -`; - -exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - + + + + Custom fields 2 + + + + + + > + + + Field 1 + + + + + + Value 1 + + + + + + + + Field 2 + + + + + + + Value 2 + + + + + + + - - + + - - + - diego.mello - - - 10:00 AM + Message - - - - - - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - + + - + - - - - + + + + +`; + +exports[`Story Snapshots: URL should match snapshot 1`] = ` + + + - - + - - + - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - + + - - - - + } + transition={null} + width={36} + /> - - - + + + - - - - - - - -`; - -exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - - diego.mello - - - - + "color": "#095AD2", + }, + ] + } + > + Rocket.Chat - Free, Open Source, Enterprise Team Chat + - 10:00 AM + Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting. - + + + + - + - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - + "color": "#6C727A", + }, + ] + } + > + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + - + - - - - + + + - - - + + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - + + Message + - - - + + - - - - diego.mello - - - - + "color": "#095AD2", + }, + ] + } + > + Google + - 10:00 AM + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - + + + + + + + + + + + + + + + + + + + + - + - + "color": "#095AD2", + }, + ] + } + > + Google + + + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + - + + - - - + + + `; -exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` - - - + - - + - - - - - - - - - - - - - - - - - diego.mello - - - - - + + - 10:00 AM - - - - - - Rocket.Chat - Free, Open Source, Enterprise Team Chat - - - Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting. - - - - - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - + transition={null} + width={36} + /> - - - + + + - - - - - - - - - - - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - - - diego.mello - - - - + "color": "#095AD2", + }, + ] + } + > + Google + - 10:00 AM + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - + + + + + + + + + + + + + + + + + + + - - - - - - Message - - - - - - - - - + + - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - + } + transition={null} + width={36} + /> + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + - + - - - - + + + + +`; + +exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - + + + diego.mello + + + + + 10:00 AM + + - - + - - - Google - - - Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. - - - - + "color": "#6C727A", + }, + ] + } + > + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + - - + - - - - - -`; - -exports[`Story Snapshots: Usernames should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + - + + + 10:00 AM + + + - - - - jd - - - 10:00 AM - - - - - - - - - - - Small username (2 chars) - - - - - - - - + "flexDirection": "column", + "gap": 4, + }, + false, + ] + } + /> - + - - - - + + + + +`; + +exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` + + + - - + - + - - - + + + - - - + style={ + { + "borderRadius": 4, + "height": 46.800000000000004, + "width": 46.800000000000004, + } + } + transition={null} + width={46.800000000000004} + /> + + + + - - - - jd - - - 10:00 AM - - - - -  - -  + diego.mello - -  - - + /> + + + 10:00 AM + + - + -  - + { + "color": "#095AD2", + }, + ] + } + > + Rocket.Chat - Free, Open Source, Enterprise Team Chat + + + Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting. + + - -  - - - -  - + + + Google + + + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + + + + + + + + + + + + + + + - + - - Encrypted message - - + + + + + + - - - - - - - - - - - - - + diego.mello + + + + + + 10:00 AM + + + + + - + + Message + - - - + + - - + + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + + + + + + + + + + + + + + + + + + + + + + + - - john.doe - - - 10:00 AM - - - - - + Google + + - - - - - - Medium username (8 chars) - - - - - - + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + - + + - - - - + + + + +`; + +exports[`Story Snapshots: Usernames should match snapshot 1`] = ` + + + - - + - + - - - + - - - + + + + + + - - - - john.doe - - - 10:00 AM - - - - -  - - -  - - - -  + jd - - -  + 10:00 AM - -  - - - -  - - - - - - - Encrypted message - + /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM + Small username (2 chars) - - - - - - - - - - Long username (22 chars) - should truncate - - - - - - + + - + - - - - + + + - + + + + + + + + + + + + + + jd + + + 10:00 AM + + + - + +  + + - - + + + - - - - - + "lineHeight": 16, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + +  + + + + +  + + + +  + + + + Encrypted message + + + + + + + + + + + + + + + + + + + + + + + + - - johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername - - - 10:00 AM - - - - -  - -  + john.doe - - -  + 10:00 AM + + + - -  - - - - -  - + { + "color": "#2F343D", + }, + ] + } + > + + + Medium username (8 chars) + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + john.doe + + + 10:00 AM + + + + +  + + + +  + + + + +  + + + + -  - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - - Encrypted message - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + +  + + + + + + + Encrypted message + - + - - - -`; - -exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` - - - + - - + - - + - - - - - - - - - - - - - - - - - Diego Mello - - @ - diego.mello - - - - 10:00 AM - - - + + - - - - - - - - Message - - - - - - + transition={null} + width={36} + /> - - - + + + - - - - - - - - - - - - - - - - - - - - - + > + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + + + + - - + - Diego Mello - - @ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - - - - 10:00 AM + Long username (22 chars) - should truncate - - - - - - - - - - Message - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername + + + 10:00 AM + + + + +  + + + +  + + + + +  + + + + +  + + + + +  + + + +  + + + + + + + Encrypted message + - - - + + + `; -exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` +exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - + Diego Mello + - - - - - - - - + @ + diego.mello + + + + 10:00 AM + + + + - - + - Diego Mello - - @ - diego.mello - - - - - - - 10:00 AM - - - - - - - - Message - - - - - - + "color": "#2F343D", + }, + ] + } + > + Message + + + - + - - - - + + + - - + + + + + + + + + + + + + - - - + Diego Mello + - - - - - - - - + @ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + + 10:00 AM + + + + - - + - Diego Mello - - @ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - + Message - - - - + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` + + + + + + + + + + + + + + - 10:00 AM - - - - - - - - Message - - - - - - + { + "borderRadius": 4, + "height": 46.800000000000004, + "width": 46.800000000000004, + } + } + transition={null} + width={46.800000000000004} + /> - - - + + + - - - - - - - -`; - -exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - - - - - This is a description - - - - - - - - - - -  - - - - - - - - - - - - - - - + [ + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "400", + "textAlign": "left", + }, + { + "color": "#6C727A", + }, + ] + } + > + @ + diego.mello + + + + + + + 10:00 AM + + + + + + + + Message + + + - + - - - - + + + - - + + + + + + + + + + + + + - + testID="username-header-Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + > + + Diego Mello + + @ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + + + + 10:00 AM + + + - - - - - - - - Header false - - - - - - - -  - - - - - - - - - - - - - - - + > + + Message + + + - - + - - - - + + + + +`; + +exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - + + + diego.mello + + + 10:00 AM + + + + - - - - - multi file - - + This is a description - - - - - - -  - - - - - - - - - - - + transition={null} + width={15} + /> - + + + + + + +  + + + + @@ -162882,1541 +156115,981 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` style={ [ { - "alignItems": "center", - "borderRadius": 4, - "borderWidth": 1, - "flex": 1, - "flexDirection": "row", - "height": 56, + "borderRadius": 2, + "height": 4, + "width": "100%", }, { - "backgroundColor": "#FFFFFF", - "borderColor": "#EBECEF", + "backgroundColor": "#CBCED1", }, ] } > - -  - - - + + - - - - - - - - - + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> - - + - - - - + + + - - + + - - - - - - - -  - - - + + + + + + - - - - - - - - - - + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + - - - - - This is a description - - - - - - - - -  - - - - - - - - - - - + "backgroundColor": "#156FF5", + }, + ] + } + /> - - - - -  - - - - - - - - - - - - + /> - - + + - - - - - -`; - -exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` - - - + + + - - + + - + + + + + + multi file + + + + + - + - - +  + + + + - + + + + - - + + - - - diego.mello +  - - - 10:00 AM - - - + - - - - This is a description - - - - - - + /> - - -  - - - - - - - - - - - - + /> - + + - - - - + + + - - + + - - + +  + + + + - - - - First message - - - + /> + + - - - - - - - - - - - - - - - - - - - - - - + + This is a description + - - - - This is a description - - - + } + transition={null} + width={15} + /> + + + + + + +  + + + + + - -  - - - + + - - - - - - - - - + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> - - - - - - - - - - - - - - - - - - - - + +  + + + + @@ -165718,419 +158200,517 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` style={ [ { - "alignItems": "center", - "borderRadius": 4, - "borderWidth": 1, - "flex": 1, - "flexDirection": "row", - "height": 56, + "borderRadius": 2, + "height": 4, + "width": "100%", }, { - "backgroundColor": "#FFFFFF", - "borderColor": "#EBECEF", + "backgroundColor": "#CBCED1", }, ] } > - -  - - - + + - - - - - - - - - + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> - - + + - - - - + + + + +`; + +exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - + + + diego.mello + + + + + 10:00 AM + + - - - - - multi file - - + This is a description - - - - - - - -  - - - - - - - - - - - - - - - - - - This is a description - - - - - - - - - - -  - - - + - - - - - - - - + transition={null} + width={15} + /> - + + + + + + +  + + + + @@ -166759,560 +158941,501 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` style={ [ { - "alignItems": "center", - "borderRadius": 4, - "borderWidth": 1, - "flex": 1, - "flexDirection": "row", - "height": 56, + "borderRadius": 2, + "height": 4, + "width": "100%", }, { - "backgroundColor": "#FFFFFF", - "borderColor": "#EBECEF", + "backgroundColor": "#CBCED1", }, ] } > - -  - - - + + - - - - - - - - - + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + + + + + + + + + + + + + + + + + + + > + + + + First message + + + + + - + + - - - - - -`; - -exports[`Story Snapshots: WithFile should match snapshot 1`] = ` - - - + + + - - + + - - + - - - - - - - - - - - + > + This is a description + + + + - diego.mello +  - + + - 10:00 AM - - - - - - + /> - - - - - - - File.pdf - - - - - - - - - - - - This is a description - - - - - - + /> + - + + - - - - + + + - - + + - - - +  + + + + - + + - - - - - - - File.pdf - - - - - - - - - - - - This is a description - - - - - - + /> - - - - - - - - - - File.pdf - - - - - - - - - - - - This is a description - - - - - - - + /> - - - - - - - - - - -`; - -exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` - - - + + + + + + + + - - + + - + + + + + + multi file + + + + + - + - + +  + + + + + + + + + + + + + + + + + + + This is a description + + + + + + + + + + +  + + + + - - + + + - - + + - - - diego.mello +  - - - 10:00 AM - - - + - - - - - - - File.pdf - - - - - - + "borderRadius": 2, + "height": 4, + "width": "100%", + }, + [Function], + { + "backgroundColor": "#156FF5", + }, + ] + } + /> + + + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: WithFile should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + - This is a description + File.pdf - - - + + + + + This is a description + + + + + + + - + - - - - + + + - - + + - - + + + + + File.pdf + + + + + + + + + + + - + + - - - - - - - File.pdf - - - - - - + } + transition={null} + width={15} + /> + + + + + + + + + - This is a description + File.pdf - - - + + + + + This is a description + + + + + + + - - + + - - - + + + `; -exports[`Story Snapshots: WithImage should match snapshot 1`] = ` +exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - - diego.mello - - - 10:00 AM - - - - - + - This is a description + File.pdf - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diego.mello - - - 10:00 AM - - - - - - - - + + - - Message, not description - - - - - + } + transition={null} + width={15} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + - This is a description + File.pdf - - - - + + + + + + + This is a description + + - - - - - -  - - - - + } + transition={null} + width={15} + /> - - + + + + + + + + + + + + + + + +`; + +exports[`Story Snapshots: WithImage should match snapshot 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + + This is a description + - - - - This is a description - - - - - - - - - - - - - -  - - - - + transition={null} + width={15} + /> - + + + + + + + - - - - - -  - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + 10:00 AM + + + + - - + - diego.mello - - - 10:00 AM + Message, not description - - - + + + + + + - - - + + - - multi file - - - - - + } + transition={null} + width={15} + /> + + + + + + + - - - - - -  - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + + + + - - + - + - - -  - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + > +  + - - + + + - - + - + - diego.mello - + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > - 10:00 AM +  - - - - - - - - This is a description for Base64 Attachment - - - - - - - - - - - - - - - - - - - - - + - - - - - -`; - -exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` - - - + + + - - + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - - - - - + "color": "#2F343D", + }, + ] + } + > + multi file + + + + - + + - diego.mello +  - + - 10:00 AM - + } + accessibilityValue={ + { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + { + "borderRadius": 4, + "flexDirection": "column", + } + } + > - - + - - - - This is a description - - - - - + - + - - - - -  - - - - - - + "lineHeight": 54, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + - + - - - - + + + - - + + + + + + + + + + + + + - + + + diego.mello + + + 10:00 AM + + + + - - + + This is a description for Base64 Attachment + - - - - Header false - - - - - + + + + + + + - + + + - - - - -  - - - - - + "padding": 16, + }, + undefined, + ] + } + /> + - - + - - - - + + + + +`; + +exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` + + + - - + + + + + + + + + + + + + - - - + diego.mello + + + + + + 10:00 AM + + + + + - - - - - - - - - - - + > + This is a description + + + + - - diego.mello - - + + - 10:00 AM - - - +  + + + + /> + + + + + + + + + + + + + + + + + + + + + + - - - - - multi file - - + Header false - - + + + + + + + /> - - - - - -  - - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - - - - -  - - - - - + /> - + + - - - - + + + - - + + + + + + + + + + + + + - + + + diego.mello + + + 10:00 AM + + + + + + + + + + + multi file + + + + + - + - - + - - + + - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + - - + + - diego.mello +  - + + + + + + + + + + + + + + + + + + + + + - 10:00 AM - + style={ + { + "borderRadius": 4, + "height": 46.800000000000004, + "width": 46.800000000000004, + } + } + transition={null} + width={46.800000000000004} + /> + + + + + + + + + + + diego.mello + + + + + + 10:00 AM + + + + - - + + This is a description for Base64 Attachment + - - - - This is a description for Base64 Attachment - - - - - - - - + + + + + + + - + + + - - - - - - - - + "padding": 16, + }, + undefined, + ] + } + /> + - + - - - + + + `; @@ -175201,1714 +169025,1654 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - + + + diego.mello + + + 10:00 AM + + + + + - + - - - - + This is a description + + - - - - - - - + > + + + + + - + - diego.mello - + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > - 10:00 AM +  - - - - - - - - - - This is a description - - - - - - - - - - - -  - - - - - - + - - - - + + + - - + + - - - - - - - - - Header false - - - - - + + + + + + - - - -  - - - - + }, + { + "borderColor": "#CBCED1", + "borderWidth": 1, + }, + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > + +  + - - + + - - - - + + + - - + + + + + + + + + + + + + - - + diego.mello + + - + 10:00 AM + + + + + + + + - - - - + - - - + "flexShrink": 1, + "fontSize": 16, + "lineHeight": 22, + }, + { + "color": "#2F343D", + }, + ] + } + > + multi file + + + - + - diego.mello - + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > - 10:00 AM +  - + + - - - - - - multi file - - - - - - - - - - -  - - - - - - + - - - -  - - - - + }, + { + "borderColor": "#CBCED1", + "borderWidth": 1, + }, + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > + +  + - + - - - + + + `; @@ -176922,1891 +170686,1831 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` } > - - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - diego.mello - - - - - - 10:00 AM - - - - + + This is a description + - - - - This is a description - - - - - - - - + + + + + + - - - -  - - - - + }, + { + "borderColor": "#CBCED1", + "borderWidth": 1, + }, + ], + { + "borderWidth": 0, + "bottom": 0, + "left": 0, + "position": "absolute", + "right": 0, + "top": 0, + }, + { + "backgroundColor": "#E4E7EA", + }, + ] + } + /> + + +  + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - diego.mello - - - - - - 10:00 AM - - - - - - - -  - - - - + }, + { + "borderColor": "#CBCED1", + "borderWidth": 1, + }, + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > + +  + - + - - - - + + + - - + + + + + + + + + + + + + - - - - - - - - - - - + > + diego.mello + + + + 10:00 AM + + + - - + - diego.mello + multi file - - - - - 10:00 AM + + + + + - - - - - - multi file - - - - - - - + - - - -  - - - - - + - + + + + + + + - - - -  - - - - + }, + { + "borderColor": "#CBCED1", + "borderWidth": 1, + }, + ], + { + "alignItems": "center", + "justifyContent": "center", + "position": "absolute", + }, + ] + } + > + +  + - + - - - + + + `; @@ -178820,232 +172524,212 @@ exports[`Story Snapshots: WithoutHeader should match snapshot 1`] = ` } > - - - + + - - - - - - - - - Message - - - - - - + Message + + + - - + + - - - + + + `; @@ -179059,232 +172743,212 @@ exports[`Story Snapshots: WithoutHeaderLargeFont should match snapshot 1`] = ` } > - - - + + - - - - - - - - - Message - - - - - - + Message + + + - - + + - - - + + + `; diff --git a/app/containers/message/components/stories/Attachments.stories.tsx b/app/containers/message/components/stories/Attachments.stories.tsx index 28ca40d4b68..060d96b4d1e 100644 --- a/app/containers/message/components/stories/Attachments.stories.tsx +++ b/app/containers/message/components/stories/Attachments.stories.tsx @@ -4,14 +4,13 @@ import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; import { selectServerSuccess } from '../../../../actions/server'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import Attachments from '../Attachments/Attachments'; const mockMessageContext: Partial = { - baseUrl: 'https://open.rocket.chat', - user: { id: 'user-id', username: 'rocket.cat', token: 'token' }, showAttachment: undefined }; @@ -50,6 +49,7 @@ const MOCK_MULTIPLE_IMAGES = [ const createServerStore = (version: string) => { const store = createMockedStore(); store.dispatch(selectServerSuccess({ server: 'https://open.rocket.chat', version, name: 'Test' })); + store.dispatch(setUser({ id: 'user-id', username: 'rocket.cat', token: 'token' })); return store; }; diff --git a/app/containers/message/components/stories/Broadcast.stories.tsx b/app/containers/message/components/stories/Broadcast.stories.tsx index c0c53a45251..9da56279b1c 100644 --- a/app/containers/message/components/stories/Broadcast.stories.tsx +++ b/app/containers/message/components/stories/Broadcast.stories.tsx @@ -2,12 +2,14 @@ import { type ReactNode } from 'react'; import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import BroadcastLeaf from '../Broadcast'; const store = createMockedStore(); +store.dispatch(setUser({ id: 'reader-id', username: 'rocket.cat' })); const otherAuthorItem = { id: 'msg-id', @@ -24,9 +26,7 @@ const ownAuthorItem = { } as unknown as TAnyMessageModel; const room: Partial = { - broadcast: true, - user: { id: 'reader-id', username: 'rocket.cat' }, - replyBroadcast: () => {} + broadcast: true }; const StoryWrapper = ({ item, children }: { item: TAnyMessageModel; children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/CallButton.stories.tsx b/app/containers/message/components/stories/CallButton.stories.tsx index 0488be2988d..ff92c6c4088 100644 --- a/app/containers/message/components/stories/CallButton.stories.tsx +++ b/app/containers/message/components/stories/CallButton.stories.tsx @@ -16,9 +16,7 @@ const item = { autoTranslate: false } as unknown as TAnyMessageModel; -const room: Partial = { - handleEnterCall: () => {} -}; +const room: Partial = {}; const StoryWrapper = ({ children }: { children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/CollapsibleQuote.stories.tsx b/app/containers/message/components/stories/CollapsibleQuote.stories.tsx index e1a5c57b0bd..a3ebd40049a 100644 --- a/app/containers/message/components/stories/CollapsibleQuote.stories.tsx +++ b/app/containers/message/components/stories/CollapsibleQuote.stories.tsx @@ -19,7 +19,7 @@ const item = { const StoryWrapper = ({ children }: { children: ReactNode }) => ( - + {children} diff --git a/app/containers/message/components/stories/Content.stories.tsx b/app/containers/message/components/stories/Content.stories.tsx index 87df5e33651..b70fff785d3 100644 --- a/app/containers/message/components/stories/Content.stories.tsx +++ b/app/containers/message/components/stories/Content.stories.tsx @@ -2,12 +2,14 @@ import { type ReactNode } from 'react'; import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import ContentLeaf from '../Content'; const store = createMockedStore(); +store.dispatch(setUser({ id: 'reader-id', username: 'reader' })); const item = { id: 'msg-id', @@ -17,9 +19,7 @@ const item = { autoTranslate: false } as unknown as TAnyMessageModel; -const room: Partial = { - user: { id: 'reader-id', username: 'reader' } -}; +const room: Partial = {}; const StoryWrapper = ({ children }: { children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/Discussion.stories.tsx b/app/containers/message/components/stories/Discussion.stories.tsx index 8a96a8463f1..310d6466455 100644 --- a/app/containers/message/components/stories/Discussion.stories.tsx +++ b/app/containers/message/components/stories/Discussion.stories.tsx @@ -2,12 +2,14 @@ import { type ReactNode } from 'react'; import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import DiscussionLeaf from '../Discussion'; const store = createMockedStore(); +store.dispatch(setUser({ id: 'reader-id', username: 'reader' })); const item = { id: 'msg-id', @@ -18,10 +20,7 @@ const item = { autoTranslate: false } as unknown as TAnyMessageModel; -const room: Partial = { - user: { id: 'reader-id', username: 'reader' }, - onDiscussionPress: () => {} -}; +const room: Partial = {}; const StoryWrapper = ({ children }: { children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/Message.stories.tsx b/app/containers/message/components/stories/Message.stories.tsx index d8b2c37816e..f1fb9c3d09d 100644 --- a/app/containers/message/components/stories/Message.stories.tsx +++ b/app/containers/message/components/stories/Message.stories.tsx @@ -17,6 +17,8 @@ import { import { mockedStore as store } from '../../../../reducers/mockedStore'; import { updateSettings } from '../../../../actions/settings'; import { setCustomEmojis } from '../../../../actions/customEmojis'; +import { setUser } from '../../../../actions/login'; +import { selectServerSuccess } from '../../../../actions/server'; import { createMessageActionStore, MessageActionStoreContext } from '../../stores/MessageActionStore'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; @@ -55,6 +57,8 @@ const date = new Date(2017, 10, 10, 10); const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; +store.dispatch(setUser(user)); +store.dispatch(selectServerSuccess({ server: baseUrl, version: '', name: '' })); store.dispatch(updateSettings('API_Embed', true)); store.dispatch( setCustomEmojis({ @@ -81,22 +85,10 @@ const containerHandlers = { }; const roomHandlers: Partial = { - user, - baseUrl, reactionInit: () => {}, - replyBroadcast: () => {}, - onReactionPress: () => {}, - onEncryptedPress: () => {}, - onDiscussionPress: () => {}, - onThreadPress: () => {}, - onReactionLongPress: () => {}, - onAnswerButtonPress: () => {}, jumpToMessage: () => {}, navToRoomInfo: () => {}, - showAttachment: undefined, - blockAction: undefined, - handleEnterCall: undefined, - fetchThreadName: undefined + showAttachment: undefined }; export default { diff --git a/app/containers/message/components/stories/MessageAvatar.stories.tsx b/app/containers/message/components/stories/MessageAvatar.stories.tsx index 59ab1850732..9efcd2061dc 100644 --- a/app/containers/message/components/stories/MessageAvatar.stories.tsx +++ b/app/containers/message/components/stories/MessageAvatar.stories.tsx @@ -3,6 +3,7 @@ import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; import { selectServerRequest } from '../../../../actions/server'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; @@ -10,6 +11,7 @@ import MessageAvatarLeaf from '../MessageAvatar'; const store = createMockedStore(); store.dispatch(selectServerRequest('https://open.rocket.chat', '7.0.0')); +store.dispatch(setUser({ id: 'reader-id', username: 'reader' })); const item = { id: 'msg-id', @@ -18,9 +20,7 @@ const item = { autoTranslate: false } as unknown as TAnyMessageModel; -const room: Partial = { - user: { id: 'reader-id', username: 'reader' } -}; +const room: Partial = {}; const StoryWrapper = ({ children }: { children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/Reactions.stories.tsx b/app/containers/message/components/stories/Reactions.stories.tsx index 77af06be29c..27187e99c63 100644 --- a/app/containers/message/components/stories/Reactions.stories.tsx +++ b/app/containers/message/components/stories/Reactions.stories.tsx @@ -2,12 +2,14 @@ import { type ReactNode } from 'react'; import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import ReactionsLeaf from '../Reactions'; const store = createMockedStore(); +store.dispatch(setUser({ id: 'reader-id', username: 'rocket.cat' })); const item = { id: 'msg-id', @@ -21,9 +23,6 @@ const item = { } as unknown as TAnyMessageModel; const room: Partial = { - user: { id: 'reader-id', username: 'rocket.cat' }, - onReactionPress: () => {}, - onReactionLongPress: () => {}, reactionInit: () => {} }; diff --git a/app/containers/message/components/stories/RightIcons/Encrypted.stories.tsx b/app/containers/message/components/stories/RightIcons/Encrypted.stories.tsx index 6a7005128bb..d25ec66291b 100644 --- a/app/containers/message/components/stories/RightIcons/Encrypted.stories.tsx +++ b/app/containers/message/components/stories/RightIcons/Encrypted.stories.tsx @@ -26,9 +26,7 @@ const plainItem = { autoTranslate: false } as unknown as TAnyMessageModel; -const room: Partial = { - onEncryptedPress: () => {} -}; +const room: Partial = {}; const StoryWrapper = ({ item, children }: { item: TAnyMessageModel; children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/RightIcons/Translated.stories.tsx b/app/containers/message/components/stories/RightIcons/Translated.stories.tsx index 969bf65ad0b..65ae0ca37eb 100644 --- a/app/containers/message/components/stories/RightIcons/Translated.stories.tsx +++ b/app/containers/message/components/stories/RightIcons/Translated.stories.tsx @@ -46,16 +46,14 @@ export default { // An auto-translated message from another user renders the language icon. export const Translated = () => ( - + ); // A message that is not translated renders nothing. export const TranslatedHidden = () => ( - + ); diff --git a/app/containers/message/components/stories/Thread.stories.tsx b/app/containers/message/components/stories/Thread.stories.tsx index 96f659d6a67..a00662fa05a 100644 --- a/app/containers/message/components/stories/Thread.stories.tsx +++ b/app/containers/message/components/stories/Thread.stories.tsx @@ -2,12 +2,14 @@ import { type ReactNode } from 'react'; import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import ThreadLeaf from '../Thread'; const store = createMockedStore(); +store.dispatch(setUser({ id: 'reader-id', username: 'reader' })); const item = { id: 'msg-id', @@ -20,8 +22,6 @@ const item = { } as unknown as TAnyMessageModel; const room: Partial = { - user: { id: 'reader-id', username: 'reader' }, - onThreadPress: () => {}, isThreadRoom: false }; diff --git a/app/containers/message/components/stories/Urls.stories.tsx b/app/containers/message/components/stories/Urls.stories.tsx index 790eafba882..3fa8dcf0580 100644 --- a/app/containers/message/components/stories/Urls.stories.tsx +++ b/app/containers/message/components/stories/Urls.stories.tsx @@ -3,6 +3,8 @@ import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; import { updateSettings } from '../../../../actions/settings'; +import { setUser } from '../../../../actions/login'; +import { selectServerSuccess } from '../../../../actions/server'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; @@ -10,6 +12,8 @@ import UrlsLeaf from '../Urls'; const store = createMockedStore(); store.dispatch(updateSettings('API_Embed', true)); +store.dispatch(setUser({ id: 'reader-id', username: 'reader', token: 'token' })); +store.dispatch(selectServerSuccess({ server: 'https://open.rocket.chat', version: '', name: '' })); const item = { id: 'msg-id', @@ -26,10 +30,7 @@ const item = { ] } as unknown as TAnyMessageModel; -const room: Partial = { - user: { id: 'reader-id', username: 'reader', token: 'token' }, - baseUrl: 'https://open.rocket.chat' -}; +const room: Partial = {}; const StoryWrapper = ({ children }: { children: ReactNode }) => ( diff --git a/app/containers/message/components/stories/User.stories.tsx b/app/containers/message/components/stories/User.stories.tsx index 8166af09f59..3e7f105d773 100644 --- a/app/containers/message/components/stories/User.stories.tsx +++ b/app/containers/message/components/stories/User.stories.tsx @@ -2,12 +2,14 @@ import { type ReactNode } from 'react'; import { Provider } from 'react-redux'; import { createMockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; import { type TAnyMessageModel } from '../../../../definitions'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { MessageProvider } from '../../stores/MessageStore'; import UserLeaf from '../User'; const store = createMockedStore(); +store.dispatch(setUser({ id: 'reader-id', username: 'reader' })); const item = { id: 'msg-id', @@ -21,7 +23,6 @@ const item = { } as unknown as TAnyMessageModel; const room: Partial = { - user: { id: 'reader-id', username: 'reader' }, timeFormat: 'LT' }; diff --git a/app/containers/message/hooks/__tests__/useMediaAutoDownload.test.tsx b/app/containers/message/hooks/__tests__/useMediaAutoDownload.test.tsx index 9fde89439df..01f710dab7e 100644 --- a/app/containers/message/hooks/__tests__/useMediaAutoDownload.test.tsx +++ b/app/containers/message/hooks/__tests__/useMediaAutoDownload.test.tsx @@ -6,6 +6,8 @@ import { useMediaAutoDownload } from '../useMediaAutoDownload'; import { MessageProvider } from '../../stores/MessageStore'; import { MessageRoomProvider, type MessageRoomState } from '../../stores/MessageRoomStore'; import { mockedStore } from '../../../../reducers/mockedStore'; +import { setUser } from '../../../../actions/login'; +import { selectServerSuccess } from '../../../../actions/server'; import { type IAttachment, type IUserMessage, type TAnyMessageModel } from '../../../../definitions'; import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; @@ -72,6 +74,9 @@ const mockEmitterOn = emitter.on as jest.Mock; const URL = 'https://open.rocket.chat/file.png'; const USER = { id: 'user-1', username: 'john', token: 'token' }; +mockedStore.dispatch(setUser(USER)); +mockedStore.dispatch(selectServerSuccess({ server: 'https://open.rocket.chat', version: '', name: '' })); + // Flushes the mount effect's async cache/auto-download chain inside act, used when the // final status equals the initial 'to-download' so there is no change for waitFor to await. const flushMount = () => @@ -92,7 +97,7 @@ const renderMediaHook = ({ showAttachment?: (file: IAttachment) => void; ctx?: Partial; }) => { - const contextValue: Partial = { baseUrl: 'https://open.rocket.chat', user: USER, ...ctx }; + const contextValue: Partial = { ...ctx }; const item = { id: 'msg-1' } as unknown as TAnyMessageModel; const wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/app/containers/message/hooks/__tests__/useMessageAccessibilityLabel.test.tsx b/app/containers/message/hooks/__tests__/useMessageAccessibilityLabel.test.tsx index f4352341876..641d7ae00c1 100644 --- a/app/containers/message/hooks/__tests__/useMessageAccessibilityLabel.test.tsx +++ b/app/containers/message/hooks/__tests__/useMessageAccessibilityLabel.test.tsx @@ -123,8 +123,7 @@ describe('useMessageAccessibilityLabel', () => { expect( renderLabel(buildItem({ autoTranslate: true, translations: [{ _id: 't1', language: 'en', value: 'translated text' }] }), { autoTranslateRoom: true, - autoTranslateLanguage: 'en', - user: { username: 'bob' } + autoTranslateLanguage: 'en' }) ).toBe(`alice ${HOUR} Message translated into English.`); }); @@ -139,7 +138,7 @@ describe('useMessageAccessibilityLabel', () => { unread: true, attachments: [{ image_url: 'https://example.com/img.png', altText: 'A wavy pattern' }] }), - { isReadReceiptEnabled: true, autoTranslateRoom: true, autoTranslateLanguage: 'en', user: { username: 'bob' } } + { isReadReceiptEnabled: true, autoTranslateRoom: true, autoTranslateLanguage: 'en' } ) ).toBe(`alice ${HOUR} Message translated into English. Image description: A wavy pattern Message was not read`); }); diff --git a/app/containers/message/hooks/useImageDescriptionLabel.ts b/app/containers/message/hooks/useImageDescriptionLabel.ts index 464fa6365c0..842528d264a 100644 --- a/app/containers/message/hooks/useImageDescriptionLabel.ts +++ b/app/containers/message/hooks/useImageDescriptionLabel.ts @@ -3,6 +3,8 @@ import { useAltTextSupported } from '../../../lib/hooks/useAltTextSupported'; import { type IAttachment } from '../../../definitions'; export const useImageDescriptionLabel = (attachments: IAttachment[] | undefined, msg: string | undefined): string => { + 'use memo'; + const isAltTextSupported = useAltTextSupported(); if (isAltTextSupported) { return ''; diff --git a/app/containers/message/hooks/useMediaAutoDownload.tsx b/app/containers/message/hooks/useMediaAutoDownload.tsx index c3deec8da0f..2c9bb122548 100644 --- a/app/containers/message/hooks/useMediaAutoDownload.tsx +++ b/app/containers/message/hooks/useMediaAutoDownload.tsx @@ -82,13 +82,13 @@ export const useMediaAutoDownload = ({ const baseUrl = useBaseUrl(); const user = useMessageUser(); const [status, dispatchDownloadEvent] = useReducer(downloadStatusReducer, 'to-download'); - const [currentFile, setCurrentFile] = useFile(file, id ?? ''); + const [currentFile, setCurrentFile] = useFile(file, id); const originalUrl = getOriginalURL(file); const url = formatAttachmentUrl( file.title_link || getFileProperty(currentFile, fileType, 'url'), - user?.id ?? '', - user?.token ?? '', - baseUrl ?? '', + user.id, + user.token, + baseUrl, originalUrl ); const isEncrypted = currentFile.e2e === 'pending'; @@ -128,7 +128,7 @@ export const useMediaAutoDownload = ({ }; const tryAutoDownload = async () => { - const isCurrentUserAuthor = author?._id === user?.id; + const isCurrentUserAuthor = author?._id === user.id; const isAutoDownloadEnabled = fetchAutoDownloadEnabled(`${fileType}PreferenceDownload`); if (isAutoDownloadEnabled || isCurrentUserAuthor) { await download(); @@ -139,7 +139,7 @@ export const useMediaAutoDownload = ({ try { dispatchDownloadEvent('download_started'); const uri = await downloadMediaFile({ - messageId: id ?? '', + messageId: id, downloadUrl: url, type: fileType, mimeType: getFileProperty(currentFile, fileType, 'type'), diff --git a/app/containers/message/stores/A11yGate.tsx b/app/containers/message/stores/A11yGate.tsx new file mode 100644 index 00000000000..6f23f11a08c --- /dev/null +++ b/app/containers/message/stores/A11yGate.tsx @@ -0,0 +1,19 @@ +import { createContext, use, type ReactElement, type ReactNode } from 'react'; + +import { useIsAccessibilityNavigationEnabled } from '../../../lib/hooks/useIsAccessibilityNavigationEnabled'; + +// One boolean per room: is accessibility navigation (screen reader OR external keyboard) active? +// Message rows read it to skip the react-native-a11y-order wrappers when nobody needs them. +// Defaults false so message leaves rendered outside a provider (stories, isolated tests) render +// gated-off — identical to how they render for a touch user with no assistive tech. +export const A11yGateContext = createContext(false); + +export const useA11yGate = (): boolean => use(A11yGateContext); + +export const A11yGateProvider = ({ children }: { children: ReactNode }): ReactElement => { + 'use memo'; + + const enabled = useIsAccessibilityNavigationEnabled(); + + return {children}; +}; diff --git a/app/containers/message/stores/MessageActionStore.tsx b/app/containers/message/stores/MessageActionStore.tsx index a5b77f8c6a8..1664426eb06 100644 --- a/app/containers/message/stores/MessageActionStore.tsx +++ b/app/containers/message/stores/MessageActionStore.tsx @@ -91,6 +91,15 @@ export const useIsBeingEdited = (messageId: string): boolean => { return useStore(store, s => s.action?.kind === 'edit' && s.action.messageId === messageId); }; +// Stable ref so the selector doesn't emit a fresh array when not quoting. +const EMPTY_MESSAGE_IDS: string[] = []; + +export const useQuotedMessageIds = (): string[] => + useMessageActionStore(s => (s.action?.kind === 'quote' ? s.action.messageIds : EMPTY_MESSAGE_IDS)); + +export const useEditingMessageId = (): string | undefined => + useMessageActionStore(s => (s.action?.kind === 'edit' ? s.action.messageId : undefined)); + export const MessageActionProvider = ({ store: externalStore, initialAction, diff --git a/app/containers/message/stores/MessageRoomStore.tsx b/app/containers/message/stores/MessageRoomStore.tsx index d397c438ed4..a66d1e2011e 100644 --- a/app/containers/message/stores/MessageRoomStore.tsx +++ b/app/containers/message/stores/MessageRoomStore.tsx @@ -2,44 +2,57 @@ import { createContext, useContext, useEffect, useRef, useState, type ReactEleme import { createStore, useStore } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; -import { type IAttachment, type IRoomInfoParam, type TAnyMessageModel } from '../../../definitions'; +import { + type IAttachment, + type IRoomInfoParam, + type IUseRoomMessageHandlersResult, + type IUser, + type TAnyMessageModel +} from '../../../definitions'; +import { useAppSelector } from '../../../lib/hooks/useAppSelector'; import { useSetting } from '../../../lib/hooks/useSetting'; +import { getUserSelector } from '../../../selectors/login'; -export type MessageRoomState = { +// Captured once at mount by the provider (handlers + room constants); callers MUST pass +// referentially stable values (see FROZEN_KEYS / the dev guard below). +type FrozenState = { // stable handlers - navToRoomInfo?: (navParam: IRoomInfoParam) => void; - showAttachment?: (file: IAttachment) => void; - blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void; - handleEnterCall?: () => void; - fetchThreadName?: (tmid: string, id: string) => Promise; - toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise; jumpToMessage?: (link: string) => void; closeEmojiAndAction?: (action?: (params?: unknown) => void, params?: unknown) => void; // row action handlers - onReactionPress?: (emoji: string, id: string) => void; - onReactionLongPress?: (item: TAnyMessageModel) => void; reactionInit?: (messageId: string) => void; - onDiscussionPress?: (drid: TAnyMessageModel['drid']) => void; - onThreadPress?: (item: TAnyMessageModel) => void; - replyBroadcast?: (item: TAnyMessageModel) => void; errorActionsShow?: (item: TAnyMessageModel) => void; - onAnswerButtonPress?: (message: string, tshow?: boolean) => void; - onEncryptedPress?: () => void; - archived?: boolean; - isReadReceiptEnabled?: boolean; + // message handler bag produced by RoomView's RoomMessageHandlersBridge; a single stable identity + // (leaves read it through the fine-grained selectors below) + handlers?: Partial; + // overrides for views (e.g. MessagesView, SearchMessagesView) that render message leaves outside + // a RoomStoreContext; merged into `handlers` at provider construction so selectors read one path + navToRoomInfo?: (navParam: IRoomInfoParam) => void; + showAttachment?: (attachment: IAttachment) => void; // room constants rid?: string; - user?: { id?: string; username?: string; token?: string }; - baseUrl?: string; - broadcast?: boolean; isThreadRoom?: boolean; + tmid?: string; +}; + +// Reactive tail: can change mid-session (e.g. an open room gets archived); the provider resyncs +// these into the store on change (see the effect below). +type ReactiveState = { + archived?: boolean; + broadcast?: boolean; + isReadReceiptEnabled?: boolean; Message_GroupingPeriod?: number; timeFormat?: string; - // reactive tail (provider keeps current) autoTranslateRoom?: boolean; autoTranslateLanguage?: string; }; +export type MessageRoomState = FrozenState & ReactiveState; + +// Exact reactive snapshot: `-?` forces every reactive key to be present, so the resync effect's +// setState payload can never silently drift from ReactiveState. +type ReactiveSnapshot = { [K in keyof Required]: ReactiveState[K] }; + export const createMessageRoomStore = (initial: MessageRoomState) => createStore(() => ({ ...initial })); export type MessageRoomStore = ReturnType; @@ -57,27 +70,15 @@ const useMessageRoomStore = (selector: (state: MessageRoomState) => T): T => // Handlers and room constants captured once at mount by MessageRoomStoreProvider below (everything // outside the reactive-tail resync effect). Callers MUST pass referentially stable values for these. const FROZEN_KEYS = [ - 'navToRoomInfo', - 'showAttachment', - 'blockAction', - 'handleEnterCall', - 'fetchThreadName', - 'toggleFollowThread', 'jumpToMessage', 'closeEmojiAndAction', - 'onReactionPress', - 'onReactionLongPress', 'reactionInit', - 'onDiscussionPress', - 'onThreadPress', - 'replyBroadcast', 'errorActionsShow', - 'onAnswerButtonPress', - 'onEncryptedPress', + 'navToRoomInfo', + 'showAttachment', 'rid', - 'user', - 'baseUrl', - 'isThreadRoom' + 'isThreadRoom', + 'tmid' ] as const satisfies readonly (keyof MessageRoomState)[]; const useFrozenHandlersGuardProd = (_state: MessageRoomState): void => {}; @@ -107,13 +108,24 @@ const useFrozenHandlersGuard: (state: MessageRoomState) => void = __DEV__ const MessageRoomStoreProvider = ({ children, ...state }: { children: ReactNode } & MessageRoomState): ReactElement => { 'use memo'; - const [store] = useState(() => createMessageRoomStore(state)); + const [store] = useState(() => { + // Fold the view-level overrides into the handler bag so selectors read a single path. + const { navToRoomInfo, showAttachment, ...rest } = state; + return createMessageRoomStore({ + ...rest, + handlers: { + ...state.handlers, + ...(navToRoomInfo ? { navToRoomInfo } : null), + ...(showAttachment ? { showAttachment } : null) + } + }); + }); useFrozenHandlersGuard(state); // These fields can change mid-session (e.g. an open room gets archived), unlike the // constants/handlers captured once above. The dep array keeps store writes on-change only. useEffect(() => { - store.setState({ + const reactiveState: ReactiveSnapshot = { timeFormat: state.timeFormat, autoTranslateRoom: state.autoTranslateRoom, autoTranslateLanguage: state.autoTranslateLanguage, @@ -121,7 +133,8 @@ const MessageRoomStoreProvider = ({ children, ...state }: { children: ReactNode broadcast: state.broadcast, isReadReceiptEnabled: state.isReadReceiptEnabled, Message_GroupingPeriod: state.Message_GroupingPeriod - }); + }; + store.setState(reactiveState); }, [ state.timeFormat, state.autoTranslateRoom, @@ -158,36 +171,51 @@ export const MessageRoomProvider = ({ children, ...state }: { children: ReactNod ); }; -export const useNavToRoomInfo = (): ((navParam: IRoomInfoParam) => void) | undefined => useMessageRoomStore(s => s.navToRoomInfo); -export const useShowAttachment = (): ((file: IAttachment) => void) | undefined => useMessageRoomStore(s => s.showAttachment); -export const useBlockAction = (): MessageRoomState['blockAction'] => useMessageRoomStore(s => s.blockAction); -export const useHandleEnterCall = (): (() => void) | undefined => useMessageRoomStore(s => s.handleEnterCall); -export const useFetchThreadName = (): MessageRoomState['fetchThreadName'] => useMessageRoomStore(s => s.fetchThreadName); -export const useToggleFollowThread = (): MessageRoomState['toggleFollowThread'] => useMessageRoomStore(s => s.toggleFollowThread); export const useJumpToMessage = (): ((link: string) => void) | undefined => useMessageRoomStore(s => s.jumpToMessage); export const useCloseEmojiAndAction = (): MessageRoomState['closeEmojiAndAction'] => useMessageRoomStore(s => s.closeEmojiAndAction); -export const useOnReactionPress = (): MessageRoomState['onReactionPress'] => useMessageRoomStore(s => s.onReactionPress); -export const useOnReactionLongPress = (): MessageRoomState['onReactionLongPress'] => - useMessageRoomStore(s => s.onReactionLongPress); export const useReactionInit = (): MessageRoomState['reactionInit'] => useMessageRoomStore(s => s.reactionInit); -export const useOnDiscussionPress = (): MessageRoomState['onDiscussionPress'] => useMessageRoomStore(s => s.onDiscussionPress); -export const useOnThreadPress = (): MessageRoomState['onThreadPress'] => useMessageRoomStore(s => s.onThreadPress); -export const useReplyBroadcast = (): MessageRoomState['replyBroadcast'] => useMessageRoomStore(s => s.replyBroadcast); export const useErrorActionsShow = (): MessageRoomState['errorActionsShow'] => useMessageRoomStore(s => s.errorActionsShow); -export const useOnAnswerButtonPress = (): MessageRoomState['onAnswerButtonPress'] => - useMessageRoomStore(s => s.onAnswerButtonPress); -export const useOnEncryptedPress = (): MessageRoomState['onEncryptedPress'] => useMessageRoomStore(s => s.onEncryptedPress); +// Leaves read handlers through these fine-grained selectors, optional-chaining a possibly-partial bag. +// The provider merges any view-level override (MessagesView, SearchMessagesView) into `handlers`, +// so navToRoomInfo/showAttachment resolve from the single bag here. +export const useNavToRoomInfo = (): IUseRoomMessageHandlersResult['navToRoomInfo'] | undefined => + useMessageRoomStore(s => s.handlers?.navToRoomInfo); +export const useShowAttachment = (): IUseRoomMessageHandlersResult['showAttachment'] | undefined => + useMessageRoomStore(s => s.handlers?.showAttachment); +export const useBlockAction = (): IUseRoomMessageHandlersResult['blockAction'] | undefined => + useMessageRoomStore(s => s.handlers?.blockAction); +export const useHandleEnterCall = (): IUseRoomMessageHandlersResult['handleEnterCall'] | undefined => + useMessageRoomStore(s => s.handlers?.handleEnterCall); +export const useOnDiscussionPress = (): IUseRoomMessageHandlersResult['onDiscussionPress'] | undefined => + useMessageRoomStore(s => s.handlers?.onDiscussionPress); +export const useOnThreadPress = (): IUseRoomMessageHandlersResult['onThreadPress'] | undefined => + useMessageRoomStore(s => s.handlers?.onThreadPress); +export const useOnEncryptedPress = (): IUseRoomMessageHandlersResult['onEncryptedPress'] | undefined => + useMessageRoomStore(s => s.handlers?.onEncryptedPress); +export const useOnReactionPress = (): IUseRoomMessageHandlersResult['onReactionPress'] | undefined => + useMessageRoomStore(s => s.handlers?.onReactionPress); +export const useOnReactionLongPress = (): IUseRoomMessageHandlersResult['onReactionLongPress'] | undefined => + useMessageRoomStore(s => s.handlers?.onReactionLongPress); +export const useReplyBroadcast = (): IUseRoomMessageHandlersResult['replyBroadcast'] | undefined => + useMessageRoomStore(s => s.handlers?.replyBroadcast); +export const useFetchThreadName = (): IUseRoomMessageHandlersResult['fetchThreadName'] | undefined => + useMessageRoomStore(s => s.handlers?.fetchThreadName); +export const useToggleFollowThread = (): IUseRoomMessageHandlersResult['toggleFollowThread'] | undefined => + useMessageRoomStore(s => s.handlers?.toggleFollowThread); +export const useOnAnswerButtonPress = (): IUseRoomMessageHandlersResult['onAnswerButtonPress'] | undefined => + useMessageRoomStore(s => s.handlers?.onAnswerButtonPress); export const useIsArchived = (): boolean | undefined => useMessageRoomStore(s => s.archived); export const useIsReadReceiptEnabled = (): boolean | undefined => useMessageRoomStore(s => s.isReadReceiptEnabled); export const useRid = (): string | undefined => useMessageRoomStore(s => s.rid); -export const useMessageUser = (): MessageRoomState['user'] => useMessageRoomStore(s => s.user); -export const useBaseUrl = (): string | undefined => useMessageRoomStore(s => s.baseUrl); +export const useMessageUser = (): IUser => useAppSelector(getUserSelector); +export const useBaseUrl = (): string => useAppSelector(state => state.server.server); export const useBroadcast = (): boolean | undefined => useMessageRoomStore(s => s.broadcast); export const useTimeFormat = (): string | undefined => useMessageRoomStore(s => s.timeFormat); export const useIsThreadRoom = (): boolean | undefined => useMessageRoomStore(s => s.isThreadRoom); +export const useRoomTmid = (): string | undefined => useMessageRoomStore(s => s.tmid); export const useMessageGroupingPeriod = (): number | undefined => useMessageRoomStore(s => s.Message_GroupingPeriod); export const useAutoTranslate = (): { autoTranslateRoom?: boolean; autoTranslateLanguage?: string } => diff --git a/app/containers/message/stores/MessageStore.tsx b/app/containers/message/stores/MessageStore.tsx index e12f6a368fd..d51233638d4 100644 --- a/app/containers/message/stores/MessageStore.tsx +++ b/app/containers/message/stores/MessageStore.tsx @@ -19,8 +19,8 @@ import { useJumpToMessage, useMessageGroupingPeriod, useMessageUser, - useOnDiscussionPress, - useOnThreadPress + useOnThreadPress, + useOnDiscussionPress } from './MessageRoomStore'; type MessageStoreState = { diff --git a/app/containers/message/stores/__tests__/MessageActionStore.test.tsx b/app/containers/message/stores/__tests__/MessageActionStore.test.tsx index a342618f4a4..0450dfea9e4 100644 --- a/app/containers/message/stores/__tests__/MessageActionStore.test.tsx +++ b/app/containers/message/stores/__tests__/MessageActionStore.test.tsx @@ -4,8 +4,10 @@ import { createMessageActionStore, inertStore, MessageActionProvider, + useEditingMessageId, useIsBeingEdited, - useMessageAction + useMessageAction, + useQuotedMessageIds } from '../MessageActionStore'; describe('MessageActionStore', () => { @@ -95,6 +97,74 @@ describe('MessageActionStore', () => { }); }); + describe('useQuotedMessageIds', () => { + it('returns the quoted messageIds under a quote action', () => { + const spy = jest.fn(); + const Probe = () => { + spy(useQuotedMessageIds()); + return null; + }; + + render( + + + + ); + + expect(spy).toHaveBeenLastCalledWith(['msg-1', 'msg-2']); + }); + + it('returns an empty array for non-quote actions', () => { + const spy = jest.fn(); + const Probe = () => { + spy(useQuotedMessageIds()); + return null; + }; + + render( + + + + ); + + expect(spy).toHaveBeenLastCalledWith([]); + }); + }); + + describe('useEditingMessageId', () => { + it('returns the messageId under an edit action', () => { + const spy = jest.fn(); + const Probe = () => { + spy(useEditingMessageId()); + return null; + }; + + render( + + + + ); + + expect(spy).toHaveBeenLastCalledWith('msg-1'); + }); + + it('returns undefined for non-edit actions', () => { + const spy = jest.fn(); + const Probe = () => { + spy(useEditingMessageId()); + return null; + }; + + render( + + + + ); + + expect(spy).toHaveBeenLastCalledWith(undefined); + }); + }); + describe('action hooks without a provider', () => { let consoleErrorSpy: jest.SpyInstance; diff --git a/app/containers/message/stores/__tests__/MessageRoomStore.test.tsx b/app/containers/message/stores/__tests__/MessageRoomStore.test.tsx index 9c5ee9304ef..eef4e72a4fb 100644 --- a/app/containers/message/stores/__tests__/MessageRoomStore.test.tsx +++ b/app/containers/message/stores/__tests__/MessageRoomStore.test.tsx @@ -1,9 +1,18 @@ -import { memo } from 'react'; +import { memo, useContext, useEffect, type ContextType } from 'react'; import { act, render } from '@testing-library/react-native'; import { Text } from 'react-native'; import { Provider } from 'react-redux'; -import { MessageRoomProvider, useAutoTranslate, useIsArchived, useTimeFormat } from '../MessageRoomStore'; +import { + MessageRoomProvider, + MessageRoomStoreContext, + useAutoTranslate, + useBlockAction, + useIsArchived, + useNavToRoomInfo, + useShowAttachment, + useTimeFormat +} from '../MessageRoomStore'; import { mockedStore } from '../../../../reducers/mockedStore'; import { updateSettings } from '../../../../actions/settings'; @@ -153,6 +162,84 @@ describe('MessageRoomStore', () => { expect(spy).toHaveBeenLastCalledWith(true); }); + describe('handlers bag', () => { + it('reads a handler through the fine-grained selector off the published bag', () => { + const blockAction = jest.fn(); + const spy = jest.fn(); + const Probe = () => { + spy(useBlockAction()); + return null; + }; + + render( + + + + + + ); + + expect(spy).toHaveBeenLastCalledWith(blockAction); + }); + + it('prefers the view-level override over the bag for navToRoomInfo/showAttachment', () => { + const overrideNav = jest.fn(); + const bagNav = jest.fn(); + const bagShow = jest.fn(); + const navSpy = jest.fn(); + const showSpy = jest.fn(); + const Probe = () => { + navSpy(useNavToRoomInfo()); + showSpy(useShowAttachment()); + return null; + }; + + render( + + + + + + ); + + // Override wins for navToRoomInfo; showAttachment has no override so it falls back to the bag. + expect(navSpy).toHaveBeenLastCalledWith(overrideNav); + expect(showSpy).toHaveBeenLastCalledWith(bagShow); + }); + + it('publishes handlers in the reactive tail: a setState swap reaches the selector', () => { + const first = jest.fn(); + const second = jest.fn(); + const spy = jest.fn(); + const captured: { store: ContextType } = { store: null }; + const StoreProbe = () => { + const store = useContext(MessageRoomStoreContext); + useEffect(() => { + captured.store = store; + }, [store]); + spy(useBlockAction()); + return null; + }; + + render( + + + + + + ); + + expect(spy).toHaveBeenLastCalledWith(first); + + act(() => captured.store?.setState({ handlers: { blockAction: second } })); + + expect(spy).toHaveBeenLastCalledWith(second); + }); + }); + describe('frozen handler guard (dev)', () => { let warnSpy: jest.SpyInstance; diff --git a/app/containers/message/stores/__tests__/MessageStore.test.tsx b/app/containers/message/stores/__tests__/MessageStore.test.tsx index fd8a8a08dee..e7bd20236af 100644 --- a/app/containers/message/stores/__tests__/MessageStore.test.tsx +++ b/app/containers/message/stores/__tests__/MessageStore.test.tsx @@ -599,7 +599,7 @@ describe('MessageStore', () => { u: { _id: 'u2', username: 'bob' } }); const { latest } = renderDerived(model, useMessageText, { - config: { autoTranslateRoom: true, autoTranslateLanguage: 'pt-BR', user: { username: 'alice' } } + config: { autoTranslateRoom: true, autoTranslateLanguage: 'pt-BR' } }); expect(latest()).toEqual({ messageText: 'Olá mundo', isTranslated: true }); }); @@ -892,7 +892,7 @@ describe('MessageStore', () => { it('reveals an ignored message instead of pressing', () => { const model = buildFakeModel({ tmid: 't1' }); const onThreadPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { isIgnored: true, config: { onThreadPress } }); + const { result } = renderMessageHook(useMessagePress, model, { isIgnored: true, config: { handlers: { onThreadPress } } }); act(() => result.current()); @@ -905,7 +905,10 @@ describe('MessageStore', () => { const onPress = jest.fn(); const onThreadPress = jest.fn(); const onDiscussionPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { onPress, config: { onThreadPress, onDiscussionPress } }); + const { result } = renderMessageHook(useMessagePress, model, { + onPress, + config: { handlers: { onThreadPress, onDiscussionPress } } + }); act(() => result.current()); @@ -918,7 +921,7 @@ describe('MessageStore', () => { it('dismisses the keyboard and routes to thread press for a threaded reply', () => { const model = buildFakeModel({ tmid: 't1' }); const onThreadPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { config: { onThreadPress } }); + const { result } = renderMessageHook(useMessagePress, model, { config: { handlers: { onThreadPress } } }); act(() => result.current()); @@ -929,7 +932,7 @@ describe('MessageStore', () => { it('routes to thread press for a root message that has replies (tlm)', () => { const model = buildFakeModel({ tlm: new Date() }); const onThreadPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { config: { onThreadPress } }); + const { result } = renderMessageHook(useMessagePress, model, { config: { handlers: { onThreadPress } } }); act(() => result.current()); @@ -939,7 +942,9 @@ describe('MessageStore', () => { it('does not route to thread press while already inside the thread room', () => { const model = buildFakeModel({ tmid: 't1' }); const onThreadPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { config: { onThreadPress, isThreadRoom: true } }); + const { result } = renderMessageHook(useMessagePress, model, { + config: { isThreadRoom: true, handlers: { onThreadPress } } + }); act(() => result.current()); @@ -949,7 +954,7 @@ describe('MessageStore', () => { it('routes to discussion press when the message has a discussion', () => { const model = buildFakeModel({ dlm: new Date(), drid: 'drid-1' }); const onDiscussionPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { config: { onDiscussionPress } }); + const { result } = renderMessageHook(useMessagePress, model, { config: { handlers: { onDiscussionPress } } }); act(() => result.current()); @@ -959,7 +964,7 @@ describe('MessageStore', () => { it('debounces on the leading edge, suppressing an immediate repeat until the window elapses', () => { const model = buildFakeModel({ tmid: 't1' }); const onThreadPress = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { config: { onThreadPress } }); + const { result } = renderMessageHook(useMessagePress, model, { config: { handlers: { onThreadPress } } }); act(() => result.current()); expect(onThreadPress).toHaveBeenCalledTimes(1); @@ -977,7 +982,9 @@ describe('MessageStore', () => { const model = buildFakeModel({ tmid: 't1' }); const onThreadPress = jest.fn(); const closeEmojiAndAction = jest.fn(); - const { result } = renderMessageHook(useMessagePress, model, { config: { onThreadPress, closeEmojiAndAction } }); + const { result } = renderMessageHook(useMessagePress, model, { + config: { closeEmojiAndAction, handlers: { onThreadPress } } + }); act(() => result.current()); @@ -990,7 +997,9 @@ describe('MessageStore', () => { const model = buildFakeModel({ tmid: 't1' }); const onThreadPress = jest.fn(); const closeEmojiAndAction = jest.fn((action?: (params?: unknown) => void) => action?.()); - const { result } = renderMessageHook(useMessagePress, model, { config: { onThreadPress, closeEmojiAndAction } }); + const { result } = renderMessageHook(useMessagePress, model, { + config: { closeEmojiAndAction, handlers: { onThreadPress } } + }); act(() => result.current()); @@ -1000,7 +1009,7 @@ describe('MessageStore', () => { }); describe('translation boundary on item.autoTranslate', () => { - const translationConfig = { autoTranslateRoom: true, autoTranslateLanguage: 'pt-BR', user: { username: 'alice' } }; + const translationConfig = { autoTranslateRoom: true, autoTranslateLanguage: 'pt-BR' }; it('useTranslateLanguage returns undefined when autoTranslate is undefined', () => { const model = buildFakeModel({ autoTranslate: undefined, u: { _id: 'u2', username: 'bob' } }); diff --git a/app/definitions/IRoomMessageHandlers.ts b/app/definitions/IRoomMessageHandlers.ts new file mode 100644 index 00000000000..7237594244c --- /dev/null +++ b/app/definitions/IRoomMessageHandlers.ts @@ -0,0 +1,27 @@ +import { type IAttachment } from './IAttachment'; +import { type IEmoji } from './IEmoji'; +import { type IMessage, type TAnyMessageModel } from './IMessage'; +import { type IRoomInfoParam } from './IRoom'; + +export interface IUseRoomMessageHandlersResult { + blockAction: (params: { + actionId: string; + appId: string; + value: any; + blockId: string; + rid: string; + mid: string; + }) => Promise; + navToRoomInfo: (navParam: IRoomInfoParam) => void; + handleEnterCall: () => void; + onDiscussionPress: (drid: TAnyMessageModel['drid']) => void; + onThreadPress: (item: TAnyMessageModel) => void; + onEncryptedPress: () => void; + showAttachment: (attachment: IAttachment) => void; + onReactionPress: (emoji: IEmoji, messageId: string) => Promise; + onReactionLongPress: (message: TAnyMessageModel) => void; + replyBroadcast: (message: IMessage) => void; + fetchThreadName: (threadId: string, messageId: string) => Promise; + toggleFollowThread: (isFollowingThread: boolean, threadId?: string) => Promise; + onAnswerButtonPress: (message?: string, tshow?: boolean) => void; +} diff --git a/app/definitions/index.ts b/app/definitions/index.ts index b2566469043..2e37b368049 100644 --- a/app/definitions/index.ts +++ b/app/definitions/index.ts @@ -21,6 +21,7 @@ export * from './IPreferences'; export * from './IProfile'; export * from './IReaction'; export * from './IRole'; +export * from './IRoomMessageHandlers'; export * from './IRoom'; export * from './ISearch'; export * from './IServer'; diff --git a/app/lib/hooks/useLiveRef.test.ts b/app/lib/hooks/useLiveRef.test.ts new file mode 100644 index 00000000000..23ba4d57524 --- /dev/null +++ b/app/lib/hooks/useLiveRef.test.ts @@ -0,0 +1,30 @@ +import { renderHook } from '@testing-library/react-native'; + +import { useLiveRef } from './useLiveRef'; + +describe('useLiveRef', () => { + it('exposes the initial value on first render', () => { + const { result } = renderHook(() => useLiveRef('first')); + + expect(result.current.current).toBe('first'); + }); + + it('refreshes ref.current to the latest value on rerender', () => { + const { result, rerender } = renderHook(({ value }: { value: number }) => useLiveRef(value), { initialProps: { value: 1 } }); + + rerender({ value: 2 }); + + expect(result.current.current).toBe(2); + }); + + it('keeps a stable ref object identity across rerenders', () => { + const { result, rerender } = renderHook(({ value }: { value: string }) => useLiveRef(value), { + initialProps: { value: 'a' } + }); + const firstRef = result.current; + + rerender({ value: 'b' }); + + expect(result.current).toBe(firstRef); + }); +}); diff --git a/app/lib/hooks/useLiveRef.ts b/app/lib/hooks/useLiveRef.ts new file mode 100644 index 00000000000..e99597db6cc --- /dev/null +++ b/app/lib/hooks/useLiveRef.ts @@ -0,0 +1,12 @@ +import { type RefObject, useEffect, useRef } from 'react'; + +// Ref refreshed every render (no deps array), so a narrowly-keyed effect can read the latest closure +// without listing it as a dependency. Blessed escape hatch for the intentional deps-less mirror. +// Stand-in for useEffectEvent (stable in React 19.2); migrate 1:1 once RN ships React >= 19.2. +export function useLiveRef(value: T): RefObject { + const ref = useRef(value); + useEffect(() => { + ref.current = value; + }); + return ref; +} diff --git a/app/lib/methods/emojis.ts b/app/lib/methods/emojis.ts index 63f4d885a9c..58ed685db6c 100644 --- a/app/lib/methods/emojis.ts +++ b/app/lib/methods/emojis.ts @@ -10,7 +10,7 @@ const FREQUENTLY_USED_TABLE = 'frequently_used_emojis'; // Looked up by content, never used as the record id: emoji content / custom names can be // non-ASCII and corrupt across the native SQLite bridge when used as WatermelonDB ids. -const getEmojiContent = (emoji: IEmoji) => (typeof emoji === 'string' ? emoji : emoji.name); +export const getEmojiContent = (emoji: IEmoji) => (typeof emoji === 'string' ? emoji : emoji.name); export const addFrequentlyUsed = async (emoji: IEmoji) => { const db = database.active; diff --git a/app/lib/methods/helpers/goRoom.test.ts b/app/lib/methods/helpers/goRoom.test.ts new file mode 100644 index 00000000000..7f588b0ab32 --- /dev/null +++ b/app/lib/methods/helpers/goRoom.test.ts @@ -0,0 +1,104 @@ +import { InteractionManager } from 'react-native'; + +import { goRoom } from './goRoom'; +import { peekOrCreateRoomStore, acquireRoomStore, releaseRoomStore } from '../../../views/RoomView/stores/RoomStore'; + +jest.mock('../../navigation/appNavigation', () => ({ + __esModule: true, + default: { + getCurrentRoute: jest.fn(() => ({ name: 'RoomsListView' })), + setParams: jest.fn(), + popTo: jest.fn(), + dispatch: jest.fn() + } +})); + +jest.mock('../../database/services/Subscription', () => ({ + getSubscriptionByRoomId: jest.fn(() => Promise.resolve(null)) +})); + +// RoomStore transitive imports pull native/ESM modules (mobile-crypto via readMessages); +// stub them so importing goRoom -> RoomStore stays runnable under jest. +jest.mock('../readMessages', () => ({ readMessages: jest.fn(() => Promise.resolve()) })); +jest.mock('../loadThreadMessages', () => ({ loadThreadMessages: jest.fn(() => Promise.resolve()) })); +jest.mock('../../services/restApi', () => ({ getUserInfo: jest.fn() })); +jest.mock('../isInviteSubscription', () => ({ isInviteSubscription: jest.fn(() => false) })); +jest.mock('../../../views/RoomView/services/getMessages', () => ({ + __esModule: true, + default: jest.fn(() => Promise.resolve()) +})); +jest.mock('../../store/auxStore', () => ({ + store: { + getState: () => ({ + settings: {}, + login: { user: { id: 'u1', username: 'user' } } + }) + } +})); +jest.mock('../../database', () => ({ + __esModule: true, + default: { + active: { + get: () => ({ + query: () => ({ observeWithColumns: () => ({ subscribe: () => ({ unsubscribe: jest.fn() }) }) }) + }) + } + } +})); + +let graceCb: (() => void) | undefined; + +describe('goRoom RoomStore warm-up', () => { + beforeEach(() => { + graceCb = undefined; + jest.spyOn(InteractionManager, 'runAfterInteractions').mockImplementation(((cb: () => void) => { + graceCb = cb; + return { then: () => {} }; + }) as unknown as typeof InteractionManager.runAfterInteractions); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('tears down the warmed store via its grace sweep when navigation is effectively cancelled (no mount)', async () => { + await goRoom({ item: { rid: 'r1', t: 'c' as any }, isMasterDetail: false } as any); + // warm-up left the entry at refCount 0 and scheduled its own grace sweep + + expect(graceCb).toBeDefined(); + + // Peek the warmed entry to capture its instance (peek does not touch refCount). + const warmed = peekOrCreateRoomStore({ rid: 'r1', initialRoom: {} as any }); + + graceCb!(); // grace sweep: refCount 0 -> entry torn down + + const fresh = peekOrCreateRoomStore({ rid: 'r1', initialRoom: {} as any }); // brand-new instance + expect(fresh).not.toBe(warmed); + + releaseRoomStore('r1'); + }); + + it('keeps the store alive when RoomView mounts before the grace sweep fires', async () => { + await goRoom({ item: { rid: 'r1', t: 'c' as any }, isMasterDetail: false } as any); + + expect(graceCb).toBeDefined(); + + // Simulate RoomView mounting against the warmed entry and acquiring it: refCount 0 -> 1. + const mounted = peekOrCreateRoomStore({ rid: 'r1', initialRoom: {} as any }); + acquireRoomStore('r1'); + + // Grace sweep fires after the transition: refCount is 1, so the entry stays alive. + graceCb!(); + + const stillAlive = peekOrCreateRoomStore({ rid: 'r1', initialRoom: {} as any }); + expect(stillAlive).toBe(mounted); + + releaseRoomStore('r1'); + }); + + it('does not warm up the store when there is no rid', async () => { + await goRoom({ item: { t: 'c' as any }, isMasterDetail: false } as any); + + expect(graceCb).toBeUndefined(); + }); +}); diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index 6433a6c08bb..f8c68db3233 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -12,6 +12,7 @@ import { import { getRoomTitle, getUidDirectMessage } from './helpers'; import { createDirectMessage } from '../createDirectMessage'; import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; +import type { peekOrCreateRoomStore as TPeekOrCreateRoomStore } from '../../../views/RoomView/stores/RoomStore'; interface IGoRoomItem { search?: boolean; // comes from spotlight @@ -21,18 +22,29 @@ interface IGoRoomItem { name?: string; prid?: string; visitor?: IVisitor; + joinCodeRequired?: boolean; } export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription | IOmnichannelRoomVisitor; +interface RoomRouteParams { + rid?: string; + name: string; + t?: SubscriptionType; + prid?: string; + visitor?: IVisitor; + joinCodeRequired?: boolean; + roomUserId?: string; +} + const navigate = ({ item, isMasterDetail, ...props }: { item: TGoRoomItem; isMasterDetail: boolean }) => { - const routeParams = { + const routeParams: RoomRouteParams = { rid: item.rid, name: getRoomTitle(item), t: item.t, prid: item.prid, - room: item, visitor: item.visitor, + joinCodeRequired: item.joinCodeRequired, roomUserId: getUidDirectMessage(item), ...props }; @@ -43,6 +55,32 @@ const navigate = ({ item, isMasterDetail, ...props }: { item: TGoRoomItem; isMas return; } + // Warm the RoomStore at press time so its DB observer runs during the nav transition and + // RoomView mounts against a hydrated store. peekOrCreate leaves the entry at refCount 0 and + // schedules its own grace sweep: if RoomView acquires it, it stays alive; otherwise (cancelled/ + // failed navigation) the sweep reclaims it after the transition. No explicit release needed. + if (routeParams.rid) { + // Lazy require: goRoom is a low-level helper imported across the app, RoomStore lives in the + // view layer and pulls the encryption/native graph. Loading it only when a warm-up actually + // runs keeps that graph out of every goRoom importer. + const { peekOrCreateRoomStore } = require('../../../views/RoomView/stores/RoomStore') as { + peekOrCreateRoomStore: typeof TPeekOrCreateRoomStore; + }; + peekOrCreateRoomStore({ + rid: routeParams.rid, + t: routeParams.t, + initialRoom: { + rid: routeParams.rid, + t: routeParams.t as string, + name: routeParams.name, + prid: routeParams.prid, + visitor: routeParams.visitor, + joinCodeRequired: routeParams.joinCodeRequired + }, + roomUserId: routeParams.roomUserId + }); + } + Navigation.popTo('DrawerNavigator'); if (isMasterDetail) { return Navigation.dispatch((state: any) => { @@ -113,11 +151,7 @@ export const goRoom = async ({ } } - /** - * Fetches subscription from database to use goRoom with a more complete room object. - * We might want to review this logic in the future, since react-navigation complains about non-serializable data - * https://reactnavigation.org/docs/troubleshooting/#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state - */ + // Re-fetch the subscription so scalar route params (name, prid, visitor, roomUserId) are as complete as possible. let _item = item; if (item.rid) { const sub = await getSubscriptionByRoomId(item.rid); diff --git a/app/lib/methods/helpers/isReadOnly.test.ts b/app/lib/methods/helpers/isReadOnly.test.ts new file mode 100644 index 00000000000..e048d6268f2 --- /dev/null +++ b/app/lib/methods/helpers/isReadOnly.test.ts @@ -0,0 +1,97 @@ +import { isReadOnly, isReadOnlySync } from './isReadOnly'; +import { hasPermission } from './helpers'; +import { store as reduxStore } from '../../store/auxStore'; + +jest.mock('./helpers', () => ({ + hasPermission: jest.fn() +})); + +jest.mock('../../store/auxStore', () => ({ + store: { getState: jest.fn() } +})); + +const mockedHasPermission = hasPermission as jest.Mock; +const mockedGetState = (reduxStore as unknown as { getState: jest.Mock }).getState; + +const username = 'user1'; +const postReadOnlyPermission = ['admin']; + +beforeEach(() => { + jest.clearAllMocks(); + mockedGetState.mockReturnValue({ permissions: { 'post-readonly': postReadOnlyPermission } }); + mockedHasPermission.mockResolvedValue([false]); +}); + +describe('isReadOnly (async)', () => { + test('archived room is read only', async () => { + expect(await isReadOnly({ archived: true }, username)).toBe(true); + expect(mockedHasPermission).not.toHaveBeenCalled(); + }); + + test('muted user is read only', async () => { + expect(await isReadOnly({ muted: [username] }, username)).toBe(true); + expect(mockedHasPermission).not.toHaveBeenCalled(); + }); + + test('non-ro room is not read only', async () => { + expect(await isReadOnly({}, username)).toBe(false); + expect(mockedHasPermission).not.toHaveBeenCalled(); + }); + + test('ro + archived returns true without fetching permission', async () => { + expect(await isReadOnly({ ro: true, rid: 'rid1', archived: true }, username)).toBe(true); + expect(mockedHasPermission).not.toHaveBeenCalled(); + }); + + test('ro + muted returns true without fetching permission', async () => { + expect(await isReadOnly({ ro: true, rid: 'rid1', muted: [username] }, username)).toBe(true); + expect(mockedHasPermission).not.toHaveBeenCalled(); + }); + + test('ro room without post permission is read only', async () => { + mockedHasPermission.mockResolvedValue([false]); + expect(await isReadOnly({ ro: true, rid: 'rid1' }, username)).toBe(true); + }); + + test('ro room with post permission is not read only', async () => { + mockedHasPermission.mockResolvedValue([true]); + expect(await isReadOnly({ ro: true, rid: 'rid1' }, username)).toBe(false); + }); + + test('ro room with unmuted user is not read only', async () => { + mockedHasPermission.mockResolvedValue([false]); + expect(await isReadOnly({ ro: true, rid: 'rid1', unmuted: [username] }, username)).toBe(false); + }); +}); + +describe('isReadOnlySync', () => { + test('archived room is read only', () => { + expect(isReadOnlySync({ archived: true }, username, postReadOnlyPermission, [])).toBe(true); + }); + + test('muted user is read only', () => { + expect(isReadOnlySync({ muted: [username] }, username, postReadOnlyPermission, [])).toBe(true); + }); + + test('non-ro room is not read only', () => { + expect(isReadOnlySync({}, username, postReadOnlyPermission, [])).toBe(false); + }); + + test('ro room without matching role is read only', () => { + expect(isReadOnlySync({ ro: true, roles: ['user'] }, username, postReadOnlyPermission, ['user'])).toBe(true); + }); + + test('ro room with matching room role is not read only', () => { + expect(isReadOnlySync({ ro: true, roles: ['admin'] }, username, postReadOnlyPermission, [])).toBe(false); + }); + + test('ro room with matching user role is not read only', () => { + expect(isReadOnlySync({ ro: true, roles: [] }, username, postReadOnlyPermission, ['admin'])).toBe(false); + }); + + test('ro room with unmuted user is not read only', () => { + expect(isReadOnlySync({ ro: true, roles: ['user'], unmuted: [username] }, username, postReadOnlyPermission, ['user'])).toBe( + false + ); + }); +}); diff --git a/app/lib/methods/helpers/isReadOnly.ts b/app/lib/methods/helpers/isReadOnly.ts index 226cd5350e9..469e03ae5ef 100644 --- a/app/lib/methods/helpers/isReadOnly.ts +++ b/app/lib/methods/helpers/isReadOnly.ts @@ -14,7 +14,9 @@ const canPostReadOnly = async (room: Partial, username: string) = const isMuted = (room: Partial, username: string) => room && room.muted && room.muted.find && !!room.muted.find(m => m === username); -export const isReadOnly = async (room: Partial, username: string): Promise => { +// Shared branch chain gating message posting. `allowPost` is resolved by each caller +// (async via a permission fetch, sync via precomputed roles) and only consulted when `ro`. +const evaluate = (room: Partial, username: string, allowPost: boolean): boolean => { if (room.archived) { return true; } @@ -22,11 +24,32 @@ export const isReadOnly = async (room: Partial, username: string) return true; } if (room?.ro) { - const allowPost = await canPostReadOnly(room, username); - if (allowPost) { - return false; - } - return true; + return !allowPost; } return false; }; + +export const isReadOnly = async (room: Partial, username: string): Promise => { + if (room.archived || isMuted(room, username)) { + return true; + } + const allowPost = room?.ro ? await canPostReadOnly(room, username) : false; + return evaluate(room, username, allowPost); +}; + +// Synchronous counterpart to `isReadOnly` for callers that already hold a reactively-observed +// room (with `.roles`) and the current user's roles, avoiding the async DB re-fetch in `hasPermission`. +export const isReadOnlySync = ( + room: Partial, + username: string, + postReadOnlyPermission: string[] | undefined, + userRoles: string[] +): boolean => { + let allowPost = false; + if (room?.ro) { + const isUnmuted = !!room?.unmuted?.find(m => m === username); + const mergedRoles = [...new Set([...(room.roles || []), ...userRoles])]; + allowPost = !!postReadOnlyPermission?.some(r => mergedRoles.includes(r)) || isUnmuted; + } + return evaluate(room, username, allowPost); +}; diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index fb3e3619344..03e6b649200 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -42,11 +42,13 @@ export type ModalStackParamList = { }; }; RoomInfoView: { - room: ISubscription; - member: any; + room?: ISubscription; + member?: any; rid: string; t: SubscriptionType; showCloseModal?: boolean; + fromRid?: string; + itsMe?: boolean; }; SelectListView: { data: any; diff --git a/app/stacks/types.ts b/app/stacks/types.ts index 8c1b6608281..734985edd49 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -14,7 +14,8 @@ import { type TDataSelect, type TMessageAction, type TSubscriptionModel, - type TThreadModel + type TThreadModel, + type IVisitor } from '../definitions'; import { type ModalStackParamList } from './MasterDetailStack/types'; import { type TNavigation } from './stackType'; @@ -38,7 +39,8 @@ export type ChatsStackParamList = { name?: string; fname?: string; prid?: string; - room?: TSubscriptionModel | { rid: string; t: string; name?: string; fname?: string; prid?: string }; + visitor?: IVisitor; + joinCodeRequired?: boolean; jumpToMessageId?: string; jumpToThreadId?: string; roomUserId?: string | null; diff --git a/app/views/ForwardMessageView/index.tsx b/app/views/ForwardMessageView/index.tsx index 739819b91c0..12248abcf54 100644 --- a/app/views/ForwardMessageView/index.tsx +++ b/app/views/ForwardMessageView/index.tsx @@ -20,6 +20,8 @@ import EventEmitter from '../../lib/methods/helpers/events'; import { LISTENER } from '../../containers/Toast'; const ForwardMessageView = () => { + 'use memo'; + const [rooms, setRooms] = useState([]); const [sending, setSending] = useState(false); const navigation = useNavigation(); diff --git a/app/views/MessagesView/index.tsx b/app/views/MessagesView/index.tsx index 4175ab9141a..81db52b6b89 100644 --- a/app/views/MessagesView/index.tsx +++ b/app/views/MessagesView/index.tsx @@ -9,6 +9,7 @@ import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-cont import { type MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import Message from '../../containers/message'; import { MessageRoomProvider } from '../../containers/message/stores/MessageRoomStore'; +import { A11yGateProvider } from '../../containers/message/stores/A11yGate'; import ActivityIndicator from '../../containers/ActivityIndicator'; import I18n from '../../i18n'; import getFileUrlAndTypeFromMessage from './getFileUrlAndTypeFromMessage'; @@ -24,7 +25,6 @@ import { type IApplicationState, type IRoomInfoParam, type TMessageModel, - type ISubscription, SubscriptionType, type IAttachment, type IMessage, @@ -44,7 +44,6 @@ interface IMessagesViewProps { username: string; token: string; }; - baseUrl: string; navigation: CompositeNavigationProp< NativeStackNavigationProp, NativeStackNavigationProp @@ -72,7 +71,6 @@ interface IParams { name?: string; fname?: string; prid?: string; - room?: ISubscription; jumpToMessageId?: string; jumpToThreadId?: string; roomUserId?: string; @@ -82,7 +80,6 @@ class MessagesView extends Component { private rid: string; private t: SubscriptionType; private content: any; - private room?: ISubscription; constructor(props: IMessagesViewProps) { super(props); @@ -141,8 +138,7 @@ class MessagesView extends Component { let params: IParams = { rid: this.rid, jumpToMessageId: item._id, - t: this.t, - room: this.room + t: this.t }; if (item.tmid) { Navigation.popToRoom(isMasterDetail); @@ -334,7 +330,7 @@ class MessagesView extends Component { render() { const { messages, loading } = this.state; - const { theme, user, baseUrl, insets } = this.props; + const { theme, insets } = this.props; if (!loading && messages.length === 0) { return this.renderEmpty(); @@ -342,31 +338,30 @@ class MessagesView extends Component { return ( - - item._id} - onEndReached={this.load} - contentContainerStyle={{ paddingBottom: insets.bottom }} - ListFooterComponent={loading ? : null} - /> - + + + item._id} + onEndReached={this.load} + contentContainerStyle={{ paddingBottom: insets.bottom }} + ListFooterComponent={loading ? : null} + /> + + ); } } const mapStateToProps = (state: IApplicationState) => ({ - baseUrl: state.server.server, user: getUserSelector(state) }); diff --git a/app/views/ModalBlockView.tsx b/app/views/ModalBlockView.tsx index ae39435bf46..969ff51e26e 100644 --- a/app/views/ModalBlockView.tsx +++ b/app/views/ModalBlockView.tsx @@ -17,8 +17,8 @@ import { triggerBlockAction, triggerCancel, triggerSubmitView } from '../lib/met import { type IApplicationState, type TAnyMessageModel } from '../definitions'; import KeyboardView from '../containers/KeyboardView'; import { MessageRoomProvider } from '../containers/message/stores/MessageRoomStore'; +import { A11yGateProvider } from '../containers/message/stores/A11yGate'; import { MessageProvider } from '../containers/message/stores/MessageStore'; -import { getUserSelector } from '../selectors/login'; const styles = StyleSheet.create({ content: { @@ -54,8 +54,6 @@ interface IModalBlockViewProps { route: RouteProp; theme: TSupportedThemes; language: string; - user: ReturnType; - baseUrl: string; } // eslint-disable-next-line no-sequences @@ -97,7 +95,7 @@ const LoadingIndicator = ({ loading }: { loading: boolean }) => { // A UIKit modal is not a message, but the shared media components it can render // (ImageContainer -> Button -> Touchable) assume a per-message context. Provide an // empty one: no long-press target, no id-scoped cache. Images still load via the -// room provider's user/baseUrl. Routed through `unknown` (the repo's convention for +// user/baseUrl the media hooks now read from redux. Routed through `unknown` (the repo's convention for // fake TAnyMessageModel fixtures) so the cast reads as intentional, not a real message. const EMPTY_MESSAGE = {} as unknown as TAnyMessageModel; @@ -260,7 +258,7 @@ class ModalBlockView extends Component - - - - - + + + + + + + @@ -295,9 +295,7 @@ class ModalBlockView extends Component ({ - language: state.login.user.language as string, - baseUrl: state.server.server, - user: getUserSelector(state) + language: state.login.user.language as string }); export default connect(mapStateToProps)(ModalBlockView); diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index 1e8b14ea772..95e445805f4 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -40,6 +40,8 @@ type TRoomInfoViewNavigationProp = CompositeNavigationProp< type TRoomInfoViewRouteProp = RouteProp; const RoomInfoView = (): ReactElement => { + 'use memo'; + const { params: { rid, t, fromRid, member, room: roomParam, showCloseModal, itsMe } } = useRoute(); diff --git a/app/views/RoomView/JoinCode.tsx b/app/views/RoomView/JoinCode.tsx deleted file mode 100644 index e4aaee45346..00000000000 --- a/app/views/RoomView/JoinCode.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { forwardRef, memo, useImperativeHandle, useState } from 'react'; -import { InteractionManager, StyleSheet, Text, View } from 'react-native'; -import Modal from 'react-native-modal'; -import { connect } from 'react-redux'; -import { GestureHandlerRootView } from 'react-native-gesture-handler'; - -import I18n from '../../i18n'; -import Button from '../../containers/Button'; -import { FormTextInput } from '../../containers/TextInput'; -import sharedStyles from '../Styles'; -import { themes } from '../../lib/constants/colors'; -import { joinRoom } from '../../lib/services/restApi'; -import { type TSupportedThemes } from '../../theme'; -import { withMasterDetail } from '../../lib/hooks/useMasterDetail'; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center' - }, - content: { - padding: 16, - width: '100%', - borderRadius: 4 - }, - title: { - fontSize: 16, - paddingBottom: 8, - ...sharedStyles.textBold, - ...sharedStyles.textAlignCenter - }, - button: { - minWidth: 96, - marginBottom: 0 - }, - buttonContainer: { - flexDirection: 'row', - justifyContent: 'space-between' - }, - tablet: { - height: undefined - } -}); - -export interface IJoinCodeProps { - rid: string; - t: string; - onJoin: Function; - isMasterDetail: boolean; - theme: TSupportedThemes; -} - -export interface IJoinCode { - show: () => void; -} - -const JoinCode = memo( - forwardRef(({ rid, t, onJoin, isMasterDetail, theme }, ref) => { - const [visible, setVisible] = useState(false); - const [error, setError] = useState(false); - const [code, setCode] = useState(''); - - const show = () => setVisible(true); - - const hide = () => setVisible(false); - - const handleJoinRoom = async () => { - try { - await joinRoom(rid, code, t as any); - onJoin(); - hide(); - } catch (e) { - setError(true); - } - }; - - useImperativeHandle(ref, () => ({ show })); - - return ( - - - - {I18n.t('Insert_Join_Code')} - { - if (e) { - InteractionManager.runAfterInteractions(() => { - e.focus(); - }); - } - }} - returnKeyType='send' - autoCapitalize='none' - onChangeText={setCode} - onSubmitEditing={handleJoinRoom} - placeholder={I18n.t('Join_Code')} - secureTextEntry - error={error ? { error: 'error-code-invalid', reason: I18n.t('Code_or_password_invalid') } : undefined} - testID='join-code-input' - /> - -