WIP fixing tests

This commit is contained in:
evilchili 2026-05-28 23:54:44 -07:00
parent 781af3cc1e
commit 59436a7d39
2 changed files with 84 additions and 32 deletions

View File

@ -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);
}
// Place caret after any prefix span, never inside it
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); this.#restoreCaret(block, caretOffset);
this.#updateEditingContext(); }
} }
// ── 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, '');
} }
} }

View File

@ -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);
} }