You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
3.4 KiB
121 lines
3.4 KiB
// @vitest-environment jsdom
|
|
|
|
import { flushSync, mount, settled, unmount } from 'svelte';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { createActiveDom } from '$adom';
|
|
import type { Morfo } from '$uix/morfo';
|
|
import { Soma } from '$soma/core/soma.svelte';
|
|
import { createSomaRuntime, type SomaRuntimeSources } from '$soma/runtime.svelte';
|
|
import { FieldProvider } from '../field';
|
|
|
|
import WordsContentHarness from './test/words-content-harness.svelte';
|
|
|
|
function installSomaHarness() {
|
|
const dom = createActiveDom();
|
|
const logger = {
|
|
trace: vi.fn(),
|
|
debug: vi.fn(),
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
fatal: vi.fn()
|
|
};
|
|
const soma = {
|
|
dom,
|
|
logger,
|
|
langs: {
|
|
ts: vi.fn((key: string) => key)
|
|
},
|
|
runtime: (morfo: Morfo, sources: Omit<SomaRuntimeSources, 'dom' | 'eventEngine'>) =>
|
|
createSomaRuntime(morfo, {
|
|
dom,
|
|
translate: (key) => key,
|
|
...sources
|
|
})
|
|
} as unknown as Soma;
|
|
|
|
vi.spyOn(Soma, 'require').mockReturnValue(soma);
|
|
vi.spyOn(FieldProvider, 'get').mockReturnValue(undefined);
|
|
|
|
return { dom };
|
|
}
|
|
|
|
function textLeaf(root: HTMLElement): Text {
|
|
const leaf = root.querySelector('[data-words-node="text"]')?.firstChild;
|
|
if (!(leaf instanceof Text)) throw new Error('Words text leaf not rendered');
|
|
return leaf;
|
|
}
|
|
|
|
function mountRenderedSelection(root: HTMLElement, offset: number) {
|
|
const leaf = textLeaf(root);
|
|
const range = document.createRange();
|
|
range.setStart(leaf, offset);
|
|
range.collapse(true);
|
|
const selection = document.getSelection();
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range);
|
|
}
|
|
|
|
describe('Words.Content component', () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
it('manages rendered html and restores DOM selection after an edit', async () => {
|
|
const { dom } = installSomaHarness();
|
|
const target = document.createElement('div');
|
|
document.body.append(target);
|
|
|
|
const component = mount(WordsContentHarness, { target });
|
|
await settled();
|
|
|
|
const content = target.querySelector('[data-testid="words-content"]') as HTMLElement | null;
|
|
expect(content).toBeTruthy();
|
|
if (!content) throw new Error('Words content did not mount');
|
|
expect(textLeaf(content).textContent).toBe('Hello');
|
|
|
|
mountRenderedSelection(content, 2);
|
|
const beforeInput = new InputEvent('beforeinput', {
|
|
inputType: 'insertText',
|
|
data: 'X',
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
|
|
content.dispatchEvent(beforeInput);
|
|
flushSync();
|
|
await settled();
|
|
|
|
expect(beforeInput.defaultPrevented).toBe(true);
|
|
expect(textLeaf(content).textContent).toBe('HeXllo');
|
|
const selection = document.getSelection();
|
|
expect(selection?.anchorNode).toBe(textLeaf(content));
|
|
expect(selection?.anchorOffset).toBe(3);
|
|
|
|
await unmount(component);
|
|
dom.dispose();
|
|
});
|
|
|
|
it('still manages html when Svelte provides an empty children snippet', async () => {
|
|
const { dom } = installSomaHarness();
|
|
const target = document.createElement('div');
|
|
document.body.append(target);
|
|
|
|
const component = mount(WordsContentHarness, {
|
|
target,
|
|
props: { emptyChildren: true }
|
|
});
|
|
await settled();
|
|
|
|
const content = target.querySelector('[data-testid="words-content"]') as HTMLElement | null;
|
|
expect(content).toBeTruthy();
|
|
if (!content) throw new Error('Words content did not mount');
|
|
expect(textLeaf(content).textContent).toBe('Hello');
|
|
|
|
await unmount(component);
|
|
dom.dispose();
|
|
});
|
|
});
|