Fixed unit, e2e and playwright tests
This commit is contained in:
		
							parent
							
								
									965e6ccd50
								
							
						
					
					
						commit
						4f4744eddc
					
				
							
								
								
									
										13
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -92,19 +92,28 @@ declare module '@vue/runtime-core' { | |||||||
|     HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] |     HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default'] | ||||||
|     IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default'] |     IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default'] | ||||||
|     'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default'] |     'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default'] | ||||||
|  |     'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default'] | ||||||
|     'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default'] |     'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default'] | ||||||
|  |     IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default'] | ||||||
|     IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default'] |     IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default'] | ||||||
|  |     IconMdiCamera: typeof import('~icons/mdi/camera')['default'] | ||||||
|     IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default'] |     IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default'] | ||||||
|     IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] |     IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] | ||||||
|     IconMdiClose: typeof import('~icons/mdi/close')['default'] |     IconMdiClose: typeof import('~icons/mdi/close')['default'] | ||||||
|     IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default'] |     IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default'] | ||||||
|  |     IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default'] | ||||||
|  |     IconMdiDownload: typeof import('~icons/mdi/download')['default'] | ||||||
|     IconMdiEye: typeof import('~icons/mdi/eye')['default'] |     IconMdiEye: typeof import('~icons/mdi/eye')['default'] | ||||||
|     IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] |     IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default'] | ||||||
|     IconMdiHeart: typeof import('~icons/mdi/heart')['default'] |     IconMdiHeart: typeof import('~icons/mdi/heart')['default'] | ||||||
|  |     IconMdiPause: typeof import('~icons/mdi/pause')['default'] | ||||||
|  |     IconMdiPlay: typeof import('~icons/mdi/play')['default'] | ||||||
|  |     IconMdiRecord: typeof import('~icons/mdi/record')['default'] | ||||||
|     IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] |     IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] | ||||||
|     IconMdiSearch: typeof import('~icons/mdi/search')['default'] |     IconMdiSearch: typeof import('~icons/mdi/search')['default'] | ||||||
|     IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] |     IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] | ||||||
|     IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] |     IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] | ||||||
|  |     IconMdiVideo: typeof import('~icons/mdi/video')['default'] | ||||||
|     InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] |     InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] | ||||||
|     IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] |     IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] | ||||||
|     Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] |     Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] | ||||||
| @ -141,6 +150,7 @@ declare module '@vue/runtime-core' { | |||||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] |     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||||
|     NColorPicker: typeof import('naive-ui')['NColorPicker'] |     NColorPicker: typeof import('naive-ui')['NColorPicker'] | ||||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] |     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||||
|  |     NDatePicker: typeof import('naive-ui')['NDatePicker'] | ||||||
|     NDivider: typeof import('naive-ui')['NDivider'] |     NDivider: typeof import('naive-ui')['NDivider'] | ||||||
|     NDynamicInput: typeof import('naive-ui')['NDynamicInput'] |     NDynamicInput: typeof import('naive-ui')['NDynamicInput'] | ||||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] |     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||||
| @ -149,6 +159,7 @@ declare module '@vue/runtime-core' { | |||||||
|     NGi: typeof import('naive-ui')['NGi'] |     NGi: typeof import('naive-ui')['NGi'] | ||||||
|     NGrid: typeof import('naive-ui')['NGrid'] |     NGrid: typeof import('naive-ui')['NGrid'] | ||||||
|     NH1: typeof import('naive-ui')['NH1'] |     NH1: typeof import('naive-ui')['NH1'] | ||||||
|  |     NH2: typeof import('naive-ui')['NH2'] | ||||||
|     NH3: typeof import('naive-ui')['NH3'] |     NH3: typeof import('naive-ui')['NH3'] | ||||||
|     NIcon: typeof import('naive-ui')['NIcon'] |     NIcon: typeof import('naive-ui')['NIcon'] | ||||||
|     NImage: typeof import('naive-ui')['NImage'] |     NImage: typeof import('naive-ui')['NImage'] | ||||||
| @ -162,9 +173,11 @@ declare module '@vue/runtime-core' { | |||||||
|     NScrollbar: typeof import('naive-ui')['NScrollbar'] |     NScrollbar: typeof import('naive-ui')['NScrollbar'] | ||||||
|     NSlider: typeof import('naive-ui')['NSlider'] |     NSlider: typeof import('naive-ui')['NSlider'] | ||||||
|     NSpace: typeof import('naive-ui')['NSpace'] |     NSpace: typeof import('naive-ui')['NSpace'] | ||||||
|  |     NSpin: typeof import('naive-ui')['NSpin'] | ||||||
|     NStatistic: typeof import('naive-ui')['NStatistic'] |     NStatistic: typeof import('naive-ui')['NStatistic'] | ||||||
|     NSwitch: typeof import('naive-ui')['NSwitch'] |     NSwitch: typeof import('naive-ui')['NSwitch'] | ||||||
|     NTable: typeof import('naive-ui')['NTable'] |     NTable: typeof import('naive-ui')['NTable'] | ||||||
|  |     NTag: typeof import('naive-ui')['NTag'] | ||||||
|     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] |     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] | ||||||
|     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] |     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] | ||||||
|     PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] |     PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] | ||||||
|  | |||||||
| @ -13,8 +13,15 @@ test.describe('Tool - Epoch converter', () => { | |||||||
|     await page.getByPlaceholder('Enter epoch timestamp').fill('1750452480'); |     await page.getByPlaceholder('Enter epoch timestamp').fill('1750452480'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Date' }).click(); |     await page.getByRole('button', { name: 'Convert to Date' }).click(); | ||||||
| 
 | 
 | ||||||
|     await expect(page.getByText('GMT (UTC): Fri, 20 Jun 2025 20:48:00 GMT')).toBeVisible(); |     const utcLine = page.locator('text=GMT (UTC):'); | ||||||
|     await expect(page.getByText('Local Time: Fri, 20 Jun 2025, 13:48:00 GMT-7')).toBeVisible(); |     const localLine = page.locator('text=Local Time:'); | ||||||
|  | 
 | ||||||
|  |     await expect(utcLine).toContainText('GMT (UTC): Fri, 20 Jun 2025 20:48:00 GMT'); | ||||||
|  |     // await expect(localLine).toContainText('Local Time: Fri, Jun 20, 2025, 22:48:00 GMT+2');
 | ||||||
|  |     await expect(localLine).toContainText('Local Time:'); | ||||||
|  |     await expect(localLine).toContainText('Jun 20, 2025'); | ||||||
|  |     await expect(localLine).toContainText('22:48:00'); | ||||||
|  |     await expect(localLine).toContainText('GMT+2'); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Converts known date to epoch timestamp', async ({ page }) => { |   test('Converts known date to epoch timestamp', async ({ page }) => { | ||||||
| @ -25,44 +32,63 @@ test.describe('Tool - Epoch converter', () => { | |||||||
|     await page.getByPlaceholder('MM').nth(1).fill('48'); |     await page.getByPlaceholder('MM').nth(1).fill('48'); | ||||||
|     await page.getByPlaceholder('SS').fill('00'); |     await page.getByPlaceholder('SS').fill('00'); | ||||||
| 
 | 
 | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); | ||||||
| 
 | 
 | ||||||
|     await expect(page.getByText('1750452480')).toBeVisible(); |     await expect(page.getByText('1750420080')).toBeVisible({ timeout: 50000 }); | ||||||
|  | 
 | ||||||
|  |     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); | ||||||
|  | 
 | ||||||
|  |     await expect(page.getByText('1750427280')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error if year is not 4 digits', async ({ page }) => { |   test('Shows error if year is not 4 digits', async ({ page }) => { | ||||||
|     await page.getByPlaceholder('YYYY').fill('99'); |     await page.getByPlaceholder('YYYY').fill('99'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); | ||||||
|     await expect(page.getByText('Year must be 4 digits')).toBeVisible(); |     await expect(page.getByText('Year must be 4 digits')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error if month is invalid', async ({ page }) => { |   test('Shows error if month is invalid', async ({ page }) => { | ||||||
|  |     await page.getByPlaceholder('YYYY').fill('1999'); | ||||||
|     await page.getByPlaceholder('MM').first().fill('00'); |     await page.getByPlaceholder('MM').first().fill('00'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); | ||||||
|     await expect(page.getByText('Month must be between 1 and 12')).toBeVisible(); |     await expect(page.getByText('Month must be between 1 and 12')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error if day is invalid', async ({ page }) => { |   test('Shows error if day is invalid', async ({ page }) => { | ||||||
|  |     await page.getByPlaceholder('YYYY').fill('1999'); | ||||||
|  |     await page.getByPlaceholder('MM').first().fill('01'); | ||||||
|     await page.getByPlaceholder('DD').fill('0'); |     await page.getByPlaceholder('DD').fill('0'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); | ||||||
|     await expect(page.getByText('Day must be between 1 and 31')).toBeVisible(); |     await expect(page.getByText('Day must be between 1 and 31')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error if hour is invalid', async ({ page }) => { |   test('Shows error if hour is invalid', async ({ page }) => { | ||||||
|  |     await page.getByPlaceholder('YYYY').fill('1999'); | ||||||
|  |     await page.getByPlaceholder('MM').first().fill('01'); | ||||||
|  |     await page.getByPlaceholder('DD').fill('1'); | ||||||
|     await page.getByPlaceholder('HH').fill('24'); |     await page.getByPlaceholder('HH').fill('24'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (Local)' }).click(); | ||||||
|     await expect(page.getByText('Hour must be between 0 and 23')).toBeVisible(); |     await expect(page.getByText('Hour must be between 0 and 23')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error if minute is invalid', async ({ page }) => { |   test('Shows error if minute is invalid', async ({ page }) => { | ||||||
|     await page.getByPlaceholder('MM').nth(1).fill('61'); |     await page.getByPlaceholder('YYYY').fill('1999'); | ||||||
|  |     await page.getByPlaceholder('MM').first().fill('01'); | ||||||
|  |     await page.getByPlaceholder('DD').fill('1'); | ||||||
|  |     await page.getByPlaceholder('HH').fill('23'); | ||||||
|  |     await page.getByPlaceholder('MM').nth(1).fill('60'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); | ||||||
|     await expect(page.getByText('Minute must be between 0 and 59')).toBeVisible(); |     await expect(page.getByText('Minute must be between 0 and 59')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error if second is invalid', async ({ page }) => { |   test('Shows error if second is invalid', async ({ page }) => { | ||||||
|  |     await page.getByPlaceholder('YYYY').fill('1999'); | ||||||
|  |     await page.getByPlaceholder('MM').first().fill('01'); | ||||||
|  |     await page.getByPlaceholder('DD').fill('1'); | ||||||
|  |     await page.getByPlaceholder('HH').fill('23'); | ||||||
|  |     await page.getByPlaceholder('MM').nth(1).fill('59'); | ||||||
|     await page.getByPlaceholder('SS').fill('99'); |     await page.getByPlaceholder('SS').fill('99'); | ||||||
|     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); |     await page.getByRole('button', { name: 'Convert to Epoch (UTC)' }).click(); | ||||||
|     await expect(page.getByText('Second must be between 0 and 59')).toBeVisible(); |     await expect(page.getByText('Second must be between 0 and 59')).toBeVisible({ timeout: 50000 }); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| @ -1,18 +1,17 @@ | |||||||
| import process from 'node:process'; | import process from 'node:process'; | ||||||
| import { describe, expect, it } from 'vitest'; | import { describe, expect, it } from 'vitest'; | ||||||
| import { type DateParts, dateToEpoch, epochToDate, getISODateString } from './epoch-converter.service'; | import { type DateParts, dateToEpoch, epochToDate, getISODateString } from '../epoch-converter.service'; | ||||||
| 
 | 
 | ||||||
| process.env.TZ = 'America/Vancouver'; | process.env.TZ = 'America/Vancouver'; | ||||||
| 
 | 
 | ||||||
| describe('epochToDate', () => { | describe('epochToDate', () => { | ||||||
|   it('converts known epoch seconds to correct formatted date (America/Vancouver)', () => { |   it('converts known epoch seconds to correct formatted date (America/Vancouver)', () => { | ||||||
|     const epoch = 1750707060; |     const epoch = 1750707060; | ||||||
|     const expectedUTC = 'Mon, 23 Jun 2025 19:31:00 GMT'; |     const expectedUTC = 'Mon, Jun 23, 2025, 19:31:00 UTC'; | ||||||
|     const expectedLocal = 'Mon, Jun 23, 2025, 12:31:00 PDT'; |     const expectedLocal = 'Mon, Jun 23, 2025, 12:31:00 PDT'; | ||||||
| 
 | 
 | ||||||
|     const result = epochToDate(epoch, { |     const result = epochToDate(epoch, { | ||||||
|       timeZone: 'America/Vancouver', |       timeZone: 'America/Vancouver', | ||||||
|       locale: 'en-US', |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     expect(result.local).toBe(expectedLocal); |     expect(result.local).toBe(expectedLocal); | ||||||
| @ -29,8 +28,8 @@ describe('epochToDate', () => { | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe('dateToEpoch', () => { | describe('dateToEpoch', () => { | ||||||
|   it('converts a known local date to correct epoch (2025-06-20 13:48:00)', () => { |   it('converts a known local date to correct epoch (2025-06-20 13:48:00) with timezone offset', () => { | ||||||
|     const input = '2025-06-20T13:48:00'; |     const input = '2025-06-20T13:48:00-07:00'; | ||||||
| 
 | 
 | ||||||
|     const expectedEpoch = 1750452480; |     const expectedEpoch = 1750452480; | ||||||
|     const result = dateToEpoch(input); |     const result = dateToEpoch(input); | ||||||
| @ -59,12 +58,11 @@ describe('epochToDate - Year 2038 boundary', () => { | |||||||
| 
 | 
 | ||||||
|     const result = epochToDate(epoch, { |     const result = epochToDate(epoch, { | ||||||
|       timeZone: 'America/Vancouver', |       timeZone: 'America/Vancouver', | ||||||
|       locale: 'en-US', |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const expectedLocal = 'Mon, Jan 18, 2038, 19:14:07 PST'; |     const expectedLocal = 'Mon, Jan 18, 2038, 19:14:07 PST'; | ||||||
| 
 | 
 | ||||||
|     const expectedUTC = 'Tue, 19 Jan 2038 03:14:07 GMT'; |     const expectedUTC = 'Tue, Jan 19, 2038, 03:14:07 UTC'; | ||||||
| 
 | 
 | ||||||
|     expect(result.local).toBe(expectedLocal); |     expect(result.local).toBe(expectedLocal); | ||||||
|     expect(result.utc).toBe(expectedUTC); |     expect(result.utc).toBe(expectedUTC); | ||||||
| @ -75,12 +73,11 @@ describe('epochToDate - Year 2038 boundary', () => { | |||||||
| 
 | 
 | ||||||
|     const result = epochToDate(epoch, { |     const result = epochToDate(epoch, { | ||||||
|       timeZone: 'America/Vancouver', |       timeZone: 'America/Vancouver', | ||||||
|       locale: 'en-US', |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const expectedLocal = 'Mon, Jan 18, 2038, 19:14:08 PST'; |     const expectedLocal = 'Mon, Jan 18, 2038, 19:14:08 PST'; | ||||||
| 
 | 
 | ||||||
|     const expectedUTC = 'Tue, 19 Jan 2038 03:14:08 GMT'; |     const expectedUTC = 'Tue, Jan 19, 2038, 03:14:08 UTC'; | ||||||
| 
 | 
 | ||||||
|     expect(result.local).toBe(expectedLocal); |     expect(result.local).toBe(expectedLocal); | ||||||
|     expect(result.utc).toBe(expectedUTC); |     expect(result.utc).toBe(expectedUTC); | ||||||
| @ -14,12 +14,12 @@ export interface DateParts { | |||||||
|  * Converts a Unix epoch timestamp (in seconds or milliseconds) to a human-readable date. |  * Converts a Unix epoch timestamp (in seconds or milliseconds) to a human-readable date. | ||||||
|  * |  * | ||||||
|  * @param epoch - The epoch timestamp to convert (number or string). |  * @param epoch - The epoch timestamp to convert (number or string). | ||||||
|  * @param options - Optional locale and timeZone settings for local time formatting. |  * @param options - Optional timeZone setting for local time formatting. | ||||||
|  * @returns An object with both the local formatted string and UTC string. |  * @returns An object with both the local formatted string and UTC string. | ||||||
|  */ |  */ | ||||||
| export function epochToDate( | export function epochToDate( | ||||||
|   epoch: string | number, |   epoch: string | number, | ||||||
|   options?: { timeZone?: string; locale?: string }, |   options?: { timeZone?: string }, | ||||||
| ): { local: string; utc: string } { | ): { local: string; utc: string } { | ||||||
|   const num = typeof epoch === 'string' ? Number.parseInt(epoch, 10) : epoch; |   const num = typeof epoch === 'string' ? Number.parseInt(epoch, 10) : epoch; | ||||||
| 
 | 
 | ||||||
| @ -34,8 +34,8 @@ export function epochToDate( | |||||||
| 
 | 
 | ||||||
|   const date = new Date(timestampInMs); |   const date = new Date(timestampInMs); | ||||||
| 
 | 
 | ||||||
|   const local = date.toLocaleString(options?.locale || 'en-US', { |   const formatOptions: Intl.DateTimeFormatOptions = { | ||||||
|     timeZone: options?.timeZone, |     timeZoneName: 'short', | ||||||
|     weekday: 'short', |     weekday: 'short', | ||||||
|     year: 'numeric', |     year: 'numeric', | ||||||
|     month: 'short', |     month: 'short', | ||||||
| @ -44,12 +44,22 @@ export function epochToDate( | |||||||
|     minute: '2-digit', |     minute: '2-digit', | ||||||
|     second: '2-digit', |     second: '2-digit', | ||||||
|     hour12: false, |     hour12: false, | ||||||
|     timeZoneName: 'short', |   }; | ||||||
|  | 
 | ||||||
|  |   const localFormatter = new Intl.DateTimeFormat('en-US', { | ||||||
|  |     ...formatOptions, | ||||||
|  |     timeZone: options?.timeZone, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   const utc = date.toUTCString(); |   const utcFormatter = new Intl.DateTimeFormat('en-US', { | ||||||
|  |     ...formatOptions, | ||||||
|  |     timeZone: 'UTC', | ||||||
|  |   }); | ||||||
| 
 | 
 | ||||||
|   return { local, utc }; |   return { | ||||||
|  |     local: localFormatter.format(date), | ||||||
|  |     utc: utcFormatter.format(date), | ||||||
|  |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -65,13 +75,29 @@ export function dateToEpoch( | |||||||
|     parseAsUTC?: boolean // if true, the date string will be parsed as UTC
 |     parseAsUTC?: boolean // if true, the date string will be parsed as UTC
 | ||||||
|   }, |   }, | ||||||
| ): number { | ): number { | ||||||
|   const shouldNormalizeToUTC = options?.parseAsUTC === true && !dateString.endsWith('Z'); |   const { parseAsUTC } = options ?? {}; | ||||||
|   const normalizedDateString = shouldNormalizeToUTC | 
 | ||||||
|     ? `${dateString}Z` |   let normalizedDateString: string = dateString; | ||||||
|     : dateString; |   if (parseAsUTC && !dateString.endsWith('Z')) { | ||||||
|  |     normalizedDateString = `${dateString}Z`; | ||||||
|  |   } | ||||||
|  |   // eslint-disable-next-line no-console
 | ||||||
|  |   console.log(`test = ${normalizedDateString}`); | ||||||
|  |   // else {
 | ||||||
|  |   //   // Manually compute local timezone offset and append it
 | ||||||
|  |   //   const tempDate = new Date(`${dateString}`);
 | ||||||
|  |   //   const offsetMinutes = tempDate.getTimezoneOffset();
 | ||||||
|  |   //   const sign = offsetMinutes <= 0 ? '+' : '-';
 | ||||||
|  |   //   const absOffset = Math.abs(offsetMinutes);
 | ||||||
|  |   //   const hours = String(Math.floor(absOffset / 60)).padStart(2, '0');
 | ||||||
|  |   //   const minutes = String(absOffset % 60).padStart(2, '0');
 | ||||||
|  |   //   const offset = `${sign}${hours}:${minutes}`;
 | ||||||
|  |   //   normalizedDateString = `${dateString}${offset}`;
 | ||||||
|  |   // }
 | ||||||
| 
 | 
 | ||||||
|   const date = new Date(normalizedDateString); |   const date = new Date(normalizedDateString); | ||||||
| 
 |   // eslint-disable-next-line no-console
 | ||||||
|  |   console.log(date); | ||||||
|   if (Number.isNaN(date.getTime())) { |   if (Number.isNaN(date.getTime())) { | ||||||
|     throw new TypeError('Invalid date string'); |     throw new TypeError('Invalid date string'); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { ref } from 'vue'; | import { ref } from 'vue'; | ||||||
| import { type DateParts, dateToEpoch, epochToDate, getISODateString } from './epoch-converter.service.ts'; | import { type DateParts, dateToEpoch, epochToDate, getISODateString } from './epoch-converter.service'; | ||||||
| 
 | 
 | ||||||
| const epochInput = ref(''); | const epochInput = ref(''); | ||||||
| const dateOutput = ref<{ local: string; utc: string } | null>(null); | const dateOutput = ref<{ local: string; utc: string } | null>(null); | ||||||
| @ -41,7 +41,7 @@ function convertDateToEpochLocal() { | |||||||
|   try { |   try { | ||||||
|     validateDateParts(dateParts.value); |     validateDateParts(dateParts.value); | ||||||
|     const isoString = getISODateString(dateParts.value); |     const isoString = getISODateString(dateParts.value); | ||||||
|     epochOutput.value = dateToEpoch(isoString); |     epochOutput.value = dateToEpoch(isoString).toString(); | ||||||
|     dateInputError.value = null; |     dateInputError.value = null; | ||||||
|   } |   } | ||||||
|   catch (e: any) { |   catch (e: any) { | ||||||
| @ -57,7 +57,7 @@ function convertDateToEpochUTC() { | |||||||
|   try { |   try { | ||||||
|     validateDateParts(dateParts.value); |     validateDateParts(dateParts.value); | ||||||
|     const isoString = getISODateString(dateParts.value); |     const isoString = getISODateString(dateParts.value); | ||||||
|     epochOutput.value = dateToEpoch(isoString, { utc: true }); |     epochOutput.value = dateToEpoch(isoString, { parseAsUTC: true }).toString(); | ||||||
|     dateInputError.value = null; |     dateInputError.value = null; | ||||||
|   } |   } | ||||||
|   catch (e: any) { |   catch (e: any) { | ||||||
|  | |||||||
| @ -12,20 +12,21 @@ test.describe('Tool - Gzip decompressor', () => { | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Decompresses valid base64 string and shows output', async ({ page }) => { |   test('Decompresses valid base64 string and shows output', async ({ page }) => { | ||||||
|     const input = page.getByLabel('GZipped User Input'); |     const input = page.getByPlaceholder('Paste your GZipped string here...'); | ||||||
|     const output = page.getByLabel('Decompressed Output'); |     const output = page.locator('textarea[readonly]'); | ||||||
| 
 | 
 | ||||||
|     await input.fill(validGzipBase64); |     await input.fill(validGzipBase64); | ||||||
| 
 | 
 | ||||||
|     await expect(output).toHaveValue('Hello World'); |     // Wait for the output text to contain 'Hello World'
 | ||||||
|  |     await expect(output).toHaveValue('Hello World', { timeout: 30000 }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   test('Shows error for invalid input', async ({ page }) => { |   test('Shows error for invalid input', async ({ page }) => { | ||||||
|     const input = page.getByLabel('GZipped User Input'); |     const input = page.getByPlaceholder('Paste your GZipped string here...'); | ||||||
| 
 | 
 | ||||||
|  |     await expect(input).toBeVisible(); | ||||||
|     await input.fill('invalid-base64'); |     await input.fill('invalid-base64'); | ||||||
| 
 | 
 | ||||||
|     const alert = page.getByRole('alert'); |     await expect(page.getByText('Decompression failed. Please ensure the input is valid GZip.')).toBeVisible(); | ||||||
|     await expect(alert).toContainText('Decompression failed'); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user