1+ /**
2+ * Unit tests for IsoDate
3+ *
4+ * AI Usage:
5+ * - This code was originally generated using
6+ * 1. OpenAI/GPT OSS 120b on Roo Code
7+ * 2. Gemini 2.5 Flash and 2.5 Pro
8+ * then modified to fix incorrect implementations and fit project needs.
9+ * The first commit contains these corrections so that all code committed
10+ * works as designed.
11+ */
12+
13+ import { describe , test , expect } from '@jest/globals' ;
14+ import { IsoDate , isValidIsoDate } from './IsoDate' ;
15+
16+ describe ( 'IsoDate.parse – valid inputs' , ( ) => {
17+
18+ test ( 'full date with hyphens' , ( ) => {
19+ const str = '2025-01-01' ;
20+ expect ( isValidIsoDate ( str ) ) . toBeTruthy ( ) ;
21+ const d = IsoDate . parse ( str ) ;
22+ expect ( d . year ) . toBe ( 2025 ) ;
23+ expect ( d . month ) . toBe ( 1 ) ;
24+ expect ( d . day ) . toBe ( 1 ) ;
25+ expect ( d . toString ( ) ) . toBe ( '2025-01-01' ) ;
26+ expect ( d . isDate ( ) ) . toBeTruthy ( ) ;
27+ expect ( d . isMonth ( ) ) . toBeFalsy ( ) ;
28+ expect ( d . isYear ( ) ) . toBeFalsy ( )
29+ } ) ;
30+
31+ test ( 'year‑month' , ( ) => {
32+ const str = '2025-01' ;
33+ expect ( isValidIsoDate ( str ) ) . toBeTruthy ( ) ;
34+ const d = IsoDate . parse ( str ) ;
35+ expect ( d . year ) . toBe ( 2025 ) ;
36+ expect ( d . month ) . toBe ( 1 ) ;
37+ expect ( d . day ) . toBeUndefined ( ) ;
38+ expect ( d . toString ( ) ) . toBe ( '2025-01' ) ;
39+ expect ( d . isDate ( ) ) . toBeFalsy ( ) ;
40+ expect ( d . isMonth ( ) ) . toBeTruthy ( ) ;
41+ expect ( d . isYear ( ) ) . toBeFalsy ( )
42+ } ) ;
43+
44+ test ( 'year only' , ( ) => {
45+ const str = '2025' ;
46+ expect ( isValidIsoDate ( str ) ) . toBeTruthy ( ) ;
47+ const d = IsoDate . parse ( str ) ;
48+ expect ( d . year ) . toBe ( 2025 ) ;
49+ expect ( d . month ) . toBeUndefined ( ) ;
50+ expect ( d . day ) . toBeUndefined ( ) ;
51+ expect ( d . toString ( ) ) . toBe ( '2025' ) ;
52+ expect ( d . isDate ( ) ) . toBeFalsy ( ) ;
53+ expect ( d . isMonth ( ) ) . toBeFalsy ( ) ;
54+ expect ( d . isYear ( ) ) . toBeTruthy ( )
55+ } ) ;
56+
57+ test ( 'leap‑year February 29' , ( ) => {
58+ const str = '2024-02-29' ;
59+ expect ( isValidIsoDate ( str ) ) . toBeTruthy ( ) ;
60+ const d = IsoDate . parse ( str ) ;
61+ expect ( d . year ) . toBe ( 2024 ) ;
62+ expect ( d . month ) . toBe ( 2 ) ;
63+ expect ( d . day ) . toBe ( 29 ) ;
64+ expect ( d . toString ( ) ) . toBe ( '2024-02-29' ) ;
65+ } ) ;
66+
67+ test ( 'month‑day boundary' , ( ) => {
68+ const str = '2025-01-30' ;
69+ expect ( isValidIsoDate ( str ) ) . toBeTruthy ( ) ;
70+ const d = IsoDate . parse ( str ) ;
71+ expect ( d . year ) . toBe ( 2025 ) ;
72+ expect ( d . month ) . toBe ( 1 ) ;
73+ expect ( d . day ) . toBe ( 30 ) ;
74+ expect ( d . toString ( ) ) . toBe ( '2025-01-30' ) ;
75+ } ) ;
76+ } ) ;
77+
78+
79+ describe ( 'IsoDate.parse/toString/isMonth/isDate/isYear' , ( ) => {
80+ const tests : Array < { value : string ; isYear ?: boolean ; isMonth ?: boolean ; isDate ?: boolean ; isDatetime ?: boolean ; threw ?: RegExp ; } > = [
81+ // valid ISO Date specs
82+ { value : `2025` , isYear : true } ,
83+ { value : ` 2025 ` , isYear : true } ,
84+ { value : `2025-10` , isMonth : true } ,
85+ { value : `2024-02-29` , isDate : true } ,
86+ // invalid ISO Date specs
87+ { value : `25` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // year must be after 1000 and before 2500
88+ { value : `1899` , threw : / Y e a r o u t o f r a n g e / } , // year must be after 1900 and before 2100
89+ { value : `2500` , threw : / Y e a r o u t o f r a n g e / } , // year must be after 1900 and before 2100
90+ { value : `1/1/25` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // bad format
91+ { value : `abc` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // bad format
92+ { value : `202501` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // year+month without hyphen
93+ { value : `20250101` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // compact full date (properly rejected in this class)
94+ { value : `250101` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // two‑digit year
95+ { value : `-2025-04-31` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // leading hyphen
96+ { value : `-2025-04` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } , // leading hyphen
97+ { value : `01-01` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } ,
98+ { value : `2025--01` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } ,
99+ { value : `2025-01-01T014:00:00:00Z` , threw : / I n v a l i d c a l e n d a r d a t e f o r m a t / } ,
100+ { value : `2025-13-01` , threw : / M o n t h o u t o f r a n g e / } ,
101+ { value : `2025-02-29` , threw : / D a y o u t o f r a n g e / } , // not a leap year
102+ { value : `2025-02-30` , threw : / D a y o u t o f r a n g e / } ,
103+ { value : `2025-04-31` , threw : / D a y o u t o f r a n g e / } ,
104+ ] ;
105+
106+ tests . forEach ( ( { value, isYear = false , isMonth = false , isDate = false , threw = '' } ) => {
107+ test ( `properly determines if ${ value } is a ${ isYear ? 'year' : '' } ${ isMonth ? 'month' : '' } ${ isDate ? 'date' : '' } ${ threw ? 'error' : '' } ` , ( ) => {
108+ if ( threw ) {
109+ expect ( ( ) => IsoDate . parse ( value ) ) . toThrow ( threw ) ;
110+ }
111+ else {
112+ const isoDate = IsoDate . parse ( value ) ;
113+ if ( isYear ) {
114+ expect ( isoDate . isYear ( ) ) . toBeTruthy ( ) ;
115+ expect ( isoDate . isMonth ( ) ) . toBeFalsy ( ) ;
116+ expect ( isoDate . isDate ( ) ) . toBeFalsy ( ) ;
117+ expect ( isoDate . isDatetime ( ) ) . toBeFalsy ( ) ;
118+ }
119+ else if ( isMonth ) {
120+ expect ( isoDate . isYear ( ) ) . toBeFalsy ( ) ;
121+ expect ( isoDate . isMonth ( ) ) . toBeTruthy ( ) ;
122+ expect ( isoDate . isDate ( ) ) . toBeFalsy ( ) ;
123+ expect ( isoDate . isDatetime ( ) ) . toBeFalsy ( ) ;
124+ }
125+ else if ( isDate ) {
126+ expect ( isoDate . isYear ( ) ) . toBeFalsy ( ) ;
127+ expect ( isoDate . isMonth ( ) ) . toBeFalsy ( ) ;
128+ expect ( isoDate . isDate ( ) ) . toBeTruthy ( ) ;
129+ expect ( isoDate . isDatetime ( ) ) . toBeFalsy ( ) ;
130+ }
131+ expect ( isoDate . toString ( ) ) . toBe ( value . trim ( ) ) ;
132+ }
133+ } ) ;
134+ } ) ;
135+ } ) ;
136+
137+
138+ describe ( 'IsoDate.daysInMonth()' , ( ) => {
139+ const tests : Array < { year : number ; month : number , expected : number ; } > = [
140+ { year : 2025 , month : 1 , expected : 31 } ,
141+ { year : 2025 , month : 2 , expected : 28 } ,
142+ { year : 2025 , month : 3 , expected : 31 } ,
143+ { year : 2025 , month : 4 , expected : 30 } ,
144+ { year : 2025 , month : 5 , expected : 31 } ,
145+ { year : 2025 , month : 6 , expected : 30 } ,
146+ { year : 2025 , month : 7 , expected : 31 } ,
147+ { year : 2025 , month : 8 , expected : 31 } ,
148+ { year : 2025 , month : 9 , expected : 30 } ,
149+ { year : 2025 , month : 10 , expected : 31 } ,
150+ { year : 2025 , month : 11 , expected : 30 } ,
151+ { year : 2025 , month : 12 , expected : 31 } ,
152+ { year : 2024 , month : 2 , expected : 29 } , // leap year
153+ ] ;
154+
155+ tests . forEach ( ( { year, month, expected } ) => {
156+ test ( `properly calculates ${ year } -${ month } to have ${ expected } days` , ( ) => {
157+ const days = IsoDate . daysInMonth ( year , month ) ;
158+ expect ( days ) . toBe ( expected ) ;
159+ } ) ;
160+ } ) ;
161+ } ) ;
0 commit comments