taiwan-validator 1.0.0 → 1.2.0

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.
@@ -0,0 +1,336 @@
1
+ import { validateLicensePlate } from "./license-plate";
2
+
3
+ describe("validateLicensePlate", () => {
4
+ describe("基本驗證", () => {
5
+ test("應該拒絕空字串", () => {
6
+ const result = validateLicensePlate("");
7
+ expect(result.isValid).toBe(false);
8
+ expect(result.message).toBe("車牌號碼必須為非空字串");
9
+ });
10
+
11
+ test("應該拒絕非字串輸入", () => {
12
+ const result = validateLicensePlate(null as unknown as string);
13
+ expect(result.isValid).toBe(false);
14
+ expect(result.message).toBe("車牌號碼必須為非空字串");
15
+ });
16
+
17
+ test("應該拒絕無效的格式", () => {
18
+ const result = validateLicensePlate("INVALID");
19
+ expect(result.isValid).toBe(false);
20
+ expect(result.message).toBe("無效的車牌號碼格式");
21
+ });
22
+
23
+ test("應該處理小寫輸入並轉換為大寫", () => {
24
+ const result = validateLicensePlate("abc-1235");
25
+ expect(result.isValid).toBe(true);
26
+ expect(result.plateType).toBe("car");
27
+ });
28
+
29
+ test("應該處理含有空格的輸入", () => {
30
+ const result = validateLicensePlate(" ABC-1235 ");
31
+ expect(result.isValid).toBe(true);
32
+ expect(result.plateType).toBe("car");
33
+ });
34
+ });
35
+
36
+ describe("新制汽車車牌驗證", () => {
37
+ test("應該驗證有效的新制汽車車牌", () => {
38
+ const validPlates = [
39
+ "ABC-1235",
40
+ "XYZ-9876",
41
+ "AAA-0000",
42
+ "ZZZ-9999",
43
+ "ABC-1230",
44
+ ];
45
+
46
+ validPlates.forEach((plate) => {
47
+ const result = validateLicensePlate(plate);
48
+ expect(result.isValid).toBe(true);
49
+ expect(result.plateType).toBe("car");
50
+ });
51
+ });
52
+
53
+ test("應該拒絕包含數字4的車牌", () => {
54
+ const invalidPlates = [
55
+ "ABC-1234", // 包含4
56
+ "ABC-4567", // 包含4
57
+ "ABC-0004", // 包含4
58
+ ];
59
+
60
+ invalidPlates.forEach((plate) => {
61
+ const result = validateLicensePlate(plate);
62
+ expect(result.isValid).toBe(false);
63
+ });
64
+ });
65
+
66
+ test("應該拒絕包含字母I或O的車牌", () => {
67
+ const invalidPlates = [
68
+ "AIC-1235", // 包含I
69
+ "AOC-1235", // 包含O
70
+ "IBC-1235", // 包含I
71
+ "OBC-1235", // 包含O
72
+ ];
73
+
74
+ invalidPlates.forEach((plate) => {
75
+ const result = validateLicensePlate(plate);
76
+ expect(result.isValid).toBe(false);
77
+ });
78
+ });
79
+
80
+ test("應該拒絕格式錯誤的汽車車牌(指定類型驗證)", () => {
81
+ const invalidPlates = [
82
+ "AB-1235", // 只有2個字母
83
+ "ABCD-1235", // 4個字母
84
+ "ABC-123", // 只有3個數字(但這是有效的小型機車格式)
85
+ "ABC-12345", // 5個數字
86
+ "ABC1235", // 缺少破折號
87
+ ];
88
+
89
+ invalidPlates.forEach((plate) => {
90
+ const result = validateLicensePlate(plate, { type: "car" });
91
+ expect(result.isValid).toBe(false);
92
+ });
93
+ });
94
+
95
+ test("當指定類型為car時,應該只驗證新制汽車格式", () => {
96
+ const result = validateLicensePlate("ABC-1235", { type: "car" });
97
+ expect(result.isValid).toBe(true);
98
+ expect(result.plateType).toBe("car");
99
+ });
100
+
101
+ test("當指定類型為car但格式錯誤時,應該返回錯誤訊息", () => {
102
+ const result = validateLicensePlate("1A-2345", { type: "car" });
103
+ expect(result.isValid).toBe(false);
104
+ expect(result.message).toBe("無效的汽車車牌號碼");
105
+ });
106
+ });
107
+
108
+ describe("舊制汽車車牌驗證", () => {
109
+ test("應該驗證有效的舊制汽車車牌", () => {
110
+ const validPlates = ["1A-2345", "9Z-0000", "5B-9999", "3X-1234"];
111
+
112
+ validPlates.forEach((plate) => {
113
+ const result = validateLicensePlate(plate);
114
+ expect(result.isValid).toBe(true);
115
+ expect(result.plateType).toBe("car-old");
116
+ });
117
+ });
118
+
119
+ test("應該拒絕格式錯誤的舊制車牌", () => {
120
+ const invalidPlates = [
121
+ "AA-2345", // 2個字母
122
+ "1AB-2345", // 2個字母
123
+ "1-2345", // 缺少字母
124
+ "1A-234", // 只有3個數字
125
+ "1A-23456", // 5個數字
126
+ ];
127
+
128
+ invalidPlates.forEach((plate) => {
129
+ const result = validateLicensePlate(plate);
130
+ expect(result.isValid).toBe(false);
131
+ });
132
+ });
133
+
134
+ test("當指定類型為car-old時,應該只驗證舊制汽車格式", () => {
135
+ const result = validateLicensePlate("1A-2345", { type: "car-old" });
136
+ expect(result.isValid).toBe(true);
137
+ expect(result.plateType).toBe("car-old");
138
+ });
139
+ });
140
+
141
+ describe("電動汽車車牌驗證", () => {
142
+ test("應該驗證有效的電動汽車車牌", () => {
143
+ const validPlates = [
144
+ "EAB-1235",
145
+ "EXY-9876",
146
+ "EAA-0000",
147
+ "EZZ-9999",
148
+ "EAB-1230",
149
+ ];
150
+
151
+ validPlates.forEach((plate) => {
152
+ const result = validateLicensePlate(plate);
153
+ expect(result.isValid).toBe(true);
154
+ expect(result.plateType).toBe("electric-car");
155
+ });
156
+ });
157
+
158
+ test("應該拒絕包含數字4的電動車牌", () => {
159
+ const invalidPlates = ["EAB-1234", "EAB-4567", "EAB-0004"];
160
+
161
+ invalidPlates.forEach((plate) => {
162
+ const result = validateLicensePlate(plate);
163
+ expect(result.isValid).toBe(false);
164
+ });
165
+ });
166
+
167
+ test("應該拒絕包含字母I或O的電動車牌", () => {
168
+ const invalidPlates = [
169
+ "EIC-1235", // 包含I
170
+ "EOC-1235", // 包含O
171
+ "EIA-1235", // 包含I
172
+ "EOA-1235", // 包含O
173
+ ];
174
+
175
+ invalidPlates.forEach((plate) => {
176
+ const result = validateLicensePlate(plate);
177
+ expect(result.isValid).toBe(false);
178
+ });
179
+ });
180
+
181
+ test("應該拒絕不以E開頭的電動車牌", () => {
182
+ const invalidPlates = ["AAB-1235", "XAB-1235"];
183
+
184
+ invalidPlates.forEach((plate) => {
185
+ const result = validateLicensePlate(plate);
186
+ expect(result.plateType).not.toBe("electric-car");
187
+ });
188
+ });
189
+
190
+ test("當指定類型為electric-car時,應該只驗證電動汽車格式", () => {
191
+ const result = validateLicensePlate("EAB-1235", {
192
+ type: "electric-car",
193
+ });
194
+ expect(result.isValid).toBe(true);
195
+ expect(result.plateType).toBe("electric-car");
196
+ });
197
+ });
198
+
199
+ describe("小型機車車牌驗證", () => {
200
+ test("應該驗證有效的小型機車車牌(數字-字母格式)", () => {
201
+ const validPlates = ["123-ABC", "000-ZZZ", "999-AAA", "456-XYZ"];
202
+
203
+ validPlates.forEach((plate) => {
204
+ const result = validateLicensePlate(plate);
205
+ expect(result.isValid).toBe(true);
206
+ expect(result.plateType).toBe("motorcycle-small");
207
+ });
208
+ });
209
+
210
+ test("應該驗證有效的小型機車車牌(字母-數字格式)", () => {
211
+ const validPlates = ["ABC-123", "ZZZ-000", "AAA-999", "XYZ-456"];
212
+
213
+ validPlates.forEach((plate) => {
214
+ const result = validateLicensePlate(plate);
215
+ expect(result.isValid).toBe(true);
216
+ expect(result.plateType).toBe("motorcycle-small");
217
+ });
218
+ });
219
+
220
+ test("應該拒絕格式錯誤的小型機車車牌", () => {
221
+ const invalidPlates = [
222
+ "12-ABC", // 只有2個數字
223
+ "1234-ABC", // 4個數字
224
+ "123-AB", // 只有2個字母
225
+ "123-ABCD", // 4個字母
226
+ "AB-123", // 字母只有2個
227
+ ];
228
+
229
+ invalidPlates.forEach((plate) => {
230
+ const result = validateLicensePlate(plate);
231
+ expect(result.plateType).not.toBe("motorcycle-small");
232
+ });
233
+ });
234
+
235
+ test("當指定類型為motorcycle-small時,應該只驗證小型機車格式", () => {
236
+ const result1 = validateLicensePlate("123-ABC", {
237
+ type: "motorcycle-small",
238
+ });
239
+ expect(result1.isValid).toBe(true);
240
+ expect(result1.plateType).toBe("motorcycle-small");
241
+
242
+ const result2 = validateLicensePlate("ABC-123", {
243
+ type: "motorcycle-small",
244
+ });
245
+ expect(result2.isValid).toBe(true);
246
+ expect(result2.plateType).toBe("motorcycle-small");
247
+ });
248
+ });
249
+
250
+ describe("一般機車車牌驗證", () => {
251
+ test("應該驗證有效的一般機車車牌", () => {
252
+ const validPlates = ["AB1-234", "XY9-000", "ZZ0-999", "AA5-123"];
253
+
254
+ validPlates.forEach((plate) => {
255
+ const result = validateLicensePlate(plate);
256
+ expect(result.isValid).toBe(true);
257
+ expect(result.plateType).toBe("motorcycle");
258
+ });
259
+ });
260
+
261
+ test("應該拒絕格式錯誤的一般機車車牌", () => {
262
+ const invalidPlates = [
263
+ "A1-234", // 只有1個字母
264
+ "ABC1-234", // 3個字母
265
+ "AB-234", // 缺少數字
266
+ "AB12-234", // 2個數字
267
+ "AB1-23", // 只有2個數字
268
+ "AB1-2345", // 4個數字
269
+ ];
270
+
271
+ invalidPlates.forEach((plate) => {
272
+ const result = validateLicensePlate(plate);
273
+ expect(result.plateType).not.toBe("motorcycle");
274
+ });
275
+ });
276
+
277
+ test("當指定類型為motorcycle時,應該只驗證一般機車格式", () => {
278
+ const result = validateLicensePlate("AB1-234", { type: "motorcycle" });
279
+ expect(result.isValid).toBe(true);
280
+ expect(result.plateType).toBe("motorcycle");
281
+ });
282
+ });
283
+
284
+ describe("車牌類型偵測選項", () => {
285
+ test("當 detectType 為 false 時,不應該回傳 plateType", () => {
286
+ const result = validateLicensePlate("ABC-1235", { detectType: false });
287
+ expect(result.isValid).toBe(true);
288
+ expect(result.plateType).toBeUndefined();
289
+ });
290
+
291
+ test("當 detectType 為 true 時,應該回傳 plateType", () => {
292
+ const result = validateLicensePlate("ABC-1235", { detectType: true });
293
+ expect(result.isValid).toBe(true);
294
+ expect(result.plateType).toBe("car");
295
+ });
296
+
297
+ test("預設應該偵測車牌類型", () => {
298
+ const result = validateLicensePlate("ABC-1235");
299
+ expect(result.isValid).toBe(true);
300
+ expect(result.plateType).toBe("car");
301
+ });
302
+
303
+ test("當指定類型且 detectType 為 false 時,不應該回傳 plateType", () => {
304
+ const result = validateLicensePlate("ABC-1235", {
305
+ type: "car",
306
+ detectType: false,
307
+ });
308
+ expect(result.isValid).toBe(true);
309
+ expect(result.plateType).toBeUndefined();
310
+ });
311
+ });
312
+
313
+ describe("類型優先順序測試", () => {
314
+ test("應該優先識別為電動汽車而非一般汽車", () => {
315
+ const result = validateLicensePlate("EAB-1235");
316
+ expect(result.isValid).toBe(true);
317
+ expect(result.plateType).toBe("electric-car");
318
+ });
319
+
320
+ test("應該正確區分新制和舊制汽車", () => {
321
+ const newCar = validateLicensePlate("ABC-1235");
322
+ expect(newCar.plateType).toBe("car");
323
+
324
+ const oldCar = validateLicensePlate("1A-2345");
325
+ expect(oldCar.plateType).toBe("car-old");
326
+ });
327
+
328
+ test("應該正確區分小型機車和一般機車", () => {
329
+ const smallMotorcycle = validateLicensePlate("123-ABC");
330
+ expect(smallMotorcycle.plateType).toBe("motorcycle-small");
331
+
332
+ const motorcycle = validateLicensePlate("AB1-234");
333
+ expect(motorcycle.plateType).toBe("motorcycle");
334
+ });
335
+ });
336
+ });
@@ -0,0 +1,215 @@
1
+ import type { ValidationResult } from "../types";
2
+
3
+ /**
4
+ * 車牌類型
5
+ */
6
+ export type LicensePlateType =
7
+ | "car" // 汽車(新制)
8
+ | "car-old" // 汽車(舊制)
9
+ | "electric-car" // 電動汽車
10
+ | "motorcycle-small" // 小型機車(50cc以下)
11
+ | "motorcycle"; // 一般機車
12
+
13
+ /**
14
+ * 車牌驗證結果(含車牌類型資訊)
15
+ */
16
+ export interface LicensePlateValidationResult extends ValidationResult {
17
+ plateType?: LicensePlateType | undefined;
18
+ }
19
+
20
+ /**
21
+ * 驗證新制汽車車牌(2012年12月後)
22
+ * 格式:3個英文字母 - 4個數字
23
+ * 不使用字母 I、O
24
+ * 不使用數字 4
25
+ */
26
+ function validateNewCarPlate(plate: string): boolean {
27
+ // 格式:3個英文字母-4個數字(例如:ABC-1234)
28
+ const pattern = /^[A-HJ-NP-Z]{3}-\d{4}$/;
29
+ if (!pattern.test(plate)) {
30
+ return false;
31
+ }
32
+
33
+ // 檢查數字部分不包含 4
34
+ const numbers = plate.split("-")[1] as string;
35
+ return !numbers.includes("4");
36
+ }
37
+
38
+ /**
39
+ * 驗證舊制汽車車牌(1992-2012)
40
+ * 格式:1個數字 + 1個英文字母 - 4個數字
41
+ */
42
+ function validateOldCarPlate(plate: string): boolean {
43
+ // 格式:1個數字+1個英文字母-4個數字(例如:1A-2345)
44
+ const pattern = /^\d[A-Z]-\d{4}$/;
45
+ return pattern.test(plate);
46
+ }
47
+
48
+ /**
49
+ * 驗證電動汽車車牌
50
+ * 格式:E + 2個英文字母 - 4個數字
51
+ * 不使用字母 I、O
52
+ * 不使用數字 4
53
+ */
54
+ function validateElectricCarPlate(plate: string): boolean {
55
+ // 格式:E + 2個英文字母-4個數字(例如:EAB-1234)
56
+ const pattern = /^E[A-HJ-NP-Z]{2}-\d{4}$/;
57
+ if (!pattern.test(plate)) {
58
+ return false;
59
+ }
60
+
61
+ // 檢查數字部分不包含 4
62
+ const numbers = plate.split("-")[1] as string;
63
+ return !numbers.includes("4");
64
+ }
65
+
66
+ /**
67
+ * 驗證小型機車車牌(50cc以下)
68
+ * 格式:3個數字-3個英文字母 或 3個英文字母-3個數字
69
+ */
70
+ function validateSmallMotorcyclePlate(plate: string): boolean {
71
+ // 格式1:3個數字-3個英文字母(例如:123-ABC)
72
+ // 格式2:3個英文字母-3個數字(例如:ABC-123)
73
+ const pattern1 = /^\d{3}-[A-Z]{3}$/;
74
+ const pattern2 = /^[A-Z]{3}-\d{3}$/;
75
+ return pattern1.test(plate) || pattern2.test(plate);
76
+ }
77
+
78
+ /**
79
+ * 驗證一般機車車牌(舊制,50-250cc)
80
+ * 格式:2個英文字母 + 1個數字 - 3個數字
81
+ */
82
+ function validateMotorcyclePlate(plate: string): boolean {
83
+ // 格式:2個英文字母+1個數字-3個數字(例如:AB1-234)
84
+ const pattern = /^[A-Z]{2}\d-\d{3}$/;
85
+ return pattern.test(plate);
86
+ }
87
+
88
+ /**
89
+ * 偵測車牌類型
90
+ */
91
+ function detectPlateType(plate: string): LicensePlateType | null {
92
+ if (validateElectricCarPlate(plate)) {
93
+ return "electric-car";
94
+ }
95
+ if (validateNewCarPlate(plate)) {
96
+ return "car";
97
+ }
98
+ if (validateOldCarPlate(plate)) {
99
+ return "car-old";
100
+ }
101
+ if (validateSmallMotorcyclePlate(plate)) {
102
+ return "motorcycle-small";
103
+ }
104
+ if (validateMotorcyclePlate(plate)) {
105
+ return "motorcycle";
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * 驗證台灣車牌號碼
112
+ * @param plate - 要驗證的車牌號碼
113
+ * @param options - 驗證選項
114
+ * @param options.type - 可選:指定車牌類型
115
+ * @param options.detectType - 是否偵測車牌類型(預設:true)
116
+ * @returns 驗證結果
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // 新制汽車
121
+ * validateLicensePlate('ABC-1235');
122
+ *
123
+ * // 電動汽車
124
+ * validateLicensePlate('EAB-1235');
125
+ *
126
+ * // 舊制汽車
127
+ * validateLicensePlate('1A-2345');
128
+ *
129
+ * // 小型機車
130
+ * validateLicensePlate('123-ABC');
131
+ * validateLicensePlate('ABC-123');
132
+ *
133
+ * // 一般機車
134
+ * validateLicensePlate('AB1-234');
135
+ *
136
+ * // 指定類型驗證
137
+ * validateLicensePlate('ABC-1235', { type: 'car' });
138
+ *
139
+ * // 不偵測類型
140
+ * validateLicensePlate('ABC-1235', { detectType: false });
141
+ * ```
142
+ */
143
+ export function validateLicensePlate(
144
+ plate: string,
145
+ options: { type?: LicensePlateType; detectType?: boolean } = {},
146
+ ): LicensePlateValidationResult {
147
+ const { type, detectType = true } = options;
148
+
149
+ if (!plate || typeof plate !== "string") {
150
+ return {
151
+ isValid: false,
152
+ message: "車牌號碼必須為非空字串",
153
+ };
154
+ }
155
+
156
+ // 移除空格並轉換為大寫
157
+ const normalized = plate.trim().toUpperCase();
158
+
159
+ // 如果指定了類型,只驗證該類型
160
+ if (type) {
161
+ let isValid = false;
162
+ switch (type) {
163
+ case "car":
164
+ isValid = validateNewCarPlate(normalized);
165
+ break;
166
+ case "car-old":
167
+ isValid = validateOldCarPlate(normalized);
168
+ break;
169
+ case "electric-car":
170
+ isValid = validateElectricCarPlate(normalized);
171
+ break;
172
+ case "motorcycle-small":
173
+ isValid = validateSmallMotorcyclePlate(normalized);
174
+ break;
175
+ case "motorcycle":
176
+ isValid = validateMotorcyclePlate(normalized);
177
+ break;
178
+ }
179
+
180
+ return {
181
+ isValid,
182
+ message: isValid ? undefined : `無效的${getPlateTypeName(type)}車牌號碼`,
183
+ plateType: isValid && detectType ? type : undefined,
184
+ };
185
+ }
186
+
187
+ // 自動偵測類型
188
+ const detectedType = detectPlateType(normalized);
189
+
190
+ if (!detectedType) {
191
+ return {
192
+ isValid: false,
193
+ message: "無效的車牌號碼格式",
194
+ };
195
+ }
196
+
197
+ return {
198
+ isValid: true,
199
+ plateType: detectType ? detectedType : undefined,
200
+ };
201
+ }
202
+
203
+ /**
204
+ * 取得車牌類型的中文名稱
205
+ */
206
+ function getPlateTypeName(type: LicensePlateType): string {
207
+ const typeNames: Record<LicensePlateType, string> = {
208
+ car: "汽車",
209
+ "car-old": "汽車(舊制)",
210
+ "electric-car": "電動汽車",
211
+ "motorcycle-small": "小型機車",
212
+ motorcycle: "機車",
213
+ };
214
+ return typeNames[type];
215
+ }
@@ -1,78 +1,51 @@
1
- import { validateNationalId } from "./national-id";
1
+ import { validateNationalId, parseNationalId } from "./national-id";
2
2
 
3
3
  describe("validateNationalId", () => {
4
- describe("Old Format", () => {
5
- test("should validate correct old format National IDs", () => {
4
+ describe("Validation", () => {
5
+ test("should validate correct National IDs", () => {
6
6
  // These are example format IDs (not real person IDs)
7
- expect(validateNationalId("A123456789", "old").isValid).toBe(true);
8
- expect(validateNationalId("F223456786", "old").isValid).toBe(true);
9
- expect(validateNationalId("O123456782", "old").isValid).toBe(true);
10
- });
11
-
12
- test("should reject invalid old format National IDs", () => {
13
- expect(validateNationalId("A123456788", "old").isValid).toBe(false); // Wrong checksum
14
- expect(validateNationalId("A323456789", "old").isValid).toBe(false); // Invalid gender code
15
- expect(validateNationalId("A12345678", "old").isValid).toBe(false); // Too short
16
- expect(validateNationalId("A1234567890", "old").isValid).toBe(false); // Too long
17
- });
18
-
19
- test("should handle case insensitive input", () => {
20
- expect(validateNationalId("a123456789", "old").isValid).toBe(true);
21
- expect(validateNationalId("f223456786", "old").isValid).toBe(true);
22
- });
23
-
24
- test("should handle whitespace", () => {
25
- expect(validateNationalId(" A123456789 ", "old").isValid).toBe(true);
26
- });
27
- });
28
-
29
- describe("New Format", () => {
30
- test("should validate correct new format National IDs", () => {
31
- // These are example format IDs (not real person IDs)
32
- expect(validateNationalId("AA23456786", "new").isValid).toBe(true);
33
- expect(validateNationalId("AB23456789", "new").isValid).toBe(true);
7
+ expect(validateNationalId("A123456789").isValid).toBe(true);
8
+ expect(validateNationalId("F223456786").isValid).toBe(true);
9
+ expect(validateNationalId("O123456782").isValid).toBe(true);
34
10
  });
35
11
 
36
- test("should reject invalid new format National IDs", () => {
37
- expect(validateNationalId("AA12345677", "new").isValid).toBe(false); // Wrong checksum
38
- expect(validateNationalId("AA1234567", "new").isValid).toBe(false); // Too short
39
- expect(validateNationalId("AA123456789", "new").isValid).toBe(false); // Too long
12
+ test("should reject invalid National IDs", () => {
13
+ expect(validateNationalId("A123456788").isValid).toBe(false); // Wrong checksum
14
+ expect(validateNationalId("A323456789").isValid).toBe(false); // Invalid gender code
15
+ expect(validateNationalId("A12345678").isValid).toBe(false); // Too short
16
+ expect(validateNationalId("A1234567890").isValid).toBe(false); // Too long
17
+ expect(validateNationalId("AA23456786").isValid).toBe(false); // 2-letter legacy format is not a National ID
40
18
  });
41
19
 
42
20
  test("should handle case insensitive input", () => {
43
- expect(validateNationalId("aa23456786", "new").isValid).toBe(true);
21
+ expect(validateNationalId("a123456789").isValid).toBe(true);
22
+ expect(validateNationalId("f223456786").isValid).toBe(true);
44
23
  });
45
24
 
46
25
  test("should handle whitespace", () => {
47
- expect(validateNationalId(" AB23456789 ", "new").isValid).toBe(true);
26
+ expect(validateNationalId(" A123456789 ").isValid).toBe(true);
48
27
  });
49
28
  });
50
29
 
51
- describe("Auto-detect Format", () => {
52
- test("should auto-detect and validate old format", () => {
53
- expect(validateNationalId("A123456789").isValid).toBe(true);
54
- expect(validateNationalId("F223456786").isValid).toBe(true);
55
- });
56
-
57
- test("should auto-detect and reject invalid old format", () => {
58
- const result = validateNationalId("A123456780");
59
- expect(result.isValid).toBe(false);
60
- expect(result.message).toBe("無效的舊式身分證字號");
61
- });
62
-
63
- test("should auto-detect and validate new format", () => {
64
- expect(validateNationalId("AA23456786").isValid).toBe(true);
65
- });
66
-
67
- test("should auto-detect and reject invalid new format", () => {
68
- const result = validateNationalId("AA23456780");
69
- expect(result.isValid).toBe(false);
70
- expect(result.message).toBe("無效的新式身分證字號");
30
+ describe("Parser (parseNationalId)", () => {
31
+ test("should parse correct National IDs with gender and region", () => {
32
+ const parsed1 = parseNationalId("A123456789");
33
+ expect(parsed1.isValid).toBe(true);
34
+ expect(parsed1.gender).toBe("male");
35
+ expect(parsed1.region).toBe("臺北市");
36
+
37
+ const parsed2 = parseNationalId("F223456786");
38
+ expect(parsed2.isValid).toBe(true);
39
+ expect(parsed2.gender).toBe("female");
40
+ expect(parsed2.region).toBe("新北市");
71
41
  });
72
42
 
73
- test("should reject completely invalid formats", () => {
74
- expect(validateNationalId("12345678").isValid).toBe(false);
75
- expect(validateNationalId("ABCDEFGH").isValid).toBe(false);
43
+ test("should return isValid: false for invalid IDs", () => {
44
+ const parsed = parseNationalId("A123456788");
45
+ expect(parsed.isValid).toBe(false);
46
+ expect(parsed.gender).toBeUndefined();
47
+ expect(parsed.region).toBeUndefined();
48
+ expect(parsed.message).toBe("無效的身分證字號");
76
49
  });
77
50
  });
78
51
 
@@ -87,9 +60,9 @@ describe("validateNationalId", () => {
87
60
  });
88
61
 
89
62
  test("should provide meaningful error messages", () => {
90
- const result1 = validateNationalId("A123456788", "old");
63
+ const result1 = validateNationalId("A123456788");
91
64
  expect(result1.isValid).toBe(false);
92
- expect(result1.message).toBeDefined();
65
+ expect(result1.message).toBe("無效的身分證字號");
93
66
 
94
67
  const result2 = validateNationalId("");
95
68
  expect(result2.isValid).toBe(false);