fixes before build
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:receipity/models/line_item.dart';
|
||||
import 'package:receipity/models/product.dart';
|
||||
import 'package:receipity/utils/product_names.dart';
|
||||
|
||||
import 'test_helpers.dart';
|
||||
|
||||
Product _product(int id, String name, {String? supermarket}) => Product(
|
||||
id: id,
|
||||
name: name,
|
||||
lastPrice: 1,
|
||||
timesSeen: 1,
|
||||
updatedAt: DateTime(2026, 1, 1),
|
||||
supermarket: supermarket,
|
||||
);
|
||||
|
||||
void main() {
|
||||
ensureTestSqlite();
|
||||
|
||||
group('nameConflictGroups', () {
|
||||
test('groups products that differ only by case at the same store', () {
|
||||
final groups = nameConflictGroups([
|
||||
_product(1, 'Broccoli', supermarket: 'Jumbo'),
|
||||
_product(2, 'BROCCOLI', supermarket: 'Jumbo'),
|
||||
_product(3, 'Milk', supermarket: 'Jumbo'),
|
||||
]);
|
||||
|
||||
expect(groups, hasLength(1));
|
||||
expect(groups.single.map((product) => product.id), [1, 2]);
|
||||
});
|
||||
|
||||
test('ignores same name at different stores', () {
|
||||
final groups = nameConflictGroups([
|
||||
_product(1, 'Broccoli', supermarket: 'Jumbo'),
|
||||
_product(2, 'BROCCOLI', supermarket: 'Lidl'),
|
||||
]);
|
||||
|
||||
expect(groups, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('buildNameConflictGroups', () {
|
||||
test('finds conflicts from trip lines linked to different products', () {
|
||||
final groups = buildNameConflictGroups(
|
||||
products: [
|
||||
_product(1, 'Broccoli', supermarket: 'Jumbo'),
|
||||
_product(2, 'BROCCOLI', supermarket: 'Jumbo'),
|
||||
],
|
||||
shopLines: const [
|
||||
(productId: 1, name: 'Broccoli', supermarket: 'Jumbo'),
|
||||
(productId: 2, name: 'BROCCOLI', supermarket: 'Jumbo'),
|
||||
],
|
||||
);
|
||||
|
||||
expect(groups, hasLength(1));
|
||||
expect(groups.single.products.map((product) => product.id), [1, 2]);
|
||||
expect(groups.single.supermarket, 'Jumbo');
|
||||
});
|
||||
});
|
||||
|
||||
group('findSpellingVariants', () {
|
||||
test('finds multiple receipt spellings for one product at one store', () {
|
||||
final variants = findSpellingVariants(
|
||||
products: [_product(1, 'Broccoli', supermarket: 'Jumbo')],
|
||||
shopLines: const [
|
||||
(productId: 1, name: 'Broccoli', supermarket: 'Jumbo'),
|
||||
(productId: 1, name: 'BROCCOLI', supermarket: 'Jumbo'),
|
||||
],
|
||||
);
|
||||
|
||||
expect(variants, hasLength(1));
|
||||
expect(variants.single.product.id, 1);
|
||||
expect(variants.single.supermarket, 'Jumbo');
|
||||
expect(variants.single.variants, ['Broccoli', 'BROCCOLI']);
|
||||
});
|
||||
|
||||
test('ignores different spellings at different stores', () {
|
||||
final variants = findSpellingVariants(
|
||||
products: [_product(1, 'Broccoli', supermarket: 'Jumbo')],
|
||||
shopLines: const [
|
||||
(productId: 1, name: 'Broccoli', supermarket: 'Jumbo'),
|
||||
(productId: 1, name: 'BROCCOLI', supermarket: 'Lidl'),
|
||||
],
|
||||
);
|
||||
|
||||
expect(variants, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('same name at different supermarkets creates separate products', () async {
|
||||
final repository = memoryRepository();
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 1),
|
||||
items: [LineItem(id: '1', name: 'Milk', price: 1.5)],
|
||||
);
|
||||
await repository.saveShop(
|
||||
supermarket: 'Lidl',
|
||||
shoppedAt: DateTime(2026, 8, 2),
|
||||
items: [LineItem(id: '2', name: 'Milk', price: 1.2)],
|
||||
);
|
||||
|
||||
final products = await repository.getAll();
|
||||
expect(products, hasLength(2));
|
||||
expect(products.map((p) => p.supermarket).toSet(), {'Jumbo', 'Lidl'});
|
||||
expect(await repository.getProductNameIssues(), isEmpty);
|
||||
});
|
||||
|
||||
test('getNameConflictGroups finds duplicates in the database', () async {
|
||||
final repository = memoryRepository();
|
||||
await seedCaseConflictCatalog(repository);
|
||||
|
||||
final groups = await repository.getNameConflictGroups();
|
||||
expect(groups, hasLength(1));
|
||||
expect(groups.single.map((product) => product.name).toSet(), {
|
||||
'Broccoli',
|
||||
'BROCCOLI',
|
||||
});
|
||||
});
|
||||
|
||||
test('getProductNameIssues finds receipt spelling variants', () async {
|
||||
final repository = memoryRepository();
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 1),
|
||||
items: [LineItem(id: '1', name: 'Broccoli', price: 1.5)],
|
||||
);
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 2),
|
||||
items: [LineItem(id: '2', name: 'BROCCOLI', price: 1.8)],
|
||||
);
|
||||
|
||||
final issues = await repository.getProductNameIssues();
|
||||
expect(issues.mergeGroups, isEmpty);
|
||||
expect(issues.spellingVariants, hasLength(1));
|
||||
expect(issues.spellingVariants.single.supermarket, 'Jumbo');
|
||||
expect(issues.spellingVariants.single.variants, ['Broccoli', 'BROCCOLI']);
|
||||
});
|
||||
|
||||
test('dismissNameIssue hides a spelling variant', () async {
|
||||
final repository = memoryRepository();
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 1),
|
||||
items: [LineItem(id: '1', name: 'Broccoli', price: 1.5)],
|
||||
);
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 2),
|
||||
items: [LineItem(id: '2', name: 'BROCCOLI', price: 1.8)],
|
||||
);
|
||||
|
||||
final issue =
|
||||
(await repository.getProductNameIssues()).spellingVariants.single;
|
||||
await repository.dismissNameIssue(issue.key);
|
||||
|
||||
expect(await repository.getProductNameIssues(), isEmpty);
|
||||
});
|
||||
|
||||
test('mergeProducts combines purchases onto the kept product', () async {
|
||||
final repository = memoryRepository();
|
||||
await seedCaseConflictCatalog(repository);
|
||||
|
||||
await repository.mergeProducts(keepId: 1, sourceIds: [2]);
|
||||
|
||||
final after = await repository.getAll();
|
||||
expect(after, hasLength(1));
|
||||
expect(after.single.name, 'Broccoli');
|
||||
expect(after.single.timesSeen, 2);
|
||||
expect(await repository.getPurchases(1), hasLength(2));
|
||||
});
|
||||
|
||||
test('unifyProductSpelling updates catalog and receipt lines', () async {
|
||||
final repository = memoryRepository();
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 1),
|
||||
items: [LineItem(id: '1', name: 'Broccoli', price: 1.5)],
|
||||
);
|
||||
await repository.saveShop(
|
||||
supermarket: 'Jumbo',
|
||||
shoppedAt: DateTime(2026, 8, 2),
|
||||
items: [LineItem(id: '2', name: 'BROCCOLI', price: 1.8)],
|
||||
);
|
||||
final product = (await repository.getAll()).single;
|
||||
|
||||
await repository.unifyProductSpelling(
|
||||
productId: product.id,
|
||||
name: 'BROCCOLI',
|
||||
supermarket: 'Jumbo',
|
||||
);
|
||||
|
||||
final updated = (await repository.getProduct(product.id))!;
|
||||
expect(updated.name, 'BROCCOLI');
|
||||
expect((await repository.getProductNameIssues()).isEmpty, isTrue);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user