Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
83 lines
2.6 KiB
Dart
83 lines
2.6 KiB
Dart
import 'dart:typed_data';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:fintracker_app/core/theme/app_theme.dart';
|
|
import 'package:fintracker_app/core/widgets/service_mark.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
Future<ByteData> _render(WidgetTester tester, double size) async {
|
|
final key = GlobalKey();
|
|
tester.view.physicalSize = Size(size, size);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: RepaintBoundary(
|
|
key: key,
|
|
child: ServiceMark(size: size),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final boundary =
|
|
key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
|
return (await tester.runAsync(() async {
|
|
final image = await boundary.toImage();
|
|
return image.toByteData(format: ui.ImageByteFormat.rawRgba);
|
|
}))!;
|
|
}
|
|
|
|
Color _at(ByteData data, int size, int x, int y) {
|
|
final i = (y * size + x) * 4;
|
|
return Color.fromARGB(
|
|
data.getUint8(i + 3),
|
|
data.getUint8(i),
|
|
data.getUint8(i + 1),
|
|
data.getUint8(i + 2),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets(
|
|
'a gradient disc with three white bars, and nothing outside the disc',
|
|
(tester) async {
|
|
const size = 256;
|
|
final data = await _render(tester, size.toDouble());
|
|
|
|
// scale of the 512 design grid
|
|
int px(num v) => (v * size / 512).round();
|
|
|
|
expect(
|
|
_at(data, size, 2, 2).a,
|
|
0,
|
|
reason: 'the corner is outside the disc',
|
|
);
|
|
// the tallest bar (x 316..380, y 131..381): white in its middle
|
|
expect(_at(data, size, px(348), px(256)), const Color(0xFFFFFFFF));
|
|
// between two bars the disc shows through, and it is coloured, not white
|
|
final gap = _at(data, size, px(210), px(300));
|
|
expect(gap.a, 1);
|
|
expect(gap.b, greaterThan(gap.r), reason: 'blue-violet, not grey');
|
|
// the gradient runs from the accent (top left) toward violet (bottom right)
|
|
final start = _at(data, size, px(90), px(90));
|
|
final end = _at(data, size, px(430), px(430));
|
|
expect(end.r, greaterThan(start.r));
|
|
expect(start.b * 255, greaterThan(200));
|
|
expect(
|
|
(start.g - end.g) * 255,
|
|
greaterThan(40),
|
|
reason: 'the green channel falls off toward violet',
|
|
);
|
|
},
|
|
);
|
|
|
|
test('the near end of the gradient is the app accent', () {
|
|
expect(AppTheme.seed, const Color(0xFF14AFFF));
|
|
expect(ServiceMark.violet, isNot(AppTheme.seed));
|
|
});
|
|
}
|