-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathdatabase-table-keyboard.spec.ts
More file actions
276 lines (221 loc) · 9.4 KB
/
Copy pathdatabase-table-keyboard.spec.ts
File metadata and controls
276 lines (221 loc) · 9.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
import { test, expect } from "./fixtures/auth";
import { createClient } from "@supabase/supabase-js";
// ---------------------------------------------------------------------------
// Admin client for cleanup
// ---------------------------------------------------------------------------
function getAdminClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!,
{ auth: { autoRefreshToken: false, persistSession: false } },
);
}
const createdDatabaseIds: string[] = [];
test.afterAll(async () => {
if (createdDatabaseIds.length === 0) return;
const admin = getAdminClient();
for (const id of createdDatabaseIds) {
await admin.from("pages").delete().eq("parent_id", id);
await admin.from("pages").delete().eq("id", id);
}
});
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function waitForSidebarTree(page: import("@playwright/test").Page) {
const sidebar = page.getByRole("complementary");
const treeLoaded = sidebar
.locator('[role="treeitem"], :text("No pages yet")')
.first();
await expect(treeLoaded).toBeVisible({ timeout: 15_000 });
return sidebar;
}
async function createDatabaseFromSidebar(
page: import("@playwright/test").Page,
): Promise<string> {
const sidebar = await waitForSidebarTree(page);
const newDbBtn = sidebar.getByTestId("sb-new-database-btn");
await expect(newDbBtn).toBeVisible({ timeout: 5_000 });
await newDbBtn.click();
await page.waitForURL(
(url) => url.pathname.split("/").filter(Boolean).length >= 2,
{ timeout: 15_000 },
);
const dbLoaded = page
.locator('[role="grid"], :text("No rows yet")')
.first();
await expect(dbLoaded).toBeVisible({ timeout: 15_000 });
const pathParts = new URL(page.url()).pathname.split("/").filter(Boolean);
const pageId = pathParts[pathParts.length - 1];
createdDatabaseIds.push(pageId);
return pageId;
}
async function addColumnViaTypePicker(
page: import("@playwright/test").Page,
typeName = "Text",
) {
const addColumnBtn = page.locator('button[aria-label="Add column"]');
await expect(addColumnBtn).toBeVisible({ timeout: 5_000 });
await addColumnBtn.click();
const menuItem = page.getByRole("menuitem", { name: typeName });
await expect(menuItem).toBeVisible({ timeout: 5_000 });
await menuItem.click();
// Wait for the menu to close (column added)
await expect(menuItem).not.toBeVisible({ timeout: 5_000 });
}
/**
* Set up a database with 2 rows and 2 property columns (Text + Number).
*/
async function setupGridWith2x2(page: import("@playwright/test").Page) {
await createDatabaseFromSidebar(page);
// Add first row
const addRowBtn = page.getByTestId("db-table-add-row");
await expect(addRowBtn).toBeVisible({ timeout: 10_000 });
await addRowBtn.click();
// Wait for grid
await expect(page.locator('[role="grid"]')).toBeVisible({ timeout: 10_000 });
// Add second row
await addRowBtn.click();
// Wait for the second row to appear
await expect(page.locator('[role="row"]')).toHaveCount(3, { timeout: 10_000 });
// Add Text column
await addColumnViaTypePicker(page, "Text");
// Add Number column
await addColumnViaTypePicker(page, "Number");
}
function getCell(page: import("@playwright/test").Page, row: number, col: number) {
return page.locator(`[data-row="${row}"][data-col="${col}"]`);
}
/**
* Enter focused (non-editing) mode on a cell by clicking it (starts editing)
* then pressing Escape (exits editing, enters focused navigation mode).
*/
async function focusCell(page: import("@playwright/test").Page, row: number, col: number) {
const cell = getCell(page, row, col);
await cell.click();
// Click starts editing — press Escape to exit editing and enter focused mode
await page.keyboard.press("Escape");
await expect(cell).toBeFocused({ timeout: 3_000 });
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
test.describe("Database table keyboard navigation", () => {
test("arrow keys navigate between cells in focused mode", async ({
authenticatedPage: page,
}) => {
await setupGridWith2x2(page);
// Enter focused mode on first property cell (row 0, col 0)
await focusCell(page, 0, 0);
// Press ArrowDown — should move to row 1, col 0
await page.keyboard.press("ArrowDown");
const cellBelow = getCell(page, 1, 0);
await expect(cellBelow).toBeFocused({ timeout: 3_000 });
// Press ArrowRight — should move to row 1, col 1
await page.keyboard.press("ArrowRight");
const cellRight = getCell(page, 1, 1);
await expect(cellRight).toBeFocused({ timeout: 3_000 });
// Press ArrowUp — should move to row 0, col 1
await page.keyboard.press("ArrowUp");
const cellAbove = getCell(page, 0, 1);
await expect(cellAbove).toBeFocused({ timeout: 3_000 });
// Press ArrowLeft — should move to row 0, col 0
await page.keyboard.press("ArrowLeft");
const firstCell = getCell(page, 0, 0);
await expect(firstCell).toBeFocused({ timeout: 3_000 });
});
test("Enter starts editing from focused mode, Escape returns to focused mode", async ({
authenticatedPage: page,
}) => {
await setupGridWith2x2(page);
// Enter focused mode
await focusCell(page, 0, 0);
// Press Enter to start editing
await page.keyboard.press("Enter");
// An input should appear
const cellInput = page.locator(
'[role="gridcell"] input[type="text"], [role="gridcell"] input[type="number"]',
);
await expect(cellInput.first()).toBeVisible({ timeout: 5_000 });
// Press Escape to exit editing — should return to focused mode on same cell
await page.keyboard.press("Escape");
const firstCell = getCell(page, 0, 0);
await expect(firstCell).toBeFocused({ timeout: 3_000 });
// The input should no longer be visible
await expect(cellInput).not.toBeVisible({ timeout: 3_000 });
});
test("Enter while editing commits and moves focus down", async ({
authenticatedPage: page,
}) => {
await setupGridWith2x2(page);
// Enter focused mode then start editing
await focusCell(page, 0, 0);
await page.keyboard.press("Enter");
const cellInput = page.locator(
'[role="gridcell"] input[type="text"], [role="gridcell"] input[type="number"]',
);
await expect(cellInput.first()).toBeVisible({ timeout: 5_000 });
// Type a value and press Enter to commit
await cellInput.first().fill("Hello");
await page.keyboard.press("Enter");
// Focus should move to the cell below (row 1, col 0)
const cellBelow = getCell(page, 1, 0);
await expect(cellBelow).toBeFocused({ timeout: 3_000 });
});
test("ArrowRight wraps to next row, ArrowLeft wraps to previous row", async ({
authenticatedPage: page,
}) => {
await setupGridWith2x2(page);
// Grid has 3 property columns: Title (0), Text (1), Number (2)
// Focus (0, 0) then navigate right to reach the last column (0, 2)
await focusCell(page, 0, 0);
await page.keyboard.press("ArrowRight"); // → (0, 1)
await page.keyboard.press("ArrowRight"); // → (0, 2)
const lastColFirstRow = getCell(page, 0, 2);
await expect(lastColFirstRow).toBeFocused({ timeout: 3_000 });
// Press ArrowRight — should wrap to first column of next row (row 1, col 0)
await page.keyboard.press("ArrowRight");
const firstColSecondRow = getCell(page, 1, 0);
await expect(firstColSecondRow).toBeFocused({ timeout: 3_000 });
// Press ArrowLeft — should wrap back to last column of previous row (row 0, col 2)
await page.keyboard.press("ArrowLeft");
await expect(lastColFirstRow).toBeFocused({ timeout: 3_000 });
});
test("navigation stops at grid boundaries", async ({
authenticatedPage: page,
}) => {
await setupGridWith2x2(page);
// Grid has 3 property columns: Title (0), Text (1), Number (2)
// Focus the first cell (row 0, col 0)
await focusCell(page, 0, 0);
// Press ArrowUp at top row — should stay on same cell
await page.keyboard.press("ArrowUp");
const firstCell = getCell(page, 0, 0);
await expect(firstCell).toBeFocused({ timeout: 3_000 });
// Navigate to last cell (row 1, col 2) using arrow keys
await page.keyboard.press("ArrowDown"); // → (1, 0)
await page.keyboard.press("ArrowRight"); // → (1, 1)
await page.keyboard.press("ArrowRight"); // → (1, 2)
const lastCell = getCell(page, 1, 2);
await expect(lastCell).toBeFocused({ timeout: 3_000 });
// Press ArrowDown at bottom row — should stay on same cell
await page.keyboard.press("ArrowDown");
await expect(lastCell).toBeFocused({ timeout: 3_000 });
// Press ArrowRight at last column of last row — should stay (boundary)
await page.keyboard.press("ArrowRight");
await expect(lastCell).toBeFocused({ timeout: 3_000 });
});
test("Escape from focused mode clears focus", async ({
authenticatedPage: page,
}) => {
await setupGridWith2x2(page);
// Enter focused mode
await focusCell(page, 0, 0);
const firstCell = getCell(page, 0, 0);
await expect(firstCell).toBeFocused({ timeout: 3_000 });
// Press Escape — should clear focus
await page.keyboard.press("Escape");
// The cell should no longer be focused
await expect(firstCell).not.toBeFocused({ timeout: 3_000 });
});
});