Files
TX/artifacts/tx-os/tests/order-incoming-delete.spec.mjs
T
riyadhafraa 7a2ae8434d Update project documentation and code comments to remove platform-specific references
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: fa18e5d4-a810-4bd5-8cde-2a60d64d9e3f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu
Replit-Helium-Checkpoint-Created: true
2026-05-14 06:23:49 +00:00

291 lines
9.0 KiB
JavaScript

// Playwright e2e for the per-card and bulk delete affordances on
// /orders/incoming added in.
//
// Covers:
// - Bulk delete: receiver selects 2 unclaimed orders via per-card
// checkboxes, opens the confirm dialog from the sticky bulk action
// bar, confirms, and the list shrinks by 2 (the rest stay).
// - Per-card delete: receiver claims a third order, then deletes it
// from the "my claimed" section using the per-card trash icon and
// the same confirm dialog. The list shrinks by 1.
//
// Mirrors the existing order-receiver-flow.spec.mjs harness (same
// password hash, same DB cleanup pattern).
//
// Run with:
// DATABASE_URL=... pnpm --filter @workspace/tx-os test:e2e
import { test, expect } from "@playwright/test";
import pg from "pg";
const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
throw new Error(
"DATABASE_URL must be set to run the incoming-orders delete UI test",
);
}
const TEST_PASSWORD = "TestPass123!";
const TEST_PASSWORD_HASH =
"$2b$10$Bs636ukPMyz01nKrsi.5m.JlDXSN22AVCvn8cgPWWDbo5yJRQX2vu";
const pool = new pg.Pool({ connectionString: DATABASE_URL });
const createdUserIds = [];
const createdOrderIds = [];
function uniqueSuffix() {
return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}
async function createUser(prefix, roleName, displayNameEn) {
const username = `${prefix}_${uniqueSuffix()}`;
const { rows } = await pool.query(
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
VALUES ($1, $2, $3, $4, 'en', true) RETURNING id`,
[
username,
`${username}@example.com`,
TEST_PASSWORD_HASH,
displayNameEn ?? username,
],
);
const id = rows[0].id;
createdUserIds.push(id);
await pool.query(
`INSERT INTO user_roles (user_id, role_id)
SELECT $1, id FROM roles WHERE name = $2`,
[id, roleName],
);
return { id, username, displayName: displayNameEn ?? username };
}
async function getAvailableService() {
const { rows } = await pool.query(
`SELECT id, name_en AS "nameEn", name_ar AS "nameAr"
FROM services WHERE is_available = true
ORDER BY id ASC LIMIT 1`,
);
if (rows.length === 0) {
throw new Error("No available service found in DB");
}
return rows[0];
}
async function placeOrderDirect(userId, serviceId, notes) {
const { rows } = await pool.query(
`INSERT INTO service_orders (user_id, service_id, status, notes)
VALUES ($1, $2, 'pending', $3) RETURNING id`,
[userId, serviceId, notes],
);
const id = rows[0].id;
createdOrderIds.push(id);
return id;
}
test.afterAll(async () => {
if (createdOrderIds.length > 0) {
await pool.query(`DELETE FROM service_orders WHERE id = ANY($1::int[])`, [
createdOrderIds,
]);
}
if (createdUserIds.length > 0) {
await pool.query(
`DELETE FROM service_orders
WHERE user_id = ANY($1::int[]) OR assigned_to = ANY($1::int[])`,
[createdUserIds],
);
await pool.query(
`DELETE FROM notifications WHERE user_id = ANY($1::int[])`,
[createdUserIds],
);
await pool.query(
`DELETE FROM user_roles WHERE user_id = ANY($1::int[])`,
[createdUserIds],
);
await pool.query(`DELETE FROM users WHERE id = ANY($1::int[])`, [
createdUserIds,
]);
}
await pool.end();
});
async function setLanguage(page, lang) {
await page.addInitScript((l) => {
try {
window.localStorage.setItem("tx-lang", l);
} catch {
/* ignore */
}
}, lang);
}
async function loginViaUi(page, username, password) {
await page.goto("/login");
await page.locator("#username").fill(username);
await page.locator("#password").fill(password);
await Promise.all([
page.waitForURL((url) => !url.pathname.endsWith("/login"), {
timeout: 15_000,
}),
page.locator('form button[type="submit"]').click(),
]);
}
async function countCards(page) {
return page.locator('[data-testid^="incoming-order-card-"]').count();
}
test.describe("Incoming orders — delete affordances", () => {
test("bulk delete two unclaimed orders shrinks the list by 2", async ({
browser,
}) => {
const requester = await createUser(
"ord_del_req",
"user",
`DelReq ${uniqueSuffix()}`,
);
const receiver = await createUser(
"ord_del_recv",
"order_receiver",
`DelRecv ${uniqueSuffix()}`,
);
const service = await getAvailableService();
// Three pending unclaimed orders so we can assert the post-delete
// count is exactly one.
const o1 = await placeOrderDirect(requester.id, service.id, "delete-me-1");
const o2 = await placeOrderDirect(requester.id, service.id, "delete-me-2");
const o3 = await placeOrderDirect(requester.id, service.id, "keep-me");
const ctx = await browser.newContext();
try {
const page = await ctx.newPage();
await setLanguage(page, "en");
await loginViaUi(page, receiver.username, TEST_PASSWORD);
await page.goto("/orders/incoming");
await expect(
page.locator(`[data-testid="incoming-order-card-${o1}"]`),
).toBeVisible();
await expect(
page.locator(`[data-testid="incoming-order-card-${o2}"]`),
).toBeVisible();
await expect(
page.locator(`[data-testid="incoming-order-card-${o3}"]`),
).toBeVisible();
const initial = await countCards(page);
expect(initial).toBeGreaterThanOrEqual(3);
// Select two via per-card checkboxes (always visible).
await page.locator(`[data-testid="order-select-${o1}"]`).click();
await page.locator(`[data-testid="order-select-${o2}"]`).click();
const bar = page.locator('[data-testid="bulk-action-bar"]');
await expect(bar).toBeVisible();
await expect(bar).toContainText("2 orders selected");
const bulkPromise = page.waitForResponse(
(resp) => {
const path = new URL(resp.url()).pathname;
return (
path === "/api/orders/bulk-delete" &&
resp.request().method() === "POST"
);
},
{ timeout: 15_000 },
);
await page.locator('[data-testid="bulk-delete"]').click();
await page.locator('[data-testid="confirm-delete"]').click();
const bulkResp = await bulkPromise;
expect(bulkResp.status()).toBe(200);
const body = await bulkResp.json();
expect(body.deletedIds.sort()).toEqual([o1, o2].sort());
expect(body.failedIds).toEqual([]);
await expect(
page.locator(`[data-testid="incoming-order-card-${o1}"]`),
).toHaveCount(0);
await expect(
page.locator(`[data-testid="incoming-order-card-${o2}"]`),
).toHaveCount(0);
await expect(
page.locator(`[data-testid="incoming-order-card-${o3}"]`),
).toBeVisible();
const after = await countCards(page);
expect(after).toBe(initial - 2);
} finally {
await ctx.close();
}
});
test("per-card trash deletes a single claimed order", async ({ browser }) => {
const requester = await createUser(
"ord_del_req2",
"user",
`DelReq2 ${uniqueSuffix()}`,
);
const receiver = await createUser(
"ord_del_recv2",
"order_receiver",
`DelRecv2 ${uniqueSuffix()}`,
);
const service = await getAvailableService();
const o1 = await placeOrderDirect(requester.id, service.id, "claim-me");
const ctx = await browser.newContext();
try {
const page = await ctx.newPage();
await setLanguage(page, "en");
await loginViaUi(page, receiver.username, TEST_PASSWORD);
await page.goto("/orders/incoming");
const card = page.locator(
`[data-testid="incoming-order-card-${o1}"]`,
);
await expect(card).toBeVisible();
// Claim the order so it moves to "My active orders".
const claimPromise = page.waitForResponse(
(resp) => {
const path = new URL(resp.url()).pathname;
return (
path === `/api/orders/${o1}/confirm-receipt` &&
resp.request().method() === "PATCH"
);
},
{ timeout: 15_000 },
);
await card.getByRole("button", { name: "Confirm Receipt", exact: true }).click();
const claimResp = await claimPromise;
expect(claimResp.status()).toBe(200);
// Now delete via per-card trash.
const before = await countCards(page);
const deletePromise = page.waitForResponse(
(resp) => {
const path = new URL(resp.url()).pathname;
return (
path === `/api/orders/${o1}` &&
resp.request().method() === "DELETE"
);
},
{ timeout: 15_000 },
);
await page.locator(`[data-testid="order-delete-${o1}"]`).click();
await page.locator('[data-testid="confirm-delete"]').click();
const delResp = await deletePromise;
expect(delResp.status()).toBe(204);
await expect(card).toHaveCount(0);
const after = await countCards(page);
expect(after).toBe(before - 1);
} finally {
await ctx.close();
}
});
});