Skip to content

Commit 9a0486d

Browse files
authored
Merge pull request #730 from objectstack-ai/copilot/fix-viewconfigpanel-details
2 parents e2b35f9 + 90d9316 commit 9a0486d

File tree

14 files changed

+767
-32
lines changed

14 files changed

+767
-32
lines changed

ROADMAP.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,12 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
204204
-`NamedListView` type extended with 24 new properties: navigation, selection, pagination, searchableFields, filterableFields, resizable, densityMode, rowHeight, hiddenFields, exportOptions, rowActions, bulkActions, sharing, addRecord, conditionalFormatting, quickFilters, showRecordCount, allowPrinting, virtualScroll, emptyState, aria
205205
-`ListViewSchema` Zod schema extended with all new properties
206206
- ✅ ViewConfigPanel aligned to full `ListViewSchema` spec: navigation mode, selection, pagination, export sub-config, searchable/filterable/hidden fields, resizable, density mode, row/bulk actions, sharing, addRecord sub-editor, conditional formatting, quick filters, showRecordCount, allowPrinting, virtualScroll, empty state, ARIA accessibility
207-
- ✅ Semantic fix: `editRecordsInline``inlineEdit` field name alignment
207+
- ✅ Semantic fix: `editRecordsInline``inlineEdit` field name alignment (i18n keys, data-testid, component label all unified to `inlineEdit`)
208208
- ✅ Semantic fix: `rowHeight` values aligned to spec (`compact`/`medium`/`tall`)
209+
- ✅ i18n keys verified complete for en/zh and all 10 locale files
209210
- ✅ Console ObjectView fullSchema propagates all 18 new spec properties
210211
- ✅ PluginObjectView renderListView schema propagates all 18 new spec properties
211-
- ⚠️ No per-view-type integration tests verifying config properties reach non-grid renderers
212+
- ✅ Per-view-type integration tests added for Grid/Kanban/Calendar/Timeline/Gantt/Gallery/Map config sync (Phase 7 complete)
212213
- [x] Conditional formatting rules (editor in Appearance section)
213214

214215
### P1.8.1 Live Preview — Gap Analysis & Phased Remediation
@@ -267,25 +268,25 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
267268
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` kanban branch
268269
- [x] Pass `color`/`striped`/`bordered` in `renderContent``renderListView` for kanban
269270
- [x] Ensure `groupBy` config changes reflect immediately (currently ✅ via `renderListView`)
270-
- [ ] Add integration test: ViewConfigPanel kanban config change → Kanban renderer receives updated props
271+
- [x] Add integration test: ViewConfigPanel kanban config change → Kanban renderer receives updated props
271272

272273
**Phase 3 — Calendar Live Preview:**
273274
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` calendar branch
274275
- [x] Pass `filter`/`sort`/appearance properties to calendar renderer in real-time
275276
- [x] Verify `startDateField`/`endDateField` config changes trigger re-render via `useMemo` deps
276-
- [ ] Add integration test: ViewConfigPanel calendar config change → Calendar renderer receives updated props
277+
- [x] Add integration test: ViewConfigPanel calendar config change → Calendar renderer receives updated props
277278

278279
**Phase 4 — Timeline/Gantt Live Preview:**
279280
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` timeline/gantt branches
280281
- [x] Pass appearance properties (`color`, `striped`, `bordered`) through `renderListView` schema
281282
- [x] Ensure `dateField`/`startDateField`/`endDateField` config changes trigger re-render
282-
- [ ] Add integration tests for timeline and gantt config sync
283+
- [x] Add integration tests for timeline and gantt config sync
283284

284285
**Phase 5 — Gallery & Map Live Preview:**
285286
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` gallery/map branches
286287
- [x] Pass appearance properties through `renderListView` schema for gallery/map
287288
- [x] Ensure gallery `imageField`/`titleField` and map `locationField`/`zoom`/`center` config changes trigger re-render
288-
- [ ] Add integration tests for gallery and map config sync
289+
- [x] Add integration tests for gallery and map config sync
289290

290291
**Phase 6 — Data Flow & Dependency Refactor:**
291292
- [x] Add `showSearch`/`showSort`/`showFilters`/`striped`/`bordered`/`color` to `NamedListView` type in `@object-ui/types`
@@ -296,13 +297,13 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
296297
- [x] Remove hardcoded `showSearch: false` from `generateViewSchema` — use `activeView.showSearch ?? schema.showSearch` instead
297298

