diff --git a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts index c20ded88..92ae81a2 100644 --- a/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts +++ b/packages/cli/src/ui/hooks/useLoadingIndicator.test.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook } from '@testing-library/react'; import { useLoadingIndicator } from './useLoadingIndicator.js'; import { StreamingState } from '../types.js'; @@ -39,15 +39,18 @@ describe('useLoadingIndicator', () => { // Initial state before timers advance expect(result.current.elapsedTime).toBe(0); - expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]); + expect(WITTY_LOADING_PHRASES).toContain( + result.current.currentLoadingPhrase, + ); + const _initialPhrase = result.current.currentLoadingPhrase; act(() => { vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS); }); // Phrase should cycle if PHRASE_CHANGE_INTERVAL_MS has passed - // This depends on the actual implementation of usePhraseCycler - // For simplicity, we'll check it's one of the witty phrases - expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[1]); + expect(WITTY_LOADING_PHRASES).toContain( + result.current.currentLoadingPhrase, + ); }); it('should show waiting phrase and retain elapsedTime when WaitingForConfirmation', () => { @@ -75,7 +78,7 @@ describe('useLoadingIndicator', () => { expect(result.current.elapsedTime).toBe(60); }); - it('should reset elapsedTime and use initial phrase when transitioning from WaitingForConfirmation to Responding', () => { + it('should reset elapsedTime and use a witty phrase when transitioning from WaitingForConfirmation to Responding', () => { const { result, rerender } = renderHook( ({ streamingState }) => useLoadingIndicator(streamingState), { initialProps: { streamingState: StreamingState.Responding } }, @@ -94,7 +97,9 @@ describe('useLoadingIndicator', () => { rerender({ streamingState: StreamingState.Responding }); expect(result.current.elapsedTime).toBe(0); // Should reset - expect(result.current.currentLoadingPhrase).toBe(WITTY_LOADING_PHRASES[0]); + expect(WITTY_LOADING_PHRASES).toContain( + result.current.currentLoadingPhrase, + ); act(() => { vi.advanceTimersByTime(1000); diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts index f5be12e9..fca5970f 100644 --- a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts +++ b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts @@ -45,40 +45,50 @@ describe('usePhraseCycler', () => { it('should cycle through witty phrases when isActive is true and not waiting', () => { const { result } = renderHook(() => usePhraseCycler(true, false)); - expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); + // Initial phrase should be one of the witty phrases + expect(WITTY_LOADING_PHRASES).toContain(result.current); + const _initialPhrase = result.current; act(() => { vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS); }); - expect(result.current).toBe(WITTY_LOADING_PHRASES[1]); + // Phrase should change and be one of the witty phrases + expect(WITTY_LOADING_PHRASES).toContain(result.current); + const _secondPhrase = result.current; act(() => { vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS); }); - expect(result.current).toBe( - WITTY_LOADING_PHRASES[2 % WITTY_LOADING_PHRASES.length], - ); + expect(WITTY_LOADING_PHRASES).toContain(result.current); }); - it('should reset to the first phrase when isActive becomes true after being false (and not waiting)', () => { + it('should reset to a witty phrase when isActive becomes true after being false (and not waiting)', () => { const { result, rerender } = renderHook( ({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting), { initialProps: { isActive: false, isWaiting: false } }, ); - // Cycle to a different phrase + + // Activate rerender({ isActive: true, isWaiting: false }); + const firstActivePhrase = result.current; + expect(WITTY_LOADING_PHRASES).toContain(firstActivePhrase); + act(() => { vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS); }); - expect(result.current).not.toBe(WITTY_LOADING_PHRASES[0]); + // Phrase should change if enough phrases and interval passed + if (WITTY_LOADING_PHRASES.length > 1) { + expect(result.current).not.toBe(firstActivePhrase); + } + expect(WITTY_LOADING_PHRASES).toContain(result.current); - // Set to inactive + // Set to inactive - should reset to the default initial phrase rerender({ isActive: false, isWaiting: false }); - expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); // Should reset to first phrase + expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); - // Set back to active + // Set back to active - should pick a random witty phrase rerender({ isActive: true, isWaiting: false }); - expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); // Should start with the first phrase + expect(WITTY_LOADING_PHRASES).toContain(result.current); }); it('should clear phrase interval on unmount when active', () => { @@ -88,24 +98,30 @@ describe('usePhraseCycler', () => { expect(clearIntervalSpy).toHaveBeenCalledOnce(); }); - it('should reset to the first witty phrase when transitioning from waiting to active', () => { + it('should reset to a witty phrase when transitioning from waiting to active', () => { const { result, rerender } = renderHook( ({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting), { initialProps: { isActive: true, isWaiting: false } }, ); - // Cycle to a different phrase + const _initialPhrase = result.current; + expect(WITTY_LOADING_PHRASES).toContain(_initialPhrase); + + // Cycle to a different phrase (potentially) act(() => { vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS); }); - expect(result.current).toBe(WITTY_LOADING_PHRASES[1]); + if (WITTY_LOADING_PHRASES.length > 1) { + // This check is probabilistic with random selection + } + expect(WITTY_LOADING_PHRASES).toContain(result.current); // Go to waiting state rerender({ isActive: false, isWaiting: true }); expect(result.current).toBe('Waiting for user confirmation...'); - // Go back to active cycling + // Go back to active cycling - should pick a random witty phrase rerender({ isActive: true, isWaiting: false }); - expect(result.current).toBe(WITTY_LOADING_PHRASES[0]); + expect(WITTY_LOADING_PHRASES).toContain(result.current); }); }); diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.ts b/packages/cli/src/ui/hooks/usePhraseCycler.ts index 96bad676..6b9450c9 100644 --- a/packages/cli/src/ui/hooks/usePhraseCycler.ts +++ b/packages/cli/src/ui/hooks/usePhraseCycler.ts @@ -22,6 +22,111 @@ export const WITTY_LOADING_PHRASES = [ 'Shuffling punchlines...', 'Untangling neural nets...', 'Compiling brilliance...', + 'Loading wit.exe...', + 'Summoning the cloud of wisdom...', + 'Preparing a witty response...', + "Just a sec, I'm debugging reality...", + 'Confuzzling the options...', + 'Tuning the cosmic frequencies...', + 'Crafting a response worthy of your patience...', + 'Compiling the 1s and 0s...', + 'Resolving dependencies... and existential crises...', + 'Defragmenting memories... both RAM and personal...', + 'Rebooting the humor module...', + 'Caching the essentials (mostly cat memes)...', + 'Running sudo make me a sandwich...', + 'Optimizing for ludicrous speed', + "Swapping bits... don't tell the bytes...", + 'Garbage collecting... be right back...', + 'Assembling the interwebs...', + 'Converting coffee into code...', + 'Pushing to production (and hoping for the best)...', + 'Updating the syntax for reality...', + 'Rewiring the synapses...', + 'Looking for a misplaced semicolon...', + "Greasin' the cogs of the machine...", + 'Pre-heating the servers...', + 'Calibrating the flux capacitor...', + 'Engaging the improbability drive...', + 'Channeling the Force...', + 'Aligning the stars for optimal response...', + 'So say we all...', + 'Loading the next great idea...', + "Just a moment, I'm in the zone...", + 'Preparing to dazzle you with brilliance...', + "Just a tick, I'm polishing my wit...", + "Hold tight, I'm crafting a masterpiece...", + "Just a jiffy, I'm debugging the universe...", + "Just a moment, I'm aligning the pixels...", + "Just a sec, I'm optimizing the humor...", + "Just a moment, I'm tuning the algorithms...", + 'Warp speed engaged...', + 'Mining for more Dilithium crystals...', + "I'm Giving Her all she's got Captain!", + "Don't panic...", + 'Following the white rabbit...', + 'The truth is in here... somewhere...', + 'Blowing on the cartridge...', + 'Looking for the princess in another castle...', + 'Loading... Do a barrel roll!', + 'Waiting for the respawn...', + 'Finishing the Kessel Run in less than 12 parsecs...', + "The cake is not a lie, it's just still loading...", + 'Fiddling with the character creation screen...', + "Just a moment, I'm finding the right meme...", + "Pressing 'A' to continue...", + 'Herding digital cats...', + 'Polishing the pixels...', + 'Finding a suitable loading screen pun...', + 'Distracting you with this witty phrase...', + 'Almost there... probably...', + 'Our hamsters are working as fast as they can...', + 'Giving Cloudy a pat on the head...', + 'Petting the cat...', + 'Rickrolling my boss...', + 'Doing research on the latest memes...', + 'Figuring out how to make this more witty...', + 'Hmmm... let me think...', + 'What do you call a fish with no eyes? A fsh...', + 'Why did the computer go to therapy? It had too many bytes...', + "Why don't programmers like nature? It has too many bugs...", + 'Why do programmers prefer dark mode? Because light attracts bugs...', + 'Why did the developer go broke? Because he used up all his cache...', + "What can you do with a broken pencil? Nothing, it's pointless...", + 'Applying percussive maintenance...', + 'Searching for the correct USB orientation...', + 'Ensuring the magic smoke stays inside the wires...', + 'Rewriting in Rust for no particular reason...', + 'Trying to exit Vim...', + 'Spinning up the hamster wheel...', + "That's not a bug, it's an undocumented feature...", + 'Engage.', + "I'll be back... with an answer.", + 'My other process is a TARDIS...', + 'Communing with the machine spirit...', + 'Letting the thoughts marinate...', + 'Just remembered where I put my keys...', + 'Pondering the orb...', + "I've seen things you people wouldn't believe... like a user who reads loading messages.", + 'Initiating thoughtful gaze...', + "What's a computer's favorite snack? Microchips.", + "Why do Java developers wear glasses? Because they don't C#.", + 'Charging the laser... pew pew!', + 'Dividing by zero... just kidding!', + 'Looking for an adult superviso... I mean, processing.', + 'Making it go beep boop.', + 'Buffering... because even AIs need a moment.', + 'Entangling quantum particles for a faster response...', + 'Polishing the chrome... on the algorithms.', + 'Are you not entertained? (Working on it!)', + 'Summoning the code gremlins... to help, of course.', + 'Just waiting for the dial-up tone to finish...', + 'Recalibrating the humor-o-meter.', + 'My other loading screen is even funnier.', + "Pretty sure there's a cat walking on the keyboard somewhere...", + 'Enhancing... Enhancing... Still loading.', + "It's not a bug, it's a feature... of this loading screen.", + 'Have you tried turning it off and on again? (The loading screen, not me.)', ]; export const PHRASE_CHANGE_INTERVAL_MS = 15000; @@ -37,7 +142,6 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => { WITTY_LOADING_PHRASES[0], ); const phraseIntervalRef = useRef(null); - const currentPhraseIndexRef = useRef(0); useEffect(() => { if (isWaiting) { @@ -50,16 +154,18 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => { if (phraseIntervalRef.current) { clearInterval(phraseIntervalRef.current); } - // Reset to the first witty phrase when starting to respond - currentPhraseIndexRef.current = 0; - setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[0]); + // Select an initial random phrase + const initialRandomIndex = Math.floor( + Math.random() * WITTY_LOADING_PHRASES.length, + ); + setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[initialRandomIndex]); phraseIntervalRef.current = setInterval(() => { - currentPhraseIndexRef.current = - (currentPhraseIndexRef.current + 1) % WITTY_LOADING_PHRASES.length; - setCurrentLoadingPhrase( - WITTY_LOADING_PHRASES[currentPhraseIndexRef.current], + // Select a new random phrase + const randomIndex = Math.floor( + Math.random() * WITTY_LOADING_PHRASES.length, ); + setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[randomIndex]); }, PHRASE_CHANGE_INTERVAL_MS); } else { // Idle or other states, clear the phrase interval @@ -69,7 +175,6 @@ export const usePhraseCycler = (isActive: boolean, isWaiting: boolean) => { phraseIntervalRef.current = null; } setCurrentLoadingPhrase(WITTY_LOADING_PHRASES[0]); - currentPhraseIndexRef.current = 0; } return () => {