feat(workspace): polish outliner slash commands, typography and actions overlay
This commit is contained in:
1 parent
ad4829796c
commit
fd72500d17
3 files changed
+559
-196
No files matched your search
@@ -129,9 +129,9 @@ describe('slash command menu and outliner fixes', () => {
|
||||
fireEvent.change(ta, { target: { value: 'https://a.de/b/' } });
|
||||
expect(queryByText('Befehl auswählen')).toBeNull();
|
||||
|
||||
fireEvent.change(ta, { target: { value: 'Text /co' } });
|
||||
fireEvent.change(ta, { target: { value: 'Text /ca' } });
|
||||
expect(queryByText('Befehl auswählen')).toBeTruthy();
|
||||
expect(queryByText('/code')).toBeTruthy();
|
||||
expect(queryByText('/callout')).toBeTruthy();
|
||||
|
||||
// Removing the slash closes the menu
|
||||
fireEvent.change(ta, { target: { value: 'Text ' } });
|
||||
@@ -375,3 +375,106 @@ describe('formatting shortcuts (Teil C)', () => {
|
||||
expect(readerTa.value).toBe('text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('outliner polish (Teil A)', () => {
|
||||
it('opens slash menu at cursor position in the middle of text and inserts link at cursor', () => {
|
||||
const updates: DocumentBlock[] = [];
|
||||
const block = { id: 'b1', type: 'bullet', content: 'Vorher Nachher', level: 0 } as DocumentBlock;
|
||||
const { container, queryByText, getByText, getByPlaceholderText, getByRole } = renderBlock(block, {
|
||||
onUpdateBlock: (u: DocumentBlock) => updates.push(u),
|
||||
});
|
||||
const ta = container.querySelector('textarea') as HTMLTextAreaElement;
|
||||
ta.focus();
|
||||
|
||||
// Type "/link" in the middle of the text (selectionStart at 12, right after "/link")
|
||||
fireEvent.change(ta, { target: { value: 'Vorher /link Nachher', selectionStart: 12, selectionEnd: 12 } });
|
||||
|
||||
// Menu should be open with /link, but without /divider or /h1
|
||||
expect(queryByText('Befehl auswählen')).toBeTruthy();
|
||||
expect(queryByText('/link')).toBeTruthy();
|
||||
expect(queryByText('/divider')).toBeNull();
|
||||
expect(queryByText('/h1')).toBeNull();
|
||||
|
||||
// Select /link by pressing Enter
|
||||
fireEvent.keyDown(ta, { key: 'Enter' });
|
||||
|
||||
// Link modal opens
|
||||
expect(queryByText('Web-Link')).toBeTruthy();
|
||||
fireEvent.click(getByText('Web-Link'));
|
||||
fireEvent.change(getByPlaceholderText('z. B. MindSift Dokumentation'), { target: { value: 'Doku' } });
|
||||
fireEvent.change(getByPlaceholderText('https://example.com'), { target: { value: 'https://example.com/test' } });
|
||||
fireEvent.click(getByRole('button', { name: 'Link einfügen' }));
|
||||
|
||||
// Result has link inserted in the middle
|
||||
expect(updates[updates.length - 1]?.content).toBe('Vorher [Doku](https://example.com/test) Nachher');
|
||||
});
|
||||
|
||||
it('does not offer /divider at end of text, but offers it in empty block', () => {
|
||||
const block = { id: 'b1', type: 'bullet', content: 'Ein Text', level: 0 } as DocumentBlock;
|
||||
const { container, queryByText } = renderBlock(block);
|
||||
const ta = container.querySelector('textarea') as HTMLTextAreaElement;
|
||||
ta.focus();
|
||||
|
||||
// At end of non-empty text
|
||||
fireEvent.change(ta, { target: { value: 'Ein Text /div' } });
|
||||
|
||||
expect(queryByText('Befehl auswählen')).toBeTruthy();
|
||||
expect(queryByText('/divider')).toBeNull();
|
||||
|
||||
// In an empty block
|
||||
fireEvent.change(ta, { target: { value: '/div' } });
|
||||
|
||||
expect(queryByText('Befehl auswählen')).toBeTruthy();
|
||||
expect(queryByText('/divider')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('converts H1 block back to bullet via /text slash command', () => {
|
||||
const updates: DocumentBlock[] = [];
|
||||
const block = { id: 'b1', type: 'h1', content: 'Meine Überschrift', level: 0 } as DocumentBlock;
|
||||
const { container, queryByText } = renderBlock(block, {
|
||||
onUpdateBlock: (u: DocumentBlock) => updates.push(u),
|
||||
});
|
||||
const ta = container.querySelector('textarea') as HTMLTextAreaElement;
|
||||
ta.focus();
|
||||
|
||||
fireEvent.change(ta, { target: { value: 'Meine Überschrift /text' } });
|
||||
|
||||
expect(queryByText('Befehl auswählen')).toBeTruthy();
|
||||
expect(queryByText('/text')).toBeTruthy();
|
||||
|
||||
fireEvent.keyDown(ta, { key: 'Enter' });
|
||||
|
||||
const last = updates[updates.length - 1];
|
||||
expect(last?.type).toBe('bullet');
|
||||
expect(last?.content).toBe('Meine Überschrift');
|
||||
});
|
||||
|
||||
it('closes slash menu on mousedown outside', () => {
|
||||
const block = { id: 'b1', type: 'bullet', content: '', level: 0 } as DocumentBlock;
|
||||
const { container, queryByText } = renderBlock(block);
|
||||
const ta = container.querySelector('textarea') as HTMLTextAreaElement;
|
||||
ta.focus();
|
||||
|
||||
fireEvent.change(ta, { target: { value: '/' } });
|
||||
expect(queryByText('Befehl auswählen')).toBeTruthy();
|
||||
|
||||
// Click outside on document body
|
||||
fireEvent.mouseDown(document.body);
|
||||
expect(queryByText('Befehl auswählen')).toBeNull();
|
||||
});
|
||||
|
||||
it('converts focused divider to empty bullet on Backspace', () => {
|
||||
const updates: DocumentBlock[] = [];
|
||||
const block = { id: 'd1', type: 'divider', content: '', level: 0 } as DocumentBlock;
|
||||
const { container } = renderBlock(block, {
|
||||
onUpdateBlock: (u: DocumentBlock) => updates.push(u),
|
||||
});
|
||||
const ta = container.querySelector('textarea') as HTMLTextAreaElement;
|
||||
ta.focus();
|
||||
|
||||
fireEvent.keyDown(ta, { key: 'Backspace' });
|
||||
const last = updates[updates.length - 1];
|
||||
expect(last?.type).toBe('bullet');
|
||||
expect(last?.content).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user