summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/usePhraseCycler.test.ts
blob: ec04aca605a756e1484c3fe353fb77ab9b075993 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import {
  usePhraseCycler,
  WITTY_LOADING_PHRASES,
  PHRASE_CHANGE_INTERVAL_MS,
} from './usePhraseCycler.js';

describe('usePhraseCycler', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should initialize with the first witty phrase when not active and not waiting', () => {
    const { result } = renderHook(() => usePhraseCycler(false, false));
    expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
  });

  it('should show "Waiting for user confirmation..." when isWaiting is true', () => {
    const { result, rerender } = renderHook(
      ({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
      { initialProps: { isActive: true, isWaiting: false } },
    );
    rerender({ isActive: true, isWaiting: true });
    expect(result.current).toBe('Waiting for user confirmation...');
  });

  it('should not cycle phrases if isActive is false and not waiting', () => {
    const { result } = renderHook(() => usePhraseCycler(false, false));
    act(() => {
      vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS * 2);
    });
    expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
  });

  it('should cycle through witty phrases when isActive is true and not waiting', () => {
    const { result } = renderHook(() => usePhraseCycler(true, false));
    // 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);
    });
    // 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(WITTY_LOADING_PHRASES).toContain(result.current);
  });

  it('should reset to a witty phrase when isActive becomes true after being false (and not waiting)', () => {
    // Ensure there are at least two phrases for this test to be meaningful.
    if (WITTY_LOADING_PHRASES.length < 2) {
      return;
    }

    // Mock Math.random to make the test deterministic.
    let callCount = 0;
    vi.spyOn(Math, 'random').mockImplementation(() => {
      // Cycle through 0, 1, 0, 1, ...
      const val = callCount % 2;
      callCount++;
      return val / WITTY_LOADING_PHRASES.length;
    });

    const { result, rerender } = renderHook(
      ({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
      { initialProps: { isActive: false, isWaiting: false } },
    );

    // Activate
    rerender({ isActive: true, isWaiting: false });
    const firstActivePhrase = result.current;
    expect(WITTY_LOADING_PHRASES).toContain(firstActivePhrase);
    // With our mock, this should be the first phrase.
    expect(firstActivePhrase).toBe(WITTY_LOADING_PHRASES[0]);

    act(() => {
      vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
    });

    // Phrase should change to the second phrase.
    expect(result.current).not.toBe(firstActivePhrase);
    expect(result.current).toBe(WITTY_LOADING_PHRASES[1]);

    // Set to inactive - should reset to the default initial phrase
    rerender({ isActive: false, isWaiting: false });
    expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);

    // Set back to active - should pick a random witty phrase (which our mock controls)
    act(() => {
      rerender({ isActive: true, isWaiting: false });
    });
    // The random mock will now return 0, so it should be the first phrase again.
    expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
  });

  it('should clear phrase interval on unmount when active', () => {
    const { unmount } = renderHook(() => usePhraseCycler(true, false));
    const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
    unmount();
    expect(clearIntervalSpy).toHaveBeenCalledOnce();
  });

  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 } },
    );

    const _initialPhrase = result.current;
    expect(WITTY_LOADING_PHRASES).toContain(_initialPhrase);

    // Cycle to a different phrase (potentially)
    act(() => {
      vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
    });
    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 - should pick a random witty phrase
    rerender({ isActive: true, isWaiting: false });
    expect(WITTY_LOADING_PHRASES).toContain(result.current);
  });
});