diff --git a/frontend/public/components/utils/__tests__/build-utils.spec.ts b/frontend/public/components/utils/__tests__/build-utils.spec.ts new file mode 100644 index 00000000000..2fd49fc350b --- /dev/null +++ b/frontend/public/components/utils/__tests__/build-utils.spec.ts @@ -0,0 +1,33 @@ +import { displayDurationInWords } from '../build-utils'; + +jest.mock('i18next', () => ({ + t: (key: string) => key.replace('public~', ''), +})); + +describe('displayDurationInWords', () => { + it('returns - when start is missing', () => { + expect(displayDurationInWords('', '2024-01-01T00:00:00Z')).toEqual('-'); + }); + + it('formats sub-hour durations', () => { + expect( + displayDurationInWords('2024-01-01T00:00:00Z', '2024-01-01T00:01:05Z'), + ).toEqual('1 minute 5 seconds'); + }); + + it('formats multi-hour durations', () => { + expect( + displayDurationInWords('2024-01-01T00:00:00Z', '2024-01-01T01:02:03Z'), + ).toEqual('1 hour 2 minutes 3 seconds'); + }); + + it('formats durations longer than 60 hours', () => { + expect( + displayDurationInWords('2024-01-01T00:00:00Z', '2024-01-05T04:00:00Z'), + ).toEqual('100 hours'); + }); + + it('returns - for invalid timestamps', () => { + expect(displayDurationInWords('invalid', '2024-01-01T00:00:00Z')).toEqual('-'); + }); +}); diff --git a/frontend/public/components/utils/build-utils.ts b/frontend/public/components/utils/build-utils.ts index f675a142596..6d4b5055a3b 100644 --- a/frontend/public/components/utils/build-utils.ts +++ b/frontend/public/components/utils/build-utils.ts @@ -6,30 +6,30 @@ export const displayDurationInWords = (start: string, stop: string): string => { } const startTime = new Date(start).getTime(); const stopTime = stop ? new Date(stop).getTime() : new Date().getTime(); - let duration = Math.round((stopTime - startTime) / 1000); - const time = []; - let durationInWords = ''; - while (duration >= 60) { - time.push(duration % 60); - duration = Math.floor(duration / 60); + const duration = Math.max(0, Math.round((stopTime - startTime) / 1000)); + if (!Number.isFinite(duration)) { + return '-'; } - time.push(duration); - if (time[2]) { - durationInWords += `${time[2]} ${ - time[2] > 1 ? i18next.t('public~hours') : i18next.t('public~hour') - } `; + const seconds = duration % 60; + const minutes = Math.floor(duration / 60) % 60; + const hours = Math.floor(duration / 3600); + const durationInWords = []; + if (hours) { + durationInWords.push( + `${hours} ${hours > 1 ? i18next.t('public~hours') : i18next.t('public~hour')}`, + ); } - if (time[1]) { - durationInWords += `${time[1]} ${ - time[1] > 1 ? i18next.t('public~minutes') : i18next.t('public~minute') - } `; + if (minutes) { + durationInWords.push( + `${minutes} ${minutes > 1 ? i18next.t('public~minutes') : i18next.t('public~minute')}`, + ); } - if (time[0]) { - durationInWords += `${time[0]} ${ - time[0] > 1 ? i18next.t('public~seconds') : i18next.t('public~second') - } `; + if (seconds || !durationInWords.length) { + durationInWords.push( + `${seconds} ${seconds === 1 ? i18next.t('public~second') : i18next.t('public~seconds')}`, + ); } - return durationInWords.trim(); + return durationInWords.join(' '); }; export enum BuildStrategyType {