feat(insights): first isights impemetation

This commit is contained in:
misha
2026-11-09 15:35:10 +03:00
parent 67920b2016
commit 45d072cff4
63 changed files with 6374 additions and 1465 deletions

View File

@@ -0,0 +1,285 @@
import 'package:eatvio_app/features/insights/domain/insights_text_builder.dart';
import 'package:eatvio_app/features/insights/models/insights_period.dart';
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
import 'package:eatvio_app/features/profile/models/weight_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
WeightModel _weight(double weight, {double? bmi, int day = 1}) {
final date = DateTime(2026, 11, day, 12);
return WeightModel(
id: day,
userId: 1,
weight: weight,
createdAt: date,
updatedAt: date,
bmi: bmi,
);
}
CircumferenceModel _circumference({
double waist = 80,
double chest = 95,
double hips = 100,
int day = 1,
}) {
final date = DateTime(2026, 11, day, 12);
return CircumferenceModel(
id: day,
userId: 1,
chestCircumference: chest,
waistCircumference: waist,
hipCircumference: hips,
createdAt: date,
updatedAt: date,
);
}
void main() {
late InsightsTextBuilder builder;
setUpAll(() async {
final s = await S.load(const Locale('ru'));
builder = InsightsTextBuilder(s);
});
group('форматирование', () {
test('formatNumber: один знак без хвостового нуля', () {
expect(builder.formatNumber(3), '3');
expect(builder.formatNumber(3.5), '3.5');
expect(builder.formatNumber(3.04), '3');
expect(builder.formatNumber(3.26), '3.3');
});
test('formatLabel: dd.MM', () {
expect(builder.formatLabel(DateTime(2026, 11, 9, 12)), '09.11');
});
test('periodLabel: все периоды', () {
expect(builder.periodLabel(BodyPeriod.month), 'месяц');
expect(builder.periodLabel(BodyPeriod.threeMonths), '3 месяца');
expect(builder.periodLabel(BodyPeriod.sixMonths), '6 месяцев');
expect(builder.periodLabel(BodyPeriod.year), 'год');
expect(builder.periodLabel(BodyPeriod.all), 'всё время');
});
});
group('вес', () {
test('снижение', () {
final model = builder.buildWeight([
_weight(74.5),
_weight(73.2, day: 9),
], BodyPeriod.month);
expect(model.deltaText, 'Снизили на 1.3 кг за месяц');
expect(model.delta, -1.3);
expect(model.last, 73.2);
expect(model.headline, '73.2');
expect(model.unit, 'кг');
expect(model.labels, ['01.11', '09.11']);
expect(model.values, [74.5, 73.2]);
});
test('набор', () {
final model = builder.buildWeight([
_weight(70),
_weight(72, day: 3),
], BodyPeriod.threeMonths);
expect(model.deltaText, 'Набрали 2 кг за 3 месяца');
expect(model.delta, 2);
});
test('порог 0.05: меньше — не изменился', () {
final model = builder.buildWeight([
_weight(70),
_weight(70.04, day: 3),
], BodyPeriod.year);
expect(model.deltaText, 'Вес не изменился за год');
});
test('порог 0.05: ровно 0.25 — уже изменение', () {
final model = builder.buildWeight([
_weight(70),
_weight(70.25, day: 3),
], BodyPeriod.all);
expect(model.deltaText, 'Набрали 0.3 кг за всё время');
});
test('одна точка: недостаточно данных, но headline есть', () {
final model = builder.buildWeight([_weight(72)], BodyPeriod.month);
expect(model.deltaText, 'Недостаточно данных');
expect(model.delta, isNull);
expect(model.headline, '72.0');
});
test('нет точек', () {
final model = builder.buildWeight([], BodyPeriod.month);
expect(model.deltaText, 'Недостаточно данных');
expect(model.last, isNull);
expect(model.headline, '—');
expect(model.values, isEmpty);
});
});
group('категории ИМТ (границы)', () {
final cases = <double, String>{
15.9: 'выраженный дефицит массы',
16.0: 'недостаточная масса',
18.4: 'недостаточная масса',
18.5: 'норма',
24.9: 'норма',
25.0: 'избыточная масса',
29.9: 'избыточная масса',
30.0: 'ожирение I ст.',
34.9: 'ожирение I ст.',
35.0: 'ожирение II ст.',
39.9: 'ожирение II ст.',
40.0: 'ожирение III ст.',
55.0: 'ожирение III ст.',
};
cases.forEach((bmi, expected) {
test('$bmi → $expected', () {
expect(builder.bmiCategory(bmi), expected);
});
});
});
group('ИМТ', () {
test('без роста: пусто и подсказка', () {
final model = builder.buildBmi(
[_weight(70, bmi: 22)],
BodyPeriod.month,
null,
);
expect(model.values, isEmpty);
expect(model.headline, '—');
expect(model.hint, 'Укажите рост, чтобы посчитать ИМТ.');
expect(model.deltaText, 'Укажите рост, чтобы посчитать ИМТ.');
expect(model.category, isNull);
});
test('нулевой рост считается отсутствующим', () {
final model = builder.buildBmi(
[_weight(70, bmi: 22)],
BodyPeriod.month,
0,
);
expect(model.values, isEmpty);
});
test('ИМТ ниже', () {
final model = builder.buildBmi(
[_weight(80, bmi: 26.5), _weight(75, bmi: 24.2, day: 5)],
BodyPeriod.threeMonths,
176,
);
expect(model.deltaText, 'ИМТ ниже на 2.3 за 3 месяца · норма');
expect(model.category, 'норма');
expect(model.headline, '24.2');
expect(model.last, 24.2);
expect(model.delta, -2.3);
expect(model.hint, contains('Считаем при росте 176 см.'));
});
test('ИМТ выше', () {
final model = builder.buildBmi(
[_weight(70, bmi: 24), _weight(80, bmi: 27, day: 5)],
BodyPeriod.month,
170,
);
expect(model.deltaText, 'ИМТ выше на 3 за месяц · избыточная масса');
});
test('ИМТ не изменился', () {
final model = builder.buildBmi(
[_weight(70, bmi: 22), _weight(70, bmi: 22.01, day: 5)],
BodyPeriod.year,
170,
);
expect(model.deltaText, 'ИМТ не изменился за год · норма');
});
test('точки с bmi = null пропускаются', () {
final model = builder.buildBmi(
[_weight(70, bmi: null), _weight(71, bmi: 22, day: 3)],
BodyPeriod.month,
170,
);
expect(model.values, [22]);
expect(model.labels, ['03.11']);
expect(model.deltaText, 'Недостаточно данных');
});
test('рост есть, точек нет', () {
final model = builder.buildBmi([], BodyPeriod.month, 170);
expect(model.values, isEmpty);
expect(model.deltaText, 'Недостаточно данных');
expect(model.hint, contains('170'));
});
});
group('замеры', () {
test('по каждой части: меньше / больше / без изменений', () {
final model = builder.buildMeasure([
_circumference(waist: 82, chest: 96, hips: 100),
_circumference(waist: 80.5, chest: 98, hips: 100, day: 9),
], BodyPeriod.month);
expect(
model.deltaText,
'Талия меньше на 1.5 см, Грудь больше на 2 см, Бёдра без изменений за месяц',
);
expect(model.headline, '81');
expect(model.waist, [82, 80.5]);
expect(model.labels, ['01.11', '09.11']);
});
test('одна точка — недостаточно данных', () {
final model = builder.buildMeasure([_circumference()], BodyPeriod.month);
expect(model.deltaText, 'Недостаточно данных');
expect(model.headline, '80');
});
test('нет точек', () {
final model = builder.buildMeasure([], BodyPeriod.month);
expect(model.headline, '—');
expect(model.deltaText, 'Недостаточно данных');
});
});
group('build', () {
test('сортирует по дате, daily/intra = null', () {
final payload = builder.build(
weights: [_weight(73, bmi: 23, day: 9), _weight(75, bmi: 24, day: 1)],
circumferences: [
_circumference(waist: 79, day: 9),
_circumference(waist: 81),
],
period: BodyPeriod.month,
heightCm: 175,
);
expect(payload.weight.values, [75, 73]);
expect(payload.bmi.values, [24, 23]);
expect(payload.measure.waist, [81, 79]);
expect(payload.daily, isNull);
expect(payload.intra, isNull);
});
});
}