Files
AFFiNE-Mirror/blocksuite/integration-test/src/__tests__/edgeless/frame.spec.ts
L-Sun d8cbeb1bb1 fix(editor): can move frame by dragging title (#12661)
Close [BS-3351](https://linear.app/affine-design/issue/BS-3351/无法通过拖拽frame-title来拖拽frame)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Improved rendering performance and consistency for widgets within frames.
  - Frame titles are now directly associated with individual frames and are draggable.

- **Bug Fixes**
  - Selection logic for frames has been refined to better handle locked states and title area interactions.

- **Refactor**
  - Frame title widget and related components have been simplified for clarity and maintainability.
  - Removed dynamic positioning and click toggling from frame titles for a cleaner interaction model.

- **Tests**
  - Added a test to verify that frame titles are draggable.
  - Temporarily disabled tests related to frame title stacking and selection due to ongoing changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-03 05:14:39 +00:00

130 lines
3.7 KiB
TypeScript

import type { FrameBlockComponent } from '@blocksuite/affine/blocks/frame';
import type { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root';
import type { FrameBlockModel } from '@blocksuite/affine/model';
import type { AffineFrameTitleWidget } from '@blocksuite/affine/widgets/frame-title';
import { assertType } from '@blocksuite/global/utils';
import { Text } from '@blocksuite/store';
import { beforeEach, describe, expect, test } from 'vitest';
import { wait } from '../utils/common.js';
import { getDocRootBlock } from '../utils/edgeless.js';
import { setupEditor } from '../utils/setup.js';
describe('frame', () => {
let service!: EdgelessRootBlockComponent['service'];
beforeEach(async () => {
const cleanup = await setupEditor('edgeless');
service = getDocRootBlock(window.doc, window.editor, 'edgeless').service;
return cleanup;
});
test('frame should have title', async () => {
const frame = service.doc.addBlock(
'affine:frame',
{
xywh: '[0,0,300,300]',
title: new Text('Frame 1'),
},
service.surface.id
);
await wait();
const getFrameTitle = (frameId: string) => {
const frameTitleWidget = service.std.view.getWidget(
'affine-frame-title-widget',
frameId
) as AffineFrameTitleWidget | null;
return frameTitleWidget?.shadowRoot?.querySelector('affine-frame-title');
};
const frameTitle = getFrameTitle(frame);
const rect = frameTitle?.getBoundingClientRect();
expect(frameTitle).toBeTruthy();
expect(rect).toBeTruthy();
expect(rect!.width).toBeGreaterThan(0);
expect(rect!.height).toBeGreaterThan(0);
const [titleX, titleY] = service.viewport.toModelCoord(rect!.x, rect!.y);
expect(titleX).toBeCloseTo(0);
expect(titleY).toBeLessThan(0);
const nestedFrame = service.doc.addBlock(
'affine:frame',
{
xywh: '[20,20,200,200]',
title: new Text('Frame 2'),
},
service.surface.id
);
await wait();
const nestedTitle = getFrameTitle(nestedFrame);
expect(nestedTitle).toBeTruthy();
if (!nestedTitle) return;
const nestedTitleRect = nestedTitle.getBoundingClientRect()!;
const [nestedTitleX, nestedTitleY] = service.viewport.toModelCoord(
nestedTitleRect.x,
nestedTitleRect.y
);
expect(nestedTitleX).toBeGreaterThan(20);
expect(nestedTitleY).toBeGreaterThan(20);
});
test('frame should have externalXYWH after moving viewport to contains frame', async () => {
const frameId = service.doc.addBlock(
'affine:frame',
{
xywh: '[1800,1800,200,200]',
title: new Text('Frame 1'),
},
service.surface.id
);
await wait();
const frame = service.doc.getBlock(frameId);
expect(frame).toBeTruthy();
assertType<FrameBlockComponent>(frame);
service.viewport.setCenter(900, 900);
expect(frame?.model.externalXYWH).toBeDefined();
});
test('descendant of frame should not contain itself', async () => {
const frameIds = [1, 2, 3].map(i => {
return service.doc.addBlock(
'affine:frame',
{
xywh: '[0,0,300,300]',
title: new Text(`Frame ${i}`),
},
service.surface.id
);
});
await wait();
const frames = frameIds.map(
id => service.doc.getBlock(id)?.model as FrameBlockModel
);
frames.forEach(frame => {
expect(frame.descendantElements).toHaveLength(0);
});
frames[0].addChild(frames[1]);
frames[1].addChild(frames[2]);
frames[2].addChild(frames[0]);
await wait();
expect(frames[0].descendantElements).toHaveLength(2);
expect(frames[1].descendantElements).toHaveLength(1);
expect(frames[2].descendantElements).toHaveLength(0);
});
});