Add unit tests for utils and module pure functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 19:14:13 +08:00
parent e596bcd18e
commit 83c2db7582
8 changed files with 563 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest';
import abbreviateNumber from '@/module/abbreviateNumber.js';
describe('abbreviateNumber', () => {
it('returns "0" for 0 seconds', () => {
expect(abbreviateNumber(0)).toBe('0');
});
it('returns seconds only when < 60', () => {
expect(abbreviateNumber(45).trim()).toBe('45s');
});
it('returns minutes and seconds', () => {
// 2m 30s = 150s
expect(abbreviateNumber(150).trim()).toBe('2m 30s');
});
it('returns hours, minutes, and seconds', () => {
// 1h 1m 1s = 3661s
expect(abbreviateNumber(3661).trim()).toBe('1h 1m 1s');
});
it('returns days, hours, minutes, and seconds', () => {
// 1d 2h 3m 4s = 93784s
expect(abbreviateNumber(93784).trim())
.toBe('1d 2h 3m 4s');
});
it('handles exact day boundary', () => {
// 1d = 86400s
expect(abbreviateNumber(86400).trim()).toBe('1d');
});
it('handles exact hour boundary', () => {
// 1h = 3600s
expect(abbreviateNumber(3600).trim()).toBe('1h');
});
it('handles string input by parsing as int', () => {
expect(abbreviateNumber('150').trim()).toBe('2m 30s');
});
it('handles NaN input', () => {
expect(abbreviateNumber('abc').trim()).toBe('');
});
});

View File

@@ -0,0 +1,26 @@
import { describe, it, expect } from 'vitest';
import numberLabel from '@/module/numberLabel.js';
describe('numberLabel', () => {
it('formats small numbers without commas', () => {
expect(numberLabel(1)).toBe('1');
expect(numberLabel(999)).toBe('999');
});
it('formats thousands with commas', () => {
expect(numberLabel(1000)).toBe('1,000');
expect(numberLabel(12345)).toBe('12,345');
});
it('formats millions with commas', () => {
expect(numberLabel(1234567)).toBe('1,234,567');
});
it('preserves decimal places', () => {
expect(numberLabel(1234.56)).toBe('1,234.56');
});
it('handles zero', () => {
expect(numberLabel(0)).toBe('0');
});
});

View File

@@ -0,0 +1,130 @@
import { describe, it, expect } from 'vitest';
import {
setLineChartData,
setBarChartData,
timeRange,
yTimeRange,
getXIndex,
formatTime,
formatMaxTwo,
} from '@/module/setChartData.js';
describe('timeRange', () => {
it('splits time into equal parts', () => {
const result = timeRange(0, 100, 3);
expect(result).toEqual([0, 50, 100]);
});
it('rounds values to integers', () => {
const result = timeRange(0, 10, 4);
// 0, 3.33, 6.67, 10 -> rounded
expect(result).toEqual([0, 3, 7, 10]);
});
});
describe('getXIndex', () => {
it('finds exact match index', () => {
expect(getXIndex([10, 20, 30], 20)).toBe(1);
});
it('finds closest value index', () => {
expect(getXIndex([10, 20, 30], 22)).toBe(1);
});
it('returns last matching index for equal distances', () => {
// 15 is equidistant from 10 and 20, <= means last wins
expect(getXIndex([10, 20, 30], 15)).toBe(1);
});
});
describe('formatTime', () => {
it('formats seconds only', () => {
expect(formatTime(45)).toBe('45s');
});
it('formats minutes and seconds', () => {
expect(formatTime(125)).toBe('2m5s');
});
it('formats hours, minutes, seconds', () => {
expect(formatTime(3661)).toBe('1h1m1s');
});
it('formats days', () => {
expect(formatTime(90061)).toBe('1d1h1m1s');
});
it('returns null for NaN', () => {
expect(formatTime('abc')).toBeNull();
});
it('handles zero seconds', () => {
expect(formatTime(0)).toBe('0s');
});
});
describe('formatMaxTwo', () => {
it('keeps only top two units', () => {
expect(formatMaxTwo(['1d2h3m4s'])).toEqual(['1d 2h']);
});
it('keeps single unit as-is', () => {
expect(formatMaxTwo(['45s'])).toEqual(['45s']);
});
it('handles two units', () => {
expect(formatMaxTwo(['3m20s'])).toEqual(['3m 20s']);
});
it('processes multiple items', () => {
expect(formatMaxTwo(['1h30m10s', '45s']))
.toEqual(['1h 30m', '45s']);
});
});
describe('setLineChartData', () => {
it('prepends min and appends max to data', () => {
const baseData = [
{ x: 1, y: 10 }, { x: 2, y: 20 },
{ x: 3, y: 30 }, { x: 4, y: 40 },
{ x: 5, y: 50 }, { x: 6, y: 60 },
{ x: 7, y: 70 }, { x: 8, y: 80 },
{ x: 9, y: 90 }, { x: 10, y: 100 },
];
const result = setLineChartData(
baseData, 'xMax', 'xMin', false, 200, 0
);
// Should have 12 elements (10 original + 2 boundary)
expect(result).toHaveLength(12);
expect(result[0].x).toBe('xMin');
expect(result[11].x).toBe('xMax');
});
it('clamps percent values between 0 and 1', () => {
const baseData = [
{ x: 1, y: 0.1 }, { x: 2, y: 0.2 },
{ x: 3, y: 0.3 }, { x: 4, y: 0.4 },
{ x: 5, y: 0.5 }, { x: 6, y: 0.6 },
{ x: 7, y: 0.7 }, { x: 8, y: 0.8 },
{ x: 9, y: 0.9 }, { x: 10, y: 0.95 },
];
const result = setLineChartData(
baseData, 'xMax', 'xMin', true, 1, 0
);
expect(result[0].y).toBeGreaterThanOrEqual(0);
expect(result[0].y).toBeLessThanOrEqual(1);
expect(result[11].y).toBeGreaterThanOrEqual(0);
expect(result[11].y).toBeLessThanOrEqual(1);
});
});
describe('setBarChartData', () => {
it('converts x values to formatted date strings', () => {
const baseData = [
{ x: '2023-01-15T12:00:00', y: 100 },
];
const result = setBarChartData(baseData);
expect(result[0].y).toBe(100);
expect(result[0].x).toMatch(/2023\/1\/15/);
});
});