298299
**Phase 7 — End-to-End Integration Tests:**
299-
- [ ] Per-view-type test: Grid config sync (showSort, showSearch, showFilters, striped, bordered)
300-
- [ ] Per-view-type test: Kanban config sync (groupBy, color, showSearch)
301-
- [ ] Per-view-type test: Calendar config sync (startDateField, endDateField, showFilters)
302-
- [ ] Per-view-type test: Timeline/Gantt config sync (dateField, appearance)
303-
- [ ] Per-view-type test: Gallery config sync (imageField, titleField, appearance)
304-
- [ ] Per-view-type test: Map config sync (locationField, zoom, center, appearance)
305-
- [ ] Cross-view-type test: Switch view type in ViewConfigPanel → verify config properties transfer correctly
300+
- [x] Per-view-type test: Grid config sync (showSort, showSearch, showFilters, striped, bordered)
301+
- [x] Per-view-type test: Kanban config sync (groupBy, color, showSearch)
302+
- [x] Per-view-type test: Calendar config sync (startDateField, endDateField, showFilters)
303+
- [x] Per-view-type test: Timeline/Gantt config sync (dateField, appearance)
304+
- [x] Per-view-type test: Gallery config sync (imageField, titleField, appearance)
305+
- [x] Per-view-type test: Map config sync (locationField, zoom, center, appearance)
306+
- [x] Cross-view-type test: Switch view type in ViewConfigPanel → verify config properties transfer correctly
306307

307308
### P1.10 Console — Dashboard Config Panel
308309

apps/console/src/__tests__/ViewConfigPanel.test.tsx

