From 89154b32c05886a6dc36244a303839959b0b4067 Mon Sep 17 00:00:00 2001 From: Martijn van Dijk Date: Wed, 21 Aug 2024 04:14:24 +0200 Subject: [PATCH] [flutter_adaptive_scaffold] Fix landscape not showing in andUp (#7425) *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* *List which issues are fixed by this PR. You must list at least one issue.* https://github.com/flutter/flutter/issues/153496 --- .../flutter_adaptive_scaffold/CHANGELOG.md | 4 + .../lib/src/breakpoints.dart | 133 +++--- .../lib/src/slot_layout.dart | 25 +- .../flutter_adaptive_scaffold/pubspec.yaml | 2 +- .../test/breakpoint_test.dart | 410 ++++++++++++++++++ .../test/simulated_layout.dart | 48 +- 6 files changed, 529 insertions(+), 93 deletions(-) diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md index 8b4782b0d3450..9646801d53364 100644 --- a/packages/flutter_adaptive_scaffold/CHANGELOG.md +++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.2 + +* Fix a bug where landscape would not show body when using `andUp`. + ## 0.2.1 * Add `Breakpoint.activeBreakpointOf(context)` to find the currently active breakpoint. diff --git a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart index 286a49811f100..c752222746f88 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/breakpoints.dart @@ -17,7 +17,7 @@ class Breakpoints { /// case that no other breakpoint is active. /// /// It is active from a width of -1 dp to infinity. - static const Breakpoint standard = Breakpoint(beginWidth: -1); + static const Breakpoint standard = Breakpoint.standard(); /// A window whose width is less than 600 dp and greater than 0 dp. static const Breakpoint small = Breakpoint.small(); @@ -86,6 +86,30 @@ class Breakpoints { /// A mobile window whose width is greater than 1600 dp. static const Breakpoint extraLargeMobile = Breakpoint.extraLarge(platform: Breakpoint.mobile); + + /// A list of all the standard breakpoints. + static const List all = [ + smallDesktop, + smallMobile, + small, + mediumDesktop, + mediumMobile, + medium, + mediumLargeDesktop, + mediumLargeMobile, + mediumLarge, + largeDesktop, + largeMobile, + large, + extraLargeDesktop, + extraLargeMobile, + extraLarge, + smallAndUp, + mediumAndUp, + mediumLargeAndUp, + largeAndUp, + standard, + ]; } /// A class to define the conditions that distinguish between types of @@ -113,10 +137,19 @@ class Breakpoint { this.endWidth, this.beginHeight, this.endHeight, - this.platform, this.andUp = false, + this.platform, }); + /// Returns a [Breakpoint] that can be used as a fallthrough in the + /// case that no other breakpoint is active. + const Breakpoint.standard({this.platform}) + : beginWidth = -1, + endWidth = null, + beginHeight = null, + endHeight = null, + andUp = true; + /// Returns a [Breakpoint] with the given constraints for a small screen. const Breakpoint.small({this.andUp = false, this.platform}) : beginWidth = 0, @@ -166,7 +199,7 @@ class Breakpoint { TargetPlatform.iOS, }; - /// When set to true, it will include any size above the set width. + /// When set to true, it will include any size above the set width and set height. final bool andUp; /// The beginning width dp value. If left null then the [Breakpoint] will have @@ -213,9 +246,9 @@ class Breakpoint { final bool isHeightActive = isDesktop || orientation == Orientation.portrait || - (orientation == Orientation.landscape && - height >= lowerBoundHeight && - height < upperBoundHeight); + (orientation == Orientation.landscape && andUp + ? isWidthActive || height >= lowerBoundHeight + : height >= lowerBoundHeight && height < upperBoundHeight); return isWidthActive && isHeightActive && isRightPlatform; } @@ -225,73 +258,15 @@ class Breakpoint { static Breakpoint? maybeActiveBreakpointFromSlotLayout(BuildContext context) { final SlotLayout? slotLayout = context.findAncestorWidgetOfExactType(); - Breakpoint? fallbackBreakpoint; - - if (slotLayout != null) { - for (final MapEntry config - in slotLayout.config.entries) { - if (config.key.isActive(context)) { - if (config.key.platform != null) { - return config.key; - } else { - fallbackBreakpoint ??= config.key; - } - } - } - } - return fallbackBreakpoint; + + return slotLayout != null + ? activeBreakpointIn(context, slotLayout.config.keys.toList()) + : null; } /// Returns the default [Breakpoint] based on the [BuildContext]. static Breakpoint defaultBreakpointOf(BuildContext context) { - final TargetPlatform host = Theme.of(context).platform; - final bool isDesktop = Breakpoint.desktop.contains(host); - final bool isMobile = Breakpoint.mobile.contains(host); - - for (final Breakpoint breakpoint in [ - Breakpoints.small, - Breakpoints.medium, - Breakpoints.mediumLarge, - Breakpoints.large, - Breakpoints.extraLarge, - ]) { - if (breakpoint.isActive(context)) { - if (isDesktop) { - switch (breakpoint) { - case Breakpoints.small: - return Breakpoints.smallDesktop; - case Breakpoints.medium: - return Breakpoints.mediumDesktop; - case Breakpoints.mediumLarge: - return Breakpoints.mediumLargeDesktop; - case Breakpoints.large: - return Breakpoints.largeDesktop; - case Breakpoints.extraLarge: - return Breakpoints.extraLargeDesktop; - default: - return Breakpoints.standard; - } - } else if (isMobile) { - switch (breakpoint) { - case Breakpoints.small: - return Breakpoints.smallMobile; - case Breakpoints.medium: - return Breakpoints.mediumMobile; - case Breakpoints.mediumLarge: - return Breakpoints.mediumLargeMobile; - case Breakpoints.large: - return Breakpoints.largeMobile; - case Breakpoints.extraLarge: - return Breakpoints.extraLargeMobile; - default: - return Breakpoints.standard; - } - } else { - return breakpoint; - } - } - } - return Breakpoints.standard; + return activeBreakpointIn(context, Breakpoints.all) ?? Breakpoints.standard; } /// Returns the currently active [Breakpoint]. @@ -299,4 +274,24 @@ class Breakpoint { return maybeActiveBreakpointFromSlotLayout(context) ?? defaultBreakpointOf(context); } + + /// Returns the currently active [Breakpoint] based on the [BuildContext] and + /// a list of [Breakpoint]s. + static Breakpoint? activeBreakpointIn( + BuildContext context, List breakpoints) { + Breakpoint? currentBreakpoint; + + for (final Breakpoint breakpoint in breakpoints) { + if (breakpoint.isActive(context)) { + if (breakpoint.platform != null) { + // Prioritize platform-specific breakpoints. + return breakpoint; + } else { + // Fallback to non-platform-specific. + currentBreakpoint = breakpoint; + } + } + } + return currentBreakpoint; + } } diff --git a/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart b/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart index 07cfea33a9d60..e2909792c273e 100644 --- a/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart +++ b/packages/flutter_adaptive_scaffold/lib/src/slot_layout.dart @@ -19,26 +19,11 @@ class SlotLayout extends StatefulWidget { /// be chosen from the config under the context's conditions. static SlotLayoutConfig? pickWidget( BuildContext context, Map config) { - SlotLayoutConfig? chosenWidget; - - for (final Breakpoint breakpoint in config.keys) { - if (breakpoint.isActive(context)) { - final SlotLayoutConfig? pickedWidget = config[breakpoint]; - if (pickedWidget != null) { - if (breakpoint.platform != null) { - // Prioritize platform-specific breakpoints. - return pickedWidget; - } else { - // Fallback to non-platform-specific. - chosenWidget = pickedWidget; - } - } else { - chosenWidget = null; - } - } - } - - return chosenWidget; + final Breakpoint? breakpoint = + Breakpoint.activeBreakpointIn(context, config.keys.toList()); + return breakpoint != null && config.containsKey(breakpoint) + ? config[breakpoint] + : null; } /// Maps [Breakpoint]s to [SlotLayoutConfig]s to determine what Widget to diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml index a4b7215af9859..ad18e0cf9cc95 100644 --- a/packages/flutter_adaptive_scaffold/pubspec.yaml +++ b/packages/flutter_adaptive_scaffold/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_adaptive_scaffold description: Widgets to easily build adaptive layouts, including navigation elements. -version: 0.2.1 +version: 0.2.2 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22 repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold diff --git a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart index cbef74c570ee1..88a8569136c69 100644 --- a/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart +++ b/packages/flutter_adaptive_scaffold/test/breakpoint_test.dart @@ -264,6 +264,416 @@ void main() { Breakpoints.largeDesktop); }, variant: TargetPlatformVariant.desktop()); }); + + group('Landscape Layout Tests', () { + testWidgets('Desktop breakpoints do not show on mobile device (landscape)', + (WidgetTester tester) async { + // Small landscape layout on a mobile device. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.smallDesktop')), findsNothing); + + // Medium landscape layout on a mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumDesktop')), findsNothing); + + // MediumLarge landscape layout on a mobile. + await tester + .pumpWidget(SimulatedLayout.mediumLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumLargeMobile')), + findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumLargeDesktop')), + findsNothing); + + // Large landscape layout on a mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.largeDesktop')), findsNothing); + + // ExtraLarge landscape layout on a mobile. + await tester.pumpWidget(SimulatedLayout.extraLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.extraLargeMobile')), + findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.extraLargeDesktop')), findsNothing); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets('Mobile breakpoints do not show on desktop device (landscape)', + (WidgetTester tester) async { + // Small landscape layout on a desktop device. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallDesktop')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsNothing); + + // Medium landscape layout on a desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + find.byKey(const Key('Breakpoints.mediumDesktop')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + + // MediumLarge landscape layout on a desktop. + await tester + .pumpWidget(SimulatedLayout.mediumLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumLargeDesktop')), + findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.mediumLargeMobile')), findsNothing); + + // Large landscape layout on a desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.largeDesktop')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); + + await tester.pumpWidget(SimulatedLayout.extraLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.extraLargeDesktop')), + findsOneWidget); + expect( + find.byKey(const Key('Breakpoints.extraLargeMobile')), findsNothing); + }, variant: TargetPlatformVariant.desktop()); + + // Additional landscape tests for `maybeActiveBreakpointFromSlotLayout`. + testWidgets( + 'maybeActiveBreakpointFromSlotLayout returns correct breakpoint on mobile (landscape)', + (WidgetTester tester) async { + // Small landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout( + tester.element(find.byKey(const Key('Breakpoints.smallMobile')))), + Breakpoints.smallMobile); + + // Medium landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.mediumMobile')))), + Breakpoints.mediumMobile); + + // Large landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout( + tester.element(find.byKey(const Key('Breakpoints.largeMobile')))), + Breakpoints.largeMobile); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets( + 'maybeActiveBreakpointFromSlotLayout returns correct breakpoint on desktop (landscape)', + (WidgetTester tester) async { + // Small landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.smallDesktop')))), + Breakpoints.smallDesktop); + + // Medium landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.mediumDesktop')))), + Breakpoints.mediumDesktop); + + // Large landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.maybeActiveBreakpointFromSlotLayout(tester + .element(find.byKey(const Key('Breakpoints.largeDesktop')))), + Breakpoints.largeDesktop); + }, variant: TargetPlatformVariant.desktop()); + + // Additional landscape tests for `defaultBreakpointOf`. + testWidgets( + 'defaultBreakpointOf returns correct default breakpoint on mobile (landscape)', + (WidgetTester tester) async { + // Small landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))), + Breakpoints.smallMobile); + + // Medium landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))), + Breakpoints.mediumMobile); + + // Large landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(Breakpoint.defaultBreakpointOf(tester.element(find.byType(Theme))), + Breakpoints.largeMobile); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets( + 'defaultBreakpointOf returns correct default breakpoint on desktop (landscape)', + (WidgetTester tester) async { + // Small landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.defaultBreakpointOf( + tester.element(find.byType(Directionality))), + Breakpoints.smallDesktop); + + // Medium landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.defaultBreakpointOf( + tester.element(find.byType(Directionality))), + Breakpoints.mediumDesktop); + + // Large landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.defaultBreakpointOf( + tester.element(find.byType(Directionality))), + Breakpoints.largeDesktop); + }, variant: TargetPlatformVariant.desktop()); + + // Additional landscape tests for `activeBreakpointOf`. + testWidgets( + 'activeBreakpointOf returns correct active breakpoint on mobile (landscape)', + (WidgetTester tester) async { + // Small landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf( + tester.element(find.byKey(const Key('Breakpoints.smallMobile')))), + Breakpoints.smallMobile); + + // Medium landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.mediumMobile')))), + Breakpoints.mediumMobile); + + // Large landscape layout on mobile. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf( + tester.element(find.byKey(const Key('Breakpoints.largeMobile')))), + Breakpoints.largeMobile); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets( + 'activeBreakpointOf returns correct active breakpoint on desktop (landscape)', + (WidgetTester tester) async { + // Small landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.smallDesktop')))), + Breakpoints.smallDesktop); + + // Medium landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.mediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.mediumDesktop')))), + Breakpoints.mediumDesktop); + + // Large landscape layout on desktop. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect( + Breakpoint.activeBreakpointOf(tester + .element(find.byKey(const Key('Breakpoints.largeDesktop')))), + Breakpoints.largeDesktop); + }, variant: TargetPlatformVariant.desktop()); + }); + + group('Portrait and Landscape Mixed Layout Tests', () { + testWidgets( + 'Layout for smallPortraitMediumLandscape shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + }); + + testWidgets( + 'Layout for smallLandscapeMediumPortrait shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumPortrait.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsNothing); + expect(find.byKey(const Key('Breakpoints.mediumMobile')), findsNothing); + }); + + testWidgets( + 'Layout for smallPortraitMediumLargeLandscape shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLargeLandscape.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); + }); + + testWidgets( + 'Layout for smallLandscapeMediumLargePortrait shows correct slot configuration', + (WidgetTester tester) async { + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumLargePortrait.slot(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.smallMobile')), findsNothing); + expect(find.byKey(const Key('Breakpoints.largeMobile')), findsNothing); + }); + }); + + group('Slot And Up Layout Tests', () { + testWidgets('slotAndUp shows correct slot for small layout', + (WidgetTester tester) async { + // Small layout should only show the small slot. + await tester.pumpWidget(SimulatedLayout.small.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets('slotAndUp shows correct slot for medium layout and up', + (WidgetTester tester) async { + // Medium layout should show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.medium.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // MediumLarge layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.mediumLarge.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // Large layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.large.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // ExtraLarge layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.extraLarge.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + }); + + testWidgets('slotAndUp shows correct slot for small landscape layout', + (WidgetTester tester) async { + // Small landscape layout should only show the small slot. + await tester.pumpWidget(SimulatedLayout.smallLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets( + 'slotAndUp shows correct slot for medium and larger landscape layouts', + (WidgetTester tester) async { + // Medium landscape layout should show the mediumAndUp slot. + await tester + .pumpWidget(SimulatedLayout.mediumLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // MediumLarge landscape layout should also show the mediumAndUp slot. + await tester + .pumpWidget(SimulatedLayout.mediumLargeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // Large landscape layout should also show the mediumAndUp slot. + await tester.pumpWidget(SimulatedLayout.largeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + + // ExtraLarge landscape layout should also show the mediumAndUp slot. + await tester + .pumpWidget(SimulatedLayout.extraLargeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + }); + }); + + group('Slot And Up Layout Tests with Portrait and Landscape Mixed Layout', + () { + testWidgets( + 'slotAndUp shows correct slot for smallPortraitMediumLandscape layout', + (WidgetTester tester) async { + // smallPortraitMediumLandscape layout should only show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets( + 'slotAndUp shows correct slot for smallLandscapeMediumPortrait layout', + (WidgetTester tester) async { + // smallLandscapeMediumPortrait layout should show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumPortrait.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + }); + + testWidgets( + 'slotAndUp shows correct slot for smallPortraitMediumLargeLandscape layout', + (WidgetTester tester) async { + // smallPortraitMediumLargeLandscape layout should show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallPortraitMediumLargeLandscape.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsOneWidget); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsNothing); + }); + + testWidgets( + 'slotAndUp shows correct slot for smallLandscapeMediumLargePortrait layout', + (WidgetTester tester) async { + // smallLandscapeMediumLargePortrait layout should show the small slot. + await tester.pumpWidget( + SimulatedLayout.smallLandscapeMediumLargePortrait.slotAndUp(tester)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('Breakpoints.small')), findsNothing); + expect(find.byKey(const Key('Breakpoints.mediumAndUp')), findsOneWidget); + }); + }); } class DummyWidget extends StatelessWidget { diff --git a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart index 8c8b6ce525181..a72014192283a 100644 --- a/packages/flutter_adaptive_scaffold/test/simulated_layout.dart +++ b/packages/flutter_adaptive_scaffold/test/simulated_layout.dart @@ -108,15 +108,32 @@ enum SimulatedLayout { medium(width: 800, navSlotKey: 'primaryNavigation'), mediumLarge(width: 1000, navSlotKey: 'primaryNavigation1'), large(width: 1200, navSlotKey: 'primaryNavigation2'), - extraLarge(width: 1600, navSlotKey: 'primaryNavigation3'); + extraLarge(width: 1600, navSlotKey: 'primaryNavigation3'), + smallLandscape(width: 500, height: 400, navSlotKey: 'bottomNavigation'), + mediumLandscape(width: 800, height: 600, navSlotKey: 'primaryNavigation'), + mediumLargeLandscape( + width: 1100, height: 900, navSlotKey: 'primaryNavigation1'), + largeLandscape(width: 1400, height: 1000, navSlotKey: 'primaryNavigation2'), + extraLargeLandscape( + width: 1700, height: 1000, navSlotKey: 'primaryNavigation3'), + smallPortraitMediumLandscape( + width: 360, height: 650, navSlotKey: 'bottomNavigation'), + smallLandscapeMediumPortrait( + width: 650, height: 360, navSlotKey: 'bottomNavigation'), + smallPortraitMediumLargeLandscape( + width: 360, height: 900, navSlotKey: 'bottomNavigation'), + smallLandscapeMediumLargePortrait( + width: 900, height: 360, navSlotKey: 'primaryNavigation'); const SimulatedLayout({ required double width, + double height = 2000, required this.navSlotKey, - }) : _width = width; + }) : _width = width, + _height = height; final double _width; - final double _height = 2000; + final double _height; final String navSlotKey; static const Color navigationRailThemeBgColor = Colors.white; @@ -234,4 +251,29 @@ enum SimulatedLayout { ), ); } + + MediaQuery slotAndUp(WidgetTester tester) { + return MediaQuery( + data: MediaQueryData.fromView(tester.view) + .copyWith(size: Size(_width, _height)), + child: Theme( + data: ThemeData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('Breakpoints.small'), + builder: (BuildContext context) => Container(), + ), + Breakpoints.mediumAndUp: SlotLayout.from( + key: const Key('Breakpoints.mediumAndUp'), + builder: (BuildContext context) => Container(), + ), + }, + ), + ), + ), + ); + } }