diff --git a/lib/core/src/lib/pipes/time-ago.pipe.spec.ts b/lib/core/src/lib/pipes/time-ago.pipe.spec.ts index f08c384bff..94afe2f9ed 100644 --- a/lib/core/src/lib/pipes/time-ago.pipe.spec.ts +++ b/lib/core/src/lib/pipes/time-ago.pipe.spec.ts @@ -15,47 +15,244 @@ * limitations under the License. */ -import { TimeAgoPipe } from './time-ago.pipe'; +import { DatePipe, registerLocaleData } from '@angular/common'; +import localeDe from '@angular/common/locales/de'; +import localeDeExtra from '@angular/common/locales/extra/de'; import { TestBed } from '@angular/core/testing'; -import { AppConfigService } from '../app-config/app-config.service'; +import { formatDistance } from 'date-fns'; import { UserPreferencesService } from '../common/services/user-preferences.service'; -import { of } from 'rxjs'; +import { DateFnsUtils } from '../common/utils/date-fns-utils'; +import { TimeAgoPipe } from './time-ago.pipe'; +import { AppConfigService } from '../app-config'; + +registerLocaleData(localeDe, 'de', localeDeExtra); describe('TimeAgoPipe', () => { - let pipe: TimeAgoPipe; - let userPreferences: UserPreferencesService; + const NOW = new Date('2026-03-27T12:00:00.000Z'); - beforeEach(() => { + const createPipe = (dateFormat, locale): TimeAgoPipe => { + TestBed.resetTestingModule(); TestBed.configureTestingModule({ - providers: [TimeAgoPipe, UserPreferencesService, AppConfigService] + providers: [ + TimeAgoPipe, + { + provide: UserPreferencesService, + useValue: { + localeSignal: jasmine.createSpy('localeSignal').and.returnValue(locale) + } + }, + { + provide: AppConfigService, + useValue: { + get: jasmine.createSpy('get').and.returnValue(dateFormat) + } + } + ] }); - userPreferences = TestBed.inject(UserPreferencesService); - spyOn(userPreferences, 'select').and.returnValue(of('')); - pipe = TestBed.inject(TimeAgoPipe); + const newPipe = TestBed.inject(TimeAgoPipe); + spyOn(newPipe, 'getCurrentDateTime').and.returnValue(NOW); + + return newPipe; + }; + + describe('AppConfigService returns "MMM d, y, h:mm" and locale="en-US" ', () => { + let pipe: TimeAgoPipe; + beforeEach(() => { + pipe = createPipe('MMM d, y, h:mm', 'en-US'); + }); + it('should return time difference for a given date', () => { + const date = pipe.getCurrentDateTime(); + expect(pipe.transform(date)).toBe('less than a minute ago'); + }); + + it('should return exact date if given date is more than seven days for en locale ', () => { + const date = new Date('1990-11-02T15:25:42.749'); + expect(pipe.transform(date)).toBe('Nov 2, 1990, 3:25'); + }); + + it('should return empty string if given date is empty', () => { + expect(pipe.transform(null)).toBe(''); + expect(pipe.transform(undefined)).toBe(''); + }); + + it('should return relative distance when date is a few hours ago', () => { + const threeHoursAgo = new Date('2026-03-27T09:00:00.000Z'); + + const result = pipe.transform(threeHoursAgo); + + const expected = formatDistance(threeHoursAgo, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('en-US') + }); + expect(result).toBe(expected); + }); + it('should return relative distance when date is 6 days ago', () => { + const sixDaysAgo = new Date('2026-03-21T12:00:00.000Z'); + + const result = pipe.transform(sixDaysAgo); + + const expected = formatDistance(sixDaysAgo, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('en-US') + }); + expect(result).toBe(expected); + }); + it('should return relative distance when date is exactly 7 days ago', () => { + const sevenDaysAgo = new Date('2026-03-20T12:00:00.000Z'); + + const result = pipe.transform(sevenDaysAgo); + + const expected = formatDistance(sevenDaysAgo, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('en-US') + }); + expect(result).toBe(expected); + }); + + it('should use locale argument over user preference locale', () => { + const oldDate = new Date('2026-03-01T10:00:00.000Z'); + const result = pipe.transform(oldDate, 'de'); + + expect(result).toBe('März 1, 2026, 10:00'); + }); }); - it('should return time difference for a given date', () => { - const date = new Date(); - expect(pipe.transform(date)).toBe('less than a minute ago'); + describe('AppConfigService return "MMM d, y, h:mm" and locale="de" ', () => { + let pipe: TimeAgoPipe; + beforeEach(() => { + pipe = createPipe('MMM d, y, h:mm', 'de'); + }); + it('should return time difference for a given date', () => { + const date = pipe.getCurrentDateTime(); + expect(pipe.transform(date)).toBe('vor weniger als 1 Minute'); + }); + + it('should return exact date if given date is more than seven days for de locale ', () => { + const date = new Date('1990-11-03T15:25:42.749'); + expect(pipe.transform(date)).toBe('Nov. 3, 1990, 3:25'); + }); + + it('should return empty string if given date is empty', () => { + expect(pipe.transform(null)).toBe(''); + expect(pipe.transform(undefined)).toBe(''); + }); + + it('should return relative distance when date is a few hours ago', () => { + const threeHoursAgo = new Date('2026-03-27T09:00:00.000Z'); + const result = pipe.transform(threeHoursAgo); + const expected = formatDistance(threeHoursAgo, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('de') + }); + expect(result).toBe(expected); + }); + it('should return relative distance when date is 6 days ago', () => { + const sixDaysAgo = new Date('2026-03-21T12:00:00.000Z'); + const result = pipe.transform(sixDaysAgo); + const expected = formatDistance(sixDaysAgo, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('de') + }); + expect(result).toBe(expected); + }); + it('should return relative distance when date is exactly 7 days ago', () => { + const sevenDaysAgo = new Date('2026-03-20T12:00:00.000Z'); + const result = pipe.transform(sevenDaysAgo); + const expected = formatDistance(sevenDaysAgo, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('de') + }); + + expect(result).toBe(expected); + }); + + it('should use locale argument over user preference locale', () => { + const oldDate = new Date('2026-03-01T10:00:00.000Z'); + const result = pipe.transform(oldDate, 'en-US'); + + expect(result).toBe('Mar 1, 2026, 10:00'); + }); }); - it('should return exact date if given date is more than seven days ', () => { - const date = new Date('1990-11-03T15:25:42.749'); - expect(pipe.transform(date)).toBe('03/11/1990 15:25'); + describe('AppConfigService has no value and returns default short and locale="en-US" ', () => { + let pipe: TimeAgoPipe; + beforeEach(() => { + pipe = createPipe('short', 'en-US'); + }); + it('should return time difference for a given date', () => { + const date = pipe.getCurrentDateTime(); + expect(pipe.transform(date)).toBe('less than a minute ago'); + }); + + it('should return exact date if given date is more than seven days for en locale ', () => { + const date = new Date('1990-11-04T15:25:42.749'); + + expect(pipe.transform(date)).toBe('11/4/90, 3:25 PM'); + }); + + it('should return empty string if given date is empty', () => { + expect(pipe.transform(null)).toBe(''); + expect(pipe.transform(undefined)).toBe(''); + }); + + it('should use locale argument over user preference locale', () => { + const oldDate = new Date('2026-03-01T10:00:00.000Z'); + const result = pipe.transform(oldDate, 'de'); + + expect(result).toBe('01.03.26, 10:00'); + }); }); - it('should return empty string if given date is empty', () => { - expect(pipe.transform(null)).toBe(''); - expect(pipe.transform(undefined)).toBe(''); + describe('AppConfigService has no value and returns default short and locale="de" ', () => { + let pipe: TimeAgoPipe; + beforeEach(() => { + pipe = createPipe('short', 'de'); + }); + it('should return time difference for a given date', () => { + const date = pipe.getCurrentDateTime(); + expect(pipe.transform(date)).toBe('vor weniger als 1 Minute'); + }); + + it('should return exact date if given date is more than seven days for de locale ', () => { + const date = new Date('1990-11-04T15:25:42.749'); + expect(pipe.transform(date)).toBe('04.11.90, 15:25'); + }); + + it('should return empty string if given date is empty', () => { + expect(pipe.transform(null)).toBe(''); + expect(pipe.transform(undefined)).toBe(''); + }); + + it('should use locale argument over user preference locale', () => { + const oldDate = new Date('2026-03-01T10:00:00.000Z'); + const result = pipe.transform(oldDate, 'en-US'); + + expect(result).toBe('3/1/26, 10:00 AM'); + }); }); - describe('When a locale is given', () => { - it('should return a localised message', () => { - const date = new Date(); - const transformedDate = pipe.transform(date, 'de'); - /* cspell:disable-next-line */ - expect(transformedDate).toBe('vor weniger als 1 Minute'); + describe('locale handling', () => { + let pipe: TimeAgoPipe; + beforeEach(() => { + pipe = createPipe('short', undefined); + }); + it('should return localized relative message when locale argument is provided', () => { + const date = new Date(NOW); + const result = pipe.transform(date, 'de'); + const expected = formatDistance(date, NOW, { + addSuffix: true, + locale: DateFnsUtils.getLocaleFromString('de') + }); + expect(result).toBe(expected); + }); + + it('should fall back to DEFAULT_LOCALE when user preference locale is empty', () => { + const oldDate = new Date('2026-03-01T10:00:00.000Z'); + const result = pipe.transform(oldDate); + const expected = new DatePipe('en-US').transform(oldDate, 'short'); + + expect(result).toBe(expected); }); }); }); diff --git a/lib/core/src/lib/pipes/time-ago.pipe.ts b/lib/core/src/lib/pipes/time-ago.pipe.ts index ee3850c639..f572af6d99 100644 --- a/lib/core/src/lib/pipes/time-ago.pipe.ts +++ b/lib/core/src/lib/pipes/time-ago.pipe.ts @@ -32,7 +32,7 @@ export class TimeAgoPipe implements PipeTransform { appConfig = inject(AppConfigService); static DEFAULT_LOCALE = 'en-US'; - static DEFAULT_DATE_TIME_FORMAT = 'dd/MM/yyyy HH:mm'; + static DEFAULT_DATE_TIME_FORMAT = 'short'; defaultDateTimeFormat: string; @@ -40,18 +40,22 @@ export class TimeAgoPipe implements PipeTransform { this.defaultDateTimeFormat = this.appConfig.get('dateValues.defaultDateTimeFormat', TimeAgoPipe.DEFAULT_DATE_TIME_FORMAT); } + getCurrentDateTime(): Date { + return new Date(); + } + transform(value: Date, locale?: string) { if (value !== null && value !== undefined) { // Use signal directly - no subscription needed! const defaultLocale = this.userPreferenceService.localeSignal() || TimeAgoPipe.DEFAULT_LOCALE; const actualLocale = locale || defaultLocale; - const diff = differenceInDays(new Date(), new Date(value)); + const diff = differenceInDays(this.getCurrentDateTime(), new Date(value)); if (diff > 7) { const datePipe: DatePipe = new DatePipe(actualLocale); return datePipe.transform(value, this.defaultDateTimeFormat); } else { const dateFnsLocale = DateFnsUtils.getLocaleFromString(actualLocale); - return formatDistance(new Date(value), new Date(), { addSuffix: true, locale: dateFnsLocale }); + return formatDistance(new Date(value), this.getCurrentDateTime(), { addSuffix: true, locale: dateFnsLocale }); } } return '';