/** * Tests for GridAdapter * * These tests verify grid item CRUD operations, position management, * uncap updates, and conflict resolution functionality. */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { GridAdapter } from '../grid.adapter' import type { GridWeapon, GridCharacter, GridSummon } from '../grid.adapter' import type { Weapon, Character, Summon } from '$lib/types/api/entities' describe('GridAdapter', () => { let adapter: GridAdapter let originalFetch: typeof global.fetch const mockWeapon: Weapon = { id: 'weapon-1', granblueId: '1040001', name: { en: 'Test Weapon', ja: 'テスト武器' }, rarity: 5, element: 1, maxLevel: 150, maxSkillLevel: 15, maxAwakeningLevel: 5, series: { id: 'series-1', slug: 'dark-opus', name: { en: 'Dark Opus', ja: 'ダークオーパス' }, hasWeaponKeys: true, hasAwakening: true, augmentType: 'no_augment', extra: false, elementChangeable: false }, proficiency: 1, ax: true, axType: 1, uncap: { flb: true, ulb: true, transcendence: false }, hp: { minHp: 100, maxHp: 500, maxHpFlb: 600, maxHpUlb: 700 }, atk: { minAtk: 200, maxAtk: 1000, maxAtkFlb: 1200, maxAtkUlb: 1400 } } const mockCharacter: Character = { id: 'char-1', granblueId: '3040001', name: { en: 'Test Character', ja: 'テストキャラ' }, rarity: 5, element: 1, maxLevel: 100, uncap: { flb: true, ulb: true }, special: false, recruits: null, gender: 0, race: { race1: 1, race2: 0 }, proficiency: [1], hp: { minHp: 150, maxHp: 750, maxHpFlb: 900 }, atk: { minAtk: 250, maxAtk: 1250, maxAtkFlb: 1500 } } const mockSummon: Summon = { id: 'summon-1', granblueId: '2040001', name: { en: 'Test Summon', ja: 'テスト召喚石' }, rarity: 5, element: 1, maxLevel: 150, uncap: { flb: true, ulb: true, transcendence: false }, subaura: false, hp: { minHp: 100, maxHp: 500, maxHpFlb: 600, maxHpUlb: 700 }, atk: { minAtk: 200, maxAtk: 1000, maxAtkFlb: 1200, maxAtkUlb: 1400 } } const mockGridWeapon: GridWeapon = { id: 'gw-1', position: 1, mainhand: true, uncapLevel: 5, transcendenceStep: 0, weapon: mockWeapon } const mockGridCharacter: GridCharacter = { id: 'gc-1', position: 1, uncapLevel: 5, transcendenceStep: 1, character: mockCharacter } const mockGridSummon: GridSummon = { id: 'gs-1', position: 1, quickSummon: true, uncapLevel: 5, transcendenceStep: 2, summon: mockSummon } beforeEach(() => { originalFetch = global.fetch adapter = new GridAdapter({ baseURL: 'https://api.example.com' }) }) afterEach(() => { global.fetch = originalFetch vi.clearAllTimers() }) describe('weapon operations', () => { it('should create a grid weapon', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockGridWeapon }) const result = await adapter.createWeapon({ partyId: 'party-1', weaponId: 'weapon-1', position: 1, mainhand: true }) expect(result).toEqual(mockGridWeapon) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_weapons', expect.objectContaining({ method: 'POST', body: JSON.stringify({ party_id: 'party-1', weapon_id: 'weapon-1', position: 1, mainhand: true }) }) ) }) it('should update a grid weapon', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...mockGridWeapon, uncapLevel: 6 }) }) const result = await adapter.updateWeapon('gw-1', { uncapLevel: 6 }) expect(result.uncapLevel).toBe(6) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_weapons/gw-1', expect.objectContaining({ method: 'PUT', body: JSON.stringify({ uncap_level: 6 }) }) ) }) it('should delete a grid weapon', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }) await adapter.deleteWeapon({ id: 'gw-1', partyId: 'party-1' }) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_weapons', expect.objectContaining({ method: 'DELETE', body: JSON.stringify({ id: 'gw-1', party_id: 'party-1' }) }) ) }) it('should update weapon uncap level', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...mockGridWeapon, uncapLevel: 6 }) }) const result = await adapter.updateWeaponUncap({ id: 'gw-1', partyId: 'party-1', uncapLevel: 6, transcendenceStep: 1 }) expect(result.uncapLevel).toBe(6) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_weapons/update_uncap', expect.objectContaining({ method: 'POST', body: JSON.stringify({ id: 'gw-1', party_id: 'party-1', uncap_level: 6, transcendence_step: 1 }) }) ) }) it('should resolve weapon conflicts', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockGridWeapon }) const result = await adapter.resolveWeaponConflict({ partyId: 'party-1', incomingId: 'weapon-2', position: 1, conflictingIds: ['gw-1'] }) expect(result).toEqual(mockGridWeapon) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_weapons/resolve', expect.objectContaining({ method: 'POST', body: JSON.stringify({ party_id: 'party-1', incoming_id: 'weapon-2', position: 1, conflicting_ids: ['gw-1'] }) }) ) }) it('should update weapon position', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...mockGridWeapon, position: 2 }) }) const result = await adapter.updateWeaponPosition({ partyId: 'party-1', id: 'gw-1', position: 2 }) expect(result.position).toBe(2) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/parties/party-1/grid_weapons/gw-1/position', expect.objectContaining({ method: 'PUT', body: JSON.stringify({ position: 2 }) }) ) }) it('should swap weapon positions', async () => { const mockResponse = { source: mockGridWeapon, target: { ...mockGridWeapon, id: 'gw-2', position: 2 } } global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockResponse }) const result = await adapter.swapWeapons({ partyId: 'party-1', sourceId: 'gw-1', targetId: 'gw-2' }) expect(result).toEqual(mockResponse) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/parties/party-1/grid_weapons/swap', expect.objectContaining({ method: 'POST', body: JSON.stringify({ source_id: 'gw-1', target_id: 'gw-2' }) }) ) }) }) describe('character operations', () => { it('should create a grid character', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockGridCharacter }) const result = await adapter.createCharacter({ partyId: 'party-1', characterId: 'char-1', position: 1 }) expect(result).toEqual(mockGridCharacter) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_characters', expect.objectContaining({ method: 'POST', body: JSON.stringify({ party_id: 'party-1', character_id: 'char-1', position: 1 }) }) ) }) it('should update character position', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...mockGridCharacter, position: 2 }) }) const result = await adapter.updateCharacterPosition({ partyId: 'party-1', id: 'gc-1', position: 2 }) expect(result.position).toBe(2) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/parties/party-1/grid_characters/gc-1/position', expect.objectContaining({ method: 'PUT', body: JSON.stringify({ position: 2 }) }) ) }) it('should swap character positions', async () => { const mockResponse = { source: mockGridCharacter, target: { ...mockGridCharacter, id: 'gc-2', position: 2 } } global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockResponse }) const result = await adapter.swapCharacters({ partyId: 'party-1', sourceId: 'gc-1', targetId: 'gc-2' }) expect(result).toEqual(mockResponse) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/parties/party-1/grid_characters/swap', expect.objectContaining({ method: 'POST', body: JSON.stringify({ source_id: 'gc-1', target_id: 'gc-2' }) }) ) }) }) describe('summon operations', () => { it('should create a grid summon', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockGridSummon }) const result = await adapter.createSummon({ partyId: 'party-1', summonId: 'summon-1', position: 1, quickSummon: true }) expect(result).toEqual(mockGridSummon) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_summons', expect.objectContaining({ method: 'POST', body: JSON.stringify({ party_id: 'party-1', summon_id: 'summon-1', position: 1, quick_summon: true }) }) ) }) it('should update quick summon', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...mockGridSummon, quickSummon: false }) }) const result = await adapter.updateQuickSummon({ id: 'gs-1', partyId: 'party-1', quickSummon: false }) expect(result.quickSummon).toBe(false) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/grid_summons/update_quick_summon', expect.objectContaining({ method: 'POST', body: JSON.stringify({ id: 'gs-1', party_id: 'party-1', quick_summon: false }) }) ) }) it('should update summon position', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...mockGridSummon, position: 2 }) }) const result = await adapter.updateSummonPosition({ partyId: 'party-1', id: 'gs-1', position: 2 }) expect(result.position).toBe(2) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/parties/party-1/grid_summons/gs-1/position', expect.objectContaining({ method: 'PUT', body: JSON.stringify({ position: 2 }) }) ) }) it('should swap summon positions', async () => { const mockResponse = { source: mockGridSummon, target: { ...mockGridSummon, id: 'gs-2', position: 2 } } global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockResponse }) const result = await adapter.swapSummons({ partyId: 'party-1', sourceId: 'gs-1', targetId: 'gs-2' }) expect(result).toEqual(mockResponse) expect(global.fetch).toHaveBeenCalledWith( 'https://api.example.com/parties/party-1/grid_summons/swap', expect.objectContaining({ method: 'POST', body: JSON.stringify({ source_id: 'gs-1', target_id: 'gs-2' }) }) ) }) }) describe('cache management', () => { it('should clear grid cache', () => { const clearCacheSpy = vi.spyOn(adapter, 'clearCache') adapter.clearGridCache('party-1') expect(clearCacheSpy).toHaveBeenCalledWith('/parties/party-1/grid') }) it('should clear all grid caches', () => { const clearCacheSpy = vi.spyOn(adapter, 'clearCache') adapter.clearGridCache() expect(clearCacheSpy).toHaveBeenCalledWith('/grid') }) }) describe('error handling', () => { it('should handle 404 errors', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 404, statusText: 'Not Found', json: async () => ({ error: 'Grid weapon not found' }) }) await expect( adapter.updateWeapon('invalid-id', { uncapLevel: 5 }) ).rejects.toThrow() }) it('should handle validation errors', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 422, statusText: 'Unprocessable Entity', json: async () => ({ errors: { position: ['is already taken'] } }) }) await expect( adapter.createWeapon({ partyId: 'party-1', weaponId: 'weapon-1', position: 1 }) ).rejects.toThrow() }) }) })