View File

@@ -0,0 +1,36 @@
import { describe, it, expect } from 'vitest';
import shortScaleNumber from '@/module/shortScaleNumber.js';
describe('shortScaleNumber', () => {
it('returns small numbers without abbreviation', () => {
expect(shortScaleNumber(1).trim()).toBe('1');
expect(shortScaleNumber(999).trim()).toBe('999');
});
it('abbreviates thousands as k', () => {
expect(shortScaleNumber(1000).trim()).toBe('1k');
expect(shortScaleNumber(1500).trim()).toBe('1.5k');
});
it('abbreviates millions as m', () => {
expect(shortScaleNumber(1000000).trim()).toBe('1m');
});
it('abbreviates billions as b', () => {
expect(shortScaleNumber(1000000000).trim()).toBe('1b');
});
it('abbreviates trillions as t', () => {
expect(shortScaleNumber(1000000000000).trim())
.toBe('1t');
});
it('rounds up using Math.ceil', () => {
// 1234 -> 1.234k -> ceil(12.34)/10 = 1.3k
expect(shortScaleNumber(1234).trim()).toBe('1.3k');
});
it('handles zero', () => {
expect(shortScaleNumber(0).trim()).toBe('0');
});
});

View File

@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest';
import {
sortNumEngZhtw,
sortNumEngZhtwForFilter,
} from '@/module/sortNumEngZhtw.js';
describe('sortNumEngZhtw', () => {
it('sorts numbers in ascending order', () => {
expect(sortNumEngZhtw(['3', '1', '2']))
.toEqual(['1', '2', '3']);
});
it('places numbers before strings', () => {
expect(sortNumEngZhtw(['b', '1', 'a']))
.toEqual(['1', 'a', 'b']);
});
it('sorts English strings alphabetically', () => {
expect(sortNumEngZhtw(['cherry', 'apple', 'banana']))
.toEqual(['apple', 'banana', 'cherry']);
});
it('sorts mixed numbers and strings', () => {
const input = ['banana', '10', 'apple', '2'];
const result = sortNumEngZhtw(input);
expect(result).toEqual(['2', '10', 'apple', 'banana']);
});
});
describe('sortNumEngZhtwForFilter', () => {
it('returns negative when a < b (numbers)', () => {
expect(sortNumEngZhtwForFilter('1', '2'))
.toBeLessThan(0);
});
it('returns positive when a > b (numbers)', () => {
expect(sortNumEngZhtwForFilter('10', '2'))
.toBeGreaterThan(0);
});
it('places numbers before strings', () => {
expect(sortNumEngZhtwForFilter('1', 'a'))
.toBeLessThan(0);
expect(sortNumEngZhtwForFilter('a', '1'))
.toBeGreaterThan(0);
});
it('compares strings using locale', () => {
expect(sortNumEngZhtwForFilter('a', 'b'))
.toBeLessThan(0);
});
});

