Skip to content

Commit 8147c33

Browse files
Copilothotlong
andcommitted
fix: update existing tests for breadcrumb changes and update roadmaps
- Fix ConsoleFeatures and ViewSwitching tests for duplicate text in breadcrumb - Fix ConsoleFeatures dataSource.find mock for record count effect - Update ROADMAP.md and ROADMAP_CONSOLE.md with inline ViewConfigPanel Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent f217fd6 commit 8147c33

5 files changed

Lines changed: 231 additions & 4 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Everything below has been built, tested, and verified. These items are stable an
4949
- **Animation:** 7 presets, reduced-motion aware, page transitions (9 types with View Transitions API).
5050
- **Notifications:** Toast/banner/snackbar with full CRUD integration.
5151
- **View Enhancements:** Gallery, column summary, grouping, row color, density modes, view sharing, ViewTabBar (reorder, pin, context menu, type-switch, personal/shared grouping).
52+
- **Inline View Config Panel:** Airtable-style right sidebar for view configuration (Page, Data, Appearance, User Filters, Actions, Advanced), breadcrumb header, record count footer — no page navigation required.
5253

5354
### Enterprise Features ✅
5455

ROADMAP_CONSOLE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ObjectStack Console — Complete Development Roadmap
22

3-
> **Last Updated:** February 19, 2026 (L3 Development — Phases 11-13 Complete, Phase 19 L1 Complete)
3+
> **Last Updated:** February 20, 2026 (L3 Development — Phases 11-13 Complete, Phase 19 L1 Complete, Phase 20 Inline ViewConfigPanel)
44
> **Current Version:** v2.0.0
55
> **Target Version:** v2.1.0 (Next Minor)
66
> **Spec Alignment:** @objectstack/spec v3.0.8
@@ -1021,6 +1021,7 @@ These were the initial tasks to bring the console prototype to production-qualit
10211021
| **Global Undo/Redo (Ctrl+Z)** | ✅ Done (global UndoManager + batch ops + persistent stack) | Post v1.0 | Phase 16 (L1+L2) |
10221022
| Notification center | ✅ Partial (ActivityFeed with filter preferences) | Post v1.0 | Phase 17 (L2) |
10231023
| Activity feed | ✅ Done | Post v1.0 | Phase 17 (L1) |
1024+
| **Inline View Config Panel** | ✅ Done (Airtable-style right sidebar, breadcrumb header, record count footer) | Post v1.0 | Phase 20 |
10241025

