furniture-3d-system-Wu/tests/lib/material-manager.test.ts

242 lines
7.0 KiB
TypeScript
Raw 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.

/**
* 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);
});
});