/** * @vitest-environment happy-dom */ import React from 'react'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { act, render } from '@testing-library/react'; import { LoadingIndicator } from '../../../components/common/LoadingIndicator.js'; import { Window } from 'happy-dom'; const advanceTimersBy = async (ms: number) => { await act(async () => { await vi.advanceTimersByTimeAsync(ms); }); }; beforeEach(() => { vi.useFakeTimers(); const window = new Window(); globalThis.window = window as any; globalThis.document = window.document as any; }); afterEach(() => { vi.clearAllTimers(); vi.useRealTimers(); }); describe('LoadingIndicator', () => { const getSpinnerText = (container: HTMLElement) => { return container.querySelector('ink-text')?.textContent ?? ''; }; const getMessageText = (container: HTMLElement) => { const texts = container.querySelectorAll('ink-text'); return texts.length > 1 ? texts[1]?.textContent ?? '' : ''; }; it('does not render before the delay elapses', async () => { const { container } = render( ); expect(container.textContent).toBe(''); await advanceTimersBy(20); expect(container.textContent).toBe(''); }); it('renders after the delay elapses', async () => { const { container } = render( ); await advanceTimersBy(30); expect(getMessageText(container)).toContain('読み込み中'); }); it('stops rendering when loading becomes false', async () => { const { container, rerender } = render( ); await advanceTimersBy(10); expect(getMessageText(container)).toContain('読み込み中'); await act(async () => { rerender(); await vi.advanceTimersByTimeAsync(0); }); expect(container.textContent).toBe(''); }); it('cycles through spinner frames over time', async () => { const customFrames = ['.', '..', '...']; const { container } = render( ); await advanceTimersBy(0); const firstFrame = getSpinnerText(container); await advanceTimersBy(5); const secondFrame = getSpinnerText(container); await advanceTimersBy(5); const thirdFrame = getSpinnerText(container); expect(secondFrame).not.toEqual(firstFrame); expect(thirdFrame).not.toEqual(secondFrame); expect(customFrames).toContain(firstFrame ?? ''); expect(customFrames).toContain(secondFrame ?? ''); expect(customFrames).toContain(thirdFrame ?? ''); expect(getMessageText(container)).toContain('読み込み中'); }); it('keeps rendering even when only a single frame is provided', async () => { const { container } = render( ); await advanceTimersBy(0); expect(getSpinnerText(container)).toBe('*'); await advanceTimersBy(30); expect(getSpinnerText(container)).toBe('*'); expect(getMessageText(container)).toContain('読み込み中'); }); });