Skip to content

Commit 71d4da2

Browse files
authored
Achieve 98.52% test coverage for dashboard with comprehensive unit and e2e tests (#36)
1 parent fb0fdd5 commit 71d4da2

File tree

12 files changed

+2253
-1
lines changed

12 files changed

+2253
-1
lines changed
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Dashboard - Resources View', () => {
4+
test.beforeEach(async ({ page }) => {
5+
// Mock the API responses
6+
await page.route('/api/project', async route => {
7+
await route.fulfill({
8+
status: 200,
9+
contentType: 'application/json',
10+
body: JSON.stringify({ name: 'test-project' }),
11+
});
12+
});
13+
14+
await page.route('/api/services', async route => {
15+
await route.fulfill({
16+
status: 200,
17+
contentType: 'application/json',
18+
body: JSON.stringify([
19+
{
20+
name: 'api',
21+
language: 'python',
22+
framework: 'flask',
23+
local: {
24+
status: 'ready',
25+
health: 'healthy',
26+
url: 'http://localhost:5000',
27+
port: 5000,
28+
pid: 12345,
29+
startTime: new Date(Date.now() - 60000).toISOString(),
30+
lastChecked: new Date().toISOString(),
31+
},
32+
},
33+
{
34+
name: 'web',
35+
language: 'node',
36+
framework: 'express',
37+
local: {
38+
status: 'ready',
39+
health: 'healthy',
40+
url: 'http://localhost:5001',
41+
port: 5001,
42+
pid: 12346,
43+
startTime: new Date(Date.now() - 120000).toISOString(),
44+
lastChecked: new Date().toISOString(),
45+
},
46+
},
47+
]),
48+
});
49+
});
50+
51+
await page.route('/api/logs*', async route => {
52+
await route.fulfill({
53+
status: 200,
54+
contentType: 'application/json',
55+
body: JSON.stringify([]),
56+
});
57+
});
58+
59+
await page.goto('/');
60+
});
61+
62+
test('should display project name in header', async ({ page }) => {
63+
await expect(page.getByText('test-project')).toBeVisible();
64+
});
65+
66+
test('should display services in table view by default', async ({ page }) => {
67+
await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible();
68+
await expect(page.getByText('api')).toBeVisible();
69+
await expect(page.getByText('web')).toBeVisible();
70+
});
71+
72+
test('should switch between table and grid view', async ({ page }) => {
73+
// Default is table view
74+
await expect(page.getByRole('button', { name: /table/i })).toBeVisible();
75+
76+
// Switch to grid view
77+
await page.getByRole('button', { name: /grid/i }).click();
78+
79+
// Grid view should be active (check for grid container)
80+
const gridContainer = page.locator('.grid.grid-cols-1');
81+
await expect(gridContainer).toBeVisible();
82+
83+
// Switch back to table view
84+
await page.getByRole('button', { name: /table/i }).click();
85+
});
86+
87+
test('should display service details in cards', async ({ page }) => {
88+
// Switch to grid view
89+
await page.getByRole('button', { name: /grid/i }).click();
90+
91+
// Check that service cards are visible
92+
await expect(page.getByText('api')).toBeVisible();
93+
await expect(page.getByText('flask')).toBeVisible();
94+
await expect(page.getByText('python')).toBeVisible();
95+
});
96+
97+
test('should display service status', async ({ page }) => {
98+
await expect(page.getByText('Running')).toBeVisible();
99+
});
100+
101+
test('should show search filter input', async ({ page }) => {
102+
const searchInput = page.getByPlaceholder('Filter...');
103+
await expect(searchInput).toBeVisible();
104+
105+
// Type in search
106+
await searchInput.fill('api');
107+
});
108+
109+
test('should navigate between views', async ({ page }) => {
110+
// Click console view
111+
await page.getByRole('button', { name: /console/i }).click();
112+
await expect(page.getByRole('heading', { name: 'Console' })).toBeVisible();
113+
114+
// Click back to resources
115+
await page.getByRole('button', { name: /resources/i }).click();
116+
await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible();
117+
});
118+
119+
test('should show coming soon for unimplemented views', async ({ page }) => {
120+
await page.getByRole('button', { name: /traces/i }).click();
121+
await expect(page.getByText('Coming Soon')).toBeVisible();
122+
await expect(page.getByText('This view is not yet implemented')).toBeVisible();
123+
});
124+
125+
test('should display header buttons', async ({ page }) => {
126+
// Check that header action buttons are present
127+
const header = page.locator('header');
128+
await expect(header).toBeVisible();
129+
});
130+
131+
test('should preserve view preference in localStorage', async ({ page }) => {
132+
// Switch to grid view
133+
await page.getByRole('button', { name: /grid/i }).click();
134+
135+
// Reload page
136+
await page.reload();
137+
138+
// Grid view should still be active
139+
const gridContainer = page.locator('.grid.grid-cols-1');
140+
await expect(gridContainer).toBeVisible();
141+
});
142+
});
143+
144+
test.describe('Dashboard - Console View', () => {
145+
test.beforeEach(async ({ page }) => {
146+
await page.route('/api/project', async route => {
147+
await route.fulfill({
148+
status: 200,
149+
contentType: 'application/json',
150+
body: JSON.stringify({ name: 'test-project' }),
151+
});
152+
});
153+
154+
await page.route('/api/services', async route => {
155+
await route.fulfill({
156+
status: 200,
157+
contentType: 'application/json',
158+
body: JSON.stringify([]),
159+
});
160+
});
161+
162+
await page.route('/api/logs*', async route => {
163+
await route.fulfill({
164+
status: 200,
165+
contentType: 'application/json',
166+
body: JSON.stringify([
167+
{
168+
service: 'api',
169+
message: 'Application started',
170+
level: 0,
171+
timestamp: new Date().toISOString(),
172+
isStderr: false,
173+
},
174+
{
175+
service: 'web',
176+
message: 'Server listening on port 5001',
177+
level: 0,
178+
timestamp: new Date().toISOString(),
179+
isStderr: false,
180+
},
181+
]),
182+
});
183+
});
184+
185+
await page.goto('/');
186+
});
187+
188+
test('should navigate to console view', async ({ page }) => {
189+
await page.getByRole('button', { name: /console/i }).click();
190+
await expect(page.getByRole('heading', { name: 'Console' })).toBeVisible();
191+
});
192+
193+
test('should display log controls', async ({ page }) => {
194+
await page.getByRole('button', { name: /console/i }).click();
195+
196+
// Check for service filter
197+
await expect(page.getByText('Service')).toBeVisible();
198+
});
199+
});
200+
201+
test.describe('Dashboard - Error States', () => {
202+
test('should display loading state', async ({ page }) => {
203+
// Delay the API response
204+
await page.route('/api/services', async route => {
205+
await new Promise(resolve => setTimeout(resolve, 500));
206+
await route.fulfill({
207+
status: 200,
208+
contentType: 'application/json',
209+
body: JSON.stringify([]),
210+
});
211+
});
212+
213+
await page.route('/api/project', async route => {
214+
await route.fulfill({
215+
status: 200,
216+
contentType: 'application/json',
217+
body: JSON.stringify({ name: 'test-project' }),
218+
});
219+
});
220+
221+
await page.goto('/');
222+
223+
// Should show loading spinner
224+
const spinner = page.locator('.animate-spin');
225+
await expect(spinner).toBeVisible();
226+
});
227+
228+
test('should display empty state when no services', async ({ page }) => {
229+
await page.route('/api/project', async route => {
230+
await route.fulfill({
231+
status: 200,
232+
contentType: 'application/json',
233+
body: JSON.stringify({ name: 'test-project' }),
234+
});
235+
});
236+
237+
await page.route('/api/services', async route => {
238+
await route.fulfill({
239+
status: 200,
240+
contentType: 'application/json',
241+
body: JSON.stringify([]),
242+
});
243+
});
244+
245+
await page.goto('/');
246+
247+
await expect(page.getByText('No Services Running')).toBeVisible();
248+
await expect(page.getByText('azd app run')).toBeVisible();
249+
});
250+
});
251+
252+
test.describe('Dashboard - Accessibility', () => {
253+
test.beforeEach(async ({ page }) => {
254+
await page.route('/api/project', async route => {
255+
await route.fulfill({
256+
status: 200,
257+
contentType: 'application/json',
258+
body: JSON.stringify({ name: 'test-project' }),
259+
});
260+
});
261+
262+
await page.route('/api/services', async route => {
263+
await route.fulfill({
264+
status: 200,
265+
contentType: 'application/json',
266+
body: JSON.stringify([
267+
{
268+
name: 'api',
269+
language: 'python',
270+
framework: 'flask',
271+
local: {
272+
status: 'ready',
273+
health: 'healthy',
274+
url: 'http://localhost:5000',
275+
port: 5000,
276+
},
277+
},
278+
]),
279+
});
280+
});
281+
282+
await page.route('/api/logs*', async route => {
283+
await route.fulfill({
284+
status: 200,
285+
contentType: 'application/json',
286+
body: JSON.stringify([]),
287+
});
288+
});
289+
290+
await page.goto('/');
291+
});
292+
293+
test('should have proper heading structure', async ({ page }) => {
294+
await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible();
295+
});
296+
297+
test('should have accessible buttons', async ({ page }) => {
298+
const tableButton = page.getByRole('button', { name: /table/i });
299+
await expect(tableButton).toBeVisible();
300+
await expect(tableButton).toBeEnabled();
301+
});
302+
303+
test('should have keyboard navigation', async ({ page }) => {
304+
// Test that buttons can be focused and activated with keyboard
305+
await page.getByRole('button', { name: /grid/i }).focus();
306+
await page.keyboard.press('Enter');
307+
308+
// Grid view should be active
309+
const gridContainer = page.locator('.grid.grid-cols-1');
310+
await expect(gridContainer).toBeVisible();
311+
});
312+
});

cli/dashboard/package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)