char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Refactor data-attrs constants, enum for bool strings (#27002)bdffafa5d

char/aliceai-t5-moe
5.1 KiB179 linesraw
1// Guards the newline contract of the ChatFormInputRich: browsers
2// restructure the flat DOM on Enter (`<div>` wrappers, `<br>` shapes) and
3// serialization must fold those back into `\n` so the emitted value never
4// diverges from what is on screen.
5
6import ChatFormInputRichHarness from './components/ChatFormInputRichHarness.svelte';
7import { tick } from 'svelte';
8import { describe, expect, it } from 'vitest';
9import { render } from 'vitest-browser-svelte';
10
11const SOURCE = 'see [docs](file:///a/b) here';
12
13function editableIn(container: HTMLElement): HTMLElement {
14	const el = container.querySelector('[role="textbox"]');
15
16	if (!(el instanceof HTMLElement)) throw new Error('ChatFormInputRich not rendered');
17
18	return el;
19}
20
21function fireInput(root: HTMLElement) {
22	root.dispatchEvent(new InputEvent('input', { bubbles: true }));
23}
24
25function setCaret(node: Node, offset: number) {
26	const range = document.createRange();
27
28	range.setStart(node, offset);
29	range.setEnd(node, offset);
30	const selection = window.getSelection();
31
32	if (!selection) throw new Error('no selection');
33
34	selection.removeAllRanges();
35	selection.addRange(range);
36}
37
38describe('ChatFormInputRich browser newline shapes', () => {
39	it('serializes a Chromium Enter <div> wrapper as a newline', async () => {
40		const screen = render(ChatFormInputRichHarness, { value: SOURCE });
41
42		await tick();
43
44		const root = editableIn(screen.container);
45		const div = document.createElement('div');
46
47		div.textContent = 'second line';
48		root.appendChild(div);
49		fireInput(root);
50		await tick();
51
52		expect(screen.component.getValue()).toBe(`${SOURCE}\nsecond line`);
53	});
54
55	it('serializes a Firefox full <div> wrap as lines, badge included', async () => {
56		const screen = render(ChatFormInputRichHarness, { value: SOURCE });
57
58		await tick();
59
60		const root = editableIn(screen.container);
61		const first = document.createElement('div');
62
63		while (root.firstChild) first.appendChild(root.firstChild);
64		const second = document.createElement('div');
65
66		second.textContent = 'second line';
67		root.appendChild(first);
68		root.appendChild(second);
69		fireInput(root);
70		await tick();
71
72		expect(screen.component.getValue()).toBe(`${SOURCE}\nsecond line`);
73	});
74
75	it('serializes a <br> as a newline', async () => {
76		const screen = render(ChatFormInputRichHarness, { value: 'here' });
77
78		await tick();
79
80		const root = editableIn(screen.container);
81
82		root.appendChild(document.createElement('br'));
83		root.appendChild(document.createTextNode('second line'));
84		fireInput(root);
85		await tick();
86
87		expect(screen.component.getValue()).toBe('here\nsecond line');
88	});
89
90	it('ignores a trailing <br> (browser caret placeholder)', async () => {
91		const screen = render(ChatFormInputRichHarness, { value: 'abc' });
92
93		await tick();
94
95		const root = editableIn(screen.container);
96
97		root.appendChild(document.createElement('br'));
98		fireInput(root);
99		await tick();
100
101		expect(screen.component.getValue()).toBe('abc');
102	});
103
104	it('serializes one newline per empty-line <div><br></div>', async () => {
105		const screen = render(ChatFormInputRichHarness, { value: 'abc' });
106
107		await tick();
108
109		const root = editableIn(screen.container);
110
111		for (let i = 0; i < 2; i++) {
112			const div = document.createElement('div');
113
114			div.appendChild(document.createElement('br'));
115			root.appendChild(div);
116		}
117		fireInput(root);
118		await tick();
119
120		expect(screen.component.getValue()).toBe('abc\n\n');
121	});
122
123	it('treats a <div><br></div>-only buffer as empty for the placeholder', async () => {
124		const screen = render(ChatFormInputRichHarness, { value: 'abc' });
125
126		await tick();
127
128		const root = editableIn(screen.container);
129		const div = document.createElement('div');
130
131		div.appendChild(document.createElement('br'));
132		root.replaceChildren(div);
133		fireInput(root);
134		await tick();
135
136		expect(screen.component.getValue()).toBe('');
137		expect(root.dataset.empty).toBe('true');
138	});
139
140	it('maps the caret across block boundaries in both directions', async () => {
141		const screen = render(ChatFormInputRichHarness, { value: 'abc\ndef' });
142
143		await tick();
144
145		// Rebuild into the Chromium block shape; the source is unchanged,
146		// so no re-render fires.
147		const root = editableIn(screen.container);
148		const div = document.createElement('div');
149
150		div.textContent = 'def';
151		root.replaceChildren(document.createTextNode('abc'), div);
152		fireInput(root);
153		await tick();
154		expect(screen.component.getValue()).toBe('abc\ndef');
155
156		const divText = div.firstChild;
157
158		if (!divText) throw new Error('div text missing');
159
160		setCaret(divText, 2);
161		expect(screen.component.getCaretOffset()).toBe(6);
162
163		screen.component.setCaretOffset(6);
164		const selection = window.getSelection();
165
166		expect(selection?.anchorNode).toBe(divText);
167		expect(selection?.anchorOffset).toBe(2);
168
169		// The boundary newline itself: offset 3 is the end of "abc", offset
170		// 4 the start of the "def" line.
171		screen.component.setCaretOffset(4);
172		expect(window.getSelection()?.anchorNode).toBe(divText);
173		expect(window.getSelection()?.anchorOffset).toBe(0);
174
175		screen.component.setCaretOffset(3);
176		expect(window.getSelection()?.anchorNode).toBe(root.firstChild);
177		expect(window.getSelection()?.anchorOffset).toBe(3);
178	});
179});