parseFloat('0') is falsy in JS so `|| 0.18` silently discarded the user's
explicit 0% selection. Replaced with an explicit empty-string guard.
Adds e2e spec gst-rate.spec.ts covering all five GST rates (0/5/12/18/28%).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
92 lines
4.3 KiB
TypeScript
92 lines
4.3 KiB
TypeScript
/**
|
|
* E2E — GST rate selection correctness on the PO line-items editor.
|
|
*
|
|
* Bug covered: GST-0PCT-FALLBACK (fixed 2026-05-26)
|
|
* `parseFloat('0') || 0.18` is falsy, so selecting 0% GST silently fell back
|
|
* to 18%. Fixed by changing the expression to an explicit null/empty-string
|
|
* guard: `row.gstRate !== "" && row.gstRate != null ? parseFloat(row.gstRate) : 0.18`
|
|
*
|
|
* User story: S-01 (create PO, line items with correct GST calculation)
|
|
* Acceptance criterion: selecting any GST rate (0 / 5 / 12 / 18 / 28 %) on a
|
|
* line item must produce the mathematically correct Grand Total in the footer.
|
|
*
|
|
* Preconditions:
|
|
* - Dev server running at http://localhost:3000
|
|
* - Seed data present (at least one vessel and account selectable)
|
|
* - tech@pelagia.local / tech1234 credentials valid
|
|
*/
|
|
|
|
import { test, expect, type Page } from "@playwright/test";
|
|
import { login, fillPoHeader, USERS } from "./helpers/login";
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
/** Selects a GST rate in the first line-item row's GST dropdown. */
|
|
async function selectGstRate(page: Page, value: string): Promise<void> {
|
|
const lineItemsSection = page.locator("section").filter({
|
|
has: page.getByRole("heading", { name: /line items/i }),
|
|
});
|
|
const firstRow = lineItemsSection.locator("tbody tr").first();
|
|
// The GST select is the second <select> in the row (first is the UOM select)
|
|
await firstRow.locator("select").nth(1).selectOption(value);
|
|
}
|
|
|
|
/**
|
|
* Reads the Grand Total cell text from the line-items tfoot.
|
|
* The tfoot last row has a label cell containing "Grand Total" and a value cell.
|
|
*/
|
|
async function getGrandTotal(page: Page): Promise<string> {
|
|
const lineItemsSection = page.locator("section").filter({
|
|
has: page.getByRole("heading", { name: /line items/i }),
|
|
});
|
|
// The last row of the tfoot contains "Grand Total"
|
|
const grandTotalRow = lineItemsSection.locator("tfoot tr").last();
|
|
// The value is the last <td> in that row
|
|
return (await grandTotalRow.locator("td").last().textContent()) ?? "";
|
|
}
|
|
|
|
// ── Test data ─────────────────────────────────────────────────────────────────
|
|
|
|
const GST_CASES = [
|
|
{ label: "0%", value: "0", expected: "₹1,000.00" },
|
|
{ label: "5%", value: "0.05", expected: "₹1,050.00" },
|
|
{ label: "12%", value: "0.12", expected: "₹1,120.00" },
|
|
{ label: "18%", value: "0.18", expected: "₹1,180.00" },
|
|
{ label: "28%", value: "0.28", expected: "₹1,280.00" },
|
|
] as const;
|
|
|
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
|
|
test.describe("GST rate selection — Grand Total correctness (GST-0PCT-FALLBACK)", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await login(page, USERS.TECH);
|
|
await page.goto("/po/new");
|
|
|
|
// Fill required header fields so the form is valid
|
|
await fillPoHeader(page, `E2E_GST_${Date.now()}`);
|
|
|
|
// Fill the first line item: name=Bearing Assembly, qty=1, unitPrice=1000
|
|
const lineItemsSection = page.locator("section").filter({
|
|
has: page.getByRole("heading", { name: /line items/i }),
|
|
});
|
|
const firstRow = lineItemsSection.locator("tbody tr").first();
|
|
|
|
await firstRow.getByPlaceholder("Item name *").fill("Bearing Assembly");
|
|
// qty input — the first number input in the row
|
|
await firstRow.locator('input[type="number"]').first().fill("1");
|
|
// unit price input — placeholder "0.00"
|
|
await firstRow.locator('input[placeholder="0.00"]').fill("1000");
|
|
});
|
|
|
|
for (const { label, value, expected } of GST_CASES) {
|
|
test(`GST-0PCT-FALLBACK: selecting ${label} GST shows Grand Total ${expected}`, async ({ page }) => {
|
|
await selectGstRate(page, value);
|
|
|
|
// The Grand Total footer cell must update reactively — assert with auto-retry
|
|
await expect(async () => {
|
|
const total = await getGrandTotal(page);
|
|
expect(total.trim()).toBe(expected);
|
|
}).toPass({ timeout: 5_000 });
|
|
});
|
|
}
|
|
});
|