mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 00:35:51 +00:00
feat: 第一版可用 pokemon
This commit is contained in:
107
packages/pokemon/src/__tests__/creature.test.ts
Normal file
107
packages/pokemon/src/__tests__/creature.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import type { SpeciesId, Creature } from '../types'
|
||||
import { generateCreature, calculateStats, getCreatureName, getTotalEV, recalculateLevel } from '../core/creature'
|
||||
import { SPECIES_DATA } from '../data/species'
|
||||
|
||||
describe('generateCreature', () => {
|
||||
test('creates a creature with correct defaults', () => {
|
||||
const c = generateCreature('bulbasaur', 42)
|
||||
expect(c.speciesId).toBe('bulbasaur')
|
||||
expect(c.level).toBe(1)
|
||||
expect(c.xp).toBe(0)
|
||||
expect(c.totalXp).toBe(0)
|
||||
expect(c.friendship).toBe(SPECIES_DATA.bulbasaur.baseHappiness)
|
||||
expect(c.isShiny).toBeDefined()
|
||||
expect(c.id).toBeTruthy()
|
||||
expect(Object.values(c.iv).every((v) => v >= 0 && v <= 31)).toBe(true)
|
||||
expect(Object.values(c.ev).every((v) => v === 0)).toBe(true)
|
||||
})
|
||||
|
||||
test('deterministic IV generation from seed', () => {
|
||||
const c1 = generateCreature('charmander', 12345)
|
||||
const c2 = generateCreature('charmander', 12345)
|
||||
expect(c1.iv).toEqual(c2.iv)
|
||||
})
|
||||
|
||||
test('different seeds produce different IVs', () => {
|
||||
const c1 = generateCreature('squirtle', 100)
|
||||
const c2 = generateCreature('squirtle', 200)
|
||||
expect(c1.iv).not.toEqual(c2.iv)
|
||||
})
|
||||
|
||||
test('all MVP species can be generated', () => {
|
||||
const species: SpeciesId[] = [
|
||||
'bulbasaur', 'ivysaur', 'venusaur',
|
||||
'charmander', 'charmeleon', 'charizard',
|
||||
'squirtle', 'wartortle', 'blastoise',
|
||||
'pikachu',
|
||||
]
|
||||
for (const s of species) {
|
||||
const c = generateCreature(s)
|
||||
expect(c.speciesId).toBe(s)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('calculateStats', () => {
|
||||
test('level 1 stats are reasonable', () => {
|
||||
const c = generateCreature('bulbasaur', 0)
|
||||
const stats = calculateStats(c)
|
||||
// HP at lv1: floor((2*45 + iv + floor(0/4)) * 1/100) + 1 + 10
|
||||
// With any IV: floor((90 + iv) / 100) + 11 = 0 + 11 = 11
|
||||
expect(stats.hp).toBeGreaterThanOrEqual(11)
|
||||
expect(stats.hp).toBeLessThanOrEqual(12)
|
||||
// Attack: floor((2*49 + iv) * 1/100) + 5 = 0 + 5 = 5
|
||||
expect(stats.attack).toBeGreaterThanOrEqual(5)
|
||||
expect(stats.attack).toBeLessThanOrEqual(6)
|
||||
})
|
||||
|
||||
test('stats increase with level', () => {
|
||||
const c1 = generateCreature('charmander', 0)
|
||||
c1.level = 1
|
||||
const stats1 = calculateStats(c1)
|
||||
|
||||
const c50 = { ...c1, level: 50 }
|
||||
const stats50 = calculateStats(c50)
|
||||
// All stats should be higher at level 50
|
||||
expect(stats50.hp).toBeGreaterThan(stats1.hp)
|
||||
expect(stats50.attack).toBeGreaterThan(stats1.attack)
|
||||
})
|
||||
|
||||
test('EVs affect stats', () => {
|
||||
const c = generateCreature('pikachu', 0)
|
||||
const statsNoEV = calculateStats(c)
|
||||
|
||||
const cWithEV = { ...c, ev: { ...c.ev, attack: 252 } }
|
||||
const statsWithEV = calculateStats(cWithEV)
|
||||
|
||||
expect(statsWithEV.attack).toBeGreaterThan(statsNoEV.attack)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCreatureName', () => {
|
||||
test('returns species name when no nickname', () => {
|
||||
const c = generateCreature('pikachu')
|
||||
c.nickname = undefined
|
||||
expect(getCreatureName(c)).toBe('Pikachu')
|
||||
})
|
||||
|
||||
test('returns nickname when set', () => {
|
||||
const c = generateCreature('pikachu')
|
||||
c.nickname = 'Sparky'
|
||||
expect(getCreatureName(c)).toBe('Sparky')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTotalEV', () => {
|
||||
test('returns 0 for new creature', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
expect(getTotalEV(c)).toBe(0)
|
||||
})
|
||||
|
||||
test('sums all EV values', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
c.ev = { hp: 10, attack: 20, defense: 30, spAtk: 40, spDef: 50, speed: 60 }
|
||||
expect(getTotalEV(c)).toBe(210)
|
||||
})
|
||||
})
|
||||
79
packages/pokemon/src/__tests__/effort.test.ts
Normal file
79
packages/pokemon/src/__tests__/effort.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { describe, test, expect, beforeEach } from 'bun:test'
|
||||
import { generateCreature } from '../core/creature'
|
||||
import { awardEV, awardTurnEV, getEVSummary, resetEVCooldowns } from '../core/effort'
|
||||
import { MAX_EV_PER_STAT, MAX_EV_TOTAL } from '../data/evMapping'
|
||||
|
||||
beforeEach(() => {
|
||||
resetEVCooldowns()
|
||||
})
|
||||
|
||||
describe('awardEV', () => {
|
||||
test('mapped tool awards correct EV', () => {
|
||||
let c = generateCreature('bulbasaur')
|
||||
// Clear cooldown by using old timestamp
|
||||
c = awardEV(c, 'Bash', 0)
|
||||
expect(c.ev.attack).toBeGreaterThan(0)
|
||||
expect(c.ev.speed).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('unmapped tool awards random EV', () => {
|
||||
let c = generateCreature('bulbasaur')
|
||||
c = awardEV(c, 'UnknownTool', 0)
|
||||
const totalEV = Object.values(c.ev).reduce((a, b) => a + b, 0)
|
||||
expect(totalEV).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('cooldown prevents repeated awards', () => {
|
||||
const now = Date.now()
|
||||
let c = generateCreature('bulbasaur')
|
||||
c = awardEV(c, 'Bash', now)
|
||||
const ev1 = { ...c.ev }
|
||||
c = awardEV(c, 'Bash', now + 1000) // Within 30s cooldown
|
||||
expect(c.ev).toEqual(ev1) // No change
|
||||
})
|
||||
|
||||
test('respects per-stat EV cap', () => {
|
||||
let c = generateCreature('bulbasaur')
|
||||
// Bash gives attack:2 + speed:1
|
||||
for (let i = 0; i < 200; i++) {
|
||||
c = awardEV(c, 'Bash', i * 60000) // Each call 60s apart (past cooldown)
|
||||
}
|
||||
expect(c.ev.attack).toBeLessThanOrEqual(MAX_EV_PER_STAT)
|
||||
})
|
||||
|
||||
test('respects total EV cap', () => {
|
||||
let c = generateCreature('bulbasaur')
|
||||
const tools = ['Bash', 'Edit', 'Write', 'Read', 'Grep', 'Glob', 'Agent', 'WebSearch', 'WebFetch']
|
||||
for (let i = 0; i < 200; i++) {
|
||||
for (const tool of tools) {
|
||||
c = awardEV(c, tool, (i * tools.length + tools.indexOf(tool)) * 60000)
|
||||
}
|
||||
}
|
||||
const total = Object.values(c.ev).reduce((a, b) => a + b, 0)
|
||||
expect(total).toBeLessThanOrEqual(MAX_EV_TOTAL)
|
||||
})
|
||||
})
|
||||
|
||||
describe('awardTurnEV', () => {
|
||||
test('awards EV for multiple tools', () => {
|
||||
let c = generateCreature('bulbasaur')
|
||||
c = awardTurnEV(c, ['Bash', 'Read', 'Write'], 0)
|
||||
const totalEV = Object.values(c.ev).reduce((a, b) => a + b, 0)
|
||||
expect(totalEV).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEVSummary', () => {
|
||||
test('returns "None" for new creature', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
expect(getEVSummary(c)).toBe('None')
|
||||
})
|
||||
|
||||
test('shows stat breakdown', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
c.ev = { hp: 0, attack: 5, defense: 0, spAtk: 3, spDef: 0, speed: 0 }
|
||||
const summary = getEVSummary(c)
|
||||
expect(summary).toContain('ATK+5')
|
||||
expect(summary).toContain('SPA+3')
|
||||
})
|
||||
})
|
||||
87
packages/pokemon/src/__tests__/egg.test.ts
Normal file
87
packages/pokemon/src/__tests__/egg.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { checkEggEligibility, generateEgg, advanceEggSteps, isEggReadyToHatch } from '../core/egg'
|
||||
import type { BuddyData } from '../types'
|
||||
import { generateCreature } from '../core/creature'
|
||||
|
||||
function makeBuddyData(overrides: Partial<BuddyData['stats']> = {}): BuddyData {
|
||||
return {
|
||||
version: 1,
|
||||
activeCreatureId: 'test',
|
||||
creatures: [generateCreature('bulbasaur')],
|
||||
eggs: [],
|
||||
dex: [{ speciesId: 'bulbasaur', discoveredAt: Date.now(), caughtCount: 1, bestLevel: 1 }],
|
||||
stats: {
|
||||
totalTurns: 50,
|
||||
consecutiveDays: 7,
|
||||
lastActiveDate: new Date().toISOString().split('T')[0],
|
||||
totalEggsObtained: 0,
|
||||
totalEvolutions: 0,
|
||||
...overrides,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('checkEggEligibility', () => {
|
||||
test('eligible when conditions met', () => {
|
||||
const data = makeBuddyData()
|
||||
expect(checkEggEligibility(data)).toBe(true)
|
||||
})
|
||||
|
||||
test('not eligible with existing egg', () => {
|
||||
const data = makeBuddyData()
|
||||
data.eggs = [{ id: 'test', obtainedAt: Date.now(), stepsRemaining: 1000, totalSteps: 3000, speciesId: 'pikachu' }]
|
||||
expect(checkEggEligibility(data)).toBe(false)
|
||||
})
|
||||
|
||||
test('not eligible with low consecutive days', () => {
|
||||
const data = makeBuddyData({ consecutiveDays: 3 })
|
||||
expect(checkEggEligibility(data)).toBe(false)
|
||||
})
|
||||
|
||||
test('not eligible when turns not multiple of 50', () => {
|
||||
const data = makeBuddyData({ totalTurns: 51 })
|
||||
expect(checkEggEligibility(data)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateEgg', () => {
|
||||
test('prefers uncollected species', () => {
|
||||
const data = makeBuddyData()
|
||||
// Already have bulbasaur, so egg should prefer others
|
||||
const egg = generateEgg(data)
|
||||
expect(egg.speciesId).not.toBe('bulbasaur')
|
||||
})
|
||||
|
||||
test('egg has valid steps', () => {
|
||||
const data = makeBuddyData()
|
||||
const egg = generateEgg(data)
|
||||
expect(egg.stepsRemaining).toBeGreaterThan(0)
|
||||
expect(egg.totalSteps).toBe(egg.stepsRemaining)
|
||||
})
|
||||
})
|
||||
|
||||
describe('advanceEggSteps', () => {
|
||||
test('reduces steps remaining', () => {
|
||||
const egg = { id: 'test', obtainedAt: Date.now(), stepsRemaining: 100, totalSteps: 200, speciesId: 'pikachu' as const }
|
||||
const advanced = advanceEggSteps(egg, 30)
|
||||
expect(advanced.stepsRemaining).toBe(70)
|
||||
})
|
||||
|
||||
test('steps do not go below 0', () => {
|
||||
const egg = { id: 'test', obtainedAt: Date.now(), stepsRemaining: 10, totalSteps: 200, speciesId: 'pikachu' as const }
|
||||
const advanced = advanceEggSteps(egg, 50)
|
||||
expect(advanced.stepsRemaining).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isEggReadyToHatch', () => {
|
||||
test('ready when steps = 0', () => {
|
||||
const egg = { id: 'test', obtainedAt: Date.now(), stepsRemaining: 0, totalSteps: 200, speciesId: 'pikachu' as const }
|
||||
expect(isEggReadyToHatch(egg)).toBe(true)
|
||||
})
|
||||
|
||||
test('not ready when steps > 0', () => {
|
||||
const egg = { id: 'test', obtainedAt: Date.now(), stepsRemaining: 1, totalSteps: 200, speciesId: 'pikachu' as const }
|
||||
expect(isEggReadyToHatch(egg)).toBe(false)
|
||||
})
|
||||
})
|
||||
91
packages/pokemon/src/__tests__/evolution.test.ts
Normal file
91
packages/pokemon/src/__tests__/evolution.test.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { checkEvolution, evolve, canEvolveFurther } from '../core/evolution'
|
||||
|
||||
describe('checkEvolution', () => {
|
||||
test('bulbasaur at level 15 cannot evolve', () => {
|
||||
const creature = { speciesId: 'bulbasaur' as const, level: 15, friendship: 70 } as any
|
||||
expect(checkEvolution(creature)).toBeNull()
|
||||
})
|
||||
|
||||
test('bulbasaur at level 16 can evolve into ivysaur', () => {
|
||||
const creature = { speciesId: 'bulbasaur' as const, level: 16, friendship: 70 } as any
|
||||
const result = checkEvolution(creature)
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.from).toBe('bulbasaur')
|
||||
expect(result!.to).toBe('ivysaur')
|
||||
})
|
||||
|
||||
test('charmander at level 16 evolves into charmeleon', () => {
|
||||
const creature = { speciesId: 'charmander' as const, level: 16, friendship: 70 } as any
|
||||
const result = checkEvolution(creature)
|
||||
expect(result!.to).toBe('charmeleon')
|
||||
})
|
||||
|
||||
test('charmeleon at level 36 evolves into charizard', () => {
|
||||
const creature = { speciesId: 'charmeleon' as const, level: 36, friendship: 70 } as any
|
||||
const result = checkEvolution(creature)
|
||||
expect(result!.to).toBe('charizard')
|
||||
})
|
||||
|
||||
test('squirtle at level 16 evolves into wartortle', () => {
|
||||
const creature = { speciesId: 'squirtle' as const, level: 16, friendship: 70 } as any
|
||||
const result = checkEvolution(creature)
|
||||
expect(result!.to).toBe('wartortle')
|
||||
})
|
||||
|
||||
test('wartortle at level 36 evolves into blastoise', () => {
|
||||
const creature = { speciesId: 'wartortle' as const, level: 36, friendship: 70 } as any
|
||||
const result = checkEvolution(creature)
|
||||
expect(result!.to).toBe('blastoise')
|
||||
})
|
||||
|
||||
test('venusaur cannot evolve further', () => {
|
||||
const creature = { speciesId: 'venusaur' as const, level: 50, friendship: 70 } as any
|
||||
expect(checkEvolution(creature)).toBeNull()
|
||||
})
|
||||
|
||||
test('pikachu cannot evolve in MVP', () => {
|
||||
const creature = { speciesId: 'pikachu' as const, level: 50, friendship: 70 } as any
|
||||
expect(checkEvolution(creature)).toBeNull()
|
||||
})
|
||||
|
||||
test('level 100 bulbasaur can still evolve (level >= minLevel)', () => {
|
||||
const creature = { speciesId: 'bulbasaur' as const, level: 100, friendship: 70 } as any
|
||||
const result = checkEvolution(creature)
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.to).toBe('ivysaur')
|
||||
})
|
||||
})
|
||||
|
||||
describe('evolve', () => {
|
||||
test('changes species and boosts friendship', () => {
|
||||
const creature = { speciesId: 'bulbasaur' as const, friendship: 70, level: 16 } as any
|
||||
const evolved = evolve(creature, 'ivysaur')
|
||||
expect(evolved.speciesId).toBe('ivysaur')
|
||||
expect(evolved.friendship).toBe(80) // +10 friendship on evolution
|
||||
})
|
||||
})
|
||||
|
||||
describe('canEvolveFurther', () => {
|
||||
test('starter species can evolve', () => {
|
||||
expect(canEvolveFurther('bulbasaur')).toBe(true)
|
||||
expect(canEvolveFurther('charmander')).toBe(true)
|
||||
expect(canEvolveFurther('squirtle')).toBe(true)
|
||||
})
|
||||
|
||||
test('middle evolution can evolve', () => {
|
||||
expect(canEvolveFurther('ivysaur')).toBe(true)
|
||||
expect(canEvolveFurther('charmeleon')).toBe(true)
|
||||
expect(canEvolveFurther('wartortle')).toBe(true)
|
||||
})
|
||||
|
||||
test('final evolution cannot evolve', () => {
|
||||
expect(canEvolveFurther('venusaur')).toBe(false)
|
||||
expect(canEvolveFurther('charizard')).toBe(false)
|
||||
expect(canEvolveFurther('blastoise')).toBe(false)
|
||||
})
|
||||
|
||||
test('pikachu cannot evolve in MVP', () => {
|
||||
expect(canEvolveFurther('pikachu')).toBe(false)
|
||||
})
|
||||
})
|
||||
84
packages/pokemon/src/__tests__/experience.test.ts
Normal file
84
packages/pokemon/src/__tests__/experience.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { generateCreature } from '../core/creature'
|
||||
import { awardXP, getXpProgress } from '../core/experience'
|
||||
import { xpForLevel, levelFromXp } from '../data/xpTable'
|
||||
|
||||
describe('xpForLevel', () => {
|
||||
test('level 1 requires 0 XP', () => {
|
||||
expect(xpForLevel(1, 'medium-slow')).toBe(0)
|
||||
})
|
||||
|
||||
test('medium-fast: level N requires N^3 XP', () => {
|
||||
expect(xpForLevel(10, 'medium-fast')).toBe(1000)
|
||||
expect(xpForLevel(100, 'medium-fast')).toBe(1000000)
|
||||
})
|
||||
|
||||
test('fast: level N requires floor(N^3 * 4/5)', () => {
|
||||
expect(xpForLevel(10, 'fast')).toBe(Math.floor(1000 * 4 / 5)) // 800
|
||||
})
|
||||
|
||||
test('slow: level N requires floor(N^3 * 5/4)', () => {
|
||||
expect(xpForLevel(10, 'slow')).toBe(Math.floor(1000 * 5 / 4))
|
||||
})
|
||||
|
||||
test('higher levels require more XP', () => {
|
||||
for (let i = 2; i < 99; i++) {
|
||||
expect(xpForLevel(i + 1, 'medium-slow')).toBeGreaterThan(xpForLevel(i, 'medium-slow'))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('levelFromXp', () => {
|
||||
test('0 XP = level 1', () => {
|
||||
expect(levelFromXp(0, 'medium-fast')).toBe(1)
|
||||
})
|
||||
|
||||
test('roundtrip: level → XP → level', () => {
|
||||
for (const growth of ['slow', 'medium-slow', 'medium-fast', 'fast'] as const) {
|
||||
for (const level of [1, 5, 10, 25, 50, 75, 100]) {
|
||||
const xp = xpForLevel(level, growth)
|
||||
expect(levelFromXp(xp, growth)).toBe(level)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('XP slightly below threshold stays at lower level', () => {
|
||||
const xp20 = xpForLevel(20, 'medium-fast')
|
||||
expect(levelFromXp(xp20 - 1, 'medium-fast')).toBe(19)
|
||||
})
|
||||
})
|
||||
|
||||
describe('awardXP', () => {
|
||||
test('awards XP and returns updated creature', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
const result = awardXP(c, 10)
|
||||
expect(result.creature.totalXp).toBe(10)
|
||||
expect(result.leveledUp).toBeDefined()
|
||||
})
|
||||
|
||||
test('large XP can cause level up', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
// Award enough XP for several levels
|
||||
const result = awardXP(c, 10000)
|
||||
expect(result.creature.level).toBeGreaterThan(1)
|
||||
expect(result.leveledUp).toBe(true)
|
||||
})
|
||||
|
||||
test('level capped at 100', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
c.level = 100
|
||||
c.totalXp = 1000000
|
||||
const result = awardXP(c, 999999)
|
||||
expect(result.creature.level).toBe(100)
|
||||
expect(result.leveledUp).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getXpProgress', () => {
|
||||
test('new creature has 0 XP progress', () => {
|
||||
const c = generateCreature('bulbasaur')
|
||||
const progress = getXpProgress(c)
|
||||
expect(progress.current).toBe(0)
|
||||
expect(progress.percentage).toBe(0)
|
||||
})
|
||||
})
|
||||
51
packages/pokemon/src/__tests__/gender.test.ts
Normal file
51
packages/pokemon/src/__tests__/gender.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { determineGender, getGenderSymbol } from '../core/gender'
|
||||
import { SPECIES_DATA } from '../data/species'
|
||||
|
||||
describe('determineGender', () => {
|
||||
test('genderless species', () => {
|
||||
// Pikachu has genderRate 4 (50% female)
|
||||
// Venusaur has genderRate 1 (12.5% female)
|
||||
// For testing genderless, we'd need a species with genderRate -1
|
||||
// None in MVP are genderless, so test the basic logic
|
||||
const pikachu = SPECIES_DATA.pikachu
|
||||
expect(pikachu.genderRate).toBe(4)
|
||||
})
|
||||
|
||||
test('pikachu 50% female ratio', () => {
|
||||
const pikachu = SPECIES_DATA.pikachu
|
||||
let males = 0
|
||||
let females = 0
|
||||
for (let seed = 0; seed < 1000; seed++) {
|
||||
const g = determineGender(pikachu, seed)
|
||||
if (g === 'male') males++
|
||||
else females++
|
||||
}
|
||||
// Should be roughly 50/50 with some tolerance
|
||||
expect(females).toBeGreaterThan(300)
|
||||
expect(males).toBeGreaterThan(300)
|
||||
})
|
||||
|
||||
test('starters are ~12.5% female', () => {
|
||||
const bulbasaur = SPECIES_DATA.bulbasaur
|
||||
let females = 0
|
||||
for (let seed = 0; seed < 1000; seed++) {
|
||||
if (determineGender(bulbasaur, seed) === 'female') females++
|
||||
}
|
||||
// ~12.5% female = ~125 out of 1000
|
||||
expect(females).toBeGreaterThan(50)
|
||||
expect(females).toBeLessThan(250)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getGenderSymbol', () => {
|
||||
test('male symbol', () => {
|
||||
expect(getGenderSymbol('male')).toBe('♂')
|
||||
})
|
||||
test('female symbol', () => {
|
||||
expect(getGenderSymbol('female')).toBe('♀')
|
||||
})
|
||||
test('genderless has no symbol', () => {
|
||||
expect(getGenderSymbol('genderless')).toBe('')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user