Battery Forecast UI Improvements (#27630)
This commit is contained in:
parent
9f982f676a
commit
ea857537f2
13 changed files with 62 additions and 175 deletions
|
|
@ -87,13 +87,12 @@ const bat = (power: number, soc: number, forecast: any, devices: any[] = []) =>
|
|||
forecast,
|
||||
});
|
||||
|
||||
const dev = (title: string, power: number, soc: number, forecast: any) => ({
|
||||
const dev = (title: string, power: number, soc: number) => ({
|
||||
title,
|
||||
power,
|
||||
soc,
|
||||
capacity: 10,
|
||||
controllable: true,
|
||||
forecast,
|
||||
});
|
||||
|
||||
export const BatteryForecastDischarging = Template.bind({});
|
||||
|
|
@ -120,26 +119,15 @@ BatteryForecastBoth.args = {
|
|||
battery: bat(-1700, 70, { full: hoursFromNow(2), empty: hoursFromNow(36) }),
|
||||
} as any;
|
||||
|
||||
export const BatteryForecastMultiDischarging = Template.bind({});
|
||||
BatteryForecastMultiDischarging.args = {
|
||||
...batteryBase,
|
||||
gridPower: 500,
|
||||
homePower: 2500,
|
||||
battery: bat(2000, 55, { full: null, empty: hoursFromNow(48) }, [
|
||||
dev("Powerwall", 1200, 60, { full: null, empty: hoursFromNow(48) }),
|
||||
dev("BYD", 800, 48, { full: null, empty: hoursFromNow(0.25) }),
|
||||
]),
|
||||
} as any;
|
||||
|
||||
export const BatteryForecastMultiCharging = Template.bind({});
|
||||
BatteryForecastMultiCharging.args = {
|
||||
export const BatteryForecastMulti = Template.bind({});
|
||||
BatteryForecastMulti.args = {
|
||||
...batteryBase,
|
||||
pvPower: 8000,
|
||||
gridPower: -1000,
|
||||
homePower: 1000,
|
||||
battery: bat(-6000, 40, { full: hoursFromNow(26), empty: null }, [
|
||||
dev("Powerwall", -3500, 35, { full: hoursFromNow(26), empty: null }),
|
||||
dev("BYD", -2500, 47, { full: hoursFromNow(0.7), empty: null }),
|
||||
dev("Powerwall", -3500, 35),
|
||||
dev("BYD", -2500, 47),
|
||||
]),
|
||||
} as any;
|
||||
|
||||
|
|
|
|||
|
|
@ -134,9 +134,7 @@
|
|||
v-if="batteryForecastEmpty"
|
||||
class="d-flex align-items-center mb-2"
|
||||
>
|
||||
<ForecastMessage>{{
|
||||
batteryForecastEmpty
|
||||
}}</ForecastMessage>
|
||||
<ForecastMessage :message="batteryForecastEmpty" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="batteryForecastFull"
|
||||
|
|
@ -153,7 +151,6 @@
|
|||
v-for="(b, index) in batteryDevices"
|
||||
:key="index"
|
||||
:name="b.title || genericBatteryTitle(index)"
|
||||
:prediction="devicePrediction(b, false)"
|
||||
:details="b.soc"
|
||||
:detailsFmt="batteryFmt"
|
||||
:power="dischargePower(b.power)"
|
||||
|
|
@ -278,7 +275,7 @@
|
|||
v-if="batteryForecastFull"
|
||||
class="d-flex align-items-center mb-2"
|
||||
>
|
||||
<ForecastMessage>{{ batteryForecastFull }}</ForecastMessage>
|
||||
<ForecastMessage :message="batteryForecastFull" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="batteryForecastEmpty"
|
||||
|
|
@ -312,7 +309,6 @@
|
|||
v-for="(b, index) in batteryDevices"
|
||||
:key="index"
|
||||
:name="b.title || genericBatteryTitle(index)"
|
||||
:prediction="devicePrediction(b, true)"
|
||||
:details="b.soc"
|
||||
:detailsFmt="batteryFmt"
|
||||
:power="chargePower(b.power)"
|
||||
|
|
@ -588,17 +584,11 @@ export default defineComponent({
|
|||
consumers() {
|
||||
return [...this.aux, ...this.ext];
|
||||
},
|
||||
batteryForecastFull(): string | null {
|
||||
if (this.batteryExpanded && this.hasMultipleBatteries) return null;
|
||||
const timeago = this.fmtForecastRelative(this.battery?.forecast?.full);
|
||||
if (!timeago) return null;
|
||||
return this.$t("main.energyflow.batteryForecastFull", { timeago });
|
||||
batteryForecastFull(): string | undefined {
|
||||
return this.fmtForecast(this.battery?.forecast, true);
|
||||
},
|
||||
batteryForecastEmpty(): string | null {
|
||||
if (this.batteryExpanded && this.hasMultipleBatteries) return null;
|
||||
const timeago = this.fmtForecastRelative(this.battery?.forecast?.empty);
|
||||
if (!timeago) return null;
|
||||
return this.$t("main.energyflow.batteryForecastEmpty", { timeago });
|
||||
batteryForecastEmpty(): string | undefined {
|
||||
return this.fmtForecast(this.battery?.forecast, false);
|
||||
},
|
||||
batteryForecastExists(): boolean {
|
||||
return !!(this.batteryForecastEmpty || this.batteryForecastFull);
|
||||
|
|
@ -704,30 +694,17 @@ export default defineComponent({
|
|||
genericConsumerTitle(index: number) {
|
||||
return `${this.$t("config.devices.consumer")} #${index + 1}`;
|
||||
},
|
||||
fmtForecastRelative(isoString?: string | null): string | null {
|
||||
if (!isoString) return null;
|
||||
const elapsed = new Date(isoString).getTime() - Date.now();
|
||||
if (elapsed <= 0) return null;
|
||||
return this.fmtTimeAgo(elapsed, "always");
|
||||
},
|
||||
deviceForecastFull(device: { forecast?: { full?: string | null } }): string | null {
|
||||
return this.fmtForecastRelative(device.forecast?.full);
|
||||
},
|
||||
deviceForecastEmpty(device: { forecast?: { empty?: string | null } }): string | null {
|
||||
return this.fmtForecastRelative(device.forecast?.empty);
|
||||
},
|
||||
devicePrediction(
|
||||
device: { forecast?: { full?: string | null; empty?: string | null } },
|
||||
fmtForecast(
|
||||
forecast: { full?: string | null; empty?: string | null } | undefined,
|
||||
full: boolean
|
||||
): string | undefined {
|
||||
const timeago = full
|
||||
? this.deviceForecastFull(device)
|
||||
: this.deviceForecastEmpty(device);
|
||||
if (!timeago) return undefined;
|
||||
const isoString = full ? forecast?.full : forecast?.empty;
|
||||
if (!isoString) return undefined;
|
||||
const time = this.fmtAbsoluteDate(new Date(isoString));
|
||||
const key = full
|
||||
? "main.energyflow.batteryForecastFull"
|
||||
: "main.energyflow.batteryForecastEmpty";
|
||||
return this.$t(key, { timeago });
|
||||
return this.$t(key, { time });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-2 entry" :class="{ 'evcc-gray': !active }">
|
||||
<div class="entry" :class="{ 'evcc-gray': !active }">
|
||||
<div class="mb-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="d-flex flex-nowrap">
|
||||
<BatteryIcon v-if="isBattery" v-bind="iconProps" />
|
||||
|
|
@ -10,10 +10,7 @@
|
|||
</span>
|
||||
<div class="d-flex flex-grow-1 ms-3 align-items-center text-truncate">
|
||||
<span v-if="!$slots['expanded']" class="text-truncate">
|
||||
{{ name
|
||||
}}<ForecastMessage v-if="prediction" class="ms-1" inline>{{
|
||||
prediction
|
||||
}}</ForecastMessage>
|
||||
{{ name }}
|
||||
</span>
|
||||
<button
|
||||
v-else
|
||||
|
|
@ -83,16 +80,14 @@ import formatter, { POWER_UNIT } from "@/mixins/formatter";
|
|||
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
||||
import VehicleIcon from "../VehicleIcon";
|
||||
import ForecastIcon from "../MaterialIcon/Forecast.vue";
|
||||
import ForecastMessage from "./ForecastMessage.vue";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "EnergyflowEntry",
|
||||
components: { BatteryIcon, AnimatedNumber, VehicleIcon, ForecastIcon, ForecastMessage },
|
||||
components: { BatteryIcon, AnimatedNumber, VehicleIcon, ForecastIcon },
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
name: { type: String },
|
||||
prediction: { type: String },
|
||||
icon: { type: String },
|
||||
iconProps: { type: Object, default: () => ({}) },
|
||||
power: { type: Number, default: 0 },
|
||||
|
|
@ -205,6 +200,7 @@ export default defineComponent({
|
|||
.entry {
|
||||
transition: color var(--evcc-transition-medium) linear;
|
||||
}
|
||||
|
||||
.power {
|
||||
min-width: 75px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<router-link to="/optimize" class="root" @click.stop>
|
||||
<template v-if="!inline">{{ $t("main.energyflow.forecast") }} </template>
|
||||
<span class="message"><slot /></span>
|
||||
{{ $t("main.energyflow.forecast") }} <span class="message">{{ message }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
|
|
@ -11,42 +10,17 @@ import { defineComponent } from "vue";
|
|||
export default defineComponent({
|
||||
name: "ForecastMessage",
|
||||
props: {
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
message: { type: String, required: true },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.root {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--evcc-battery) 0%,
|
||||
var(--evcc-battery) 15%,
|
||||
var(--evcc-self) 30%,
|
||||
var(--evcc-battery) 50%,
|
||||
var(--evcc-battery) 65%,
|
||||
var(--evcc-self) 80%,
|
||||
var(--evcc-battery) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: forecast-sweep 6s linear infinite;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.message {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@keyframes forecast-sweep {
|
||||
0% {
|
||||
background-position: 0% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -189,27 +189,10 @@ export default defineComponent({
|
|||
const today = new Date();
|
||||
return today.toDateString() === date.toDateString();
|
||||
},
|
||||
isTomorrow(date: Date) {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
return tomorrow.toDateString() === date.toDateString();
|
||||
},
|
||||
weekdayPrefix(date: Date) {
|
||||
if (this.isToday(date)) {
|
||||
return "";
|
||||
}
|
||||
if (this.isTomorrow(date)) {
|
||||
try {
|
||||
const rtf = new Intl.RelativeTimeFormat(this.$i18n?.locale, {
|
||||
numeric: "auto",
|
||||
});
|
||||
const part = rtf.formatToParts(1, "day")[0];
|
||||
return part?.value || "";
|
||||
} catch (e) {
|
||||
console.warn("weekdayPrefix: Intl.RelativeTimeFormat not supported", e);
|
||||
return "tomorrow";
|
||||
}
|
||||
}
|
||||
return new Intl.DateTimeFormat(this.$i18n?.locale, {
|
||||
weekday: "short",
|
||||
}).format(date);
|
||||
|
|
@ -332,7 +315,7 @@ export default defineComponent({
|
|||
const unit = this.energyPriceSubunit(currency) || CURRENCY_SYMBOLS[currency] || currency;
|
||||
return `${unit}${short ? "" : "/kWh"}`;
|
||||
},
|
||||
fmtTimeAgo(elapsed: number, numeric: "auto" | "always" = "auto") {
|
||||
fmtTimeAgo(elapsed: number) {
|
||||
const units = {
|
||||
day: 24 * 60 * 60 * 1000,
|
||||
hour: 60 * 60 * 1000,
|
||||
|
|
@ -346,7 +329,7 @@ export default defineComponent({
|
|||
if (Math.abs(elapsed) > units[unitKey] || u == "second") {
|
||||
try {
|
||||
const rtf = new Intl.RelativeTimeFormat(this.$i18n?.locale, {
|
||||
numeric,
|
||||
numeric: "auto",
|
||||
});
|
||||
return rtf.format(Math.round(elapsed / units[unitKey]), unitKey);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -528,7 +528,6 @@ export interface BatteryMeter extends Meter {
|
|||
soc: number;
|
||||
controllable: boolean;
|
||||
capacity: number; // 0 when not specified
|
||||
forecast?: BatteryForecast;
|
||||
}
|
||||
|
||||
export interface Vehicle {
|
||||
|
|
|
|||
|
|
@ -666,11 +666,6 @@ func (site *Site) updateBatteryMeters() {
|
|||
site.log.DEBUG.Printf("battery soc: %.0f%%", math.Round(site.battery.Soc))
|
||||
}
|
||||
|
||||
// keep forecast property created by optimizer
|
||||
for i := range site.battery.Devices {
|
||||
mm[i].Forecast = site.battery.Devices[i].Forecast
|
||||
}
|
||||
|
||||
site.battery.Devices = mm
|
||||
|
||||
site.publish(keys.Battery, site.battery)
|
||||
|
|
|
|||
|
|
@ -255,17 +255,6 @@ func (site *Site) optimizerUpdate(battery []types.Measurement) error {
|
|||
}
|
||||
|
||||
batteries = append(batteries, batResult)
|
||||
|
||||
for j, dev := range site.batteryMeters {
|
||||
if details.BatteryDetails[i].Name == dev.Config().Name {
|
||||
site.battery.Devices[j].Forecast = &types.BatteryForecast{
|
||||
Full: batResult.Full,
|
||||
Empty: batResult.Empty,
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
site.publish("evopt-batteries", batteries)
|
||||
|
|
|
|||
|
|
@ -8,17 +8,16 @@ import (
|
|||
|
||||
// Measurement is the device measurements struct
|
||||
type Measurement struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Power float64 `json:"power"`
|
||||
Energy float64 `json:"energy,omitempty"`
|
||||
Powers []float64 `json:"powers,omitempty"`
|
||||
Currents []float64 `json:"currents,omitempty"`
|
||||
ExcessDCPower float64 `json:"excessdcpower,omitempty"`
|
||||
Capacity *float64 `json:"capacity,omitempty"`
|
||||
Soc *float64 `json:"soc,omitempty"`
|
||||
Controllable *bool `json:"controllable,omitempty"`
|
||||
Forecast *BatteryForecast `json:"forecast,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Power float64 `json:"power"`
|
||||
Energy float64 `json:"energy,omitempty"`
|
||||
Powers []float64 `json:"powers,omitempty"`
|
||||
Currents []float64 `json:"currents,omitempty"`
|
||||
ExcessDCPower float64 `json:"excessdcpower,omitempty"`
|
||||
Capacity *float64 `json:"capacity,omitempty"`
|
||||
Soc *float64 `json:"soc,omitempty"`
|
||||
Controllable *bool `json:"controllable,omitempty"`
|
||||
}
|
||||
|
||||
type BatteryForecast struct {
|
||||
|
|
@ -26,26 +25,6 @@ type BatteryForecast struct {
|
|||
Empty *time.Time `json:"empty"`
|
||||
}
|
||||
|
||||
// func (f BatteryForecast) MarshalJSON() ([]byte, error) {
|
||||
// var full, empty int64
|
||||
// if !f.Full.IsZero() {
|
||||
// full = int64(time.Until(f.Full).Seconds())
|
||||
// }
|
||||
// if !f.Empty.IsZero() {
|
||||
// empty = int64(time.Until(f.Empty).Seconds())
|
||||
// }
|
||||
|
||||
// return json.Marshal(struct {
|
||||
// BatteryForecast
|
||||
// UntilFull int64 `json:"untilFull,omitempty"`
|
||||
// UntilEmpty int64 `json:"untilEmpty,omitempty"`
|
||||
// }{
|
||||
// BatteryForecast: f,
|
||||
// UntilFull: full,
|
||||
// UntilEmpty: empty,
|
||||
// })
|
||||
// }
|
||||
|
||||
var _ api.TitleDescriber = (*Measurement)(nil)
|
||||
|
||||
// GetTitle implements api.TitleDescriber interface for InfluxDB tagging
|
||||
|
|
|
|||
|
|
@ -994,8 +994,8 @@
|
|||
"battery": "Batterie",
|
||||
"batteryCharge": "Batterie laden",
|
||||
"batteryDischarge": "Batterie entladen",
|
||||
"batteryForecastEmpty": "leer {timeago}",
|
||||
"batteryForecastFull": "voll {timeago}",
|
||||
"batteryForecastEmpty": "leer {time}",
|
||||
"batteryForecastFull": "voll {time}",
|
||||
"batteryGridChargeActive": "Netzladen: aktiv",
|
||||
"batteryGridChargeLimit": "Netzladen: wenn",
|
||||
"batteryHold": "Batterie (gesperrt)",
|
||||
|
|
|
|||
|
|
@ -994,8 +994,8 @@
|
|||
"battery": "Battery",
|
||||
"batteryCharge": "Battery charging",
|
||||
"batteryDischarge": "Battery discharging",
|
||||
"batteryForecastEmpty": "empty {timeago}",
|
||||
"batteryForecastFull": "full {timeago}",
|
||||
"batteryForecastEmpty": "empty {time}",
|
||||
"batteryForecastFull": "full {time}",
|
||||
"batteryGridChargeActive": "Grid charging: active",
|
||||
"batteryGridChargeLimit": "Grid charging: when",
|
||||
"batteryHold": "Battery (locked)",
|
||||
|
|
|
|||
|
|
@ -106,32 +106,32 @@ func (suite *mqttSuite) TestNilInterface() {
|
|||
|
||||
func (suite *mqttSuite) TestMeasurement() {
|
||||
topics := lo.Map([]string{
|
||||
"title", "icon", "power", "energy", "powers", "currents", "excessDCPower", "capacity", "soc", "controllable", "forecast",
|
||||
"title", "icon", "power", "energy", "powers", "currents", "excessDCPower", "capacity", "soc", "controllable",
|
||||
}, func(s string, _ int) string {
|
||||
return "test/" + s
|
||||
})
|
||||
|
||||
suite.publish("test", false, types.Measurement{})
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"", "", "0", "", "", "", "", "", "", "", ""}, suite.payloads, "empty payloads")
|
||||
suite.Equal([]string{"", "", "0", "", "", "", "", "", "", ""}, suite.payloads, "empty payloads")
|
||||
|
||||
suite.publish("test", false, types.Measurement{Energy: 1})
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"", "", "0", "1", "", "", "", "", "", "", ""}, suite.payloads, "energy payloads")
|
||||
suite.Equal([]string{"", "", "0", "1", "", "", "", "", "", ""}, suite.payloads, "energy payloads")
|
||||
|
||||
suite.publish("test", false, types.Measurement{Controllable: new(false)})
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"", "", "0", "", "", "", "", "", "", "false", ""}, suite.payloads, "controllable payloads")
|
||||
suite.Equal([]string{"", "", "0", "", "", "", "", "", "", "false"}, suite.payloads, "controllable payloads")
|
||||
|
||||
suite.publish("test", false, types.Measurement{Currents: []float64{1, 2, 3}})
|
||||
suite.Equal(append(topics, "test/currents/1", "test/currents/2", "test/currents/3"), suite.topics, "currents topics")
|
||||
suite.Equal([]string{"", "", "0", "", "", "3", "", "", "", "", "", "1", "2", "3"}, suite.payloads, "currents payloads")
|
||||
suite.Equal([]string{"", "", "0", "", "", "3", "", "", "", "", "1", "2", "3"}, suite.payloads, "currents payloads")
|
||||
}
|
||||
|
||||
func (suite *mqttSuite) TestBatteryState() {
|
||||
topics := lo.Map([]string{
|
||||
"power", "energy", "capacity", "soc",
|
||||
"devices", "devices/1/title", "devices/1/icon", "devices/1/power", "devices/1/energy", "devices/1/powers", "devices/1/currents", "devices/1/excessDCPower", "devices/1/capacity", "devices/1/soc", "devices/1/controllable", "devices/1/forecast",
|
||||
"devices", "devices/1/title", "devices/1/icon", "devices/1/power", "devices/1/energy", "devices/1/powers", "devices/1/currents", "devices/1/excessDCPower", "devices/1/capacity", "devices/1/soc", "devices/1/controllable",
|
||||
"forecast",
|
||||
}, func(s string, _ int) string {
|
||||
return "test/" + s
|
||||
|
|
@ -147,5 +147,5 @@ func (suite *mqttSuite) TestBatteryState() {
|
|||
})
|
||||
|
||||
suite.Equal(topics, suite.topics, "topics")
|
||||
suite.Equal([]string{"2", "", "", "20", "1", "", "", "1", "", "", "", "", "", "10", "", "", ""}, suite.payloads, "payloads")
|
||||
suite.Equal([]string{"2", "", "", "20", "1", "", "", "1", "", "", "", "", "", "10", "", ""}, suite.payloads, "payloads")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ test.afterEach(async () => {
|
|||
await stop();
|
||||
});
|
||||
|
||||
function getWeekday(offset = 1) {
|
||||
function getWeekday(offset = 1, style: "long" | "short" = "long") {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + offset);
|
||||
return date.toLocaleDateString("en-US", { weekday: "long" });
|
||||
return date.toLocaleDateString("en-US", { weekday: style });
|
||||
}
|
||||
|
||||
async function setAndVerifyPlan(
|
||||
|
|
@ -85,16 +85,19 @@ test.describe("basic functionality", async () => {
|
|||
await page.getByTestId("static-plan-active").click();
|
||||
await page.getByRole("button", { name: "Close" }).click();
|
||||
|
||||
const tomorrowShort = getWeekday(1, "short");
|
||||
await expect(lp1.getByTestId("plan-marker")).toBeVisible();
|
||||
await expect(lp1.getByTestId("charging-plan").getByRole("button")).toHaveText(
|
||||
["tomorrow 09:30", "80%"].join("")
|
||||
[`${tomorrowShort} 09:30`, "80%"].join("")
|
||||
);
|
||||
|
||||
await expect(lp1.getByTestId("vehicle-status-charger")).toHaveText("Connected.");
|
||||
await expect(lp1.getByTestId("vehicle-status-planstart")).toHaveText(/tomorrow .*/);
|
||||
await expect(lp1.getByTestId("vehicle-status-planstart")).toHaveText(
|
||||
new RegExp(`${tomorrowShort} .*`)
|
||||
);
|
||||
await expect(lp1.getByTestId("plan-marker")).toBeVisible();
|
||||
await expect(lp1.getByTestId("charging-plan").getByRole("button")).toHaveText(
|
||||
["tomorrow 09:30", "80%"].join("")
|
||||
[`${tomorrowShort} 09:30`, "80%"].join("")
|
||||
);
|
||||
await lp1.getByTestId("charging-plan").getByRole("button").click();
|
||||
await expect(page.getByTestId("static-plan-soc")).toHaveValue("80");
|
||||
|
|
@ -633,6 +636,7 @@ test.describe("repeating", async () => {
|
|||
await page.goto("/");
|
||||
|
||||
const tomorrow = getWeekday(1);
|
||||
const tomorrowShort = getWeekday(1, "short");
|
||||
|
||||
let lp1 = await page.getByTestId("loadpoint").first();
|
||||
await lp1
|
||||
|
|
@ -658,7 +662,7 @@ test.describe("repeating", async () => {
|
|||
await expect(modal.getByTestId("target-text")).toContainText("09:20");
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expect(
|
||||
lp1.getByTestId("charging-plan").getByRole("button", { name: "tomorrow 09:20" })
|
||||
lp1.getByTestId("charging-plan").getByRole("button", { name: `${tomorrowShort} 09:20` })
|
||||
).toBeVisible();
|
||||
|
||||
await restart(CONFIG);
|
||||
|
|
@ -670,7 +674,10 @@ test.describe("repeating", async () => {
|
|||
.locator("select")
|
||||
.selectOption("Vehicle with SoC with Capacity");
|
||||
|
||||
await lp1.getByTestId("charging-plan").getByRole("button", { name: "tomorrow 09:20" }).click();
|
||||
await lp1
|
||||
.getByTestId("charging-plan")
|
||||
.getByRole("button", { name: `${tomorrowShort} 09:20` })
|
||||
.click();
|
||||
await expect(modal.getByTestId("plan-entry")).toHaveCount(2);
|
||||
await expect(modal.getByTestId("plan-preview-title")).toHaveText("Next plan #2");
|
||||
await expect(modal.getByTestId("target-text")).toContainText("09:20");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue