@aurodesignsystem/auro-library 5.12.2 → 5.13.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,284 @@
1
+ import { expect } from "@open-wc/testing";
2
+ import { dateFormatter } from "./dateFormatter.mjs";
3
+
4
+ describe("dateFormatter.parseDate", () => {
5
+ describe("null / undefined / empty input", () => {
6
+ it("returns undefined for null", () => {
7
+ expect(dateFormatter.parseDate(null)).to.be.undefined;
8
+ });
9
+
10
+ it("returns undefined for undefined", () => {
11
+ expect(dateFormatter.parseDate(undefined)).to.be.undefined;
12
+ });
13
+
14
+ it("returns undefined for empty string", () => {
15
+ expect(dateFormatter.parseDate("")).to.be.undefined;
16
+ });
17
+ });
18
+ });
19
+
20
+ describe("dateFormatter.toNorthAmericanFormat", () => {
21
+ it("returns the string unchanged for mm/dd/yyyy", () => {
22
+ expect(
23
+ dateFormatter.toNorthAmericanFormat("01/15/2024", "mm/dd/yyyy"),
24
+ ).to.equal("01/15/2024");
25
+ });
26
+
27
+ it("reorders dd/mm/yyyy to mm/dd/yyyy", () => {
28
+ expect(
29
+ dateFormatter.toNorthAmericanFormat("15/01/2024", "dd/mm/yyyy"),
30
+ ).to.equal("01/15/2024");
31
+ });
32
+
33
+ it("reorders yyyy-mm-dd to mm/dd/yyyy", () => {
34
+ expect(
35
+ dateFormatter.toNorthAmericanFormat("2024-01-15", "yyyy-mm-dd"),
36
+ ).to.equal("01/15/2024");
37
+ });
38
+
39
+ it("throws when the date string cannot be parsed", () => {
40
+ expect(() =>
41
+ dateFormatter.toNorthAmericanFormat(null, "dd/mm/yyyy"),
42
+ ).to.throw(Error);
43
+ });
44
+ });
45
+
46
+ describe("dateFormatter.stringToDateInstance", () => {
47
+ describe("ISO input", () => {
48
+ const isoCases = [
49
+ { input: "2024-01-15", description: "typical ISO date" },
50
+ { input: "1999-12-31", description: "end of year ISO date" },
51
+ ];
52
+
53
+ isoCases.forEach(({ input, description }) => {
54
+ it(`creates Date directly from ISO string for: ${description}`, () => {
55
+ const result = dateFormatter.stringToDateInstance(input);
56
+
57
+ expect(result).to.be.instanceOf(Date);
58
+ expect(result.toISOString().slice(0, 10)).to.equal(input);
59
+ });
60
+ });
61
+ });
62
+
63
+ describe("non-ISO input with explicit format", () => {
64
+ it("returns a Date using parseDate result for dd/mm/yyyy format", () => {
65
+ const inputStr = "15/01/2024";
66
+ const expectedYear = 2024;
67
+ const expectedMonth = 1; // January (1-based)
68
+ const expectedDay = 15;
69
+
70
+ const result = dateFormatter.stringToDateInstance(inputStr, "dd/mm/yyyy");
71
+
72
+ expect(result).to.be.instanceOf(Date);
73
+ expect(result.getFullYear()).to.equal(expectedYear);
74
+ expect(result.getMonth()).to.equal(expectedMonth - 1);
75
+ expect(result.getDate()).to.equal(expectedDay);
76
+ });
77
+ });
78
+
79
+ describe("default format behaviour", () => {
80
+ it("uses default format parameter when format is not provided", () => {
81
+ const inputStr = "2024-02-29";
82
+
83
+ const result = dateFormatter.stringToDateInstance(inputStr);
84
+
85
+ expect(result).to.be.instanceOf(Date);
86
+ expect(result.getFullYear()).to.equal(2024);
87
+ expect(result.getMonth()).to.equal(1);
88
+ expect(result.getDate()).to.equal(29);
89
+ });
90
+ });
91
+
92
+ describe("month adjustment", () => {
93
+ it("correctly adjusts month from 1-based to 0-based when creating Date", () => {
94
+ const inputStr = "2024-12-31";
95
+
96
+ const result = dateFormatter.stringToDateInstance(inputStr);
97
+
98
+ expect(result.getFullYear()).to.equal(2024);
99
+ expect(result.getMonth()).to.equal(11);
100
+ expect(result.getDate()).to.equal(31);
101
+ });
102
+ });
103
+ });
104
+
105
+ describe("dateFormatter.isValidDate", () => {
106
+ describe("valid date inputs", () => {
107
+ const validCases = [
108
+ {
109
+ args: ["2024-02-29"],
110
+ description: "valid leap day ISO (default format)",
111
+ },
112
+ {
113
+ args: ["1999-12-31", "yyyy-mm-dd"],
114
+ description: "valid ISO with explicit format",
115
+ },
116
+ {
117
+ args: ["12/31/1999", "mm/dd/yyyy"],
118
+ description: "valid mm/dd/yyyy",
119
+ },
120
+ {
121
+ args: ["31-01-2020", "dd-mm-yyyy"],
122
+ description: "valid dd-mm-yyyy",
123
+ },
124
+ {
125
+ args: ["01/15/01", "mm/dd/yy"],
126
+ description: "2-digit year below 50 normalizes to 20xx (01 → 2001)",
127
+ },
128
+ {
129
+ args: ["12/31/99", "mm/dd/yy"],
130
+ description: "2-digit year >= 50 normalizes to 19xx (99 → 1999)",
131
+ },
132
+ ];
133
+
134
+ validCases.forEach(({ args, description }) => {
135
+ it(`returns true for: ${description}`, () => {
136
+ const result = dateFormatter.isValidDate(...args);
137
+ expect(result, `Expected ${JSON.stringify(args)} to be a valid date`).to
138
+ .be.true;
139
+ });
140
+ });
141
+ });
142
+
143
+ describe("invalid date inputs", () => {
144
+ const invalidCases = [
145
+ {
146
+ args: ["02/2024", "mm/yyyy"],
147
+ description: "short format mm/yyyy",
148
+ },
149
+ {
150
+ args: ["2024/02", "yyyy/mm"],
151
+ description: "short format yyyy/mm",
152
+ },
153
+ {
154
+ args: ["2024", "yyyy"],
155
+ description: "short format yyyy",
156
+ },
157
+ { args: ["2024-02-30"], description: "invalid ISO day" },
158
+ {
159
+ args: ["12/31/1999", "yyyy-mm-dd"],
160
+ description: "non-ISO string with yyyy-mm-dd format",
161
+ },
162
+ {
163
+ args: ["02/30/2024", "mm/dd/yyyy"],
164
+ description: "invalid mm/dd/yyyy with impossible day",
165
+ },
166
+ {
167
+ args: [null, "mm/dd/yyyy"],
168
+ description: "non-string date value",
169
+ },
170
+ {
171
+ args: ["13/2024", "mm/yyyy"],
172
+ description: "invalid mm/yyyy with month out of range",
173
+ },
174
+ {
175
+ args: ["00/24", "mm/yy"],
176
+ description: "invalid mm/yy with month below range",
177
+ },
178
+ {
179
+ args: ["02/24", "mm/yy"],
180
+ description: "invalid mm/yy",
181
+ },
182
+ {
183
+ args: ["2024/13", "yyyy/mm"],
184
+ description: "invalid yyyy/mm with month out of range",
185
+ },
186
+ {
187
+ args: ["ab24", "yyyy"],
188
+ description: "invalid yyyy with non-numeric value",
189
+ },
190
+ {
191
+ args: ["2a", "yy"],
192
+ description: "invalid yy with non-numeric value",
193
+ },
194
+ {
195
+ args: ["24", "yy"],
196
+ description: "invalid yy",
197
+ },
198
+ {
199
+ args: ["00", "mm"],
200
+ description: "invalid mm below range",
201
+ },
202
+ {
203
+ args: ["12", "mm"],
204
+ description: "invalid mm",
205
+ },
206
+ {
207
+ args: ["00", "dd"],
208
+ description: "invalid dd below range",
209
+ },
210
+ {
211
+ args: ["31", "dd"],
212
+ description: "invalid dd",
213
+ },
214
+ {
215
+ args: ["32", "dd"],
216
+ description: "invalid dd above range",
217
+ },
218
+ ];
219
+
220
+ invalidCases.forEach(({ args, description }) => {
221
+ it(`returns false for: ${description}`, () => {
222
+ const result = dateFormatter.isValidDate(...args);
223
+ expect(result, `Expected ${JSON.stringify(args)} to be an invalid date`)
224
+ .to.be.false;
225
+ });
226
+ });
227
+ });
228
+ });
229
+
230
+ describe("dateFormatter.toISOFormatString", () => {
231
+ describe("valid Date inputs", () => {
232
+ const validCases = [
233
+ {
234
+ input: new Date("2024/01/15"),
235
+ expected: "2024-01-15",
236
+ description: "typical date",
237
+ },
238
+ {
239
+ input: new Date("1999/12/31"),
240
+ expected: "1999-12-31",
241
+ description: "end of year",
242
+ },
243
+ {
244
+ input: new Date("2024/02/29"),
245
+ expected: "2024-02-29",
246
+ description: "leap day",
247
+ },
248
+ {
249
+ input: new Date("2000/01/01"),
250
+ expected: "2000-01-01",
251
+ description: "first day of millennium",
252
+ },
253
+ {
254
+ input: new Date("2024/12/31"),
255
+ expected: "2024-12-31",
256
+ description: "last day of year",
257
+ },
258
+ ];
259
+
260
+ validCases.forEach(({ input, expected, description }) => {
261
+ it(`returns "${expected}" for: ${description}`, () => {
262
+ const result = dateFormatter.toISOFormatString(input);
263
+ expect(result).to.equal(expected);
264
+ });
265
+ });
266
+ });
267
+
268
+ describe("invalid inputs", () => {
269
+ const invalidCases = [
270
+ { input: new Date("not-a-date"), description: "invalid Date instance" },
271
+ { input: "2024-01-15", description: "string input" },
272
+ { input: null, description: "null input" },
273
+ { input: undefined, description: "undefined input" },
274
+ { input: 20240115, description: "number input" },
275
+ { input: {}, description: "plain object input" },
276
+ ];
277
+
278
+ invalidCases.forEach(({ input, description }) => {
279
+ it(`throws for: ${description}`, () => {
280
+ expect(() => dateFormatter.toISOFormatString(input)).to.throw(Error);
281
+ });
282
+ });
283
+ });
284
+ });
@@ -3,12 +3,11 @@ import { AuroDateUtilitiesBase } from "./baseDateUtilities.mjs";
3
3
  import { dateFormatter } from "./dateFormatter.mjs";
