fix(m4): plans page - correct API fields (speedDownMbps/speedUpMbps/monthlyPrice)
This commit is contained in:
@@ -19,8 +19,8 @@ interface Plan {
|
|||||||
type: "PREPAID" | "POSTPAID";
|
type: "PREPAID" | "POSTPAID";
|
||||||
speedDownMbps: number;
|
speedDownMbps: number;
|
||||||
speedUpMbps: number;
|
speedUpMbps: number;
|
||||||
monthlyPrice: number;
|
monthlyPrice: string | number;
|
||||||
description?: string;
|
description?: string | null;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ const typeVariant: Record<string, "success" | "muted"> = {
|
|||||||
|
|
||||||
const emptyForm = {
|
const emptyForm = {
|
||||||
name: "",
|
name: "",
|
||||||
type: "PREPAID" as "PREPAID" | "POSTPAID",
|
type: "POSTPAID" as "PREPAID" | "POSTPAID",
|
||||||
speedDown: "",
|
speedDownMbps: "",
|
||||||
speedUp: "",
|
speedUpMbps: "",
|
||||||
monthlyPrice: "",
|
monthlyPrice: "",
|
||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
@@ -51,24 +51,30 @@ export default function PlansPage() {
|
|||||||
const [createForm, setCreateForm] = useState({ ...emptyForm });
|
const [createForm, setCreateForm] = useState({ ...emptyForm });
|
||||||
const [editForm, setEditForm] = useState({ ...emptyForm });
|
const [editForm, setEditForm] = useState({ ...emptyForm });
|
||||||
|
|
||||||
// GET /api/v1/plans returns a plain array
|
// GET /plans returns a plain array (not paginated)
|
||||||
const { data: plans = [], isLoading, isError, refetch } = useQuery<Plan[]>({
|
const { data: allPlans = [], isLoading, isError, refetch } = useQuery<Plan[]>({
|
||||||
queryKey: ["plans", search],
|
queryKey: ["plans"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const params = new URLSearchParams({ limit: "100" });
|
const res = await api.get<Plan[] | { data: Plan[] }>("/api/v1/plans");
|
||||||
if (search) params.set("search", search);
|
return Array.isArray(res.data) ? res.data : (res.data as any).data ?? [];
|
||||||
const res = await api.get<Plan[]>(`/api/v1/plans?${params}`);
|
|
||||||
return res.data;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Client-side search filter
|
||||||
|
const plans = search
|
||||||
|
? allPlans.filter(p =>
|
||||||
|
p.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
p.type.toLowerCase().includes(search.toLowerCase())
|
||||||
|
)
|
||||||
|
: allPlans;
|
||||||
|
|
||||||
const createMutation = useMutation({
|
const createMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await api.post("/api/v1/plans", {
|
await api.post("/api/v1/plans", {
|
||||||
name: createForm.name,
|
name: createForm.name,
|
||||||
type: createForm.type,
|
type: createForm.type,
|
||||||
speedDownMbps: Number(createForm.speedDown),
|
speedDownMbps: Number(createForm.speedDownMbps),
|
||||||
speedUpMbps: Number(createForm.speedUp),
|
speedUpMbps: Number(createForm.speedUpMbps),
|
||||||
monthlyPrice: Number(createForm.monthlyPrice),
|
monthlyPrice: Number(createForm.monthlyPrice),
|
||||||
description: createForm.description || undefined,
|
description: createForm.description || undefined,
|
||||||
});
|
});
|
||||||
@@ -79,11 +85,7 @@ export default function PlansPage() {
|
|||||||
setCreateForm({ ...emptyForm });
|
setCreateForm({ ...emptyForm });
|
||||||
qc.invalidateQueries({ queryKey: ["plans"] });
|
qc.invalidateQueries({ queryKey: ["plans"] });
|
||||||
},
|
},
|
||||||
onError: (e: any) => toast.error(
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create plan"),
|
||||||
Array.isArray(e.response?.data?.message)
|
|
||||||
? e.response.data.message.join(", ")
|
|
||||||
: (e.response?.data?.message ?? "Failed to create plan")
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
@@ -91,8 +93,8 @@ export default function PlansPage() {
|
|||||||
await api.patch(`/api/v1/plans/${editPlan!.id}`, {
|
await api.patch(`/api/v1/plans/${editPlan!.id}`, {
|
||||||
name: editForm.name,
|
name: editForm.name,
|
||||||
type: editForm.type,
|
type: editForm.type,
|
||||||
speedDownMbps: Number(editForm.speedDown),
|
speedDownMbps: Number(editForm.speedDownMbps),
|
||||||
speedUpMbps: Number(editForm.speedUp),
|
speedUpMbps: Number(editForm.speedUpMbps),
|
||||||
monthlyPrice: Number(editForm.monthlyPrice),
|
monthlyPrice: Number(editForm.monthlyPrice),
|
||||||
description: editForm.description || undefined,
|
description: editForm.description || undefined,
|
||||||
});
|
});
|
||||||
@@ -117,16 +119,12 @@ export default function PlansPage() {
|
|||||||
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to delete plan"),
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to delete plan"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const filtered = search
|
|
||||||
? plans.filter(p => p.name.toLowerCase().includes(search.toLowerCase()))
|
|
||||||
: plans;
|
|
||||||
|
|
||||||
const openEdit = (plan: Plan) => {
|
const openEdit = (plan: Plan) => {
|
||||||
setEditForm({
|
setEditForm({
|
||||||
name: plan.name,
|
name: plan.name,
|
||||||
type: plan.type,
|
type: plan.type,
|
||||||
speedDown: String(plan.speedDownMbps),
|
speedDownMbps: String(plan.speedDownMbps),
|
||||||
speedUp: String(plan.speedUpMbps),
|
speedUpMbps: String(plan.speedUpMbps),
|
||||||
monthlyPrice: String(plan.monthlyPrice),
|
monthlyPrice: String(plan.monthlyPrice),
|
||||||
description: plan.description ?? "",
|
description: plan.description ?? "",
|
||||||
});
|
});
|
||||||
@@ -139,7 +137,7 @@ export default function PlansPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Plans</h1>
|
<h1 className="text-2xl font-bold text-gray-900">Plans</h1>
|
||||||
<p className="text-sm text-gray-500 mt-1">{filtered.length} total plans</p>
|
<p className="text-sm text-gray-500 mt-1">{allPlans.length} total plans</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button onClick={() => refetch()} variant="outline" size="sm" data-testid="btn-refresh">
|
<Button onClick={() => refetch()} variant="outline" size="sm" data-testid="btn-refresh">
|
||||||
@@ -169,6 +167,7 @@ export default function PlansPage() {
|
|||||||
<Th>Type</Th>
|
<Th>Type</Th>
|
||||||
<Th>Speed (Down/Up)</Th>
|
<Th>Speed (Down/Up)</Th>
|
||||||
<Th>Monthly Price</Th>
|
<Th>Monthly Price</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
<Th>Description</Th>
|
<Th>Description</Th>
|
||||||
<Th></Th>
|
<Th></Th>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -177,29 +176,34 @@ export default function PlansPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
Array.from({ length: 5 }).map((_, i) => (
|
Array.from({ length: 5 }).map((_, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
<Td colSpan={6}><div className="h-4 bg-gray-100 rounded animate-pulse" /></Td>
|
<Td colSpan={7}><div className="h-4 bg-gray-100 rounded animate-pulse" /></Td>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<Td colSpan={6}>
|
<Td colSpan={7}>
|
||||||
<p className="text-center py-6 text-red-400 text-sm">
|
<p className="text-center py-6 text-red-400 text-sm">
|
||||||
Failed to load plans.{" "}
|
Failed to load plans.{" "}
|
||||||
<button onClick={() => refetch()} className="underline">Retry</button>
|
<button onClick={() => refetch()} className="underline">Retry</button>
|
||||||
</p>
|
</p>
|
||||||
</Td>
|
</Td>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : filtered.length === 0 ? (
|
) : plans.length === 0 ? (
|
||||||
<EmptyState colSpan={6} message="No plans found" icon={<Package size={24} />} />
|
<EmptyState colSpan={7} message="No plans found" icon={<Package size={24} />} />
|
||||||
) : (
|
) : (
|
||||||
filtered.map(plan => (
|
plans.map(plan => (
|
||||||
<TableRow key={plan.id} data-testid="plan-row">
|
<TableRow key={plan.id} data-testid="plan-row">
|
||||||
<Td className="font-medium">{plan.name}</Td>
|
<Td className="font-medium">{plan.name}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Badge variant={typeVariant[plan.type] ?? "muted"}>{plan.type}</Badge>
|
<Badge variant={typeVariant[plan.type] ?? "muted"}>{plan.type}</Badge>
|
||||||
</Td>
|
</Td>
|
||||||
<Td className="font-mono text-sm">{plan.speedDownMbps}/{plan.speedUpMbps} Mbps</Td>
|
<Td className="font-mono text-sm">{plan.speedDownMbps}/{plan.speedUpMbps} Mbps</Td>
|
||||||
<Td className="font-semibold">{formatCurrency(plan.monthlyPrice)}</Td>
|
<Td className="font-semibold">{formatCurrency(Number(plan.monthlyPrice))}</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant={plan.isActive ? "success" : "muted"}>
|
||||||
|
{plan.isActive ? "Active" : "Inactive"}
|
||||||
|
</Badge>
|
||||||
|
</Td>
|
||||||
<Td className="text-gray-500 text-sm max-w-xs truncate">{plan.description ?? "—"}</Td>
|
<Td className="text-gray-500 text-sm max-w-xs truncate">{plan.description ?? "—"}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
@@ -231,7 +235,7 @@ export default function PlansPage() {
|
|||||||
|
|
||||||
{/* Create Plan Modal */}
|
{/* Create Plan Modal */}
|
||||||
<Modal isOpen={showCreate} onClose={() => { setShowCreate(false); setCreateForm({ ...emptyForm }); }} title="Add Plan" className="max-w-md">
|
<Modal isOpen={showCreate} onClose={() => { setShowCreate(false); setCreateForm({ ...emptyForm }); }} title="Add Plan" className="max-w-md">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4" data-testid="modal-create-plan">
|
||||||
<Input
|
<Input
|
||||||
label="Plan Name"
|
label="Plan Name"
|
||||||
value={createForm.name}
|
value={createForm.name}
|
||||||
@@ -247,24 +251,24 @@ export default function PlansPage() {
|
|||||||
onChange={e => setCreateForm(f => ({ ...f, type: e.target.value as "PREPAID" | "POSTPAID" }))}
|
onChange={e => setCreateForm(f => ({ ...f, type: e.target.value as "PREPAID" | "POSTPAID" }))}
|
||||||
data-testid="select-plan-type"
|
data-testid="select-plan-type"
|
||||||
>
|
>
|
||||||
<option value="PREPAID">PREPAID</option>
|
|
||||||
<option value="POSTPAID">POSTPAID</option>
|
<option value="POSTPAID">POSTPAID</option>
|
||||||
|
<option value="PREPAID">PREPAID</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Input
|
<Input
|
||||||
label="Download (Mbps)"
|
label="Download (Mbps)"
|
||||||
type="number"
|
type="number"
|
||||||
value={createForm.speedDown}
|
value={createForm.speedDownMbps}
|
||||||
onChange={e => setCreateForm(f => ({ ...f, speedDown: e.target.value }))}
|
onChange={e => setCreateForm(f => ({ ...f, speedDownMbps: e.target.value }))}
|
||||||
placeholder="e.g. 25"
|
placeholder="e.g. 25"
|
||||||
data-testid="input-plan-speed-down"
|
data-testid="input-plan-speed-down"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Upload (Mbps)"
|
label="Upload (Mbps)"
|
||||||
type="number"
|
type="number"
|
||||||
value={createForm.speedUp}
|
value={createForm.speedUpMbps}
|
||||||
onChange={e => setCreateForm(f => ({ ...f, speedUp: e.target.value }))}
|
onChange={e => setCreateForm(f => ({ ...f, speedUpMbps: e.target.value }))}
|
||||||
placeholder="e.g. 10"
|
placeholder="e.g. 10"
|
||||||
data-testid="input-plan-speed-up"
|
data-testid="input-plan-speed-up"
|
||||||
/>
|
/>
|
||||||
@@ -290,7 +294,7 @@ export default function PlansPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => createMutation.mutate()}
|
onClick={() => createMutation.mutate()}
|
||||||
isLoading={createMutation.isPending}
|
isLoading={createMutation.isPending}
|
||||||
disabled={!createForm.name || !createForm.speedDown || !createForm.speedUp || !createForm.monthlyPrice}
|
disabled={!createForm.name || !createForm.speedDownMbps || !createForm.speedUpMbps || !createForm.monthlyPrice}
|
||||||
data-testid="btn-submit-create"
|
data-testid="btn-submit-create"
|
||||||
>
|
>
|
||||||
Create Plan
|
Create Plan
|
||||||
@@ -301,7 +305,7 @@ export default function PlansPage() {
|
|||||||
|
|
||||||
{/* Edit Plan Modal */}
|
{/* Edit Plan Modal */}
|
||||||
<Modal isOpen={!!editPlan} onClose={() => setEditPlan(null)} title={`Edit Plan: ${editPlan?.name ?? ""}`} className="max-w-md">
|
<Modal isOpen={!!editPlan} onClose={() => setEditPlan(null)} title={`Edit Plan: ${editPlan?.name ?? ""}`} className="max-w-md">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4" data-testid="modal-edit-plan">
|
||||||
<Input
|
<Input
|
||||||
label="Plan Name"
|
label="Plan Name"
|
||||||
value={editForm.name}
|
value={editForm.name}
|
||||||
@@ -316,23 +320,23 @@ export default function PlansPage() {
|
|||||||
onChange={e => setEditForm(f => ({ ...f, type: e.target.value as "PREPAID" | "POSTPAID" }))}
|
onChange={e => setEditForm(f => ({ ...f, type: e.target.value as "PREPAID" | "POSTPAID" }))}
|
||||||
data-testid="select-edit-type"
|
data-testid="select-edit-type"
|
||||||
>
|
>
|
||||||
<option value="PREPAID">PREPAID</option>
|
|
||||||
<option value="POSTPAID">POSTPAID</option>
|
<option value="POSTPAID">POSTPAID</option>
|
||||||
|
<option value="PREPAID">PREPAID</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Input
|
<Input
|
||||||
label="Download (Mbps)"
|
label="Download (Mbps)"
|
||||||
type="number"
|
type="number"
|
||||||
value={editForm.speedDown}
|
value={editForm.speedDownMbps}
|
||||||
onChange={e => setEditForm(f => ({ ...f, speedDown: e.target.value }))}
|
onChange={e => setEditForm(f => ({ ...f, speedDownMbps: e.target.value }))}
|
||||||
data-testid="input-edit-speed-down"
|
data-testid="input-edit-speed-down"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Upload (Mbps)"
|
label="Upload (Mbps)"
|
||||||
type="number"
|
type="number"
|
||||||
value={editForm.speedUp}
|
value={editForm.speedUpMbps}
|
||||||
onChange={e => setEditForm(f => ({ ...f, speedUp: e.target.value }))}
|
onChange={e => setEditForm(f => ({ ...f, speedUpMbps: e.target.value }))}
|
||||||
data-testid="input-edit-speed-up"
|
data-testid="input-edit-speed-up"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -355,7 +359,7 @@ export default function PlansPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => updateMutation.mutate()}
|
onClick={() => updateMutation.mutate()}
|
||||||
isLoading={updateMutation.isPending}
|
isLoading={updateMutation.isPending}
|
||||||
disabled={!editForm.name || !editForm.speedDown || !editForm.speedUp || !editForm.monthlyPrice}
|
disabled={!editForm.name || !editForm.speedDownMbps || !editForm.speedUpMbps || !editForm.monthlyPrice}
|
||||||
data-testid="btn-submit-edit"
|
data-testid="btn-submit-edit"
|
||||||
>
|
>
|
||||||
Save Changes
|
Save Changes
|
||||||
@@ -366,7 +370,7 @@ export default function PlansPage() {
|
|||||||
|
|
||||||
{/* Delete Confirmation Modal */}
|
{/* Delete Confirmation Modal */}
|
||||||
<Modal isOpen={!!deletePlan} onClose={() => setDeletePlan(null)} title="Delete Plan" className="max-w-sm">
|
<Modal isOpen={!!deletePlan} onClose={() => setDeletePlan(null)} title="Delete Plan" className="max-w-sm">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4" data-testid="modal-delete-plan">
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
Are you sure you want to delete <strong>{deletePlan?.name}</strong>? This action cannot be undone.
|
Are you sure you want to delete <strong>{deletePlan?.name}</strong>? This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -5,75 +5,90 @@ test.describe('Plans', () => {
|
|||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await login(page);
|
await login(page);
|
||||||
await page.goto('/plans');
|
await page.goto('/plans');
|
||||||
await page.waitForURL(/\/plans/);
|
await page.waitForLoadState('networkidle');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('plans page renders with header and table', async ({ page }) => {
|
test('plans page renders with header and table', async ({ page }) => {
|
||||||
await expect(page.locator('h1:has-text("Plans")')).toBeVisible();
|
await expect(page.locator('h1:has-text("Plans")')).toBeVisible();
|
||||||
await expect(page.locator('table')).toBeVisible();
|
await expect(page.locator('table')).toBeVisible();
|
||||||
await expect(page.locator('th:has-text("Name")')).toBeVisible();
|
await expect(page.locator('[data-testid="btn-add-plan"]')).toBeVisible();
|
||||||
await expect(page.locator('th:has-text("Type")')).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('plans list loads rows from API', async ({ page }) => {
|
test('plans list loads rows from API', async ({ page }) => {
|
||||||
// Wait for at least one row (demo data has 5 plans)
|
// Wait for data to load
|
||||||
await expect(page.locator('[data-testid="plan-row"]').first()).toBeVisible({ timeout: 10000 });
|
await page.waitForTimeout(3000);
|
||||||
const count = await page.locator('[data-testid="plan-row"]').count();
|
const rows = page.locator('[data-testid="plan-row"]');
|
||||||
|
const count = await rows.count();
|
||||||
|
// Demo tenant has seeded plans
|
||||||
expect(count).toBeGreaterThan(0);
|
expect(count).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('add plan modal opens and submits', async ({ page }) => {
|
test('add plan modal opens and submits', async ({ page }) => {
|
||||||
// Open create modal
|
|
||||||
await page.click('[data-testid="btn-add-plan"]');
|
await page.click('[data-testid="btn-add-plan"]');
|
||||||
await expect(page.locator('[data-testid="input-plan-name"]')).toBeVisible();
|
|
||||||
|
|
||||||
// Fill form with correct numeric fields
|
// Modal should appear
|
||||||
|
await expect(page.locator('[data-testid="modal-create-plan"]')).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
|
// Fill form with real API fields
|
||||||
const planName = `E2E Plan ${Date.now()}`;
|
const planName = `E2E Plan ${Date.now()}`;
|
||||||
await page.fill('[data-testid="input-plan-name"]', planName);
|
await page.fill('[data-testid="input-plan-name"]', planName);
|
||||||
await page.selectOption('[data-testid="select-plan-type"]', 'PREPAID');
|
await page.selectOption('[data-testid="select-plan-type"]', 'PREPAID');
|
||||||
await page.fill('[data-testid="input-plan-speed-down"]', '25');
|
await page.fill('[data-testid="input-plan-speed-down"]', '50');
|
||||||
await page.fill('[data-testid="input-plan-speed-up"]', '10');
|
await page.fill('[data-testid="input-plan-speed-up"]', '20');
|
||||||
await page.fill('[data-testid="input-plan-price"]', '999');
|
await page.fill('[data-testid="input-plan-price"]', '1499');
|
||||||
|
await page.fill('[data-testid="input-plan-description"]', 'E2E test plan');
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
await page.click('[data-testid="btn-submit-create"]');
|
await page.click('[data-testid="btn-submit-create"]');
|
||||||
|
|
||||||
// Modal should close after successful API call
|
// Wait for modal to close (API call + state update)
|
||||||
await expect(page.locator('[data-testid="input-plan-name"]')).not.toBeVisible({ timeout: 10000 });
|
await expect(page.locator('[data-testid="modal-create-plan"]')).not.toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
// New plan should appear in table
|
// New plan should appear in the table
|
||||||
await expect(page.locator(`text=${planName}`)).toBeVisible({ timeout: 10000 });
|
await page.waitForTimeout(1000);
|
||||||
|
await expect(page.locator(`text=${planName}`)).toBeVisible({ timeout: 5000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('edit plan modal opens and submits', async ({ page }) => {
|
test('edit plan modal opens and submits', async ({ page }) => {
|
||||||
// Wait for rows
|
await page.waitForTimeout(2000);
|
||||||
await expect(page.locator('[data-testid="plan-row"]').first()).toBeVisible({ timeout: 10000 });
|
const editBtns = page.locator('[data-testid="btn-edit-plan"]');
|
||||||
|
const count = await editBtns.count();
|
||||||
|
expect(count).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Click first edit button
|
// Click first edit button
|
||||||
await page.locator('[data-testid="btn-edit-plan"]').first().click();
|
await editBtns.first().click();
|
||||||
|
|
||||||
|
// Edit modal should open
|
||||||
|
await expect(page.locator('[data-testid="modal-edit-plan"]')).toBeVisible({ timeout: 5000 });
|
||||||
await expect(page.locator('[data-testid="input-edit-name"]')).toBeVisible();
|
await expect(page.locator('[data-testid="input-edit-name"]')).toBeVisible();
|
||||||
|
|
||||||
// Edit the name
|
// Change download speed
|
||||||
await page.fill('[data-testid="input-edit-name"]', 'Updated Plan Name');
|
await page.fill('[data-testid="input-edit-speed-down"]', '100');
|
||||||
|
|
||||||
|
// Submit
|
||||||
await page.click('[data-testid="btn-submit-edit"]');
|
await page.click('[data-testid="btn-submit-edit"]');
|
||||||
|
|
||||||
// Modal should close
|
// Modal should close
|
||||||
await expect(page.locator('[data-testid="input-edit-name"]')).not.toBeVisible({ timeout: 10000 });
|
await expect(page.locator('[data-testid="modal-edit-plan"]')).not.toBeVisible({ timeout: 10000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('delete plan shows confirmation dialog', async ({ page }) => {
|
test('delete plan shows confirmation and cancels', async ({ page }) => {
|
||||||
// Wait for rows
|
await page.waitForTimeout(2000);
|
||||||
await expect(page.locator('[data-testid="plan-row"]').first()).toBeVisible({ timeout: 10000 });
|
const deleteBtns = page.locator('[data-testid="btn-delete-plan"]');
|
||||||
|
const count = await deleteBtns.count();
|
||||||
|
expect(count).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Click first delete button
|
// Click last delete button (to avoid deleting seeded data, target the E2E-created plan)
|
||||||
await page.locator('[data-testid="btn-delete-plan"]').first().click();
|
await deleteBtns.last().click();
|
||||||
|
|
||||||
// Confirmation modal should appear
|
// Confirmation modal should appear
|
||||||
|
await expect(page.locator('[data-testid="modal-delete-plan"]')).toBeVisible({ timeout: 5000 });
|
||||||
await expect(page.locator('[data-testid="btn-confirm-delete"]')).toBeVisible();
|
await expect(page.locator('[data-testid="btn-confirm-delete"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-testid="btn-cancel-delete"]')).toBeVisible();
|
|
||||||
|
|
||||||
// Cancel — don't actually delete
|
// Cancel the delete
|
||||||
await page.click('[data-testid="btn-cancel-delete"]');
|
await page.click('[data-testid="btn-cancel-delete"]');
|
||||||
await expect(page.locator('[data-testid="btn-confirm-delete"]')).not.toBeVisible();
|
|
||||||
|
// Modal should close
|
||||||
|
await expect(page.locator('[data-testid="modal-delete-plan"]')).not.toBeVisible({ timeout: 5000 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user