import 'dart:convert'; import 'dart:io'; import 'package:etm_powersync_app/providers/load_config_provider.dart'; import 'package:etm_powersync_app/services/load_priority.dart'; import 'package:flutter_test/flutter_test.dart'; List> _configs(String fixture) { final raw = File('test/fixtures/$fixture').readAsStringSync(); final map = jsonDecode(raw) as Map; return (map['loadConfigs'] as List).cast>(); } void main() { // ─────────────────────────────────────────────────────────────────────────── group('§7-4 — domaine', () { test('les deux charges de .75 arrivent « non classées », jamais rangées d\'office', () { final p = LoadConfigProvider()..adoptForTest(_configs('loadconfig_hems75_v3.json')); // `domain: ""` sort toujours de GetLoadConfig (sérialisation par méta-objet) ; // vide ET absent se lisent tous deux « non classée ». expect(p.groups.length, 1); expect(p.groups.single.isUnclassified, isTrue); expect(p.groups.single.loads.map((e) => e.id), ['chauffe-eau', 'pac-terrain']); expect(p.hasPendingChanges, isFalse, reason: 'une simple lecture ne doit jamais produire de brouillon'); }); test('classer une charge la déplace dans son domaine — dans le brouillon seulement', () { final p = LoadConfigProvider()..adoptForTest(_configs('loadconfig_hems75_v3.json')); p.assignDomain('chauffe-eau', 'ecs'); expect(p.hasPendingChanges, isTrue); expect(p.groups.map((g) => g.domain), [kUnclassifiedDomain, 'ecs']); expect(p.groups.first.loads.single.id, 'pac-terrain'); expect(p.groups.last.loads.single.domain, 'ecs'); }); test('la charge utile du mécanisme traverse l\'affectation intacte', () { final source = _configs('loadconfig_hems75_v3.json'); final p = LoadConfigProvider()..adoptForTest(source); p.assignDomain('pac-terrain', 'hvac'); final payload = flattenToPayload(p.groups); final pac = payload.firstWhere((m) => m['id'] == 'pac-terrain'); final origin = source.firstWhere((m) => m['id'] == 'pac-terrain'); // Seuls `domain` et `priority` changent : la table d'états SG-Ready, les relais // vides, `needs` et `powerLevels` sont transportés tels quels. Une // re-sérialisation depuis un modèle typé les amputerait en silence. expect(pac['domain'], 'hvac'); for (final key in origin.keys) { if (key == 'domain' || key == 'priority') continue; expect(pac[key], equals(origin[key]), reason: 'la clé $key a été altérée par l\'affectation de domaine'); } }); test('déclasser une charge la ramène au bucket « non classé »', () { final p = LoadConfigProvider()..adoptForTest(_configs('loadconfig_hems75_v3.json')); p.assignDomain('chauffe-eau', 'ecs'); p.assignDomain('chauffe-eau', kUnclassifiedDomain); final ecs = p.groups.where((g) => g.domain == 'ecs'); expect(ecs, isEmpty, reason: 'un groupe vidé ne doit pas subsister'); expect( p.groups .firstWhere((g) => g.isUnclassified) .loads .map((e) => e.id) .toSet(), {'chauffe-eau', 'pac-terrain'}); }); test('l\'aplatissement renumérote 1..N sans jamais réordonner les groupes', () { final p = LoadConfigProvider()..adoptForTest(_configs('loadconfig_hems75_v3.json')); p.assignDomain('chauffe-eau', 'ecs'); final payload = flattenToPayload(p.groups); expect(payload.map((m) => m['id']), ['pac-terrain', 'chauffe-eau']); expect(payload.map((m) => m['priority']), [1, 2]); }); test('les domaines proposés sont exactement l\'énumération fermée du plugin', () { // `SetLoadConfig` rejette EN BLOC une valeur hors énumération : proposer un // domaine inventé ferait échouer l'enregistrement de toutes les charges. expect(kAssignableDomains.toSet(), {'ev', 'ecs', 'hvac', 'heating', 'battery'}); }); test('l\'ordre de mise en service ne contient pas la batterie', () { // Classable, mais sa place ne s'exprime pas en watts : elle s'exprime en cible // de SOC, hors périmètre de l'ordre de service. expect(kDefaultDomainOrder, isNot(contains('battery'))); }); }); }