147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
CAMPFIRE_CLASS_ORDER,
|
|
cancelConnecting,
|
|
clickBattleNetButton,
|
|
confirmCharacterSelect,
|
|
createInitialFlowState,
|
|
enterCharCreate,
|
|
hoverCampfireClass,
|
|
isValidCharacterName,
|
|
selectCampfireClass,
|
|
stepFlowTick,
|
|
submitBattleNetLogin,
|
|
submitCharacterCreate,
|
|
submitCreateGame,
|
|
toggleChatGem,
|
|
} from '../../src/client/frontend/flow-state.ts'
|
|
|
|
describe('Diablo II v1.13c Front-End & Battle.net Flow State Machine', () => {
|
|
it('validates character names according to D2Launch.dll rules', () => {
|
|
expect(isValidCharacterName('A')).toBe(false)
|
|
expect(isValidCharacterName('Isendra')).toBe(true)
|
|
expect(isValidCharacterName('Dark_Wanderer')).toBe(true)
|
|
expect(isValidCharacterName('-Invalid')).toBe(false)
|
|
expect(isValidCharacterName('Invalid-')).toBe(false)
|
|
expect(isValidCharacterName('In--valid')).toBe(false)
|
|
expect(isValidCharacterName('TooLongCharacterName')).toBe(false)
|
|
})
|
|
|
|
it('walks through all 7 stages from Main Menu to Act 1 Rogue Encampment', () => {
|
|
const state = createInitialFlowState()
|
|
expect(state.stage).toBe('main_menu')
|
|
|
|
// Stage 1 -> Stage 2 Connecting Modal
|
|
clickBattleNetButton(state)
|
|
expect(state.stage).toBe('bnet_connecting')
|
|
|
|
// Can cancel back to main menu
|
|
cancelConnecting(state)
|
|
expect(state.stage).toBe('main_menu')
|
|
|
|
// Re-click Battle.net and advance 18 ticks to Login Screen
|
|
clickBattleNetButton(state)
|
|
for (let i = 0; i < 18; i++) {
|
|
stepFlowTick(state)
|
|
}
|
|
expect(state.stage).toBe('bnet_login')
|
|
expect(state.activeInput).toBe('accountName')
|
|
|
|
// Stage 2: Mock authentication accepts ANY username & password
|
|
state.accountName = 'any_user_123'
|
|
state.accountPassword = 'any_password_456'
|
|
expect(submitBattleNetLogin(state)).toBe(true)
|
|
|
|
// With empty initial roster, transitions directly to Stage 3 (7-Class Campfire)
|
|
expect(state.stage).toBe('char_create')
|
|
|
|
// Stage 3: Hover Amazon ('am') -> transitions from 'nu1' to 'nu2'
|
|
hoverCampfireClass(state, 'am')
|
|
expect(state.campfireClasses.am.phase).toBe('nu2')
|
|
|
|
// Click Sorceress ('so') -> walks forward ('fw' -> 'nu3')
|
|
selectCampfireClass(state, 'so')
|
|
expect(state.selectedClass).toBe('so')
|
|
expect(state.campfireClasses.so.phase).toBe('fw')
|
|
expect(state.activeInput).toBe('charName')
|
|
|
|
const fwCount = state.campfireClasses.so.frameCount.fw
|
|
for (let i = 0; i < fwCount; i++) {
|
|
stepFlowTick(state)
|
|
}
|
|
expect(state.campfireClasses.so.phase).toBe('nu3')
|
|
|
|
// Switching to Necromancer ('ne') causes Sorceress to walk backward ('bw' -> 'nu1')
|
|
selectCampfireClass(state, 'ne')
|
|
expect(state.selectedClass).toBe('ne')
|
|
expect(state.campfireClasses.so.phase).toBe('bw')
|
|
expect(state.campfireClasses.ne.phase).toBe('fw')
|
|
|
|
const bwCount = state.campfireClasses.so.frameCount.bw
|
|
for (let i = 0; i < bwCount; i++) {
|
|
stepFlowTick(state)
|
|
}
|
|
expect(state.campfireClasses.so.phase).toBe('nu1')
|
|
|
|
// Enter character name and click OK -> transitions to Stage 4 (8-Slot Character Selection)
|
|
state.charName = 'XulNecro'
|
|
state.isExpansion = true
|
|
state.isHardcore = false
|
|
const created = submitCharacterCreate(state)
|
|
expect(created).not.toBeNull()
|
|
expect(created?.name).toBe('XulNecro')
|
|
expect(created?.heroToken).toBe('ne')
|
|
expect(state.stage).toBe('char_select')
|
|
expect(state.roster).toHaveLength(1)
|
|
expect(state.selectedSlotIndex).toBe(0)
|
|
|
|
// Stage 4 -> Stage 5 (Battle.net Lobby & Create Game Room)
|
|
expect(confirmCharacterSelect(state)).toBe(true)
|
|
expect(state.stage).toBe('bnet_lobby')
|
|
expect(state.lobbySubPanel).toBe('create')
|
|
|
|
// Toggle Chat Gem in Lobby
|
|
toggleChatGem(state)
|
|
expect(state.gemActivated).toBe(true)
|
|
expect(state.chatMessages.at(-1)).toBe('Gem Activated')
|
|
toggleChatGem(state)
|
|
expect(state.gemActivated).toBe(false)
|
|
expect(state.chatMessages.at(-1)).toBe('Gem Deactivated')
|
|
|
|
// Stage 5: Configure room and click CREATE GAME -> transitions to Stage 6 (Door Opening Loading)
|
|
state.gameName = 'Sanctuary1'
|
|
state.gamePassword = '123'
|
|
state.difficulty = 'nightmare'
|
|
expect(submitCreateGame(state)).toBe(true)
|
|
expect(state.stage).toBe('door_loading')
|
|
expect(state.doorFrame).toBe(0)
|
|
|
|
// Stage 6: Step through all 10 frames (0..9) of Loadingscreen.dc6
|
|
for (let i = 0; i < 27; i++) {
|
|
stepFlowTick(state)
|
|
}
|
|
expect(state.doorFrame).toBe(9)
|
|
// Does not enter game until sceneReady is true
|
|
expect(state.stage).toBe('door_loading')
|
|
|
|
state.sceneReady = true
|
|
for (let i = 0; i < 5; i++) {
|
|
stepFlowTick(state)
|
|
}
|
|
// Stage 7: Act 1 Rogue Encampment active!
|
|
expect(state.stage).toBe('in_game')
|
|
})
|
|
|
|
it('maps Assassin campfire class id "as" to in-game hero token "ai"', () => {
|
|
const state = createInitialFlowState()
|
|
enterCharCreate(state)
|
|
expect(CAMPFIRE_CLASS_ORDER).toHaveLength(7)
|
|
selectCampfireClass(state, 'as')
|
|
state.charName = 'Natalya'
|
|
const created = submitCharacterCreate(state)
|
|
expect(created?.classId).toBe('as')
|
|
expect(created?.heroToken).toBe('ai')
|
|
expect(created?.className).toBe('Assassin')
|
|
})
|
|
})
|