import { describe, test, expect, vi } from 'vitest'; import { Question } from '../src/question'; describe('Question', () => { describe('ask', () => { test('should return the initial result if no callbacks are added', () => { const question = new Question(); const initialResult = 10; const result = question.ask(initialResult); expect(result).toBe(initialResult); }); test('should call a single added callback with the initial result and arguments', () => { const question = new Question(); const initialResult = 'hello'; const arg1 = 5; const callback = vi.fn((res: string, num: number) => `${res} world ${num}`); question.addAnswer(callback); const result = question.ask(initialResult, arg1); expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(initialResult, arg1); expect(result).toBe('hello world 5'); }); test('should call multiple callbacks in the order they were added', () => { const question = new Question(); const initialResult = 1; const arg1 = 'test'; const callback1 = vi.fn((res: number, str: string) => { expect(str).toBe(arg1); return res * 2; // 1 * 2 = 2 }); const callback2 = vi.fn((res: number, str: string) => { expect(str).toBe(arg1); return res + 5; // 2 + 5 = 7 }); const callback3 = vi.fn((res: number, str: string) => { expect(str).toBe(arg1); return res - 3; // 7 - 3 = 4 }); question.addAnswer(callback1); question.addAnswer(callback2); question.addAnswer(callback3); const result = question.ask(initialResult, arg1); expect(callback1).toHaveBeenCalledTimes(1); expect(callback1).toHaveBeenCalledWith(1, arg1); expect(callback2).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledWith(2, arg1); // Result from callback1 expect(callback3).toHaveBeenCalledTimes(1); expect(callback3).toHaveBeenCalledWith(7, arg1); // Result from callback2 // Ensure order by checking mock call order const callback1Order = callback1.mock.invocationCallOrder[0]; const callback2Order = callback2.mock.invocationCallOrder[0]; const callback3Order = callback3.mock.invocationCallOrder[0]; expect(callback1Order).toBeLessThan(callback2Order); expect(callback2Order).toBeLessThan(callback3Order); expect(result).toBe(4); // Final result from callback3 }); test('should pass arguments correctly to each callback', () => { const question = new Question(); const initialResult: number[] = []; const arg1 = 'data'; const arg2 = true; const callback1 = vi.fn((res: number[], str: string, bool: boolean) => { expect(str).toBe(arg1); expect(bool).toBe(arg2); return [...res, 1]; }); const callback2 = vi.fn((res: number[], str: string, bool: boolean) => { expect(str).toBe(arg1); expect(bool).toBe(arg2); return [...res, 2]; }); question.addAnswer(callback1); question.addAnswer(callback2); const result = question.ask(initialResult, arg1, arg2); expect(callback1).toHaveBeenCalledWith([], arg1, arg2); expect(callback2).toHaveBeenCalledWith([1], arg1, arg2); expect(result).toEqual([1, 2]); }); test('should not call removed callbacks', () => { const question = new Question(); const initialResult = 0; const callback1 = vi.fn((res: number) => res + 1); const callback2 = vi.fn((res: number) => res * 2); // This one will be removed const callback3 = vi.fn((res: number) => res + 3); question.addAnswer(callback1); question.addAnswer(callback2); question.addAnswer(callback3); question.removeAnswer(callback2); const result = question.ask(initialResult); expect(callback1).toHaveBeenCalledTimes(1); expect(callback1).toHaveBeenCalledWith(0); // Called with initial expect(callback2).not.toHaveBeenCalled(); expect(callback3).toHaveBeenCalledTimes(1); expect(callback3).toHaveBeenCalledWith(1); // Called with result from callback1 expect(result).toBe(4); // 0 + 1 + 3 = 4 }); test('should execute one-time callbacks only once', () => { const question = new Question(); const initialResult = 'start'; const oneTimeCallback = vi.fn((res: string) => res + '-once'); const regularCallback = vi.fn((res: string) => res + '-regular'); question.addOneTimeAnswer(oneTimeCallback); question.addAnswer(regularCallback); // First ask const result1 = question.ask(initialResult); expect(oneTimeCallback).toHaveBeenCalledTimes(1); expect(oneTimeCallback).toHaveBeenCalledWith('start'); expect(regularCallback).toHaveBeenCalledTimes(1); expect(regularCallback).toHaveBeenCalledWith('start-once'); expect(result1).toBe('start-once-regular'); // Second ask const result2 = question.ask(initialResult); expect(oneTimeCallback).toHaveBeenCalledTimes(1); // Should not be called again expect(regularCallback).toHaveBeenCalledTimes(2); expect(regularCallback).toHaveBeenCalledWith('start'); // Called with initial result this time expect(result2).toBe('start-regular'); }); }); describe('addAnswer', () => { test('should add a callback correctly', () => { const question = new Question(); const callback = vi.fn((res: number) => res + 1); question.addAnswer(callback); question.ask(0); expect(callback).toHaveBeenCalledTimes(1); }); test('should not add the same callback twice (covers lines 9-10)', () => { const question = new Question(); const callback = vi.fn((res: number) => res + 1); question.addAnswer(callback); question.addAnswer(callback); // Add again question.ask(0); // Callback should only be called once despite being added twice expect(callback).toHaveBeenCalledTimes(1); // Internal check (less ideal but confirms the implementation detail) // @ts-expect-error Accessing protected members for test verification expect(question._callbacks.length).toBe(1); // @ts-expect-error Accessing protected members for test verification expect(question._callbacksSet.size).toBe(1); }); }); describe('removeAnswer', () => { test('should remove an existing callback', () => { const question = new Question(); const callback = vi.fn((res: number) => res + 1); question.addAnswer(callback); question.removeAnswer(callback); question.ask(0); expect(callback).not.toHaveBeenCalled(); // @ts-expect-error Accessing protected members for test verification expect(question._callbacks.length).toBe(0); // @ts-expect-error Accessing protected members for test verification expect(question._callbacksSet.size).toBe(0); }); test('should do nothing if the callback to remove does not exist (covers lines 17-18)', () => { const question = new Question(); const callbackToAdd = vi.fn((res: number) => res + 1); const callbackToRemove = vi.fn((res: number) => res + 2); // Never added question.addAnswer(callbackToAdd); // @ts-expect-error Accessing protected members const initialCallbackCount = question._callbacks.length; // @ts-expect-error Accessing protected members const initialSetSize = question._callbacksSet.size; question.removeAnswer(callbackToRemove); // Attempt to remove non-existent callback question.ask(0); // The added callback should still be called expect(callbackToAdd).toHaveBeenCalledTimes(1); // The non-added callback should not have been called expect(callbackToRemove).not.toHaveBeenCalled(); // Internal state should remain unchanged by the remove attempt // @ts-expect-error Accessing protected members for test verification expect(question._callbacks.length).toBe(initialCallbackCount); // @ts-expect-error Accessing protected members for test verification expect(question._callbacksSet.size).toBe(initialSetSize); }); }); });