3 Commits
Author SHA1 Message Date
dh 5b6125d086 fix(energy-pie): represent indeterminate shares and preserve prepared styling
Tests / test (push) Successful in 50s
2026-10-06 17:40:42 +00:00
dh c5f924bd19 fix(metadata): set Enelix Utils author to Enelix
Tests / test (push) Successful in 50s
2026-10-06 13:38:01 +00:00
dh 82860c0eb9 fix(energiediagramm): Visualisierungsthema übernehmen
Tests / test (push) Successful in 1m51s
2026-10-01 15:34:23 +00:00
12 changed files with 279 additions and 30 deletions
+8
View File
@@ -2,6 +2,12 @@
Alle wesentlichen Aenderungen an Enelix Utils werden in dieser Datei dokumentiert. Alle wesentlichen Aenderungen an Enelix Utils werden in dieser Datei dokumentiert.
## Unreleased
- Energiediagramm zeigt wieder den urspruenglichen dunklen Energy-Pie-Hintergrund
mit blauen und violetten Farbverlaeufen, auch bei hellem Visualisierungsthema.
Bedienelemente bleiben lesbar; Konfiguration und Berechnungen sind unveraendert.
## 0.1 Build 1 - Erste Beta - 2026-09-29 ## 0.1 Build 1 - Erste Beta - 2026-09-29
### Enthalten ### Enthalten
@@ -14,6 +20,8 @@ Alle wesentlichen Aenderungen an Enelix Utils werden in dieser Datei dokumentier
### Beta-Aenderungen ### Beta-Aenderungen
- Energiediagramm uebernimmt Kachel- und Textfarbe der Visualisierung; Zeitraum-
und Datumsauswahl bleiben in hellen und dunklen Themen lesbar.
- Dezimale IP-Symcon-Farbwerte werden im Energiediagramm korrekt verarbeitet. - Dezimale IP-Symcon-Farbwerte werden im Energiediagramm korrekt verarbeitet.
- Kachelabstand fuer den Symcon-Kopf und versionsabhaengiger Vollbildmodus. - Kachelabstand fuer den Symcon-Kopf und versionsabhaengiger Vollbildmodus.
- Vollstaendige PHP-, Struktur- und Symcon-Modultests fuer alle Module. - Vollstaendige PHP-, Struktur- und Symcon-Modultests fuer alle Module.
+10 -3
View File
@@ -31,9 +31,16 @@ dezimalen Farbwerte.
## Darstellung ## Darstellung
Der Inhalt beginnt unterhalb des von Symcon eingeblendeten Kachelkopfs, damit Der Inhalt beginnt unterhalb des von Symcon eingeblendeten Kachelkopfs, damit
Zeitraum und Datum vollstaendig bedienbar bleiben. Beim Oeffnen und beim erneuten Zeitraum und Datum vollstaendig bedienbar bleiben. Der urspruengliche Energy-Pie-
Sichtbarwerden wird die Darstellung aktualisiert. Das Diagrammraster wechselt Hintergrund mit dunkler Grundfarbe und blauen/violetten Farbverlaeufen bleibt
bei geringer Breite automatisch von zwei Spalten auf eine Spalte. auch in einem hellen Visualisierungsthema sichtbar. Text, Auswahlfelder und
native Datumselemente verwenden innerhalb des Moduls dazu passende helle
Schrift und dunkle Bedienflaechen. Die konfigurierten Diagrammfarben bleiben
unveraendert; eine Konfigurationsmigration ist nicht erforderlich. Die
Hintergrundebene blockiert weder Klicks noch Scrollen. Beim Oeffnen und beim
erneuten Sichtbarwerden wird die Darstellung aktualisiert. Das
Diagrammraster wechselt bei geringer Breite automatisch von zwei Spalten auf
eine Spalte.
IP-Symcon 8.x unterstuetzt HTML-SDK-Darstellungen nur in der normalen Kachel. IP-Symcon 8.x unterstuetzt HTML-SDK-Darstellungen nur in der normalen Kachel.
Der vom Client angebotene Vergroesserungsdialog kann deshalb leer bleiben. Der vom Client angebotene Vergroesserungsdialog kann deshalb leer bleiben.
+18
View File
@@ -0,0 +1,18 @@
# Nicht eindeutig bestimmbare Energieanteile
Die optionale Property `EnergieanteileBerechenbar` ist standardmaessig true und
erhaelt das bisherige Verhalten. Setzt ein aufrufender Manager sie auf false,
bleiben gemessene Summen erhalten. Eigenverbrauch und zugehoerige Anteile werden
als null geliefert und als nicht bestimmbar dargestellt, niemals als 0 Prozent.
Anwendungsfall: Netzbezug enthaelt SDL, die Hauslast wurde um SDL bereinigt.
Ohne eindeutige Zuordnung laesst sich der solare Anteil nicht exakt bestimmen.
Die Policy gehoert in EMS; Utils implementiert nur die generische Darstellung.
Individuelle Quellen, Farben und Energiezaehler werden nicht veraendert. Die
bereits vorbereitete Wiederherstellung des dunklen Pie-Hintergrunds bleibt
erhalten. Utils vor einem Manager mit dieser neuen Property aktualisieren.
Validierung: 58 PHPUnit-Tests und vier zusaetzliche JavaScript-Visualisierungstests
erfolgreich; unbekannte, alte numerische und spaeter wieder verfuegbare Anteile
sind abgedeckt.
+9
View File
@@ -32,6 +32,15 @@
"maximum": 1000000, "maximum": 1000000,
"digits": 6 "digits": 6
}, },
{
"type": "CheckBox",
"name": "EnergieanteileBerechenbar",
"caption": "Eigenverbrauch und Autarkie aus gemeinsamer Energiebilanz berechnen"
},
{
"type": "Label",
"caption": "Bei abweichenden Messgrenzen oder nicht zuordenbaren Speicherfluessen ausschalten. Energiesummen bleiben sichtbar; Anteile werden als nicht bestimmbar gekennzeichnet."
},
{ {
"type": "Select", "type": "Select",
"name": "Standardzeitraum", "name": "Standardzeitraum",
+10 -9
View File
@@ -82,16 +82,17 @@ final class EnergyChartCalculator
* Einspeisung: float, * Einspeisung: float,
* Netzbezug: float, * Netzbezug: float,
* Hausverbrauch: float, * Hausverbrauch: float,
* Eigenverbrauch: float, * Eigenverbrauch: float|null,
* Eigenverbrauchsquote: float, * Eigenverbrauchsquote: float|null,
* Autarkiegrad: float * Autarkiegrad: float|null
* } * }
*/ */
public static function metrics( public static function metrics(
float $production, float $production,
float $feedIn, float $feedIn,
float $grid, float $grid,
?float $consumption = null ?float $consumption = null,
bool $sharesAvailable = true
): array { ): array {
$production = max(0.0, $production); $production = max(0.0, $production);
$feedIn = max(0.0, $feedIn); $feedIn = max(0.0, $feedIn);
@@ -106,13 +107,13 @@ final class EnergyChartCalculator
'Einspeisung' => $feedIn, 'Einspeisung' => $feedIn,
'Netzbezug' => $grid, 'Netzbezug' => $grid,
'Hausverbrauch' => $house, 'Hausverbrauch' => $house,
'Eigenverbrauch' => $selfConsumption, 'Eigenverbrauch' => $sharesAvailable ? $selfConsumption : null,
'Eigenverbrauchsquote' => $production > 0.0 'Eigenverbrauchsquote' => !$sharesAvailable ? null : ($production > 0.0
? min(100.0, ($selfConsumption / $production) * 100.0) ? min(100.0, ($selfConsumption / $production) * 100.0)
: 0.0, : 0.0),
'Autarkiegrad' => $house > 0.0 'Autarkiegrad' => !$sharesAvailable ? null : ($house > 0.0
? min(100.0, ($selfConsumption / $house) * 100.0) ? min(100.0, ($selfConsumption / $house) * 100.0)
: 0.0, : 0.0),
]; ];
} }
+74 -15
View File
@@ -63,19 +63,41 @@
html, html,
body { body {
min-width: 0; min-width: 0;
height: 100%;
min-height: 100%; min-height: 100%;
margin: 0; margin: 0;
color: var(--content-color, inherit);
background-color: var(--card-color, transparent);
} }
#energy-app { #energy-app {
--card-color: #121216;
--content-color: #f5f5f7;
position: relative;
isolation: isolate;
color-scheme: dark;
width: 100%; width: 100%;
min-height: 100%; min-height: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 58px 14px 14px; padding: 58px 14px 14px;
overflow: auto; overflow: auto;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif; font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
color: inherit; color: var(--content-color, inherit);
background: transparent; background-color: var(--card-color, transparent);
}
/* Keep the original Energy Pie colors behind the scrollable controls. */
#energy-app::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background:
radial-gradient(520px 420px at 18% 18%, rgba(99, 179, 255, 0.42), transparent 60%),
radial-gradient(560px 460px at 82% 28%, rgba(168, 85, 247, 0.38), transparent 62%),
radial-gradient(620px 520px at 55% 85%, rgba(99, 179, 255, 0.26), transparent 64%);
filter: blur(18px);
pointer-events: none;
} }
.toolbar, .toolbar,
@@ -104,11 +126,16 @@
border: 1px solid rgba(127, 127, 127, 0.45); border: 1px solid rgba(127, 127, 127, 0.45);
border-radius: 6px; border-radius: 6px;
padding: 7px 10px; padding: 7px 10px;
color: inherit; color: var(--content-color, inherit);
background: rgba(127, 127, 127, 0.08); background-color: var(--card-color, transparent);
font: inherit; font: inherit;
} }
select option {
color: var(--content-color, CanvasText);
background-color: var(--card-color, Canvas);
}
button { button {
cursor: pointer; cursor: pointer;
font-weight: 650; font-weight: 650;
@@ -221,6 +248,11 @@
dominant-baseline: middle; dominant-baseline: middle;
} }
.donut-label.unavailable {
font-size: 14px;
font-weight: 600;
}
.error { .error {
min-height: 18px; min-height: 18px;
margin-top: 8px; margin-top: 8px;
@@ -277,6 +309,23 @@
const selfConsumptionChart = document.getElementById('self-consumption-chart'); const selfConsumptionChart = document.getElementById('self-consumption-chart');
const autarkyChart = document.getElementById('autarky-chart'); const autarkyChart = document.getElementById('autarky-chart');
function syncColorScheme() {
const cardColor = getComputedStyle(document.documentElement)
.getPropertyValue('--card-color')
.trim();
const match = cardColor.match(/^#?([0-9a-f]{6})$/i);
if (!match) {
return;
}
const value = Number.parseInt(match[1], 16);
const red = (value >> 16) & 255;
const green = (value >> 8) & 255;
const blue = value & 255;
const luminance = ((0.2126 * red) + (0.7152 * green) + (0.0722 * blue)) / 255;
document.documentElement.style.colorScheme = luminance < 0.5 ? 'dark' : 'light';
}
function send(action, value) { function send(action, value) {
if (typeof requestAction !== 'function') { if (typeof requestAction !== 'function') {
return; return;
@@ -342,6 +391,8 @@
} }
function donut(target, percent, color) { function donut(target, percent, color) {
const available = percent !== null && percent !== undefined
&& Number.isFinite(Number(percent));
const value = Math.max(0, Math.min(100, asNumber(percent))); const value = Math.max(0, Math.min(100, asNumber(percent)));
const radius = 58; const radius = 58;
const circumference = 2 * Math.PI * radius; const circumference = 2 * Math.PI * radius;
@@ -349,15 +400,17 @@
target.innerHTML = [ target.innerHTML = [
'<svg viewBox="0 0 176 176" role="img" aria-label="' '<svg viewBox="0 0 176 176" role="img" aria-label="'
+ value.toFixed(1) + ' Prozent">', + (available ? value.toFixed(1) + ' Prozent' : 'Nicht bestimmbar') + '">',
'<circle class="donut-track" cx="88" cy="88" r="' + radius '<circle class="donut-track" cx="88" cy="88" r="' + radius
+ '" fill="none" stroke-width="18"></circle>', + '" fill="none" stroke-width="18"></circle>',
'<circle class="donut-value" cx="88" cy="88" r="' + radius available ? '<circle class="donut-value" cx="88" cy="88" r="' + radius
+ '" fill="none" stroke="' + color + '" stroke-width="18"' + '" fill="none" stroke="' + color + '" stroke-width="18"'
+ ' stroke-linecap="round" transform="rotate(-90 88 88)"' + ' stroke-linecap="round" transform="rotate(-90 88 88)"'
+ ' stroke-dasharray="' + filled + ' ' + circumference + '"></circle>', + ' stroke-dasharray="' + filled + ' ' + circumference + '"></circle>' : '',
'<text class="donut-label" x="88" y="91">' available ? '<text class="donut-label" x="88" y="91">'
+ value.toFixed(1) + '%</text>', + value.toFixed(1) + '%</text>'
: '<text class="donut-label unavailable" x="88" y="80">'
+ '<tspan x="88">Nicht</tspan><tspan x="88" dy="20">bestimmbar</tspan></text>',
'</svg>' '</svg>'
].join(''); ].join('');
} }
@@ -372,6 +425,7 @@
return; return;
} }
syncColorScheme();
range.value = data.range || 'day'; range.value = data.range || 'day';
date.value = data.date || ''; date.value = data.date || '';
customStart.value = data.customStart || ''; customStart.value = data.customStart || '';
@@ -396,19 +450,24 @@
const colors = data.colors || {}; const colors = data.colors || {};
donut( donut(
selfConsumptionChart, selfConsumptionChart,
values.Eigenverbrauchsquote, data.sharesAvailable === false ? null : values.Eigenverbrauchsquote,
colors.selfConsumption || '#2F80ED' colors.selfConsumption || '#2F80ED'
); );
donut( donut(
autarkyChart, autarkyChart,
values.Autarkiegrad, data.sharesAvailable === false ? null : values.Autarkiegrad,
colors.autarky || '#22A06B' colors.autarky || '#22A06B'
); );
message.hidden = data.hasData !== false; const hints = [];
message.textContent = data.hasData === false if (data.hasData === false) {
? (data.noDataHint || 'Keine Archivwerte verfügbar.') hints.push(data.noDataHint || 'Keine Archivwerte verfügbar.');
: ''; }
if (data.sharesAvailable === false) {
hints.push(data.attributionHint || 'Eigenverbrauch und Autarkie sind nicht eindeutig bestimmbar.');
}
message.hidden = hints.length === 0;
message.textContent = hints.join(' ');
} }
window.handleMessage = function (data) { window.handleMessage = function (data) {
+8 -2
View File
@@ -25,6 +25,7 @@ class Energiediagramm extends IPSModule
$this->RegisterPropertyInteger('NetzbezugZaehlerID', 0); $this->RegisterPropertyInteger('NetzbezugZaehlerID', 0);
$this->RegisterPropertyInteger('VerbrauchZaehlerID', 0); $this->RegisterPropertyInteger('VerbrauchZaehlerID', 0);
$this->RegisterPropertyFloat('Zaehlerfaktor', 1.0); $this->RegisterPropertyFloat('Zaehlerfaktor', 1.0);
$this->RegisterPropertyBoolean('EnergieanteileBerechenbar', true);
$this->RegisterPropertyString('Standardzeitraum', 'day'); $this->RegisterPropertyString('Standardzeitraum', 'day');
$this->RegisterPropertyInteger('Aktualisierungsintervall', 60); $this->RegisterPropertyInteger('Aktualisierungsintervall', 60);
$this->RegisterPropertyString('FarbeEigenverbrauch', self::COLOR_SELF_CONSUMPTION); $this->RegisterPropertyString('FarbeEigenverbrauch', self::COLOR_SELF_CONSUMPTION);
@@ -253,6 +254,7 @@ class Energiediagramm extends IPSModule
$customStart, $customStart,
$customEnd $customEnd
); );
$sharesAvailable = $this->ReadPropertyBoolean('EnergieanteileBerechenbar');
$payload = [ $payload = [
'range' => $range, 'range' => $range,
@@ -263,6 +265,9 @@ class Energiediagramm extends IPSModule
'tEnd' => $end, 'tEnd' => $end,
'hasData' => false, 'hasData' => false,
'noDataHint' => 'Energiezähler konfigurieren.', 'noDataHint' => 'Energiezähler konfigurieren.',
'sharesAvailable' => $sharesAvailable,
'attributionHint' => $sharesAvailable ? ''
: 'Eigenverbrauch und Autarkie sind bei unterschiedlichen Messgrenzen nicht eindeutig bestimmbar. Die Energiesummen bleiben separat sichtbar.',
'colors' => [ 'colors' => [
'selfConsumption' => EnergyChartCalculator::normalizeColor( 'selfConsumption' => EnergyChartCalculator::normalizeColor(
$this->ReadPropertyString('FarbeEigenverbrauch'), $this->ReadPropertyString('FarbeEigenverbrauch'),
@@ -273,7 +278,7 @@ class Energiediagramm extends IPSModule
self::COLOR_AUTARKY self::COLOR_AUTARKY
), ),
], ],
'values' => EnergyChartCalculator::metrics(0.0, 0.0, 0.0), 'values' => EnergyChartCalculator::metrics(0.0, 0.0, 0.0, null, $sharesAvailable),
]; ];
if (!$this->configurationIsValid()) { if (!$this->configurationIsValid()) {
@@ -329,7 +334,8 @@ class Energiediagramm extends IPSModule
$production['value'] * $factor, $production['value'] * $factor,
$feedIn['value'] * $factor, $feedIn['value'] * $factor,
$grid['value'] * $factor, $grid['value'] * $factor,
$consumption $consumption,
$sharesAvailable
); );
if (!$payload['hasData']) { if (!$payload['hasData']) {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"id": "{05DE4157-F0E8-4CA9-8B5E-D6AB9310775F}", "id": "{05DE4157-F0E8-4CA9-8B5E-D6AB9310775F}",
"name": "Enelix Utils", "name": "Enelix Utils",
"author": "Belevo AG", "author": "Enelix",
"url": "https://git.belevo.ch/ENELIX/Enelix-Utils", "url": "https://git.belevo.ch/ENELIX/Enelix-Utils",
"compatibility": { "compatibility": {
"version": "8.0" "version": "8.0"
@@ -33,6 +33,7 @@ final class EnergiediagrammModulstrukturTest extends TestCase
'EinspeisungZaehlerID', 'EinspeisungZaehlerID',
'NetzbezugZaehlerID', 'NetzbezugZaehlerID',
'VerbrauchZaehlerID', 'VerbrauchZaehlerID',
'EnergieanteileBerechenbar',
'Standardzeitraum', 'Standardzeitraum',
'FarbeEigenverbrauch', 'FarbeEigenverbrauch',
'FarbeAutarkie', 'FarbeAutarkie',
@@ -51,10 +52,37 @@ final class EnergiediagrammModulstrukturTest extends TestCase
self::assertStringContainsString('grid-template-columns: minmax(0, 1fr)', $html); self::assertStringContainsString('grid-template-columns: minmax(0, 1fr)', $html);
self::assertStringContainsString('padding: 58px 14px 14px', $html); self::assertStringContainsString('padding: 58px 14px 14px', $html);
self::assertStringContainsString('padding: 58px 10px 10px', $html); self::assertStringContainsString('padding: 58px 10px 10px', $html);
self::assertStringContainsString('background-color: var(--card-color, transparent)', $html);
self::assertStringContainsString('color: var(--content-color, CanvasText)', $html);
self::assertStringContainsString('function syncColorScheme()', $html);
self::assertStringContainsString('SetCustomRange', $html); self::assertStringContainsString('SetCustomRange', $html);
self::assertStringNotContainsString("addEventListener('load'", $html); self::assertStringNotContainsString("addEventListener('load'", $html);
} }
public function testOriginalEnergyPieBackgroundStaysReadableAndNonInteractive(): void
{
$html = file_get_contents(__DIR__ . '/../Energiediagramm/module.html');
self::assertNotFalse($html);
self::assertMatchesRegularExpression('/#energy-app\\s*\\{[^}]*--card-color: #121216;/s', $html);
self::assertMatchesRegularExpression('/#energy-app\\s*\\{[^}]*--content-color: #f5f5f7;/s', $html);
self::assertMatchesRegularExpression('/#energy-app\\s*\\{[^}]*color-scheme: dark;/s', $html);
self::assertMatchesRegularExpression('/#energy-app\\s*\\{[^}]*isolation: isolate;/s', $html);
self::assertSame(1, preg_match('/#energy-app::before\\s*\\{([^}]+)\\}/s', $html, $background));
foreach ([
'inset: 0',
'z-index: -1',
'radial-gradient(520px 420px at 18% 18%, rgba(99, 179, 255, 0.42), transparent 60%)',
'radial-gradient(560px 460px at 82% 28%, rgba(168, 85, 247, 0.38), transparent 62%)',
'radial-gradient(620px 520px at 55% 85%, rgba(99, 179, 255, 0.26), transparent 64%)',
'filter: blur(18px)',
'pointer-events: none',
] as $style) {
self::assertStringContainsString($style, $background[1]);
}
self::assertStringContainsString('overflow: auto', $html);
self::assertStringNotContainsString('touch-action: none', $html);
}
public function testModuleEmbedsInitialPayloadAndEnablesSupportedVisualization(): void public function testModuleEmbedsInitialPayloadAndEnablesSupportedVisualization(): void
{ {
$module = file_get_contents(__DIR__ . '/../Energiediagramm/module.php'); $module = file_get_contents(__DIR__ . '/../Energiediagramm/module.php');
+24
View File
@@ -127,4 +127,28 @@ final class EnergiediagrammTest extends TestCase
EnergyChartCalculator::normalizeColor('-1', '#22a06b') EnergyChartCalculator::normalizeColor('-1', '#22a06b')
); );
} }
public function testMixedMeasurementBoundariesKeepTotalsWithoutInventingShares(): void
{
$values = EnergyChartCalculator::metrics(10.0, 0.0, 10.0, 5.0, false);
self::assertSame(10.0, $values['Produktion']);
self::assertSame(0.0, $values['Einspeisung']);
self::assertSame(10.0, $values['Netzbezug']);
self::assertSame(5.0, $values['Hausverbrauch']);
self::assertNull($values['Eigenverbrauch']);
self::assertNull($values['Eigenverbrauchsquote']);
self::assertNull($values['Autarkiegrad']);
}
public function testUnavailableSharesRemainNullWithZeroEnergy(): void
{
$unavailable = EnergyChartCalculator::metrics(0.0, 0.0, 0.0, null, false);
self::assertNull($unavailable['Eigenverbrauchsquote']);
self::assertNull($unavailable['Autarkiegrad']);
$available = EnergyChartCalculator::metrics(0.0, 0.0, 0.0);
self::assertSame(0.0, $available['Eigenverbrauchsquote']);
self::assertSame(0.0, $available['Autarkiegrad']);
}
} }
+88
View File
@@ -0,0 +1,88 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
function visualization(initialState) {
const elements = new Map();
const element = id => {
if (!elements.has(id)) {
elements.set(id, {
innerHTML: '', textContent: '', value: '', hidden: false,
addEventListener() {}, classList: { toggle() {} }
});
}
return elements.get(id);
};
const window = { setTimeout() {} };
const document = {
getElementById: element,
documentElement: { style: {} },
addEventListener() {}
};
const html = fs.readFileSync(path.join(__dirname, '../Energiediagramm/module.html'), 'utf8');
const script = html.match(/<script>([\s\S]*?)<\/script>/)[1]
.replace('__ENELIX_INITIAL_STATE__', JSON.stringify(initialState));
vm.runInNewContext(script, {
window, document,
getComputedStyle: () => ({ getPropertyValue: () => '' })
});
return { element, window };
}
function payload(overrides = {}) {
return {
range: 'day', date: '2026-10-06', hasData: true,
values: {
Produktion: 10, Hausverbrauch: 5, Netzbezug: 10, Einspeisung: 0,
Eigenverbrauchsquote: null, Autarkiegrad: null
},
...overrides
};
}
test('unavailable shares show neutral rings while preserving energy totals', () => {
const ui = visualization(payload({ sharesAvailable: false, attributionHint: 'Different measurement boundaries.' }));
for (const id of ['self-consumption-chart', 'autarky-chart']) {
const html = ui.element(id).innerHTML;
assert.match(html, /Nicht bestimmbar/);
assert.doesNotMatch(html, /donut-value|0\.0%/);
}
assert.match(ui.element('summary').innerHTML, /Produktion/);
assert.match(ui.element('summary').innerHTML, /10\.00 kWh/);
assert.match(ui.element('summary').innerHTML, /5\.00 kWh/);
assert.equal(ui.element('message').hidden, false);
assert.equal(ui.element('message').textContent, 'Different measurement boundaries.');
});
test('unavailable flag cannot display stale numeric percentages', () => {
const state = payload({ sharesAvailable: false });
state.values.Eigenverbrauchsquote = 75;
state.values.Autarkiegrad = 50;
const ui = visualization(state);
assert.doesNotMatch(ui.element('self-consumption-chart').innerHTML, /75\.0%/);
assert.doesNotMatch(ui.element('autarky-chart').innerHTML, /50\.0%/);
});
test('legacy numeric percentages and later recovery remain compatible', () => {
const ui = visualization(payload({ sharesAvailable: false }));
const state = payload();
state.values.Eigenverbrauchsquote = 75;
state.values.Autarkiegrad = 81.25;
ui.window.handleMessage(JSON.stringify(state));
assert.match(ui.element('self-consumption-chart').innerHTML, /75\.0%/);
assert.match(ui.element('autarky-chart').innerHTML, /81\.3%/);
assert.equal(ui.element('message').hidden, true);
});
test('missing archive values do not hide the attribution limitation', () => {
const ui = visualization(payload({
hasData: false, noDataHint: 'No archive data.',
sharesAvailable: false, attributionHint: 'Different measurement boundaries.'
}));
assert.equal(ui.element('message').textContent,
'No archive data. Different measurement boundaries.');
});
+1
View File
@@ -15,6 +15,7 @@ final class LibraryMetadataTest extends TestCase
$daten = json_decode($inhalt, true, 512, JSON_THROW_ON_ERROR); $daten = json_decode($inhalt, true, 512, JSON_THROW_ON_ERROR);
self::assertSame('Enelix Utils', $daten['name']); self::assertSame('Enelix Utils', $daten['name']);
self::assertSame('Enelix', $daten['author']);
self::assertSame('8.0', $daten['compatibility']['version']); self::assertSame('8.0', $daten['compatibility']['version']);
self::assertSame('0.1', $daten['version']); self::assertSame('0.1', $daten['version']);
self::assertSame(1, $daten['build']); self::assertSame(1, $daten['build']);