Repository: zulip/zulip-flutter (language: dart)

Issue:
content/msglist: Horizontal scrollbar in code block sometimes has wrong vertical position (again)
In a message-list page without a compose box, the scroll bar in a code block is misaligned, if it's in a message in the top sliver:

| Bottom sliver (aligned correctly) | Top sliver (bug) |
| --- | --- |
| <img width="559" alt="Image" src="https://github.com/user-attachments/assets/7844a4a1-f261-4c10-9781-6b33d7c9a667" /> | <img width="559" alt="Image" src="https://github.com/user-attachments/assets/e020f9cd-389d-414f-80b8-4887cc39c45d" /> |

## Diagnosis

This is #736 again, reappearing in fca651bf5f14562a8a742d25c8e64385ac3ed9a7; the bug is present at that commit but not its parent. We have a regression test for that issue, but it doesn't check the case where the message is in the top sliver:

<details>

```dart
    testWidgets('content not asked to consume insets (including bottom), even without compose box', (tester) async {
      // Regression test for: https://github.com/zulip/zulip-flutter/issues/736
      const fakePadding = FakeViewPadding(left: 10, top: 10, right: 10, bottom: 10);
      tester.view.viewInsets = fakePadding;
      tester.view.padding = fakePadding;

      await setupMessageListPage(tester, narrow: const CombinedFeedNarrow(),
        messages: [eg.streamMessage(content: ContentExample.codeBlockPlain.html)]);

      // Verify this message list lacks a compose box.
      // (The original bug wouldn't reproduce with a compose box present.)
      final state = MessageListPage.ancestorOf(tester.element(find.text("verb\natim")));
      check(state.composeBoxState).isNull();

      final element = tester.element(find.byType(CodeBlock));
      final padding = MediaQuery.of(element).padding;
      check(padding).equals(EdgeInsets.zero);
    });
```

</details>

cc @gnprice as the author of fca651bf5f14562a8a742d25c8e64385ac3ed9a7.


Discussion hints:
This seems to fix it; @gnprice, would you check this and send a PR, updating the regression test so it exercises the top-sliver case?

```diff
diff --git lib/widgets/message_list.dart lib/widgets/message_list.dart
index dcd063a62..071231412 100644
--- lib/widgets/message_list.dart
+++ lib/widgets/message_list.dart
@@ -579,7 +579,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
 
     // The top sliver has its child 0 as the item just before the
     // sliver boundary, child 1 as the item before that, and so on.
-    final topSliver = SliverStickyHeaderList(
+    Widget topSliver = SliverStickyHeaderList(
       headerPlacement: HeaderPlacement.scrollingStart,
       delegate: SliverChildBuilderDelegate(
         // To preserve state across rebuilds for individual [MessageItem]
@@ -663,6 +663,8 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
     if (!ComposeBox.hasComposeBox(widget.narrow)) {
       // TODO(#311) If we have a bottom nav, it will pad the bottom inset,
       //   and this can be removed; also remove mention in MessageList dartdoc
+      topSliver = MediaQuery.removePadding(context: context,
+        removeBottom: true, child: topSliver);
       bottomSliver = SliverSafeArea(key: bottomSliver.key, sliver: bottomSliver);
     }
```
Hi @chrisbobbe and @gnprice,

