replace Jest with Vitest

This commit is contained in:
MaysWind
2026-04-25 00:56:58 +08:00
parent 1428ce921c
commit de885c963d
11 changed files with 867 additions and 3098 deletions
-21
View File
@@ -1,21 +0,0 @@
import { type JestConfigWithTsJest, createDefaultEsmPreset } from 'ts-jest';
const presetConfig = createDefaultEsmPreset({
tsconfig: '<rootDir>/tsconfig.jest.json'
});
const config: JestConfigWithTsJest = {
...presetConfig,
clearMocks: true,
collectCoverage: false,
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1"
},
testEnvironment: "node",
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"!**/__tests__/*_gen.[jt]s?(x)"
]
};
export default config;
+497 -2548
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -16,7 +16,7 @@
"build": "cross-env NODE_ENV=production vite build",
"serve:dist": "vite preview",
"lint": "vue-tsc --noEmit && eslint . --fix",
"test": "cross-env TS_NODE_PROJECT=\"./tsconfig.jest.json\" jest"
"test": "vitest run"
},
"dependencies": {
"@mdi/js": "7.4.47",
@@ -56,7 +56,6 @@
"@types/crypto-js": "4.2.2",
"@types/git-rev-sync": "2.0.2",
"@types/jalaali-js": "1.2.0",
"@types/jest": "30.0.0",
"@types/node": "25.6.0",
"@types/ua-parser-js": "0.7.39",
"@vitejs/plugin-vue": "6.0.6",
@@ -66,16 +65,15 @@
"eslint": "10.2.1",
"eslint-plugin-vue": "10.9.0",
"git-rev-sync": "3.0.2",
"jest": "30.3.0",
"postcss-preset-env": "11.2.1",
"sass": "1.99.0",
"ts-jest": "29.4.9",
"ts-node": "10.9.2",
"typescript": "6.0.3",
"vite": "7.3.2",
"vite-plugin-checker": "0.13.0",
"vite-plugin-pwa": "1.2.0",
"vite-plugin-vuetify": "2.1.3",
"vitest": "4.1.5",
"vue-tsc": "3.2.7"
},
"browserslist": [
+225
View File
@@ -0,0 +1,225 @@
import fs from 'fs';
import path from 'path';
import { describe, expect, it, beforeAll } from 'vitest';
import moment from 'moment-timezone';
import type { TextualYearMonth } from '@/core/datetime.ts';
import { FiscalYearStart, FiscalYearUnixTime } from '@/core/fiscalyear.ts';
import {
getFiscalYearFromUnixTime,
getFiscalYearStartUnixTime,
getFiscalYearEndUnixTime,
getFiscalYearTimeRangeFromUnixTime,
getAllFiscalYearsStartAndEndUnixTimes,
getFiscalYearTimeRangeFromYear
} from '@/lib/datetime.ts';
// Set test environment timezone to UTC, since the test data constants are in UTC
beforeAll(() => {
moment.tz.setDefault('UTC');
});
function importTestData(datasetName: string): unknown[] {
const data = JSON.parse(
fs.readFileSync(path.join(__dirname, 'fiscal_year.data.json'), 'utf8')
);
if (!data || typeof data[datasetName] === 'undefined') {
throw new Error(`${datasetName} is undefined or missing in the data object.`);
}
return data[datasetName];
}
function formatUnixTimeISO(unixTime: number): string {
return moment.unix(unixTime).format('YYYY-MM-DDTHH:mm:ssZ');
}
function withISO(data: FiscalYearUnixTime) {
return {
...data,
minUnixTimeISO: formatUnixTimeISO(data.minUnixTime),
maxUnixTimeISO: formatUnixTimeISO(data.maxUnixTime),
};
}
type FiscalYearStartConfig = {
id: string;
monthDateString: string;
value: number;
};
const FISCAL_YEAR_START_PRESETS: Record<string, FiscalYearStartConfig> = {
'January 1': { id: 'January 1', monthDateString: '01-01', value: 0x0101 },
'April 1': { id: 'April 1', monthDateString: '04-01', value: 0x0401 },
'October 1': { id: 'October 1', monthDateString: '10-01', value: 0x0A01 },
};
describe('validateFiscalYearStart', () => {
Object.values(FISCAL_YEAR_START_PRESETS).forEach(({ id, value, monthDateString }) => {
it(`should return a fiscal year start object for valid value 0x${value.toString(16)} (${id})`, () => {
expect(FiscalYearStart.valueOf(value)).toBeDefined();
});
it(`should return the correct month-date string for valid value 0x${value.toString(16)} (${id})`, () => {
expect(FiscalYearStart.valueOf(value)?.toMonthDashDayString()).toStrictEqual(monthDateString);
});
});
});
const INVALID_FISCAL_YEAR_VALUES = [
0x0000, // Invalid: L0/0
0x0D01, // Invalid: Month 13
0x0100, // Invalid: Day 0
0x0120, // Invalid: January 32
0x021D, // Invalid: February 29 (not permitted)
0x021E, // Invalid: February 30
0x041F, // Invalid: April 31
0x061F, // Invalid: June 31
0x091F, // Invalid: September 31
0x0B20, // Invalid: November 32
0xFFFF, // Invalid: Largest uint16
];
describe('validateFiscalYearStartInvalidValues', () => {
INVALID_FISCAL_YEAR_VALUES.forEach((value) => {
it(`should return undefined for invalid fiscal year start value 0x${value.toString(16)}`, () => {
expect(FiscalYearStart.valueOf(value)).not.toBeDefined();
});
});
});
describe('validateFiscalYearStartLeapDay', () => {
it('should return undefined for February 29 value (0x021D)', () => {
expect(FiscalYearStart.valueOf(0x021D)).not.toBeDefined();
});
it('should return undefined when parsing month-day string "02-29"', () => {
expect(FiscalYearStart.parse('02-29')).not.toBeDefined();
});
});
type FiscalYearFromUnixTimeCase = {
date: string;
unixTime: number;
expected: { [fiscalYearStartId: string]: number };
};
const TEST_CASES_GET_FISCAL_YEAR_FROM_UNIX_TIME =
importTestData('test_cases_getFiscalYearFromUnixTime') as FiscalYearFromUnixTimeCase[];
describe('getFiscalYearFromUnixTime', () => {
Object.values(FISCAL_YEAR_START_PRESETS).forEach(({ id, value }) => {
TEST_CASES_GET_FISCAL_YEAR_FROM_UNIX_TIME.forEach((testCase) => {
it(`should return correct fiscal year for FY_START ${id}, date ${moment(testCase.date).format('MMMM D, YYYY')}`, () => {
expect(getFiscalYearFromUnixTime(moment(testCase.date).unix(), value)).toBe(testCase.expected[id]);
});
});
});
});
type FiscalYearStartUnixTimeCase = {
date: string;
expected: {
[fiscalYearStart: string]: { unixTime: number; unixTimeISO: string };
};
};
const TEST_CASES_GET_FISCAL_YEAR_START_UNIX_TIME =
importTestData('test_cases_getFiscalYearStartUnixTime') as FiscalYearStartUnixTimeCase[];
describe('getFiscalYearStartUnixTime', () => {
Object.values(FISCAL_YEAR_START_PRESETS).forEach(({ id, value }) => {
TEST_CASES_GET_FISCAL_YEAR_START_UNIX_TIME.forEach((testCase) => {
it(`should return correct start unix time for FY_START ${id}, date ${moment(testCase.date).format('MMMM D, YYYY')}`, () => {
const startUnixTime = getFiscalYearStartUnixTime(moment(testCase.date).unix(), value);
const expected = testCase.expected[id];
expect({ unixTime: startUnixTime, ISO: formatUnixTimeISO(startUnixTime) })
.toStrictEqual({ unixTime: expected!.unixTime, ISO: expected!.unixTimeISO });
});
});
});
});
type FiscalYearEndUnixTimeCase = {
date: string;
expected: {
[fiscalYearStart: string]: { unixTime: number; unixTimeISO: string };
};
};
const TEST_CASES_GET_FISCAL_YEAR_END_UNIX_TIME =
importTestData('test_cases_getFiscalYearEndUnixTime') as FiscalYearEndUnixTimeCase[];
describe('getFiscalYearEndUnixTime', () => {
Object.values(FISCAL_YEAR_START_PRESETS).forEach(({ id, value }) => {
TEST_CASES_GET_FISCAL_YEAR_END_UNIX_TIME.forEach((testCase) => {
it(`should return correct end unix time for FY_START ${id}, date ${moment(testCase.date).format('MMMM D, YYYY')}`, () => {
const endUnixTime = getFiscalYearEndUnixTime(moment(testCase.date).unix(), value);
const expected = testCase.expected[id];
expect({ unixTime: endUnixTime, ISO: formatUnixTimeISO(endUnixTime) })
.toStrictEqual({ unixTime: expected!.unixTime, ISO: expected!.unixTimeISO });
});
});
});
});
type FiscalYearTimeRangeFromUnixTimeCase = {
date: string;
expected: { [fiscalYearStart: string]: FiscalYearUnixTime[] };
};
const TEST_CASES_GET_FISCAL_YEAR_UNIX_TIME_RANGE =
importTestData('test_cases_getFiscalYearTimeRangeFromUnixTime') as FiscalYearTimeRangeFromUnixTimeCase[];
describe('getFiscalYearTimeRangeFromUnixTime', () => {
Object.values(FISCAL_YEAR_START_PRESETS).forEach(({ id, value }) => {
TEST_CASES_GET_FISCAL_YEAR_UNIX_TIME_RANGE.forEach((testCase) => {
it(`should return correct fiscal year unix time range for FY_START ${id}, date ${moment(testCase.date).format('MMMM D, YYYY')}`, () => {
expect(getFiscalYearTimeRangeFromUnixTime(moment(testCase.date).unix(), value))
.toStrictEqual(testCase.expected[id]);
});
});
});
});
type AllFiscalYearsStartAndEndUnixTimesCase = {
startYearMonth: TextualYearMonth;
endYearMonth: TextualYearMonth;
fiscalYearStart: string;
fiscalYearStartId: string;
expected: FiscalYearUnixTime[];
};
const TEST_CASES_GET_ALL_FISCAL_YEARS_START_AND_END_UNIX_TIMES =
importTestData('test_cases_getAllFiscalYearsStartAndEndUnixTimes') as AllFiscalYearsStartAndEndUnixTimesCase[];
describe('getAllFiscalYearsStartAndEndUnixTimes', () => {
TEST_CASES_GET_ALL_FISCAL_YEARS_START_AND_END_UNIX_TIMES.forEach((testCase) => {
it(`should return correct fiscal year start and end unix times for FY_START ${testCase.fiscalYearStartId}, range ${testCase.startYearMonth} to ${testCase.endYearMonth}`, () => {
const fiscalYearStart = FiscalYearStart.parse(testCase.fiscalYearStart);
expect(fiscalYearStart).toBeDefined();
expect(getAllFiscalYearsStartAndEndUnixTimes(testCase.startYearMonth, testCase.endYearMonth, fiscalYearStart?.value || 0).map(withISO))
.toStrictEqual(testCase.expected.map(withISO));
});
});
});
type FiscalYearTimeRangeFromYearCase = {
year: number;
fiscalYearStart: string;
expected: FiscalYearUnixTime;
};
const TEST_CASES_GET_FISCAL_YEAR_RANGE_FROM_YEAR =
importTestData('test_cases_getFiscalYearTimeRangeFromYear') as FiscalYearTimeRangeFromYearCase[];
describe('getFiscalYearTimeRangeFromYear', () => {
TEST_CASES_GET_FISCAL_YEAR_RANGE_FROM_YEAR.forEach((testCase) => {
it(`should return correct fiscal year unix time range for year ${testCase.year} and FY_START ${testCase.fiscalYearStart}`, () => {
const fiscalYearStart = FiscalYearStart.parse(testCase.fiscalYearStart);
expect(fiscalYearStart).toBeDefined();
expect(getFiscalYearTimeRangeFromYear(testCase.year, fiscalYearStart?.value || 0))
.toStrictEqual(testCase.expected);
});
});
});
-289
View File
@@ -1,289 +0,0 @@
// Unit tests for fiscal year functions
import fs from 'fs';
import path from 'path';
import { describe, expect, test, beforeAll } from '@jest/globals';
import moment from 'moment-timezone';
// Import all the fiscal year functions from the lib
import type { TextualYearMonth } from '@/core/datetime.ts';
import { FiscalYearStart, FiscalYearUnixTime } from '@/core/fiscalyear.ts';
import {
getFiscalYearFromUnixTime,
getFiscalYearStartUnixTime,
getFiscalYearEndUnixTime,
getFiscalYearTimeRangeFromUnixTime,
getAllFiscalYearsStartAndEndUnixTimes,
getFiscalYearTimeRangeFromYear
} from '@/lib/datetime.ts';
// Set test environment timezone to UTC, since the test data constants are in UTC
beforeAll(() => {
moment.tz.setDefault('UTC');
});
// UTILITIES
function importTestData(datasetName: string): unknown[] {
const data = JSON.parse(
fs.readFileSync(path.join(__dirname, 'fiscal_year.data.json'), 'utf8')
);
if (!data || typeof data[datasetName] === 'undefined') {
throw new Error(`${datasetName} is undefined or missing in the data object.`);
}
return data[datasetName];
}
function formatUnixTimeISO(unixTime: number): string {
return moment.unix(unixTime).format('YYYY-MM-DDTHH:mm:ssZ');
}
function getTestTitleFormatDate(testFiscalYearStartId: string, testCaseDateString: string): string {
return `FY_START: ${testFiscalYearStartId.padStart(10, ' ')}; DATE: ${moment(testCaseDateString).format('MMMM D, YYYY')}`;
}
function getTestTitleFormatString(testFiscalYearStartId: string, testCaseString: string): string {
return `FY_START: ${testFiscalYearStartId.padStart(10, ' ')}; ${testCaseString}`;
}
// FISCAL YEAR START CONFIGURATION
type FiscalYearStartConfig = {
id: string;
monthDateString: string;
value: number;
};
const TEST_FISCAL_YEAR_START_PRESETS: Record<string, FiscalYearStartConfig> = {
'January 1': {
id: 'January 1',
monthDateString: '01-01',
value: 0x0101,
},
'April 1': {
id: 'April 1',
monthDateString: '04-01',
value: 0x0401,
},
'October 1': {
id: 'October 1',
monthDateString: '10-01',
value: 0x0A01,
},
};
// VALIDATE FISCAL YEAR START PRESETS
describe('validateFiscalYearStart', () => {
Object.values(TEST_FISCAL_YEAR_START_PRESETS).forEach((testFiscalYearStart) => {
test(`should return fiscal year start object if fiscal year start value (uint16) is valid: id: ${testFiscalYearStart.id}; value: 0x${testFiscalYearStart.value.toString(16)}`, () => {
expect(FiscalYearStart.valueOf(testFiscalYearStart.value)).toBeDefined();
});
test(`returns same month-date string for valid fiscal year start value: id: ${testFiscalYearStart.id}; value: 0x${testFiscalYearStart.value.toString(16)}`, () => {
const fiscalYearStart = FiscalYearStart.valueOf(testFiscalYearStart.value);
expect(fiscalYearStart?.toMonthDashDayString()).toStrictEqual(testFiscalYearStart.monthDateString);
});
});
});
// VALIDATE INVALID FISCAL YEAR START VALUES
const TestCase_invalidFiscalYearValues = [
0x0000, // Invalid: L0/0
0x0D01, // Invalid: Month 13
0x0100, // Invalid: Day 0
0x0120, // Invalid: January 32
0x021D, // Invalid: February 29 (not permitted)
0x021E, // Invalid: February 30
0x041F, // Invalid: April 31
0x061F, // Invalid: June 31
0x091F, // Invalid: September 31
0x0B20, // Invalid: November 32
0xFFFF, // Invalid: Largest uint16
]
describe('validateFiscalYearStartInvalidValues', () => {
TestCase_invalidFiscalYearValues.forEach((testCase) => {
test(`should return undefined if fiscal year start value (uint16) is invalid: value: 0x${testCase.toString(16)}`, () => {
expect(FiscalYearStart.valueOf(testCase)).not.toBeDefined();
});
});
});
// VALIDATE LEAP DAY FEBRUARY 29 IS NOT VALID
describe('validateFiscalYearStartLeapDay', () => {
test(`should return undefined if fiscal year start value (uint16) for February 29 is invalid: value: 0x0229}`, () => {
expect(FiscalYearStart.valueOf(0x021D)).not.toBeDefined();
});
test(`should return undefined if fiscal year month-day string "02-29" is used to create fiscal year start object`, () => {
expect(FiscalYearStart.parse('02-29')).not.toBeDefined();
});
});
// FISCAL YEAR FROM UNIX TIME
type TestCase_getFiscalYearFromUnixTime = {
date: string;
unixTime: number;
expected: {
[fiscalYearStartId: string]: number;
};
};
const TEST_CASES_GET_FISCAL_YEAR_FROM_UNIX_TIME: TestCase_getFiscalYearFromUnixTime[] =
importTestData('test_cases_getFiscalYearFromUnixTime') as TestCase_getFiscalYearFromUnixTime[];
describe('getFiscalYearFromUnixTime', () => {
Object.values(TEST_FISCAL_YEAR_START_PRESETS).forEach((testFiscalYearStart) => {
TEST_CASES_GET_FISCAL_YEAR_FROM_UNIX_TIME.forEach((testCase) => {
test(`returns correct fiscal year for ${getTestTitleFormatDate(testFiscalYearStart.id, testCase.date)}`, () => {
const testCaseUnixTime = moment(testCase.date).unix();
const fiscalYear = getFiscalYearFromUnixTime(testCaseUnixTime, testFiscalYearStart.value);
const expected = testCase.expected[testFiscalYearStart.id];
expect(fiscalYear).toBe(expected);
});
});
});
});
// FISCAL YEAR START UNIX TIME
type TestCase_getFiscalYearStartUnixTime = {
date: string;
expected: {
[fiscalYearStart: string]: {
unixTime: number;
unixTimeISO: string;
};
};
}
const TEST_CASES_GET_FISCAL_YEAR_START_UNIX_TIME: TestCase_getFiscalYearStartUnixTime[] =
importTestData('test_cases_getFiscalYearStartUnixTime') as TestCase_getFiscalYearStartUnixTime[];
describe('getFiscalYearStartUnixTime', () => {
Object.values(TEST_FISCAL_YEAR_START_PRESETS).forEach((testFiscalYearStart) => {
TEST_CASES_GET_FISCAL_YEAR_START_UNIX_TIME.forEach((testCase) => {
test(`returns correct start unix time for ${getTestTitleFormatDate(testFiscalYearStart.id, testCase.date)}`, () => {
const testCaseUnixTime = moment(testCase.date).unix();
const startUnixTime = getFiscalYearStartUnixTime(testCaseUnixTime, testFiscalYearStart.value);
const expected = testCase.expected[testFiscalYearStart.id];
const unixTimeISO = formatUnixTimeISO(startUnixTime);
expect({ unixTime: startUnixTime, ISO: unixTimeISO }).toStrictEqual({ unixTime: expected!.unixTime, ISO: expected!.unixTimeISO });
});
});
});
});
// FISCAL YEAR END UNIX TIME
type TestCase_getFiscalYearEndUnixTime = {
date: string;
expected: {
[fiscalYearStart: string]: {
unixTime: number;
unixTimeISO: string;
};
};
}
const TEST_CASES_GET_FISCAL_YEAR_END_UNIX_TIME: TestCase_getFiscalYearEndUnixTime[] =
importTestData('test_cases_getFiscalYearEndUnixTime') as TestCase_getFiscalYearEndUnixTime[];
describe('getFiscalYearEndUnixTime', () => {
Object.values(TEST_FISCAL_YEAR_START_PRESETS).forEach((testFiscalYearStart) => {
TEST_CASES_GET_FISCAL_YEAR_END_UNIX_TIME.forEach((testCase) => {
test(`returns correct end unix time for ${getTestTitleFormatDate(testFiscalYearStart.id, testCase.date)}`, () => {
const testCaseUnixTime = moment(testCase.date).unix();
const endUnixTime = getFiscalYearEndUnixTime(testCaseUnixTime, testFiscalYearStart.value);
const expected = testCase.expected[testFiscalYearStart.id];
const unixTimeISO = formatUnixTimeISO(endUnixTime);
expect({ unixTime: endUnixTime, ISO: unixTimeISO }).toStrictEqual({ unixTime: expected!.unixTime, ISO: expected!.unixTimeISO });
});
});
});
});
// GET FISCAL YEAR UNIX TIME RANGE
type TestCase_getFiscalYearTimeRangeFromUnixTime = {
date: string;
expected: {
[fiscalYearStart: string]: FiscalYearUnixTime[]
}
}
const TEST_CASES_GET_FISCAL_YEAR_UNIX_TIME_RANGE: TestCase_getFiscalYearTimeRangeFromUnixTime[] =
importTestData('test_cases_getFiscalYearTimeRangeFromUnixTime') as TestCase_getFiscalYearTimeRangeFromUnixTime[];
describe('getFiscalYearTimeRangeFromUnixTime', () => {
Object.values(TEST_FISCAL_YEAR_START_PRESETS).forEach((testFiscalYearStart) => {
TEST_CASES_GET_FISCAL_YEAR_UNIX_TIME_RANGE.forEach((testCase) => {
test(`returns correct fiscal year unix time range for ${getTestTitleFormatDate(testFiscalYearStart.id, testCase.date)}`, () => {
const testCaseUnixTime = moment(testCase.date).unix();
const fiscalYearUnixTimeRange = getFiscalYearTimeRangeFromUnixTime(testCaseUnixTime, testFiscalYearStart.value);
expect(fiscalYearUnixTimeRange).toStrictEqual(testCase.expected[testFiscalYearStart.id]);
});
});
});
});
// GET ALL FISCAL YEAR START AND END UNIX TIMES
type TestCase_getAllFiscalYearsStartAndEndUnixTimes = {
startYearMonth: TextualYearMonth;
endYearMonth: TextualYearMonth;
fiscalYearStart: string;
fiscalYearStartId: string;
expected: FiscalYearUnixTime[]
}
const TEST_CASES_GET_ALL_FISCAL_YEARS_START_AND_END_UNIX_TIMES: TestCase_getAllFiscalYearsStartAndEndUnixTimes[] =
importTestData('test_cases_getAllFiscalYearsStartAndEndUnixTimes') as TestCase_getAllFiscalYearsStartAndEndUnixTimes[];
describe('getAllFiscalYearsStartAndEndUnixTimes', () => {
TEST_CASES_GET_ALL_FISCAL_YEARS_START_AND_END_UNIX_TIMES.forEach((testCase) => {
const fiscalYearStart = FiscalYearStart.parse(testCase.fiscalYearStart);
test(`returns correct fiscal year start and end unix times for ${getTestTitleFormatString(testCase.fiscalYearStartId, `${testCase.startYearMonth} to ${testCase.endYearMonth}`)}`, () => {
expect(fiscalYearStart).toBeDefined();
const fiscalYearStartAndEndUnixTimes = getAllFiscalYearsStartAndEndUnixTimes(testCase.startYearMonth, testCase.endYearMonth, fiscalYearStart?.value || 0);
// Convert results to include ISO strings for better test output
const resultWithISO = fiscalYearStartAndEndUnixTimes.map(data => ({
...data,
minUnixTimeISO: formatUnixTimeISO(data.minUnixTime),
maxUnixTimeISO: formatUnixTimeISO(data.maxUnixTime)
}));
// Convert expected to include ISO strings
const expectedWithISO = testCase.expected.map(data => ({
...data,
minUnixTimeISO: formatUnixTimeISO(data.minUnixTime),
maxUnixTimeISO: formatUnixTimeISO(data.maxUnixTime)
}));
expect(resultWithISO).toStrictEqual(expectedWithISO);
});
});
});
// GET FISCAL YEAR RANGE FROM YEAR
type TestCase_getFiscalYearTimeRangeFromYear = {
year: number;
fiscalYearStart: string;
expected: FiscalYearUnixTime;
}
const TEST_CASES_GET_FISCAL_YEAR_RANGE_FROM_YEAR: TestCase_getFiscalYearTimeRangeFromYear[] =
importTestData('test_cases_getFiscalYearTimeRangeFromYear') as TestCase_getFiscalYearTimeRangeFromYear[];
describe('getFiscalYearTimeRangeFromYear', () => {
TEST_CASES_GET_FISCAL_YEAR_RANGE_FROM_YEAR.forEach((testCase) => {
const fiscalYearStart = FiscalYearStart.parse(testCase.fiscalYearStart);
test(`returns correct fiscal year unix time range for input year integer ${testCase.year} and FY_START: ${testCase.fiscalYearStart}`, () => {
expect(fiscalYearStart).toBeDefined();
const fiscalYearRange = getFiscalYearTimeRangeFromYear(testCase.year, fiscalYearStart?.value || 0);
expect(fiscalYearRange).toStrictEqual(testCase.expected);
});
});
});
+79
View File
@@ -0,0 +1,79 @@
import { describe, expect, it } from 'vitest';
import { mean, median, percentile, sumMaxN } from '@/lib/math.ts';
describe('mean', () => {
it('should return zero for empty array', () => {
expect(mean([], item => item)).toBeCloseTo(0);
});
it('should return the average for positive values', () => {
expect(mean([1, 2, 3, 4], item => item)).toBeCloseTo(2.5);
});
it('should return the average for negative and positive values', () => {
expect(mean([-10, 0, 20], item => item)).toBeCloseTo(10 / 3);
});
});
describe('median', () => {
it('should return zero for empty sorted array', () => {
expect(median([], item => item)).toBeCloseTo(0);
});
it('should return the middle value for odd-length sorted array', () => {
expect(median([1, 3, 5], item => item)).toBeCloseTo(3);
});
it('should return the average of the two middle values for even-length sorted array', () => {
expect(median([1, 3, 5, 7], item => item)).toBeCloseTo(4);
});
});
describe('percentile', () => {
it('should return zero for empty sorted array', () => {
expect(percentile([], 0.5, item => item)).toBeCloseTo(0);
});
it('should return zero when percentile is smaller than zero', () => {
expect(percentile([1, 2, 3], -0.1, item => item)).toBeCloseTo(0);
});
it('should return zero when percentile is larger than one', () => {
expect(percentile([1, 2, 3], 1.1, item => item)).toBeCloseTo(0);
});
it('should return the minimum value for zero percentile', () => {
expect(percentile([5, 10, 15, 20], 0, item => item)).toBeCloseTo(5);
});
it('should return the maximum value for one percentile', () => {
expect(percentile([5, 10, 15, 20], 1, item => item)).toBeCloseTo(20);
});
it('should return the exact indexed value when percentile maps to an integer index', () => {
expect(percentile([10, 20, 30, 40, 50], 0.25, item => item)).toBeCloseTo(20);
});
it('should interpolate between neighboring values when percentile maps to a fractional index', () => {
expect(percentile([10, 20, 30, 40, 50, 60, 70, 80], 0.25, item => item)).toBeCloseTo(27.5);
});
});
describe('sumMaxN', () => {
it('should return zero for empty sorted array', () => {
expect(sumMaxN([], 3, item => item)).toBe(0);
});
it('should return zero when n is zero', () => {
expect(sumMaxN([1, 2, 3], 0, item => item)).toBe(0);
});
it('should return the sum of the largest n values', () => {
expect(sumMaxN([1, 2, 3, 4, 5], 2, item => item)).toBe(9);
});
it('should return the sum of all values when n is larger than array length', () => {
expect(sumMaxN([1, 2, 3, 4], 10, item => item)).toBe(10);
});
});
-190
View File
@@ -1,190 +0,0 @@
import { describe, expect, test } from '@jest/globals';
import { mean, median, percentile, sumMaxN } from '@/lib/math.ts';
type TestNumberItem = {
value: number;
};
function createNumberItems(values: number[]): TestNumberItem[] {
return values.map(value => ({ value }));
}
function getTestTitle(functionName: string, title: string): string {
return `${functionName}: ${title}`;
}
type MeanTestCase = {
title: string;
values: number[];
expected: number;
};
const TEST_CASES_MEAN: MeanTestCase[] = [
{
title: 'returns zero for empty array',
values: [],
expected: 0,
},
{
title: 'returns the average for positive values',
values: [1, 2, 3, 4],
expected: 2.5,
},
{
title: 'returns the average for negative and positive values',
values: [-10, 0, 20],
expected: 10 / 3,
},
];
describe('mean', () => {
TEST_CASES_MEAN.forEach((testCase) => {
test(getTestTitle('mean', testCase.title), () => {
const result = mean(createNumberItems(testCase.values), item => item.value);
expect(result).toBeCloseTo(testCase.expected);
});
});
});
type MedianTestCase = {
title: string;
values: number[];
expected: number;
};
const TEST_CASES_MEDIAN: MedianTestCase[] = [
{
title: 'returns zero for empty sorted array',
values: [],
expected: 0,
},
{
title: 'returns the middle value for odd-length sorted array',
values: [1, 3, 5],
expected: 3,
},
{
title: 'returns the average of the two middle values for even-length sorted array',
values: [1, 3, 5, 7],
expected: 4,
},
];
describe('median', () => {
TEST_CASES_MEDIAN.forEach((testCase) => {
test(getTestTitle('median', testCase.title), () => {
const result = median(createNumberItems(testCase.values), item => item.value);
expect(result).toBeCloseTo(testCase.expected);
});
});
});
type PercentileTestCase = {
title: string;
values: number[];
percentileValue: number;
expected: number;
};
const TEST_CASES_PERCENTILE: PercentileTestCase[] = [
{
title: 'returns zero for empty sorted array',
values: [],
percentileValue: 0.5,
expected: 0,
},
{
title: 'returns zero when percentile is smaller than zero',
values: [1, 2, 3],
percentileValue: -0.1,
expected: 0,
},
{
title: 'returns zero when percentile is larger than one',
values: [1, 2, 3],
percentileValue: 1.1,
expected: 0,
},
{
title: 'returns the minimum value for zero percentile',
values: [5, 10, 15, 20],
percentileValue: 0,
expected: 5,
},
{
title: 'returns the maximum value for one percentile',
values: [5, 10, 15, 20],
percentileValue: 1,
expected: 20,
},
{
title: 'returns the exact indexed value when percentile maps to an integer index',
values: [10, 20, 30, 40, 50],
percentileValue: 0.25,
expected: 20,
},
{
title: 'interpolates between neighboring values when percentile maps to a fractional index',
values: [10, 20, 30, 40, 50, 60, 70, 80],
percentileValue: 0.25,
expected: 27.5,
},
];
describe('percentile', () => {
TEST_CASES_PERCENTILE.forEach((testCase) => {
test(getTestTitle('percentile', testCase.title), () => {
const result = percentile(
createNumberItems(testCase.values),
testCase.percentileValue,
item => item.value
);
expect(result).toBeCloseTo(testCase.expected);
});
});
});
type SumMaxNTestCase = {
title: string;
values: number[];
n: number;
expected: number;
};
const TEST_CASES_SUM_MAX_N: SumMaxNTestCase[] = [
{
title: 'returns zero for empty sorted array',
values: [],
n: 3,
expected: 0,
},
{
title: 'returns zero when n is zero',
values: [1, 2, 3],
n: 0,
expected: 0,
},
{
title: 'returns the sum of the largest n values',
values: [1, 2, 3, 4, 5],
n: 2,
expected: 9,
},
{
title: 'returns the sum of all values when n is larger than array length',
values: [1, 2, 3, 4],
n: 10,
expected: 10,
},
];
describe('sumMaxN', () => {
TEST_CASES_SUM_MAX_N.forEach((testCase) => {
test(getTestTitle('sumMaxN', testCase.title), () => {
const result = sumMaxN(createNumberItems(testCase.values), testCase.n, item => item.value);
expect(result).toBe(testCase.expected);
});
});
});
@@ -1,13 +1,12 @@
import fs from 'fs';
import path from 'path';
import { describe, expect, test } from '@jest/globals';
import { describe, expect, it } from 'vitest';
import { DEFAULT_CONTENT } from '@/locales/calendar/chinese/index.ts';
import { itemAndIndex, entries } from '@/core/base.ts';
import type { ChineseCalendarLocaleData } from '@/core/calendar.ts';
import {
type ChineseYearMonthDayInfo,
getChineseYearMonthAllDayInfos,
getChineseYearMonthDayInfo
} from '@/lib/calendar/chinese_calendar.ts';
@@ -45,12 +44,33 @@ const localeData: ChineseCalendarLocaleData = {
'Winter Solstice'
]
};
const ordinalSuffix = ['st', 'nd', 'rd'];
describe('getChineseYearMonthAllDayInfos', () => {
const lines: string[] = fs.readFileSync(path.join(__dirname, 'chinese_calendar_all_data.txt'), 'utf8').replace(/\r/g, '').split('\n');
function lunarMonthOrDayLabel(month: number, day: number): string {
return day === 1
? `${month}${ordinalSuffix[month - 1] ?? 'th'} Lunar Month`.toLowerCase()
: day.toString();
}
type PerDayEntry = {
gregorianDate: string;
gregorianYear: number;
gregorianMonth: number;
gregorianDay: number;
expectedChineseMonthOrDay: string;
expectedSolarTermName: string;
};
function parseCalendarDataFile(): {
allMonthChineseDays: Record<string, string[]>;
allMonthSolarTermNames: Record<string, string[]>;
perDayEntries: PerDayEntry[];
} {
const lines = fs.readFileSync(path.join(__dirname, 'chinese_calendar_all_data.txt'), 'utf8').replace(/\r/g, '').split('\n');
const allMonthChineseDays: Record<string, string[]> = {};
const allMonthSolarTermNames: Record<string, string[]> = {};
const perDayEntries: PerDayEntry[] = [];
let currentMonthChineseDays: string[] = [];
let currentMonthSolarTermNames: string[] = [];
let currentYear: number = 0;
@@ -66,16 +86,17 @@ describe('getChineseYearMonthAllDayInfos', () => {
const gregorianDateItems = gregorianDate.split('/');
const gregorianYear = parseInt(gregorianDateItems[0] as string, 10);
const gregorianMonth = parseInt(gregorianDateItems[1] as string, 10);
const gregorianDay = parseInt(gregorianDateItems[2] as string, 10);
const chineseDay = items[1] as string;
const solarTermName = items.length > 3 ? items[3] as string : '';
perDayEntries.push({ gregorianDate, gregorianYear, gregorianMonth, gregorianDay, expectedChineseMonthOrDay: chineseDay, expectedSolarTermName: solarTermName });
if (currentYear > 0 && currentMonth > 0 && (gregorianYear !== currentYear || gregorianMonth !== currentMonth)) {
allMonthChineseDays[`${currentYear}-${currentMonth}`] = currentMonthChineseDays;
allMonthSolarTermNames[`${currentYear}-${currentMonth}`] = currentMonthSolarTermNames;
currentMonthChineseDays = [];
currentMonthSolarTermNames = [];
currentYear = gregorianYear;
currentMonth = gregorianMonth;
} else if (currentYear === 0 && currentMonth === 0) {
@@ -92,27 +113,27 @@ describe('getChineseYearMonthAllDayInfos', () => {
allMonthChineseDays[`${currentYear}-${currentMonth}`] = currentMonthChineseDays;
allMonthSolarTermNames[`${currentYear}-${currentMonth}`] = currentMonthSolarTermNames;
return { allMonthChineseDays, allMonthSolarTermNames, perDayEntries };
}
const { allMonthChineseDays, allMonthSolarTermNames, perDayEntries } = parseCalendarDataFile();
describe('getChineseYearMonthAllDayInfos', () => {
for (const [yearMonth, monthChineseDays] of entries(allMonthChineseDays)) {
test(`returns correct chinese all dates in month for ${yearMonth}`, () => {
it(`should return correct chinese dates for all days in ${yearMonth}`, () => {
const [yearStr, monthStr] = yearMonth.split('-');
const year = parseInt(yearStr as string);
const month = parseInt(monthStr as string);
const expectedChineseMonthOrDays = monthChineseDays;
const expectedSolarTermNames = allMonthSolarTermNames[yearMonth] as string[];
const actualChineseDates: ChineseYearMonthDayInfo[] | undefined = getChineseYearMonthAllDayInfos({
year: year,
month1base: month
}, localeData);
const actualChineseDates = getChineseYearMonthAllDayInfos({ year, month1base: month }, localeData);
expect(actualChineseDates).toBeDefined();
if (actualChineseDates) {
for (const [actualChineseDate, index] of itemAndIndex(actualChineseDates)) {
const chineseMonthOrDay: string | undefined = actualChineseDate?.day === 1 ? `${actualChineseDate?.month}${ordinalSuffix[actualChineseDate?.month - 1] ?? 'th'} Lunar Month`.toLowerCase() : actualChineseDate?.day.toString();
expect(actualChineseDate).toBeDefined();
expect(chineseMonthOrDay).toBe(expectedChineseMonthOrDays[index]);
expect(lunarMonthOrDayLabel(actualChineseDate!.month, actualChineseDate!.day)).toBe(monthChineseDays[index]);
expect(actualChineseDate?.solarTermName).toBe(expectedSolarTermNames[index]);
}
}
@@ -121,32 +142,12 @@ describe('getChineseYearMonthAllDayInfos', () => {
});
describe('getChineseYearMonthDayInfo', () => {
const lines: string[] = fs.readFileSync(path.join(__dirname, 'chinese_calendar_all_data.txt'), 'utf8').replace(/\r/g, '').split('\n');
for (const line of lines) {
if (!line.trim() || line.startsWith('#')) {
continue;
}
const items = line.split('\t');
const gregorianDate = items[0] as string;
const gregorianDateItems = gregorianDate.split('/');
const gregorianYear = parseInt(gregorianDateItems[0] as string);
const gregorianMonth = parseInt(gregorianDateItems[1] as string);
const gregorianDay = parseInt(gregorianDateItems[2] as string);
const expectedChineseMonthOrDay = items[1] as string;
const expectedSolarTermName = items.length > 3 ? items[3] as string : '';
test(`returns correct chinese date for ${gregorianDate}`, () => {
const actualChineseDate: ChineseYearMonthDayInfo | undefined = getChineseYearMonthDayInfo({
year: gregorianYear,
month: gregorianMonth,
day: gregorianDay
}, localeData);
const actualChineseMonthOrDay: string | undefined = actualChineseDate?.day === 1 ? `${actualChineseDate?.month}${ordinalSuffix[actualChineseDate?.month - 1] ?? 'th'} Lunar Month`.toLowerCase() : actualChineseDate?.day.toString();
for (const { gregorianDate, gregorianYear, gregorianMonth, gregorianDay, expectedChineseMonthOrDay, expectedSolarTermName } of perDayEntries) {
it(`should return correct chinese date for ${gregorianDate}`, () => {
const actualChineseDate = getChineseYearMonthDayInfo({ year: gregorianYear, month: gregorianMonth, day: gregorianDay }, localeData);
expect(actualChineseDate).toBeDefined();
expect(actualChineseMonthOrDay).toBe(expectedChineseMonthOrDay.toLowerCase());
expect(lunarMonthOrDayLabel(actualChineseDate!.month, actualChineseDate!.day)).toBe(expectedChineseMonthOrDay.toLowerCase());
expect(actualChineseDate?.solarTermName).toBe(expectedSolarTermName);
});
}
-6
View File
@@ -1,6 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"verbatimModuleSyntax": false
}
}
+1 -1
View File
@@ -18,6 +18,6 @@
"include": [
"src/**/*",
"vite.config.ts",
"jest.config.ts"
"vitest.config.ts"
]
}
+23
View File
@@ -0,0 +1,23 @@
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
import path from 'node:path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
test: {
environment: 'node',
clearMocks: true,
restoreMocks: true,
mockReset: true,
isolate: true,
coverage: {
provider: 'v8',
reporter: ['text', 'html']
}
}
})