Skip to content

Commit cf145b5

Browse files
cryptomilkclaude
andcommitted
fix(entity): move name position/alignment selectors to Appearance section
The entity widget's name_position and name_align fields controlled visual placement of the name label but lived inline in the Content section instead of Appearance, and had no test coverage for the invert-condition panel that entity shares with tile. Extract them into namePositionSelector()/nameAlignSelector() helpers matching the existing selector pattern, move them into Appearance, and add entity cases to the invert condition panel tests alongside the existing tile ones. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 32d585e commit cf145b5

2 files changed

Lines changed: 158 additions & 31 deletions

File tree

custom_components/eink_dashboard/frontend/src/eink-dashboard-editor.ts

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,46 @@ function boldValueSelector(): HaFormSchema {
388388
};
389389
}
390390

391+
/**
392+
* Name position dropdown selector (above or below the value).
393+
*
394+
* @returns A single ha-form schema entry.
395+
*/
396+
function namePositionSelector(): HaFormSchema {
397+
return {
398+
name: "name_position",
399+
default: "bottom",
400+
selector: {
401+
select: {
402+
options: [
403+
{ value: "bottom", label: "Bottom" },
404+
{ value: "top", label: "Top" },
405+
],
406+
},
407+
},
408+
};
409+
}
410+
411+
/**
412+
* Name alignment dropdown selector.
413+
*
414+
* @returns A single ha-form schema entry.
415+
*/
416+
function nameAlignSelector(): HaFormSchema {
417+
return {
418+
name: "name_align",
419+
default: "left",
420+
selector: {
421+
select: {
422+
options: [
423+
{ value: "left", label: "Left" },
424+
{ value: "right", label: "Right" },
425+
],
426+
},
427+
},
428+
};
429+
}
430+
391431
/**
392432
* Icon style dropdown selector.
393433
*
@@ -757,30 +797,6 @@ export const SCHEMAS: Record<
757797
},
758798
{ name: "attribute", selector: { text: {} } },
759799
{ name: "unit", selector: { text: {} } },
760-
{
761-
name: "name_position",
762-
default: "bottom",
763-
selector: {
764-
select: {
765-
options: [
766-
{ value: "bottom", label: "Bottom" },
767-
{ value: "top", label: "Top" },
768-
],
769-
},
770-
},
771-
},
772-
{
773-
name: "name_align",
774-
default: "left",
775-
selector: {
776-
select: {
777-
options: [
778-
{ value: "left", label: "Left" },
779-
{ value: "right", label: "Right" },
780-
],
781-
},
782-
},
783-
},
784800
],
785801
},
786802
{
@@ -797,7 +813,13 @@ export const SCHEMAS: Record<
797813
flatten: true,
798814
title: "Appearance",
799815
icon: "mdi:palette",
800-
schema: [cardStyleSelector(), iconStyleSelector(), boldValueSelector()],
816+
schema: [
817+
cardStyleSelector(),
818+
iconStyleSelector(),
819+
boldValueSelector(),
820+
namePositionSelector(),
821+
nameAlignSelector(),
822+
],
801823
},
802824
],
803825

custom_components/eink_dashboard/frontend/test/eink-dashboard-editor.test.ts

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ describe("Visibility field", () => {
595595
});
596596
});
597597

598-
// ── Invert condition panel (tile widgets) ───────────────────────────
598+
// ── Invert condition panel (tile/entity widgets) ────────────────────
599599

600600
// Stub HA's <ha-card-conditions-editor> Lit component so the editor's
601601
// conditions panel builder takes its normal path instead of the
@@ -660,19 +660,29 @@ async function mountEditor(
660660
return editor;
661661
}
662662

663-
describe("Invert condition panel (tile widgets)", () => {
663+
describe("Invert condition panel", () => {
664664
it("appears for tile widgets", async () => {
665-
// The Invert panel is tile-specific — it must render when a
666-
// tile widget row is expanded.
665+
// The Invert panel is tile/entity-specific — it must render
666+
// when a tile widget row is expanded.
667667
const editor = await mountEditor([
668668
{ type: "tile", entity: "sensor.x" },
669669
]);
670670
await expandWidget(editor, 0);
671671
expect(findPanelHeader(editor, "Invert")).toBeTruthy();
672672
});
673673

674-
it("does not appear for non-tile widgets", async () => {
675-
// Non-tile widgets have no invert-on-condition rendering, so the
674+
it("appears for entity widgets", async () => {
675+
// The Invert panel is tile/entity-specific — it must render
676+
// when an entity widget row is expanded.
677+
const editor = await mountEditor([
678+
{ type: "entity", entity: "sensor.x" },
679+
]);
680+
await expandWidget(editor, 0);
681+
expect(findPanelHeader(editor, "Invert")).toBeTruthy();
682+
});
683+
684+
it("does not appear for non-tile/entity widgets", async () => {
685+
// Other widgets have no invert-on-condition rendering, so the
676686
// panel must be absent, while the Visibility panel still shows.
677687
const editor = await mountEditor([
678688
{ type: "separator" },
@@ -733,6 +743,46 @@ describe("Invert condition panel (tile widgets)", () => {
733743
},
734744
);
735745

746+
it(
747+
"value-changed on entity invert editor writes invert_condition",
748+
async () => {
749+
// Same write-back behaviour as tile, verified for the entity
750+
// widget's invert panel.
751+
const editor = await mountEditor([
752+
{ type: "entity", entity: "sensor.x" },
753+
]);
754+
await expandWidget(editor, 0);
755+
756+
const header = findPanelHeader(editor, "Invert")!;
757+
const panel = header.parentElement!;
758+
const conditionsEditor = panel.querySelector(
759+
"ha-card-conditions-editor",
760+
)!;
761+
762+
let received: Widget[] | undefined;
763+
editor.addEventListener(
764+
"widget-change",
765+
((ev: CustomEvent<{ widgets: Widget[] }>) => {
766+
received = ev.detail.widgets;
767+
}) as EventListener,
768+
);
769+
770+
const condition: StateCondition = {
771+
condition: "state",
772+
entity: "sensor.test",
773+
state: "on",
774+
};
775+
conditionsEditor.dispatchEvent(
776+
new CustomEvent("value-changed", {
777+
detail: { value: [condition] },
778+
}),
779+
);
780+
781+
expect(received).toBeDefined();
782+
expect(received![0].invert_condition).toEqual([condition]);
783+
},
784+
);
785+
736786
it(
737787
"clearing invert conditions to an empty array deletes the key",
738788
async () => {
@@ -833,4 +883,59 @@ describe("Invert condition panel (tile widgets)", () => {
833883
expect(widget.visibility).toEqual([visibilityCondition]);
834884
},
835885
);
886+
887+
it(
888+
"ha-form edit on an entity preserves invert_condition and visibility",
889+
async () => {
890+
// Same re-attach behaviour as tile, verified for the entity
891+
// widget's ha-form.
892+
const invertCondition: StateCondition = {
893+
condition: "state",
894+
entity: "sensor.invert-source",
895+
state: "on",
896+
};
897+
const visibilityCondition: StateCondition = {
898+
condition: "state",
899+
entity: "sensor.visibility-source",
900+
state: "on",
901+
};
902+
const editor = await mountEditor([
903+
{
904+
type: "entity",
905+
entity: "sensor.x",
906+
invert_condition: [invertCondition],
907+
visibility: [visibilityCondition],
908+
},
909+
]);
910+
await expandWidget(editor, 0);
911+
912+
const form = editor.shadowRoot!.querySelector(
913+
'.widget-item[data-index="0"] ha-form',
914+
)!;
915+
916+
let received: Widget[] | undefined;
917+
editor.addEventListener(
918+
"widget-change",
919+
((ev: CustomEvent<{ widgets: Widget[] }>) => {
920+
received = ev.detail.widgets;
921+
}) as EventListener,
922+
);
923+
924+
// Simulate ha-form emitting a rebuilt value that only contains
925+
// schema-known fields — invert_condition/visibility are absent.
926+
form.dispatchEvent(
927+
new CustomEvent("value-changed", {
928+
detail: { value: { entity: "sensor.y" } },
929+
}),
930+
);
931+
932+
expect(received).toBeDefined();
933+
const widget = received![0] as Widget & {
934+
invert_condition?: unknown;
935+
};
936+
expect(widget.entity).toBe("sensor.y");
937+
expect(widget.invert_condition).toEqual([invertCondition]);
938+
expect(widget.visibility).toEqual([visibilityCondition]);
939+
},
940+
);
836941
});

0 commit comments

Comments
 (0)