View File

@@ -0,0 +1,147 @@
import { describe, it, expect } from 'vitest';
import {
getTimeLabel,
simpleTimeLabel,
followTimeLabel,
getStepSizeOfYTicks,
getYTicksByIndex,
setTimeStringFormatBaseOnTimeDifference,
mapTimestampToAxisTicksByFormat,
} from '@/module/timeLabel.js';
describe('getTimeLabel', () => {
it('returns days for >= 86400 seconds', () => {
expect(getTimeLabel(86400)).toBe('1 days');
expect(getTimeLabel(172800)).toBe('2 days');
});
it('returns hours for >= 3600 seconds', () => {
expect(getTimeLabel(3600)).toBe('1 hrs');
expect(getTimeLabel(7200)).toBe('2 hrs');
});
it('returns minutes for >= 60 seconds', () => {
expect(getTimeLabel(60)).toBe('1 mins');
expect(getTimeLabel(120)).toBe('2 mins');
});
it('returns seconds for < 60', () => {
expect(getTimeLabel(30)).toBe('30 sec');
});
it('returns 0 sec for zero', () => {
expect(getTimeLabel(0)).toBe('0 sec');
});
it('respects fixedNumber parameter', () => {
expect(getTimeLabel(5400, 1)).toBe('1.5 hrs');
});
});
describe('simpleTimeLabel', () => {
it('returns days with d suffix', () => {
expect(simpleTimeLabel(86400)).toBe('1d');
});
it('returns seconds with s suffix for small values', () => {
expect(simpleTimeLabel(30)).toBe('30s');
});
it('returns 0s for zero', () => {
expect(simpleTimeLabel(0)).toBe('0s');
});
});
describe('followTimeLabel', () => {
it('uses day unit when max > 1 day', () => {
// max = 100000 (> 86400), second = 86400 => 1d
expect(followTimeLabel(86400, 100000)).toBe('1d');
});
it('uses hour unit when max > 1 hour', () => {
// max = 7200 (> 3600), second = 3600 => 1h
expect(followTimeLabel(3600, 7200)).toBe('1h');
});
it('uses minute unit when max > 1 minute', () => {
// max = 120 (> 60), second = 60 => 1m
expect(followTimeLabel(60, 120)).toBe('1m');
});
it('uses second unit when max <= 60', () => {
expect(followTimeLabel(30, 50)).toBe('30s');
});
it('returns 0 without decimals when value is 0', () => {
expect(followTimeLabel(0, 7200, 2)).toBe('0h');
});
});
describe('getStepSizeOfYTicks', () => {
it('returns object with resultStepSize and unitToUse', () => {
const result = getStepSizeOfYTicks(7200, 5);
expect(result).toHaveProperty('resultStepSize');
expect(result).toHaveProperty('unitToUse');
expect(result.unitToUse).toBe('h');
});
it('uses day unit for large values', () => {
const result = getStepSizeOfYTicks(200000, 5);
expect(result.unitToUse).toBe('d');
});
it('uses second unit for small values', () => {
const result = getStepSizeOfYTicks(30, 5);
expect(result.unitToUse).toBe('s');
});
});
describe('getYTicksByIndex', () => {
it('formats tick label with unit', () => {
expect(getYTicksByIndex(2, 3, 'h')).toBe('6h');
});
it('truncates to 1 decimal place', () => {
expect(getYTicksByIndex(1.5, 1, 'h')).toBe('1.5h');
});
});
describe('setTimeStringFormatBaseOnTimeDifference', () => {
it('returns seconds format for < 60s difference', () => {
expect(setTimeStringFormatBaseOnTimeDifference(0, 30))
.toBe('HH:mm:ss');
});
it('returns minute format for < 3600s difference', () => {
expect(setTimeStringFormatBaseOnTimeDifference(0, 1800))
.toBe('MM/DD HH:mm');
});
it('returns hour format for < 86400s difference', () => {
expect(setTimeStringFormatBaseOnTimeDifference(0, 43200))
.toBe('MM/DD HH:mm');
});
it('returns day format for < 30 days difference', () => {
expect(setTimeStringFormatBaseOnTimeDifference(0, 864000))
.toBe('YYYY/MM/DD');
});
it('returns month format for >= 30 days difference', () => {
expect(setTimeStringFormatBaseOnTimeDifference(0, 5000000))
.toBe('YYYY/MM/DD');
});
});
describe('mapTimestampToAxisTicksByFormat', () => {
it('formats timestamps with given format', () => {
const ts = [new Date('2023-01-15').getTime()];
const result = mapTimestampToAxisTicksByFormat(ts, 'YYYY/MM/DD');
expect(result[0]).toBe('2023/01/15');
});
it('returns undefined for falsy input', () => {
expect(mapTimestampToAxisTicksByFormat(null, 'YYYY'))
.toBeUndefined();
});
});

