From 32397e93c9dab511e7bf4b261dbc9e91c95d341b Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Fri, 6 Nov 2026 13:46:40 +0000 Subject: [PATCH 1/2] Use the mobile-ui and mobile-air main that swipe-to-reply needs Swipe-to-reply (#157) relies on the gesture area's trigger from mobile-ui#197 and mobile-air#651, but merged with the lock still on mobile-ui 0.9.0, so it didn't work and eight of its tests fail on main. Packagist's dev-main for mobile-ui is stuck at 29 Oct, so mobile-ui comes straight from GitHub until that's fixed. Co-Authored-By: Claude Opus 5.5 --- composer.json | 8 ++++-- composer.lock | 74 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index bfde9571..122d0d56 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,10 @@ "type": "vcs", "url": "https://github.com/simonhamp/toastkit" }, + { + "type": "vcs", + "url": "https://github.com/NativePHP/mobile-ui" + }, { "name": "nativephp-plugins", "type": "composer", @@ -33,7 +37,7 @@ "laravel/boost": "^2.0", "laravel/framework": "^13.0", "laravel/tinker": "^3.0", - "nativephp/mobile": "^4.7", + "nativephp/mobile": "dev-main as 4.8.0", "nativephp/mobile-browser": "^1.2", "nativephp/mobile-camera": "^1.1", "nativephp/mobile-clipboard": "^1.0", @@ -43,7 +47,7 @@ "nativephp/mobile-secure-storage": "^1.1", "nativephp/mobile-share": "^1.1", "nativephp/mobile-skia": "^1.0", - "nativephp/mobile-ui": "^0.9", + "nativephp/mobile-ui": "dev-main", "s2br/nativephp-mobile-splashscreen": "^1.4", "victorycodedev/toastkit": "dev-fix/android-toasts-above-sheets as 1.4.0" }, diff --git a/composer.lock b/composer.lock index e9cd6014..eefe7986 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "745d59f03866816f94bab0b20f027a25", + "content-hash": "4564a3ffac02898824b683cfa1aedddd", "packages": [ { "name": "bacon/bacon-qr-code", @@ -1534,16 +1534,16 @@ }, { "name": "laravel/framework", - "version": "v13.39.0", + "version": "v13.40.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d52c3b3129e4c8e213c97cdc68f94b0b7d27f181" + "reference": "1449bf241017087fbd38c118ecaabef3f6797fdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d52c3b3129e4c8e213c97cdc68f94b0b7d27f181", - "reference": "d52c3b3129e4c8e213c97cdc68f94b0b7d27f181", + "url": "https://api.github.com/repos/laravel/framework/zipball/1449bf241017087fbd38c118ecaabef3f6797fdc", + "reference": "1449bf241017087fbd38c118ecaabef3f6797fdc", "shasum": "" }, "require": { @@ -1761,7 +1761,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-10-27T14:04:39+00:00" + "time": "2026-11-03T15:07:18+00:00" }, { "name": "laravel/mcp", @@ -2090,16 +2090,16 @@ }, { "name": "league/commonmark", - "version": "2.10.4", + "version": "2.10.7", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "40821b7606d8d946bac57dbd2290076c722e3a65" + "reference": "8eb53e906fd60c67038d22f1f1cab420ed58b8be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/40821b7606d8d946bac57dbd2290076c722e3a65", - "reference": "40821b7606d8d946bac57dbd2290076c722e3a65", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/8eb53e906fd60c67038d22f1f1cab420ed58b8be", + "reference": "8eb53e906fd60c67038d22f1f1cab420ed58b8be", "shasum": "" }, "require": { @@ -2193,7 +2193,7 @@ "type": "tidelift" } ], - "time": "2026-10-17T19:59:50+00:00" + "time": "2026-10-31T13:35:37+00:00" }, { "name": "league/config", @@ -2755,16 +2755,16 @@ }, { "name": "nativephp/mobile", - "version": "4.7.0", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/NativePHP/mobile-air.git", - "reference": "0d7e2e6509b1a0514726e67016fecade5e9137b2" + "reference": "c0da5d781df99416fb92f398a354ceac0b532922" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/NativePHP/mobile-air/zipball/0d7e2e6509b1a0514726e67016fecade5e9137b2", - "reference": "0d7e2e6509b1a0514726e67016fecade5e9137b2", + "url": "https://api.github.com/repos/NativePHP/mobile-air/zipball/c0da5d781df99416fb92f398a354ceac0b532922", + "reference": "c0da5d781df99416fb92f398a354ceac0b532922", "shasum": "" }, "require": { @@ -2804,6 +2804,7 @@ "suggest": { "ext-zip": "Required by native:install to validate and extract the downloaded Android PHP binary archives" }, + "default-branch": true, "type": "library", "extra": { "laravel": { @@ -2846,9 +2847,9 @@ ], "support": { "issues": "https://github.com/NativePHP/mobile-air/issues", - "source": "https://github.com/NativePHP/mobile-air/tree/4.7.0" + "source": "https://github.com/NativePHP/mobile-air/tree/main" }, - "time": "2026-10-29T17:31:02+00:00" + "time": "2026-11-05T15:42:02+00:00" }, { "name": "nativephp/mobile-browser", @@ -3355,16 +3356,16 @@ }, { "name": "nativephp/mobile-ui", - "version": "0.9.0", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/NativePHP/mobile-ui.git", - "reference": "9cee87cbc676a6500247bec99be5d19f633a04e9" + "reference": "ce627333f74293a719e1b86891cec09772f74b4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/NativePHP/mobile-ui/zipball/9cee87cbc676a6500247bec99be5d19f633a04e9", - "reference": "9cee87cbc676a6500247bec99be5d19f633a04e9", + "url": "https://api.github.com/repos/NativePHP/mobile-ui/zipball/ce627333f74293a719e1b86891cec09772f74b4a", + "reference": "ce627333f74293a719e1b86891cec09772f74b4a", "shasum": "" }, "require": { @@ -3376,6 +3377,7 @@ "orchestra/testbench": "^10.0", "pestphp/pest": "^3.0" }, + "default-branch": true, "type": "nativephp-plugin", "extra": { "laravel": { @@ -3392,7 +3394,17 @@ "Native\\Mobile\\UI\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", + "scripts": { + "test": [ + "pest" + ], + "lint": [ + "pint" + ], + "lint:test": [ + "pint --test" + ] + }, "license": [ "MIT" ], @@ -3404,16 +3416,16 @@ ], "description": "Native SwiftUI and Jetpack Composer components for NativePHP", "support": { - "issues": "https://github.com/NativePHP/mobile-ui/issues", - "source": "https://github.com/NativePHP/mobile-ui/tree/0.9.0" + "source": "https://github.com/NativePHP/mobile-ui/tree/main", + "issues": "https://github.com/NativePHP/mobile-ui/issues" }, "funding": [ { - "url": "https://nativephp.com/sponsor", - "type": "other" + "type": "other", + "url": "https://nativephp.com/sponsor" } ], - "time": "2026-10-29T17:44:09+00:00" + "time": "2026-11-05T16:47:04+00:00" }, { "name": "nesbot/carbon", @@ -11562,6 +11574,12 @@ } ], "aliases": [ + { + "package": "nativephp/mobile", + "version": "dev-main", + "alias": "4.8.0", + "alias_normalized": "4.8.0.0" + }, { "package": "victorycodedev/toastkit", "version": "dev-fix/android-toasts-above-sheets", @@ -11571,6 +11589,8 @@ ], "minimum-stability": "stable", "stability-flags": { + "nativephp/mobile": 20, + "nativephp/mobile-ui": 20, "victorycodedev/toastkit": 20 }, "prefer-stable": true, From d6efe9198b496c8d777a30b117c4c9360d3a7e9f Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Fri, 6 Nov 2026 13:49:27 +0000 Subject: [PATCH 2/2] Put Reply just off the bubble you're pulling, centred on it Reply used to come in from the far edge of the thread, a long way from the bubble, and was centred on the whole message: name, reply ghost, link card, reactions and time. Each bubble and its pin now sit in a row of their own, and Reply lies over that row's edge, 8 points past the side the pull uncovers. So it's centred on the bubble and moves with it. It's absolutely positioned rather than in the row, because Android rows don't shrink a bubble to make room for what's beside it, and negative margins only offset the drawing there. Bubbles wrap exactly where they did before. Co-Authored-By: Claude Opus 5.5 --- app/NativeComponents/Conversation.php | 4 +- .../native/partials/message-item.blade.php | 84 +++++++++---------- .../partials/message-swipe-reply.blade.php | 17 ++++ .../native/partials/message-voice.blade.php | 37 ++++---- tests/Feature/Screens/ConversationTest.php | 40 +++++---- 5 files changed, 102 insertions(+), 80 deletions(-) create mode 100644 resources/views/native/partials/message-swipe-reply.blade.php diff --git a/app/NativeComponents/Conversation.php b/app/NativeComponents/Conversation.php index 2302f411..5dbf439d 100644 --- a/app/NativeComponents/Conversation.php +++ b/app/NativeComponents/Conversation.php @@ -132,8 +132,8 @@ class Conversation extends NativeComponent public const array QuickReactions = ['😂', '✅', '❤️']; /** - * How far a message is pulled toward its edge before letting go replies to it. Reply comes in over that distance from - * the other side of the thread. + * How far a message is pulled toward its edge before letting go replies to it. Reply grows in beside the bubble over + * that distance. */ public const int ReplyPull = 64; diff --git a/resources/views/native/partials/message-item.blade.php b/resources/views/native/partials/message-item.blade.php index 77b67da2..1944691a 100644 --- a/resources/views/native/partials/message-item.blade.php +++ b/resources/views/native/partials/message-item.blade.php @@ -6,8 +6,8 @@ message the web gives a status, in that status. Pulling it toward its edge replies to it: someone else's to the left and your own to the right. It follows the - finger that way only, partly off the screen, though a short bubble stops while some of it still shows. Reply slides - in from the other side of the thread as it grows, turns upright and fades in. At Conversation::ReplyPull the phone + finger that way only, partly off the screen, though a short bubble stops while some of it still shows. Reply grows + in a little way off the bubble's other side (see partials/message-swipe-reply). At Conversation::ReplyPull the phone buzzes lightly; let go there and the composer replies to it and takes the keyboard. It springs back either way. The pull, the buzz and the spring back all run on the phone, and only the reply comes here. A double tap toggles a heart. A pinned message keeps its pin beside it. A message still being pushed can't be pulled, and nothing replies @@ -49,18 +49,22 @@ @endif {{-- - An image-only message keeps its pin beside its images, and React's quick reactions point at them; otherwise - those go with the bubble. The bubble's row stops short of the far side, so a long message doesn't reach it. + An image-only message keeps its pin and Reply beside its images, and React's quick reactions point at them; + otherwise those go with the bubble. The bubble's row stops short of the far side, so a long message doesn't + reach it. --}} @if ($message->images !== [] && $message->body === '') - @if ($mine) - @include('native.partials.message-pin') - @endif - @include('native.partials.message-images', ['photosMenu' => $menu]) - @if (! $mine) - @include('native.partials.message-pin') - @endif + + @if ($mine) + @include('native.partials.message-pin') + @endif + @include('native.partials.message-images', ['photosMenu' => $menu]) + @if (! $mine) + @include('native.partials.message-pin') + @endif + @include('native.partials.message-swipe-reply') + @if ($reactionsFor === $message->id && ! $reactsFromPinned) @include('native.partials.quick-reactions', ['anchor' => $reactionsAnchor]) @endif @@ -72,27 +76,30 @@ @include('native.partials.message-voice') @if ($message->body !== '') - {{-- The bubble shrinks to wrap short of the far side, and of its pin when it has one. --}} + {{-- The bubble shrinks to wrap short of the far side, and of its pin when it has one. Reply sits just past both. --}} - @if ($mine) - @include('native.partials.message-pin') - @endif - @if ($message->isPending()) - - - @include('native.partials.message-body', ['ink' => $message->status === 'rejected' ? 'text-theme-ink' : 'text-theme-on-'.$myColour, 'mentionInk' => 'text-theme-on-'.$myColour, 'quoteRule' => $message->status === 'rejected' ? 'border-theme-lane-5' : 'border-theme-on-'.$myColour, 'linkPrefixInk' => $message->status === 'rejected' ? 'text-theme-ink-muted' : 'text-theme-on-'.$myColour.'/70']) - - - @else - - - @include('native.partials.message-body', ['ink' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-ink', 'mentionInk' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-primary', 'quoteRule' => $mine ? 'border-theme-on-'.$myColour : 'border-theme-lane-5', 'linkPrefixInk' => $mine ? 'text-theme-on-'.$myColour.'/70' : 'text-theme-ink-muted']) - - - @endif - @if (! $mine) - @include('native.partials.message-pin') - @endif + + @if ($mine) + @include('native.partials.message-pin') + @endif + @if ($message->isPending()) + + + @include('native.partials.message-body', ['ink' => $message->status === 'rejected' ? 'text-theme-ink' : 'text-theme-on-'.$myColour, 'mentionInk' => 'text-theme-on-'.$myColour, 'quoteRule' => $message->status === 'rejected' ? 'border-theme-lane-5' : 'border-theme-on-'.$myColour, 'linkPrefixInk' => $message->status === 'rejected' ? 'text-theme-ink-muted' : 'text-theme-on-'.$myColour.'/70']) + + + @else + + + @include('native.partials.message-body', ['ink' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-ink', 'mentionInk' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-primary', 'quoteRule' => $mine ? 'border-theme-on-'.$myColour : 'border-theme-lane-5', 'linkPrefixInk' => $mine ? 'text-theme-on-'.$myColour.'/70' : 'text-theme-ink-muted']) + + + @endif + @if (! $mine) + @include('native.partials.message-pin') + @endif + @include('native.partials.message-swipe-reply') + @if ($reactionsFor === $message->id && ! $reactsFromPinned) @include('native.partials.quick-reactions', ['anchor' => $reactionsAnchor]) @endif @@ -131,20 +138,5 @@ @if ($replyable) - {{-- - Reply, just past the far side of the thread, comes in with the finger as the message goes the other way: - growing from 40%, turning upright from a quarter turn and fading in, and in place once the pull reaches - ReplyPull. It's off the row, so it never lies over a bubble and follows the finger even where a short - bubble has stopped. - --}} - - - - - @endif diff --git a/resources/views/native/partials/message-swipe-reply.blade.php b/resources/views/native/partials/message-swipe-reply.blade.php new file mode 100644 index 00000000..f9ecba92 --- /dev/null +++ b/resources/views/native/partials/message-swipe-reply.blade.php @@ -0,0 +1,17 @@ +{{-- + Reply, a little way past the bubble on the side a pull uncovers (past its pin when it has one), so it's where you're + looking. It's laid over the bubble's row as the row is tall, so it's centred on the bubble, and it goes with it. It + grows from 40%, turns upright from a quarter turn and fades in as the pull goes, and is in place at + Conversation::ReplyPull. Lying over the row rather than in it, it takes none of the bubble's room on either + platform, so the bubble wraps where it would without it. +--}} +@if ($replyable) + + + + + +@endif diff --git a/resources/views/native/partials/message-voice.blade.php b/resources/views/native/partials/message-voice.blade.php index a17f4d9e..abb7b577 100644 --- a/resources/views/native/partials/message-voice.blade.php +++ b/resources/views/native/partials/message-voice.blade.php @@ -1,6 +1,6 @@ {{-- - A message's voice note, in a bubble of its own as the web draws it, with the message's pin beside it as a text - bubble has it (see partials/message-item). Yours is in your lane, staged in it faded until devnull has it, or + A message's voice note, in a bubble of its own as the web draws it, with the message's pin and Reply beside it as a + text bubble has them (see partials/message-item). Yours is in your lane, staged in it faded until devnull has it, or outlined in red once it turned the note down; everyone else's is on the wash. The player is partials/voice-note. A long press opens the message's menu, as on a text bubble, without Copy text, and React's quick reactions point at the bubble. @@ -8,23 +8,26 @@ @if ($message->voice !== null) @php($voiceInk = $message->status === 'rejected' ? 'ink' : ($mine ? 'on-'.$myColour : 'ink')) - @if ($mine) - @include('native.partials.message-pin') - @endif - @if ($message->isPending()) - - @include('native.partials.voice-note', ['voice' => $message->voice, 'voiceKey' => $message->voiceKey(), 'ref' => 'voice-'.$key, 'ink' => $voiceInk]) - - @else - - + + @if ($mine) + @include('native.partials.message-pin') + @endif + @if ($message->isPending()) + @include('native.partials.voice-note', ['voice' => $message->voice, 'voiceKey' => $message->voiceKey(), 'ref' => 'voice-'.$key, 'ink' => $voiceInk]) - - @endif - @if (! $mine) - @include('native.partials.message-pin') - @endif + @else + + + @include('native.partials.voice-note', ['voice' => $message->voice, 'voiceKey' => $message->voiceKey(), 'ref' => 'voice-'.$key, 'ink' => $voiceInk]) + + + @endif + @if (! $mine) + @include('native.partials.message-pin') + @endif + @include('native.partials.message-swipe-reply') + @if ($reactionsFor === $message->id && ! $reactsFromPinned) @include('native.partials.quick-reactions', ['anchor' => $reactionsAnchor]) @endif diff --git a/tests/Feature/Screens/ConversationTest.php b/tests/Feature/Screens/ConversationTest.php index 14d6bc75..2e4f23d7 100644 --- a/tests/Feature/Screens/ConversationTest.php +++ b/tests/Feature/Screens/ConversationTest.php @@ -63,9 +63,9 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = // Theirs at the start of the row and yours at the end, each on its own, in a thread that opens on its latest message. expect(nodeByRef($screen, 'content-1')['layout']['justify_content'])->toBe(0) - ->and(childRefs($screen, 'content-1'))->toBe(['bubble-1']) + ->and(childRefs($screen, 'bubble-and-pin-1'))->toBe(['bubble-1', 'swipe-reply-1']) ->and(nodeByRef($screen, 'content-2')['layout']['justify_content'])->toBe(2) - ->and(childRefs($screen, 'content-2'))->toBe(['bubble-2']) + ->and(childRefs($screen, 'bubble-and-pin-2'))->toBe(['bubble-2', 'swipe-reply-2']) ->and(nodeByRef($screen, 'thread')['props']['scroll_anchor'] ?? null)->toBe('bottom'); }); @@ -161,6 +161,11 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = ->assertSee('staged') ->assertSet('draft', ''); + // Until devnull has it, it can't be pulled, so it has no Reply. + $staged = $screen->instance()->pending[0]->clientId; + + expect(childRefs($screen, "bubble-and-pin-{$staged}"))->toBe(["bubble-{$staged}"]); + finishRefreshes($screen)->assertSee('pushed')->assertSet('pending', []); $sent = json_decode(sentTo('POST', 'remotes/@ada/messages')[0], true); @@ -204,7 +209,7 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = expect(array_map(fn (string $body): string => json_decode($body, true)['client_id'], sentTo('POST', 'remotes/@ada/messages')))->toBe([$clientId, $clientId]); }); -it('pulls theirs left and yours right, toward their edges, with Reply coming in from the other side', function () { +it('pulls theirs left and yours right, toward their edges, with Reply coming in just off the bubble', function () { serveRemoteThread(threadJson([ remoteMessageJson(['id' => 1, 'body' => 'did you see the PR?']), remoteMessageJson(['id' => 2, 'user_id' => RemotesViewerId, 'body' => 'which one?']), @@ -222,18 +227,22 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = ->and(nodeByRef($screen, 'pulled-1')['props']['translate-x_sv'])->toBe($pan('1')) ->and(nodeByRef($screen, 'pulled-2')['props']['translate-x_sv'])->toBe($pan('2')); - // Reply waits just past the far side of the row, off the bubbles, and follows the finger in, growing, turning - // upright and fading in, all the way by ReplyPull. Insets go top, right, bottom, left. - expect(nodeByRef($screen, 'swipe-reply-1')['layout'])->toMatchArray(['width' => $pull, 'position' => [-0.0, -$pull, -0.0, 0.0]]) - ->and(nodeByRef($screen, 'swipe-reply-2')['layout']['position'])->toEqual([-0.0, 0.0, -0.0, -$pull]) + // Reply lies over the bubble's own row, as tall as it so it's centred on the bubble, 8 points past the side the pull + // uncovers, and goes with it, growing, turning upright and fading in, all the way by ReplyPull. Lying over the row + // and not in it, it takes none of the bubble's room. Insets go top, right, bottom, left. + expect(childRefs($screen, 'content-1'))->toBe(['bubble-and-pin-1']) + ->and(childRefs($screen, 'bubble-and-pin-1'))->toBe(['bubble-1', 'swipe-reply-1']) + ->and(childRefs($screen, 'bubble-and-pin-2'))->toBe(['bubble-2', 'swipe-reply-2']) + ->and(nodeByRef($screen, 'content-1')['layout']['padding'])->toEqual([0, 64, 0, 0]) + ->and(nodeByRef($screen, 'swipe-reply-1')['layout'])->toMatchArray(['width' => 30.0, 'position' => [-0.0, -38.0, -0.0, 0.0]]) + ->and(nodeByRef($screen, 'swipe-reply-2')['layout']['position'])->toEqual([-0.0, 0.0, -0.0, -38.0]) + ->and(nodeByRef($screen, 'swipe-reply-1')['props'])->not->toHaveKey('translate-x_sv') ->and(nodeByRef($screen, 'swipe-reply-1')['props'])->toMatchArray([ - 'translate-x_sv' => $pan('1'), 'opacity_sv' => $pan('1')."|interp:-{$pull},0:1,0", 'scale_sv' => $pan('1')."|interp:-{$pull},0:1,0.4", 'rotate_sv' => $pan('1')."|interp:-{$pull},0:0,90", ]) ->and(nodeByRef($screen, 'swipe-reply-2')['props'])->toMatchArray([ - 'translate-x_sv' => $pan('2'), 'opacity_sv' => $pan('2')."|interp:0,{$pull}:0,1", 'rotate_sv' => $pan('2')."|interp:0,{$pull}:-90,0", ]); @@ -293,7 +302,7 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = expect($bubble['type'])->toBe('popover') ->and($bubble['props']['visible'])->toBeTrue() - ->and(childRefs($screen, 'content-1'))->toBe(['bubble-1', 'reactions-bubble']) + ->and(childRefs($screen, 'content-1'))->toBe(['bubble-and-pin-1', 'reactions-bubble']) ->and(childRefs($screen, 'quick-reactions'))->toBe(['quick-0', 'quick-1', 'quick-2', 'more-reactions']); // A tap outside it closes it. @@ -303,7 +312,7 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = $screen->tap('react-menu-2'); - expect(childRefs($screen, 'content-2'))->toBe(['bubble-2', 'reactions-bubble']); + expect(childRefs($screen, 'content-2'))->toBe(['bubble-and-pin-2', 'reactions-bubble']); }); it("points the quick reactions at an image-only message's images, and pulls it to reply like any other", function () { @@ -311,7 +320,8 @@ function serveRemoteThread(array $thread, ?array $poll = null, int $pushStatus = $screen = finishRefreshes(Native::visit('/remotes/conversations/ada'))->tap('react-menu-1'); - expect(childRefs($screen, 'content-1'))->toBe(['photos-1', 'reactions-bubble']); + expect(childRefs($screen, 'content-1'))->toBe(['bubble-and-pin-1', 'reactions-bubble']) + ->and(childRefs($screen, 'bubble-and-pin-1'))->toBe(['photos-1', 'swipe-reply-1']); $screen->trigger('swipe-1')->assertSet('replyToId', 1); }); @@ -850,7 +860,7 @@ function serveOrgThread(array $thread, ?array $poll = null, ?Closure $pins = nul ->emitNative(ButtonPressed::class, ['index' => 1, 'label' => Conversation::PinMessage, 'id' => Conversation::PinAlert]) ->assertSee('pinned · 1'); - expect(childRefs($screen, 'content-1'))->toBe(['bubble-1', 'pinned-1']) + expect(childRefs($screen, 'bubble-and-pin-1'))->toBe(['bubble-1', 'pinned-1', 'swipe-reply-1']) ->and(array_column(menuItems($screen, 'bubble-1'), 0))->toContain('unpin-menu-1'); finishRefreshes($screen)->assertSee('pinned · 2'); @@ -881,8 +891,8 @@ function serveOrgThread(array $thread, ?array $poll = null, ?Closure $pins = nul $screen = finishRefreshes(Native::visit('/remotes/orgs/org-1')); - expect(childRefs($screen, 'content-1'))->toBe(['bubble-1', 'pinned-1']) - ->and(childRefs($screen, 'content-2'))->toBe(['pinned-2', 'bubble-2']) + expect(childRefs($screen, 'bubble-and-pin-1'))->toBe(['bubble-1', 'pinned-1', 'swipe-reply-1']) + ->and(childRefs($screen, 'bubble-and-pin-2'))->toBe(['pinned-2', 'bubble-2', 'swipe-reply-2']) ->and(nodeByRef($screen, 'pinned-1')['type'])->toBe('stack'); });