I’m interested in addressing this bug (#1523). May I begin working on a fix, or is someone else already assigned?
Sure, I've just assigned it to you. A key part of this work will be to update the regression test so that it fails without the fix and passes with the fix.
Hi @chrisbobbe,

Thanks for assigning me this issue! I'm really excited to work on it and make sure the fix is solid.

I've already implemented the core fix for the issue, and now I'm focusing on writing the regression test. To be completely transparent, I haven't had much prior experience writing comprehensive regression tests in Flutter before. My main focus has been on feature development.

However, I'm fully committed to learning and implementing this correctly. I understand the importance of having a robust regression test that fails without the fix and passes with it. I've already started diving into Flutter's testing documentation and examples, and I'm confident I can get this done.

I'll be sure to keep you updated on my progress, and if I face any major blockers, I'll reach out for guidance.

Thanks again for the opportunity!


"Hey Chris,

Hope you're doing well!

I wanted to let you know that I've completed the main fix for *Bug #1523
(ComposeBox Padding)*. The padding issue has now been resolved.

However, I'm facing a bit of a challenge with updating the regression test.
To be honest, I don't have much experience with testing and regression
tests yet, and I'm finding this part difficult to grasp properly.

Would it be possible for me to commit just this padding fix for now? This
would resolve the core problem, and we could get it merged quickly. I'm
definitely keen to learn more about testing concepts in the future, but for
now, I'd like to see this fix go live.

After this, I'd like to focus on other help wanted issues that might be
more critical and where I can contribute effectively with my current skills.

Please let me know what you think.

Thanks a lot, Vinod"

On Thu, Jun 5, 2025 at 11:02 AM Chris Bobbe ***@***.***>
wrote:

> *chrisbobbe* left a comment (zulip/zulip-flutter#1523)
> <https://github.com/zulip/zulip-flutter/issues/1523#issuecomment-2942814219>
>
> Sure, I've just assigned it to you. A key part of this work will be to
> update the regression test so that it fails without the fix and passes with
> the fix.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/zulip/zulip-flutter/issues/1523#issuecomment-2942814219>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/BSX2GV63I35CR2JRHQXLLCD3B7I6ZAVCNFSM6AAAAAB5SIEGBKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDSNBSHAYTIMRRHE>
> .
> You are receiving this because you were assigned.Message ID:
> ***@***.***>
>

The test is an important part of the change: it's how we make sure your fix doesn't get accidentally broken by a future change.

To ask for help with writing the test, you're welcome to stop by the #mobile-dev-help channel on chat.zulip.org and ask there. See our guide to using chat.zulip.org, and to asking for help:
https://zulip.com/development-community/



Pull request: msglist: Suppress bottom inset in top sliver, too
Fixes #1523.

Chris reported the issue, debugged, and wrote the core of the fix. I added the test, found that the left and right insets reappeared, debugged why, and added removeLeft and removeRight to fix those.

Co-authored-by: Chris Bobbe <cbobbe@zulip.com>


Test command: flutter test test/widgets/message_list_test.dart --reporter expanded 2>&1

Fix patch:
diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart
index 6316655705..77e2e8cb24 100644
--- a/lib/widgets/message_list.dart
+++ b/lib/widgets/message_list.dart
@@ -1069,7 +1069,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
 
     // The top sliver has its child 0 as the item just before the
     // sliver boundary, child 1 as the item before that, and so on.
-    final topSliver = SliverStickyHeaderList(
+    Widget topSliver = SliverStickyHeaderList(
       headerPlacement: HeaderPlacement.scrollingStart,
       delegate: SliverChildBuilderDelegate(
         // To preserve state across rebuilds for individual [MessageItem]
@@ -1148,6 +1148,15 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
       // TODO(#311) If we have a bottom nav, it will pad the bottom inset,
       //   and this can be removed; also remove mention in MessageList dartdoc
       bottomSliver = SliverSafeArea(key: bottomSliver.key, sliver: bottomSliver);
+      topSliver = MediaQuery.removePadding(context: context,
+        // In the top sliver, forget the bottom inset;
+        // we're having the bottom sliver take care of it.
+        removeBottom: true,
+        // (Also forget the left and right insets; the outer SafeArea, above,
+        // does that, but the `context` we're passing to this `removePadding`
+        // is from outside that SafeArea, so we need to repeat it.)
+        removeLeft: true, removeRight: true,
+        child: topSliver);
     }
 
     return MessageListScrollView(


Test patch:
diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart
index 1755994cfd..acdde3fd2c 100644
--- a/test/widgets/message_list_test.dart
+++ b/test/widgets/message_list_test.dart
@@ -405,7 +405,31 @@ void main() {
   });
 
   group('presents message content appropriately', () {
-    testWidgets('content not asked to consume insets (including bottom), even without compose box', (tester) async {
+    testWidgets('content not asked to consume insets (including bottom), even without compose box, in top sliver', (tester) async {
+      // Regression test for: https://github.com/zulip/zulip-flutter/issues/1523
+      const fakePadding = FakeViewPadding(left: 10, top: 10, right: 10, bottom: 10);
+      tester.view.viewInsets = fakePadding;
+      tester.view.padding = fakePadding;
+
+      await setupMessageListPage(tester, narrow: const CombinedFeedNarrow(),
+        messages: [
+          eg.streamMessage(content: ContentExample.codeBlockPlain.html),
+          eg.streamMessage(),
+        ]);
+
+      // Verify this message list lacks a compose box.
+      // (The original bug wouldn't reproduce with a compose box present.)
+      final state = MessageListPage.ancestorOf(tester.element(find.text("verb\natim")));
+      check(state.composeBoxState).isNull();
+      // Also verify that the first message is in the top sliver.
+      check(state.model!.middleMessage).equals(1);
+
+      final element = tester.element(find.byType(CodeBlock));
+      final padding = MediaQuery.of(element).padding;
+      check(padding).equals(EdgeInsets.zero);
+    });
+
+    testWidgets('content not asked to consume insets (including bottom), even without compose box, in bottom sliver', (tester) async {
       // Regression test for: https://github.com/zulip/zulip-flutter/issues/736
       const fakePadding = FakeViewPadding(left: 10, top: 10, right: 10, bottom: 10);
       tester.view.viewInsets = fakePadding;
@@ -418,6 +442,8 @@ void main() {
       // (The original bug wouldn't reproduce with a compose box present.)
       final state = MessageListPage.ancestorOf(tester.element(find.text("verb\natim")));
       check(state.composeBoxState).isNull();
+      // Also verify that the message is in the bottom sliver.
+      check(state.model!.middleMessage).equals(0);
 
       final element = tester.element(find.byType(CodeBlock));
       final padding = MediaQuery.of(element).padding;