- Global Carbon toast stack replaces scattered inline notices; errors persist until closed. - Settings is split into eight tabs with one Save and a remembered last tab; Numbering embeds the series settings. - New Clients page with a DataTable, shared add/edit modal, structured addresses, GST category (informational), per-client notes and payment terms. The composed address string is kept for back-compat. Deleting a client warns about invoice count; issued invoices keep their own snapshot. - Migration M7 adds the client columns and item_presets; existing clients with a GSTIN are backfilled to registered_regular. - New invoice form: client ComboBox with type-ahead and inline create, client defaults applied on selection, and an Add from preset control. - Rust tests for M7 (fresh and v6 upgrade), client validation and CRUD, presets CRUD; vitest for the toast store and new helpers. PDF output and fingerprint goldens are unchanged. The new UI has not been run in a webview yet.
44 lines
2.1 KiB
TypeScript
44 lines
2.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { clientGstinStateError, gstinChecksumError, gstinFullError, vendorGstinError } from "./validators";
|
|
|
|
// Same GSTINs as the Rust tests in src-tauri/src/gst.rs.
|
|
describe("GSTIN checksum", () => {
|
|
it("accepts valid GSTINs", () => {
|
|
expect(gstinChecksumError("27AAPFU0939F1ZV")).toBeUndefined();
|
|
expect(gstinChecksumError("07AAGFF2194N1Z1")).toBeUndefined();
|
|
expect(gstinChecksumError("29ABCDE1234F1ZW")).toBeUndefined();
|
|
});
|
|
|
|
it("rejects a wrong check digit and bad shapes", () => {
|
|
expect(gstinChecksumError("29ABCDE1234F1Z5")).toMatch(/expected W/);
|
|
expect(gstinChecksumError("27AAPFU0939F1ZX")).toMatch(/check digit/);
|
|
expect(gstinChecksumError("27AAPFU0939F1Z")).toMatch(/15-character/);
|
|
});
|
|
|
|
it("cross-checks the PAN and the state", () => {
|
|
expect(gstinFullError("27AAPFU0939F1ZV", "AAPFU0939F")).toBeUndefined();
|
|
expect(gstinFullError("27AAPFU0939F1ZV", "AAPFU0939G")).toMatch(/PAN/);
|
|
expect(gstinFullError("")).toBeUndefined();
|
|
expect(vendorGstinError("unregistered", "", "", "29")).toBeUndefined();
|
|
expect(vendorGstinError("regular", "", "", "27")).toMatch(/needs a GSTIN/);
|
|
expect(vendorGstinError("regular", "27AAPFU0939F1ZV", "AAPFU0939F", "29")).toMatch(/state code/);
|
|
expect(vendorGstinError("regular", "27AAPFU0939F1ZV", "AAPFU0939F", "27")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("clientGstinStateError", () => {
|
|
it("allows empty and NA", () => {
|
|
expect(clientGstinStateError("", "29")).toBeUndefined();
|
|
expect(clientGstinStateError("NA", "29")).toBeUndefined();
|
|
});
|
|
it("checks shape and checksum", () => {
|
|
expect(clientGstinStateError("29ABCDE1234F1Z5", "29")).toMatch(/check digit/);
|
|
expect(clientGstinStateError("29ABC", "")).toMatch(/15-character/);
|
|
});
|
|
it("checks the state prefix only when a state is chosen", () => {
|
|
expect(clientGstinStateError("29ABCDE1234F1ZW", "29")).toBeUndefined();
|
|
expect(clientGstinStateError("29abcde1234f1zw", "")).toBeUndefined();
|
|
expect(clientGstinStateError("29ABCDE1234F1ZW", "27")).toMatch(/starts with 29/);
|
|
});
|
|
});
|