| Lay every screen out in a test, and fix what that found 36bdfc5 nandi yesterday | 1 | /// Every screen, laid out for real, at sizes that squeeze. |
| 2 | /// |
| 3 | /// This is the test that was missing. `Cannot hit test a render box that has |
| 4 | /// never been laid out` is what a failed layout looks like from the outside, |
| 5 | /// and nothing automated ever laid the chat screen out — a GUI on Wayland |
| 6 | /// cannot be clicked, so every check stopped at the room list while the |
| 7 | /// biggest screen in the app went out unverified. |
| 8 | /// |
| 9 | /// `tester.takeException()` is the whole point: a layout error is reported to |
| 10 | /// FlutterError rather than thrown at the caller, so a test that only pumps |
| 11 | /// and asserts on widgets passes while the screen is broken. These fail. |
| 12 | /// |
| 13 | /// just test layout |
| Quality pass: reuse, dead weight, and two real costs 4dfc719 nandi yesterday | 14 | library; |
| 15 | |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi yesterday | 16 | import 'package:flutter/material.dart'; |
| 17 | import 'package:flutter_test/flutter_test.dart'; |
| 18 | import 'package:frq_core/frq_core.dart' as core; |
| The last of the Clojure 284b59c nandi 21h ago | 19 | import 'package:frq/nim_renderer.dart'; |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi yesterday | 20 | |
| 21 | /// Phone, small desktop, and a deliberately cramped one. The head row of the |
| 22 | /// chat screen asks for more than 360 points has, which is why it wraps. |
| 23 | const sizes = <String, Size>{ |
| 24 | 'phone': Size(360, 690), |
| 25 | 'desktop': Size(1280, 800), |
| 26 | 'cramped': Size(300, 500), |
| 27 | }; |
| 28 | |
| 29 | Future<void> layOut(WidgetTester tester, Size size) async { |
| 30 | await tester.binding.setSurfaceSize(size); |
| 31 | addTearDown(() => tester.binding.setSurfaceSize(null)); |
| 32 | await tester.pumpWidget(const NimApp()); |
| 33 | await tester.pump(); |
| 34 | } |
| 35 | |
| 36 | /// Nothing went to FlutterError while that frame was built. |
| 37 | void expectLaidOut(WidgetTester tester, String what) { |
| 38 | final e = tester.takeException(); |
| 39 | expect(e, isNull, reason: '$what reported: $e'); |
| 40 | } |
| 41 | |
| 42 | void main() { |
| 43 | // No offline guard needed: `demoUi` sets the state directly and none of |
| 44 | // these dispatch `connect`, so nothing here opens a socket. |
| 45 | |
| 46 | group('the connect screen', () { |
| 47 | for (final entry in sizes.entries) { |
| 48 | testWidgets('lays out at ${entry.key}', (tester) async { |
| 49 | core.resetUi(); |
| 50 | await layOut(tester, entry.value); |
| 51 | expectLaidOut(tester, 'connect at ${entry.key}'); |
| 52 | }); |
| 53 | } |
| 54 | |
| 55 | testWidgets('lays out in every auth mode', (tester) async { |
| 56 | for (final mode in ['guest', 'bluesky', 'app-password']) { |
| 57 | core.resetUi(); |
| 58 | core.dispatch('mode.$mode'); |
| 59 | await layOut(tester, sizes['phone']!); |
| 60 | expectLaidOut(tester, 'connect in $mode'); |
| 61 | } |
| 62 | }); |
| 63 | }); |
| 64 | |
| 65 | group('the chat screen', () { |
| 66 | // The one that was never laid out by anything automated. |
| 67 | for (final entry in sizes.entries) { |
| 68 | testWidgets('lays out at ${entry.key}', (tester) async { |
| 69 | core.demoUi(); |
| 70 | await layOut(tester, entry.value); |
| 71 | expectLaidOut(tester, 'chat at ${entry.key}'); |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | testWidgets('renders the conversation it was given', (tester) async { |
| 76 | core.demoUi(); |
| 77 | await layOut(tester, sizes['desktop']!); |
| 78 | expect(find.text('hello there'), findsOneWidget); |
| 79 | expect(find.text('#test'), findsWidgets); |
| 80 | expectLaidOut(tester, 'chat content'); |
| 81 | }); |
| 82 | |
| 83 | testWidgets('lays out with the people panel up', (tester) async { |
| 84 | core.demoUi(); |
| 85 | core.dispatch('users.toggle'); |
| 86 | await layOut(tester, sizes['desktop']!); |
| 87 | expectLaidOut(tester, 'chat with people'); |
| 88 | }); |
| 89 | |
| 90 | testWidgets('lays out with every compose banner showing', (tester) async { |
| 91 | core.demoUi(); |
| 92 | core.dispatch('reply.to:2'); |
| 93 | await layOut(tester, sizes['phone']!); |
| 94 | expectLaidOut(tester, 'chat replying'); |
| 95 | |
| 96 | core.demoUi(); |
| 97 | core.dispatch('edit.start:7'); |
| 98 | await layOut(tester, sizes['phone']!); |
| 99 | expectLaidOut(tester, 'chat editing'); |
| 100 | }); |
| 101 | |
| Three buttons that did nothing now do what they say 98d6135 nandi 20h ago | 102 | testWidgets('lays out with the emoji picker open', (tester) async { |
| 103 | // 120 emoji in a grid under a message, on a phone. |
| 104 | core.demoUi(); |
| 105 | core.dispatch('react.open:2'); |
| 106 | await layOut(tester, sizes['phone']!); |
| 107 | expectLaidOut(tester, 'chat with the picker'); |
| 108 | }); |
| 109 | |
| 110 | testWidgets('lays out with the picker showing a whole group', |
| 111 | (tester) async { |
| 112 | core.demoUi(); |
| 113 | core.dispatch('react.open:2'); |
| 114 | core.dispatch('emoji.group:Smileys & Emotion'); |
| 115 | await layOut(tester, sizes['cramped']!); |
| 116 | expectLaidOut(tester, 'chat with a full picker'); |
| 117 | }); |
| 118 | |
| 119 | testWidgets('lays out with the overview open', (tester) async { |
| 120 | core.demoUi(); |
| 121 | core.dispatch('overview.toggle'); |
| 122 | await layOut(tester, sizes['phone']!); |
| 123 | expectLaidOut(tester, 'chat with the overview'); |
| 124 | }); |
| 125 | |
| 126 | testWidgets('lays out with the lightbox open', (tester) async { |
| 127 | core.demoUi(); |
| 128 | core.dispatch('lightbox:https://example.com/a.png'); |
| 129 | await layOut(tester, sizes['phone']!); |
| 130 | expectLaidOut(tester, 'chat with the lightbox'); |
| 131 | }); |
| 132 | |
| A face that opens someone 9bb81a1 nandi 20h ago | 133 | testWidgets('lays out with a profile open', (tester) async { |
| 134 | core.demoUi(); |
| 135 | // A guest: no identity to fetch, so the panel says so rather than |
| 136 | // spinning — and it lays out without a network. |
| 137 | core.dispatch('profile.open:alice:'); |
| 138 | await layOut(tester, sizes['phone']!); |
| 139 | expectLaidOut(tester, 'chat with a guest profile'); |
| 140 | }); |
| 141 | |
| Lay every screen out in a test, and fix what that found 36bdfc5 nandi yesterday | 142 | testWidgets('lays out when scrolled off the present', (tester) async { |
| 143 | core.demoUi(); |
| 144 | core.dispatch('jump.present'); |
| 145 | await layOut(tester, sizes['phone']!); |
| 146 | expectLaidOut(tester, 'chat jumping'); |
| 147 | }); |
| 148 | }); |
| 149 | |
| 150 | group('the chats list', () { |
| 151 | for (final entry in sizes.entries) { |
| 152 | testWidgets('lays out at ${entry.key}', (tester) async { |
| 153 | core.demoUi(); |
| 154 | core.dispatch('screen.chats'); |
| 155 | await layOut(tester, entry.value); |
| 156 | expectLaidOut(tester, 'chats at ${entry.key}'); |
| 157 | }); |
| 158 | } |
| 159 | |
| 160 | testWidgets('lays out with a search term in the box', (tester) async { |
| 161 | core.demoUi(); |
| 162 | core.dispatch('screen.chats'); |
| 163 | core.dispatch('search.change', 'te'); |
| 164 | await layOut(tester, sizes['phone']!); |
| 165 | expectLaidOut(tester, 'chats searching'); |
| 166 | }); |
| 167 | }); |
| 168 | |
| 169 | group('discover and settings', () { |
| 170 | for (final screen in ['discover', 'settings']) { |
| 171 | for (final entry in sizes.entries) { |
| 172 | testWidgets('$screen lays out at ${entry.key}', (tester) async { |
| 173 | core.demoUi(); |
| 174 | core.dispatch('screen.$screen'); |
| 175 | await layOut(tester, entry.value); |
| 176 | expectLaidOut(tester, '$screen at ${entry.key}'); |
| 177 | }); |
| 178 | } |
| 179 | } |
| 180 | }); |
| 181 | } |