10251026
### 5.5 Kanban & Visual Views
10261027

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('ObjectView Console Features', () => {
7676
];
7777

7878
const mockDataSource = {
79-
find: vi.fn(),
79+
find: vi.fn().mockResolvedValue([]),
8080
findOne: vi.fn(),
8181
create: vi.fn(),
8282
update: vi.fn(),
@@ -102,7 +102,9 @@ describe('ObjectView Console Features', () => {
102102
it('renders ListView', () => {
103103
renderObjectView();
104104

105-
expect(screen.getByText('Todo Task')).toBeInTheDocument();
105+
// "Todo Task" appears in breadcrumb and h1
106+
const headers = screen.getAllByText('Todo Task');
107+
expect(headers.length).toBeGreaterThanOrEqual(1);
106108
// Verify ListView is rendered instead of direct Filter button
107109
expect(screen.getByTestId('list-view')).toBeInTheDocument();
108110
expect(screen.getByText('ListView for todo_task')).toBeInTheDocument();
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/**
2+
* ViewConfigPanel Tests
3+
*
4+
* Tests for the Airtable-style inline view configuration panel.
5+
*/
6+
7+
import { describe, it, expect, vi } from 'vitest';
8+
import { render, screen, fireEvent } from '@testing-library/react';
9+
import '@testing-library/jest-dom';
10+
import { ViewConfigPanel } from '../components/ViewConfigPanel';
11+
12+
// Mock i18n — return keys as-is for test assertions
13+
vi.mock('@object-ui/i18n', () => ({
14+
useObjectTranslation: () => ({
15+
t: (key: string, params?: Record<string, any>) => {
16+
if (params?.count !== undefined) return key.replace('{{count}}', String(params.count));
17+
return key;
18+
},
19+
}),
20+
}));
21+
22+
// Mock components to simple HTML elements
23+
vi.mock('@object-ui/components', () => ({
24+
Button: ({ children, onClick, title, ...props }: any) => (
25+
<button onClick={onClick} title={title} {...props}>{children}</button>
26+
),
27+
}));
28+
29+
const mockActiveView = {
30+
id: 'all',
31+
label: 'All Records',
32+
type: 'grid',
33+
columns: ['name', 'stage', 'amount'],
34+
filter: [{ field: 'stage', operator: '=', value: 'active' }],
35+
sort: [{ field: 'name', order: 'asc' }],
36+
};
37+
38+
const mockObjectDef = {
39+
name: 'opportunity',
40+
label: 'Opportunity',
41+
description: 'Track sales pipeline and deals',
42+
fields: {
43+
name: { label: 'Name', type: 'text' },
44+
stage: { label: 'Stage', type: 'select' },
45+
amount: { label: 'Amount', type: 'currency' },
46+
},
47+
};
48+
49+
describe('ViewConfigPanel', () => {
50+
it('renders nothing when closed', () => {
51+
render(
52+
<ViewConfigPanel
53+
open={false}
54+
onClose={vi.fn()}
55+
activeView={mockActiveView}
56+
objectDef={mockObjectDef}
57+
/>
58+
);
59+
60+
expect(screen.queryByTestId('view-config-panel')).not.toBeInTheDocument();
61+
});
62+
63+
it('renders panel with all sections when open', () => {
64+
render(
65+
<ViewConfigPanel
66+
open={true}
67+
onClose={vi.fn()}
68+
activeView={mockActiveView}
69+
objectDef={mockObjectDef}
70+
/>
71+
);
72+
73+
expect(screen.getByTestId('view-config-panel')).toBeInTheDocument();
74+
75+
// Check section headers
76+
expect(screen.getByText('console.objectView.page')).toBeInTheDocument();
77+
expect(screen.getByText('console.objectView.data')).toBeInTheDocument();
78+
expect(screen.getByText('console.objectView.appearance')).toBeInTheDocument();
79+
expect(screen.getByText('console.objectView.userFilters')).toBeInTheDocument();
80+
expect(screen.getByText('console.objectView.userActions')).toBeInTheDocument();
81+
expect(screen.getByText('console.objectView.advanced')).toBeInTheDocument();
82+
});
83+
84+
it('displays view title', () => {
85+
render(
86+
<ViewConfigPanel
87+
open={true}
88+
onClose={vi.fn()}
89+
activeView={mockActiveView}
90+
objectDef={mockObjectDef}
91+
/>
92+
);
93+
94+
expect(screen.getByText('All Records')).toBeInTheDocument();
95+
});
96+
97+
it('displays object description', () => {
98+
render(
99+
<ViewConfigPanel
100+
open={true}
101+
onClose={vi.fn()}
102+
activeView={mockActiveView}
103+
objectDef={mockObjectDef}
104+
/>
105+
);
106+
107+
expect(screen.getByText('Track sales pipeline and deals')).toBeInTheDocument();
108+
});
109+
110+
it('shows "no description" when object has no description', () => {
111+
render(
112+
<ViewConfigPanel
113+
open={true}
114+
onClose={vi.fn()}
115+
activeView={mockActiveView}
116+
objectDef={{ ...mockObjectDef, description: undefined }}
117+
/>
118+
);
119+
120+
expect(screen.getByText('console.objectView.noDescription')).toBeInTheDocument();
121+
});
122+
123+
it('displays column count', () => {
124+
render(
125+
<ViewConfigPanel
126+
open={true}
127+
onClose={vi.fn()}
128+
activeView={mockActiveView}
129+
objectDef={mockObjectDef}
130+
/>
131+
);
132+
133+
// 3 columns configured
134+
expect(screen.getByText('console.objectView.columnsConfigured'.replace('{{count}}', '3'))).toBeInTheDocument();
135+
});
136+
137+
it('displays object source name', () => {
138+
render(
139+
<ViewConfigPanel
140+
open={true}
141+
onClose={vi.fn()}
142+
activeView={mockActiveView}
143+
objectDef={mockObjectDef}
144+
/>
145+
);
146+
147+
expect(screen.getByText('Opportunity')).toBeInTheDocument();
148+
});
149+
150+
it('displays view type', () => {
151+
render(
152+
<ViewConfigPanel
153+
open={true}
154+
onClose={vi.fn()}
155+
activeView={mockActiveView}
156+
objectDef={mockObjectDef}
157+
/>
158+
);
159+
160+
expect(screen.getByText('Grid')).toBeInTheDocument();
161+
});
162+
163+
it('calls onClose when close button is clicked', () => {
164+
const onClose = vi.fn();
165+
render(
166+
<ViewConfigPanel
167+
open={true}
168+
onClose={onClose}
169+
activeView={mockActiveView}
170+
objectDef={mockObjectDef}
171+
/>
172+
);
173+
174+
const closeBtn = screen.getByTitle('console.objectView.closePanel');
175+
fireEvent.click(closeBtn);
176+
177+
expect(onClose).toHaveBeenCalledOnce();
178+
});
179+
180+
it('updates content when activeView changes', () => {
181+
const { rerender } = render(
182+
<ViewConfigPanel
183+
open={true}
184+
onClose={vi.fn()}
185+
activeView={mockActiveView}
186+
objectDef={mockObjectDef}
187+
/>
188+
);
189+
190+
expect(screen.getByText('All Records')).toBeInTheDocument();
191+
expect(screen.getByText('Grid')).toBeInTheDocument();
192+
193+
// Switch to kanban view
194+
rerender(
195+
<ViewConfigPanel
196+
open={true}
197+
onClose={vi.fn()}
198+
activeView={{ id: 'pipeline', label: 'Pipeline', type: 'kanban', columns: ['name'] }}
199+
objectDef={mockObjectDef}
200+
/>
201+
);
202+
203+
expect(screen.getByText('Pipeline')).toBeInTheDocument();
204+
expect(screen.getByText('Kanban')).toBeInTheDocument();
205+
});
206+
207+
it('shows "None" for empty filters and columns', () => {
208+
render(
209+
<ViewConfigPanel
210+
open={true}
211+
onClose={vi.fn()}
212+
activeView={{ id: 'empty', label: 'Empty View', type: 'grid' }}
213+
objectDef={mockObjectDef}
214+
/>
215+
);
216+
217+
// Should show "None" for columns, filters
218+
const noneTexts = screen.getAllByText('console.objectView.none');
219+
expect(noneTexts.length).toBeGreaterThanOrEqual(2);
220+
});
221+
});

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ describe('Console View Switching Integration', () => {
9393
it('renders all view tabs', () => {
9494
renderObjectView();
9595

96-
expect(screen.getByText('All Tasks')).toBeInTheDocument();
96+
// "All Tasks" appears in breadcrumb and tab, so use getAllByText
97+
const allTasksElements = screen.getAllByText('All Tasks');
98+
expect(allTasksElements.length).toBeGreaterThanOrEqual(1);
9799
expect(screen.getByText('Board')).toBeInTheDocument();
98100
expect(screen.getByText('Schedule')).toBeInTheDocument();
99101
expect(screen.getByText('Roadmap')).toBeInTheDocument();

0 commit comments

Comments
 (0)