import { describe, it, expect, vi, beforeEach } from 'vitest'; import EpisodeManager from '../utils/renderer/EpisodeManager'; describe('EpisodeManager', () => { let em; let fileListEl; beforeEach(() => { em = new EpisodeManager(); fileListEl = document.createElement('div'); fileListEl.id = 'file-list'; }); describe('constructor', () => { it('initializes with isUpdatingEpisodeNumbers false', () => { expect(em.getIsUpdatingEpisodeNumbers()).toBe(false); }); }); describe('updateEpisodeNumbers', () => { it('updates episode numbers sequentially', () => { const item1 = document.createElement('div'); item1.className = 'file-item'; const ep1 = document.createElement('span'); ep1.className = 'episode-number'; ep1.dataset.episodeStart = ''; ep1.dataset.episodeEnd = ''; item1.appendChild(ep1); const item2 = document.createElement('div'); item2.className = 'file-item'; const ep2 = document.createElement('span'); ep2.className = 'episode-number'; ep2.dataset.episodeStart = ''; ep2.dataset.episodeEnd = ''; item2.appendChild(ep2); fileListEl.appendChild(item1); fileListEl.appendChild(item2); em.updateEpisodeNumbers(fileListEl); expect(ep1.textContent).toBe('1'); expect(ep2.textContent).toBe('2'); }); it('skips re-entrant calls', () => { em.setIsUpdatingEpisodeNumbers(true); em.updateEpisodeNumbers(fileListEl); expect(em.getIsUpdatingEpisodeNumbers()).toBe(true); }); it('respects stored episode ranges', () => { const item = document.createElement('div'); item.className = 'file-item'; const ep = document.createElement('span'); ep.className = 'episode-number'; ep.dataset.episodeStart = '5'; ep.dataset.episodeEnd = '7'; item.appendChild(ep); fileListEl.appendChild(item); em.updateEpisodeNumbers(fileListEl); expect(ep.textContent).toBe('5-7'); }); }); describe('setIsUpdatingEpisodeNumbers', () => { it('sets the flag', () => { em.setIsUpdatingEpisodeNumbers(true); expect(em.getIsUpdatingEpisodeNumbers()).toBe(true); em.setIsUpdatingEpisodeNumbers(false); expect(em.getIsUpdatingEpisodeNumbers()).toBe(false); }); }); describe('highlightEpisodes', () => { it('highlights episodes in range', () => { const container = document.createElement('div'); container.id = 'episodes-container'; const ep1 = document.createElement('div'); ep1.className = 'episode-item'; ep1.textContent = 'E1 Pilot'; const ep2 = document.createElement('div'); ep2.className = 'episode-item'; ep2.textContent = 'E2 Second'; const ep3 = document.createElement('div'); ep3.className = 'episode-item'; ep3.textContent = 'E3 Third'; container.appendChild(ep1); container.appendChild(ep2); container.appendChild(ep3); em.highlightEpisodes(1, 2, container); expect(ep1.classList.contains('highlighted-episode')).toBe(true); expect(ep2.classList.contains('highlighted-episode')).toBe(true); expect(ep3.classList.contains('highlighted-episode')).toBe(false); }); it('handles null container', () => { expect(() => em.highlightEpisodes(1, 2, null)).not.toThrow(); }); }); describe('removeEpisodeHighlights', () => { it('removes highlights', () => { const container = document.createElement('div'); const ep = document.createElement('div'); ep.className = 'episode-item highlighted-episode'; container.appendChild(ep); em.removeEpisodeHighlights(container); expect(ep.classList.contains('highlighted-episode')).toBe(false); }); it('handles null container', () => { expect(() => em.removeEpisodeHighlights(null)).not.toThrow(); }); }); describe('getEpisodeRange', () => { it('returns range from data attributes', () => { const epEl = document.createElement('span'); epEl.dataset.episodeStart = '3'; epEl.dataset.episodeEnd = '5'; const range = em.getEpisodeRange(epEl); expect(range).toEqual({ start: 3, end: 5 }); }); it('defaults to 1 when no data', () => { const epEl = document.createElement('span'); const range = em.getEpisodeRange(epEl); expect(range).toEqual({ start: 1, end: 1 }); }); }); describe('calculateTotalEpisodeCount', () => { it('sums episode ranges', () => { const item1 = document.createElement('div'); item1.className = 'file-item'; const ep1 = document.createElement('span'); ep1.className = 'episode-number'; ep1.dataset.episodeStart = '1'; ep1.dataset.episodeEnd = '1'; item1.appendChild(ep1); const item2 = document.createElement('div'); item2.className = 'file-item'; const ep2 = document.createElement('span'); ep2.className = 'episode-number'; ep2.dataset.episodeStart = '2'; ep2.dataset.episodeEnd = '3'; item2.appendChild(ep2); fileListEl.appendChild(item1); fileListEl.appendChild(item2); expect(em.calculateTotalEpisodeCount(fileListEl)).toBe(3); }); it('returns 0 for empty list', () => { expect(em.calculateTotalEpisodeCount(fileListEl)).toBe(0); }); }); describe('getLastEpisodeEnd', () => { it('returns highest episode end', () => { const item = document.createElement('div'); item.className = 'file-item'; const ep = document.createElement('span'); ep.className = 'episode-number'; ep.dataset.episodeEnd = '13'; item.appendChild(ep); fileListEl.appendChild(item); expect(em.getLastEpisodeEnd(fileListEl)).toBe(13); }); it('returns 0 for empty list', () => { expect(em.getLastEpisodeEnd(fileListEl)).toBe(0); }); }); describe('handleEpisodeArrowClick', () => { it('increases range with right arrow', () => { const item = document.createElement('div'); item.className = 'file-item'; item.dataset.filePath = '/path/file.mp4'; const ep = document.createElement('span'); ep.className = 'episode-number'; ep.dataset.episodeStart = '1'; ep.dataset.episodeEnd = '1'; item.appendChild(ep); const arrow = document.createElement('button'); arrow.className = 'episode-arrow-right'; item.appendChild(arrow); fileListEl.appendChild(item); em.handleEpisodeArrowClick(arrow, 'right', fileListEl); expect(ep.dataset.episodeEnd).toBe('2'); }); it('decreases range with left arrow', () => { const item = document.createElement('div'); item.className = 'file-item'; item.dataset.filePath = '/path/file.mp4'; const ep = document.createElement('span'); ep.className = 'episode-number'; ep.dataset.episodeStart = '3'; ep.dataset.episodeEnd = '3'; item.appendChild(ep); const arrow = document.createElement('button'); arrow.className = 'episode-arrow-left'; item.appendChild(arrow); fileListEl.appendChild(item); em.handleEpisodeArrowClick(arrow, 'left', fileListEl); expect(ep.dataset.episodeStart).toBe('2'); }); it('does not decrease below 1', () => { const item = document.createElement('div'); item.className = 'file-item'; item.dataset.filePath = '/path/file.mp4'; const ep = document.createElement('span'); ep.className = 'episode-number'; ep.dataset.episodeStart = '1'; ep.dataset.episodeEnd = '1'; item.appendChild(ep); const arrow = document.createElement('button'); arrow.className = 'episode-arrow-left'; item.appendChild(arrow); fileListEl.appendChild(item); em.handleEpisodeArrowClick(arrow, 'left', fileListEl); expect(ep.dataset.episodeStart).toBe('1'); }); it('returns early when no episode element', () => { const item = document.createElement('div'); item.className = 'file-item'; const arrow = document.createElement('button'); arrow.className = 'episode-arrow-right'; item.appendChild(arrow); fileListEl.appendChild(item); expect(() => em.handleEpisodeArrowClick(arrow, 'right', fileListEl)).not.toThrow(); }); }); describe('makeEpisodeRangeEditable', () => { it('makes element editable', () => { const el = document.createElement('span'); el.textContent = '1'; el.dataset.episodeStart = '1'; el.dataset.episodeEnd = '1'; em.makeEpisodeRangeEditable(el); expect(el.contentEditable).toBe('true'); expect(el.classList.contains('editing')).toBe(true); }); it('returns early if already editable', () => { const el = document.createElement('span'); el.contentEditable = 'true'; const focusSpy = vi.spyOn(el, 'focus'); em.makeEpisodeRangeEditable(el); expect(focusSpy).not.toHaveBeenCalled(); }); }); });