| Lay every screen out in a test, and fix what that found 36bdfc5 nandi 19h ago | 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 |
| 14 | import 'package:flutter/material.dart'; |
| 15 | import 'package:flutter_test/flutter_test.dart'; |
| 16 | import 'package:frq_core/frq_core.dart' as core; |
| 17 | import 'package:cljd_flutter/nim_renderer.dart'; |
| 18 | |
| 19 | /// Phone, small desktop, and a deliberately cramped one. The head row of the |
| 20 | /// chat screen asks for more than 360 points has, which is why it wraps. |
| 21 | const sizes = <String, Size>{ |
| 22 | 'phone': Size(360, 690), |
| 23 | 'desktop': Size(1280, 800), |
| 24 | 'cramped': Size(300, 500), |
| 25 | }; |
| 26 | |
| 27 | Future<void> layOut(WidgetTester tester, Size size) async { |
| 28 | await tester.binding.setSurfaceSize(size); |
| 29 | addTearDown(() => tester.binding.setSurfaceSize(null)); |
| 30 | await tester.pumpWidget(const NimApp()); |
| 31 | await tester.pump(); |
| 32 | } |
| 33 | |
| 34 | /// Nothing went to FlutterError while that frame was built. |
| 35 | void expectLaidOut(WidgetTester tester, String what) { |
| 36 | final e = tester.takeException(); |
| 37 | expect(e, isNull, reason: '$what reported: $e'); |
| 38 | } |
| 39 | |
| 40 | void main() { |
| 41 | // No offline guard needed: `demoUi` sets the state directly and none of |
| 42 | // these dispatch `connect`, so nothing here opens a socket. |
| 43 | |
| 44 | group('the connect screen', () { |
| 45 | for (final entry in sizes.entries) { |
| 46 | testWidgets('lays out at ${entry.key}', (tester) async { |
| 47 | core.resetUi(); |
| 48 | await layOut(tester, entry.value); |
| 49 | expectLaidOut(tester, 'connect at ${entry.key}'); |
| 50 | }); |
| 51 | } |
| 52 | |
| 53 | testWidgets('lays out in every auth mode', (tester) async { |
| 54 | for (final mode in ['guest', 'bluesky', 'app-password']) { |
| 55 | core.resetUi(); |
| 56 | core.dispatch('mode.$mode'); |
| 57 | await layOut(tester, sizes['phone']!); |
| 58 | expectLaidOut(tester, 'connect in $mode'); |
| 59 | } |
| 60 | }); |
| 61 | }); |
| 62 | |
| 63 | group('the chat screen', () { |
| 64 | // The one that was never laid out by anything automated. |
| 65 | for (final entry in sizes.entries) { |
| 66 | testWidgets('lays out at ${entry.key}', (tester) async { |
| 67 | core.demoUi(); |
| 68 | await layOut(tester, entry.value); |
| 69 | expectLaidOut(tester, 'chat at ${entry.key}'); |
| 70 | }); |
| 71 | } |
| 72 | |
| 73 | testWidgets('renders the conversation it was given', (tester) async { |
| 74 | core.demoUi(); |
| 75 | await layOut(tester, sizes['desktop']!); |
| 76 | expect(find.text('hello there'), findsOneWidget); |
| 77 | expect(find.text('#test'), findsWidgets); |
| 78 | expectLaidOut(tester, 'chat content'); |
| 79 | }); |
| 80 | |
| 81 | testWidgets('lays out with the people panel up', (tester) async { |
| 82 | core.demoUi(); |
| 83 | core.dispatch('users.toggle'); |
| 84 | await layOut(tester, sizes['desktop']!); |
| 85 | expectLaidOut(tester, 'chat with people'); |
| 86 | }); |
| 87 | |
| 88 | testWidgets('lays out with every compose banner showing', (tester) async { |
| 89 | core.demoUi(); |
| 90 | core.dispatch('reply.to:2'); |
| 91 | await layOut(tester, sizes['phone']!); |
| 92 | expectLaidOut(tester, 'chat replying'); |
| 93 | |
| 94 | core.demoUi(); |
| 95 | core.dispatch('edit.start:7'); |
| 96 | await layOut(tester, sizes['phone']!); |
| 97 | expectLaidOut(tester, 'chat editing'); |
| 98 | }); |
| 99 | |
| 100 | testWidgets('lays out when scrolled off the present', (tester) async { |
| 101 | core.demoUi(); |
| 102 | core.dispatch('jump.present'); |
| 103 | await layOut(tester, sizes['phone']!); |
| 104 | expectLaidOut(tester, 'chat jumping'); |
| 105 | }); |
| 106 | }); |
| 107 | |
| 108 | group('the chats list', () { |
| 109 | for (final entry in sizes.entries) { |
| 110 | testWidgets('lays out at ${entry.key}', (tester) async { |
| 111 | core.demoUi(); |
| 112 | core.dispatch('screen.chats'); |
| 113 | await layOut(tester, entry.value); |
| 114 | expectLaidOut(tester, 'chats at ${entry.key}'); |
| 115 | }); |
| 116 | } |
| 117 | |
| 118 | testWidgets('lays out with a search term in the box', (tester) async { |
| 119 | core.demoUi(); |
| 120 | core.dispatch('screen.chats'); |
| 121 | core.dispatch('search.change', 'te'); |
| 122 | await layOut(tester, sizes['phone']!); |
| 123 | expectLaidOut(tester, 'chats searching'); |
| 124 | }); |
| 125 | }); |
| 126 | |
| 127 | group('discover and settings', () { |
| 128 | for (final screen in ['discover', 'settings']) { |
| 129 | for (final entry in sizes.entries) { |
| 130 | testWidgets('$screen lays out at ${entry.key}', (tester) async { |
| 131 | core.demoUi(); |
| 132 | core.dispatch('screen.$screen'); |
| 133 | await layOut(tester, entry.value); |
| 134 | expectLaidOut(tester, '$screen at ${entry.key}'); |
| 135 | }); |
| 136 | } |
| 137 | } |
| 138 | }); |
| 139 | } |