refactor(frontend): EditorLayout final shell (B4-7) (#906)

* refactor(frontend): extract useOverlayState hook from EditorLayout

* refactor(frontend): extract useStackActions hook and wire useOverlayState into EditorLayout

* refactor(frontend): fix quality issues in useStackActions post-review

* fix(frontend): fix interval leak, RunResult contract, yml hardcode, and loadFile length in useStackActions

* refactor(frontend): extract useSidebarContextMenu hook from EditorLayout

* refactor(frontend): extract ShellOverlays component from EditorLayout

* refactor(frontend): relocate Monaco layout effect and log-viewer event listener out of EditorLayout

The Monaco tab-switch layout effect is now self-contained in EditorView,
alongside its monacoEditorRef. The SENCHO_OPEN_LOGS_EVENT listener moves
into useOverlayState, where openLogViewer lives. EditorLayout is left with
the two coordination effects that depend on cross-hook state.
This commit is contained in:
Anso
2026-05-04 07:43:02 -04:00
committed by GitHub
parent 34fcb2591f
commit 1cf996142b
7 changed files with 1491 additions and 1102 deletions
@@ -0,0 +1,100 @@
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { useOverlayState } from './useOverlayState';
describe('useOverlayState', () => {
it('initialises with all overlays closed and null/empty data', () => {
const { result } = renderHook(() => useOverlayState());
expect(result.current.createDialogOpen).toBe(false);
expect(result.current.deleteDialogOpen).toBe(false);
expect(result.current.stackToDelete).toBeNull();
expect(result.current.pendingUnsavedLoad).toBeNull();
expect(result.current.pendingUnsavedNode).toBeNull();
expect(result.current.bashModalOpen).toBe(false);
expect(result.current.selectedContainer).toBeNull();
expect(result.current.logViewerOpen).toBe(false);
expect(result.current.logContainer).toBeNull();
expect(result.current.alertSheetOpen).toBe(false);
expect(result.current.alertSheetStack).toBe('');
expect(result.current.autoHealStackName).toBeNull();
expect(result.current.policyBlock).toBeNull();
expect(result.current.policyBypassing).toBe(false);
expect(result.current.stackMisconfigScanId).toBeNull();
expect(result.current.diffPreview).toBeNull();
expect(result.current.diffPreviewConfirming).toBe(false);
});
it('openBashModal sets open flag and container object', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openBashModal({ id: 'abc', name: 'my-container' }));
expect(result.current.bashModalOpen).toBe(true);
expect(result.current.selectedContainer).toEqual({ id: 'abc', name: 'my-container' });
});
it('closeBashModal resets bash state', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openBashModal({ id: 'abc', name: 'my-container' }));
act(() => result.current.closeBashModal());
expect(result.current.bashModalOpen).toBe(false);
expect(result.current.selectedContainer).toBeNull();
});
it('openDeleteDialog sets open flag and stack name', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openDeleteDialog('my-stack'));
expect(result.current.deleteDialogOpen).toBe(true);
expect(result.current.stackToDelete).toBe('my-stack');
});
it('closeDeleteDialog resets delete state', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openDeleteDialog('my-stack'));
act(() => result.current.closeDeleteDialog());
expect(result.current.deleteDialogOpen).toBe(false);
expect(result.current.stackToDelete).toBeNull();
});
it('openLogViewer sets open flag and container object', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openLogViewer({ id: 'xyz', name: 'log-container' }));
expect(result.current.logViewerOpen).toBe(true);
expect(result.current.logContainer).toEqual({ id: 'xyz', name: 'log-container' });
});
it('closeLogViewer resets log viewer state', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openLogViewer({ id: 'xyz', name: 'log-container' }));
act(() => result.current.closeLogViewer());
expect(result.current.logViewerOpen).toBe(false);
expect(result.current.logContainer).toBeNull();
});
it('openAlertSheet sets sheet state', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openAlertSheet('web-stack'));
expect(result.current.alertSheetOpen).toBe(true);
expect(result.current.alertSheetStack).toBe('web-stack');
});
it('openAlertSheet with autoHeal sets autoHealStackName', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openAlertSheet('web-stack', 'web-stack'));
expect(result.current.alertSheetOpen).toBe(true);
expect(result.current.alertSheetStack).toBe('web-stack');
expect(result.current.autoHealStackName).toBe('web-stack');
});
it('openAlertSheet without autoHeal leaves autoHealStackName null', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openAlertSheet('web-stack'));
expect(result.current.alertSheetOpen).toBe(true);
expect(result.current.autoHealStackName).toBeNull();
});
it('closeAlertSheet sets alertSheetOpen to false', () => {
const { result } = renderHook(() => useOverlayState());
act(() => result.current.openAlertSheet('web-stack'));
act(() => result.current.closeAlertSheet());
expect(result.current.alertSheetOpen).toBe(false);
});
});