232 lines
8.9 KiB
Dart
232 lines
8.9 KiB
Dart
import 'package:fancake/core/data/planet_visual_state.dart';
|
|
import 'package:fancake/core/ui/planet_25d.dart';
|
|
import 'package:fancake/features/home/planet_create_screen.dart';
|
|
import 'package:fancake/features/galaxy/galaxy_data.dart';
|
|
import 'package:fancake/features/galaxy/widgets/galaxy_field.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
test('진화 5% 전 아우라는 두 조건 중 낮은 쪽이 95% 이상일 때 (Figma 2024:20758)', () {
|
|
bool imminent(num pop, num sfpi) => PlanetVisualState.fromDocument({
|
|
'levelPop': pop, 'nextStagePop': 100, 'sfpi': sfpi, 'nextStageSfpi': 100,
|
|
}).evolutionImminent;
|
|
expect(imminent(96, 99), isTrue);
|
|
expect(imminent(99, 94), isFalse);
|
|
expect(imminent(100, 100), isFalse, reason: '도달은 진화 판정의 몫');
|
|
expect(PlanetVisualState.fromDocument({'stage': 2}).evolutionImminent, isFalse);
|
|
});
|
|
|
|
test('생성 때 고른 형태가 HOME · GALAXY · 상세 · MY 그림을 정한다 (A-08 · Figma 344:214421)', () {
|
|
for (var i = 1; i <= 6; i++) {
|
|
expect(planetShapeImage('shape$i'), 'assets/figma/planet-shape-$i.png');
|
|
}
|
|
expect(planetShapeImage(null), isNull, reason: '성운 · 옛 행성은 화면이 준 그림');
|
|
expect(planetShapeImage('shape7'), isNull);
|
|
// 생성 화면이 보내는 키와 같은 표여야 한다.
|
|
for (final k in PlanetCreateScreen.shapes) {
|
|
expect(planetShapeImage(k), isNotNull);
|
|
}
|
|
final state = PlanetVisualState.fromDocument({'planetType': 'shape3', 'colorKey': 'gold'});
|
|
expect(state.planetType, 'shape3');
|
|
final node = const PlanetNode(
|
|
id: 'f', name: 'F', rank: 1, fpi: 0, members: 0, online: 0, planetType: 'shape2',
|
|
);
|
|
expect(GalaxyField.imageOf(node, 0), 'assets/figma/planet-shape-2.png');
|
|
});
|
|
|
|
test('컬러는 마스터(config/planetColors)가 정한다 — 추가 색은 그리고 숨긴 색은 고르지 못한다 (10/3 #6)', () {
|
|
final master = planetColorsFrom({'colors': [
|
|
{'key': 'gold', 'hex': '#FFC500', 'visible': false},
|
|
{'key': 'mint', 'hex': '3EB489'},
|
|
{'key': 'bad', 'hex': 'not-a-color'},
|
|
]});
|
|
expect(master.visible.map((c) => c.$1), ['mint']);
|
|
expect(master.all.map((c) => c.$1), ['gold', 'mint']);
|
|
setPlanetColorMaster(master.all);
|
|
addTearDown(() => setPlanetColorMaster(planetColors));
|
|
expect(planetColorOf('mint'), const Color(0xFF3EB489));
|
|
expect(planetColorOf('gold'), const Color(0xFFFFC500));
|
|
// 마스터가 비면 Figma 기본 8색.
|
|
expect(planetColorsFrom(null).visible, planetColors);
|
|
});
|
|
|
|
// C-50 — 생성 화면이 고른 colorKey 를 같은 팔레트로 본체에 입힌다.
|
|
testWidgets('행성 본체는 서버 colorKey 의 생성 팔레트 색을 입는다', (tester) async {
|
|
expect(identical(PlanetCreateScreen.colors, planetColors), isTrue);
|
|
expect(PlanetVisualState.fromDocument({'colorKey': 'gold'}).colorKey, 'gold');
|
|
expect(planetColorOf('gold'), const Color(0xFFFFC500));
|
|
expect(planetColorOf('violet'), isNull, reason: '서버 기본값은 원래 일러스트 그대로');
|
|
|
|
Future<Iterable<ColorFiltered>> filters(Color? tint) async {
|
|
await tester.pumpWidget(MaterialApp(home: Center(child: Planet25D(
|
|
image: 'assets/figma/planet-home.png', size: 200, stage: 1, tint: tint, animate: false,
|
|
))));
|
|
return tester.widgetList<ColorFiltered>(find.byType(ColorFiltered));
|
|
}
|
|
|
|
expect(await filters(null), isEmpty);
|
|
final tinted = await filters(planetColorOf('gold'));
|
|
expect(tinted.single.colorFilter,
|
|
const ColorFilter.mode(Color(0xFFFFC500), BlendMode.modulate));
|
|
});
|
|
|
|
testWidgets('행성 본체와 로고는 시간이 지나도 평면 회전하지 않는다', (tester) async {
|
|
final previous = Planet25D.animationsEnabled;
|
|
Planet25D.animationsEnabled = true;
|
|
addTearDown(() => Planet25D.animationsEnabled = previous);
|
|
await tester.pumpWidget(const MaterialApp(home: Center(child: Planet25D(
|
|
image: 'assets/figma/planet-home.png', size: 200, stage: 1,
|
|
))));
|
|
final body = find.byWidgetPredicate((w) => w is Image &&
|
|
w.image is AssetImage && (w.image as AssetImage).assetName == 'assets/figma/planet-home.png');
|
|
await tester.pump(const Duration(milliseconds: 1));
|
|
for (var quarter = 0; quarter < 4; quarter++) {
|
|
await tester.pump(const Duration(seconds: 12));
|
|
final box = tester.renderObject<RenderBox>(body);
|
|
final left = box.localToGlobal(Offset(0, box.size.height / 2));
|
|
final right = box.localToGlobal(Offset(box.size.width, box.size.height / 2));
|
|
expect(right.dx - left.dx, greaterThan(0));
|
|
expect(right.dy, closeTo(left.dy, 0.01));
|
|
}
|
|
await tester.pumpWidget(const SizedBox());
|
|
});
|
|
|
|
testWidgets('뒤 고리와 잔해는 본체 안쪽에서 차폐하고 앞 고리만 통과한다', (tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Center(
|
|
child: Planet25D(
|
|
image: 'assets/figma/planet-home.png',
|
|
size: 200,
|
|
stage: 4,
|
|
animate: false,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final clips = tester
|
|
.widgetList<ClipPath>(
|
|
find.descendant(
|
|
of: find.byType(Planet25D),
|
|
matching: find.byType(ClipPath),
|
|
),
|
|
)
|
|
.where((w) => w.clipper != null)
|
|
.toList();
|
|
expect(clips.length, 2);
|
|
const size = Size(340, 340);
|
|
final rear = clips.first.clipper!.getClip(size);
|
|
final front = clips.last.clipper!.getClip(size);
|
|
expect(rear.contains(const Offset(170, 145)), false, reason: '본체 뒤는 투과 금지');
|
|
expect(
|
|
rear.contains(const Offset(55, 150)),
|
|
true,
|
|
reason: '본체 밖의 뒤 고리는 표시',
|
|
);
|
|
expect(
|
|
front.contains(const Offset(170, 195)),
|
|
true,
|
|
reason: '앞 고리는 본체 위에 합성',
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('갤럭시의 Stage 0도 고체 행성 대신 원본 성운을 렌더한다', (tester) async {
|
|
const nebula = PlanetNode(
|
|
id: 'nebula',
|
|
name: '성운',
|
|
stage: 0,
|
|
rank: 1,
|
|
fpi: 0,
|
|
members: 1,
|
|
online: 1,
|
|
isMine: true,
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: GalaxyField(
|
|
planets: const [nebula],
|
|
selected: nebula,
|
|
onSelect: (_) {},
|
|
spin: 0,
|
|
pulse: 0,
|
|
),
|
|
),
|
|
);
|
|
expect(find.byType(Planet25D), findsOneWidget);
|
|
final images = tester
|
|
.widgetList<Image>(find.byType(Image))
|
|
.map((image) => image.image)
|
|
.whereType<AssetImage>()
|
|
.map((asset) => asset.assetName);
|
|
expect(images.contains('assets/fx/nebula-figma.png'), true);
|
|
expect(images.contains('assets/figma/planet-home.png'), false);
|
|
});
|
|
|
|
testWidgets(
|
|
'Stage 0 uses the Figma nebula instead of a solid planet; stages expose their names',
|
|
(tester) async {
|
|
final semantics = tester.ensureSemantics();
|
|
const names = ['성운', '시작', '성장', '함께', '동행', '빛', '영원'];
|
|
for (var stage = 0; stage <= 6; stage++) {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Planet25D(
|
|
image: 'assets/figma/planet-home.png',
|
|
size: 200,
|
|
stage: stage,
|
|
animate: false,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final images = tester
|
|
.widgetList<Image>(find.byType(Image))
|
|
.map((image) => image.image)
|
|
.whereType<AssetImage>()
|
|
.map((asset) => asset.assetName);
|
|
expect(images.contains('assets/figma/planet-home.png'), stage != 0);
|
|
expect(images.contains('assets/fx/nebula-figma.png'), stage == 0);
|
|
expect(
|
|
find.bySemanticsLabel('Stage $stage · ${names[stage]}'),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
}
|
|
semantics.dispose();
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Reduce Motion stops the ambient ticker, including runtime changes',
|
|
(tester) async {
|
|
Planet25D.animationsEnabled = true;
|
|
addTearDown(() => Planet25D.animationsEnabled = false);
|
|
Future<void> show(bool reduced) => tester.pumpWidget(
|
|
MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(disableAnimations: reduced),
|
|
child: const Center(
|
|
child: Planet25D(
|
|
image: 'assets/figma/planet-home.png',
|
|
size: 200,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await show(true);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(tester.binding.transientCallbackCount, 0);
|
|
await show(false);
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(tester.binding.transientCallbackCount, greaterThan(0));
|
|
await show(true);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(tester.binding.transientCallbackCount, 0);
|
|
},
|
|
);
|
|
}
|