Fix delete-row handler: nested slug extraction in OCaml dispatch
The slug extractor after (api. scanned for the first ) but for nested URLs like (api.(delete.1)) it got "(delete.1" instead of "delete". Now handles nested parens: extracts handler name and injects path params into query string. Also strengthened the Playwright test to accept confirm dialogs and assert strict row count decrease. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2658,8 +2658,29 @@ let http_mode port =
|
||||
if i + 5 > String.length s then ""
|
||||
else if String.sub s i 5 = "(api." then
|
||||
let start = i + 5 in
|
||||
let end_ = try String.index_from s start ')' with Not_found -> String.length s in
|
||||
String.sub s start (end_ - start)
|
||||
if start < String.length s && s.[start] = '(' then
|
||||
(* Nested: (api.(delete.1)) — extract "delete" as slug,
|
||||
inject path param into query string *)
|
||||
let inner = start + 1 in
|
||||
let end_ =
|
||||
let rec scan j =
|
||||
if j >= String.length s then j
|
||||
else match s.[j] with '.' | ')' -> j | _ -> scan (j + 1)
|
||||
in scan inner in
|
||||
let handler_name = String.sub s inner (end_ - inner) in
|
||||
(* Extract path param value if present (after the dot) *)
|
||||
if end_ < String.length s && s.[end_] = '.' then begin
|
||||
let val_start = end_ + 1 in
|
||||
let val_end = try String.index_from s val_start ')' with Not_found -> String.length s in
|
||||
let param_val = String.sub s val_start (val_end - val_start) in
|
||||
(* Append to query string so request-arg can find it *)
|
||||
let sep = if !_req_query = "" then "" else "&" in
|
||||
_req_query := !_req_query ^ sep ^ "item-id=" ^ param_val
|
||||
end;
|
||||
handler_name
|
||||
else
|
||||
let end_ = try String.index_from s start ')' with Not_found -> String.length s in
|
||||
String.sub s start (end_ - start)
|
||||
else find_api s (i + 1) in
|
||||
find_api path 0 in
|
||||
let handler_key = "handler:ex-" ^ slug in
|
||||
|
||||
@@ -191,14 +191,20 @@ test.describe('Mutation handlers', () => {
|
||||
|
||||
test('delete-row: clicking delete removes row', async ({ page }) => {
|
||||
await loadPage(page, '(geography.(hypermedia.(example.delete-row)))');
|
||||
const rowsBefore = await page.locator('table tbody tr').count();
|
||||
const deleteBtn = page.locator('button:has-text("Delete")').first();
|
||||
if (await deleteBtn.count() > 0 && rowsBefore > 0) {
|
||||
await deleteBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
const rowsAfter = await page.locator('table tbody tr').count();
|
||||
// Row count should decrease or stay same (if swap replaces)
|
||||
expect(rowsAfter).toBeLessThanOrEqual(rowsBefore);
|
||||
// Auto-accept confirm dialogs (sx-confirm triggers window.confirm)
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
const rowsBefore = await page.locator('tbody#delete-rows tr').count();
|
||||
expect(rowsBefore).toBeGreaterThan(0);
|
||||
const firstRowId = await page.locator('tbody#delete-rows tr').first().getAttribute('id');
|
||||
const deleteBtn = page.locator('tbody#delete-rows tr:first-child button').first();
|
||||
await deleteBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
const rowsAfter = await page.locator('tbody#delete-rows tr').count();
|
||||
// Row count must strictly decrease
|
||||
expect(rowsAfter).toBe(rowsBefore - 1);
|
||||
// The specific row must be gone
|
||||
if (firstRowId) {
|
||||
await expect(page.locator(`#${firstRowId}`)).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user