Files
lupin-demo/tests/utils.test.ts
2026-01-23 23:20:39 +08:00

296 lines
8.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 工具函数单元测试
* 使用 Vitest 进行测试
*/
import { describe, it, expect } from 'vitest';
import {
polarToXY,
normalizeDeg,
annularSectorCentroid,
annularSectorPath,
annularSectorInsetPath,
generateTextPath,
generateVerticalTextPath,
getTextColorForBackground,
calculateSectorFontSize,
} from '../src/utils';
describe('polarToXY', () => {
it('应该正确转换 0° 角度(北方)', () => {
const result = polarToXY(0, 100);
expect(result.x).toBeCloseTo(0);
expect(result.y).toBeCloseTo(-100);
});
it('应该正确转换 90° 角度(东方)', () => {
const result = polarToXY(90, 100);
expect(result.x).toBeCloseTo(100);
expect(result.y).toBeCloseTo(0);
});
it('应该正确转换 180° 角度(南方)', () => {
const result = polarToXY(180, 100);
expect(result.x).toBeCloseTo(0);
expect(result.y).toBeCloseTo(100);
});
it('应该正确转换 270° 角度(西方)', () => {
const result = polarToXY(270, 100);
expect(result.x).toBeCloseTo(-100);
expect(result.y).toBeCloseTo(0);
});
});
describe('normalizeDeg', () => {
it('应该保持 0-360 范围内的角度不变', () => {
expect(normalizeDeg(45)).toBe(45);
expect(normalizeDeg(180)).toBe(180);
expect(normalizeDeg(359)).toBe(359);
});
it('应该将负角度转换为正角度', () => {
expect(normalizeDeg(-45)).toBe(315);
expect(normalizeDeg(-180)).toBe(180);
expect(normalizeDeg(-90)).toBe(270);
});
it('应该将大于 360 的角度归一化', () => {
expect(normalizeDeg(405)).toBe(45);
expect(normalizeDeg(720)).toBe(0);
expect(normalizeDeg(370)).toBe(10);
});
});
describe('annularSectorCentroid', () => {
it('应该为简单扇形计算正确的形心', () => {
const result = annularSectorCentroid({
rInner: 0,
rOuter: 100,
aStartDeg: 0,
aEndDeg: 90,
});
expect(result.aMidDeg).toBe(45);
expect(result.deltaDeg).toBe(90);
expect(result.rho).toBeGreaterThan(0);
});
it('应该为圆环扇形计算正确的形心', () => {
const result = annularSectorCentroid({
rInner: 50,
rOuter: 100,
aStartDeg: 0,
aEndDeg: 90,
});
expect(result.aMidDeg).toBe(45);
expect(result.deltaDeg).toBe(90);
expect(result.rho).toBeGreaterThan(50);
expect(result.rho).toBeLessThan(100);
});
it('应该处理跨越 0° 的扇形', () => {
const result = annularSectorCentroid({
rInner: 0,
rOuter: 100,
aStartDeg: 315,
aEndDeg: 45,
});
expect(result.aMidDeg).toBe(0);
expect(result.deltaDeg).toBe(90);
});
it('应该返回零形心当内外半径相等时', () => {
const result = annularSectorCentroid({
rInner: 100,
rOuter: 100,
aStartDeg: 0,
aEndDeg: 90,
});
expect(result.cx).toBe(0);
expect(result.cy).toBe(0);
expect(result.rho).toBe(0);
});
});
describe('annularSectorPath', () => {
it('应该生成有效的 SVG 路径', () => {
const path = annularSectorPath(50, 100, 0, 90);
expect(path).toContain('M ');
expect(path).toContain('A ');
expect(path).toContain('L ');
expect(path).toContain('Z');
});
it('应该为纯扇形(内半径为 0生成简化路径', () => {
const path = annularSectorPath(0, 100, 0, 90);
expect(path).toContain('M ');
expect(path).toContain('A ');
expect(path).toContain('L 0 0');
expect(path).toContain('Z');
});
it('应该在大角度时设置 large-arc-flag', () => {
const path = annularSectorPath(50, 100, 0, 270);
// 大角度应包含 `large-arc-flag` = 1
expect(path).toBeTruthy();
});
});
describe('annularSectorInsetPath', () => {
it('应该生成内缩路径', () => {
const path = annularSectorInsetPath(50, 100, 0, 90, 2);
expect(path).toContain('M ');
expect(path).toContain('A ');
expect(path).toContain('Z');
});
it('应该在内缩过大时返回空路径', () => {
const path = annularSectorInsetPath(50, 60, 0, 90, 20);
expect(path).toBe('');
});
it('应该处理小角度扇区', () => {
const path = annularSectorInsetPath(50, 100, 0, 5, 2);
expect(path).toBeTruthy();
});
it('应该处理从圆心开始的扇区', () => {
const path = annularSectorInsetPath(0, 100, 0, 90, 2);
expect(path).toContain('M ');
expect(path).toContain('A ');
});
});
describe('generateTextPath', () => {
it('应该生成正向文字路径', () => {
const path = generateTextPath(50, 100, 0, 90, 'middle');
expect(path).toContain('M ');
expect(path).toContain('A ');
expect(path).toContain('0 1'); // 正向扫描
});
it('应该生成反向文字路径', () => {
const path = generateTextPath(50, 100, 120, 240, 'middle');
expect(path).toContain('M ');
expect(path).toContain('A ');
expect(path).toContain('0 0'); // 反向扫描
});
it('应该在 centroid 模式下使用形心半径', () => {
const pathCentroid = generateTextPath(50, 100, 0, 90, 'centroid');
const pathMiddle = generateTextPath(50, 100, 0, 90, 'middle');
expect(pathCentroid).not.toBe(pathMiddle);
});
it('应该处理跨越360°的扇区', () => {
const path = generateTextPath(50, 100, 315, 45, 'middle');
expect(path).toContain('M ');
expect(path).toContain('A ');
});
it('应该处理大角度扇区', () => {
const path = generateTextPath(50, 100, 0, 270, 'middle');
expect(path).toContain('M ');
expect(path).toContain('A ');
});
});
describe('generateVerticalTextPath', () => {
it('应该生成竖排文字路径', () => {
const path = generateVerticalTextPath(50, 100, 0, 30, 'middle');
expect(path).toContain('M ');
expect(path).toContain('L ');
});
it('应该在 centroid 模式下使用形心', () => {
const pathCentroid = generateVerticalTextPath(50, 100, 0, 30, 'centroid');
const pathMiddle = generateVerticalTextPath(50, 100, 0, 30, 'middle');
expect(pathCentroid).not.toBe(pathMiddle);
});
it('应该处理不同的角度', () => {
const path1 = generateVerticalTextPath(50, 100, 0, 30, 'middle');
const path2 = generateVerticalTextPath(50, 100, 150, 180, 'middle');
expect(path1).not.toBe(path2);
});
it('应该处理窄扇区', () => {
const path = generateVerticalTextPath(80, 100, 0, 10, 'middle');
expect(path).toContain('M ');
expect(path).toContain('L ');
});
});
describe('getTextColorForBackground', () => {
it('应该为深色背景返回白色', () => {
const color = getTextColorForBackground('#111111');
expect(color).toBe('#ffffff');
});
it('应该为浅色背景返回深色', () => {
const color = getTextColorForBackground('#f9fafb');
expect(color).toBe('#111827');
});
it('应该支持 HSL 输入', () => {
const color = getTextColorForBackground('hsl(180 70% 25%)');
expect(color).toBe('#ffffff');
});
it('应该处理无效输入', () => {
const color = getTextColorForBackground('invalid');
expect(color).toBe('#111827');
});
});
describe('calculateSectorFontSize', () => {
it('应该为标准扇区计算字体大小', () => {
const fontSize = calculateSectorFontSize(50, 100, 0, 90, 4, 3, 24);
expect(fontSize).toBeGreaterThan(3);
expect(fontSize).toBeLessThanOrEqual(24);
});
it('应该为窄扇区返回较小的字体', () => {
const narrowSize = calculateSectorFontSize(90, 100, 0, 10, 4, 3, 24);
const wideSize = calculateSectorFontSize(50, 100, 0, 90, 4, 3, 24);
expect(narrowSize).toBeLessThan(wideSize);
});
it('应该根据文字长度调整', () => {
const shortText = calculateSectorFontSize(50, 100, 0, 90, 2, 3, 24);
const longText = calculateSectorFontSize(50, 100, 0, 90, 8, 3, 24);
expect(shortText).toBeGreaterThan(longText);
});
it('应该为小角度扇区应用额外限制', () => {
const size1 = calculateSectorFontSize(50, 100, 0, 10, 2, 3, 24);
const size2 = calculateSectorFontSize(50, 100, 0, 90, 2, 3, 24);
expect(size1).toBeLessThan(size2);
});
it('应该尊重最小字体大小限制', () => {
const fontSize = calculateSectorFontSize(95, 100, 0, 5, 10, 6, 24);
expect(fontSize).toBeGreaterThanOrEqual(6);
});
it('应该尊重最大字体大小限制', () => {
const fontSize = calculateSectorFontSize(0, 200, 0, 180, 1, 3, 20);
expect(fontSize).toBeLessThanOrEqual(20);
});
it('应该处理零文字长度', () => {
const fontSize = calculateSectorFontSize(50, 100, 0, 90, 0, 3, 24);
expect(fontSize).toBe(3);
});
it('应该处理从圆心开始的扇区', () => {
const fontSize = calculateSectorFontSize(0, 100, 0, 90, 4, 3, 24);
expect(fontSize).toBeGreaterThan(3);
expect(fontSize).toBeLessThanOrEqual(24);
});
});