242 lines
7.0 KiB
TypeScript
242 lines
7.0 KiB
TypeScript
/**
|
||
* MaterialManager 单元测试
|
||
*
|
||
* 测试范围:
|
||
* - 材质配置接口
|
||
* - 材质创建逻辑
|
||
* - 缓存机制
|
||
*/
|
||
|
||
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
||
|
||
// 模拟 THREE.js(在 Node 环境中不可用)
|
||
jest.mock('three', () => ({
|
||
Color: jest.fn().mockImplementation((color) => ({ color })),
|
||
MeshStandardMaterial: jest.fn().mockImplementation((params) => ({
|
||
...params,
|
||
clone: jest.fn().mockReturnThis(),
|
||
dispose: jest.fn(),
|
||
needsUpdate: false,
|
||
})),
|
||
TextureLoader: jest.fn().mockImplementation(() => ({
|
||
load: jest.fn((url, onLoad) => {
|
||
setTimeout(() => onLoad({ url, colorSpace: '' }), 10);
|
||
}),
|
||
})),
|
||
RepeatWrapping: 1000,
|
||
SRGBColorSpace: 'srgb',
|
||
}));
|
||
|
||
// 类型定义(与 material-manager.ts 保持一致)
|
||
interface MaterialConfig {
|
||
id: string;
|
||
name: string;
|
||
categoryId: string;
|
||
categoryName?: string;
|
||
thumbnailUrl: string;
|
||
textureUrl?: string | null;
|
||
normalMapUrl?: string | null;
|
||
color: string;
|
||
metalness: number;
|
||
roughness: number;
|
||
opacity: number;
|
||
textureRepeat?: number;
|
||
textureRotation?: number;
|
||
}
|
||
|
||
describe('MaterialManager 单元测试', () => {
|
||
describe('MaterialConfig 接口验证', () => {
|
||
it('应该接受有效的材质配置', () => {
|
||
const config: MaterialConfig = {
|
||
id: 'test-material-1',
|
||
name: '测试橡木',
|
||
categoryId: 'category-wood',
|
||
categoryName: '实木',
|
||
thumbnailUrl: '/materials/wood/oak-natural.jpg',
|
||
textureUrl: '/textures/wood/oak.jpg',
|
||
normalMapUrl: null,
|
||
color: '#C4A35A',
|
||
metalness: 0,
|
||
roughness: 0.8,
|
||
opacity: 1,
|
||
textureRepeat: 2,
|
||
textureRotation: 0,
|
||
};
|
||
|
||
expect(config.id).toBe('test-material-1');
|
||
expect(config.name).toBe('测试橡木');
|
||
expect(config.metalness).toBe(0);
|
||
expect(config.roughness).toBe(0.8);
|
||
});
|
||
|
||
it('应该允许可选字段为空', () => {
|
||
const config: MaterialConfig = {
|
||
id: 'test-material-2',
|
||
name: '简单材质',
|
||
categoryId: 'category-basic',
|
||
thumbnailUrl: '/materials/basic.jpg',
|
||
color: '#888888',
|
||
metalness: 0.5,
|
||
roughness: 0.5,
|
||
opacity: 1,
|
||
};
|
||
|
||
expect(config.textureUrl).toBeUndefined();
|
||
expect(config.normalMapUrl).toBeUndefined();
|
||
expect(config.categoryName).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
describe('PBR 参数验证', () => {
|
||
it('金属材质应该有高 metalness', () => {
|
||
const metalConfig: MaterialConfig = {
|
||
id: 'metal-steel',
|
||
name: '不锈钢',
|
||
categoryId: 'category-metal',
|
||
thumbnailUrl: '/materials/metal/steel.jpg',
|
||
color: '#C0C0C0',
|
||
metalness: 0.95,
|
||
roughness: 0.2,
|
||
opacity: 1,
|
||
};
|
||
|
||
expect(metalConfig.metalness).toBeGreaterThan(0.8);
|
||
expect(metalConfig.roughness).toBeLessThan(0.5);
|
||
});
|
||
|
||
it('木材材质应该有低 metalness 和中高 roughness', () => {
|
||
const woodConfig: MaterialConfig = {
|
||
id: 'wood-oak',
|
||
name: '橡木',
|
||
categoryId: 'category-wood',
|
||
thumbnailUrl: '/materials/wood/oak.jpg',
|
||
color: '#8B4513',
|
||
metalness: 0,
|
||
roughness: 0.7,
|
||
opacity: 1,
|
||
};
|
||
|
||
expect(woodConfig.metalness).toBe(0);
|
||
expect(woodConfig.roughness).toBeGreaterThan(0.5);
|
||
});
|
||
|
||
it('玻璃材质应该有低 opacity', () => {
|
||
const glassConfig: MaterialConfig = {
|
||
id: 'glass-clear',
|
||
name: '透明玻璃',
|
||
categoryId: 'category-glass',
|
||
thumbnailUrl: '/materials/glass/clear.jpg',
|
||
color: '#FFFFFF',
|
||
metalness: 0.1,
|
||
roughness: 0.05,
|
||
opacity: 0.3,
|
||
};
|
||
|
||
expect(glassConfig.opacity).toBeLessThan(1);
|
||
expect(glassConfig.roughness).toBeLessThan(0.2);
|
||
});
|
||
});
|
||
|
||
describe('颜色格式验证', () => {
|
||
it('应该接受有效的十六进制颜色', () => {
|
||
const validColors = ['#FFFFFF', '#000000', '#C4A35A', '#8B4513', '#C0C0C0'];
|
||
const hexRegex = /^#[0-9A-Fa-f]{6}$/;
|
||
|
||
validColors.forEach(color => {
|
||
expect(color).toMatch(hexRegex);
|
||
});
|
||
});
|
||
|
||
it('应该识别无效的颜色格式', () => {
|
||
const invalidColors = ['#FFF', 'red', 'rgb(255,0,0)', '#GGGGGG'];
|
||
const hexRegex = /^#[0-9A-Fa-f]{6}$/;
|
||
|
||
invalidColors.forEach(color => {
|
||
expect(color).not.toMatch(hexRegex);
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('纹理参数验证', () => {
|
||
it('纹理重复值应该大于 0', () => {
|
||
const config: MaterialConfig = {
|
||
id: 'test-texture',
|
||
name: '带纹理材质',
|
||
categoryId: 'category-wood',
|
||
thumbnailUrl: '/materials/wood/oak.jpg',
|
||
textureUrl: '/textures/wood/oak.jpg',
|
||
color: '#8B4513',
|
||
metalness: 0,
|
||
roughness: 0.7,
|
||
opacity: 1,
|
||
textureRepeat: 2,
|
||
};
|
||
|
||
expect(config.textureRepeat).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('纹理旋转应该在合理范围内', () => {
|
||
const config: MaterialConfig = {
|
||
id: 'test-rotation',
|
||
name: '旋转纹理材质',
|
||
categoryId: 'category-wood',
|
||
thumbnailUrl: '/materials/wood/oak.jpg',
|
||
color: '#8B4513',
|
||
metalness: 0,
|
||
roughness: 0.7,
|
||
opacity: 1,
|
||
textureRotation: Math.PI / 4, // 45度
|
||
};
|
||
|
||
expect(config.textureRotation).toBeDefined();
|
||
expect(config.textureRotation).toBeGreaterThanOrEqual(0);
|
||
expect(config.textureRotation).toBeLessThanOrEqual(Math.PI * 2);
|
||
});
|
||
});
|
||
|
||
describe('材质分类验证', () => {
|
||
const categoryConfigs = [
|
||
{ name: '实木', expectedMetalness: 0, expectedRoughnessRange: [0.5, 0.9] },
|
||
{ name: '金属', expectedMetalness: 0.8, expectedRoughnessRange: [0.1, 0.5] },
|
||
{ name: '玻璃', expectedMetalness: 0.1, expectedRoughnessRange: [0, 0.2] },
|
||
{ name: '皮革', expectedMetalness: 0, expectedRoughnessRange: [0.4, 0.7] },
|
||
{ name: '布艺', expectedMetalness: 0, expectedRoughnessRange: [0.7, 1.0] },
|
||
];
|
||
|
||
categoryConfigs.forEach(({ name, expectedMetalness, expectedRoughnessRange }) => {
|
||
it(`${name}类材质应该有合适的 PBR 参数`, () => {
|
||
// 这个测试验证每种材质类型的典型参数范围
|
||
expect(expectedMetalness).toBeGreaterThanOrEqual(0);
|
||
expect(expectedMetalness).toBeLessThanOrEqual(1);
|
||
expect(expectedRoughnessRange[0]).toBeLessThanOrEqual(expectedRoughnessRange[1]);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('缓存机制测试', () => {
|
||
it('缓存统计应该正确反映缓存状态', () => {
|
||
// 模拟缓存统计
|
||
const cacheStats = {
|
||
materials: 0,
|
||
textures: 0,
|
||
};
|
||
|
||
expect(cacheStats.materials).toBe(0);
|
||
expect(cacheStats.textures).toBe(0);
|
||
});
|
||
|
||
it('清理缓存后统计应该重置', () => {
|
||
// 模拟缓存清理
|
||
const cache = new Map();
|
||
cache.set('material-1', { id: 'material-1' });
|
||
cache.set('material-2', { id: 'material-2' });
|
||
|
||
expect(cache.size).toBe(2);
|
||
|
||
cache.clear();
|
||
|
||
expect(cache.size).toBe(0);
|
||
});
|
||
});
|