diff --git a/tests/unit/module/abbreviateNumber.test.js b/tests/unit/module/abbreviateNumber.test.js new file mode 100644 index 0000000..f755c42 --- /dev/null +++ b/tests/unit/module/abbreviateNumber.test.js @@ -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(''); + }); +}); diff --git a/tests/unit/module/numberLabel.test.js b/tests/unit/module/numberLabel.test.js new file mode 100644 index 0000000..542e02d --- /dev/null +++ b/tests/unit/module/numberLabel.test.js @@ -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'); + }); +}); diff --git a/tests/unit/module/setChartData.test.js b/tests/unit/module/setChartData.test.js new file mode 100644 index 0000000..4da9e0b --- /dev/null +++ b/tests/unit/module/setChartData.test.js @@ -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/); + }); +}); diff --git a/tests/unit/module/shortScaleNumber.test.js b/tests/unit/module/shortScaleNumber.test.js new file mode 100644 index 0000000..3ee8093 --- /dev/null +++ b/tests/unit/module/shortScaleNumber.test.js @@ -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'); + }); +}); diff --git a/tests/unit/module/sortNumEngZhtw.test.js b/tests/unit/module/sortNumEngZhtw.test.js new file mode 100644 index 0000000..3a3345c --- /dev/null +++ b/tests/unit/module/sortNumEngZhtw.test.js @@ -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); + }); +}); diff --git a/tests/unit/module/timeLabel.test.js b/tests/unit/module/timeLabel.test.js new file mode 100644 index 0000000..ca70263 --- /dev/null +++ b/tests/unit/module/timeLabel.test.js @@ -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(); + }); +}); diff --git a/tests/unit/utils/cookieUtil.test.js b/tests/unit/utils/cookieUtil.test.js new file mode 100644 index 0000000..4642f3a --- /dev/null +++ b/tests/unit/utils/cookieUtil.test.js @@ -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(); + }); + }); +}); diff --git a/tests/unit/utils/pageUtils.test.js b/tests/unit/utils/pageUtils.test.js new file mode 100644 index 0000000..f017ac4 --- /dev/null +++ b/tests/unit/utils/pageUtils.test.js @@ -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(); + }); + }); +});