125 lines
6.9 KiB
Dart
125 lines
6.9 KiB
Dart
import 'dart:io';
|
||
import 'dart:ui' as ui;
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/rendering.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:integration_test/integration_test.dart';
|
||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||
import 'package:jj_mkvmaker/app/i18n_controller.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/explorer_page.dart';
|
||
import 'package:jj_mkvmaker/ui/theme.dart';
|
||
import 'package:path/path.dart' as p;
|
||
|
||
import 'support/temp_dir.dart';
|
||
import '../test/support/test_explorer_device.dart';
|
||
|
||
/// 관리자 지시 (10/21, 28 지적): Windows · TC 스타일의 날짜 열이 시간까지 들어가지 않으면 "2026-10-2…" 로 잘리던 것 -
|
||
/// 모자라면 짧은 꼴 (올해 "10-21 14:05" · 아니면 "2025-10-21"), 그래도 모자라면 숨김. 실제 글꼴로 312 조합
|
||
/// (폭 13 × 스타일 2 × 언어 3 × 글자 배율 4) 에서 날짜 칸이 잘리지 않는지 (RenderParagraph.didExceedMaxLines) 와
|
||
/// 날짜를 보일 수 있는 넓은 창에서는 다 쓴 꼴이 나오는지 본다. 좁은 몇 개는 PNG (JJ_SHOT_DIR).
|
||
void main() {
|
||
late TestExplorerDevice device;
|
||
setUp(() => device = TestExplorerDevice());
|
||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_datecol');
|
||
final dateLike = RegExp(r'^(\d{4}[-/])?\d{2}[-/]\d{2}( \d{2}:\d{2})?$');
|
||
|
||
testWidgets('날짜 열: 312 조합에서 잘림 0', (t) async {
|
||
final tmp = testTempDir('jj_datecol_');
|
||
final dir = Directory(p.join(tmp.path, 'files'))..createSync();
|
||
final now = DateTime.now();
|
||
for (final (n, at) in [
|
||
('올해 사진.jpg', DateTime(now.year, 12, 28, 23, 58).isAfter(now) ? now : DateTime(now.year, 12, 28, 23, 58)),
|
||
('작년 문서.pdf', DateTime(now.year - 1, 12, 28, 23, 58)),
|
||
]) {
|
||
File(p.join(dir.path, n))
|
||
..writeAsStringSync('x')
|
||
..setLastModifiedSync(at);
|
||
}
|
||
device.volumes = () => [(dir.path, 'T:')];
|
||
final key = GlobalKey();
|
||
var checked = 0, short = 0, hidden = 0;
|
||
final emptyOnce = <String>[];
|
||
final widths = <double>[300, 340, 360, 390, 412, 460, 520, 600, 700, 800, 960, 1280, 1600];
|
||
final scales = [(1.0, 80), (1.0, 100), (1.0, 150), (1.3, 150)];
|
||
for (final lang in ['ko', 'ja', 'zh-Hans']) {
|
||
await t.runAsync(() => i18n.apply(lang, save: false));
|
||
for (final style in ['windows', 'totalcmd']) {
|
||
for (final (dev, list) in scales) {
|
||
for (final w in widths) {
|
||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||
c.settings
|
||
..explorerStyle = style
|
||
..explorerLayout = 'single'
|
||
..explorerTextScale = list
|
||
..explorerPaths = [dir.path, dir.path];
|
||
t.view.physicalSize = Size(w, 800);
|
||
t.view.devicePixelRatio = 1;
|
||
await t.runAsync(() => t.pumpWidget(RepaintBoundary(
|
||
key: key,
|
||
child: MaterialApp(
|
||
theme: buildTheme(),
|
||
debugShowCheckedModeBanner: false,
|
||
builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: TextScaler.linear(dev)), child: child!),
|
||
home: ExplorerPage(key: UniqueKey(), device: device, c: c),
|
||
),
|
||
)));
|
||
for (var i = 0; i < 200 && find.textContaining('작년 문서').evaluate().isEmpty; i++) {
|
||
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 50)));
|
||
await t.pump();
|
||
}
|
||
await t.pump(const Duration(milliseconds: 100));
|
||
final name = '${lang}_${style}_${w.round()}_d${dev}_l$list';
|
||
if (find.textContaining('작년 문서').evaluate().isEmpty) {
|
||
// 목록이 빈 채로 남는 일이 가끔 있다 (빙글이 없이 - 3a 에 알림). 그림을 남기고 [새로 고침] 으로 한 번 더
|
||
emptyOnce.add(name);
|
||
final r = key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
||
final img = await t.runAsync(() => r.toImage());
|
||
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));
|
||
Directory(out).createSync(recursive: true);
|
||
File(p.join(out, 'datecol_EMPTY_$name.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
|
||
await t.tap(find.byIcon(Icons.refresh).first);
|
||
for (var i = 0; i < 200 && find.textContaining('작년 문서').evaluate().isEmpty; i++) {
|
||
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 50)));
|
||
await t.pump();
|
||
}
|
||
await t.pump(const Duration(milliseconds: 100));
|
||
}
|
||
expect(find.textContaining('작년 문서'), findsWidgets, reason: '$name 목록');
|
||
final dates = find.byWidgetPredicate((x) => x is Text && x.data != null && dateLike.hasMatch(x.data!));
|
||
for (final e in dates.evaluate()) {
|
||
final para = e.findRenderObject()! as RenderParagraph;
|
||
expect(para.didExceedMaxLines, isFalse, reason: '$name: "${(e.widget as Text).data}" 잘림');
|
||
if (!(e.widget as Text).data!.contains(':') || !(e.widget as Text).data!.startsWith('20')) short++;
|
||
}
|
||
if (dates.evaluate().isEmpty) hidden++;
|
||
// 넓은 창 (1280 이상) 에서는 다 쓴 꼴 (연도 + 시각)
|
||
if (w >= 1280 && dev == 1.0 && list <= 100) {
|
||
expect(dates.evaluate().map((e) => (e.widget as Text).data!).every((x) => x.length == 16), isTrue, reason: '$name 다 쓴 꼴');
|
||
}
|
||
if (w == 360 || w == 412) {
|
||
final r = key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
||
final img = await t.runAsync(() => r.toImage());
|
||
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));
|
||
Directory(out).createSync(recursive: true);
|
||
File(p.join(out, 'datecol_$name.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
|
||
}
|
||
checked++;
|
||
await t.pumpWidget(const SizedBox());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
await t.runAsync(() => i18n.apply('ko', save: false));
|
||
t.view.reset();
|
||
expect(checked, 312);
|
||
// ignore: avoid_print
|
||
print('날짜 열 조합 $checked · 짧은 꼴 칸 $short · 날짜 숨긴 조합 $hidden · 목록이 빈 채였던 조합 $emptyOnce');
|
||
}, timeout: const Timeout(Duration(minutes: 30)));
|
||
}
|