4
4
 
5
5
  export class AuroDateUtilities extends AuroDateUtilitiesBase {
6
-
7
6
  /**
8
7
  * Returns the current century.
9
8
  * @returns {String} The current century.
10
9
  */
11
- getCentury () {
10
+ getCentury() {
12
11
  return String(new Date().getFullYear()).slice(0, 2);
13
12
  }
14
13
 
@@ -17,14 +16,12 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
17
16
  * @param {String} year - The year to convert to four digits.
18
17
  * @returns {String} The four digit year.
19
18
  */
20
- getFourDigitYear (year) {
21
-
19
+ getFourDigitYear(year) {
22
20
  const strYear = String(year).trim();
23
21
  return strYear.length <= 2 ? this.getCentury() + strYear : strYear;
24
22
  }
25
23
 
26
24
  constructor() {
27
-
28
25
  super();
29
26
 
30
27
  /**
@@ -33,7 +30,8 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
33
30
  * @param {Object} date2 - Second date to compare.
34
31
  * @returns {Boolean} Returns true if the dates match.
35
32
  */
36
- this.datesMatch = (date1, date2) => new Date(date1).getTime() === new Date(date2).getTime();
33
+ this.datesMatch = (date1, date2) =>
34
+ new Date(date1).getTime() === new Date(date2).getTime();
37
35
 
38
36
  /**
39
37
  * Returns true if value passed in is a valid date.
@@ -42,54 +40,41 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
42
40
  * @returns {Boolean}
43
41
  */
44
42
  this.validDateStr = (date, format) => {
45
-
46
43
  // The length we expect the date string to be
47
- const dateStrLength = format.length;
44
+ const dateStrLength = format?.length || 0;
48
45
 
49
46
  // Guard Clause: Date and format are defined
50
47
  if (typeof date === "undefined" || typeof format === "undefined") {
51
- throw new Error('AuroDatepickerUtilities | validateDateStr: Date and format are required');
48
+ throw new Error(
49
+ "AuroDatepickerUtilities | validateDateStr: Date and format are required",
50
+ );
52
51
  }
53
52
 
54
53
  // Guard Clause: Date should be of type string
55
54
  if (typeof date !== "string") {
56
- throw new Error('AuroDatepickerUtilities | validateDateStr: Date must be a string');
55
+ throw new Error(
56
+ "AuroDatepickerUtilities | validateDateStr: Date must be a string",
57
+ );
57
58
  }
58
59
 
59
60
  // Guard Clause: Format should be of type string
60
61
  if (typeof format !== "string") {
61
- throw new Error('AuroDatepickerUtilities | validateDateStr: Format must be a string');
62
+ throw new Error(
63
+ "AuroDatepickerUtilities | validateDateStr: Format must be a string",
64
+ );
62
65
  }
63
66
 
64
67
  // Guard Clause: Length is what we expect it to be
65
68
  if (date.length !== dateStrLength) {
66
69
  return false;
67
- };
68
-
69
- // Get a formatted date string and parse it
70
- const dateParts = dateFormatter.parseDate(date, format);
71
-
72
- // Guard Clause: Date parse succeeded
73
- if (!dateParts) {
74
- return false;
75
70
  }
76
71
 
77
- // Create the expected date string based on the date parts
78
- const expectedDateStr = `${dateParts.month}/${dateParts.day || "01"}/${this.getFourDigitYear(dateParts.year)}`;
79
-
80
- // Generate a date object that we will extract a string date from to compare to the passed in date string
81
- const dateObj = new Date(this.getFourDigitYear(dateParts.year), dateParts.month - 1, dateParts.day || 1);
82
-
83
- // Get the date string of the date object we created from the string date
84
- const actualDateStr = dateFormatter.getDateAsString(dateObj, "en-US");
85
-
86
- // Guard Clause: Generated date matches date string input
87
- if (expectedDateStr !== actualDateStr) {
72
+ // Get a formatted date string and parse and validate it
73
+ try {
74
+ return Boolean(dateFormatter.parseDate(date, format));
75
+ } catch (error) {
88
76
  return false;
89
77
  }
90
-
91
- // If we passed all other checks, we can assume the date is valid
92
- return true;
93
78
  };
94
79
 
95
80
  /**
@@ -99,10 +84,11 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
99
84
  * @returns {boolean}
100
85
  */
101
86
  this.dateAndFormatMatch = (value, format) => {
102
-
103
87
  // Ensure we have both values we need to do the comparison
104
88
  if (!value || !format) {
105
- throw new Error('AuroFormValidation | dateFormatMatch: value and format are required');
89
+ throw new Error(
90
+ "AuroFormValidation | dateFormatMatch: value and format are required",
91
+ );
106
92
  }
107
93
 
108
94
  // If the lengths are different, they cannot match
@@ -111,11 +97,10 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
111
97
  }
112
98
 
113
99
  // Get the parts of the date
114
- const dateParts = dateFormatter.parseDate(value, format);
100
+ const dateParts = dateFormatter.getDateParts(value, format);
115
101
 
116
102
  // Validator for day
117
103
  const dayValueIsValid = (day) => {
118
-
119
104
  // Guard clause: if there is no day in the dateParts, we can ignore this check.
120
105
  if (!dateParts.day) {
121
106
  return true;
@@ -131,7 +116,9 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
131
116
 
132
117
  // Guard clause: ensure day is a valid integer
133
118
  if (Number.isNaN(numDay)) {
134
- throw new Error('AuroDatepickerUtilities | dayValueIsValid: Unable to parse day value integer');
119
+ throw new Error(
120
+ "AuroDatepickerUtilities | dayValueIsValid: Unable to parse day value integer",
121
+ );
135
122
  }
136
123
 
137
124
  // Guard clause: ensure day is within the valid range
@@ -145,6 +132,10 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
145
132
 
146
133
  // Validator for month
147
134
  const monthValueIsValid = (month) => {
135
+ // Guard clause: if there is no month in the dateParts, we can ignore this check.
136
+ if (!dateParts.month) {
137
+ return true;
138
+ }
148
139
 
149
140
  // Guard clause: ensure month exists.
150
141
  if (!month) {
@@ -156,7 +147,9 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
156
147
 
157
148
  // Guard clause: ensure month is a valid integer
158
149
  if (Number.isNaN(numMonth)) {
159
- throw new Error('AuroDatepickerUtilities | monthValueIsValid: Unable to parse month value integer');
150
+ throw new Error(
151
+ "AuroDatepickerUtilities | monthValueIsValid: Unable to parse month value integer",
152
+ );
160
153
  }
161
154
 
162
155
  // Guard clause: ensure month is within the valid range
@@ -170,6 +163,10 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
170
163
 
171
164
  // Validator for year
172
165
  const yearIsValid = (_year) => {
166
+ // Guard clause: if there is no year in the dateParts, we can ignore this check.
167
+ if (!dateParts.year) {
168
+ return true;
169
+ }
173
170
 
174
171
  // Guard clause: ensure year exists.
175
172
  if (!_year) {
@@ -184,7 +181,9 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
184
181
 
185
182
  // Guard clause: ensure year is a valid integer
186
183
  if (Number.isNaN(numYear)) {
187
- throw new Error('AuroDatepickerUtilities | yearValueIsValid: Unable to parse year value integer');
184
+ throw new Error(
185
+ "AuroDatepickerUtilities | yearValueIsValid: Unable to parse year value integer",
186
+ );
188
187
  }
189
188
 
190
189
  // Guard clause: ensure year is within the valid range
@@ -200,7 +199,7 @@ export class AuroDateUtilities extends AuroDateUtilitiesBase {
200
199
  const checks = [
201
200
  monthValueIsValid(dateParts.month),
202
201
  dayValueIsValid(dateParts.day),
203
- yearIsValid(dateParts.year)
202
+ yearIsValid(dateParts.year),
204
203
  ];
205
204
 
206
205
  // If any of the checks failed, the date format does not match and the result is invalid
@@ -0,0 +1,80 @@
1
+ import { expect } from "@open-wc/testing";
2
+ import { AuroDateUtilities } from "./dateUtilities.mjs";
3
+
4
+ const utils = new AuroDateUtilities();
5
+
6
+ describe("AuroDateUtilities.validDateStr", () => {
7
+ it("throws when date is undefined", () => {
8
+ expect(() => utils.validDateStr(undefined, "mm/dd/yyyy")).to.throw(Error);
9
+ });
10
+
11
+ it("throws when format is undefined", () => {
12
+ expect(() => utils.validDateStr("01/15/2024", undefined)).to.throw(Error);
13
+ });
14
+
15
+ it("throws when date is not a string", () => {
16
+ expect(() => utils.validDateStr(20240115, "mm/dd/yyyy")).to.throw(Error);
17
+ });
18
+
19
+ it("returns false when date length does not match format length", () => {
20
+ expect(utils.validDateStr("01/15", "mm/dd/yyyy")).to.be.false;
21
+ });
22
+
23
+ it("returns true for a valid date matching its format", () => {
24
+ expect(utils.validDateStr("01/15/2024", "mm/dd/yyyy")).to.be.true;
25
+ });
26
+
27
+ it("returns false for an invalid calendar date (Feb 30)", () => {
28
+ expect(utils.validDateStr("02/30/2024", "mm/dd/yyyy")).to.be.false;
29
+ });
30
+ });
31
+
32
+ describe("AuroDateUtilities.dateAndFormatMatch", () => {
33
+ it("throws when value is falsy", () => {
34
+ expect(() => utils.dateAndFormatMatch(null, "mm/dd/yyyy")).to.throw(Error);
35
+ });
36
+
37
+ it("throws when format is falsy", () => {
38
+ expect(() => utils.dateAndFormatMatch("01/15/2024", null)).to.throw(Error);
39
+ });
40
+
41
+ it("returns false when value length differs from format length", () => {
42
+ expect(utils.dateAndFormatMatch("01/15", "mm/dd/yyyy")).to.be.false;
43
+ });
44
+
45
+ it("returns true for a value that matches the format", () => {
46
+ expect(utils.dateAndFormatMatch("01/15/2024", "mm/dd/yyyy")).to.be.true;
47
+ });
48
+
49
+ it("returns false when month is below range", () => {
50
+ expect(utils.dateAndFormatMatch("00/15/2024", "mm/dd/yyyy")).to.be.false;
51
+ });
52
+
53
+ it("returns false when month is above range", () => {
54
+ expect(utils.dateAndFormatMatch("13/15/2024", "mm/dd/yyyy")).to.be.false;
55
+ });
56
+
57
+ it("returns false when day is below range", () => {
58
+ expect(utils.dateAndFormatMatch("01/00/2024", "mm/dd/yyyy")).to.be.false;
59
+ });
60
+
61
+ it("returns false when day is above range", () => {
62
+ expect(utils.dateAndFormatMatch("01/32/2024", "mm/dd/yyyy")).to.be.false;
63
+ });
64
+
65
+ it("returns false when year is below range", () => {
66
+ expect(utils.dateAndFormatMatch("01/15/1899", "mm/dd/yyyy")).to.be.false;
67
+ });
68
+
69
+ it("returns false when year is above range", () => {
70
+ expect(utils.dateAndFormatMatch("01/15/2401", "mm/dd/yyyy")).to.be.false;
71
+ });
72
+
73
+ it("skips day check when format has no day component", () => {
74
+ expect(utils.dateAndFormatMatch("01/2024", "mm/yyyy")).to.be.true;
75
+ });
76
+
77
+ it("skips month check when format has no month component", () => {
78
+ expect(utils.dateAndFormatMatch("15/2024", "dd/yyyy")).to.be.true;
79
+ });
80
+ });