Files
open-design/apps/web/tests/components/project-split-layout.test.ts
Ray XiandGitHub 3c7b16b442 fix(web): keep the project split still across the Home hand-off (OPEND-3207) (#8229)
* fix(web): keep the project split still across the Home hand-off

The optimistic creation frame left `.split` on its stylesheet default
(460px) while ProjectView resolved the saved width, or the equal split of
the container, only after it mounted — and that first write ran through
the 200ms width transition, so the chat column moved once the real view
arrived (OPEND-3207).

Move the split geometry into `project-split-layout.ts`, have both surfaces
resolve their first-paint width through `resolveProjectSplitLayout`, and
settle a surface's first measured write without the transition.

Red spec: e2e/ui/home-send-split-width.test.ts (red on main, green here).

* test(web): read the 4px handle constant from project-split-layout

The material spec pinned `SPLIT_RESIZE_HANDLE_WIDTH = 4` in ProjectView's
source; the constant now lives in project-split-layout.ts and ProjectView
imports it, so assert on the module and on the import.

* test(cms): wait for the Test modal impression before switching language

The locale case only passed while the impression frame never fired: on a
fast machine the next language switch unmounted the modal before its
requestAnimationFrame recorded the impression. CI is slow enough for the
frame to land, and the switch that follows then closes the modal for good.
Waiting for the recorded impression makes that order deterministic.

* fix(cms): keep the open Test modal across a same-deployment reload

A language switch (or lease renewal) reloads the selected Test deployment.
While the new decisions are in flight the runtime publishes a session
without decisions, and the modal treated that gap as the presentation
ending: it dropped its "open" marker, then the returning decision looked
like a new offer of an activity this account had already seen and stayed
closed.

Key the open presentation by the selected deployment and snapshot. A
reload of the same selection continues it; a redeploy, a different
account, a dismissal, or the session ending still releases it.

* test(e2e): enroll the Home hand-off specs in the ui_p0 entry-settings group

home-amr-pending, home-send-single-loading and home-send-split-width were
merged (or opened) without being enrolled in uiP0Groups, so no merge lane
had ever executed them; only the prerelease full pool would have. Enroll
them with the other Home-entry files.
2026-09-18 07:45:47 +00:00

74 lines
3.0 KiB
TypeScript

// @vitest-environment jsdom
// OPEND-3207 · one resolver for the project split's first-paint chat width.
// ProjectView and the creation frame that precedes it both ask this module,
// so the column cannot move when one hands over to the other.
import { afterEach, describe, expect, it } from 'vitest';
import {
CHAT_PANEL_WIDTH_STORAGE_KEY,
DEFAULT_CHAT_PANEL_WIDTH,
FALLBACK_MAX_CHAT_PANEL_WIDTH,
MIN_CHAT_PANEL_WIDTH,
readSavedChatPanelWidth,
resolveProjectSplitLayout,
writeProjectSplitLayout,
} from '../../src/components/project-split-layout';
afterEach(() => {
window.localStorage.clear();
document.body.innerHTML = '';
});
describe('resolveProjectSplitLayout', () => {
it('takes the equal split when nothing was saved', () => {
const layout = resolveProjectSplitLayout(1280, { width: DEFAULT_CHAT_PANEL_WIDTH, customized: false });
expect(layout.chatPanelWidth).toBe(638);
expect(layout.workspacePanelTrack).toBe('minmax(400px, 1fr)');
});
it('keeps the saved width at every container size', () => {
const saved = { width: 380, customized: true };
expect(resolveProjectSplitLayout(1280, saved).chatPanelWidth).toBe(380);
expect(resolveProjectSplitLayout(1900, saved).chatPanelWidth).toBe(380);
});
it('clamps a saved width the container cannot fit', () => {
const layout = resolveProjectSplitLayout(900, { width: 700, customized: true });
// 900 - 4 handle - 400 workspace floor = 496.
expect(layout.chatPanelWidth).toBe(496);
expect(layout.chatPanelMaxWidth).toBe(496);
});
it('falls back to the stylesheet default before the container is measured', () => {
const layout = resolveProjectSplitLayout(0, { width: DEFAULT_CHAT_PANEL_WIDTH, customized: false });
expect(layout.chatPanelWidth).toBe(DEFAULT_CHAT_PANEL_WIDTH);
expect(layout.chatPanelMaxWidth).toBe(FALLBACK_MAX_CHAT_PANEL_WIDTH);
});
});
describe('readSavedChatPanelWidth', () => {
it('reports a saved width as customized and floors it at the minimum', () => {
window.localStorage.setItem(CHAT_PANEL_WIDTH_STORAGE_KEY, '200');
expect(readSavedChatPanelWidth()).toEqual({ width: MIN_CHAT_PANEL_WIDTH, customized: true });
});
it('reports the default as not customized when nothing was saved', () => {
expect(readSavedChatPanelWidth()).toEqual({ width: DEFAULT_CHAT_PANEL_WIDTH, customized: false });
});
});
describe('writeProjectSplitLayout', () => {
it('writes the three grid properties and leaves no settling class behind', () => {
const split = document.createElement('div');
split.className = 'split';
document.body.append(split);
writeProjectSplitLayout(split, 512, 'minmax(400px, 1fr)', { animate: false });
expect(split.style.getPropertyValue('--project-chat-panel-width')).toBe('512px');
expect(split.style.getPropertyValue('--project-chat-handle-width')).toBe('4px');
expect(split.style.getPropertyValue('--project-workspace-panel-track')).toBe('minmax(400px, 1fr)');
expect(split.classList.contains('split-settling')).toBe(false);
});
});