typopo
Version:
Fix frequent microtypography errors in multiple languages. Write neat texts without bothering about typography rules. Typopo works for English, German, Slovak, Czech and Rusyn language.
377 lines (351 loc) • 9.75 kB
JavaScript
import { describe, it, expect } from "vitest";
import { excludeExceptions, placeExceptions } from "../../src/modules/words/exceptions.js";
const emails = [
"john.doe@example.com",
"jane_doe123@example.co.uk",
"user.name+tag+sorting@example.com",
"customer_service@sub.example.com",
"no-reply@company.travel",
"support@helpdesk.example.org",
"admin@mailserver1.example.museum",
"contact@company.example",
"someone@host.com",
"devs+info@example.com",
"marketing@business.biz",
"sales@company.jobs",
"feedback@website.co.in",
"info@example.aero",
"newsletter@media.company",
"user@subdomain.example.edu",
"hr@recruitment.example",
"legal@lawfirm.example",
"inquiry@inbox.example.us",
"webmaster@company.com",
"service@example.co.in",
"owner@domain.com",
"registrar@domain.biz",
"client@example.tel",
"admin@127.0.0.1",
"postmaster@example.net",
"alerts@sub.example.info",
"user1@example.com",
"firstname.lastname@example.com",
"anonymous@company.qa",
"firstname+lastname@domain.name",
"bot123@host.mobi",
"events@community.travel",
"staff@support.example",
"qa@example.test",
"notifications@example.org",
"abuse@provider.com",
"info@charity.co",
"order@example.shop",
"management@corporate.us",
"user-service@domain.pro",
"admin@domain.tel",
"system@example.global",
"user.name@organization.ai",
"developer@example.code",
"dev@example.dev",
"owner@platform.biz",
"contact@api.net",
"sales@website.co",
"test.account@domain.com",
"billing@company.travel",
];
const urls = [
"http://example.com",
"https://example.com",
"http://www.example.com",
"https://subdomain.example.co.uk",
"https://example.com:8080/path/to/resource",
"https://example.com/#fragment",
"https://192.168.1.1",
"https://www.example.com/path/to/resource?query=string#fragment",
"https://example.com/long/path/with/many/segments",
"http://example.co.in",
"http://example.travel",
"http://example.travel:80",
"https://127.0.0.1",
"http://255.255.255.255",
"https://example.museum",
"http://sub.domain.example.com",
"https://shop.example.com",
"http://example.org/resource",
"https://example.biz",
"http://example.jobs",
"https://www.example.edu",
"http://subdomain.example.mobi",
"https://example.info/resource?param=value",
"http://123.45.67.89:8080",
"http://example.com/~user",
"http://example.com:3000/path/to/resource",
"http://sub.example.com/resource?query=string",
"https://shop.example.travel",
"https://media.example.co.uk",
"https://intranet.example.org",
"http://api.example.net/v1/resources",
"https://subnet.example.om",
"http://cdn.example.aero",
"http://example.tel",
"https://example.tel",
"http://example.qa/resource",
"https://example.xyz/path/to/file",
"http://123.123.123.123:9999",
"https://example.com/path/with/many_segments",
"https://store.example.dev",
"http://example.dev/path/to/somewhere",
"https://example.com:1234/complex/path?query=value&another=thing",
"http://example.com:9090/resource",
"http://test.example.net/resource/1",
"https://www.example.com/resource/1?query=example",
"https://blog.example.com/path/to/article",
"http://example.com/resource_with_underscores",
"http://longdomainname.example.info",
"https://123.123.123.123",
"http://www.example.net/resource/endpoint",
"https://example.tv",
"http://media.example.net",
"https://example.work",
"http://example.one",
"https://cloud.example.ai",
"https://example.company",
"https://example.store",
"http://shop.example.us",
"https://sub.example.uk",
"http://api.example.biz",
"https://cdn.example.cloud",
"https://support.example.com",
"http://test.example.om/path",
"https://mail.example.co",
"https://www.example.travel/resource?query=string",
"http://web.example.dev/file",
"https://store.example.shop",
"http://media.example.agency",
"https://sub.example.team",
"http://example.guide",
"http://example.com/with/slashes",
"http://example.com/with/slashes/",
"http://example.com?query=string",
"http://example.com/path/file.jpg",
"http://www.example.org/file.zip",
"https://sub.example.com/resource.php",
"https://example.com?param1=value1¶m2=value2",
// tbd later
// "http://example.blog",
// "http://localhost:8000/path/to/resource",
// "https://localhost:9000/path",
// "https://example:9000/resource?param=value",
// "http://localhost:8080",
// "http://[2001:db8::ff00:42:8329]",
// "https://localhost:3000",
// "http://localhost",
];
const filenames = [
"analysis.py",
"analysis.r",
"app.js",
"archive.tar.gz",
"archive.tar",
"archive.zip",
"avatar.png",
"backup.rar",
"book.pdf",
"bootstrap.css",
"calendar.ics",
"chapter.odt",
"chart.xls",
"code.cpp",
"code.java",
"code.kt",
"code.py",
"code.rs",
"code.scala",
"codebase.ts",
"codefile.cpp",
"config.xml",
"config.yaml",
"configuration.yaml",
"contract.docx",
"data.cpp",
"data.csv",
"data.json",
"data.sql",
"data.yml",
"database.sql",
"database.yml",
"dataset.csv",
"design.ai",
"design.psd",
"diagram.xml",
"diary.txt",
"dockerfile.yml",
"document.odt",
"example.gif",
"experiment.py",
"file.asm",
"file.doc",
"function.cs",
"function.kt",
"guide.docx",
"guide.pdf",
"home.html",
"homepage.html",
"image.bmp",
"image.gif",
"image.jpg",
"index.html",
"index.php",
"invoice.pdf",
"lambda.js",
"lambda.py",
"layout.css",
"log.txt",
"logfile.log",
"logo.svg",
"manual.doc",
"markdown.md",
"maths.xls",
"module.swift",
"notes.txt",
"notes.yaml",
"package.json",
"package.xml",
"photo.jpeg",
"photo.tiff",
"picture.jpeg",
"presentation.key",
"presentation.odp",
"presentation.pdf",
"presentation.ppt",
"presentation.pptx",
"process.sh",
"program.c",
"program.py",
"program.vbs",
"project.asm",
"project.swift",
"python.py",
"readme.md",
"report.md",
"report.odp",
"report.pdf",
"research.odt",
"results.txt",
"resume.doc",
"screenshot.png",
"script.js",
"script.pl",
"server.go",
"setup.exe",
"shell.sh",
"solution.java",
"spreadsheet.ods",
"spreadsheet.xlsx",
"style.css",
"style.less",
"style.scss",
"stylesheet.css",
"task.go",
"test.asm",
"test.sh",
"test.swift",
"testcase.rb",
"textfile.txt",
"thesis.pdf",
"thesis.tex",
"tutorial.py",
"url_to_image_5.jpg",
"url-to-image-5.jpg",
"url%to%image%5.jpg",
"video.mp4",
"webapp.ts",
"webapp.zip",
"website.html",
"website.php",
"windows.bat",
];
function countMatches(text, string) {
const regex = new RegExp(string, "g");
const matches = text.match(regex);
return matches ? matches.length : 0;
}
function prepareTestItems(testCase, testString = "just the string") {
// Add uppercase to testCase items
const modifiedTestCase = testCase.concat(testCase.map((item) => item.toUpperCase()));
// Expand testCase with duplicate items
const testItemsArray = [...modifiedTestCase];
testItemsArray.splice(Math.floor(testItemsArray.length / 2), 0, modifiedTestCase[0]);
testItemsArray.push(modifiedTestCase[0]);
// Expand testCase with items that shouldn’t be replaced
testItemsArray.unshift(testString);
testItemsArray.splice(Math.floor(testItemsArray.length / 2), 0, testString);
testItemsArray.push(testString);
const testItemsString = testItemsArray.join(" ");
return {
modifiedTestCase,
testItemsString,
testString,
};
}
function testExcludeExceptions(testCase, label) {
// Arrange
const { modifiedTestCase, testItemsString, testString } = prepareTestItems(testCase);
// Act
const { processedText } = excludeExceptions(testItemsString);
// Assert
it(`shouldn’t exclude test string “${testString}”`, () => {
expect(countMatches(processedText, testString)).toBe(3);
});
modifiedTestCase.forEach((item) => {
it(`should exclude all ${label}s `, () => {
expect(
processedText.includes(item),
false,
`${item} should be excluded from the processed text.`
);
});
});
}
describe("Exclude Exceptions: Emails", () => {
testExcludeExceptions(emails, "email");
});
describe("Exclude Exceptions: URLs", () => {
testExcludeExceptions(urls, "URL");
});
describe("Exclude Exceptions: filenames", () => {
testExcludeExceptions(filenames, "filename");
});
describe("Exclude Exceptions: Emails+URLs+filenames", () => {
const allExceptions = emails.concat(urls, filenames);
testExcludeExceptions(allExceptions, "Emails+URLs+filenames");
});
function testPlaceExceptions(testCase, label) {
// Arrange
const { testItemsString } = prepareTestItems(testCase);
// Act
const { processedText, exceptions } = excludeExceptions(testItemsString);
const replacedText = placeExceptions(processedText, exceptions);
// Assert
it(`original text and text after replacement should be equal for ${label}`, () => {
expect(replacedText).toBe(testItemsString);
});
}
describe("Place Exceptions: Emails", () => {
testPlaceExceptions(emails, "email");
});
describe("Place Exceptions: URLs", () => {
testPlaceExceptions(urls, "URL");
});
describe("Place Exceptions: filenames", () => {
testPlaceExceptions(filenames, "filename");
});
describe("Place Exceptions: Emails+URLs+filenames", () => {
const allExceptions = emails.concat(urls, filenames);
testPlaceExceptions(allExceptions, "Emails+URLs+filenames");
});
export const exceptionsSet = {
...Object.fromEntries(emails.map((item) => [item, item])),
...Object.fromEntries(urls.map((item) => [item, item])),
...Object.fromEntries(filenames.map((item) => [item, item])),
};