renusify 3.1.3 → 3.1.5
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 +1 -1
- package/plugins/auto-loader.mjs +223 -191
- package/plugins/trans/DateTime.js +135 -74
- package/tests/DateTime.test.js +596 -0
- package/tests/autoloader.test.js +905 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import DateTime from "../plugins/trans/DateTime.js";
|
|
2
|
+
|
|
3
|
+
// Simple test utilities
|
|
4
|
+
const colors = {
|
|
5
|
+
reset: "\x1b[0m",
|
|
6
|
+
green: "\x1b[32m",
|
|
7
|
+
red: "\x1b[31m",
|
|
8
|
+
yellow: "\x1b[33m",
|
|
9
|
+
blue: "\x1b[34m",
|
|
10
|
+
gray: "\x1b[90m",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let testCount = 0;
|
|
14
|
+
let passCount = 0;
|
|
15
|
+
let failCount = 0;
|
|
16
|
+
let currentSuite = "";
|
|
17
|
+
|
|
18
|
+
function suite(name, fn) {
|
|
19
|
+
currentSuite = name;
|
|
20
|
+
console.log(`\n${colors.blue}${name}${colors.reset}`);
|
|
21
|
+
fn();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function test(description, fn) {
|
|
25
|
+
testCount++;
|
|
26
|
+
try {
|
|
27
|
+
fn();
|
|
28
|
+
passCount++;
|
|
29
|
+
console.log(` ${colors.green}✓${colors.reset} ${description}`);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
failCount++;
|
|
32
|
+
console.log(` ${colors.red}✗${colors.reset} ${description}`);
|
|
33
|
+
console.log(` ${colors.red}${error.message}${colors.reset}`);
|
|
34
|
+
if (error.stack) {
|
|
35
|
+
console.log(
|
|
36
|
+
` ${colors.gray}${error.stack.split("\n").slice(1, 3).join("\n ")}${colors.reset}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assert(condition, message) {
|
|
43
|
+
if (!condition) {
|
|
44
|
+
throw new Error(message || "Assertion failed");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertEqual(actual, expected, message) {
|
|
49
|
+
if (actual !== expected) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
message ||
|
|
52
|
+
`Expected ${JSON.stringify(expected)}, but got ${JSON.stringify(actual)}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertDeepEqual(actual, expected, message) {
|
|
58
|
+
const actualStr = JSON.stringify(actual);
|
|
59
|
+
const expectedStr = JSON.stringify(expected);
|
|
60
|
+
if (actualStr !== expectedStr) {
|
|
61
|
+
throw new Error(message || `Expected ${expectedStr}, but got ${actualStr}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function assertMatch(actual, pattern, message) {
|
|
66
|
+
if (!pattern.test(actual)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
message || `Expected "${actual}" to match pattern ${pattern}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function assertUndefined(actual, message) {
|
|
74
|
+
if (actual !== undefined) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
message || `Expected undefined, but got ${JSON.stringify(actual)}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function assertDefined(actual, message) {
|
|
82
|
+
if (actual === undefined) {
|
|
83
|
+
throw new Error(message || "Expected value to be defined");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function assertGreaterThan(actual, expected, message) {
|
|
88
|
+
if (actual <= expected) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
message || `Expected ${actual} to be greater than ${expected}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Mock console methods
|
|
96
|
+
const originalConsoleError = console.error;
|
|
97
|
+
const originalConsoleWarn = console.warn;
|
|
98
|
+
let consoleErrorCalled = false;
|
|
99
|
+
let consoleWarnCalled = false;
|
|
100
|
+
|
|
101
|
+
function mockConsoleError() {
|
|
102
|
+
consoleErrorCalled = false;
|
|
103
|
+
console.error = () => {
|
|
104
|
+
consoleErrorCalled = true;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function mockConsoleWarn() {
|
|
109
|
+
consoleWarnCalled = false;
|
|
110
|
+
console.warn = () => {
|
|
111
|
+
consoleWarnCalled = true;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function restoreConsole() {
|
|
116
|
+
console.error = originalConsoleError;
|
|
117
|
+
console.warn = originalConsoleWarn;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Test setup
|
|
121
|
+
function createDateTime() {
|
|
122
|
+
const mockR = {
|
|
123
|
+
lang: "en",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const langs = {
|
|
127
|
+
en: {
|
|
128
|
+
localizeName: "en-US",
|
|
129
|
+
timeZone: "America/Los_Angeles",
|
|
130
|
+
first_day: 0,
|
|
131
|
+
time_zone_offset: -480,
|
|
132
|
+
},
|
|
133
|
+
fa: {
|
|
134
|
+
first_day: 6,
|
|
135
|
+
time_zone_offset: 210,
|
|
136
|
+
localizeName: "fa-IR",
|
|
137
|
+
timeZone: "Asia/Tehran",
|
|
138
|
+
calendar: "persian",
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return new DateTime(mockR, langs);
|
|
143
|
+
}
|
|
144
|
+
function createDateTimeFa() {
|
|
145
|
+
const mockR = {
|
|
146
|
+
lang: "fa",
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const langs = {
|
|
150
|
+
en: {
|
|
151
|
+
localizeName: "en-US",
|
|
152
|
+
timeZone: "America/Los_Angeles",
|
|
153
|
+
first_day: 0,
|
|
154
|
+
time_zone_offset: -480,
|
|
155
|
+
},
|
|
156
|
+
fa: {
|
|
157
|
+
first_day: 6,
|
|
158
|
+
time_zone_offset: 210,
|
|
159
|
+
localizeName: "fa-IR",
|
|
160
|
+
timeZone: "Asia/Tehran",
|
|
161
|
+
calendar: "persian",
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return new DateTime(mockR, langs);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Run tests
|
|
169
|
+
console.log(`${colors.yellow}\n=== DateTime Tests ===${colors.reset}\n`);
|
|
170
|
+
|
|
171
|
+
suite("Constructor", () => {
|
|
172
|
+
test("should initialize with $r and langs", () => {
|
|
173
|
+
const mockR = { lang: "en" };
|
|
174
|
+
const langs = { en: { localizeName: "en-US" } };
|
|
175
|
+
const dateTime = new DateTime(mockR, langs);
|
|
176
|
+
|
|
177
|
+
assertEqual(dateTime.$r, mockR, "$r should be set");
|
|
178
|
+
assertEqual(dateTime.langs, langs, "langs should be set");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("should have predefined format options", () => {
|
|
182
|
+
const dateTime = createDateTime();
|
|
183
|
+
|
|
184
|
+
assertDeepEqual(dateTime.format.narrow, { weekday: "narrow" });
|
|
185
|
+
assertDeepEqual(dateTime.format.day, { day: "numeric" });
|
|
186
|
+
assertDeepEqual(dateTime.format.month, { month: "short" });
|
|
187
|
+
assertDeepEqual(dateTime.format.year, { year: "numeric" });
|
|
188
|
+
assertDeepEqual(dateTime.format.short, {
|
|
189
|
+
year: "numeric",
|
|
190
|
+
month: "short",
|
|
191
|
+
day: "numeric",
|
|
192
|
+
});
|
|
193
|
+
assertDeepEqual(dateTime.format.medium, {
|
|
194
|
+
year: "numeric",
|
|
195
|
+
month: "numeric",
|
|
196
|
+
day: "numeric",
|
|
197
|
+
});
|
|
198
|
+
assertDeepEqual(dateTime.format.weekday, { weekday: "short" });
|
|
199
|
+
assertDeepEqual(dateTime.format.long, {
|
|
200
|
+
year: "numeric",
|
|
201
|
+
month: "numeric",
|
|
202
|
+
day: "numeric",
|
|
203
|
+
weekday: "short",
|
|
204
|
+
hour: "numeric",
|
|
205
|
+
minute: "numeric",
|
|
206
|
+
second: "numeric",
|
|
207
|
+
});
|
|
208
|
+
assertDeepEqual(dateTime.format["date-latin"], {
|
|
209
|
+
month: "numeric",
|
|
210
|
+
day: "numeric",
|
|
211
|
+
numberingSystem: "latn",
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
suite("set_lang", () => {
|
|
217
|
+
test("should add a new language with valid config", () => {
|
|
218
|
+
const dateTime = createDateTime();
|
|
219
|
+
const newLang = {
|
|
220
|
+
localizeName: "de-DE",
|
|
221
|
+
timeZone: "Europe/Berlin",
|
|
222
|
+
first_day: 1,
|
|
223
|
+
time_zone_offset: 60,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
dateTime.set_lang("de", newLang);
|
|
227
|
+
assertDeepEqual(dateTime.langs.de, newLang);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("should log error and return early if localizeName is missing", () => {
|
|
231
|
+
const dateTime = createDateTime();
|
|
232
|
+
mockConsoleError();
|
|
233
|
+
|
|
234
|
+
const invalidLang = {
|
|
235
|
+
timeZone: "Europe/Berlin",
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
dateTime.set_lang("de", invalidLang);
|
|
239
|
+
|
|
240
|
+
assert(consoleErrorCalled, "console.error should have been called");
|
|
241
|
+
assertUndefined(dateTime.langs.de, "lang should not be added");
|
|
242
|
+
|
|
243
|
+
restoreConsole();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
suite("set_format", () => {
|
|
248
|
+
test("should merge custom format options", () => {
|
|
249
|
+
const dateTime = createDateTime();
|
|
250
|
+
const customFormat = {
|
|
251
|
+
custom: {
|
|
252
|
+
year: "numeric",
|
|
253
|
+
month: "long",
|
|
254
|
+
day: "numeric",
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
dateTime.set_format(customFormat);
|
|
259
|
+
assertDeepEqual(dateTime.format.custom, customFormat.custom);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("should preserve existing format options", () => {
|
|
263
|
+
const dateTime = createDateTime();
|
|
264
|
+
const customFormat = {
|
|
265
|
+
custom: {
|
|
266
|
+
year: "numeric",
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
dateTime.set_format(customFormat);
|
|
271
|
+
assertDefined(dateTime.format.short, "short format should still exist");
|
|
272
|
+
assertDefined(dateTime.format.long, "long format should still exist");
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
suite("formatLocal", () => {
|
|
277
|
+
const testDate = new Date("2024-03-15T10:30:00Z");
|
|
278
|
+
|
|
279
|
+
test("should format date with default locale from $r.lang", () => {
|
|
280
|
+
const dateTime = createDateTime();
|
|
281
|
+
const result = dateTime.formatLocal(testDate, "short");
|
|
282
|
+
|
|
283
|
+
assert(typeof result === "string", "result should be a string");
|
|
284
|
+
assert(
|
|
285
|
+
result.includes("2024") || result.includes("24"),
|
|
286
|
+
"result should contain year",
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("should format date with specified locale", () => {
|
|
291
|
+
const dateTime = createDateTime();
|
|
292
|
+
const result = dateTime.formatLocal(testDate, "short", "en");
|
|
293
|
+
|
|
294
|
+
assert(typeof result === "string", "result should be a string");
|
|
295
|
+
assertGreaterThan(result.length, 0, "result should not be empty");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("should format date with different format types", () => {
|
|
299
|
+
const dateTime = createDateTime();
|
|
300
|
+
|
|
301
|
+
const dayResult = dateTime.formatLocal(testDate, "day", "en");
|
|
302
|
+
assert(dayResult.includes("15"), "day format should include day number");
|
|
303
|
+
|
|
304
|
+
const yearResult = dateTime.formatLocal(testDate, "year", "en");
|
|
305
|
+
assert(yearResult.includes("2024"), "year format should include year");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("should warn and create default lang config if locale not found", () => {
|
|
309
|
+
const dateTime = createDateTime();
|
|
310
|
+
mockConsoleWarn();
|
|
311
|
+
|
|
312
|
+
const result = dateTime.formatLocal(testDate, "short", "unknown");
|
|
313
|
+
|
|
314
|
+
assert(consoleWarnCalled, "console.warn should have been called");
|
|
315
|
+
assertDefined(dateTime.langs.unknown, "unknown lang should be created");
|
|
316
|
+
assertEqual(dateTime.langs.unknown.localizeName, "unknown");
|
|
317
|
+
|
|
318
|
+
restoreConsole();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("should format with date-latin format", () => {
|
|
322
|
+
const dateTime = createDateTime();
|
|
323
|
+
const result = dateTime.formatLocal(testDate, "date-latin", "en");
|
|
324
|
+
|
|
325
|
+
assertMatch(result, /\d+\/\d+/, "result should match date pattern");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("should handle formatting errors gracefully", () => {
|
|
329
|
+
const dateTime = createDateTime();
|
|
330
|
+
mockConsoleError();
|
|
331
|
+
|
|
332
|
+
const invalidDate = "not a date";
|
|
333
|
+
const result = dateTime.formatLocal(invalidDate, "short", "en");
|
|
334
|
+
|
|
335
|
+
assertEqual(result, invalidDate, "should return original value on error");
|
|
336
|
+
|
|
337
|
+
restoreConsole();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
suite("add_days", () => {
|
|
342
|
+
test("should add positive days to a date", () => {
|
|
343
|
+
const dateTime = createDateTime();
|
|
344
|
+
const date = new Date("2024-03-15");
|
|
345
|
+
const result = dateTime.add_days(date, 5);
|
|
346
|
+
|
|
347
|
+
assertEqual(result.getDate(), 20);
|
|
348
|
+
assertEqual(result.getMonth(), 2); // March (0-indexed)
|
|
349
|
+
assertEqual(result.getFullYear(), 2024);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("should subtract days with negative value", () => {
|
|
353
|
+
const dateTime = createDateTime();
|
|
354
|
+
const date = new Date("2024-03-15");
|
|
355
|
+
const result = dateTime.add_days(date, -5);
|
|
356
|
+
|
|
357
|
+
assertEqual(result.getDate(), 10);
|
|
358
|
+
assertEqual(result.getMonth(), 2);
|
|
359
|
+
assertEqual(result.getFullYear(), 2024);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("should handle month boundary when adding days", () => {
|
|
363
|
+
const dateTime = createDateTime();
|
|
364
|
+
const date = new Date("2024-03-30");
|
|
365
|
+
const result = dateTime.add_days(date, 5);
|
|
366
|
+
|
|
367
|
+
assertEqual(result.getDate(), 4);
|
|
368
|
+
assertEqual(result.getMonth(), 3); // April
|
|
369
|
+
assertEqual(result.getFullYear(), 2024);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("should handle year boundary when adding days", () => {
|
|
373
|
+
const dateTime = createDateTime();
|
|
374
|
+
const date = new Date("2024-12-30");
|
|
375
|
+
const result = dateTime.add_days(date, 5);
|
|
376
|
+
|
|
377
|
+
assertEqual(result.getDate(), 4);
|
|
378
|
+
assertEqual(result.getMonth(), 0); // January
|
|
379
|
+
assertEqual(result.getFullYear(), 2025);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("should not mutate the original date", () => {
|
|
383
|
+
const dateTime = createDateTime();
|
|
384
|
+
const date = new Date("2024-03-15");
|
|
385
|
+
const originalDate = date.getDate();
|
|
386
|
+
|
|
387
|
+
dateTime.add_days(date, 5);
|
|
388
|
+
|
|
389
|
+
assertEqual(
|
|
390
|
+
date.getDate(),
|
|
391
|
+
originalDate,
|
|
392
|
+
"original date should not change",
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("should handle leap year", () => {
|
|
397
|
+
const dateTime = createDateTime();
|
|
398
|
+
const date = new Date("2024-02-28");
|
|
399
|
+
const result = dateTime.add_days(date, 1);
|
|
400
|
+
|
|
401
|
+
assertEqual(result.getDate(), 29);
|
|
402
|
+
assertEqual(result.getMonth(), 1); // February
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
suite("add_months", () => {
|
|
407
|
+
test("should add one month to a date", () => {
|
|
408
|
+
const dateTime = createDateTime();
|
|
409
|
+
const date = new Date("2024-01-15");
|
|
410
|
+
const result = dateTime.add_months(date, 1);
|
|
411
|
+
|
|
412
|
+
assertEqual(result.getMonth(), 1, "should be February (month 1)");
|
|
413
|
+
assertEqual(result.getDate(), 15, "should be day 15");
|
|
414
|
+
assertEqual(result.getFullYear(), 2024, "should be year 2024");
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("should add multiple months", () => {
|
|
418
|
+
const dateTime = createDateTime();
|
|
419
|
+
const date = new Date("2024-01-15");
|
|
420
|
+
const result = dateTime.add_months(date, 3);
|
|
421
|
+
|
|
422
|
+
assertEqual(result.getMonth(), 3, "should be April (month 3)");
|
|
423
|
+
assertEqual(result.getDate(), 15, "should be day 15");
|
|
424
|
+
assertEqual(result.getFullYear(), 2024, "should be year 2024");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("should handle month-end dates (31st to 29-day leap month)", () => {
|
|
428
|
+
const dateTime = createDateTime();
|
|
429
|
+
const date = new Date("2024-01-31");
|
|
430
|
+
const result = dateTime.add_months(date, 1);
|
|
431
|
+
|
|
432
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
433
|
+
assertMatch(formattedResult, /2\/29/, "should be Feb 29th (leap year)");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("should handle month-end dates (31st to 30-day month)", () => {
|
|
437
|
+
const dateTime = createDateTime();
|
|
438
|
+
const date = new Date("2024-03-31");
|
|
439
|
+
const result = dateTime.add_months(date, 1);
|
|
440
|
+
|
|
441
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
442
|
+
assertMatch(formattedResult, /4\/30/, "should be April 30th");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("should handle year boundary", () => {
|
|
446
|
+
const dateTime = createDateTime();
|
|
447
|
+
const date = new Date("2024-11-15");
|
|
448
|
+
const result = dateTime.add_months(date, 3);
|
|
449
|
+
|
|
450
|
+
assertEqual(result.getMonth(), 1, "should be February (month 1)");
|
|
451
|
+
assertEqual(result.getDate(), 15, "should be day 15");
|
|
452
|
+
assertEqual(result.getFullYear(), 2025, "should be year 2025");
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("should handle adding 0 months", () => {
|
|
456
|
+
const dateTime = createDateTime();
|
|
457
|
+
const date = new Date("2024-03-15");
|
|
458
|
+
const result = dateTime.add_months(date, 0);
|
|
459
|
+
|
|
460
|
+
assertEqual(result.getTime(), date.getTime(), "date should be unchanged");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("should preserve the day of month when possible", () => {
|
|
464
|
+
const dateTime = createDateTime();
|
|
465
|
+
const date = new Date("2024-01-15");
|
|
466
|
+
const result1 = dateTime.add_months(date, 1);
|
|
467
|
+
const result2 = dateTime.add_months(date, 2);
|
|
468
|
+
|
|
469
|
+
assertEqual(result1.getDate(), 15, "should preserve day 15 after 1 month");
|
|
470
|
+
assertEqual(result2.getDate(), 15, "should preserve day 15 after 2 months");
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("should handle leap year to non-leap year transition", () => {
|
|
474
|
+
const dateTime = createDateTime();
|
|
475
|
+
const date = new Date("2024-02-29");
|
|
476
|
+
const result = dateTime.add_months(date, 12);
|
|
477
|
+
|
|
478
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
479
|
+
assertMatch(formattedResult, /2\/28/, "should be Feb 28th (non-leap year)");
|
|
480
|
+
assertEqual(result.getFullYear(), 2025);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
suite("add_months_fa", () => {
|
|
485
|
+
test("should add one month to a date", () => {
|
|
486
|
+
const dateTime = createDateTimeFa();
|
|
487
|
+
const date = new Date("2024-01-15");
|
|
488
|
+
const result = dateTime.add_months(date, 1);
|
|
489
|
+
|
|
490
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
491
|
+
assertMatch(
|
|
492
|
+
formattedResult,
|
|
493
|
+
/11\/25/,
|
|
494
|
+
"should be month 11, day 25 in Persian calendar",
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("should add multiple months", () => {
|
|
499
|
+
const dateTime = createDateTimeFa();
|
|
500
|
+
const date = new Date("2024-01-15");
|
|
501
|
+
const result = dateTime.add_months(date, 3);
|
|
502
|
+
|
|
503
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
504
|
+
assertMatch(
|
|
505
|
+
formattedResult,
|
|
506
|
+
/1\/25/,
|
|
507
|
+
"should be month 1, day 25 in Persian calendar (year boundary crossed)",
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test("should handle month-end dates (31st to 29-day leap month)", () => {
|
|
512
|
+
const dateTime = createDateTimeFa();
|
|
513
|
+
const date = new Date("2027-02-18");
|
|
514
|
+
const result = dateTime.add_months(date, 1);
|
|
515
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
516
|
+
assertMatch(
|
|
517
|
+
formattedResult,
|
|
518
|
+
/12\/29/,
|
|
519
|
+
"should be month 12, day 29 in Persian calendar",
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("should handle month-end dates (31st to 30-day month)", () => {
|
|
524
|
+
const dateTime = createDateTimeFa();
|
|
525
|
+
const date = new Date("2024-09-21");
|
|
526
|
+
const result = dateTime.add_months(date, 1);
|
|
527
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
528
|
+
console.log(formattedResult);
|
|
529
|
+
assertMatch(
|
|
530
|
+
formattedResult,
|
|
531
|
+
/7\/30/,
|
|
532
|
+
"should be month 7, day 30 in Persian calendar",
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("should handle year boundary", () => {
|
|
537
|
+
const dateTime = createDateTimeFa();
|
|
538
|
+
const date = new Date("2024-11-15");
|
|
539
|
+
const result = dateTime.add_months(date, 3);
|
|
540
|
+
|
|
541
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
542
|
+
assertMatch(
|
|
543
|
+
formattedResult,
|
|
544
|
+
/11\/25/,
|
|
545
|
+
"should be month 11, day 25 in Persian calendar",
|
|
546
|
+
);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test("should handle adding 0 months", () => {
|
|
550
|
+
const dateTime = createDateTimeFa();
|
|
551
|
+
const date = new Date("2024-03-15");
|
|
552
|
+
const result = dateTime.add_months(date, 0);
|
|
553
|
+
|
|
554
|
+
assertEqual(result.getTime(), date.getTime(), "date should be unchanged");
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("should preserve the day of month when possible", () => {
|
|
558
|
+
const dateTime = createDateTimeFa();
|
|
559
|
+
let date = new Date("2024-01-15");
|
|
560
|
+
for (let i = 0; i < 100; i++) {
|
|
561
|
+
date = dateTime.add_months(date, 1);
|
|
562
|
+
|
|
563
|
+
const formattedResult = dateTime.formatLocal(date, "day");
|
|
564
|
+
assertEqual(
|
|
565
|
+
formattedResult,
|
|
566
|
+
"۲۵",
|
|
567
|
+
"should preserve day 25 after 1 month",
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("should handle leap year to non-leap year transition", () => {
|
|
573
|
+
const dateTime = createDateTimeFa();
|
|
574
|
+
const date = new Date("2024-03-29");
|
|
575
|
+
const result = dateTime.add_months(date, 12);
|
|
576
|
+
|
|
577
|
+
const formattedResult = dateTime.formatLocal(result, "date-latin");
|
|
578
|
+
assertMatch(
|
|
579
|
+
formattedResult,
|
|
580
|
+
/1\/10/,
|
|
581
|
+
"should be month 1, day 10 in Persian calendar",
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Summary
|
|
587
|
+
console.log(`\n${colors.yellow}=== Test Summary ===${colors.reset}`);
|
|
588
|
+
console.log(`Total: ${testCount} tests`);
|
|
589
|
+
console.log(`${colors.green}Passed: ${passCount}${colors.reset}`);
|
|
590
|
+
if (failCount > 0) {
|
|
591
|
+
console.log(`${colors.red}Failed: ${failCount}${colors.reset}`);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
} else {
|
|
594
|
+
console.log(`${colors.green}\n✓ All tests passed!${colors.reset}\n`);
|
|
595
|
+
process.exit(0);
|
|
596
|
+
}
|