From 83db85eb2589c5dc4a84fa1f8adf3970dc6b851a Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Thu, 30 Apr 2026 17:25:19 -0400 Subject: [PATCH] Add scenario tests from design doc verification matrix Tests matching the verification matrix in mobile-terminal-phone-mode-checklist.md: - Desktop window resize while mobile viewing: override persists - Mobile disconnect restores all terminals: overrides fully cleared - PTY exits while phone-fitted: no stale state remains - Mobile reconnects after disconnect: new override works - Multiple tabs with same pane IDs: cross-tab isolation - Change listener lifecycle: fires for both override and restore Co-authored-by: Orca --- .../pane-manager/mobile-fit-overrides.test.ts | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/renderer/src/lib/pane-manager/mobile-fit-overrides.test.ts b/src/renderer/src/lib/pane-manager/mobile-fit-overrides.test.ts index a284b6b19b1..66a4cf4cf28 100644 --- a/src/renderer/src/lib/pane-manager/mobile-fit-overrides.test.ts +++ b/src/renderer/src/lib/pane-manager/mobile-fit-overrides.test.ts @@ -301,3 +301,109 @@ describe('getAllOverrides', () => { expect(getFitOverrideForPty('pty-1')).not.toBeNull() }) }) + +// --------------------------------------------------------------------------- +// Scenario tests (from design doc verification matrix) +// --------------------------------------------------------------------------- + +describe('scenario: desktop window resize while mobile is viewing', () => { + it('override persists across setFitOverride calls — desktop safeFit will see it', () => { + setFitOverride('pty-1', 'mobile-fit', 49, 20) + bindPanePtyId(1, 'pty-1', 'tab-0') + + // Simulate desktop resize triggering a re-check — override should still be there + expect(getFitOverrideForPty('pty-1')).toEqual({ mode: 'mobile-fit', cols: 49, rows: 20 }) + expect(getFitOverrideForPane(1, 'tab-0')).toEqual({ mode: 'mobile-fit', cols: 49, rows: 20 }) + }) +}) + +describe('scenario: mobile disconnect restores all terminals', () => { + it('clearing all overrides for a disconnected client removes all traces', () => { + setFitOverride('pty-1', 'mobile-fit', 49, 20) + setFitOverride('pty-2', 'mobile-fit', 49, 20) + bindPanePtyId(1, 'pty-1', 'tab-0') + bindPanePtyId(2, 'pty-2', 'tab-0') + + // Simulate runtime clearing overrides on disconnect + setFitOverride('pty-1', 'desktop-fit', 120, 40) + setFitOverride('pty-2', 'desktop-fit', 120, 40) + + expect(getFitOverrideForPty('pty-1')).toBeNull() + expect(getFitOverrideForPty('pty-2')).toBeNull() + expect(getFitOverrideForPane(1, 'tab-0')).toBeNull() + expect(getFitOverrideForPane(2, 'tab-0')).toBeNull() + }) +}) + +describe('scenario: PTY exits while phone-fitted', () => { + it('clearing override for exited PTY leaves no stale state', () => { + setFitOverride('pty-1', 'mobile-fit', 49, 20) + bindPanePtyId(1, 'pty-1', 'tab-0') + + // Runtime clears override on PTY exit + setFitOverride('pty-1', 'desktop-fit', 0, 0) + unbindPane(1, 'tab-0') + + expect(getFitOverrideForPty('pty-1')).toBeNull() + expect(getFitOverrideForPane(1, 'tab-0')).toBeNull() + expect(getPaneIdsForPty('pty-1')).toEqual([]) + expect(getAllOverrides().size).toBe(0) + }) +}) + +describe('scenario: mobile reconnects after disconnect', () => { + it('new override after clear works correctly', () => { + // First session + setFitOverride('pty-1', 'mobile-fit', 49, 20) + bindPanePtyId(1, 'pty-1', 'tab-0') + + // Disconnect + setFitOverride('pty-1', 'desktop-fit', 120, 40) + + // Reconnect — new session sets override again + setFitOverride('pty-1', 'mobile-fit', 55, 22) + + expect(getFitOverrideForPty('pty-1')).toEqual({ mode: 'mobile-fit', cols: 55, rows: 22 }) + expect(getFitOverrideForPane(1, 'tab-0')).toEqual({ mode: 'mobile-fit', cols: 55, rows: 22 }) + }) +}) + +describe('scenario: multiple tabs with same pane IDs', () => { + it('override on one tab does not affect the other', () => { + bindPanePtyId(1, 'pty-A', 'tab-0') + bindPanePtyId(1, 'pty-B', 'tab-1') + + setFitOverride('pty-A', 'mobile-fit', 49, 20) + + expect(getFitOverrideForPane(1, 'tab-0')?.cols).toBe(49) + expect(getFitOverrideForPane(1, 'tab-1')).toBeNull() + }) + + it('clearing override for one tab does not affect the other', () => { + bindPanePtyId(1, 'pty-A', 'tab-0') + bindPanePtyId(1, 'pty-B', 'tab-1') + + setFitOverride('pty-A', 'mobile-fit', 49, 20) + setFitOverride('pty-B', 'mobile-fit', 60, 25) + + // Clear only tab-0's PTY + setFitOverride('pty-A', 'desktop-fit', 120, 40) + + expect(getFitOverrideForPane(1, 'tab-0')).toBeNull() + expect(getFitOverrideForPane(1, 'tab-1')?.cols).toBe(60) + }) +}) + +describe('scenario: change listener fires for both override and restore', () => { + it('tracks full lifecycle: mobile-fit → desktop-fit', () => { + const events: { mode: string }[] = [] + const unsub = onOverrideChange((e) => events.push({ mode: e.mode })) + + setFitOverride('pty-1', 'mobile-fit', 49, 20) + setFitOverride('pty-1', 'desktop-fit', 120, 40) + + expect(events).toEqual([{ mode: 'mobile-fit' }, { mode: 'desktop-fit' }]) + + unsub() + }) +})