testsAndMisc-archive/billsplit/test/models_test.dart
Krzysztof Rudnicki 4dd582a1df Move billsplit Flutter app into the monorepo
billsplit was a standalone Flutter app in ~/Downloads with one commit and no
git remote — that directory was the only copy of its history.

Three things the move needed beyond copying the tree:

- The root .gitignore silently swallowed 32 of its files (*.png, *.ico,
  *.manifest). Ignored files do not error on `git add`, they just vanish, so
  this would have committed an app that cannot build. Added a trailing block
  of ! overrides plus .binary-allowlist (which did not exist yet) — the
  documented in-repo exception for binaries a build genuinely needs. The
  ../testsAndMisc_binaries/ symlink scheme does not apply: those directories
  are gone, and a symlinked PNG breaks a fresh clone.

- billsplit/.github/workflows/ci.yaml was inert once nested; GitHub only reads
  workflows at the repository root. Relocated with a working-directory and a
  billsplit/** path filter.

- The app's coverage gate moved to python_pkg/billsplit_coverage/ so every
  Python file stays under python_pkg/, and gained a test suite at 100% branch
  coverage as that location requires. billsplit/ now holds only Dart.

Also replaces the stock Flutter icon with a family icon from the shared
generator. The glyph is a filled silhouette because the family's 72px stroke
cannot carry a fine torn edge, and the split is an evenodd-cut perforation so
the holes stay transparent on the foreground and monochrome layers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:22:22 +02:00

79 lines
2.4 KiB
Dart

import 'dart:convert';
import 'package:billsplit/domain/models.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('formatPln handles zero, cents padding and negatives', () {
expect(formatPln(0), '0,00 zł');
expect(formatPln(5), '0,05 zł');
expect(formatPln(123456), '1234,56 zł');
expect(formatPln(-7), '-0,07 zł');
});
test('Ids are unique and ordered enough', () {
final a = Ids.next();
final b = Ids.next();
expect(a, isNot(b));
});
test('Person/Group/Assignment/Item/Receipt JSON roundtrips', () {
final p = Person(id: 'p1', name: 'Ala', drinks: false);
expect(Person.fromJson(p.toJson()).drinks, isFalse);
final g = Group(id: 'g1', name: 'Meat', memberIds: {'p1'});
expect(Group.fromJson(g.toJson()).memberIds, {'p1'});
final a = Assignment(mode: AssignMode.group, groupId: 'g1');
final a2 = Assignment.fromJson(a.toJson());
expect(a2.mode, AssignMode.group);
expect(a2.groupId, 'g1');
final i = Item(
id: 'i1',
name: 'Piwo',
totalGr: 500,
qty: 2,
unitPriceGr: 250,
discountGr: -100,
category: Categories.alcohol,
assignment: Assignment(mode: AssignMode.custom, customIds: {'p1'}),
);
final i2 = Item.fromJson(
jsonDecode(jsonEncode(i.toJson())) as Map<String, dynamic>,
);
expect(i2.assignment.customIds, {'p1'});
expect(i2.discountGr, -100);
final r = Receipt(
id: 'r1',
title: 't',
store: 's',
items: [i],
participantIds: {'p1'},
paidTotalGr: 500,
);
final r2 = Receipt.fromJson(
jsonDecode(jsonEncode(r.toJson())) as Map<String, dynamic>,
);
expect(r2.itemsTotalGr, 500);
expect(r2.participantIds, {'p1'});
expect(r2.store, 's');
});
test('fromJson tolerates missing optional fields', () {
final p = Person.fromJson(const {'id': 'x', 'name': 'n'});
expect(p.drinks, isTrue);
final g = Group.fromJson(const {'id': 'x', 'name': 'n'});
expect(g.memberIds, isEmpty);
final a = Assignment.fromJson(const {});
expect(a.mode, AssignMode.everyone);
final i = Item.fromJson(const {'id': 'x', 'name': 'n', 'totalGr': 1});
expect(i.qty, 1);
expect(i.category, Categories.general);
final r = Receipt.fromJson(const {'id': 'x', 'title': 't', 'date': 'bad'});
expect(r.items, isEmpty);
expect(r.paidTotalGr, isNull);
});
}