update at 2026-01-21 21:48:08
This commit is contained in:
@@ -22,26 +22,23 @@ tests/
|
||||
- 角度数组和半径数组的有效性验证
|
||||
- 不同类型示例的存在性检查
|
||||
|
||||
### 2. utils.test.ts (48 个测试)
|
||||
### 2. utils.test.ts (39 个测试)
|
||||
测试所有工具函数的核心逻辑:
|
||||
- **polarToXY**: 极坐标转换
|
||||
- **normalizeDeg**: 角度归一化
|
||||
- **annularSectorCentroid**: 扇形形心计算
|
||||
- **annularSectorPath**: SVG 路径生成
|
||||
- **annularSectorInsetPath**: 内缩路径生成
|
||||
- **calculateLabelRotation**: 文字旋转角度
|
||||
- **generateSectorColor**: 扇区颜色生成
|
||||
- **generateTextPath**: 文字路径生成
|
||||
- **generateVerticalTextPath**: 竖排文字路径
|
||||
- **getTextColorForBackground**: 文字颜色适配
|
||||
- **calculateSectorFontSize**: 字体大小计算
|
||||
|
||||
### 3. useLuopan.test.ts (32 个测试)
|
||||
### 3. useLuopan.test.ts (29 个测试)
|
||||
测试罗盘业务逻辑组合函数:
|
||||
- 基本功能和返回值验证
|
||||
- 扇区生成逻辑
|
||||
- 文字位置模式(middle/centroid)
|
||||
- 文字方向判断
|
||||
- 竖排文字判断
|
||||
- 内部填色逻辑
|
||||
- 响应式更新
|
||||
@@ -90,7 +87,7 @@ npm test -- --coverage
|
||||
## 测试统计
|
||||
|
||||
- **总测试文件**: 4 个
|
||||
- **总测试用例**: 159 个
|
||||
- **总测试用例**: 147 个
|
||||
- **测试通过率**: 100%
|
||||
- **测试运行时间**: ~1.8 秒
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ describe('useLuopan', () => {
|
||||
expect(result).toHaveProperty('rings');
|
||||
expect(result).toHaveProperty('outerMost');
|
||||
expect(result).toHaveProperty('sectors');
|
||||
expect(result).toHaveProperty('getLabelTransform');
|
||||
expect(result).toHaveProperty('toXY');
|
||||
});
|
||||
|
||||
@@ -92,7 +91,6 @@ describe('useLuopan', () => {
|
||||
expect(sector).toHaveProperty('path');
|
||||
expect(sector).toHaveProperty('textPath');
|
||||
expect(sector).toHaveProperty('textPathId');
|
||||
expect(sector).toHaveProperty('needReverse');
|
||||
expect(sector).toHaveProperty('isVertical');
|
||||
expect(sector).toHaveProperty('fontSize');
|
||||
});
|
||||
@@ -161,8 +159,8 @@ describe('useLuopan', () => {
|
||||
const { sectors } = useLuopan(example, textRadialPosition);
|
||||
|
||||
sectors.value.forEach((sector) => {
|
||||
expect(sector.fill).toMatch(/^(hsl\(.*\)|#[0-9a-fA-F]{6}|#ffffff)$/);
|
||||
expect(sector.textColor).toMatch(/^(#[0-9a-fA-F]{6}|#ffffff)$/);
|
||||
expect(sector.fill).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
expect(sector.textColor).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -248,38 +246,6 @@ describe('useLuopan', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('文字方向', () => {
|
||||
it('应该为左半圆的扇区设置 needReverse', () => {
|
||||
const example = ref(createMockExample());
|
||||
const textRadialPosition = ref<TextRadialPosition>('middle');
|
||||
|
||||
const { sectors } = useLuopan(example, textRadialPosition);
|
||||
|
||||
// 中间角度在 (90, 270) 范围内的扇区应该需要反向
|
||||
const leftSectors = sectors.value.filter(
|
||||
(s) => s.aMidDeg > 90 && s.aMidDeg < 270
|
||||
);
|
||||
leftSectors.forEach((sector) => {
|
||||
expect(sector.needReverse).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('应该为右半圆的扇区不设置 needReverse', () => {
|
||||
const example = ref(createMockExample());
|
||||
const textRadialPosition = ref<TextRadialPosition>('middle');
|
||||
|
||||
const { sectors } = useLuopan(example, textRadialPosition);
|
||||
|
||||
// 中间角度在 [0, 90] 或 [270, 360] 范围内的扇区不需要反向
|
||||
const rightSectors = sectors.value.filter(
|
||||
(s) => s.aMidDeg <= 90 || s.aMidDeg >= 270
|
||||
);
|
||||
rightSectors.forEach((sector) => {
|
||||
expect(sector.needReverse).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('竖排文字判断', () => {
|
||||
it('应该为窄扇区设置 isVertical', () => {
|
||||
const example = ref({
|
||||
@@ -384,21 +350,6 @@ describe('useLuopan', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLabelTransform', () => {
|
||||
it('应该返回有效的 SVG transform 字符串', () => {
|
||||
const example = ref(createMockExample());
|
||||
const textRadialPosition = ref<TextRadialPosition>('middle');
|
||||
|
||||
const { sectors, getLabelTransform } = useLuopan(example, textRadialPosition);
|
||||
|
||||
const sector = sectors.value[0];
|
||||
const transform = getLabelTransform(sector);
|
||||
|
||||
expect(transform).toContain('translate');
|
||||
expect(transform).toContain('rotate');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toXY', () => {
|
||||
it('应该正确转换极坐标', () => {
|
||||
const example = ref(createMockExample());
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
annularSectorCentroid,
|
||||
annularSectorPath,
|
||||
annularSectorInsetPath,
|
||||
calculateLabelRotation,
|
||||
generateSectorColor,
|
||||
generateTextPath,
|
||||
generateVerticalTextPath,
|
||||
getTextColorForBackground,
|
||||
@@ -142,73 +140,6 @@ describe('annularSectorPath', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateLabelRotation', () => {
|
||||
it('应该在上半圆不进行翻转', () => {
|
||||
expect(calculateLabelRotation(0)).toBe(0);
|
||||
expect(calculateLabelRotation(45)).toBe(45);
|
||||
expect(calculateLabelRotation(90)).toBe(90);
|
||||
expect(calculateLabelRotation(180)).toBe(180);
|
||||
});
|
||||
|
||||
it('应该在下半圆翻转180°避免倒字', () => {
|
||||
expect(calculateLabelRotation(181)).toBe(361); // 181 + 180
|
||||
expect(calculateLabelRotation(270)).toBe(450); // 270 + 180
|
||||
expect(calculateLabelRotation(359)).toBe(539); // 359 + 180
|
||||
});
|
||||
|
||||
it('应该在边界值正确处理', () => {
|
||||
expect(calculateLabelRotation(180)).toBe(180); // 等于180不翻转
|
||||
expect(calculateLabelRotation(360)).toBe(360); // 等于360不翻转
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSectorColor', () => {
|
||||
it('应该生成有效的 HSL 颜色', () => {
|
||||
const color = generateSectorColor(0, 0);
|
||||
expect(color).toMatch(/^hsl\(\d+(\.\d+)? \d+% \d+%\)$/);
|
||||
});
|
||||
|
||||
it('应该根据层索引改变亮度', () => {
|
||||
const color1 = generateSectorColor(0, 0);
|
||||
const color2 = generateSectorColor(1, 0);
|
||||
const color3 = generateSectorColor(2, 0);
|
||||
|
||||
// 提取亮度值
|
||||
const light1 = parseInt(color1.match(/(\d+)%\)$/)?.[1] || '0');
|
||||
const light2 = parseInt(color2.match(/(\d+)%\)$/)?.[1] || '0');
|
||||
const light3 = parseInt(color3.match(/(\d+)%\)$/)?.[1] || '0');
|
||||
|
||||
expect(light1).toBeGreaterThan(light2);
|
||||
expect(light2).toBeGreaterThan(light3);
|
||||
});
|
||||
|
||||
it('应该根据扇区索引改变色相', () => {
|
||||
const color1 = generateSectorColor(0, 0);
|
||||
const color2 = generateSectorColor(0, 6);
|
||||
|
||||
// 提取色相值
|
||||
const hue1 = parseInt(color1.match(/^hsl\((\d+(\.\d+)?)/)?.[1] || '0');
|
||||
const hue2 = parseInt(color2.match(/^hsl\((\d+(\.\d+)?)/)?.[1] || '0');
|
||||
|
||||
expect(hue1).not.toBe(hue2);
|
||||
});
|
||||
|
||||
it('应该为单层生成颜色', () => {
|
||||
const color = generateSectorColor(0, 0, 1, 24);
|
||||
expect(color).toMatch(/^hsl\(\d+(\.\d+)? \d+% \d+%\)$/);
|
||||
});
|
||||
|
||||
it('应该处理大量层数', () => {
|
||||
const color1 = generateSectorColor(0, 0, 31, 24);
|
||||
const color2 = generateSectorColor(30, 0, 31, 24);
|
||||
|
||||
const light1 = parseInt(color1.match(/(\d+)%\)$/)?.[1] || '0');
|
||||
const light2 = parseInt(color2.match(/(\d+)%\)$/)?.[1] || '0');
|
||||
|
||||
expect(light1).toBeGreaterThan(light2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('annularSectorInsetPath', () => {
|
||||
it('应该生成内缩路径', () => {
|
||||
const path = annularSectorInsetPath(50, 100, 0, 90, 2);
|
||||
@@ -236,33 +167,33 @@ describe('annularSectorInsetPath', () => {
|
||||
|
||||
describe('generateTextPath', () => {
|
||||
it('应该生成正向文字路径', () => {
|
||||
const path = generateTextPath(50, 100, 0, 90, false, 'middle', 12);
|
||||
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, 0, 90, true, 'middle', 12);
|
||||
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, false, 'centroid', 12);
|
||||
const pathMiddle = generateTextPath(50, 100, 0, 90, false, 'middle', 12);
|
||||
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, false, 'middle', 12);
|
||||
const path = generateTextPath(50, 100, 315, 45, 'middle');
|
||||
expect(path).toContain('M ');
|
||||
expect(path).toContain('A ');
|
||||
});
|
||||
|
||||
it('应该处理大角度扇区', () => {
|
||||
const path = generateTextPath(50, 100, 0, 270, false, 'middle', 12);
|
||||
const path = generateTextPath(50, 100, 0, 270, 'middle');
|
||||
expect(path).toContain('M ');
|
||||
expect(path).toContain('A ');
|
||||
});
|
||||
@@ -270,25 +201,25 @@ describe('generateTextPath', () => {
|
||||
|
||||
describe('generateVerticalTextPath', () => {
|
||||
it('应该生成竖排文字路径', () => {
|
||||
const path = generateVerticalTextPath(50, 100, 0, 30, 15, 'middle', 12);
|
||||
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, 15, 'centroid', 12);
|
||||
const pathMiddle = generateVerticalTextPath(50, 100, 0, 30, 15, 'middle', 12);
|
||||
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, 15, 'middle', 12);
|
||||
const path2 = generateVerticalTextPath(50, 100, 0, 30, 180, 'middle', 12);
|
||||
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, 5, 'middle', 12);
|
||||
const path = generateVerticalTextPath(80, 100, 0, 10, 'middle');
|
||||
expect(path).toContain('M ');
|
||||
expect(path).toContain('L ');
|
||||
});
|
||||
@@ -296,22 +227,17 @@ describe('generateVerticalTextPath', () => {
|
||||
|
||||
describe('getTextColorForBackground', () => {
|
||||
it('应该为深色背景返回白色', () => {
|
||||
const color = getTextColorForBackground('hsl(180 70% 25%)');
|
||||
const color = getTextColorForBackground('#111111');
|
||||
expect(color).toBe('#ffffff');
|
||||
});
|
||||
|
||||
it('应该为浅色背景返回深色', () => {
|
||||
const color = getTextColorForBackground('hsl(180 70% 85%)');
|
||||
const color = getTextColorForBackground('#f9fafb');
|
||||
expect(color).toBe('#111827');
|
||||
});
|
||||
|
||||
it('应该处理边界值(50%)', () => {
|
||||
const color = getTextColorForBackground('hsl(180 70% 50%)');
|
||||
expect(color).toBe('#111827');
|
||||
});
|
||||
|
||||
it('应该处理边界值(49%)', () => {
|
||||
const color = getTextColorForBackground('hsl(180 70% 49%)');
|
||||
it('应该支持 HSL 输入', () => {
|
||||
const color = getTextColorForBackground('hsl(180 70% 25%)');
|
||||
expect(color).toBe('#ffffff');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user