import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:doormile_cx/ui/widgets/states.dart'; /// ───────────────────────────────────────────────────────────────────────── /// A SHEET DOES NOT CHANGE SIZE WHILE IT IS OPENING /// /// Tapping BOOK NOW opened a tall sheet that snapped shorter a few frames /// later. Nothing was wrong with either height — the sheet was simply as tall /// as whatever [DmAsyncList] happened to be rendering, and that changes twice /// before the customer has done anything: four skeleton rows at 302pt, then /// the states, which are nearer 200. /// /// The modal is still animating up while that swap happens, so the resize is /// not a subtle reflow. It is the first thing the customer sees. /// /// Both halves of the fix are held here: the box the list lives in keeps one /// height across every state the list can be in, and a list handed cached /// items renders them on the first frame instead of a skeleton over data it /// already has. /// ───────────────────────────────────────────────────────────────────────── void main() { Widget host(Widget child) => MaterialApp( home: Scaffold( body: Center( child: SizedBox(width: 360, child: child), ), ), ); /// A load that never finishes, so the widget stays in its loading state. Future> never({bool refresh = false}) => Completer>().future; group('a fixed box holds its height', () { testWidgets('loading and loaded measure the same', (tester) async { const box = 300.0; await tester.pumpWidget( host( SizedBox( height: box, child: DmAsyncList( skeletonRows: 3, load: never, emptyTitle: 'none', emptyMessage: 'none', builder: (_, items) => Column(children: [for (final i in items) Text(i)]), ), ), ), ); await tester.pump(); final loading = tester.getSize(find.byType(DmAsyncList)); await tester.pumpWidget( host( SizedBox( height: box, child: DmAsyncList( skeletonRows: 3, initialItems: const ['Tamil Nadu'], load: never, emptyTitle: 'none', emptyMessage: 'none', builder: (_, items) => Column(children: [for (final i in items) Text(i)]), ), ), ), ); await tester.pump(); final loaded = tester.getSize(find.byType(DmAsyncList)); expect(loaded, loading, reason: 'the sheet would resize as it opened'); expect(loading.height, box); }); testWidgets('three skeleton rows fit the smallest box the clamp allows', ( tester, ) async { // The destination sheet floors its list box at 240. Four rows are 302 and // overflowed it, which is why that sheet asks for three. await tester.pumpWidget( host(const SizedBox(height: 240, child: DmSkeleton(rows: 3))), ); await tester.pump(); expect(tester.takeException(), isNull); }); }); group('cached items skip the skeleton', () { testWidgets('the first frame is the content, not a shimmer', ( tester, ) async { await tester.pumpWidget( host( SizedBox( height: 300, child: DmAsyncList( initialItems: const ['Kerala'], load: never, emptyTitle: 'none', emptyMessage: 'none', builder: (_, items) => Column(children: [for (final i in items) Text(i)]), ), ), ), ); // No settle, no extra pump: this is the frame the sheet opens on. expect(find.text('Kerala'), findsOneWidget); expect(find.byType(DmSkeleton), findsNothing); }); testWidgets('without them it still loads honestly', (tester) async { await tester.pumpWidget( host( SizedBox( height: 300, child: DmAsyncList( // Three for the same reason the destination sheet asks for // three: the default of four is 302pt and does not fit here. skeletonRows: 3, load: never, emptyTitle: 'none', emptyMessage: 'none', builder: (_, items) => Column(children: [for (final i in items) Text(i)]), ), ), ), ); await tester.pump(); expect(find.byType(DmSkeleton), findsOneWidget); }); }); }