|
| 1 | +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; |
| 2 | +import type { DisplaySettings, GistMessage, StepDisplayConfig } from '../types'; |
| 3 | + |
| 4 | +const mockGist = vi.hoisted(() => ({ |
| 5 | + currentMessages: [] as GistMessage[], |
| 6 | + dismissMessage: vi.fn(() => Promise.resolve()), |
| 7 | +})); |
| 8 | + |
| 9 | +vi.mock('../gist', () => ({ default: mockGist })); |
| 10 | +vi.mock('../utilities/log', () => ({ log: vi.fn() })); |
| 11 | +vi.mock('./message-manager', () => ({ |
| 12 | + applyMessageStepChange: vi.fn(), |
| 13 | + hideMessageVisually: vi.fn(), |
| 14 | +})); |
| 15 | +vi.mock('./message-component-manager', () => ({ |
| 16 | + sendDisplaySettingsToIframe: vi.fn(), |
| 17 | +})); |
| 18 | +vi.mock('../utilities/message-utils', () => ({ |
| 19 | + hasDisplayChanged: vi.fn(() => false), |
| 20 | + wideOverlayPositions: ['x-gist-top-full', 'x-gist-bottom-full'], |
| 21 | + mapOverlayPositionToElementId: vi.fn((pos: string) => pos), |
| 22 | +})); |
| 23 | +vi.mock('./preview-bar-styles', () => ({ |
| 24 | + PREVIEW_BAR_CSS: '.gist-pb-toggle-btn { color: white; }', |
| 25 | + chevronSvg: vi.fn(() => '<svg></svg>'), |
| 26 | +})); |
| 27 | +vi.mock('../services/preview-service', () => ({ |
| 28 | + savePreviewDisplaySettings: vi.fn(() => Promise.resolve({ status: 200 })), |
| 29 | + deletePreviewSession: vi.fn(() => Promise.resolve()), |
| 30 | +})); |
| 31 | +vi.mock('../utilities/preview-mode', () => ({ |
| 32 | + PREVIEW_PARAM_ID: 'cioPreviewId', |
| 33 | + teardownPreview: vi.fn(), |
| 34 | +})); |
| 35 | + |
| 36 | +import { |
| 37 | + initPreviewBar, |
| 38 | + updatePreviewBarMessage, |
| 39 | + updatePreviewBarStep, |
| 40 | + destroyPreviewBar, |
| 41 | +} from './preview-bar-manager'; |
| 42 | + |
| 43 | +describe('preview-bar-manager', () => { |
| 44 | + beforeEach(() => { |
| 45 | + vi.clearAllMocks(); |
| 46 | + document.body.innerHTML = ''; |
| 47 | + mockGist.currentMessages = []; |
| 48 | + }); |
| 49 | + |
| 50 | + afterEach(() => { |
| 51 | + destroyPreviewBar(); |
| 52 | + }); |
| 53 | + |
| 54 | + function initBarWithMessage( |
| 55 | + steps: StepDisplayConfig[] = [], |
| 56 | + displayType: DisplaySettings['displayType'] = 'modal' |
| 57 | + ): GistMessage { |
| 58 | + initPreviewBar(); |
| 59 | + const message: GistMessage = { |
| 60 | + messageId: 'msg-1', |
| 61 | + instanceId: 'inst-1', |
| 62 | + displaySettings: steps.length > 0 ? (steps as unknown as DisplaySettings) : { displayType }, |
| 63 | + }; |
| 64 | + mockGist.currentMessages = [message]; |
| 65 | + updatePreviewBarMessage(message); |
| 66 | + return message; |
| 67 | + } |
| 68 | + |
| 69 | + describe('initPreviewBar', () => { |
| 70 | + it('creates the preview bar element in the DOM', () => { |
| 71 | + initPreviewBar(); |
| 72 | + expect(document.getElementById('gist-preview-bar')).not.toBeNull(); |
| 73 | + }); |
| 74 | + |
| 75 | + it('injects preview bar styles', () => { |
| 76 | + initPreviewBar(); |
| 77 | + expect(document.getElementById('gist-pb-styles')).not.toBeNull(); |
| 78 | + }); |
| 79 | + |
| 80 | + it('does not duplicate the bar on repeated calls', () => { |
| 81 | + initPreviewBar(); |
| 82 | + initPreviewBar(); |
| 83 | + expect(document.querySelectorAll('#gist-preview-bar').length).toBe(1); |
| 84 | + }); |
| 85 | + }); |
| 86 | + |
| 87 | + describe('display type dropdown', () => { |
| 88 | + it('includes tooltip option in display type dropdown', () => { |
| 89 | + initBarWithMessage(); |
| 90 | + const bar = document.getElementById('gist-preview-bar')!; |
| 91 | + const select = bar.querySelector<HTMLSelectElement>('.gist-pb-select'); |
| 92 | + expect(select).not.toBeNull(); |
| 93 | + |
| 94 | + const options = Array.from(select!.options).map((o) => o.value); |
| 95 | + expect(options).toContain('tooltip'); |
| 96 | + }); |
| 97 | + |
| 98 | + it('includes all four display types: modal, overlay, inline, tooltip', () => { |
| 99 | + initBarWithMessage(); |
| 100 | + const bar = document.getElementById('gist-preview-bar')!; |
| 101 | + const selects = bar.querySelectorAll<HTMLSelectElement>('.gist-pb-select'); |
| 102 | + const displayTypeSelect = selects[0]; |
| 103 | + const values = Array.from(displayTypeSelect.options).map((o) => o.value); |
| 104 | + |
| 105 | + expect(values).toEqual(['modal', 'overlay', 'inline', 'tooltip']); |
| 106 | + }); |
| 107 | + }); |
| 108 | + |
| 109 | + describe('tooltip controls', () => { |
| 110 | + it('renders element selector and position controls when display type is tooltip', () => { |
| 111 | + initBarWithMessage([], 'tooltip'); |
| 112 | + const bar = document.getElementById('gist-preview-bar')!; |
| 113 | + const labels = Array.from(bar.querySelectorAll('.gist-pb-label')).map((el) => el.textContent); |
| 114 | + |
| 115 | + expect(labels).toContain('Element Selector'); |
| 116 | + expect(labels).toContain('Position'); |
| 117 | + }); |
| 118 | + |
| 119 | + it('renders tooltip position options: top, bottom, left, right', () => { |
| 120 | + initBarWithMessage([], 'tooltip'); |
| 121 | + const bar = document.getElementById('gist-preview-bar')!; |
| 122 | + const selects = bar.querySelectorAll<HTMLSelectElement>('.gist-pb-select'); |
| 123 | + const positionSelect = Array.from(selects).find((s) => |
| 124 | + Array.from(s.options).some((o) => o.value === 'left') |
| 125 | + ); |
| 126 | + expect(positionSelect).not.toBeUndefined(); |
| 127 | + |
| 128 | + const values = Array.from(positionSelect!.options).map((o) => o.value); |
| 129 | + expect(values).toEqual(['top', 'bottom', 'left', 'right']); |
| 130 | + }); |
| 131 | + |
| 132 | + it('renders Select Element button', () => { |
| 133 | + initBarWithMessage([], 'tooltip'); |
| 134 | + const bar = document.getElementById('gist-preview-bar')!; |
| 135 | + const selectBtn = bar.querySelector('.gist-pb-select-elem-btn'); |
| 136 | + expect(selectBtn).not.toBeNull(); |
| 137 | + expect(selectBtn!.textContent).toBe('Select Element'); |
| 138 | + }); |
| 139 | + |
| 140 | + it('renders element selector input with placeholder text', () => { |
| 141 | + initBarWithMessage([], 'tooltip'); |
| 142 | + const bar = document.getElementById('gist-preview-bar')!; |
| 143 | + const input = bar.querySelector<HTMLInputElement>('.gist-pb-input[type="text"]'); |
| 144 | + expect(input).not.toBeNull(); |
| 145 | + expect(input!.placeholder).toBe('Element ID or selector'); |
| 146 | + }); |
| 147 | + }); |
| 148 | + |
| 149 | + describe('inline controls', () => { |
| 150 | + it('renders element selector for inline display type', () => { |
| 151 | + initBarWithMessage([], 'inline'); |
| 152 | + const bar = document.getElementById('gist-preview-bar')!; |
| 153 | + const labels = Array.from(bar.querySelectorAll('.gist-pb-label')).map((el) => el.textContent); |
| 154 | + |
| 155 | + expect(labels).toContain('Element Selector'); |
| 156 | + }); |
| 157 | + |
| 158 | + it('does not render position dropdown for inline type', () => { |
| 159 | + initBarWithMessage([], 'inline'); |
| 160 | + const bar = document.getElementById('gist-preview-bar')!; |
| 161 | + const labels = Array.from(bar.querySelectorAll('.gist-pb-label')).map((el) => el.textContent); |
| 162 | + |
| 163 | + expect(labels).not.toContain('Position'); |
| 164 | + }); |
| 165 | + }); |
| 166 | + |
| 167 | + describe('button type attributes', () => { |
| 168 | + it('toggle button has type="button"', () => { |
| 169 | + initBarWithMessage(); |
| 170 | + const bar = document.getElementById('gist-preview-bar')!; |
| 171 | + const toggleBtn = bar.querySelector<HTMLButtonElement>('.gist-pb-toggle-btn'); |
| 172 | + expect(toggleBtn).not.toBeNull(); |
| 173 | + expect(toggleBtn!.type).toBe('button'); |
| 174 | + }); |
| 175 | + |
| 176 | + it('end session button has type="button"', () => { |
| 177 | + initBarWithMessage(); |
| 178 | + const bar = document.getElementById('gist-preview-bar')!; |
| 179 | + const endBtn = bar.querySelector<HTMLButtonElement>('.gist-pb-save-btn'); |
| 180 | + expect(endBtn).not.toBeNull(); |
| 181 | + expect(endBtn!.type).toBe('button'); |
| 182 | + }); |
| 183 | + |
| 184 | + it('select element button has type="button"', () => { |
| 185 | + initBarWithMessage([], 'tooltip'); |
| 186 | + const bar = document.getElementById('gist-preview-bar')!; |
| 187 | + const selectBtn = bar.querySelector<HTMLButtonElement>('.gist-pb-select-elem-btn'); |
| 188 | + expect(selectBtn).not.toBeNull(); |
| 189 | + expect(selectBtn!.type).toBe('button'); |
| 190 | + }); |
| 191 | + }); |
| 192 | + |
| 193 | + describe('updatePreviewBarStep', () => { |
| 194 | + it('updates the bar when step changes', () => { |
| 195 | + const steps: StepDisplayConfig[] = [ |
| 196 | + { stepName: 'step-1', displaySettings: { displayType: 'modal' } }, |
| 197 | + { |
| 198 | + stepName: 'step-2', |
| 199 | + displaySettings: { displayType: 'tooltip', tooltipPosition: 'bottom' }, |
| 200 | + }, |
| 201 | + ]; |
| 202 | + initBarWithMessage(steps); |
| 203 | + |
| 204 | + updatePreviewBarStep('step-2', { displayType: 'tooltip', tooltipPosition: 'bottom' }); |
| 205 | + |
| 206 | + const bar = document.getElementById('gist-preview-bar')!; |
| 207 | + const labels = Array.from(bar.querySelectorAll('.gist-pb-label')).map((el) => el.textContent); |
| 208 | + expect(labels).toContain('Element Selector'); |
| 209 | + expect(labels).toContain('Position'); |
| 210 | + }); |
| 211 | + }); |
| 212 | + |
| 213 | + describe('destroyPreviewBar', () => { |
| 214 | + it('removes the bar and styles from the DOM', () => { |
| 215 | + initPreviewBar(); |
| 216 | + expect(document.getElementById('gist-preview-bar')).not.toBeNull(); |
| 217 | + expect(document.getElementById('gist-pb-styles')).not.toBeNull(); |
| 218 | + |
| 219 | + destroyPreviewBar(); |
| 220 | + |
| 221 | + expect(document.getElementById('gist-preview-bar')).toBeNull(); |
| 222 | + expect(document.getElementById('gist-pb-styles')).toBeNull(); |
| 223 | + }); |
| 224 | + }); |
| 225 | +}); |
0 commit comments