شائع شدہ سورس
سورس ZIP ڈاؤن لوڈ کریں
CH-J SM Mobile
ایپلیکیشن کی کسی مخصوص ریلیز کی ڈائریکٹریز اور فائلیں دیکھیں۔
CH-J / SM Mobile
175 فائلیں
sm-mobile-20261016T185235Z-8e93ecc1b59a
شامل کیا گیا: · بلڈ کیا گیا:
1
import 'dart:math' as math;2
import 'dart:ui' show DisplayFeature, DisplayFeatureState, DisplayFeatureType;3
import 'package:flutter/material.dart';4
import 'package:flutter_test/flutter_test.dart';5
import 'package:sm_mobile/core/app_controller.dart';6
import 'package:sm_mobile/main.dart';7
import 'package:sm_mobile/ui/adaptive.dart';8
import 'package:sm_mobile/ui/diagnostics_page.dart';9
import 'package:sm_mobile/ui/servers_page.dart';10
import 'support.dart';12
// Breakpoints and the tested range. The range is what the tests sweep, not a13
// limit of supported devices.14
const breakpoints = [15
WindowClass.mediumMin,16
WindowClass.expandedMin,17
WindowClass.largeMin,18
WindowClass.extraLargeMin,19
];20
const minWidth = 240.0,21
maxWidth = 2560.0,22
minHeight = 320.0,23
maxHeight = 3000.0;25
/// Widths: range ends, an even spread, and every breakpoint -1/-0.5/0/+0.5/+1.26
List<double> generatedWidths({int steps = 12}) => <double>{27
minWidth,28
maxWidth,29
for (var i = 0; i <= steps; i++)30
(minWidth + (maxWidth - minWidth) * i / steps).roundToDouble(),31
for (final b in breakpoints) ...[b - 1, b - .5, b, b + .5, b + 1],32
}.toList()..sort();34
List<double> generatedHeights({int steps = 8}) => <double>{35
minHeight,36
maxHeight,37
479,38
480,39
481,40
for (var i = 0; i <= steps; i++)41
(minHeight + (maxHeight - minHeight) * i / steps).roundToDouble(),42
}.toList()..sort();44
const ratios = [1.0, 1.5, 2.0, 2.625, 3.0, 3.5];45
const textScales = [1.0, 1.3, 2.0];47
/// Safe areas in logical pixels: none, portrait notch, landscape notch,48
/// and a status area beside the content.49
const paddings = [50
EdgeInsets.zero,51
EdgeInsets.only(top: 59, bottom: 34),52
EdgeInsets.only(left: 59, right: 59, bottom: 21),53
EdgeInsets.only(right: 44),54
];56
class Shape {57
final Size size;58
final double ratio, text;59
final EdgeInsets padding;60
final EdgeInsets keyboard;61
final List<DisplayFeature> features;62
const Shape(63
this.size, {64
this.ratio = 3,65
this.text = 1,66
this.padding = EdgeInsets.zero,67
this.keyboard = EdgeInsets.zero,68
this.features = const [],69
});70
@override71
String toString() =>72
'${size.width}x${size.height}dp @${ratio}x text${text}x '73
'pad$padding kb${keyboard.bottom} features${features.length}';74
}76
/// Every width × height combination, cycling ratio, text size and safe area77
/// deterministically so that each value meets many window shapes.78
List<Shape> generatedShapes() {79
final out = <Shape>[];80
var n = 0;81
for (final w in generatedWidths()) {82
for (final h in generatedHeights()) {83
out.add(84
Shape(85
Size(w, h),86
ratio: ratios[n % ratios.length],87
text: textScales[(n ~/ 2) % textScales.length],88
padding: paddings[(n ~/ 3) % paddings.length],89
),90
);91
n++;92
}93
}94
return out;95
}97
FakeViewPadding fake(EdgeInsets e, double ratio) => FakeViewPadding(98
left: e.left * ratio,99
top: e.top * ratio,100
right: e.right * ratio,101
bottom: e.bottom * ratio,102
);104
Future<void> apply(WidgetTester tester, Shape s) async {105
final v = tester.view;106
v.devicePixelRatio = s.ratio;107
// Physical pixels = logical size × devicePixelRatio. Layout only ever sees108
// the logical size.109
v.physicalSize = Size(s.size.width * s.ratio, s.size.height * s.ratio);110
v.padding = fake(s.padding, s.ratio);111
v.viewPadding = fake(s.padding, s.ratio);112
v.viewInsets = fake(s.keyboard, s.ratio);113
v.displayFeatures = s.features;114
tester.platformDispatcher.textScaleFactorTestValue = s.text;115
await tester.pump();116
await tester.pump(const Duration(milliseconds: 50));117
}119
void reset(WidgetTester tester) {120
tester.view.reset();121
tester.platformDispatcher.clearTextScaleFactorTestValue();122
}124
Future<AppController> openWorkspace(WidgetTester tester) async {125
final c = AppController(MemoryStore());126
await tester.runAsync(() async {127
await c.initialize();128
await c.acceptLicense();129
await c.openVault('adaptive layout test password', create: true);130
});131
await tester.pumpWidget(SmMobileApp(c));132
await tester.pumpAndSettle();133
return c;134
}136
Future<void> closeWorkspace(WidgetTester tester, AppController c) async {137
await tester.pumpWidget(const SizedBox());138
c.dispose();139
}141
/// Opens a page through whatever navigation the current layout shows.142
Future<void> go(WidgetTester tester, IconData icon) async {143
final nav = find.byWidgetPredicate(144
(w) => w is NavigationRail || w is NavigationBar,145
);146
final direct = find.descendant(of: nav, matching: find.byIcon(icon));147
if (direct.evaluate().isNotEmpty) {148
await tester.tap(direct.first);149
} else if (icon == Icons.dns_outlined &&150
find.byType(NavigationRail).evaluate().isNotEmpty) {151
// Two panes: the server list is always on screen.152
expect(find.byType(ServersPage), findsOneWidget);153
return;154
} else {155
// The fourth bottom bar item opens the remaining destinations.156
await tester.tap(find.byType(NavigationDestination).last);157
await tester.pumpAndSettle();158
await tester.tap(159
find.descendant(of: find.byType(ListTile), matching: find.byIcon(icon)),160
);161
}162
await tester.pumpAndSettle();163
}165
const pageIcons = [166
Icons.dns_outlined,167
Icons.terminal,168
Icons.folder_outlined,169
Icons.monitor_heart_outlined,170
Icons.network_check,171
Icons.settings_outlined,172
];174
DisplayFeature crease(175
Rect bounds, {176
DisplayFeatureType type = DisplayFeatureType.fold,177
DisplayFeatureState state = DisplayFeatureState.postureHalfOpened,178
}) => DisplayFeature(bounds: bounds, type: type, state: state);180
void main() {181
group('window classes', () {182
test('every breakpoint just below, on and just above', () {183
expect(WindowClass.ofWidth(0), WindowClass.compact);184
final classes = WindowClass.values;185
for (var i = 0; i < breakpoints.length; i++) {186
final b = breakpoints[i];187
for (final d in [1.0, .5, .01]) {188
expect(WindowClass.ofWidth(b - d), classes[i], reason: '${b - d}');189
expect(190
WindowClass.ofWidth(b + d),191
classes[i + 1],192
reason: '${b + d}',193
);194
}195
expect(WindowClass.ofWidth(b), classes[i + 1], reason: '$b');196
}197
expect(WindowClass.ofWidth(100000), WindowClass.extraLarge);198
});200
test('class grows monotonically over the generated range', () {201
var last = WindowClass.compact;202
for (var w = 1.0; w <= 4000; w += 0.5) {203
final c = WindowClass.ofWidth(w);204
expect(c.index >= last.index, true, reason: '$w');205
last = c;206
}207
});209
test('class ignores devicePixelRatio: only logical size counts', () {210
for (final w in generatedWidths()) {211
final expected = WindowClass.ofWidth(w);212
for (final r in ratios) {213
// A physical width divided by its ratio is the same logical width.214
expect(WindowClass.ofWidth(w * r / r), expected);215
}216
}217
});219
test('navigation and pane rules', () {220
AdaptiveLayout l(221
double w,222
double h, {223
EdgeInsets? pad,224
List<DisplayFeature>? f,225
}) => AdaptiveLayout(226
Size(w, h),227
padding: pad ?? EdgeInsets.zero,228
features: f ?? const [],229
);230
expect(l(390, 844).useRail, false);231
expect(l(844, 390).useRail, true, reason: 'short landscape phone');232
expect(l(479, 320).useRail, false, reason: 'too narrow for a rail');233
expect(l(568, 320).useRail, true);234
expect(l(599, 900).useRail, false);235
expect(l(600, 900).useRail, true);236
expect(l(500, 900, pad: const EdgeInsets.only(right: 44)).useRail, true);237
expect(l(839, 1000).twoPane, false);238
expect(l(840, 1000).twoPane, true);239
// A flat fold does not separate; a half-opened fold or a hinge does.240
final flat = crease(241
const Rect.fromLTWH(420, 0, 0, 900),242
state: DisplayFeatureState.postureFlat,243
);244
expect(l(840, 900, f: [flat]).separatingFeature, null);245
final half = crease(const Rect.fromLTWH(300, 0, 0, 900));246
expect(l(600, 900, f: [half]).twoPane, true);247
final hinge = crease(248
const Rect.fromLTWH(290, 0, 20, 900),249
type: DisplayFeatureType.hinge,250
state: DisplayFeatureState.unknown,251
);252
expect(l(600, 900, f: [hinge]).separatingFeature, hinge);253
final cutout = crease(254
const Rect.fromLTWH(150, 0, 100, 30),255
type: DisplayFeatureType.cutout,256
state: DisplayFeatureState.unknown,257
);258
expect(l(400, 900, f: [cutout]).separatingFeature, null);259
final table = crease(const Rect.fromLTWH(0, 450, 840, 0));260
expect(l(840, 900, f: [table]).horizontalFold, true);261
expect(l(840, 900, f: [table]).useRail, false);262
});263
});265
testWidgets(266
'generated sizes 240–2560 × 320–3000 dp on every page, resized live',267
(tester) async {268
addTearDown(() => reset(tester));269
await apply(tester, const Shape(Size(700, 1000)));270
final c = await openWorkspace(tester);271
final shapes = generatedShapes();272
final failures = <String>[];273
var checked = 0;274
for (var page = 0; page < pageIcons.length; page++) {275
await apply(tester, const Shape(Size(700, 1000)));276
await go(tester, pageIcons[page]);277
// Each page meets the shapes in a different order, so it is resized278
// from many different previous shapes.279
final order = [...shapes]..shuffle(math.Random(page));280
for (final s in order) {281
await apply(tester, s);282
checked++;283
final error = tester.takeException();284
if (error != null) failures.add('page $page $s: $error');285
final layout = AdaptiveLayout(s.size, padding: s.padding);286
final canvas =287
s.size.width >= minimumCanvas.width &&288
s.size.height >= minimumCanvas.height;289
if (canvas) {290
final rail = find.byType(NavigationRail).evaluate().length;291
final bar = find.byType(NavigationBar).evaluate().length;292
if (layout.useRail != (rail == 1) || layout.useRail == (bar == 1)) {293
failures.add('page $page $s: navigation rail=$rail bar=$bar');294
}295
}296
}297
}298
expect(failures, isEmpty, reason: failures.take(20).join('\n'));299
expect(checked, greaterThan(1000));300
await closeWorkspace(tester, c);301
},302
);304
testWidgets('every destination is reachable at representative shapes', (305
tester,306
) async {307
addTearDown(() => reset(tester));308
await apply(tester, const Shape(Size(390, 844)));309
final c = await openWorkspace(tester);310
// Shapes below the minimum canvas are covered by their own test.311
for (final size in const [312
Size(320, 568),313
Size(360, 640),314
Size(375, 667),315
Size(390, 844),316
Size(430, 932),317
Size(600, 960),318
Size(768, 1024),319
Size(820, 1180),320
Size(1024, 1366),321
Size(1280, 800),322
Size(1440, 900),323
Size(240, 320),324
Size(2560, 1600),325
]) {326
for (final s in [327
size,328
size.flipped,329
].where((s) => s.height >= minimumCanvas.height)) {330
for (final text in [1.0, 2.0]) {331
await apply(tester, Shape(s, text: text));332
for (var page = 1; page < pageIcons.length; page++) {333
await go(tester, pageIcons[page]);334
expect(tester.takeException(), null, reason: '$s page $page');335
}336
}337
}338
}339
await closeWorkspace(tester, c);340
});342
testWidgets('below the minimum canvas the app scrolls instead of breaking', (343
tester,344
) async {345
addTearDown(() => reset(tester));346
await apply(tester, const Shape(Size(390, 844)));347
final c = await openWorkspace(tester);348
for (final s in const [349
Size(120, 160),350
Size(200, 300),351
Size(239, 319),352
Size(600, 200),353
Size(1, 1),354
]) {355
await apply(tester, Shape(s, text: 2));356
expect(tester.takeException(), null, reason: '$s');357
// Content is laid out on the minimum canvas and remains scrollable.358
final scaffold = tester.getSize(find.byType(Scaffold).first);359
expect(scaffold.width, greaterThanOrEqualTo(minimumCanvas.width));360
expect(scaffold.height, greaterThanOrEqualTo(minimumCanvas.height));361
}362
await apply(tester, const Shape(Size(390, 844)));363
expect(tester.getSize(find.byType(Scaffold).first), const Size(390, 844));364
await closeWorkspace(tester, c);365
});367
testWidgets('software keyboard over every page and large text', (368
tester,369
) async {370
addTearDown(() => reset(tester));371
await apply(tester, const Shape(Size(390, 844)));372
final c = await openWorkspace(tester);373
for (var page = 0; page < pageIcons.length; page++) {374
await apply(tester, const Shape(Size(390, 844)));375
await go(tester, pageIcons[page]);376
for (final s in const [377
Shape(Size(390, 844), keyboard: EdgeInsets.only(bottom: 336)),378
Shape(Size(320, 568), keyboard: EdgeInsets.only(bottom: 260), text: 2),379
Shape(Size(844, 390), keyboard: EdgeInsets.only(bottom: 200)),380
Shape(381
Size(568, 320),382
keyboard: EdgeInsets.only(bottom: 200),383
text: 1.3,384
),385
Shape(Size(1024, 768), keyboard: EdgeInsets.only(bottom: 400)),386
Shape(Size(768, 1024), keyboard: EdgeInsets.only(bottom: 320), text: 3),387
]) {388
await apply(tester, s);389
expect(tester.takeException(), null, reason: 'page $page $s');390
}391
}392
await closeWorkspace(tester, c);393
});395
testWidgets(396
'typed data and page state survive every class, rotation and fold',397
(tester) async {398
addTearDown(() => reset(tester));399
await apply(tester, const Shape(Size(390, 844)));400
final c = await openWorkspace(tester);401
// Unsaved input: diagnostics form, and the server search once the list402
// is shown next to it.403
await go(tester, Icons.network_check);404
await tester.enterText(405
find.byType(TextField).first,406
'draft.example.test',407
);408
final diagnostics = tester.state(find.byType(DiagnosticsPage));409
State? servers;410
final sequence = <Shape>[411
for (final w in generatedWidths(steps: 6)) Shape(Size(w, 900)),412
for (final w in generatedWidths(steps: 6).reversed) Shape(Size(900, w)),413
const Shape(Size(466, 678)),414
const Shape(Size(626, 890)),415
Shape(416
const Size(626, 890),417
features: [crease(const Rect.fromLTWH(313, 0, 0, 890))],418
),419
Shape(420
const Size(890, 626),421
features: [crease(const Rect.fromLTWH(0, 313, 890, 0))],422
),423
Shape(424
const Size(900, 700),425
features: [426
crease(427
const Rect.fromLTWH(440, 0, 20, 700),428
type: DisplayFeatureType.hinge,429
state: DisplayFeatureState.unknown,430
),431
],432
),433
const Shape(Size(390, 844)),434
];435
for (final s in sequence) {436
await apply(tester, s);437
await tester.pump();438
expect(tester.takeException(), null, reason: '$s');439
expect(440
tester.state(find.byType(DiagnosticsPage)),441
same(diagnostics),442
reason: '$s',443
);444
expect(find.text('draft.example.test'), findsOneWidget, reason: '$s');445
final list = find.byType(ServersPage);446
if (list.evaluate().isNotEmpty) {447
if (servers == null) {448
servers = tester.state(list);449
await tester.enterText(450
find.descendant(of: list, matching: find.byType(TextField)),451
'search-draft',452
);453
await tester.pump();454
}455
// The list may be rebuilt after a fold hid it; the typed search stays.456
expect(find.text('search-draft'), findsOneWidget, reason: '$s');457
}458
}459
expect(servers, isNotNull, reason: 'server list was shown in two panes');460
await closeWorkspace(tester, c);461
},462
);464
testWidgets('server list offers desktop-compatible import and export', (465
tester,466
) async {467
addTearDown(() => reset(tester));468
for (final size in const [469
Size(240, 568),470
Size(390, 844),471
Size(1280, 800),472
]) {473
await apply(tester, Shape(size));474
final c = await openWorkspace(tester);475
await tester.tap(find.byIcon(Icons.import_export));476
await tester.pumpAndSettle();477
expect(find.byIcon(Icons.file_open), findsOneWidget, reason: '$size');478
expect(find.byIcon(Icons.ios_share), findsOneWidget, reason: '$size');479
// Export with no profiles reports the problem instead of failing.480
await tester.tap(find.byIcon(Icons.ios_share));481
await tester.pumpAndSettle();482
expect(find.byType(SnackBar), findsOneWidget, reason: '$size');483
expect(tester.takeException(), null);484
await closeWorkspace(tester, c);485
}486
});488
testWidgets('a tap on empty space closes the software keyboard', (489
tester,490
) async {491
addTearDown(() => reset(tester));492
await apply(tester, const Shape(Size(390, 844)));493
final c = await openWorkspace(tester);494
await go(tester, Icons.network_check);495
await tester.tap(find.byType(TextField).first);496
await tester.pump();497
expect(FocusManager.instance.primaryFocus?.context?.widget, isNotNull);498
expect(tester.testTextInput.isVisible, true);499
// Non-interactive text in the diagnostics form.500
await tester.tapAt(501
tester.getCenter(find.textContaining('TCP ping measures').first),502
);503
await tester.pump();504
expect(tester.testTextInput.isVisible, false);505
await closeWorkspace(tester, c);506
});508
group('folds and hinges', () {509
Future<void> expectSides(510
WidgetTester tester,511
Rect fold, {512
required bool vertical,513
}) async {514
final list = tester.getRect(find.byType(ServersPage));515
final tool = tester.getRect(find.byType(DiagnosticsPage));516
expect(list.overlaps(fold.inflate(-.01)) && fold.width > 0, false);517
if (vertical) {518
expect(list.right, lessThanOrEqualTo(fold.left + .01));519
expect(tool.left, greaterThanOrEqualTo(fold.right - .01));520
} else {521
expect(tool.bottom, lessThanOrEqualTo(fold.top + .01));522
expect(list.top, greaterThanOrEqualTo(fold.bottom - .01));523
}524
// No tappable control may sit across the fold line.525
for (final e526
in find527
.byWidgetPredicate(528
(w) =>529
w is ButtonStyleButton || w is IconButton || w is TextField,530
)531
.evaluate()) {532
final box = e.renderObject! as RenderBox;533
if (!box.attached || !box.hasSize) continue;534
final whole = box.localToGlobal(Offset.zero) & box.size;535
// Only the part visible inside a pane counts; scrolled-away parts are536
// clipped by the pane.537
final pane = whole.overlaps(list) ? list : tool;538
if (!whole.overlaps(pane)) continue;539
final r = whole.intersect(pane);540
final crosses = vertical541
? r.left < fold.left - .5 && r.right > fold.right + .5542
: r.top < fold.top - .5 && r.bottom > fold.bottom + .5;543
expect(544
crosses,545
false,546
reason: '${e.widget.runtimeType} $r crosses $fold',547
);548
}549
}551
for (final ratio in [2.0, 3.0]) {552
// iPhone Duo: Apple publishes physical pixels only (outer 1398 × 2034,553
// inner 1878 × 2670). The display scale is not published and no554
// iPhone Duo simulator is installed, so both common scales are tested555
// as assumptions; logical size = physical / ratio.556
final outer = const Size(1398, 2034) / ratio;557
final inner = const Size(1878, 2670) / ratio;558
testWidgets(559
'iPhone Duo physical resolutions at @${ratio}x: closed, open, half folded, both orientations',560
(tester) async {561
addTearDown(() => reset(tester));562
await apply(tester, Shape(outer, ratio: ratio));563
final c = await openWorkspace(tester);564
await go(tester, Icons.network_check);565
expect(tester.view.physicalSize, const Size(1398, 2034));566
for (final s in [outer, outer.flipped, inner, inner.flipped, outer]) {567
await apply(568
tester,569
Shape(570
s,571
ratio: ratio,572
padding: const EdgeInsets.only(top: 50, bottom: 20),573
),574
);575
expect(tester.takeException(), null, reason: '$s');576
expect(WindowClass.ofWidth(s.width), AdaptiveLayout(s).windowClass);577
}578
// Half folded: the reserved region in the middle of the inner display,579
// for a crease along either axis.580
for (final s in [inner, inner.flipped]) {581
for (final vertical in [true, false]) {582
final fold = vertical583
? Rect.fromLTWH(s.width / 2 - 8, 0, 16, s.height)584
: Rect.fromLTWH(0, s.height / 2 - 8, s.width, 16);585
await apply(586
tester,587
Shape(s, ratio: ratio, features: [crease(fold)]),588
);589
await tester.pump();590
expect(tester.takeException(), null, reason: '$s $fold');591
await expectSides(tester, fold, vertical: vertical);592
}593
}594
await closeWorkspace(tester, c);595
},596
);597
}599
testWidgets('book fold, tabletop fold, hinge and flat fold', (600
tester,601
) async {602
addTearDown(() => reset(tester));603
await apply(tester, const Shape(Size(673, 841)));604
final c = await openWorkspace(tester);605
await go(tester, Icons.network_check);606
// Closed foldable (narrow outer screen) and open inner screen.607
for (final s in const [608
Size(344, 882),609
Size(673, 841),610
Size(841, 673),611
Size(373, 840),612
Size(900, 1008),613
]) {614
await apply(tester, Shape(s, ratio: 2.625));615
expect(tester.takeException(), null, reason: '$s');616
}617
final book = crease(const Rect.fromLTWH(336, 0, 0, 841));618
await apply(619
tester,620
Shape(const Size(673, 841), ratio: 2.625, features: [book]),621
);622
await tester.pump();623
await expectSides(tester, book.bounds, vertical: true);624
final table = crease(const Rect.fromLTWH(0, 420, 841, 0));625
await apply(626
tester,627
Shape(const Size(841, 673), ratio: 2.625, features: [table]),628
);629
await tester.pump();630
expect(find.byType(NavigationBar), findsOneWidget);631
await expectSides(tester, table.bounds, vertical: false);632
final hinge = crease(633
const Rect.fromLTWH(540, 0, 34, 720),634
type: DisplayFeatureType.hinge,635
state: DisplayFeatureState.unknown,636
);637
await apply(638
tester,639
Shape(const Size(1114, 720), ratio: 2.5, features: [hinge]),640
);641
await tester.pump();642
await expectSides(tester, hinge.bounds, vertical: true);643
// A fully open (flat) fold is one continuous surface.644
await apply(645
tester,646
Shape(647
const Size(673, 841),648
features: [649
crease(650
const Rect.fromLTWH(336, 0, 0, 841),651
state: DisplayFeatureState.postureFlat,652
),653
],654
),655
);656
expect(find.byType(ServersPage), findsNothing);657
expect(658
tester.getRect(find.byType(DiagnosticsPage)).width,659
greaterThan(500),660
);661
await closeWorkspace(tester, c);662
});663
});665
testWidgets('split screen and small windows on tablets', (tester) async {666
addTearDown(() => reset(tester));667
await apply(tester, const Shape(Size(1194, 834)));668
final c = await openWorkspace(tester);669
for (var page = 1; page < pageIcons.length; page++) {670
await go(tester, pageIcons[page]);671
// iPad / Android tablet: full, 2/3, 1/2, 1/3 split and slide-over widths,672
// then free-form resizing step by step.673
for (final s in [674
const Size(1194, 834),675
const Size(796, 834),676
const Size(597, 834),677
const Size(390, 834),678
const Size(320, 693),679
const Size(1366, 1024),680
const Size(678, 1024),681
const Size(507, 1024),682
const Size(1280, 800),683
const Size(640, 800),684
const Size(1600, 1000),685
const Size(2560, 1440),686
for (var w = 300.0; w <= 1400; w += 137) Size(w, 760),687
]) {688
await apply(tester, Shape(s, ratio: 2));689
expect(tester.takeException(), null, reason: 'page $page $s');690
}691
}692
await closeWorkspace(tester, c);693
});695
for (final s in const [696
Size(320, 568),697
Size(390, 844),698
Size(844, 390),699
Size(820, 1180),700
Size(1440, 900),701
]) {702
testWidgets('accessibility guidelines at ${s.width}x${s.height}', (703
tester,704
) async {705
addTearDown(() => reset(tester));706
final semantics = tester.ensureSemantics();707
await apply(tester, Shape(s));708
final c = await openWorkspace(tester);709
for (final mode in [ThemeMode.light, ThemeMode.dark]) {710
c.theme = mode;711
c.changed();712
await tester.pumpAndSettle();713
for (var page = 0; page < pageIcons.length; page++) {714
await go(tester, pageIcons[page]);715
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));716
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));717
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));718
await expectLater(tester, meetsGuideline(textContrastGuideline));719
}720
}721
await closeWorkspace(tester, c);722
semantics.dispose();723
});724
}725
}SHA-256
a45a209596381f871a5edd36a67c3b19eabfd06aebfc65a08de1fb1daba915c4CH-J Proprietary Software License 1.14 · سورس CH-J Proprietary Software License 1.14 کے تحت فراہم کیا جاتا ہے۔ اس کی دستیابی لائسنس کی شرائط نہیں بدلتی اور نہ ہی اضافی حقوق دیتی ہے۔
آرکائیو کا SHA-256
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655