JJ_MKVMaker/test/theme_test.dart

125 lines
6.2 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/settings.dart';
import 'package:jj_mkvmaker/main.dart';
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/theme.dart';
/// X-plore 비교 5-1 ⑦ 밝은 테마 (관리자 지시 10/21): 처음 값 어둡게 (예전 그대로) · 밝게 · 기기 따라 · 바꾸면 상태는 그대로 다시 그림
double _lum(Color c) {
double ch(double v) => v <= 0.03928 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4).toDouble();
return 0.2126 * ch(c.r) + 0.7152 * ch(c.g) + 0.0722 * ch(c.b);
}
/// WCAG 대비
double contrast(Color a, Color b) {
final x = _lum(a), y = _lum(b);
return (max(x, y) + 0.05) / (min(x, y) + 0.05);
}
class _Probe extends StatefulWidget {
const _Probe();
@override
State<_Probe> createState() => _ProbeState();
}
class _ProbeState extends State<_Probe> {
int taps = 0;
@override
Widget build(BuildContext context) => GestureDetector(
onTap: () => setState(() => taps++),
child: Text('probe $taps', style: TextStyle(color: JjColors.text)),
);
}
void main() {
tearDown(() => JjColors.use(Brightness.dark));
test('처음 값은 어둡게 (예전 색 그대로) · 모르는 값 · 없는 값도 어둡게 · 고른 값은 그대로', () {
expect(AppSettings().display.brightness, 'dark');
expect(AppSettings.fromJson({}).display.brightness, 'dark');
expect(AppSettings.fromJson({'uiBrightness': 'neon'}).display.brightness, 'dark');
expect(AppSettings.fromJson({'uiBrightness': 'light'}).display.brightness, 'light');
expect(AppSettings.fromJson(AppSettings.fromJson({'uiBrightness': 'system'}).toJson()).display.brightness, 'system');
expect(JjColors.bg, const Color(0xFF141417));
expect(JjColors.accent, const Color(0xFF00C4CC));
expect(JjColors.textDim, const Color(0xFF8E8E99));
// 예전에 쓰던 Material 색 그대로 (어두운 화면이 바뀌지 않게)
expect([JjColors.warning, JjColors.alert, JjColors.amber, JjColors.amberText, JjColors.positive], [Colors.orangeAccent, Colors.redAccent, Colors.amber, Colors.amber, Colors.greenAccent].map((c) => Color(c.toARGB32())).toList());
expect(resolveBrightness('dark', Brightness.light), Brightness.dark);
expect(resolveBrightness('light', Brightness.dark), Brightness.light);
expect(resolveBrightness('system', Brightness.light), Brightness.light);
expect(resolveBrightness('?', Brightness.light), Brightness.dark);
});
test('밝은 색: 바탕 · 칸 위 글자 대비 4.5:1 이상 (글 · 흐린 글 · 강조 · 경고 · 성공 · 강조 위 글자)', () {
const l = JjPalette.light;
for (final bg in [l.bg, l.panel, l.panelHigh]) {
for (final (name, fg) in [('text', l.text), ('textDim', l.textDim), ('accent', l.accent), ('danger', l.danger), ('success', l.success), ('warning', l.warning), ('alert', l.alert), ('amberText', l.amberText), ('positive', l.positive)]) {
expect(contrast(fg, bg), greaterThanOrEqualTo(4.5), reason: '$name on ${bg.toARGB32().toRadixString(16)}');
}
}
expect(contrast(l.onAccent, l.accent), greaterThanOrEqualTo(4.5));
// 아이콘 (폴더 · 별) 은 3:1
for (final bg in [l.bg, l.panel, l.panelHigh]) {
expect(contrast(l.amber, bg), greaterThanOrEqualTo(3));
}
expect(buildTheme(Brightness.light).brightness, Brightness.light);
expect(buildTheme(Brightness.light).scaffoldBackgroundColor, l.bg);
expect(buildTheme(Brightness.dark).scaffoldBackgroundColor, JjPalette.dark.bg);
});
testWidgets('색을 바꾸면 JjColors 를 직접 읽는 곳도 다시 그림 · 상태는 그대로', (t) async {
await t.pumpWidget(const MaterialApp(home: Scaffold(body: _Probe())));
await t.tap(find.text('probe 0'));
await t.pump();
expect(t.widget<Text>(find.text('probe 1')).style!.color, JjPalette.dark.text);
expect(JjColors.use(Brightness.light), isTrue);
expect(JjColors.use(Brightness.light), isFalse, reason: '같으면 바꾸지 않음');
rebuildAllElements(t.binding.rootElement!);
await t.pump();
expect(t.widget<Text>(find.text('probe 1')).style!.color, JjPalette.light.text, reason: '눌러 둔 수 (상태) 는 그대로');
});
testWidgets('앱: 설정 "화면 밝기" 를 밝게 → 바로 밝은 테마 · 기기 따라는 기기 밝기를 따라 바뀜 · 어둡게로 돌아옴', (t) async {
t.view.physicalSize = const Size(1400, 900);
t.view.devicePixelRatio = 1;
addTearDown(t.view.reset);
addTearDown(t.platformDispatcher.clearPlatformBrightnessTestValue);
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
await t.runAsync(c.init);
await t.pumpWidget(JjMkvMakerApp(controller: c));
await t.pump();
BuildContext ctx() => t.element(find.byType(Scaffold).first);
expect(Theme.of(ctx()).brightness, Brightness.dark);
expect(JjColors.isLight, isFalse);
await t.runAsync(() => c.updateSettings((x) => x.display.brightness = 'light'));
await t.pump();
await t.pump();
expect(JjColors.isLight, isTrue);
expect(Theme.of(ctx()).brightness, Brightness.light);
expect(Theme.of(ctx()).scaffoldBackgroundColor, JjPalette.light.bg);
// 기기 따라
t.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
await t.runAsync(() => c.updateSettings((x) => x.display.brightness = 'system'));
await t.pump();
await t.pump();
expect(JjColors.isLight, isFalse);
t.platformDispatcher.platformBrightnessTestValue = Brightness.light;
await t.pump();
await t.pump();
expect(JjColors.isLight, isTrue, reason: '기기 밝기가 바뀌면 따라감');
await t.runAsync(() => c.updateSettings((x) => x.display.brightness = 'dark'));
await t.pump();
await t.pump();
expect(JjColors.isLight, isFalse);
expect(Theme.of(ctx()).brightness, Brightness.dark);
await t.pumpWidget(const SizedBox());
});
}