@kahitsan/ksui 0.47.0 → 0.47.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.47.
|
|
3
|
+
"version": "0.47.2",
|
|
4
4
|
"description": "ksui is a standalone set of SolidJS UI components for KahitSan/Hilinga and any SolidJS app. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with only solid-js externalized; it depends on nothing but solid-js + lucide-solid and injects its own CSS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// and address, offers inputs when they are missing, and warns when withholding
|
|
3
3
|
// is on but no TIN is known.
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import { render, fireEvent, waitFor } from "@solidjs/testing-library";
|
|
5
|
+
import { render, fireEvent, waitFor, screen } from "@solidjs/testing-library";
|
|
6
|
+
import { createSignal } from "solid-js";
|
|
6
7
|
import TransactionForm, { type TransactionFormProps } from "./TransactionForm";
|
|
7
8
|
import { isValidTin } from "./payee-tin";
|
|
8
9
|
|
|
@@ -102,15 +103,19 @@ beforeEach(() => vi.unstubAllGlobals());
|
|
|
102
103
|
afterEach(() => vi.unstubAllGlobals());
|
|
103
104
|
|
|
104
105
|
describe("isValidTin", () => {
|
|
105
|
-
it("accepts 9
|
|
106
|
+
it("accepts 9 digits with a 3 or 5 digit branch code, dashes optional", () => {
|
|
106
107
|
expect(isValidTin("123456789")).toBe(true);
|
|
107
108
|
expect(isValidTin("123-456-789")).toBe(true);
|
|
108
109
|
expect(isValidTin("123-456-789-000")).toBe(true);
|
|
109
110
|
expect(isValidTin("123456789000")).toBe(true);
|
|
111
|
+
expect(isValidTin("123-456-789-00043")).toBe(true);
|
|
112
|
+
expect(isValidTin("12345678900043")).toBe(true);
|
|
110
113
|
});
|
|
111
114
|
it("rejects other lengths and non-digits", () => {
|
|
112
115
|
expect(isValidTin("12345678")).toBe(false);
|
|
113
116
|
expect(isValidTin("1234567890")).toBe(false);
|
|
117
|
+
expect(isValidTin("1234567890001")).toBe(false);
|
|
118
|
+
expect(isValidTin("123456789000430")).toBe(false);
|
|
114
119
|
expect(isValidTin("12a-456-789")).toBe(false);
|
|
115
120
|
});
|
|
116
121
|
it("treats blank as valid because the field is optional", () => {
|
|
@@ -221,4 +226,51 @@ describe("TransactionForm payee tax ID", () => {
|
|
|
221
226
|
await Promise.resolve();
|
|
222
227
|
expect(queryByTestId("form-payee-tin-input")).toBeNull();
|
|
223
228
|
});
|
|
229
|
+
|
|
230
|
+
it("shows the newly picked payee's tax ID when switching payees", async () => {
|
|
231
|
+
const payees = [
|
|
232
|
+
{ id: 1, name: "Alpha Mart", kind: "vendor", tin: "123-456-789-00043", address: "1 Main St" },
|
|
233
|
+
{ id: 2, name: "Beta Shop", kind: "vendor", tin: null, address: null },
|
|
234
|
+
];
|
|
235
|
+
vi.stubGlobal(
|
|
236
|
+
"fetch",
|
|
237
|
+
vi.fn(async (url: string) => {
|
|
238
|
+
const one = /\/api\/payees\/(\d+)/.exec(String(url));
|
|
239
|
+
const body = one ? payees.find((p) => p.id === Number(one[1])) : { data: payees };
|
|
240
|
+
return new Response(JSON.stringify(body), { status: 200 });
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
const [payee, setPayee] = createSignal("");
|
|
244
|
+
const [payeeId, setPayeeId] = createSignal<number | null>(null);
|
|
245
|
+
const { findByTestId, queryByTestId } = render(() => (
|
|
246
|
+
<TransactionForm
|
|
247
|
+
{...tinProps({
|
|
248
|
+
get payee() {
|
|
249
|
+
return payee();
|
|
250
|
+
},
|
|
251
|
+
setPayee,
|
|
252
|
+
get payeeId() {
|
|
253
|
+
return payeeId();
|
|
254
|
+
},
|
|
255
|
+
setPayeeId,
|
|
256
|
+
})}
|
|
257
|
+
/>
|
|
258
|
+
));
|
|
259
|
+
const pick = async (id: number) => {
|
|
260
|
+
fireEvent.click(await screen.findByTestId("form-payee-picker-trigger"));
|
|
261
|
+
fireEvent.input(await screen.findByTestId("form-payee-picker-input"), { target: { value: "a" } });
|
|
262
|
+
fireEvent.click(await screen.findByTestId(`form-payee-picker-result-${id}`));
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
await pick(1);
|
|
266
|
+
expect((await findByTestId("form-payee-tin-readonly")).textContent).toContain("123-456-789-00043");
|
|
267
|
+
|
|
268
|
+
await pick(2);
|
|
269
|
+
await findByTestId("form-payee-tin-input");
|
|
270
|
+
expect(queryByTestId("form-payee-tin-readonly")).toBeNull();
|
|
271
|
+
|
|
272
|
+
await pick(1);
|
|
273
|
+
expect((await findByTestId("form-payee-tin-readonly")).textContent).toContain("123-456-789-00043");
|
|
274
|
+
expect(queryByTestId("form-payee-tin-input")).toBeNull();
|
|
275
|
+
});
|
|
224
276
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// advanced-fields toggle entirely, for a caller that locks `category` to one
|
|
8
8
|
// value and never needs EWT/sharing (e.g. a "record my own expense" surface).
|
|
9
9
|
|
|
10
|
-
import { createEffect, createSignal, Show, For } from "solid-js";
|
|
10
|
+
import { batch, createEffect, createSignal, Show, For } from "solid-js";
|
|
11
11
|
import X from "lucide-solid/icons/x";
|
|
12
12
|
import Upload from "lucide-solid/icons/upload";
|
|
13
13
|
import FileIcon from "lucide-solid/icons/file";
|
|
@@ -788,13 +788,16 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
788
788
|
icon={Store}
|
|
789
789
|
noun="payee"
|
|
790
790
|
placeholder={catConfig().payeePlaceholder!}
|
|
791
|
-
onChange={(p) =>
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
791
|
+
onChange={(p) =>
|
|
792
|
+
// One batch so the sync effect never sees the new name with the old id.
|
|
793
|
+
batch(() => {
|
|
794
|
+
setSelectedPayee(p);
|
|
795
|
+
props.setPayee(p ? p.name : "");
|
|
796
|
+
props.setPayeeId(p ? p.id : null);
|
|
797
|
+
props.setPayeeTin?.("");
|
|
798
|
+
props.setPayeeAddress?.("");
|
|
799
|
+
})
|
|
800
|
+
}
|
|
798
801
|
/>
|
|
799
802
|
</FormField>
|
|
800
803
|
<FormField label={props.category === "sale" ? "Invoice ID" : "Receipt / Ref #"}>
|
|
@@ -833,14 +836,14 @@ export default function TransactionForm(props: TransactionFormProps) {
|
|
|
833
836
|
value={props.payeeTin ?? ""}
|
|
834
837
|
onInput={(e) => props.setPayeeTin!(e.currentTarget.value)}
|
|
835
838
|
class="w-full bg-[color-mix(in_srgb,var(--ks-overlay-surface,#18181b)_60%,transparent)] border border-[color-mix(in_srgb,var(--ks-border,rgba(39,39,42,0.5))_60%,transparent)] px-3 py-3 text-sm text-[var(--ks-fg,#ffffff)] ks-hud-clip-button focus:outline-none focus:border-[color-mix(in_srgb,var(--ks-focus-ring,#c9a961)_50%,transparent)]"
|
|
836
|
-
placeholder="000-000-000-
|
|
839
|
+
placeholder="000-000-000-00000"
|
|
837
840
|
/>
|
|
838
841
|
<Show when={!isValidTin(props.payeeTin ?? "")}>
|
|
839
842
|
<p
|
|
840
843
|
data-testid="form-payee-tin-error"
|
|
841
844
|
class="mt-1 text-[10px] text-[var(--ks-danger,#ef4444)]"
|
|
842
845
|
>
|
|
843
|
-
A TIN has 9 digits, or
|
|
846
|
+
A TIN has 9 digits, plus a 3 or 5 digit branch code.
|
|
844
847
|
</p>
|
|
845
848
|
</Show>
|
|
846
849
|
</FormField>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Tax-ID helpers shared by the transaction form and its hosts, so both sides
|
|
2
2
|
// apply the same rule before a TIN is saved.
|
|
3
3
|
|
|
4
|
-
const TIN_PATTERN = /^\d{9}(\d{3})?$/;
|
|
4
|
+
const TIN_PATTERN = /^\d{9}(\d{3}|\d{5})?$/;
|
|
5
5
|
|
|
6
6
|
/** Trims the input and drops dashes and spaces: "123-456-789" -> "123456789". */
|
|
7
7
|
export function normalizeTin(value: string): string {
|
|
8
8
|
return value.replace(/[\s-]/g, "");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/** A TIN is 9 digits
|
|
11
|
+
/** A TIN is 9 digits plus an optional 3 or 5 digit branch code. Blank is valid (optional). */
|
|
12
12
|
export function isValidTin(value: string): boolean {
|
|
13
13
|
const digits = normalizeTin(value);
|
|
14
14
|
return digits === "" || TIN_PATTERN.test(digits);
|