WIP fixing tests
This commit is contained in:
parent
781af3cc1e
commit
59436a7d39
|
|
@ -401,7 +401,13 @@ export class RibbitEditor extends Ribbit {
|
||||||
prefixSpan.className = rule.isList ? LIST_PREFIX_CLASS : DELIM_CLASS;
|
prefixSpan.className = rule.isList ? LIST_PREFIX_CLASS : DELIM_CLASS;
|
||||||
prefixSpan.textContent = line.slice(0, prefixLength);
|
prefixSpan.textContent = line.slice(0, prefixLength);
|
||||||
block.appendChild(prefixSpan);
|
block.appendChild(prefixSpan);
|
||||||
block.appendChild(this.#parseInline(line.slice(prefixLength)));
|
|
||||||
|
const content = line.slice(prefixLength);
|
||||||
|
if (content) {
|
||||||
|
block.appendChild(this.#parseInline(content));
|
||||||
|
} else {
|
||||||
|
block.appendChild(document.createTextNode('\u200B'));
|
||||||
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,27 +548,38 @@ export class RibbitEditor extends Ribbit {
|
||||||
*/
|
*/
|
||||||
#updateCurrentBlock(): void {
|
#updateCurrentBlock(): void {
|
||||||
const block = this.#findCurrentBlock();
|
const block = this.#findCurrentBlock();
|
||||||
if (!block) {
|
if (!block) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve the caret position across the rebuild
|
|
||||||
const caretOffset = this.#getCaretOffset(block);
|
const caretOffset = this.#getCaretOffset(block);
|
||||||
|
|
||||||
// Normalize that browsers insert in contentEditable.
|
|
||||||
// Without this, NBSP characters in textContent break pattern
|
|
||||||
// matching for block classifiers like "## " or "> ".
|
|
||||||
const lineText = block.textContent!.replace(/\u00A0/g, ' ');
|
const lineText = block.textContent!.replace(/\u00A0/g, ' ');
|
||||||
|
|
||||||
const newBlock = this.#buildBlock(lineText);
|
const newBlock = this.#buildBlock(lineText);
|
||||||
block.className = newBlock.className;
|
block.className = newBlock.className;
|
||||||
block.innerHTML = '';
|
block.innerHTML = '';
|
||||||
while (newBlock.firstChild) {
|
while (newBlock.firstChild) block.appendChild(newBlock.firstChild);
|
||||||
block.appendChild(newBlock.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#restoreCaret(block, caretOffset);
|
// Place caret after any prefix span, never inside it
|
||||||
this.#updateEditingContext();
|
const prefixSpan = block.firstElementChild;
|
||||||
|
const prefixLen = (prefixSpan?.classList.contains(DELIM_CLASS) ||
|
||||||
|
prefixSpan?.classList.contains(LIST_PREFIX_CLASS))
|
||||||
|
? prefixSpan.textContent!.length : 0;
|
||||||
|
|
||||||
|
if (caretOffset <= prefixLen && prefixSpan) {
|
||||||
|
|
||||||
|
const sel = window.getSelection()!;
|
||||||
|
const range = document.createRange();
|
||||||
|
const next = prefixSpan.nextSibling;
|
||||||
|
if (next && next.nodeType === 3) {
|
||||||
|
range.setStart(next as Text, 0);
|
||||||
|
} else {
|
||||||
|
range.setStartAfter(prefixSpan);
|
||||||
|
}
|
||||||
|
range.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
} else {
|
||||||
|
this.#restoreCaret(block, caretOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Keyboard handling ──────────────────────────────────────────────────────
|
// ── Keyboard handling ──────────────────────────────────────────────────────
|
||||||
|
|
@ -831,7 +848,8 @@ export class RibbitEditor extends Ribbit {
|
||||||
if (block.dataset.macro) {
|
if (block.dataset.macro) {
|
||||||
return block.dataset.source || '';
|
return block.dataset.source || '';
|
||||||
}
|
}
|
||||||
return block.textContent || '';
|
return block.textContent!.replace(/\u200B/g, '');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ const PORT = (() => {
|
||||||
const portArg = process.argv.find(arg => arg.startsWith('--port='));
|
const portArg = process.argv.find(arg => arg.startsWith('--port='));
|
||||||
return portArg ? parseInt(portArg.split('=')[1]) : 5023;
|
return portArg ? parseInt(portArg.split('=')[1]) : 5023;
|
||||||
})();
|
})();
|
||||||
const USE_DEV_SERVER = process.argv.includes('--port');
|
|
||||||
const DELAY = 20; // ms between keystrokes
|
const DELAY = 20; // ms between keystrokes
|
||||||
|
|
||||||
// ── State ─────────────────────────────────────────────────────────────────────
|
// ── State ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -37,6 +36,30 @@ const errors = [];
|
||||||
|
|
||||||
// ── Setup / teardown ──────────────────────────────────────────────────────────
|
// ── Setup / teardown ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function getCaretInfo() {
|
||||||
|
return page.evaluate(() => {
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (!sel || !sel.rangeCount) return 'no selection';
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
return {
|
||||||
|
container: range.startContainer.nodeType === 3
|
||||||
|
? `text:"${range.startContainer.textContent}"`
|
||||||
|
: `element:${range.startContainer.nodeName}.${range.startContainer.className}`,
|
||||||
|
offset: range.startOffset,
|
||||||
|
parentClass: range.startContainer.parentElement?.className,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isPortInUse(port) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const net = require('net');
|
||||||
|
const tester = net.createServer()
|
||||||
|
.once('error', () => resolve(true))
|
||||||
|
.once('listening', () => tester.close(() => resolve(false)))
|
||||||
|
.listen(port, '127.0.0.1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function serverStart() {
|
async function serverStart() {
|
||||||
var liveServer = require("live-server");
|
var liveServer = require("live-server");
|
||||||
|
|
@ -60,18 +83,18 @@ async function serverStart() {
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
|
|
||||||
if (!USE_DEV_SERVER) {
|
if (!await isPortInUse(PORT)) {
|
||||||
await serverStart();
|
await serverStart();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
}
|
}
|
||||||
browser = await chromium.launch({ headless: HEADLESS });
|
browser = await chromium.launch({ headless: HEADLESS, channel: 'chromium' });
|
||||||
page = await browser.newPage();
|
page = await browser.newPage();
|
||||||
await page.goto(`http://localhost:${PORT}`);
|
await page.goto(`http://localhost:${PORT}`);
|
||||||
await page.waitForFunction(() => window.__ribbitReady === true, { timeout: 10000 });
|
await page.waitForFunction(() => window.__ribbitReady === true, { timeout: 10000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
//if (browser) { await browser.close(); }
|
if (browser) { await browser.close(); }
|
||||||
//if (server) { await server.stop(); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Editor helpers ────────────────────────────────────────────────────────────
|
// ── Editor helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
@ -83,8 +106,10 @@ async function teardown() {
|
||||||
async function resetEditor() {
|
async function resetEditor() {
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
const editor = window.__ribbitEditor;
|
const editor = window.__ribbitEditor;
|
||||||
editor.wysiwyg();
|
editor.view();
|
||||||
|
editor.sourceMarkdown = '';
|
||||||
editor.element.innerHTML = '';
|
editor.element.innerHTML = '';
|
||||||
|
editor.wysiwyg();
|
||||||
});
|
});
|
||||||
await page.focus('#ribbit');
|
await page.focus('#ribbit');
|
||||||
await page.waitForTimeout(30);
|
await page.waitForTimeout(30);
|
||||||
|
|
@ -96,7 +121,11 @@ async function resetEditor() {
|
||||||
*/
|
*/
|
||||||
async function typeString(text) {
|
async function typeString(text) {
|
||||||
for (const character of text) {
|
for (const character of text) {
|
||||||
await page.keyboard.type(character);
|
if (character == ' ') {
|
||||||
|
await page.keyboard.press('Space');
|
||||||
|
} else {
|
||||||
|
await page.keyboard.insertText(character);
|
||||||
|
}
|
||||||
await page.waitForTimeout(DELAY);
|
await page.waitForTimeout(DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,14 +190,6 @@ async function runTests() {
|
||||||
|
|
||||||
console.log('Block classification:');
|
console.log('Block classification:');
|
||||||
|
|
||||||
await test('plain text becomes md-paragraph', async () => {
|
|
||||||
await resetEditor();
|
|
||||||
await typeString("hello\n\n");
|
|
||||||
const classes = await getBlockClasses();
|
|
||||||
assert(classes.some(c => c.includes('md-paragraph')), `Expected md-paragraph, got: ${classes}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
await test('# space becomes md-h1', async () => {
|
await test('# space becomes md-h1', async () => {
|
||||||
await resetEditor();
|
await resetEditor();
|
||||||
await typeString('#');
|
await typeString('#');
|
||||||
|
|
@ -184,6 +205,13 @@ async function runTests() {
|
||||||
assert(markdown.includes('# Title'), `Expected "# Title" in markdown: ${markdown}`);
|
assert(markdown.includes('# Title'), `Expected "# Title" in markdown: ${markdown}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await test('plain text becomes md-paragraph', async () => {
|
||||||
|
await resetEditor();
|
||||||
|
await typeString("hello\n\n");
|
||||||
|
const classes = await getBlockClasses();
|
||||||
|
assert(classes.some(c => c.includes('md-paragraph')), `Expected md-paragraph, got: ${classes}`);
|
||||||
|
});
|
||||||
|
|
||||||
await test('## space becomes md-h2', async () => {
|
await test('## space becomes md-h2', async () => {
|
||||||
await resetEditor();
|
await resetEditor();
|
||||||
await typeString('## ');
|
await typeString('## ');
|
||||||
|
|
@ -502,7 +530,9 @@ async function runTests() {
|
||||||
const markdown = await getMarkdown();
|
const markdown = await getMarkdown();
|
||||||
assert(markdown === 'first\n\nsecond', `Expected "first\\n\\nsecond", got: "${markdown}"`);
|
assert(markdown === 'first\n\nsecond', `Expected "first\\n\\nsecond", got: "${markdown}"`);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
//*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Main ──────────────────────────────────────────────────────────────────────
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -524,6 +554,10 @@ async function runTests() {
|
||||||
console.log(` ${message}`);
|
console.log(` ${message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!HEADLESS) {
|
||||||
|
console.log('\nPress Enter to close...');
|
||||||
|
await new Promise(resolve => process.stdin.once('data', resolve));
|
||||||
|
}
|
||||||
await teardown();
|
await teardown();
|
||||||
process.exit(failed > 0 ? 1 : 0);
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user