-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathdatabase-table-editor-portal.spec.ts
More file actions
255 lines (215 loc) · 9.1 KB
/
Copy pathdatabase-table-editor-portal.spec.ts
File metadata and controls
255 lines (215 loc) · 9.1 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
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: string,
) {
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, exact: true });
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 });
}
// ---------------------------------------------------------------------------
// Tests — verify floating editors are not clipped by table overflow
// ---------------------------------------------------------------------------
test.describe("Table editor portals", () => {
test("date picker calendar is fully visible when opened in a table cell", async ({
authenticatedPage: page,
}) => {
await createDatabaseFromSidebar(page);
// Add a row so the grid renders
const addRowBtn = page.getByTestId("db-table-add-row");
await expect(addRowBtn).toBeVisible({ timeout: 10_000 });
await addRowBtn.click();
await expect(page.locator('[role="grid"]')).toBeVisible({
timeout: 10_000,
});
// Add a date column
await addColumnViaTypePicker(page, "Date");
// Click the date cell to open the editor
const dateCells = page.locator('[role="gridcell"]');
// Find the date column cell — it's the last property cell before the
// empty add-column cell. We look for the cell in the data row that
// corresponds to the date column header.
const dateHeader = page.locator('[role="columnheader"]', {
hasText: /date/i,
});
await expect(dateHeader.first()).toBeVisible({ timeout: 5_000 });
// The date cell is in the same column position. Click it to open the editor.
// In the grid layout, cells follow the same column order as headers.
// The date column is the last property column we added.
// Find all gridcells in the data row and click the one for the date column.
const allCells = dateCells.all();
const cells = await allCells;
// The grid has: title cell, then property cells, then empty add-column cell.
// Date is the last property column, so it's the second-to-last cell.
const dateCell = cells[cells.length - 2];
await dateCell.click();
// The date picker calendar should be visible — rendered via portal on document.body
const calendar = page.locator(".grid-cols-7").first();
await expect(calendar).toBeVisible({ timeout: 5_000 });
// Verify the calendar is not clipped: its bounding box should be fully
// within the viewport (not cut off by the table overflow container).
const calendarBox = await calendar.boundingBox();
expect(calendarBox).not.toBeNull();
if (calendarBox) {
const viewport = page.viewportSize();
expect(viewport).not.toBeNull();
if (viewport) {
// Calendar should be fully visible within the viewport
expect(calendarBox.x).toBeGreaterThanOrEqual(0);
expect(calendarBox.y).toBeGreaterThanOrEqual(0);
expect(calendarBox.x + calendarBox.width).toBeLessThanOrEqual(
viewport.width + 1,
);
expect(calendarBox.y + calendarBox.height).toBeLessThanOrEqual(
viewport.height + 1,
);
}
}
// Dismiss with Escape
await page.keyboard.press("Escape");
await expect(calendar).not.toBeVisible({ timeout: 3_000 });
});
test("select dropdown is fully visible when opened in a table cell", async ({
authenticatedPage: page,
}) => {
await createDatabaseFromSidebar(page);
// Add a row
const addRowBtn = page.getByTestId("db-table-add-row");
await expect(addRowBtn).toBeVisible({ timeout: 10_000 });
await addRowBtn.click();
await expect(page.locator('[role="grid"]')).toBeVisible({
timeout: 10_000,
});
// Add a select column
await addColumnViaTypePicker(page, "Select");
// Click the select cell to open the editor
const selectHeader = page.locator('[role="columnheader"]', {
hasText: /select/i,
});
await expect(selectHeader.first()).toBeVisible({ timeout: 5_000 });
const dataCells = page.locator('[role="gridcell"]');
const cells = await dataCells.all();
const selectCell = cells[cells.length - 2];
await selectCell.click();
// The select dropdown should be visible — rendered via portal
const dropdown = page.locator(
".rounded-sm.border.border-border.bg-background",
);
await expect(dropdown).toBeVisible({ timeout: 5_000 });
// Verify the dropdown is not clipped
const dropdownBox = await dropdown.boundingBox();
expect(dropdownBox).not.toBeNull();
if (dropdownBox) {
const viewport = page.viewportSize();
expect(viewport).not.toBeNull();
if (viewport) {
expect(dropdownBox.x).toBeGreaterThanOrEqual(0);
expect(dropdownBox.y).toBeGreaterThanOrEqual(0);
expect(dropdownBox.x + dropdownBox.width).toBeLessThanOrEqual(
viewport.width + 1,
);
expect(dropdownBox.y + dropdownBox.height).toBeLessThanOrEqual(
viewport.height + 1,
);
}
}
// Dismiss with Escape
await page.keyboard.press("Escape");
await expect(dropdown).not.toBeVisible({ timeout: 3_000 });
});
test("adding a status column succeeds and renders status badges (#662)", async ({
authenticatedPage: page,
}) => {
await createDatabaseFromSidebar(page);
// Add a row
const addRowBtn = page.getByTestId("db-table-add-row");
await expect(addRowBtn).toBeVisible({ timeout: 10_000 });
await addRowBtn.click();
await expect(page.locator('[role="grid"]')).toBeVisible({
timeout: 10_000,
});
// Add a status column — this was failing before the fix (#662)
await addColumnViaTypePicker(page, "Status");
// Verify the status column header appears
const statusHeader = page.locator('[role="columnheader"]', {
hasText: /status/i,
});
await expect(statusHeader.first()).toBeVisible({ timeout: 5_000 });
// Click the status cell to open the editor
const dataCells = page.locator('[role="gridcell"]');
const cells = await dataCells.all();
// Status is the last property column — second-to-last cell
const statusCell = cells[cells.length - 2];
await statusCell.click();
// The status dropdown should be visible with default options
const dropdown = page.locator(
".rounded-sm.border.border-border.bg-background",
);
await expect(dropdown).toBeVisible({ timeout: 5_000 });
// Verify default status options are present
await expect(dropdown.getByText("Not Started")).toBeVisible();
await expect(dropdown.getByText("In Progress")).toBeVisible();
await expect(dropdown.getByText("Done")).toBeVisible();
// Select "In Progress" and verify the badge renders
await dropdown.getByText("In Progress").click();
// The status badge should now be visible in the cell
const badge = page.locator('[role="grid"]').getByText("In Progress");
await expect(badge).toBeVisible({ timeout: 5_000 });
});
});