Lines changed: 186 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,7 @@ describe('ViewConfigPanel', () => {
11981198

11991199
// ── User actions fields tests ──
12001200

1201-
it('renders new user action fields: editRecordsInline, addDeleteRecordsInline, and navigation mode select', () => {
1201+
it('renders new user action fields: inlineEdit, addDeleteRecordsInline, and navigation mode select', () => {
12021202
render(
12031203
<ViewConfigPanel
12041204
open={true}
@@ -1208,12 +1208,12 @@ describe('ViewConfigPanel', () => {
12081208
/>
12091209
);
12101210

1211-
expect(screen.getByTestId('toggle-editRecordsInline')).toBeInTheDocument();
1211+
expect(screen.getByTestId('toggle-inlineEdit')).toBeInTheDocument();
12121212
expect(screen.getByTestId('toggle-addDeleteRecordsInline')).toBeInTheDocument();
12131213
expect(screen.getByTestId('select-navigation-mode')).toBeInTheDocument();
12141214
});
12151215

1216-
it('toggles editRecordsInline via Switch (maps to inlineEdit)', () => {
1216+
it('toggles inlineEdit via Switch', () => {
12171217
const onViewUpdate = vi.fn();
12181218
render(
12191219
<ViewConfigPanel
@@ -1225,7 +1225,7 @@ describe('ViewConfigPanel', () => {
12251225
/>
12261226
);
12271227

1228-
fireEvent.click(screen.getByTestId('toggle-editRecordsInline'));
1228+
fireEvent.click(screen.getByTestId('toggle-inlineEdit'));
12291229
expect(onViewUpdate).toHaveBeenCalledWith('inlineEdit', false);
12301230
});
12311231

@@ -1520,7 +1520,7 @@ describe('ViewConfigPanel', () => {
15201520
expect(screen.getByText('console.objectView.listConfigHint')).toBeInTheDocument();
15211521
});
15221522

1523-
it('renders list-level inline action items in the User Actions section (editRecordsInline, addDeleteRecordsInline)', () => {
1523+
it('renders list-level inline action items in the User Actions section (inlineEdit, addDeleteRecordsInline)', () => {
15241524
render(
15251525
<ViewConfigPanel
15261526
open={true}
@@ -1531,7 +1531,7 @@ describe('ViewConfigPanel', () => {
15311531
);
15321532

15331533
// List-level inline actions should be in the User Actions collapsible section
1534-
expect(screen.getByTestId('toggle-editRecordsInline')).toBeInTheDocument();
1534+
expect(screen.getByTestId('toggle-inlineEdit')).toBeInTheDocument();
15351535
expect(screen.getByTestId('toggle-addDeleteRecordsInline')).toBeInTheDocument();
15361536
});
15371537

@@ -2150,4 +2150,184 @@ describe('ViewConfigPanel', () => {
21502150
fireEvent.click(screen.getByTestId('row-height-tall'));
21512151
expect(onViewUpdate).toHaveBeenCalledWith('rowHeight', 'tall');
21522152
});
2153+
2154+
// ── Interaction tests for emptyState, ARIA, rowActions, bulkActions, showRecordCount, allowPrinting, virtualScroll ──
2155+
2156+
it('updates emptyState title via input and calls onViewUpdate', () => {
2157+
const onViewUpdate = vi.fn();
2158+
render(
2159+
<ViewConfigPanel
2160+
open={true}
2161+
onClose={vi.fn()}
2162+
activeView={mockActiveView}
2163+
objectDef={mockObjectDef}
2164+
onViewUpdate={onViewUpdate}
2165+
/>
2166+
);
2167+
2168+
fireEvent.change(screen.getByTestId('input-emptyState-title'), { target: { value: 'No data' } });
2169+
expect(onViewUpdate).toHaveBeenCalledWith('emptyState', expect.objectContaining({ title: 'No data' }));
2170+
});
2171+
2172+
it('updates emptyState message via input and calls onViewUpdate', () => {
2173+
const onViewUpdate = vi.fn();
2174+
render(
2175+
<ViewConfigPanel
2176+
open={true}
2177+
onClose={vi.fn()}
2178+
activeView={mockActiveView}
2179+
objectDef={mockObjectDef}
2180+
onViewUpdate={onViewUpdate}
2181+
/>
2182+
);
2183+
2184+
fireEvent.change(screen.getByTestId('input-emptyState-message'), { target: { value: 'Try adding records' } });
2185+
expect(onViewUpdate).toHaveBeenCalledWith('emptyState', expect.objectContaining({ message: 'Try adding records' }));
2186+
});
2187+
2188+
it('updates emptyState icon via input and calls onViewUpdate', () => {
2189+
const onViewUpdate = vi.fn();
2190+
render(
2191+
<ViewConfigPanel
2192+
open={true}
2193+
onClose={vi.fn()}
2194+
activeView={mockActiveView}
2195+
objectDef={mockObjectDef}
2196+
onViewUpdate={onViewUpdate}
2197+
/>
2198+
);
2199+
2200+
fireEvent.change(screen.getByTestId('input-emptyState-icon'), { target: { value: 'inbox' } });
2201+
expect(onViewUpdate).toHaveBeenCalledWith('emptyState', expect.objectContaining({ icon: 'inbox' }));
2202+
});
2203+
2204+
it('updates ARIA label via input and calls onViewUpdate', () => {
2205+
const onViewUpdate = vi.fn();
2206+
render(
2207+
<ViewConfigPanel
2208+
open={true}
2209+
onClose={vi.fn()}
2210+
activeView={mockActiveView}
2211+
objectDef={mockObjectDef}
2212+
onViewUpdate={onViewUpdate}
2213+
/>
2214+
);
2215+
2216+
fireEvent.change(screen.getByTestId('input-aria-label'), { target: { value: 'Contacts table' } });
2217+
expect(onViewUpdate).toHaveBeenCalledWith('aria', expect.objectContaining({ label: 'Contacts table' }));
2218+
});
2219+
2220+
it('updates ARIA describedBy via input and calls onViewUpdate', () => {
2221+
const onViewUpdate = vi.fn();
2222+
render(
2223+
<ViewConfigPanel
2224+
open={true}
2225+
onClose={vi.fn()}
2226+
activeView={mockActiveView}
2227+
objectDef={mockObjectDef}
2228+
onViewUpdate={onViewUpdate}
2229+
/>
2230+
);
2231+
2232+
fireEvent.change(screen.getByTestId('input-aria-describedBy'), { target: { value: 'table-desc' } });
2233+
expect(onViewUpdate).toHaveBeenCalledWith('aria', expect.objectContaining({ describedBy: 'table-desc' }));
2234+
});
2235+
2236+
it('changes ARIA live region and calls onViewUpdate', () => {
2237+
const onViewUpdate = vi.fn();
2238+
render(
2239+
<ViewConfigPanel
2240+
open={true}
2241+
onClose={vi.fn()}
2242+
activeView={mockActiveView}
2243+
objectDef={mockObjectDef}
2244+
onViewUpdate={onViewUpdate}
2245+
/>
2246+
);
2247+
2248+
fireEvent.change(screen.getByTestId('select-aria-live'), { target: { value: 'polite' } });
2249+
expect(onViewUpdate).toHaveBeenCalledWith('aria', expect.objectContaining({ live: 'polite' }));
2250+
});
2251+
2252+
it('updates rowActions via input and calls onViewUpdate', () => {
2253+
const onViewUpdate = vi.fn();
2254+
render(
2255+
<ViewConfigPanel
2256+
open={true}
2257+
onClose={vi.fn()}
2258+
activeView={mockActiveView}
2259+
objectDef={mockObjectDef}
2260+
onViewUpdate={onViewUpdate}
2261+
/>
2262+
);
2263+
2264+
fireEvent.click(screen.getByText('console.objectView.rowActions'));
2265+
fireEvent.change(screen.getByTestId('input-rowActions'), { target: { value: 'edit, delete' } });
2266+
expect(onViewUpdate).toHaveBeenCalledWith('rowActions', ['edit', 'delete']);
2267+
});
2268+
2269+
it('updates bulkActions via input and calls onViewUpdate', () => {
2270+
const onViewUpdate = vi.fn();
2271+
render(
2272+
<ViewConfigPanel
2273+
open={true}
2274+
onClose={vi.fn()}
2275+
activeView={mockActiveView}
2276+
objectDef={mockObjectDef}
2277+
onViewUpdate={onViewUpdate}
2278+
/>
2279+
);
2280+
2281+
fireEvent.click(screen.getByText('console.objectView.bulkActions'));
2282+
fireEvent.change(screen.getByTestId('input-bulkActions'), { target: { value: 'delete, export' } });
2283+
expect(onViewUpdate).toHaveBeenCalledWith('bulkActions', ['delete', 'export']);
2284+
});
2285+
2286+
it('toggles showRecordCount and calls onViewUpdate', () => {
2287+
const onViewUpdate = vi.fn();
2288+
render(
2289+
<ViewConfigPanel
2290+
open={true}
2291+
onClose={vi.fn()}
2292+
activeView={mockActiveView}
2293+
objectDef={mockObjectDef}
2294+
onViewUpdate={onViewUpdate}
2295+
/>
2296+
);
2297+
2298+
fireEvent.click(screen.getByTestId('toggle-showRecordCount'));
2299+
expect(onViewUpdate).toHaveBeenCalledWith('showRecordCount', true);
2300+
});
2301+
2302+
it('toggles allowPrinting and calls onViewUpdate', () => {
2303+
const onViewUpdate = vi.fn();
2304+
render(
2305+
<ViewConfigPanel
2306+
open={true}
2307+
onClose={vi.fn()}
2308+
activeView={mockActiveView}
2309+
objectDef={mockObjectDef}
2310+
onViewUpdate={onViewUpdate}
2311+
/>
2312+
);
2313+
2314+
fireEvent.click(screen.getByTestId('toggle-allowPrinting'));
2315+
expect(onViewUpdate).toHaveBeenCalledWith('allowPrinting', true);
2316+
});
2317+
2318+
it('toggles virtualScroll and calls onViewUpdate', () => {
2319+
const onViewUpdate = vi.fn();
2320+
render(
2321+
<ViewConfigPanel
2322+
open={true}
2323+
onClose={vi.fn()}
2324+
activeView={mockActiveView}
2325+
objectDef={mockObjectDef}
2326+
onViewUpdate={onViewUpdate}
2327+
/>
2328+
);
2329+
2330+
fireEvent.click(screen.getByTestId('toggle-virtualScroll'));
2331+
expect(onViewUpdate).toHaveBeenCalledWith('virtualScroll', true);
2332+
});
21532333
});

apps/console/src/components/ViewConfigPanel.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,10 +1481,9 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
14811481
/>
14821482
{!collapsedSections.userActions && (
14831483
<div className="space-y-0.5">
1484-
{/* Semantic fix A: editRecordsInline → inlineEdit */}
1485-
<ConfigRow label={t('console.objectView.editRecordsInline')}>
1484+
<ConfigRow label={t('console.objectView.inlineEdit')}>
14861485
<Switch
1487-
data-testid="toggle-editRecordsInline"
1486+
data-testid="toggle-inlineEdit"
14881487
checked={draft.inlineEdit !== false}
14891488
onCheckedChange={(checked: boolean) => updateDraft('inlineEdit', checked)}
14901489
className="scale-75"

packages/i18n/src/locales/ar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ const ar = {
244244
collapseAllByDefault: 'طي الكل افتراضياً',
245245
striped: 'صفوف مخططة',
246246
bordered: 'خلايا محاطة بحدود',
247-
editRecordsInline: 'تحرير السجلات مباشرة',
247+
inlineEdit: 'تحرير السجلات مباشرة',
248248
addDeleteRecordsInline: 'إضافة/حذف السجلات مباشرة',
249249
clickIntoRecordDetails: 'انقر لعرض تفاصيل السجل',
250250
},

packages/i18n/src/locales/de.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ const de = {
248248
collapseAllByDefault: 'Standardmäßig alle einklappen',
249249
striped: 'Gestreifte Zeilen',
250250
bordered: 'Umrandete Zellen',
251-
editRecordsInline: 'Datensätze inline bearbeiten',
251+
inlineEdit: 'Datensätze inline bearbeiten',
252252
addDeleteRecordsInline: 'Datensätze inline hinzufügen/löschen',
253253
clickIntoRecordDetails: 'Klicken für Datensatzdetails',
254254
},

packages/i18n/src/locales/en.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ const en = {
248248
collapseAllByDefault: 'Collapse all by default',
249249
striped: 'Striped rows',
250250
bordered: 'Bordered cells',
251-
editRecordsInline: 'Edit records inline',
251+
inlineEdit: 'Edit records inline',
252252
addDeleteRecordsInline: 'Add/delete records inline',
253253
clickIntoRecordDetails: 'Click into record details',
254254
navigationMode: 'Navigation mode',
@@ -295,6 +295,7 @@ const en = {
295295
ariaDescribedBy: 'ARIA described by',
296296
ariaLive: 'ARIA live',
297297
accessibility: 'Accessibility',
298+
viewTabs: 'View Tabs',
298299
},
299300
localeSwitcher: {
300301
label: 'Language',

packages/i18n/src/locales/es.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ const es = {
243243
collapseAllByDefault: 'Contraer todo por defecto',
244244
striped: 'Filas alternadas',
245245
bordered: 'Celdas con borde',
246-
editRecordsInline: 'Editar registros en línea',
246+
inlineEdit: 'Editar registros en línea',
247247
addDeleteRecordsInline: 'Agregar/eliminar registros en línea',
248248
clickIntoRecordDetails: 'Clic para detalles del registro',
249249
},

packages/i18n/src/locales/fr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ const fr = {
248248
collapseAllByDefault: 'Tout réduire par défaut',
249249
striped: 'Lignes rayées',
250250
bordered: 'Cellules bordées',
251-
editRecordsInline: 'Modifier les enregistrements en ligne',
251+
inlineEdit: 'Modifier les enregistrements en ligne',
252252
addDeleteRecordsInline: 'Ajouter/supprimer des enregistrements en ligne',
253253
clickIntoRecordDetails: "Cliquer pour les détails de l'enregistrement",
254254
},

packages/i18n/src/locales/ja.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ const ja = {
243243
collapseAllByDefault: 'デフォルトですべて折りたたむ',
244244
striped: 'ストライプ行',
245245
bordered: 'ボーダーセル',
246-
editRecordsInline: 'インラインでレコードを編集',
246+
inlineEdit: 'インラインでレコードを編集',
247247
addDeleteRecordsInline: 'インラインでレコードを追加/削除',
248248
clickIntoRecordDetails: 'レコード詳細をクリックで表示',
249249
},

packages/i18n/src/locales/ko.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ const ko = {
243243
collapseAllByDefault: '기본적으로 모두 접기',
244244
striped: '줄무늬 행',
245245
bordered: '테두리 셀',
246-
editRecordsInline: '인라인으로 레코드 편집',
246+
inlineEdit: '인라인으로 레코드 편집',
247247
addDeleteRecordsInline: '인라인으로 레코드 추가/삭제',
248248
clickIntoRecordDetails: '레코드 상세 보기 클릭',
249249
},

0 commit comments

Comments
 (0)