Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions src/utils/__tests__/formatRemaining.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { describe, expect, it } from 'vitest';
import { formatRemaining } from '../formatRemaining';

const ONE_SECOND_MS = 1000;
const ONE_MINUTE_MS = 60 * ONE_SECOND_MS;
const ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
const ONE_DAY_MS = 24 * ONE_HOUR_MS;

describe('formatRemaining', () => {
describe('matured case (diffMs <= 0)', () => {
it('returns matured status when current time equals maturity time', () => {
const baseTime = 1_700_000_000_000;
expect(formatRemaining(baseTime, baseTime)).toEqual({
text: 'Matured',
status: 'matured',
});
});

it('returns matured status when maturity time is in the past', () => {
const current = 1_700_000_000_000;
const maturity = current - 5 * ONE_MINUTE_MS;
expect(formatRemaining(maturity, current)).toEqual({
text: 'Matured',
status: 'matured',
});
});

it('uses Date.now by default when maturity timestamp is in the past', () => {
const pastTime = Date.now() - 10_000;
expect(formatRemaining(pastTime)).toEqual({
text: 'Matured',
status: 'matured',
});
});
});

describe('threshold boundaries', () => {
const current = 1_700_000_000_000;

it('returns critical status when remaining time is exactly one day', () => {
const maturity = current + ONE_DAY_MS;
expect(formatRemaining(maturity, current)).toEqual({
text: '1d 0h 0m',
status: 'critical',
});
});

it('returns critical status when remaining time is less than one day', () => {
const maturity = current + ONE_DAY_MS - 1;
expect(formatRemaining(maturity, current)).toEqual({
text: '23h 59m',
status: 'critical',
});
});

it('returns critical status for small remaining durations under one hour', () => {
const maturity = current + 30 * ONE_MINUTE_MS;
expect(formatRemaining(maturity, current)).toEqual({
text: '30m',
status: 'critical',
});
});

it('returns warning status when remaining time is slightly greater than one day', () => {
const maturity = current + ONE_DAY_MS + 1;
expect(formatRemaining(maturity, current)).toEqual({
text: '1d 0h 0m',
status: 'warning',
});
});

it('returns warning status when remaining time is between one and seven days', () => {
const maturity = current + 3 * ONE_DAY_MS + 4 * ONE_HOUR_MS + 15 * ONE_MINUTE_MS;
expect(formatRemaining(maturity, current)).toEqual({
text: '3d 4h 15m',
status: 'warning',
});
});

it('returns warning status when remaining time is exactly seven days', () => {
const maturity = current + 7 * ONE_DAY_MS;
expect(formatRemaining(maturity, current)).toEqual({
text: '7d 0h 0m',
status: 'warning',
});
});

it('returns healthy status when remaining time is strictly greater than seven days', () => {
const maturity = current + 7 * ONE_DAY_MS + 1;
expect(formatRemaining(maturity, current)).toEqual({
text: '7d 0h 0m',
status: 'healthy',
});
});

it('returns healthy status for long remaining durations', () => {
const maturity = current + 14 * ONE_DAY_MS + 2 * ONE_HOUR_MS + 5 * ONE_MINUTE_MS;
expect(formatRemaining(maturity, current)).toEqual({
text: '14d 2h 5m',
status: 'healthy',
});
});
});

describe('formatting branches (lines 33-35)', () => {
const current = 1_700_000_000_000;

it('includes days prefix and hours when days > 0 and hours > 0', () => {
const maturity = current + 4 * ONE_DAY_MS + 6 * ONE_HOUR_MS + 20 * ONE_MINUTE_MS;
const result = formatRemaining(maturity, current);
expect(result.text).toBe('4d 6h 20m');
expect(result.status).toBe('warning');
});

it('includes 0h when days > 0 and hours === 0', () => {
const maturity = current + 2 * ONE_DAY_MS + 45 * ONE_MINUTE_MS;
const result = formatRemaining(maturity, current);
expect(result.text).toBe('2d 0h 45m');
expect(result.status).toBe('warning');
});

it('omits days and includes hours when days === 0 and hours > 0', () => {
const maturity = current + 5 * ONE_HOUR_MS + 12 * ONE_MINUTE_MS;
const result = formatRemaining(maturity, current);
expect(result.text).toBe('5h 12m');
expect(result.status).toBe('critical');
});

it('omits days and hours when days === 0 and hours === 0', () => {
const maturity = current + 42 * ONE_MINUTE_MS;
const result = formatRemaining(maturity, current);
expect(result.text).toBe('42m');
expect(result.status).toBe('critical');
});

it('formats 0m when diff is less than one minute', () => {
const maturity = current + 45 * ONE_SECOND_MS;
const result = formatRemaining(maturity, current);
expect(result.text).toBe('0m');
expect(result.status).toBe('critical');
});
});
});
48 changes: 48 additions & 0 deletions src/utils/formatRemaining.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export type MaturityStatus = 'healthy' | 'warning' | 'critical' | 'matured';

export interface RemainingMaturity {
text: string;
status: MaturityStatus;
}

/**
* Calculates human-readable remaining time until maturity and determines urgency status.
*
* @param maturityTimestamp - Target maturity epoch timestamp in milliseconds.
* @param currentTimestamp - Current epoch timestamp in milliseconds, defaulting to Date.now().
* @returns Object containing formatted remaining time string and urgency status.
*/
export const formatRemaining = (
maturityTimestamp: number,
currentTimestamp: number = Date.now(),
): RemainingMaturity => {
const diffMs = maturityTimestamp - currentTimestamp;

if (diffMs <= 0) {
return { text: 'Matured', status: 'matured' };
}

const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const hours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((diffMs / (1000 * 60)) % 60);

let status: MaturityStatus = 'healthy';
const oneDayMs = 24 * 60 * 60 * 1000;
const sevenDaysMs = 7 * oneDayMs;

if (diffMs <= oneDayMs) {
status = 'critical';
} else if (diffMs <= sevenDaysMs) {
status = 'warning';
}

const timeParts: string[] = [];
if (days > 0) timeParts.push(`${days}d`);
if (hours > 0 || days > 0) timeParts.push(`${hours}h`);
timeParts.push(`${minutes}m`);

return {
text: timeParts.join(' '),
status,
};
};