mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 02:38:53 +00:00
Merge branch 'rc'
This commit is contained in:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20251203.2"
|
version = "20251203.3"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*"]
|
license-files = ["LICENSE*"]
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ export class HaAssistChat extends LitElement {
|
|||||||
--markdown-table-border-color: var(--divider-color);
|
--markdown-table-border-color: var(--divider-color);
|
||||||
--markdown-code-background-color: var(--primary-background-color);
|
--markdown-code-background-color: var(--primary-background-color);
|
||||||
--markdown-code-text-color: var(--primary-text-color);
|
--markdown-code-text-color: var(--primary-text-color);
|
||||||
--markdown-list-indent: 1rem;
|
--markdown-list-indent: 1.15em;
|
||||||
&:not(:has(ha-markdown-element)) {
|
&:not(:has(ha-markdown-element)) {
|
||||||
min-height: 1lh;
|
min-height: 1lh;
|
||||||
min-width: 1lh;
|
min-width: 1lh;
|
||||||
|
|||||||
@@ -344,7 +344,10 @@ export class HaGenericPicker extends LitElement {
|
|||||||
|
|
||||||
wa-popover::part(body) {
|
wa-popover::part(body) {
|
||||||
width: max(var(--body-width), 250px);
|
width: max(var(--body-width), 250px);
|
||||||
max-width: max(var(--body-width), 250px);
|
max-width: var(
|
||||||
|
--ha-generic-picker-max-width,
|
||||||
|
max(var(--body-width), 250px)
|
||||||
|
);
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -74,9 +74,6 @@ export class HaMarkdown extends LitElement {
|
|||||||
background-color: var(--markdown-image-background-color);
|
background-color: var(--markdown-image-background-color);
|
||||||
border-radius: var(--markdown-image-border-radius);
|
border-radius: var(--markdown-image-border-radius);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
|
||||||
width: auto;
|
|
||||||
transition: height 0.2s ease-in-out;
|
|
||||||
}
|
}
|
||||||
p:first-child > img:first-child {
|
p:first-child > img:first-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
@@ -84,8 +81,7 @@ export class HaMarkdown extends LitElement {
|
|||||||
p:first-child > img:last-child {
|
p:first-child > img:last-child {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
:host > ul,
|
ha-markdown-element > :is(ol, ul) {
|
||||||
:host > ol {
|
|
||||||
padding-inline-start: var(--markdown-list-indent, revert);
|
padding-inline-start: var(--markdown-list-indent, revert);
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
@@ -136,6 +132,18 @@ export class HaMarkdown extends LitElement {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
margin: var(--ha-space-4) 0;
|
margin: var(--ha-space-4) 0;
|
||||||
}
|
}
|
||||||
|
table[role="presentation"] {
|
||||||
|
--markdown-table-border-collapse: separate;
|
||||||
|
--markdown-table-border-width: attr(border, 0);
|
||||||
|
--markdown-table-padding-inline: 0;
|
||||||
|
--markdown-table-padding-block: 0;
|
||||||
|
th {
|
||||||
|
vertical-align: attr(align, center);
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
vertical-align: attr(align, left);
|
||||||
|
}
|
||||||
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: var(--markdown-table-border-collapse, collapse);
|
border-collapse: var(--markdown-table-border-collapse, collapse);
|
||||||
}
|
}
|
||||||
@@ -143,14 +151,15 @@ export class HaMarkdown extends LitElement {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
text-align: start;
|
text-align: var(--markdown-table-text-align, start);
|
||||||
}
|
}
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border-width: var(--markdown-table-border-width, 1px);
|
border-width: var(--markdown-table-border-width, 1px);
|
||||||
border-style: var(--markdown-table-border-style, solid);
|
border-style: var(--markdown-table-border-style, solid);
|
||||||
border-color: var(--markdown-table-border-color, var(--divider-color));
|
border-color: var(--markdown-table-border-color, var(--divider-color));
|
||||||
padding: 0.25em 0.5em;
|
padding-inline: var(--markdown-table-padding-inline, 0.5em);
|
||||||
|
padding-block: var(--markdown-table-padding-block, 0.25em);
|
||||||
}
|
}
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 4px solid var(--divider-color);
|
border-left: 4px solid var(--divider-color);
|
||||||
|
|||||||
@@ -952,10 +952,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
let hasFloor = false;
|
let hasFloor = false;
|
||||||
let rtl = false;
|
let rtl = false;
|
||||||
let showEntityId = false;
|
let showEntityId = false;
|
||||||
|
|
||||||
if (type === "area" || type === "floor") {
|
if (type === "area" || type === "floor") {
|
||||||
item.id = item[type]?.[`${type}_id`];
|
|
||||||
|
|
||||||
rtl = computeRTL(this.hass);
|
rtl = computeRTL(this.hass);
|
||||||
hasFloor =
|
hasFloor =
|
||||||
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export class HaToast extends Snackbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mdc-snackbar {
|
.mdc-snackbar {
|
||||||
|
z-index: 10;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
right: calc(8px + var(--safe-area-inset-right));
|
right: calc(8px + var(--safe-area-inset-right));
|
||||||
bottom: calc(8px + var(--safe-area-inset-bottom));
|
bottom: calc(8px + var(--safe-area-inset-bottom));
|
||||||
|
|||||||
@@ -321,12 +321,16 @@ export class HaAutomationAddSearch extends LitElement {
|
|||||||
></ha-tree-indicator>
|
></ha-tree-indicator>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${item.icon
|
${(item as AutomationItemComboBoxItem).renderedIcon
|
||||||
|
? html`<div slot="start">
|
||||||
|
${(item as AutomationItemComboBoxItem).renderedIcon}
|
||||||
|
</div>`
|
||||||
|
: item.icon
|
||||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||||
: item.icon_path
|
: item.icon_path || type === "area"
|
||||||
? html`<ha-svg-icon
|
? html`<ha-svg-icon
|
||||||
slot="start"
|
slot="start"
|
||||||
.path=${item.icon_path}
|
.path=${item.icon_path || mdiTextureBox}
|
||||||
></ha-svg-icon>`
|
></ha-svg-icon>`
|
||||||
: type === "entity" && (item as EntityComboBoxItem).stateObj
|
: type === "entity" && (item as EntityComboBoxItem).stateObj
|
||||||
? html`
|
? html`
|
||||||
@@ -350,11 +354,6 @@ export class HaAutomationAddSearch extends LitElement {
|
|||||||
slot="start"
|
slot="start"
|
||||||
.floor=${(item as FloorComboBoxItem).floor!}
|
.floor=${(item as FloorComboBoxItem).floor!}
|
||||||
></ha-floor-icon>`
|
></ha-floor-icon>`
|
||||||
: type === "area"
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
.path=${item.icon_path || mdiTextureBox}
|
|
||||||
></ha-svg-icon>`
|
|
||||||
: nothing}
|
: nothing}
|
||||||
<span slot="headline">${item.primary}</span>
|
<span slot="headline">${item.primary}</span>
|
||||||
${item.secondary
|
${item.secondary
|
||||||
@@ -784,7 +783,7 @@ export class HaAutomationAddSearch extends LitElement {
|
|||||||
id: key,
|
id: key,
|
||||||
primary: name,
|
primary: name,
|
||||||
secondary: description,
|
secondary: description,
|
||||||
iconPath,
|
icon_path: iconPath,
|
||||||
renderedIcon: icon,
|
renderedIcon: icon,
|
||||||
type,
|
type,
|
||||||
search_labels: [key, name, description],
|
search_labels: [key, name, description],
|
||||||
|
|||||||
@@ -631,6 +631,7 @@ class HaPanelHistory extends LitElement {
|
|||||||
|
|
||||||
:host([virtualize]) {
|
:host([virtualize]) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
--ha-generic-picker-max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-wrapper {
|
.progress-wrapper {
|
||||||
|
|||||||
@@ -303,6 +303,9 @@ export class HaPanelLogbook extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
|
:host {
|
||||||
|
--ha-generic-picker-max-width: 400px;
|
||||||
|
}
|
||||||
ha-logbook {
|
ha-logbook {
|
||||||
height: calc(
|
height: calc(
|
||||||
100vh -
|
100vh -
|
||||||
|
|||||||
@@ -295,32 +295,41 @@ export function fillDataGapsAndRoundCaps(datasets: BarSeriesOption[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDatapointX(datapoint: NonNullable<LineSeriesOption["data"]>[0]) {
|
||||||
|
const item =
|
||||||
|
datapoint && typeof datapoint === "object" && "value" in datapoint
|
||||||
|
? datapoint
|
||||||
|
: { value: datapoint };
|
||||||
|
return Number(item.value?.[0]);
|
||||||
|
}
|
||||||
|
|
||||||
export function fillLineGaps(datasets: LineSeriesOption[]) {
|
export function fillLineGaps(datasets: LineSeriesOption[]) {
|
||||||
const buckets = Array.from(
|
const buckets = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
datasets
|
datasets
|
||||||
.map((dataset) =>
|
.map((dataset) =>
|
||||||
dataset.data!.map((datapoint) => Number(datapoint![0]))
|
dataset.data!.map((datapoint) => getDatapointX(datapoint))
|
||||||
)
|
)
|
||||||
.flat()
|
.flat()
|
||||||
)
|
)
|
||||||
).sort((a, b) => a - b);
|
).sort((a, b) => a - b);
|
||||||
buckets.forEach((bucket, index) => {
|
|
||||||
for (let i = datasets.length - 1; i >= 0; i--) {
|
datasets.forEach((dataset) => {
|
||||||
const dataPoint = datasets[i].data![index];
|
const dataMap = new Map<number, LineDataItemOption>();
|
||||||
|
dataset.data!.forEach((datapoint) => {
|
||||||
const item: LineDataItemOption =
|
const item: LineDataItemOption =
|
||||||
dataPoint && typeof dataPoint === "object" && "value" in dataPoint
|
datapoint && typeof datapoint === "object" && "value" in datapoint
|
||||||
? dataPoint
|
? datapoint
|
||||||
: ({ value: dataPoint } as LineDataItemOption);
|
: ({ value: datapoint } as LineDataItemOption);
|
||||||
const x = item.value?.[0];
|
const x = getDatapointX(datapoint);
|
||||||
if (x === undefined) {
|
if (!Number.isNaN(x)) {
|
||||||
continue;
|
dataMap.set(x, item);
|
||||||
}
|
|
||||||
if (Number(x) !== bucket) {
|
|
||||||
datasets[i].data?.splice(index, 0, [bucket, 0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dataset.data = buckets.map((bucket) => dataMap.get(bucket) ?? [bucket, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
return datasets;
|
return datasets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ const computeHeadingCard = (
|
|||||||
action: "navigate",
|
action: "navigate",
|
||||||
navigation_path,
|
navigation_path,
|
||||||
}
|
}
|
||||||
: undefined,
|
: {
|
||||||
|
action: "none",
|
||||||
|
},
|
||||||
}) satisfies HeadingCardConfig;
|
}) satisfies HeadingCardConfig;
|
||||||
|
|
||||||
@customElement("home-area-view-strategy")
|
@customElement("home-area-view-strategy")
|
||||||
@@ -182,7 +184,7 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
|||||||
computeHeadingCard(
|
computeHeadingCard(
|
||||||
hass.localize("ui.panel.lovelace.strategy.home.scenes"),
|
hass.localize("ui.panel.lovelace.strategy.home.scenes"),
|
||||||
"mdi:palette",
|
"mdi:palette",
|
||||||
"/config/scene/dashboard"
|
hass.user?.is_admin ? "/config/scene/dashboard" : undefined
|
||||||
),
|
),
|
||||||
...scenes.map(computeTileCard),
|
...scenes.map(computeTileCard),
|
||||||
],
|
],
|
||||||
@@ -285,12 +287,13 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
|||||||
{
|
{
|
||||||
type: "heading",
|
type: "heading",
|
||||||
heading: heading,
|
heading: heading,
|
||||||
tap_action: device
|
tap_action:
|
||||||
|
hass.user?.is_admin && device
|
||||||
? {
|
? {
|
||||||
action: "navigate",
|
action: "navigate",
|
||||||
navigation_path: `/config/devices/device/${device.id}`,
|
navigation_path: `/config/devices/device/${device.id}`,
|
||||||
}
|
}
|
||||||
: undefined,
|
: { action: "none" },
|
||||||
badges: [
|
badges: [
|
||||||
...batteryEntities.slice(0, 1).map((e) => ({
|
...batteryEntities.slice(0, 1).map((e) => ({
|
||||||
entity: e,
|
entity: e,
|
||||||
@@ -334,7 +337,7 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
|||||||
computeHeadingCard(
|
computeHeadingCard(
|
||||||
hass.localize("ui.panel.lovelace.strategy.home.automations"),
|
hass.localize("ui.panel.lovelace.strategy.home.automations"),
|
||||||
"mdi:robot",
|
"mdi:robot",
|
||||||
"/config/automation/dashboard"
|
hass.user?.is_admin ? "/config/automation/dashboard" : undefined
|
||||||
),
|
),
|
||||||
...automations.map(computeTileCard),
|
...automations.map(computeTileCard),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const renderMarkdown = async (
|
|||||||
if (!whiteListNormal) {
|
if (!whiteListNormal) {
|
||||||
whiteListNormal = {
|
whiteListNormal = {
|
||||||
...getDefaultWhiteList(),
|
...getDefaultWhiteList(),
|
||||||
|
table: [...(getDefaultWhiteList().table ?? []), "role"],
|
||||||
input: ["type", "disabled", "checked"],
|
input: ["type", "disabled", "checked"],
|
||||||
"ha-icon": ["icon"],
|
"ha-icon": ["icon"],
|
||||||
"ha-svg-icon": ["path"],
|
"ha-svg-icon": ["path"],
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { assert, describe, it } from "vitest";
|
||||||
|
import type { LineSeriesOption } from "echarts/charts";
|
||||||
|
|
||||||
|
import { fillLineGaps } from "../../../../../../src/panels/lovelace/cards/energy/common/energy-chart-options";
|
||||||
|
|
||||||
|
// Helper to get x value from either [x,y] or {value: [x,y]} format
|
||||||
|
function getX(item: any): number {
|
||||||
|
return item?.value?.[0] ?? item?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get y value from either [x,y] or {value: [x,y]} format
|
||||||
|
function getY(item: any): number {
|
||||||
|
return item?.value?.[1] ?? item?.[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("fillLineGaps", () => {
|
||||||
|
it("fills gaps in datasets with missing timestamps", () => {
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
[1000, 10],
|
||||||
|
[3000, 30],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
[1000, 100],
|
||||||
|
[2000, 200],
|
||||||
|
[3000, 300],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
// First dataset should have gap at 2000 filled with 0
|
||||||
|
assert.equal(result[0].data!.length, 3);
|
||||||
|
assert.equal(getX(result[0].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[0].data![0]), 10);
|
||||||
|
assert.equal(getX(result[0].data![1]), 2000);
|
||||||
|
assert.equal(getY(result[0].data![1]), 0);
|
||||||
|
assert.equal(getX(result[0].data![2]), 3000);
|
||||||
|
assert.equal(getY(result[0].data![2]), 30);
|
||||||
|
|
||||||
|
// Second dataset should be unchanged
|
||||||
|
assert.equal(result[1].data!.length, 3);
|
||||||
|
assert.equal(getX(result[1].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[1].data![0]), 100);
|
||||||
|
assert.equal(getX(result[1].data![1]), 2000);
|
||||||
|
assert.equal(getY(result[1].data![1]), 200);
|
||||||
|
assert.equal(getX(result[1].data![2]), 3000);
|
||||||
|
assert.equal(getY(result[1].data![2]), 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles unsorted data from multiple sources", () => {
|
||||||
|
// This is the bug we're fixing: when multiple power sources are combined,
|
||||||
|
// the data may not be in chronological order
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
[3000, 30],
|
||||||
|
[1000, 10],
|
||||||
|
[2000, 20],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
// Data should be sorted by timestamp
|
||||||
|
assert.equal(result[0].data!.length, 3);
|
||||||
|
assert.equal(getX(result[0].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[0].data![0]), 10);
|
||||||
|
assert.equal(getX(result[0].data![1]), 2000);
|
||||||
|
assert.equal(getY(result[0].data![1]), 20);
|
||||||
|
assert.equal(getX(result[0].data![2]), 3000);
|
||||||
|
assert.equal(getY(result[0].data![2]), 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles multiple datasets with unsorted data", () => {
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
[3000, 30],
|
||||||
|
[1000, 10],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
[2000, 200],
|
||||||
|
[1000, 100],
|
||||||
|
[3000, 300],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
// First dataset should be sorted and have gap at 2000 filled
|
||||||
|
assert.equal(result[0].data!.length, 3);
|
||||||
|
assert.equal(getX(result[0].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[0].data![0]), 10);
|
||||||
|
assert.equal(getX(result[0].data![1]), 2000);
|
||||||
|
assert.equal(getY(result[0].data![1]), 0);
|
||||||
|
assert.equal(getX(result[0].data![2]), 3000);
|
||||||
|
assert.equal(getY(result[0].data![2]), 30);
|
||||||
|
|
||||||
|
// Second dataset should be sorted
|
||||||
|
assert.equal(result[1].data!.length, 3);
|
||||||
|
assert.equal(getX(result[1].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[1].data![0]), 100);
|
||||||
|
assert.equal(getX(result[1].data![1]), 2000);
|
||||||
|
assert.equal(getY(result[1].data![1]), 200);
|
||||||
|
assert.equal(getX(result[1].data![2]), 3000);
|
||||||
|
assert.equal(getY(result[1].data![2]), 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles data with object format (LineDataItemOption)", () => {
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [{ value: [3000, 30] }, { value: [1000, 10] }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
assert.equal(result[0].data!.length, 2);
|
||||||
|
assert.equal(getX(result[0].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[0].data![0]), 10);
|
||||||
|
assert.equal(getX(result[0].data![1]), 3000);
|
||||||
|
assert.equal(getY(result[0].data![1]), 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty array for empty datasets", () => {
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
assert.deepEqual(result[0].data, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles already sorted data with no gaps", () => {
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
[1000, 10],
|
||||||
|
[2000, 20],
|
||||||
|
[3000, 30],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
assert.equal(result[0].data!.length, 3);
|
||||||
|
assert.equal(getX(result[0].data![0]), 1000);
|
||||||
|
assert.equal(getY(result[0].data![0]), 10);
|
||||||
|
assert.equal(getX(result[0].data![1]), 2000);
|
||||||
|
assert.equal(getY(result[0].data![1]), 20);
|
||||||
|
assert.equal(getX(result[0].data![2]), 3000);
|
||||||
|
assert.equal(getY(result[0].data![2]), 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves original data item properties", () => {
|
||||||
|
const datasets: LineSeriesOption[] = [
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: [
|
||||||
|
{ value: [2000, 20], itemStyle: { color: "red" } },
|
||||||
|
{ value: [1000, 10], itemStyle: { color: "blue" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = fillLineGaps(datasets);
|
||||||
|
|
||||||
|
// First item should be the one with timestamp 1000
|
||||||
|
const firstItem = result[0].data![0] as any;
|
||||||
|
assert.equal(getX(firstItem), 1000);
|
||||||
|
assert.equal(firstItem.itemStyle.color, "blue");
|
||||||
|
|
||||||
|
// Second item should be the one with timestamp 2000
|
||||||
|
const secondItem = result[0].data![1] as any;
|
||||||
|
assert.equal(getX(secondItem), 2000);
|
||||||
|
assert.equal(secondItem.itemStyle.color, "red");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user