Skip to content

Commit 1e99fb3

Browse files
committed
Render LVGL common properties before schema
Call addCommonLVGLProperties for widgets whose type starts with "lvgl_" prior to running the schema renderer so LVGL-specific common controls appear before schema-driven fields. Tests adjusted to introduce a reusable mock for registry.get, reset it in setup, and updated several specs: verify single-widget fallback behavior, auto-populate title from an entity, and ensure common LVGL properties are added before SchemaRenderer is invoked.
1 parent e8b74fc commit 1e99fb3

File tree

2 files changed

+35
-31
lines changed

2 files changed

+35
-31
lines changed

custom_components/esphome_designer/frontend/js/core/properties.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ export class PropertiesPanel {
177177
const mode = AppState.settings?.renderingMode || 'direct';
178178

179179
if (registryEntry && registryEntry.schema) {
180+
if (type.startsWith('lvgl_')) {
181+
this.addCommonLVGLProperties(widget, widget.props || {});
182+
}
180183
SchemaRenderer.render(this, widget, registryEntry.schema);
181184
} else if (registryEntry && registryEntry.renderProperties) {
182185
registryEntry.renderProperties(this, widget);

custom_components/esphome_designer/frontend/tests/core/properties.test.js

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const mockMultiRender = vi.fn();
66
const mockGridRender = vi.fn();
77
const mockLegacyRender = vi.fn();
88
const mockProtocolRender = vi.fn();
9+
const mockRegistryGet = vi.fn(() => null);
910

1011
const controlsMethods = {
1112
addLabeledInput: vi.fn(),
@@ -57,7 +58,7 @@ vi.mock('../../js/core/state', () => ({
5758

5859
vi.mock('../../js/core/plugin_registry', () => ({
5960
registry: {
60-
get: vi.fn(() => null)
61+
get: mockRegistryGet
6162
}
6263
}));
6364

@@ -115,6 +116,8 @@ describe('PropertiesPanel', () => {
115116
mockAppState.getSelectedWidget.mockReturnValue(null);
116117
mockAppState.getSelectedWidgets.mockReturnValue([]);
117118
mockAppState.settings.renderingMode = 'direct';
119+
mockRegistryGet.mockReset();
120+
mockRegistryGet.mockReturnValue(null);
118121

119122
document.body.innerHTML = `
120123
<div id="propertiesPanel"></div>
@@ -159,7 +162,7 @@ describe('PropertiesPanel', () => {
159162
expect(mockMultiRender).toHaveBeenCalledWith(panel, ['w1', 'w2']);
160163
});
161164

162-
it('re-renders into multi-select mode when selection expands but first id stays the same', async () => {
165+
it('renders single-widget panel and falls back to legacy renderer', async () => {
163166
const widget = {
164167
id: 'w1',
165168
type: 'sensor_text',
@@ -171,39 +174,49 @@ describe('PropertiesPanel', () => {
171174
};
172175

173176
mockAppState.selectedWidgetId = 'w1';
177+
mockAppState.selectedWidgetIds = ['w1'];
178+
mockAppState.getSelectedWidgetIds.mockReturnValue(['w1']);
174179
mockAppState.getSelectedWidget.mockReturnValue(widget);
175180
mockAppState.getSelectedWidgets.mockReturnValue([widget]);
176181

177182
const { PropertiesPanel } = await import('../../js/core/properties.js');
178183
const panel = new PropertiesPanel(mockApp);
179184

180-
// First render in single-select mode
181-
mockAppState.getSelectedWidgetIds.mockReturnValue(['w1']);
182-
mockAppState.selectedWidgetIds = ['w1'];
183185
panel.render();
184186

185-
// Expand selection to multi-select while keeping first selected ID
186-
mockAppState.getSelectedWidgetIds.mockReturnValue(['w1', 'w2']);
187-
mockAppState.selectedWidgetIds = ['w1', 'w2'];
188-
panel.render();
187+
const content = document.getElementById('propertiesPanel')?.textContent || '';
188+
expect(content).toContain('Properties');
189+
expect(mockGridRender).toHaveBeenCalled();
190+
expect(mockLegacyRender).toHaveBeenCalled();
191+
expect(controlsMethods.addVisibilityConditions).toHaveBeenCalled();
192+
});
189193

190-
expect(mockMultiRender).toHaveBeenCalledWith(panel, ['w1', 'w2']);
194+
it('auto-populates title from selected entity state', async () => {
195+
const { PropertiesPanel } = await import('../../js/core/properties.js');
196+
const panel = new PropertiesPanel(mockApp);
197+
198+
panel.autoPopulateTitleFromEntity('w1', 'sensor.temp');
199+
200+
expect(mockAppState.updateWidget).toHaveBeenCalledWith('w1', { title: 'Temperature' });
191201
});
192202

193-
it('renders single-widget panel and falls back to legacy renderer', async () => {
203+
it('renders common LVGL properties before schema-driven LVGL fields', async () => {
194204
const widget = {
195-
id: 'w1',
196-
type: 'sensor_text',
205+
id: 'w-lvgl',
206+
type: 'lvgl_button',
197207
x: 10,
198208
y: 20,
199209
width: 100,
200210
height: 40,
201-
props: {}
211+
props: { clickable: true }
202212
};
203213

204-
mockAppState.selectedWidgetId = 'w1';
205-
mockAppState.selectedWidgetIds = ['w1'];
206-
mockAppState.getSelectedWidgetIds.mockReturnValue(['w1']);
214+
mockRegistryGet.mockReturnValue({
215+
schema: [{ section: 'Content', fields: [] }]
216+
});
217+
mockAppState.selectedWidgetId = 'w-lvgl';
218+
mockAppState.selectedWidgetIds = ['w-lvgl'];
219+
mockAppState.getSelectedWidgetIds.mockReturnValue(['w-lvgl']);
207220
mockAppState.getSelectedWidget.mockReturnValue(widget);
208221
mockAppState.getSelectedWidgets.mockReturnValue([widget]);
209222

@@ -212,20 +225,8 @@ describe('PropertiesPanel', () => {
212225

213226
panel.render();
214227

215-
const content = document.getElementById('propertiesPanel')?.textContent || '';
216-
expect(content).toContain('Properties');
217-
expect(mockGridRender).toHaveBeenCalled();
218-
expect(mockLegacyRender).toHaveBeenCalled();
219-
expect(controlsMethods.addVisibilityConditions).toHaveBeenCalled();
220-
});
221-
222-
it('auto-populates title from selected entity state', async () => {
223-
const { PropertiesPanel } = await import('../../js/core/properties.js');
224-
const panel = new PropertiesPanel(mockApp);
225-
226-
panel.autoPopulateTitleFromEntity('w1', 'sensor.temp');
227-
228-
expect(mockAppState.updateWidget).toHaveBeenCalledWith('w1', { title: 'Temperature' });
228+
expect(controlsMethods.addCommonLVGLProperties).toHaveBeenCalledWith(widget, widget.props);
229+
expect(mockSchemaRender).toHaveBeenCalled();
229230
});
230231

231232
it('invokes drop-shadow helper action for selected widgets', async () => {

0 commit comments

Comments
 (0)