View File

@@ -0,0 +1,72 @@
import { describe, it, expect, beforeEach } from 'vitest';
import {
getCookie,
setCookie,
setCookieWithoutExpiration,
deleteCookie,
} from '@/utils/cookieUtil.js';
describe('cookieUtil', () => {
beforeEach(() => {
// Clear all cookies before each test
document.cookie.split(';').forEach((c) => {
const name = c.split('=')[0].trim();
if (name) {
document.cookie =
name + '=; Max-Age=-99999999; path=/';
}
});
});
describe('getCookie', () => {
it('returns null when cookie does not exist', () => {
expect(getCookie('nonexistent')).toBeNull();
});
it('returns value of an existing cookie', () => {
document.cookie = 'testKey=testValue';
expect(getCookie('testKey')).toBe('testValue');
});
it('returns correct value when multiple cookies exist',
() => {
document.cookie = 'first=aaa';
document.cookie = 'second=bbb';
document.cookie = 'third=ccc';
expect(getCookie('second')).toBe('bbb');
});
it('does not match partial cookie names', () => {
document.cookie = 'testKey=value';
expect(getCookie('test')).toBeNull();
});
});
describe('setCookie', () => {
it('sets a cookie with default 1-day expiration', () => {
setCookie('myKey', 'myValue');
expect(getCookie('myKey')).toBe('myValue');
});
it('sets a cookie with empty value', () => {
setCookie('emptyKey', '');
expect(getCookie('emptyKey')).toBe('');
});
});
describe('setCookieWithoutExpiration', () => {
it('sets a session cookie', () => {
setCookieWithoutExpiration('sessionKey', 'sessionVal');
expect(getCookie('sessionKey')).toBe('sessionVal');
});
});
describe('deleteCookie', () => {
it('removes an existing cookie', () => {
setCookie('toDelete', 'value');
expect(getCookie('toDelete')).toBe('value');
deleteCookie('toDelete');
expect(getCookie('toDelete')).toBeNull();
});
});
});

View File

@@ -0,0 +1,54 @@
import { describe, it, expect } from 'vitest';
import {
mapPageNameToCapitalUnifiedName,
} from '@/utils/pageUtils.js';
describe('pageUtils', () => {
describe('mapPageNameToCapitalUnifiedName', () => {
it('converts CheckMap to MAP', () => {
expect(mapPageNameToCapitalUnifiedName('CheckMap'))
.toBe('MAP');
});
it('converts CheckConformance to CONFORMANCE', () => {
expect(
mapPageNameToCapitalUnifiedName('CheckConformance')
).toBe('CONFORMANCE');
});
it('converts CheckPerformance to PERFORMANCE', () => {
expect(
mapPageNameToCapitalUnifiedName('CheckPerformance')
).toBe('PERFORMANCE');
});
it('converts CompareDashboard to DASHBOARD', () => {
expect(
mapPageNameToCapitalUnifiedName('CompareDashboard')
).toBe('DASHBOARD');
});
it('converts other names to uppercase', () => {
expect(mapPageNameToCapitalUnifiedName('files'))
.toBe('FILES');
expect(mapPageNameToCapitalUnifiedName('Map'))
.toBe('MAP');
});
it('handles case-insensitive input', () => {
expect(mapPageNameToCapitalUnifiedName('checkmap'))
.toBe('MAP');
expect(mapPageNameToCapitalUnifiedName('CHECKMAP'))
.toBe('MAP');
});
it('returns undefined for falsy input', () => {
expect(mapPageNameToCapitalUnifiedName(undefined))
.toBeUndefined();
expect(mapPageNameToCapitalUnifiedName(null))
.toBeUndefined();
expect(mapPageNameToCapitalUnifiedName(''))
.toBeUndefined();
});
});
});