Config UI: streamline loadpoint creation and unify wording (#31658)

This commit is contained in:
Michael Geers 2026-07-11 13:09:21 +02:00 • committed by GitHub
parent 999bed78c5
commit b900237a8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 246 additions and 91 deletions

View file

@ -0,0 +1,31 @@
<template>
<div v-if="inProgress" class="d-flex align-items-start gap-3 py-2">
<div class="spinner-border flex-shrink-0 text-gray" role="status" aria-hidden="true"></div>
<p class="mb-0 pt-1 text-gray">{{ creatingText }}</p>
</div>
<div v-else class="d-flex flex-column gap-4">
<div class="d-flex align-items-start gap-3">
<SuccessCheck class="flex-shrink-0" />
<p class="mb-0 pt-2">{{ successText }}</p>
</div>
<div class="d-flex align-items-center justify-content-between gap-3">
<slot name="actions" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import SuccessCheck from "../Helper/SuccessCheck.vue";
// Reusable creating -> created status for device creation flows.
export default defineComponent({
name: "CreateFlowStatus",
components: { SuccessCheck },
props: {
inProgress: Boolean,
creatingText: String,
successText: String,
},
});
</script>

View file

@ -9,7 +9,22 @@
@close="onClose"
@dismiss="onDismiss"
>
<div v-if="!loadpointType" class="d-flex flex-column gap-4">
<CreateFlowStatus
v-if="autoCreate"
:in-progress="autoCreateInProgress"
:creating-text="$t(`config.loadpoint.creating.${loadpointType}`)"
:success-text="$t('config.loadpoint.created', { name: values.title })"
>
<template #actions>
<a href="#" @click.prevent="autoCreate = false">
{{ $t("config.loadpoint.advancedConfig") }}
</a>
<button type="button" class="btn btn-primary" @click="finish">
{{ $t("config.general.close") }}
</button>
</template>
</CreateFlowStatus>
<div v-else-if="!loadpointType" class="d-flex flex-column gap-4">
<NewDeviceButton
v-for="t in typeChoices"
:key="t"
@ -90,13 +105,7 @@
</div>
<div v-if="values.charger || !isNew">
<p v-if="isNew && !showAllSettings" class="mt-4 mb-0 text-muted">
{{ $t("config.loadpoint.defaultsHint") }}
<a href="#" @click.prevent="showAllSettings = true">
{{ $t("config.loadpoint.defaultsHintLink") }} </a
>.
</p>
<div class="collapsible-wrapper" :class="{ open: !isNew || showAllSettings }">
<div class="collapsible-wrapper" :class="{ open: !isNew }">
<div class="collapsible-content">
<h6 class="mt-4">{{ $t("config.loadpoint.chargingTitle") }}</h6>
@ -625,10 +634,12 @@ import api from "@/api";
import GenericModal from "../Helper/GenericModal.vue";
import deepClone from "@/utils/deepClone";
import deepEqual from "@/utils/deepEqual";
import sleep from "@/utils/sleep";
import formatter, { POWER_UNIT } from "@/mixins/formatter";
import DeviceRefBox from "./DeviceRefBox.vue";
import NewDeviceButton from "./NewDeviceButton.vue";
import InvalidReferenceAlert from "./InvalidReferenceAlert.vue";
import CreateFlowStatus from "./CreateFlowStatus.vue";
import { handleError, customChargerName, createDeviceUtils } from "./DeviceModal";
import { getModal, openModal, replaceModal, closeModal } from "@/configModal";
import {
@ -688,6 +699,7 @@ export default {
DeviceRefBox,
NewDeviceButton,
InvalidReferenceAlert,
CreateFlowStatus,
},
mixins: [formatter],
props: {
@ -706,12 +718,12 @@ export default {
data() {
return {
isModalVisible: false,
showAllSettings: false,
saving: false,
values: deepClone(defaultValues) as ConfigLoadpoint,
chargerPower: "11kw",
solarMode: "default",
created: false,
autoCreate: false,
autoCreateInProgress: false,
tab: "solar",
powerUnit: POWER_UNIT,
};
@ -724,7 +736,10 @@ export default {
return getModal("loadpoint")?.type as LoadpointType | undefined;
},
modalTitle() {
if (this.isNew) {
if (this.autoCreate && !this.autoCreateInProgress) {
return this.$t(`config.loadpoint.createdTitle.${this.loadpointType}`);
}
if (this.isNew || this.autoCreate) {
return this.$t(`config.loadpoint.titleAdd.${this.loadpointType || "unknown"}`);
}
return this.$t(`config.loadpoint.titleEdit.${this.loadpointType || "unknown"}`);
@ -751,24 +766,24 @@ export default {
},
chargerStatus() {
if (!this.chargerValues || !this.values.charger) {
return {};
return null;
}
return this.chargerValues[this.values.charger] || {};
return this.chargerValues[this.values.charger] || null;
},
chargerSupports1p3p() {
return this.chargerStatus.phases1p3p?.value || false;
return this.chargerStatus?.phases1p3p?.value || false;
},
chargerIsSinglePhase() {
return this.chargerStatus.singlePhase?.value || false;
return this.chargerStatus?.singlePhase?.value || false;
},
chargerIsIntegratedDevice() {
return this.chargerStatus.integratedDevice?.value || false;
return this.chargerStatus?.integratedDevice?.value || false;
},
chargerIsSwitchDevice() {
return this.chargerStatus.switchDevice?.value || false;
return this.chargerStatus?.switchDevice?.value || false;
},
chargerIsHeating() {
return this.chargerStatus.heating?.value === true;
return this.chargerStatus?.heating?.value === true;
},
meterTitle() {
const name = this.values.meter;
@ -857,6 +872,12 @@ export default {
}
}
},
id(newId) {
// replaceModal swaps the id in place (after create) without a remount
if (this.isModalVisible && newId !== undefined && this.values?.id !== newId) {
this.loadConfiguration();
}
},
chargerPower(value) {
if (value === "11kw") {
this.values.minCurrent = 6;
@ -881,8 +902,8 @@ export default {
methods: {
reset() {
this.values = deepClone(defaultValues);
this.created = false;
this.showAllSettings = false;
this.autoCreate = false;
this.autoCreateInProgress = false;
this.updatePhases();
},
async loadConfiguration() {
@ -922,18 +943,30 @@ export default {
}
},
async create() {
this.saving = true;
this.autoCreate = true;
this.autoCreateInProgress = true;
await sleep(1000); // ux pause
try {
await api.post("config/loadpoints", this.values);
this.created = true;
this.emitChanged("added");
// the created response has no id to compare against on next open, so
// reset explicitly instead of relying on the isModalVisible watcher
this.reset();
for (let i = 0; !this.chargerStatus && i < 100; i++) {
await sleep(50);
}
const res = await api.post("config/loadpoints", this.values);
this.$emit("changed", { action: "added" });
// edit mode of the newly loadpoint
replaceModal("loadpoint", {
id: res.data?.id,
type: this.loadpointType || undefined,
});
this.autoCreateInProgress = false;
} catch (e) {
this.autoCreate = false;
this.autoCreateInProgress = false;
handleError(e, "create failed");
}
this.saving = false;
},
async finish() {
await closeModal({ action: "added" });
this.reset();
},
onOpen() {
this.isModalVisible = true;
@ -942,12 +975,17 @@ export default {
this.isModalVisible = false;
},
async onDismiss() {
if (!this.values.id && !this.created) {
// closeModal already cleared the route id, so check values.id, not isNew
if (!this.values.id && !this.autoCreate) {
await this.cleanupDevice("charger", this.values.charger, this.chargers);
await this.cleanupDevice("meter", this.values.meter, this.meters);
this.$emit("dismissed");
this.reset();
return;
}
// keep values so reopening the same loadpoint stays populated
this.autoCreate = false;
this.autoCreateInProgress = false;
},
async cleanupDevice(type: DeviceType, name: string, list: { name: string; id: number }[]) {
const id = list.find((d) => d.name === name)?.id;
@ -965,7 +1003,11 @@ export default {
type: this.loadpointType || undefined,
});
if (result.action === "added" && result.name) {
const instantCreate = this.isNew && !this.values.charger;
this.values.charger = result.name;
if (instantCreate) {
await this.create();
}
} else if (result.action === "removed") {
this.values.charger = "";
}

View file

@ -0,0 +1,38 @@
<template>
<span class="successCheck">
<shopicon-regular-checkmark></shopicon-regular-checkmark>
</span>
</template>
<script lang="ts">
import "@h2d2/shopicons/es/regular/checkmark";
import { defineComponent } from "vue";
export default defineComponent({
name: "SuccessCheck",
});
</script>
<style scoped>
.successCheck {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background: var(--evcc-green);
color: var(--evcc-darkest-green);
animation: successCheckIn var(--evcc-transition-fast, 250ms) ease-in both;
}
@keyframes successCheckIn {
from {
transform: scale(0.5);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
</style>

View file

@ -143,16 +143,16 @@
"template": "Hersteller",
"titleAdd": {
"charging": "Wallbox hinzufügen",
"heating": "Heizung hinzufügen"
"heating": "Heizgerät hinzufügen"
},
"titleEdit": {
"charging": "Wallbox bearbeiten",
"heating": "Heizung bearbeiten"
"heating": "Heizgerät bearbeiten"
},
"type": {
"custom": {
"charging": "Benutzerdefinierte Wallbox",
"heating": "Benutzerdefinierte Heizung"
"heating": "Benutzerdefiniertes Heizgerät"
},
"heatpump": "Benutzerdefinierte Wärmepumpe",
"sgready": "Benutzerdefinierte Wärmepumpe (sg-ready über Plugins)",
@ -396,17 +396,18 @@
"loadpoint": {
"addCharger": {
"charging": "Wallbox hinzufügen",
"heating": "Heizung hinzufügen"
"heating": "Heizgerät hinzufügen"
},
"addMeter": "Zusätzlichen Energiezähler hinzufügen",
"advancedConfig": "Erweiterte Konfiguration",
"cancel": "Abbrechen",
"chargerError": {
"charging": "Wallbox muss konfiguriert sein.",
"heating": "Heizung muss konfiguriert sein."
"heating": "Heizgerät muss konfiguriert sein."
},
"chargerLabel": {
"charging": "Wallbox",
"heating": "Heizung"
"heating": "Heizgerät"
},
"chargerPower11kw": "11 kW",
"chargerPower11kwHelp": "Verwendet einen Strombereich von 6 bis 16 A.",
@ -420,14 +421,21 @@
"circuitInvalid": "Stromkreis existiert nicht",
"circuitLabel": "Stromkreis",
"circuitUnassigned": "nicht zugewiesen",
"created": "„{name}“ wurde angelegt und verwendet sinnvolle Standardwerte.",
"createdTitle": {
"charging": "Ladepunkt erstellt",
"heating": "Heizung erstellt"
},
"creating": {
"charging": "Wallbox gespeichert. Ladepunkt wird erstellt …",
"heating": "Heizgerät gespeichert. Heizung wird erstellt …"
},
"defaultModeHelp": {
"charging": "Lademodus beim Anschließen des Fahrzeugs.",
"heating": "Wird beim Systemstart gesetzt."
},
"defaultModeHelpKeep": "Zuletzt ausgewählter Modus wird beibehalten.",
"defaultModeLabel": "Standard-Modus",
"defaultsHint": "Standardmodus, PV-Überschussverhalten und elektrische Details verwenden sinnvolle Standardwerte.",
"defaultsHintLink": "Einstellungen anpassen",
"delete": "Löschen",
"electricalSubtitle": "Im Zweifelsfall Elektriker fragen.",
"electricalTitle": "Elektrik",
@ -444,7 +452,7 @@
"noVehicles": "Keine Fahrzeuge konfiguriert.",
"option": {
"charging": "Ladepunkt hinzufügen",
"heating": "Heizungsgerät hinzufügen"
"heating": "Heizung hinzufügen"
},
"phases1p": "1-phasig",
"phases3p": "3-phasig",
@ -485,12 +493,12 @@
"titleAdd": {
"charging": "Ladepunkt hinzufügen",
"heating": "Heizung hinzufügen",
"unknown": "Wallbox oder Heizung hinzufügen"
"unknown": "Ladepunkt oder Heizung hinzufügen"
},
"titleEdit": {
"charging": "Ladepunkt bearbeiten",
"heating": "Heizung bearbeiten",
"unknown": "Wallbox oder Heizung bearbeiten"
"unknown": "Ladepunkt oder Heizung bearbeiten"
},
"titleExample": {
"charging": "Garage, Carport, etc.",
@ -508,7 +516,7 @@
"addAdditional": "Zusätzlichen Zähler hinzufügen",
"addConsumer": "Verbraucher hinzufügen",
"addGrid": "Netzzähler hinzufügen",
"addLoadpoint": "Wallbox oder Heizung hinzufügen",
"addLoadpoint": "Ladepunkt oder Heizung hinzufügen",
"addPvBattery": "PV oder Speicher hinzufügen",
"addTariffs": "Tarife hinzufügen",
"addVehicle": "Fahrzeug hinzufügen",
@ -801,7 +809,7 @@
"general": "Allgemein",
"grid": "Netzanschluss",
"integrations": "Integrationen",
"loadpoints": "Laden & Heizen",
"loadpoints": "Ladepunkte & Heizungen",
"meter": "PV & Batterie",
"services": "Dienste",
"system": "System",

View file

@ -143,16 +143,16 @@
"template": "Manufacturer",
"titleAdd": {
"charging": "Add Charger",
"heating": "Add Heater"
"heating": "Add Heating Device"
},
"titleEdit": {
"charging": "Edit Charger",
"heating": "Edit Heater"
"heating": "Edit Heating Device"
},
"type": {
"custom": {
"charging": "User-defined charger",
"heating": "User-defined heater"
"heating": "User-defined heating device"
},
"heatpump": "User-defined heat pump",
"sgready": "User-defined heat pump (sg-ready via plugins)",
@ -396,17 +396,18 @@
"loadpoint": {
"addCharger": {
"charging": "Add charger",
"heating": "Add heater"
"heating": "Add heating device"
},
"addMeter": "Add dedicated energy meter",
"advancedConfig": "Advanced configuration",
"cancel": "Cancel",
"chargerError": {
"charging": "Configuring a charger is required.",
"heating": "Configuring a heater is required."
"heating": "Configuring a heating device is required."
},
"chargerLabel": {
"charging": "Charger",
"heating": "Heater"
"heating": "Heating device"
},
"chargerPower11kw": "11 kW",
"chargerPower11kwHelp": "Will use a current range of 6 to 16 A.",
@ -420,14 +421,21 @@
"circuitInvalid": "Circuit does not exist",
"circuitLabel": "Circuit",
"circuitUnassigned": "unassigned",
"created": "“{name}” was created and uses sensible defaults.",
"createdTitle": {
"charging": "Charging point created",
"heating": "Heater created"
},
"creating": {
"charging": "Charger saved. Creating charging point …",
"heating": "Heating device saved. Creating heater …"
},
"defaultModeHelp": {
"charging": "Charging mode when connecting the vehicle.",
"heating": "Is set when on system start."
},
"defaultModeHelpKeep": "Keeps the last selected mode.",
"defaultModeLabel": "Default mode",
"defaultsHint": "Default mode, solar behaviour and electrical details use sensible defaults.",
"defaultsHintLink": "Adjust settings",
"delete": "Delete",
"electricalSubtitle": "When in doubt, ask your electrician.",
"electricalTitle": "Electrics",
@ -444,7 +452,7 @@
"noVehicles": "No vehicles are configured.",
"option": {
"charging": "Add charging point",
"heating": "Add heating device"
"heating": "Add heater"
},
"phases1p": "1-phase",
"phases3p": "3-phase",
@ -484,13 +492,13 @@
"thresholdEnableLabel": "Enable grid power",
"titleAdd": {
"charging": "Add Charging Point",
"heating": "Add Heating Device",
"unknown": "Add Charger or Heater"
"heating": "Add Heater",
"unknown": "Add Charging Point or Heater"
},
"titleEdit": {
"charging": "Edit Charging Point",
"heating": "Edit Heating Device",
"unknown": "Edit Charger or Heater"
"heating": "Edit Heater",
"unknown": "Edit Charging Point or Heater"
},
"titleExample": {
"charging": "Garage, Carport, etc.",
@ -508,7 +516,7 @@
"addAdditional": "Add additional meter",
"addConsumer": "Add consumer",
"addGrid": "Add grid meter",
"addLoadpoint": "Add charger or heater",
"addLoadpoint": "Add charging point or heater",
"addPvBattery": "Add solar or battery",
"addTariffs": "Add tariffs",
"addVehicle": "Add vehicle",
@ -801,7 +809,7 @@
"general": "General",
"grid": "Grid",
"integrations": "Integrations",
"loadpoints": "Charging & Heating",
"loadpoints": "Charging points & Heaters",
"meter": "Solar & Battery",
"services": "Services",
"system": "System",

View file

@ -189,7 +189,9 @@ func newLoadpointHandler() http.HandlerFunc {
setConfigDirty()
w.WriteHeader(http.StatusOK)
jsonWrite(w, struct {
ID int `json:"id"`
}{ID: conf.ID})
}
}

View file

@ -5,6 +5,7 @@ import {
expectModalVisible,
newLoadpoint,
addDemoCharger,
finishLoadpoint,
ChargerStatus,
} from "./utils";
test.use({ baseURL: baseUrl() });
@ -120,9 +121,7 @@ test.describe("boost", async () => {
await page.goto("/#/config");
await newLoadpoint(page, "New Charger");
await addDemoCharger(page, undefined, ChargerStatus.Connected);
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
// restart evcc to apply new loadpoint
await restart(CONFIG_BATTERY);

View file

@ -51,7 +51,7 @@ test.describe("circuit", async () => {
// add loadpoint and charger
const lpModal = page.getByTestId("loadpoint-modal");
await page.getByRole("button", { name: "Add charger or heater" }).click();
await page.getByRole("button", { name: "Add charging point or heater" }).click();
await expectModalVisible(lpModal);
await lpModal.getByRole("button", { name: "Add charging point" }).click();
await lpModal.getByLabel("Title").fill("Carport");
@ -69,6 +69,7 @@ test.describe("circuit", async () => {
await expectModalVisible(lpModal);
// no load management, no circuits
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await expect(lpModal.getByLabel("Circuit")).not.toBeVisible();
await lpModal.getByRole("button", { name: "Save" }).click();

View file

@ -69,6 +69,7 @@ test.describe("fatal config handling", async () => {
await addDemoCharger(page);
// add shelly meter
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await lpModal.getByRole("button", { name: "Add dedicated energy meter" }).click();
const meterModal = page.getByTestId("meter-modal");
await expectModalVisible(meterModal);

View file

@ -45,6 +45,7 @@ test.describe("invalid references", async () => {
const lpModal = page.getByTestId("loadpoint-modal");
await newLoadpoint(page, "Test Carport");
await addDemoCharger(page);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
// Wait for circuit field to be available and assign to circuit main
await expect(lpModal.getByLabel("Circuit")).toBeVisible();
@ -118,6 +119,7 @@ test.describe("invalid references", async () => {
// Create loadpoint with demo charger and assign vehicle
await newLoadpoint(page, "Garage");
await addDemoCharger(page);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await expect(lpModal.getByLabel("Default vehicle")).toBeVisible();
await lpModal.getByLabel("Default vehicle").selectOption("Legacy Vehicle");
await lpModal.getByRole("button", { name: "Save" }).click();

View file

@ -10,6 +10,7 @@ import {
addDemoMeter,
addVehicle,
newLoadpoint,
finishLoadpoint,
} from "./utils";
const CONFIG_ONE_LP = "config-one-lp.evcc.yaml";
@ -43,6 +44,12 @@ test.describe("charging loadpoint", async () => {
await chargerModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
// instant-create success screen
await expect(lpModal.getByRole("heading", { name: "Charging point created" })).toBeVisible();
await expect(lpModal).toContainText("“Solar Carport” was created and uses sensible defaults.");
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await expect(lpModal.getByLabel("Title")).toHaveValue("Solar Carport");
// verify defaults
await expect(lpModal.getByLabel("Default mode")).toHaveValue("");
@ -146,8 +153,7 @@ test.describe("charging loadpoint", async () => {
// add loadpoint via UI
await newLoadpoint(page, "Garage");
await addDemoCharger(page);
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
// two loadpoints
await expect(page.getByTestId("loadpoint")).toHaveCount(2);
@ -203,8 +209,7 @@ test.describe("charging loadpoint", async () => {
await chargerModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
await expect(page.getByTestId("loadpoint")).toHaveCount(1);
await expect(page.getByTestId("loadpoint")).toContainText("11.0 kW");
@ -221,8 +226,7 @@ test.describe("charging loadpoint", async () => {
await chargerModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
// two loadpoints, each with its own distinct charger
await expect(page.getByTestId("loadpoint")).toHaveCount(2);
@ -251,6 +255,7 @@ test.describe("charging loadpoint", async () => {
// add loadpoint > no vehicle option
await newLoadpoint(page, LP_1);
await addDemoCharger(page);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await expect(lpModal.getByLabel("Default vehicle")).not.toBeVisible();
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
@ -278,6 +283,7 @@ test.describe("charging loadpoint", async () => {
// add second loadpoint
await newLoadpoint(page, LP_2);
await addDemoCharger(page);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await lpModal.getByLabel("Default vehicle").selectOption(VEHICLE_2);
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
@ -313,6 +319,7 @@ test.describe("charging loadpoint", async () => {
const lpModal = page.getByTestId("loadpoint-modal");
await newLoadpoint(page, "Carport");
await addDemoCharger(page);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await lpModal.getByLabel("Default mode").selectOption("---");
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
@ -356,10 +363,14 @@ test.describe("charging loadpoint", async () => {
await addDemoCharger(page);
const lpModal = page.getByTestId("loadpoint-modal");
await expectModalVisible(lpModal);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await lpModal.getByLabel("Default vehicle").selectOption("Porsche");
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await restart();
await page.reload();
// delete vehicle
await page.getByTestId("vehicle").nth(0).getByRole("button", { name: "edit" }).click();
const vehicleModal = page.getByTestId("vehicle-modal");
@ -385,8 +396,7 @@ test.describe("charging loadpoint", async () => {
await newLoadpoint(page, "Garage");
await addDemoCharger(page);
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
// delete charger
await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click();
@ -397,7 +407,9 @@ test.describe("charging loadpoint", async () => {
await chargerModal.getByRole("button", { name: "Delete" }).click();
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
await expect(lpModal.getByRole("heading", { name: "Edit Charger or Heater" })).toBeVisible();
await expect(
lpModal.getByRole("heading", { name: "Edit Charging Point or Heater" })
).toBeVisible();
// close modal before restarting
await lpModal.getByRole("button", { name: "Close" }).click();
@ -426,6 +438,8 @@ test.describe("charging loadpoint", async () => {
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await restart();
await page.reload();
await expect(page.getByTestId("loadpoint")).toContainText("11.0 kW");
// delete charger
@ -504,6 +518,7 @@ power:
await expectModalVisible(lpModal);
// add user-defined meter
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await lpModal.getByRole("button", { name: "Add dedicated energy meter" }).click();
const meterModal = page.getByTestId("meter-modal");
await expectModalVisible(meterModal);
@ -585,6 +600,7 @@ power:
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
const modeSelect = lpModal.getByLabel("Default mode");
await expect(modeSelect.getByRole("option", { name: "Solar", exact: true })).toHaveCount(1);
await expect(modeSelect.getByRole("option", { name: "Min+Solar" })).toHaveCount(0);
@ -611,10 +627,8 @@ test.describe("heating loadpoint", async () => {
const lpModal = page.getByTestId("loadpoint-modal");
await addDemoCharger(page, LoadpointType.Heating);
// check heading
await expect(lpModal.getByRole("heading", { name: "Add Heating Device" })).toBeVisible();
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await expect(lpModal.getByRole("heading", { name: "Heater created" })).toBeVisible();
await finishLoadpoint(page);
// restart edit
await restart();
@ -624,7 +638,7 @@ test.describe("heating loadpoint", async () => {
await expect(lpEntry).toContainText("Wärmepumpe");
await lpEntry.getByRole("button", { name: "edit" }).click();
await expectModalVisible(lpModal);
await expect(lpModal.getByRole("heading", { name: "Edit Heating Device" })).toBeVisible();
await expect(lpModal.getByRole("heading", { name: "Edit Heater" })).toBeVisible();
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
@ -650,12 +664,12 @@ test.describe("heating loadpoint", async () => {
// add loadpoint
await newLoadpoint(page, "Wärmepumpe", LoadpointType.Heating);
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByRole("button", { name: "Add heater" }).click();
await lpModal.getByRole("button", { name: "Add heating device" }).click();
// add user-defined heat pump
const modal = page.getByTestId("charger-modal");
await expectModalVisible(modal);
await modal.getByLabel("Manufacturer").selectOption("User-defined heater");
await modal.getByLabel("Manufacturer").selectOption("User-defined heating device");
await modal.getByLabel("Manufacturer").selectOption("User-defined heat pump");
await modal
.getByLabel("Manufacturer")
@ -706,8 +720,7 @@ temp:
await expectModalVisible(lpModal);
// create
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
await expect(page.getByTestId("loadpoint")).toHaveCount(1);
const lpEntry = page.getByTestId("loadpoint").first();
@ -724,8 +737,7 @@ temp:
await newLoadpoint(page, "Wärmepumpe", LoadpointType.Heating);
await addDemoCharger(page, LoadpointType.Heating);
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
// delete heater
await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click();
@ -736,9 +748,11 @@ temp:
await chargerModal.getByRole("button", { name: "Delete" }).click();
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
await expect(lpModal.getByRole("heading", { name: "Edit Charger or Heater" })).toBeVisible();
await expect(
lpModal.getByRole("heading", { name: "Edit Charging Point or Heater" })
).toBeVisible();
await expect(lpModal.getByRole("button", { name: "Add charging point" })).toBeVisible();
await expect(lpModal.getByRole("button", { name: "Add heating device" })).toBeVisible();
await expect(lpModal.getByRole("button", { name: "Add heater" })).toBeVisible();
});
});

View file

@ -116,7 +116,7 @@ test.describe("ocpp", () => {
await page.goto("/#/config");
// Open loadpoint modal and select charging point type
await page.getByRole("button", { name: "Add charger or heater" }).click();
await page.getByRole("button", { name: "Add charging point or heater" }).click();
const lpModal = page.getByTestId("loadpoint-modal");
await expectModalVisible(lpModal);
await lpModal.getByRole("button", { name: "Charging point" }).click();

View file

@ -1,6 +1,6 @@
import { test, expect } from "@playwright/test";
import { start, stop, restart, baseUrl } from "./evcc";
import { expectModalHidden, expectModalVisible } from "./utils";
import { expectModalHidden, expectModalVisible, finishLoadpoint } from "./utils";
test.use({ baseURL: baseUrl() });
@ -49,8 +49,7 @@ test.describe("onboarding", async () => {
await chargerModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(chargerModal);
await expectModalVisible(lpModal);
await lpModal.getByRole("button", { name: "Save" }).click();
await expectModalHidden(lpModal);
await finishLoadpoint(page);
await restart();

View file

@ -385,6 +385,7 @@ limit:
await addDemoCharger(page);
const lpModal = page.getByTestId("loadpoint-modal");
await expectModalVisible(lpModal);
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
const circuitOptions = lpModal.getByLabel("Circuit").getByRole("option");
await expect(circuitOptions).toHaveText(["---", "House [main]"]);
});

View file

@ -51,7 +51,9 @@ export async function addDemoCharger(
): Promise<void> {
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal
.getByRole("button", { name: type === LoadpointType.Heating ? "Add heater" : "Add charger" })
.getByRole("button", {
name: type === LoadpointType.Heating ? "Add heating device" : "Add charger",
})
.click();
const modal = page.getByTestId("charger-modal");
@ -69,6 +71,7 @@ export async function addDemoCharger(
export async function addDemoMeter(page: Page, power = "0"): Promise<void> {
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByRole("link", { name: "Advanced configuration" }).click();
await lpModal.getByRole("button", { name: "Add dedicated energy meter" }).click();
const modal = page.getByTestId("meter-modal");
@ -96,16 +99,22 @@ export async function newLoadpoint(
type: LoadpointType = LoadpointType.Charging
): Promise<void> {
const lpModal = page.getByTestId("loadpoint-modal");
await page.getByRole("button", { name: "Add charger or heater" }).click();
await page.getByRole("button", { name: "Add charging point or heater" }).click();
await expectModalVisible(lpModal);
await lpModal
.getByRole("button", {
name: type === LoadpointType.Heating ? "Add heating device" : "Add charging point",
name: type === LoadpointType.Heating ? "Add heater" : "Add charging point",
})
.click();
await lpModal.getByLabel("Title").fill(title);
}
export async function finishLoadpoint(page: Page): Promise<void> {
const lpModal = page.getByTestId("loadpoint-modal");
await lpModal.getByText("Close", { exact: true }).click();
await expectModalHidden(lpModal);
}
export async function dragElement(
page: Page,
sourceElement: Locator,