Files
Chrome-RTL/test/content-engine.js
T
arsam 240937ff4d Import tests: test/content-engine
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:07:30 +03:30

193 lines
7.7 KiB
JavaScript

"use strict";
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const { JSDOM } = require("jsdom");
const source = fs.readFileSync(path.join(__dirname, "../src/content.js"), "utf8");
function createExtensionDom(body, overrides = {}) {
const settings = {
enabled: true,
fixDirection: true,
useFont: true,
fixInputs: true,
disabledSites: [],
...overrides
};
let onChanged;
const dom = new JSDOM(`<!doctype html><html><head></head><body>${body}</body></html>`, {
url: "https://chat.example/",
runScripts: "dangerously",
pretendToBeVisual: true
});
dom.window.requestIdleCallback = (callback) => callback({ didTimeout: false, timeRemaining: () => 50 });
dom.window.chrome = {
runtime: {
getURL: (file) => `chrome-extension://test/${file}`,
lastError: null
},
storage: {
local: { get: (_defaults, callback) => callback(settings) },
onChanged: { addListener: (callback) => { onChanged = callback; } }
}
};
const script = dom.window.document.createElement("script");
script.textContent = source;
dom.window.document.head.appendChild(script);
return {
dom,
changeSettings(patch) {
for (const [key, value] of Object.entries(patch)) settings[key] = value;
const changes = Object.fromEntries(Object.entries(patch).map(([key, value]) => [key, { newValue: value }]));
onChanged(changes, "local");
}
};
}
function element(body, id = "target", overrides) {
const fixture = createExtensionDom(body, overrides);
return { fixture, target: fixture.dom.window.document.getElementById(id) };
}
const tests = [];
function test(name, fn) { tests.push({ name, fn }); }
function nextTurn() { return new Promise((resolve) => setTimeout(resolve, 0)); }
test("real Persian text is corrected to RTL", () => {
const { target } = element('<p id="target">این یک متن فارسی است</p>');
assert.equal(target.getAttribute("dir"), "rtl");
});
test("Arabic-only text is not treated as Persian", () => {
const { target } = element('<p id="target">هذا نص عربي للاختبار</p>');
assert.equal(target.hasAttribute("dir"), false);
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
test("English-leading Persian text uses auto direction", () => {
const { target } = element('<p id="target">The answer is متن فارسی برای آزمایش.</p>');
assert.equal(target.getAttribute("dir"), "auto");
assert.equal(target.hasAttribute("data-rtlx-rtl"), false);
});
test("Latin digits inside Persian text remain byte-identical", () => {
const text = "نسخه 1234567890 برای آزمایش";
const { target } = element(`<p id="target">${text}</p>`);
assert.equal(target.textContent, text);
});
test("a sensible explicit RTL direction is preserved without ownership markers", () => {
const { target } = element('<p id="target" dir="rtl">این متن از قبل راست‌چین است</p>');
assert.equal(target.getAttribute("dir"), "rtl");
assert.equal(target.hasAttribute("data-rtlx-setdir"), false);
});
test("a sensible explicit auto direction is preserved without ownership markers", () => {
const { target } = element('<p id="target" dir="auto">این متن جهت خودکار دارد</p>');
assert.equal(target.getAttribute("dir"), "auto");
assert.equal(target.hasAttribute("data-rtlx-setdir"), false);
});
test("a clearly wrong explicit LTR direction is corrected reversibly", () => {
const { target } = element('<p id="target" dir="ltr">این متن فارسی راست‌به‌چپ است</p>');
assert.equal(target.getAttribute("dir"), "rtl");
assert.equal(target.getAttribute("data-rtlx-dir"), "ltr");
});
test("an inline author font is never replaced", () => {
const { target } = element('<p id="target" style="font-family: Arial">این متن فونت دارد</p>');
assert.equal(target.style.fontFamily, "Arial");
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
test("an inherited author font is never replaced", () => {
const { target } = element('<div style="font-family: sans-serif"><p id="target">این متن فونت ارث‌بری دارد</p></div>');
assert.equal(target.style.fontFamily, "");
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
test("a stylesheet font on the Persian element is never replaced", () => {
const { target } = element('<style>.authored { font-family: system-ui; }</style><p id="target" class="authored">این متن فونت دارد</p>');
assert.equal(target.style.fontFamily, "");
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
test("an inherited stylesheet font is never replaced", () => {
const { target } = element('<style>.chat { font-family: Tahoma; }</style><article class="chat"><p id="target">این پاسخ هوش مصنوعی است</p></article>');
assert.equal(target.style.fontFamily, "");
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
test("unstyled Persian text receives Vazirmatn", () => {
const { target } = element('<p id="target">این متن بدون فونت است</p>');
assert.match(target.style.fontFamily, /Vazirmatn/i);
});
test("streamed Persian text is corrected and Latin digits are untouched", async () => {
const { fixture, target } = element('<p id="target">Waiting</p>');
target.textContent = "پاسخ زنده 1234567890";
await nextTurn();
assert.equal(target.getAttribute("dir"), "rtl");
assert.equal(target.textContent, "پاسخ زنده 1234567890");
fixture.dom.window.close();
});
test("a streamed block that becomes English is restored", async () => {
const { fixture, target } = element('<p id="target">پاسخ فارسی</p>');
assert.equal(target.getAttribute("dir"), "rtl");
target.textContent = "The final answer is English.";
await nextTurn();
assert.equal(target.hasAttribute("dir"), false);
assert.equal(target.hasAttribute("data-rtlx-rtl"), false);
assert.equal(target.style.fontFamily, "");
fixture.dom.window.close();
});
test("disabling the extension restores owned direction and font", () => {
const { fixture, target } = element('<p id="target">پاسخ فارسی</p>');
fixture.changeSettings({ enabled: false });
assert.equal(target.hasAttribute("dir"), false);
assert.equal(target.style.fontFamily, "");
});
test("unstyled inputs receive Vazirmatn only after Persian is present", () => {
const { fixture, target } = element('<input id="target" type="text" />');
assert.equal(target.style.fontFamily, "");
target.value = "متن فارسی";
target.dispatchEvent(new fixture.dom.window.Event("input", { bubbles: true }));
assert.match(target.style.fontFamily, /Vazirmatn/i);
});
test("author-styled Persian inputs preserve their font", () => {
const { target } = element('<input id="target" type="text" style="font-family: Arial" value="متن فارسی" />');
assert.equal(target.style.fontFamily, "Arial");
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
test("input listeners do not reapply styling while the extension is disabled", () => {
const { fixture, target } = element('<input id="target" type="text" />');
fixture.changeSettings({ enabled: false });
target.value = "متن فارسی";
target.dispatchEvent(new fixture.dom.window.Event("input", { bubbles: true }));
assert.equal(target.style.fontFamily, "");
assert.equal(target.hasAttribute("data-rtlx-font"), false);
});
(async () => {
let passed = 0;
for (const { name, fn } of tests) {
try {
await fn();
passed += 1;
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
console.error(error.message);
}
}
console.log(`\n${passed}/${tests.length} passed`);
process.exitCode = passed === tests.length ? 0 : 1;
})();