arblant
Railynx / test / app / full_audit_test.dart
full_audit_test.dart205 lines · 8.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:railway_clean_ip_scanner/app/controller.dart';
import 'package:railway_clean_ip_scanner/app/prefs_store.dart';
import 'package:railway_clean_ip_scanner/main.dart';
import 'package:railway_clean_ip_scanner/ui/settings_page.dart';
import 'package:railway_clean_ip_scanner/ui/widgets.dart';

import '../fakes.dart';
import 'package:railway_clean_ip_scanner/ui/home_page.dart';

/// Full-audit tests added for the 2.0.0+10 verification pass: every
/// Settings switch must drive the flag it claims to, a prefs file from
/// the previous release must upgrade cleanly, and every preset /
/// glow / row variant must paint without exceptions.
void main() {
  group('settings wiring sweep', () {
    testWidgets('every switch flips exactly its own controller flag', (tester) async {
      tester.view.physicalSize = const Size(1280, 1600);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);
      final c = AppController(engine: FakeEngine());
      addTearDown(c.dispose);
      await tester.pumpWidget(ScannerApp(controller: c));
      await tester.pump();

      await tester.tap(find.byIcon(Icons.settings_rounded));
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 400));
      expect(find.byType(SettingsPage), findsOneWidget);

      final cases = <String, bool Function(AppController)>{
        'Animated aurora': (c) => c.animatedAurora,
        'Aurora lights': (c) => c.fxBlobs,
        'Half-resolution aurora': (c) => c.fxHalfRes,
        'Light washes': (c) => c.fxWashes,
        'Rich shadows': (c) => c.fxRichShadows,
        'Blurred glow': (c) => c.fxGlowBlur,
        'Dock blur': (c) => c.fxDockBlur,
        'Button flow': (c) => c.fxButtonFlow,
        'Ring glow': (c) => c.fxRingGlow,
        'Row animations': (c) => c.fxRowAnimations,
        'Lightweight rows': (c) => c.fxLiteRows,
      };
      final scrollable = find.descendant(
        of: find.byType(SettingsPage),
        matching: find.byType(Scrollable),
      );
      for (final entry in cases.entries) {
        await tester.scrollUntilVisible(find.text(entry.key), 250, scrollable: scrollable);
        await tester.pump(const Duration(milliseconds: 250));
        final sw = find.descendant(
          of: find.ancestor(of: find.text(entry.key), matching: find.byType(Row)),
          matching: find.byType(AppSwitch),
        );
        expect(sw, findsOneWidget, reason: 'switch for ${entry.key}');
        final before = entry.value(c);
        await tester.tap(sw);
        await tester.pump();
        expect(entry.value(c), !before, reason: '${entry.key} must flip its flag');
        expect(tester.takeException(), isNull, reason: '${entry.key} tap');
      }
    });
  });

  group('settings reach the home page', () {
    testWidgets('a switch flipped in Settings restyles the home backdrop after popping back', (tester) async {
      tester.view.physicalSize = const Size(1280, 1600);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);
      final c = AppController(engine: FakeEngine());
      addTearDown(c.dispose);
      await tester.pumpWidget(ScannerApp(controller: c));
      await tester.pump();

      AuroraBackdrop homeBackdrop() => tester.widget<AuroraBackdrop>(
            find.descendant(of: find.byType(HomePage), matching: find.byType(AuroraBackdrop)),
          );
      expect(homeBackdrop().showBlobs, isTrue);
      expect(homeBackdrop().animated, isTrue);

      await tester.tap(find.byIcon(Icons.settings_rounded));
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 400));
      expect(find.byType(SettingsPage), findsOneWidget);

      final scrollable = find.descendant(
        of: find.byType(SettingsPage),
        matching: find.byType(Scrollable),
      );
      for (final title in <String>['Aurora lights', 'Animated aurora']) {
        await tester.scrollUntilVisible(find.text(title), 250, scrollable: scrollable);
        await tester.pump(const Duration(milliseconds: 250));
        final sw = find.descendant(
          of: find.ancestor(of: find.text(title), matching: find.byType(Row)),
          matching: find.byType(AppSwitch),
        );
        await tester.tap(sw);
        await tester.pump();
      }
      expect(c.fxBlobs, isFalse);
      expect(c.animatedAurora, isFalse);

      await tester.tap(find.byIcon(Icons.arrow_back_rounded));
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 400));

      // The home backdrop must have received the new values even though
      // the change happened while Settings covered it.
      expect(homeBackdrop().showBlobs, isFalse, reason: 'home backdrop ignored the blobs switch');
      expect(homeBackdrop().animated, isFalse, reason: 'home backdrop ignored the animation switch');
      expect(tester.takeException(), isNull);
    });
  });

  group('empty state layout', () {
    testWidgets('the empty card content is centered in the card', (tester) async {
      tester.view.physicalSize = const Size(1280, 1400);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);
      final c = AppController(engine: FakeEngine());
      c.animatedAurora = false;
      addTearDown(c.dispose);
      await tester.pumpWidget(ScannerApp(controller: c));
      await tester.pump();
      final cardFinder = find.ancestor(of: find.text('No clean IPs yet'), matching: find.byType(GlassCard));
      final card = tester.getRect(cardFinder);
      final title = tester.getRect(find.text('No clean IPs yet'));
      final orbit = tester.getRect(
        find.descendant(of: cardFinder, matching: find.byType(OrbitArt)),
      );
      expect((title.center.dx - card.center.dx).abs(), lessThan(2),
          reason: 'title must sit at the card’s horizontal center');
      expect((orbit.center.dx - card.center.dx).abs(), lessThan(2),
          reason: 'orbit art must sit at the card’s horizontal center');
    });
  });

  group('prefs upgrade from the previous release', () {
    test('a +7-era prefs file loads with the new defaults', () async {
      final dir = await Directory.systemTemp.createTemp('railynx_upgrade');
      addTearDown(() => dir.delete(recursive: true));
      final file = File('${dir.path}/prefs.json');
      // Exactly the keys 2.0.0+7 could write — no fx_rich_shadows,
      // no fx_glow_blur, no fx_half_res, no fx_lite_rows, no fps_limit.
      file.writeAsStringSync(jsonEncode(<String, Object?>{
        'aurora_anim': true,
        'fx_blobs': true,
        'fx_washes': true,
        'fx_dock_blur': true,
        'fx_button_flow': true,
        'fx_ring_glow': true,
        'fx_row_anim': true,
      }));

      final c = AppController(engine: FakeEngine(), prefs: PrefsStore(file));
      addTearDown(c.dispose);
      expect(c.fxRichShadows, isTrue);
      expect(c.fxGlowBlur, isTrue);
      expect(c.fxHalfRes, isFalse);
      expect(c.fxLiteRows, isTrue);
      expect(c.fpsLimit, 0);
      expect(c.uiPreset, 'original');
    });
  });

  group('paint smoke across look variants', () {
    testWidgets('every preset and glow/row variant paints clean with results', (tester) async {
      tester.view.physicalSize = const Size(1280, 1400);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);
      final engine = FakeEngine(
        scanResults: [ok('10.0.0.1', 90), ok('10.0.0.2', 240), ok('10.0.0.3', 700)],
      );
      final c = AppController(engine: engine);
      addTearDown(c.dispose);
      await c.parseLink(sampleLink);
      await tester.pumpWidget(ScannerApp(controller: c));
      await tester.runAsync(() => c.toggleScan());
      await tester.pump();
      expect(find.text('FASTEST'), findsOneWidget);
      expect(tester.takeException(), isNull);

      for (final preset in <String>['original', 'balanced', 'performance']) {
        c.applyPreset(preset);
        await tester.pump();
        await tester.pump(const Duration(milliseconds: 350));
        expect(tester.takeException(), isNull, reason: 'preset $preset');
      }
      // The mixed custom state: stepped glow + wrapped rows + half-res.
      c.setFx(glowBlur: false, liteRows: false, halfRes: true, richShadows: false);
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 350));
      expect(tester.takeException(), isNull, reason: 'custom mix');
      // And back to the blurred glow with lite rows.
      c.setFx(glowBlur: true, liteRows: true);
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 350));
      expect(tester.takeException(), isNull, reason: 'blurred + lite');
    });
  });
}