247 lines
7.3 KiB
TypeScript
247 lines
7.3 KiB
TypeScript
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||
|
|
import { describe, expect, it, vi } from 'vitest';
|
||
|
|
|
||
|
|
import { FileWorkspace, scrollWorkspaceTabsWithWheel } from '../../src/components/FileWorkspace';
|
||
|
|
import { projectSplitClassName } from '../../src/components/ProjectView';
|
||
|
|
|
||
|
|
describe('FileWorkspace upload input', () => {
|
||
|
|
it('keeps the Design Files picker aligned with drag-and-drop file support', () => {
|
||
|
|
const markup = renderToStaticMarkup(
|
||
|
|
<FileWorkspace
|
||
|
|
projectId="project-1"
|
||
|
|
files={[]}
|
||
|
|
liveArtifacts={[]}
|
||
|
|
onRefreshFiles={vi.fn()}
|
||
|
|
isDeck={false}
|
||
|
|
tabsState={{ tabs: [], active: null }}
|
||
|
|
onTabsStateChange={vi.fn()}
|
||
|
|
/>,
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(markup).toContain('data-testid="design-files-upload-input"');
|
||
|
|
expect(markup).not.toContain('accept=');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('keeps focus mode controls in the workspace tab bar', () => {
|
||
|
|
const markup = renderToStaticMarkup(
|
||
|
|
<FileWorkspace
|
||
|
|
projectId="project-1"
|
||
|
|
files={[]}
|
||
|
|
liveArtifacts={[]}
|
||
|
|
onRefreshFiles={vi.fn()}
|
||
|
|
isDeck={false}
|
||
|
|
tabsState={{ tabs: [], active: null }}
|
||
|
|
onTabsStateChange={vi.fn()}
|
||
|
|
focusMode={false}
|
||
|
|
onFocusModeChange={vi.fn()}
|
||
|
|
/>,
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(markup).toContain('data-testid="workspace-focus-toggle"');
|
||
|
|
expect(markup).toContain('Focus workspace');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('keeps the focus mode action outside the horizontally scrollable tablist', () => {
|
||
|
|
const markup = renderToStaticMarkup(
|
||
|
|
<FileWorkspace
|
||
|
|
projectId="project-1"
|
||
|
|
files={[]}
|
||
|
|
liveArtifacts={[]}
|
||
|
|
onRefreshFiles={vi.fn()}
|
||
|
|
isDeck={false}
|
||
|
|
tabsState={{ tabs: [], active: null }}
|
||
|
|
onTabsStateChange={vi.fn()}
|
||
|
|
focusMode={false}
|
||
|
|
onFocusModeChange={vi.fn()}
|
||
|
|
/>,
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(markup).toContain('class="ws-tabs-shell"');
|
||
|
|
expect(markup).toContain('class="ws-tabs-actions"');
|
||
|
|
expect(markup).toMatch(
|
||
|
|
/<div class="ws-tabs-bar" role="tablist"[^>]*>[\s\S]*?<\/div><div class="ws-tabs-actions">/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('labels the same workspace control as chat restore while focused', () => {
|
||
|
|
const markup = renderToStaticMarkup(
|
||
|
|
<FileWorkspace
|
||
|
|
projectId="project-1"
|
||
|
|
files={[]}
|
||
|
|
liveArtifacts={[]}
|
||
|
|
onRefreshFiles={vi.fn()}
|
||
|
|
isDeck={false}
|
||
|
|
tabsState={{ tabs: [], active: null }}
|
||
|
|
onTabsStateChange={vi.fn()}
|
||
|
|
focusMode
|
||
|
|
onFocusModeChange={vi.fn()}
|
||
|
|
/>,
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(markup).toContain('Show chat');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('projectSplitClassName', () => {
|
||
|
|
it('marks the project split as focused so the chat pane can collapse globally', () => {
|
||
|
|
expect(projectSplitClassName(false)).toBe('split');
|
||
|
|
expect(projectSplitClassName(true)).toBe('split split-focus');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('scrollWorkspaceTabsWithWheel', () => {
|
||
|
|
function makeTabBar(scrollLeft: number, scrollWidth = 400, clientWidth = 200) {
|
||
|
|
return { scrollLeft, scrollWidth, clientWidth } as HTMLDivElement;
|
||
|
|
}
|
||
|
|
|
||
|
|
function makeClampedTabBar(scrollLeft: number, scrollWidth = 400, clientWidth = 200) {
|
||
|
|
let value = scrollLeft;
|
||
|
|
return {
|
||
|
|
scrollWidth,
|
||
|
|
clientWidth,
|
||
|
|
get scrollLeft() {
|
||
|
|
return value;
|
||
|
|
},
|
||
|
|
set scrollLeft(next: number) {
|
||
|
|
value = Math.min(Math.max(next, 0), scrollWidth - clientWidth);
|
||
|
|
},
|
||
|
|
} as HTMLDivElement;
|
||
|
|
}
|
||
|
|
|
||
|
|
it('maps vertical mouse wheel movement to horizontal tab scrolling', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(12);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 0,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: 40,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(52);
|
||
|
|
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('supports reverse vertical wheel movement', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(52);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 0,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: -40,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(12);
|
||
|
|
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('normalizes line-based wheel deltas to useful pixel movement', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(12);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 1,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: 3,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(60);
|
||
|
|
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('normalizes page-based wheel deltas to useful pixel movement', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(12, 600, 200);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 2,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: 1,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(172);
|
||
|
|
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('leaves native horizontal wheel gestures alone', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(12);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 0,
|
||
|
|
deltaX: 50,
|
||
|
|
deltaY: 10,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(12);
|
||
|
|
expect(preventDefault).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('leaves ctrl-wheel zoom gestures alone', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(12);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: true,
|
||
|
|
deltaMode: 0,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: 40,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(12);
|
||
|
|
expect(preventDefault).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not intercept vertical wheel movement when tabs do not overflow', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeTabBar(12, 200, 200);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 0,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: 40,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(12);
|
||
|
|
expect(preventDefault).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('lets page scrolling continue when the tab bar is already at the wheel boundary', () => {
|
||
|
|
const preventDefault = vi.fn();
|
||
|
|
const currentTarget = makeClampedTabBar(200, 400, 200);
|
||
|
|
const event = {
|
||
|
|
ctrlKey: false,
|
||
|
|
deltaMode: 0,
|
||
|
|
deltaX: 0,
|
||
|
|
deltaY: 40,
|
||
|
|
preventDefault,
|
||
|
|
} as unknown as WheelEvent;
|
||
|
|
|
||
|
|
scrollWorkspaceTabsWithWheel(currentTarget, event);
|
||
|
|
|
||
|
|
expect(currentTarget.scrollLeft).toBe(200);
|
||
|
|
expect(preventDefault).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
});
|