test(web): add tests for utility functions (#102)
Add tests for utility modules: - formatters/number.ts: formatHp, formatAge - mapStyles.ts: REGION_STYLES, getRegionTextStyle - procedural.ts: getClusteredTileVariant - theme.ts: GRADE_COLORS, getEntityColor Total: 52 new tests added Closes #87
This commit is contained in:
53
web/src/__tests__/utils/formatters/number.test.ts
Normal file
53
web/src/__tests__/utils/formatters/number.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { formatHp, formatAge } from '@/utils/formatters/number'
|
||||
|
||||
describe('formatters/number', () => {
|
||||
describe('formatHp', () => {
|
||||
it('should format integer HP values', () => {
|
||||
expect(formatHp(100, 200)).toBe('100 / 200')
|
||||
})
|
||||
|
||||
it('should floor decimal current HP', () => {
|
||||
expect(formatHp(99.7, 200)).toBe('99 / 200')
|
||||
expect(formatHp(99.2, 200)).toBe('99 / 200')
|
||||
})
|
||||
|
||||
it('should handle full HP', () => {
|
||||
expect(formatHp(100, 100)).toBe('100 / 100')
|
||||
})
|
||||
|
||||
it('should handle zero HP', () => {
|
||||
expect(formatHp(0, 100)).toBe('0 / 100')
|
||||
})
|
||||
|
||||
it('should handle large HP values', () => {
|
||||
expect(formatHp(12345, 99999)).toBe('12345 / 99999')
|
||||
})
|
||||
|
||||
it('should handle negative HP', () => {
|
||||
expect(formatHp(-10, 100)).toBe('-10 / 100')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatAge', () => {
|
||||
it('should format age and lifespan', () => {
|
||||
expect(formatAge(25, 100)).toBe('25 / 100')
|
||||
})
|
||||
|
||||
it('should handle zero age', () => {
|
||||
expect(formatAge(0, 100)).toBe('0 / 100')
|
||||
})
|
||||
|
||||
it('should handle age equal to lifespan', () => {
|
||||
expect(formatAge(100, 100)).toBe('100 / 100')
|
||||
})
|
||||
|
||||
it('should handle large age values', () => {
|
||||
expect(formatAge(1000, 5000)).toBe('1000 / 5000')
|
||||
})
|
||||
|
||||
it('should handle age exceeding lifespan', () => {
|
||||
expect(formatAge(150, 100)).toBe('150 / 100')
|
||||
})
|
||||
})
|
||||
})
|
||||
64
web/src/__tests__/utils/mapStyles.test.ts
Normal file
64
web/src/__tests__/utils/mapStyles.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { REGION_STYLES, getRegionTextStyle } from '@/utils/mapStyles'
|
||||
|
||||
describe('mapStyles', () => {
|
||||
describe('REGION_STYLES', () => {
|
||||
it('should have sect style', () => {
|
||||
expect(REGION_STYLES.sect).toBeDefined()
|
||||
expect(REGION_STYLES.sect.fontSize).toBe(60)
|
||||
expect(REGION_STYLES.sect.fill).toBe('#ffcc00')
|
||||
})
|
||||
|
||||
it('should have city style', () => {
|
||||
expect(REGION_STYLES.city).toBeDefined()
|
||||
expect(REGION_STYLES.city.fontSize).toBe(72)
|
||||
expect(REGION_STYLES.city.fill).toBe('#ccffcc')
|
||||
})
|
||||
|
||||
it('should have default style', () => {
|
||||
expect(REGION_STYLES.default).toBeDefined()
|
||||
expect(REGION_STYLES.default.fontSize).toBe(72)
|
||||
expect(REGION_STYLES.default.fill).toBe('#ffffff')
|
||||
})
|
||||
|
||||
it('should have consistent font family', () => {
|
||||
const expectedFont = '"Microsoft YaHei", sans-serif'
|
||||
expect(REGION_STYLES.sect.fontFamily).toBe(expectedFont)
|
||||
expect(REGION_STYLES.city.fontFamily).toBe(expectedFont)
|
||||
expect(REGION_STYLES.default.fontFamily).toBe(expectedFont)
|
||||
})
|
||||
|
||||
it('should have drop shadow on all styles', () => {
|
||||
expect(REGION_STYLES.sect.dropShadow).toBeDefined()
|
||||
expect(REGION_STYLES.city.dropShadow).toBeDefined()
|
||||
expect(REGION_STYLES.default.dropShadow).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRegionTextStyle', () => {
|
||||
it('should return sect style for sect type', () => {
|
||||
const style = getRegionTextStyle('sect')
|
||||
expect(style).toBe(REGION_STYLES.sect)
|
||||
})
|
||||
|
||||
it('should return city style for city type', () => {
|
||||
const style = getRegionTextStyle('city')
|
||||
expect(style).toBe(REGION_STYLES.city)
|
||||
})
|
||||
|
||||
it('should return default style for default type', () => {
|
||||
const style = getRegionTextStyle('default')
|
||||
expect(style).toBe(REGION_STYLES.default)
|
||||
})
|
||||
|
||||
it('should return default style for unknown type', () => {
|
||||
const style = getRegionTextStyle('unknown')
|
||||
expect(style).toBe(REGION_STYLES.default)
|
||||
})
|
||||
|
||||
it('should return default style for empty string', () => {
|
||||
const style = getRegionTextStyle('')
|
||||
expect(style).toBe(REGION_STYLES.default)
|
||||
})
|
||||
})
|
||||
})
|
||||
116
web/src/__tests__/utils/procedural.test.ts
Normal file
116
web/src/__tests__/utils/procedural.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { getClusteredTileVariant } from '@/utils/procedural'
|
||||
|
||||
describe('procedural', () => {
|
||||
describe('getClusteredTileVariant', () => {
|
||||
it('should return startIndex when count is 0', () => {
|
||||
const result = getClusteredTileVariant(5, 5, 0)
|
||||
expect(result).toBe(0)
|
||||
})
|
||||
|
||||
it('should return startIndex when count is 1', () => {
|
||||
const result = getClusteredTileVariant(5, 5, 1)
|
||||
expect(result).toBe(0)
|
||||
})
|
||||
|
||||
it('should return startIndex when count is 1 with custom startIndex', () => {
|
||||
const result = getClusteredTileVariant(5, 5, 1, 10)
|
||||
expect(result).toBe(10)
|
||||
})
|
||||
|
||||
it('should return value within valid range for count > 1', () => {
|
||||
const count = 9
|
||||
for (let x = 0; x < 20; x++) {
|
||||
for (let y = 0; y < 20; y++) {
|
||||
const result = getClusteredTileVariant(x, y, count)
|
||||
expect(result).toBeGreaterThanOrEqual(0)
|
||||
expect(result).toBeLessThan(count)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should return value within valid range with custom startIndex', () => {
|
||||
const count = 5
|
||||
const startIndex = 10
|
||||
for (let x = 0; x < 20; x++) {
|
||||
for (let y = 0; y < 20; y++) {
|
||||
const result = getClusteredTileVariant(x, y, count, startIndex)
|
||||
expect(result).toBeGreaterThanOrEqual(startIndex)
|
||||
expect(result).toBeLessThanOrEqual(startIndex + count - 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should be deterministic for same coordinates', () => {
|
||||
const x = 42
|
||||
const y = 17
|
||||
const count = 9
|
||||
|
||||
const result1 = getClusteredTileVariant(x, y, count)
|
||||
const result2 = getClusteredTileVariant(x, y, count)
|
||||
const result3 = getClusteredTileVariant(x, y, count)
|
||||
|
||||
expect(result1).toBe(result2)
|
||||
expect(result2).toBe(result3)
|
||||
})
|
||||
|
||||
it('should produce different values for different coordinates', () => {
|
||||
const count = 9
|
||||
const results = new Set<number>()
|
||||
|
||||
// Sample 100 different coordinates.
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const result = getClusteredTileVariant(i * 7, i * 13, count)
|
||||
results.add(result)
|
||||
}
|
||||
|
||||
// Should produce multiple different values (not all the same).
|
||||
expect(results.size).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should show clustering behavior - nearby coordinates tend to have similar values', () => {
|
||||
const count = 9
|
||||
let similarCount = 0
|
||||
const samples = 50
|
||||
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const x = Math.floor(Math.random() * 100)
|
||||
const y = Math.floor(Math.random() * 100)
|
||||
|
||||
const center = getClusteredTileVariant(x, y, count)
|
||||
const neighbor = getClusteredTileVariant(x + 1, y, count)
|
||||
|
||||
// Count how often neighbors have similar values (within 2).
|
||||
if (Math.abs(center - neighbor) <= 2) {
|
||||
similarCount++
|
||||
}
|
||||
}
|
||||
|
||||
// With clustering, we expect neighbors to be similar more often than random.
|
||||
// Random would give similarity ~44% of the time (5/9 * 9 = 55% chance of diff >= 3).
|
||||
// Clustering should give higher similarity rate.
|
||||
expect(similarCount).toBeGreaterThan(samples * 0.3)
|
||||
})
|
||||
|
||||
it('should handle negative coordinates', () => {
|
||||
const count = 9
|
||||
const result = getClusteredTileVariant(-10, -20, count)
|
||||
expect(result).toBeGreaterThanOrEqual(0)
|
||||
expect(result).toBeLessThan(count)
|
||||
})
|
||||
|
||||
it('should handle large coordinates', () => {
|
||||
const count = 9
|
||||
const result = getClusteredTileVariant(10000, 20000, count)
|
||||
expect(result).toBeGreaterThanOrEqual(0)
|
||||
expect(result).toBeLessThan(count)
|
||||
})
|
||||
|
||||
it('should handle floating point coordinates', () => {
|
||||
const count = 9
|
||||
const result = getClusteredTileVariant(5.5, 7.3, count)
|
||||
expect(result).toBeGreaterThanOrEqual(0)
|
||||
expect(result).toBeLessThan(count)
|
||||
})
|
||||
})
|
||||
})
|
||||
146
web/src/__tests__/utils/theme.test.ts
Normal file
146
web/src/__tests__/utils/theme.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { GRADE_COLORS, getEntityColor } from '@/utils/theme'
|
||||
import type { EffectEntity } from '@/types/core'
|
||||
|
||||
describe('theme', () => {
|
||||
describe('GRADE_COLORS', () => {
|
||||
it('should have purple colors for upper grade items', () => {
|
||||
expect(GRADE_COLORS['上品']).toBe('#c488fd')
|
||||
expect(GRADE_COLORS['宝物']).toBe('#c488fd')
|
||||
expect(GRADE_COLORS['SR']).toBe('#c488fd')
|
||||
expect(GRADE_COLORS['Upper']).toBe('#c488fd')
|
||||
})
|
||||
|
||||
it('should have green colors for middle grade items', () => {
|
||||
expect(GRADE_COLORS['中品']).toBe('#88fdc4')
|
||||
expect(GRADE_COLORS['R']).toBe('#88fdc4')
|
||||
expect(GRADE_COLORS['Middle']).toBe('#88fdc4')
|
||||
})
|
||||
|
||||
it('should have gold colors for artifact grade items', () => {
|
||||
expect(GRADE_COLORS['法宝']).toBe('#fddc88')
|
||||
expect(GRADE_COLORS['SSR']).toBe('#fddc88')
|
||||
expect(GRADE_COLORS['Artifact']).toBe('#fddc88')
|
||||
})
|
||||
|
||||
it('should have default color', () => {
|
||||
expect(GRADE_COLORS['Default']).toBe('#cccccc')
|
||||
})
|
||||
|
||||
it('should have realm colors', () => {
|
||||
expect(GRADE_COLORS['练气']).toBe('#cccccc')
|
||||
expect(GRADE_COLORS['筑基']).toBe('#88fdc4')
|
||||
expect(GRADE_COLORS['金丹']).toBe('#c488fd')
|
||||
expect(GRADE_COLORS['元婴']).toBe('#fddc88')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEntityColor', () => {
|
||||
it('should return undefined for null entity', () => {
|
||||
expect(getEntityColor(null)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for undefined entity', () => {
|
||||
expect(getEntityColor(undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for empty entity', () => {
|
||||
expect(getEntityColor({})).toBeUndefined()
|
||||
})
|
||||
|
||||
describe('RGB array color', () => {
|
||||
it('should convert RGB array to rgb string', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
color: [255, 128, 64]
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('rgb(255,128,64)')
|
||||
})
|
||||
|
||||
it('should handle RGB array with zeros', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
color: [0, 0, 0]
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('rgb(0,0,0)')
|
||||
})
|
||||
|
||||
it('should ignore array with wrong length', () => {
|
||||
const entity = {
|
||||
color: [255, 128] as any
|
||||
}
|
||||
expect(getEntityColor(entity)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('string color', () => {
|
||||
it('should return string color directly', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
color: '#ff0000'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('#ff0000')
|
||||
})
|
||||
|
||||
it('should return named color directly', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
color: 'red'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('red')
|
||||
})
|
||||
})
|
||||
|
||||
describe('grade-based color', () => {
|
||||
it('should return color based on grade', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
grade: '上品'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('#c488fd')
|
||||
})
|
||||
|
||||
it('should return color based on rarity when no grade', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
rarity: 'SR'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('#c488fd')
|
||||
})
|
||||
|
||||
it('should prefer grade over rarity', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
grade: '上品',
|
||||
rarity: '中品'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('#c488fd')
|
||||
})
|
||||
|
||||
it('should match partial grade strings', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
grade: '上品丹药'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('#c488fd')
|
||||
})
|
||||
|
||||
it('should return undefined for unknown grade', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
grade: 'unknown'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('priority', () => {
|
||||
it('should prefer RGB array over grade', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
color: [100, 200, 150],
|
||||
grade: '上品'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('rgb(100,200,150)')
|
||||
})
|
||||
|
||||
it('should prefer string color over grade', () => {
|
||||
const entity: Partial<EffectEntity> = {
|
||||
color: '#custom',
|
||||
grade: '上品'
|
||||
}
|
||||
expect(getEntityColor(entity)).toBe('#custom')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user