Skip to content

Commit 313be35

Browse files
Copilothotlong
andcommitted
test: add empty state and system routes tests
Validates: create-app button always visible, system settings link present, system routes accessible when no active app exists. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 58a4892 commit 313be35

1 file changed

Lines changed: 209 additions & 0 deletions

File tree

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/**
2+
* Empty State & System Routes Tests
3+
*
4+
* Validates the empty state behavior when no apps are configured
5+
* and the availability of system routes and create-app entry points.
6+
*
7+
* Requirements:
8+
* - "Create App" button always visible in empty state (even on error)
9+
* - "System Settings" link always visible in empty state
10+
* - System routes accessible without app context
11+
* - Login/Register/Forgot password always accessible
12+
*/
13+
14+
import { describe, it, expect, vi, beforeEach } from 'vitest';
15+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
16+
import '@testing-library/jest-dom';
17+
import { MemoryRouter, Routes, Route } from 'react-router-dom';
18+
import { AppContent } from '../App';
19+
20+
// --- Mocks ---
21+
22+
// Mock MetadataProvider with NO apps (empty state)
23+
vi.mock('../context/MetadataProvider', () => ({
24+
MetadataProvider: ({ children }: any) => <>{children}</>,
25+
useMetadata: () => ({
26+
apps: [],
27+
objects: [],
28+
dashboards: [],
29+
reports: [],
30+
pages: [],
31+
loading: false,
32+
error: null,
33+
refresh: vi.fn(),
34+
}),
35+
}));
36+
37+
// Mock AdapterProvider
38+
const MockAdapterInstance = {
39+
find: vi.fn().mockResolvedValue([]),
40+
findOne: vi.fn(),
41+
create: vi.fn(),
42+
update: vi.fn(),
43+
delete: vi.fn(),
44+
connect: vi.fn().mockResolvedValue(true),
45+
onConnectionStateChange: vi.fn().mockReturnValue(() => {}),
46+
getConnectionState: vi.fn().mockReturnValue('connected'),
47+
discovery: {},
48+
};
49+
50+
vi.mock('../context/AdapterProvider', () => ({
51+
AdapterProvider: ({ children }: any) => <>{children}</>,
52+
useAdapter: () => MockAdapterInstance,
53+
}));
54+
55+
vi.mock('../dataSource', () => {
56+
const MockAdapter = class {
57+
find = vi.fn().mockResolvedValue([]);
58+
findOne = vi.fn();
59+
create = vi.fn();
60+
update = vi.fn();
61+
delete = vi.fn();
62+
connect = vi.fn().mockResolvedValue(true);
63+
onConnectionStateChange = vi.fn().mockReturnValue(() => {});
64+
getConnectionState = vi.fn().mockReturnValue('connected');
65+
discovery = {};
66+
};
67+
return {
68+
ObjectStackAdapter: MockAdapter,
69+
ObjectStackDataSource: MockAdapter,
70+
};
71+
});
72+
73+
// Mock child components to simplify testing
74+
vi.mock('../components/ObjectView', () => ({
75+
ObjectView: () => <div data-testid="object-view">Object View</div>,
76+
}));
77+
78+
vi.mock('@object-ui/components', async (importOriginal) => {
79+
const actual = await importOriginal<any>();
80+
return {
81+
...actual,
82+
TooltipProvider: ({ children }: any) => <div>{children}</div>,
83+
Dialog: ({ children, open }: any) => open ? <div data-testid="dialog">{children}</div> : null,
84+
DialogContent: ({ children }: any) => <div>{children}</div>,
85+
};
86+
});
87+
88+
vi.mock('lucide-react', async (importOriginal) => {
89+
const actual = await importOriginal<any>();
90+
return {
91+
...actual,
92+
Database: () => <span data-testid="icon-database" />,
93+
Settings: () => <span data-testid="icon-settings" />,
94+
Plus: () => <span />,
95+
Search: () => <span />,
96+
ChevronsUpDown: () => <span />,
97+
LogOut: () => <span />,
98+
ChevronRight: () => <span />,
99+
Clock: () => <span />,
100+
Star: () => <span />,
101+
StarOff: () => <span />,
102+
Pencil: () => <span />,
103+
};
104+
});
105+
106+
// System pages mocks
107+
vi.mock('../pages/system/SystemHubPage', () => ({
108+
SystemHubPage: () => <div data-testid="system-hub-page">System Hub</div>,
109+
}));
110+
111+
vi.mock('../pages/system/AppManagementPage', () => ({
112+
AppManagementPage: () => <div data-testid="app-management-page">App Management</div>,
113+
}));
114+
115+
vi.mock('../pages/system/UserManagementPage', () => ({
116+
UserManagementPage: () => <div data-testid="user-management-page">User Management</div>,
117+
}));
118+
119+
vi.mock('../pages/system/OrgManagementPage', () => ({
120+
OrgManagementPage: () => <div data-testid="org-management-page">Org Management</div>,
121+
}));
122+
123+
vi.mock('../pages/system/RoleManagementPage', () => ({
124+
RoleManagementPage: () => <div data-testid="role-management-page">Role Management</div>,
125+
}));
126+
127+
vi.mock('../pages/system/PermissionManagementPage', () => ({
128+
PermissionManagementPage: () => <div data-testid="permission-management-page">Permission Management</div>,
129+
}));
130+
131+
vi.mock('../pages/system/AuditLogPage', () => ({
132+
AuditLogPage: () => <div data-testid="audit-log-page">Audit Log</div>,
133+
}));
134+
135+
vi.mock('../pages/system/ProfilePage', () => ({
136+
ProfilePage: () => <div data-testid="profile-page">Profile</div>,
137+
}));
138+
139+
vi.mock('../pages/CreateAppPage', () => ({
140+
CreateAppPage: () => <div data-testid="create-app-page">Create App Page</div>,
141+
}));
142+
143+
describe('Empty State — No Apps Configured', () => {
144+
beforeEach(() => {
145+
vi.clearAllMocks();
146+
});
147+
148+
const renderApp = (initialRoute = '/apps/_new/') => {
149+
return render(
150+
<MemoryRouter initialEntries={[initialRoute]}>
151+
<Routes>
152+
<Route path="/apps/:appName/*" element={<AppContent />} />
153+
</Routes>
154+
</MemoryRouter>,
155+
);
156+
};
157+
158+
it('shows "Create Your First App" button in empty state', async () => {
159+
renderApp();
160+
await waitFor(() => {
161+
expect(screen.getByTestId('create-first-app-btn')).toBeInTheDocument();
162+
}, { timeout: 10000 });
163+
expect(screen.getByText('No Apps Configured')).toBeInTheDocument();
164+
});
165+
166+
it('shows "System Settings" button in empty state', async () => {
167+
renderApp();
168+
await waitFor(() => {
169+
expect(screen.getByTestId('go-to-settings-btn')).toBeInTheDocument();
170+
}, { timeout: 10000 });
171+
});
172+
173+
it('shows descriptive text about creating apps or visiting settings', async () => {
174+
renderApp();
175+
await waitFor(() => {
176+
expect(screen.getByText(/Create your first app or visit System Settings/i)).toBeInTheDocument();
177+
}, { timeout: 10000 });
178+
});
179+
});
180+
181+
describe('System Routes Within App Context (No Active App)', () => {
182+
beforeEach(() => {
183+
vi.clearAllMocks();
184+
});
185+
186+
const renderApp = (initialRoute: string) => {
187+
return render(
188+
<MemoryRouter initialEntries={[initialRoute]}>
189+
<Routes>
190+
<Route path="/apps/:appName/*" element={<AppContent />} />
191+
</Routes>
192+
</MemoryRouter>,
193+
);
194+
};
195+
196+
it('renders system hub page at /apps/_new/system when no active app', async () => {
197+
renderApp('/apps/_new/system');
198+
await waitFor(() => {
199+
expect(screen.getByTestId('system-hub-page')).toBeInTheDocument();
200+
}, { timeout: 10000 });
201+
});
202+
203+
it('renders create app page at /apps/_new/create-app when no active app', async () => {
204+
renderApp('/apps/_new/create-app');
205+
await waitFor(() => {
206+
expect(screen.getByTestId('create-app-page')).toBeInTheDocument();
207+
}, { timeout: 10000 });
208+
});
209+
});

0 commit comments

Comments
 (0)