wrapper as a newline', async () => {
const screen = render(ChatFormInputRichHarness, { value: SOURCE });
await tick();
const root = editableIn(screen.container);
const div = document.createElement('div');
div.textContent = 'second line';
root.appendChild(div);
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe(`${SOURCE}\nsecond line`);
});
it('serializes a Firefox full
wrap as lines, badge included', async () => {
const screen = render(ChatFormInputRichHarness, { value: SOURCE });
await tick();
const root = editableIn(screen.container);
const first = document.createElement('div');
while (root.firstChild) first.appendChild(root.firstChild);
const second = document.createElement('div');
second.textContent = 'second line';
root.appendChild(first);
root.appendChild(second);
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe(`${SOURCE}\nsecond line`);
});
it('serializes a
as a newline', async () => {
const screen = render(ChatFormInputRichHarness, { value: 'here' });
await tick();
const root = editableIn(screen.container);
root.appendChild(document.createElement('br'));
root.appendChild(document.createTextNode('second line'));
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe('here\nsecond line');
});
it('ignores a trailing
(browser caret placeholder)', async () => {
const screen = render(ChatFormInputRichHarness, { value: 'abc' });
await tick();
const root = editableIn(screen.container);
root.appendChild(document.createElement('br'));
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe('abc');
});
it('serializes one newline per empty-line
', async () => {
const screen = render(ChatFormInputRichHarness, { value: 'abc' });
await tick();
const root = editableIn(screen.container);
for (let i = 0; i < 2; i++) {
const div = document.createElement('div');
div.appendChild(document.createElement('br'));
root.appendChild(div);
}
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe('abc\n\n');
});
it('treats a
-only buffer as empty for the placeholder', async () => {
const screen = render(ChatFormInputRichHarness, { value: 'abc' });
await tick();
const root = editableIn(screen.container);
const div = document.createElement('div');
div.appendChild(document.createElement('br'));
root.replaceChildren(div);
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe('');
expect(root.dataset.empty).toBe('true');
});
it('maps the caret across block boundaries in both directions', async () => {
const screen = render(ChatFormInputRichHarness, { value: 'abc\ndef' });
await tick();
// Rebuild into the Chromium block shape; the source is unchanged,
// so no re-render fires.
const root = editableIn(screen.container);
const div = document.createElement('div');
div.textContent = 'def';
root.replaceChildren(document.createTextNode('abc'), div);
fireInput(root);
await tick();
expect(screen.component.getValue()).toBe('abc\ndef');
const divText = div.firstChild;
if (!divText) throw new Error('div text missing');
setCaret(divText, 2);
expect(screen.component.getCaretOffset()).toBe(6);
screen.component.setCaretOffset(6);
const selection = window.getSelection();
expect(selection?.anchorNode).toBe(divText);
expect(selection?.anchorOffset).toBe(2);
// The boundary newline itself: offset 3 is the end of "abc", offset
// 4 the start of the "def" line.
screen.component.setCaretOffset(4);
expect(window.getSelection()?.anchorNode).toBe(divText);
expect(window.getSelection()?.anchorOffset).toBe(0);
screen.component.setCaretOffset(3);
expect(window.getSelection()?.anchorNode).toBe(root.firstChild);
expect(window.getSelection()?.anchorOffset).toBe(3);
});
});