diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..c0e60799 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## Description + +Briefly describe the changes made in this Pull Request. + +## Additional context (optional) + +Add any additional context or information about the changes made in this PR. + +## How to test + +Provide clear and concise instructions on how to test the changes made in this PR. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..08b72bba --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: Build and Test ๐Ÿ—๏ธ + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code ๐Ÿ“ฅ + uses: actions/checkout@v4 + + - name: Set up Node.js โš™๏ธ + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + cache-dependency-path: yarn.lock + + - name: Install dependencies ๐Ÿ“ฆ + run: | + HUSKY=0 yarn install --prefer-offline --frozen-lockfile + + - name: Lint code ๐ŸŽจ + run: | + yarn lint + + - name: Build project ๐Ÿ—๏ธ + run: | + yarn build + + - name: Get number of CPU cores ๐Ÿ’ป + id: cpu-cores + uses: SimenB/github-actions-cpu-cores@v2 + + - name: Run Unit tests ๐Ÿงช + run: | + yarn coverage --max-workers ${{ steps.cpu-cores.outputs.count }} + + - name: Upload coverage to Codecov ๐Ÿ“Š + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/__tests__/SiteForm.spec.tsx b/__tests__/SiteForm.spec.tsx index 8d16ac43..9bd65207 100644 --- a/__tests__/SiteForm.spec.tsx +++ b/__tests__/SiteForm.spec.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach } from 'vitest'; diff --git a/__tests__/formatOCPdate.spec.ts b/__tests__/formatOCPdate.spec.ts index f27908bd..a8178d17 100644 --- a/__tests__/formatOCPdate.spec.ts +++ b/__tests__/formatOCPdate.spec.ts @@ -1,42 +1,77 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { formatOCPDate } from '../src/console/core/utils/formatOCPDate'; import { ISO8601Timestamp } from '../src/console/interfaces/CRD_Base'; describe('formatOCPDate', () => { - it('formats date correctly', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-02-05T12:00:00Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('formats date with default locale and timezone', () => { + const navigatorSpy = vi.spyOn(window.navigator, 'language', 'get'); + navigatorSpy.mockReturnValue('en-US'); + + const intlSpy = vi.spyOn(Intl.DateTimeFormat.prototype, 'resolvedOptions'); + intlSpy.mockReturnValue({ + timeZone: 'UTC', + locale: '', + calendar: '', + numberingSystem: '' + }); + const testDate = '2024-02-05T14:30:00Z' as ISO8601Timestamp; const result = formatOCPDate(testDate); - expect(result).toBe('5 feb 2024, 15:30'); + expect(result).toBe('Feb 5, 2024, 14:30'); }); - it('pads minutes with leading zero when needed', () => { - const testDate = '2024-02-05T14:05:00Z' as ISO8601Timestamp; - const result = formatOCPDate(testDate); - expect(result).toBe('5 feb 2024, 15:05'); + it('formats date with custom locale and timezone', () => { + const testDate = '2024-02-05T14:30:00Z' as ISO8601Timestamp; + const result = formatOCPDate(testDate, { + locale: 'it-IT', + timeZone: 'UTC' + }); + expect(result).toBe('5 feb 2024, 14:30'); }); it('handles empty date input', () => { - const testDate = '' as ISO8601Timestamp; - const result = formatOCPDate(testDate); - expect(result).toBe(' '); + expect(formatOCPDate('' as ISO8601Timestamp)).toBe(' '); + expect(formatOCPDate(undefined as unknown as ISO8601Timestamp)).toBe(' '); }); - it('handles undefined date input', () => { - const testDate = undefined as unknown as ISO8601Timestamp; - const result = formatOCPDate(testDate); - expect(result).toBe(' '); + it('formats dates across month boundaries', () => { + const testDate = '2024-01-31T23:30:00Z' as ISO8601Timestamp; + const result = formatOCPDate(testDate, { + locale: 'en-US', + timeZone: 'UTC' + }); + expect(result).toBe('Jan 31, 2024, 23:30'); }); - it('formats date at the end of year', () => { - const testDate = '2024-12-31T23:59:00Z' as ISO8601Timestamp; - const result = formatOCPDate(testDate); - expect(result).toBe('1 gen 2025, 0:59'); + it('formats dates across year boundaries', () => { + const testDate = '2024-12-31T23:30:00Z' as ISO8601Timestamp; + const result = formatOCPDate(testDate, { + locale: 'en-US', + timeZone: 'UTC' + }); + expect(result).toBe('Dec 31, 2024, 23:30'); }); - it('formats date at the beginning of year', () => { - const testDate = '2024-01-01T00:01:00Z' as ISO8601Timestamp; - const result = formatOCPDate(testDate); - expect(result).toBe('1 gen 2024, 1:01'); + it('formats dates consistently across different locales', () => { + const testDate = '2024-02-05T14:30:00Z' as ISO8601Timestamp; + const locales = ['en-US', 'it-IT', 'ja-JP', 'de-DE']; + + locales.forEach((locale) => { + const result = formatOCPDate(testDate, { + locale, + timeZone: 'UTC' + }); + expect(result).toMatch(/[\d\s\w,:]+/); // Verifies format without specific string + }); }); }); diff --git a/src/console/core/utils/formatOCPDate.ts b/src/console/core/utils/formatOCPDate.ts index 4b803aad..9920b4fb 100644 --- a/src/console/core/utils/formatOCPDate.ts +++ b/src/console/core/utils/formatOCPDate.ts @@ -1,15 +1,32 @@ import { ISO8601Timestamp } from '@interfaces/CRD_Base'; -export function formatOCPDate(date: ISO8601Timestamp): string { +interface FormatOptions { + locale?: string; + timeZone?: string; +} + +export function formatOCPDate(date: ISO8601Timestamp, options: FormatOptions = {}): string { if (!date) { return ' '; } + const { locale = navigator.language, timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone } = options; + const baseTime = new Date(date); - // Format date as 'DD MMM YYYY' and time as 'HH:mm' - const formattedDate = `${baseTime.getDate()} ${baseTime.toLocaleString('it-IT', { month: 'short' })} ${baseTime.getFullYear()}`; - const formattedTime = `${baseTime.getHours()}:${baseTime.getMinutes().toString().padStart(2, '0')}`; + const formattedDate = baseTime.toLocaleDateString(locale, { + day: 'numeric', + month: 'short', + year: 'numeric', + timeZone + }); + + const formattedTime = baseTime.toLocaleTimeString(locale, { + hour: 'numeric', + minute: '2-digit', + hour12: false, + timeZone + }); return `${formattedDate}, ${formattedTime}`; }