diff --git a/common/package-lock.json b/common/package-lock.json index ee15bf5..b90e4f5 100644 --- a/common/package-lock.json +++ b/common/package-lock.json @@ -19,11 +19,9 @@ }, "devDependencies": { "@types/chai": "^5.0.0", - "@types/chai-spies": "^1.0.6", "@types/mocha": "^10.0.8", "chai": "^5.1.1", "chai-as-promised": "^8.0.0", - "chai-spies": "^1.1.0", "esm": "^3.2.25", "mocha": "^10.7.3" } @@ -2201,16 +2199,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/chai-spies": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/chai-spies/-/chai-spies-1.0.6.tgz", - "integrity": "sha512-xkk4HmhBB9OQeTAifa9MJ+6R5/Rq9+ungDe4JidZD+vqZVeiWZwc2i7/pd1ZKjyGlSBIQePoWdyUyFUGT0rv5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2526,19 +2514,6 @@ "chai": ">= 2.1.2 < 6" } }, - "node_modules/chai-spies": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.1.0.tgz", - "integrity": "sha512-ikaUhQvQWchRYj2K54itFp3nrcxaFRpSDQxDlRzSn9aWgu9Pi7lD8yFxTso4WnQ39+WZ69oB/qOvqp+isJIIWA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - }, - "peerDependencies": { - "chai": "*" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", diff --git a/common/package.json b/common/package.json index 6940d87..9ef203c 100644 --- a/common/package.json +++ b/common/package.json @@ -24,11 +24,9 @@ }, "devDependencies": { "@types/chai": "^5.0.0", - "@types/chai-spies": "^1.0.6", "@types/mocha": "^10.0.8", "chai": "^5.1.1", "chai-as-promised": "^8.0.0", - "chai-spies": "^1.1.0", "esm": "^3.2.25", "mocha": "^10.7.3" } diff --git a/common/src/events.mjs b/common/src/events.mjs index 702fc7d..dbdd00f 100644 --- a/common/src/events.mjs +++ b/common/src/events.mjs @@ -79,8 +79,7 @@ export class BaseEventEmitter { if(eventType.includes(" ")) { // Unlisten to several different events with the same listener. let found = false for(const type of eventType.split(" ")) - found ||= this.off(type, eventListener) - return found + found ||= this.off(eventType, eventListener) } else { if(!this.constructor.emits.includes(eventType)) { const className = this.constructor.name diff --git a/common/src/module/latex.mjs b/common/src/module/latex.mjs index 4ff81cb..8ccbeb5 100644 --- a/common/src/module/latex.mjs +++ b/common/src/module/latex.mjs @@ -137,9 +137,9 @@ class LatexAPI extends Module { */ parif(elem, contents) { elem = elem.toString() - if(elem[0] !== "(" && elem.at(-1) !== ")" && contents.some(x => elem.indexOf(x) > 0)) + if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0)) return this.par(elem) - if(elem[0] === "(" && elem.at(-1) === ")") + if(elem[0] === "(" && elem[elem.length - 1] === ")") return elem.removeEnclosure() return elem } diff --git a/common/src/utils.mjs b/common/src/utils.mjs index 0e451f4..094147d 100644 --- a/common/src/utils.mjs +++ b/common/src/utils.mjs @@ -21,16 +21,14 @@ * Replaces latin characters with their uppercase versions. * @return {string} */ -String.prototype.toLatinUppercase = function() { +String.prototype.toLatinUppercase = String.prototype.toLatinUppercase || function() { return this.replace(/[a-z]/g, function(match) { return match.toUpperCase() }) } /** - * Removes the first and last character of a string - * Used to remove enclosing characters like quotes, parentheses, brackets... - * @note Does NOT check for their existence ahead of time. + * Removes the 'enclosers' of a string (e.g. quotes, parentheses, brackets...) * @return {string} */ String.prototype.removeEnclosure = function() { @@ -45,126 +43,126 @@ String.prototype.removeEnclosure = function() { * @return {number} */ Number.prototype.toDecimalPrecision = function(decimalPlaces = 0) { - const p = Math.pow(10, decimalPlaces) - const n = (this * p) * (1 + Number.EPSILON) - return Math.round(n) / p + const p = Math.pow(10, decimalPlaces); + const n = (this * p) * (1 + Number.EPSILON); + return Math.round(n) / p; } -const CHARACTER_TO_POWER = new Map([ - ["-", "⁻"], - ["+", "⁺"], - ["=", "⁼"], - [" ", " "], - ["(", "⁽"], - [")", "⁾"], - ["0", "⁰"], - ["1", "¹"], - ["2", "²"], - ["3", "³"], - ["4", "⁴"], - ["5", "⁵"], - ["6", "⁶"], - ["7", "⁷"], - ["8", "⁸"], - ["9", "⁹"], - ["a", "ᵃ"], - ["b", "ᵇ"], - ["c", "ᶜ"], - ["d", "ᵈ"], - ["e", "ᵉ"], - ["f", "ᶠ"], - ["g", "ᵍ"], - ["h", "ʰ"], - ["i", "ⁱ"], - ["j", "ʲ"], - ["k", "ᵏ"], - ["l", "ˡ"], - ["m", "ᵐ"], - ["n", "ⁿ"], - ["o", "ᵒ"], - ["p", "ᵖ"], - ["r", "ʳ"], - ["s", "ˢ"], - ["t", "ᵗ"], - ["u", "ᵘ"], - ["v", "ᵛ"], - ["w", "ʷ"], - ["x", "ˣ"], - ["y", "ʸ"], - ["z", "ᶻ"] -]) +const powerpos = { + "-": "⁻", + "+": "⁺", + "=": "⁼", + " ": " ", + "(": "⁽", + ")": "⁾", + "0": "⁰", + "1": "¹", + "2": "²", + "3": "³", + "4": "⁴", + "5": "⁵", + "6": "⁶", + "7": "⁷", + "8": "⁸", + "9": "⁹", + "a": "ᵃ", + "b": "ᵇ", + "c": "ᶜ", + "d": "ᵈ", + "e": "ᵉ", + "f": "ᶠ", + "g": "ᵍ", + "h": "ʰ", + "i": "ⁱ", + "j": "ʲ", + "k": "ᵏ", + "l": "ˡ", + "m": "ᵐ", + "n": "ⁿ", + "o": "ᵒ", + "p": "ᵖ", + "r": "ʳ", + "s": "ˢ", + "t": "ᵗ", + "u": "ᵘ", + "v": "ᵛ", + "w": "ʷ", + "x": "ˣ", + "y": "ʸ", + "z": "ᶻ" +} -const CHARACTER_TO_INDICE = new Map([ - ["-", "₋"], - ["+", "₊"], - ["=", "₌"], - ["(", "₍"], - [")", "₎"], - [" ", " "], - ["0", "₀"], - ["1", "₁"], - ["2", "₂"], - ["3", "₃"], - ["4", "₄"], - ["5", "₅"], - ["6", "₆"], - ["7", "₇"], - ["8", "₈"], - ["9", "₉"], - ["a", "ₐ"], - ["e", "ₑ"], - ["h", "ₕ"], - ["i", "ᵢ"], - ["j", "ⱼ"], - ["k", "ₖ"], - ["l", "ₗ"], - ["m", "ₘ"], - ["n", "ₙ"], - ["o", "ₒ"], - ["p", "ₚ"], - ["r", "ᵣ"], - ["s", "ₛ"], - ["t", "ₜ"], - ["u", "ᵤ"], - ["v", "ᵥ"], - ["x", "ₓ"] -]) - -const EXPONENTS = [ - "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" +const exponents = [ + "⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹" ] -const EXPONENTS_REG = new RegExp("([" + EXPONENTS.join("") + "]+)", "g") +const exponentReg = new RegExp('(['+exponents.join('')+']+)', 'g') -/** - * Put a text in sup position - * @param {string} text - * @return {string} - */ +const indicepos = { + "-": "₋", + "+": "₊", + "=": "₌", + "(": "₍", + ")": "₎", + " ": " ", + "0": "₀", + "1": "₁", + "2": "₂", + "3": "₃", + "4": "₄", + "5": "₅", + "6": "₆", + "7": "₇", + "8": "₈", + "9": "₉", + "a": "ₐ", + "e": "ₑ", + "h": "ₕ", + "i": "ᵢ", + "j": "ⱼ", + "k": "ₖ", + "l": "ₗ", + "m": "ₘ", + "n": "ₙ", + "o": "ₒ", + "p": "ₚ", + "r": "ᵣ", + "s": "ₛ", + "t": "ₜ", + "u": "ᵤ", + "v": "ᵥ", + "x": "ₓ", +} +// Put a text in sup position export function textsup(text) { let ret = "" text = text.toString() - for(let letter of text) - ret += CHARACTER_TO_POWER.has(letter) ? CHARACTER_TO_POWER.get(letter) : letter + for (let i = 0; i < text.length; i++) { + if(Object.keys(powerpos).indexOf(text[i]) >= 0) { + ret += powerpos[text[i]] + } else { + ret += text[i] + } + } return ret } -/** - * Put a text in sub position - * @param {string} text - * @return {string} - */ +// Put a text in sub position export function textsub(text) { let ret = "" text = text.toString() - for(let letter of text) - ret += CHARACTER_TO_INDICE.has(letter) ? CHARACTER_TO_INDICE.get(letter) : letter + for (let i = 0; i < text.length; i++) { + if(Object.keys(indicepos).indexOf(text[i]) >= 0) { + ret += indicepos[text[i]] + } else { + ret += text[i] + } + } return ret } /** * Simplifies (mathematically) a mathematical expression. - * @deprecated * @param {string} str - Expression to parse * @returns {string} */ @@ -187,43 +185,37 @@ export function simplifyExpression(str) { // n1 & n3 are multiplied, opeM is the main operation (- or +). // Putting all n in form of number //n2 = n2 == undefined ? 1 : parseFloat(n) - n1 = m1 === undefined ? 1 : eval(m1 + "1") - n2 = m2 === undefined ? 1 : eval("1" + m2) - n3 = m3 === undefined ? 1 : eval(m3 + "1") - n4 = m4 === undefined ? 1 : eval("1" + m4) - //let [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n)) - // Falling back to * in case it does not exist (the corresponding n would be 1) - [ope2, ope4] = [ope2, ope4].map(ope => ope === "/" ? "/" : "*") - let coeff1 = n1 * n2 - let coeff2 = n3 * n4 - let coefficient = coeff1 + coeff2 - (opeM === "-" ? 2 * coeff2 : 0) - + n1 = m1 === undefined ? 1 : eval(m1 + '1') + n2 = m2 === undefined ? 1 : eval('1' + m2) + n3 = m3 === undefined ? 1 : eval(m3 + '1') + n4 = m4 === undefined ? 1 : eval('1' + m4) + //let [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n)) + // Falling back to * in case it does not exist (the corresponding n would be 1) + [ope2, ope4] = [ope2, ope4].map(ope => ope === '/' ? '/' : '*') + let coeff1 = n1*n2 + let coeff2 = n3*n4 + let coefficient = coeff1+coeff2-(opeM === '-' ? 2*coeff2 : 0) + return `${coefficient} * π` } ], [ // Removing parenthesis when content is only added from both sides. /(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g, - function(match, b4, middle, after) { - return `${b4}${middle}${after}` - } + function(match, b4, middle, after) {return `${b4}${middle}${after}`} ], [ // Removing parenthesis when content is only multiplied. /(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g, - function(match, b4, middle, after) { - return `${b4}${middle}${after}` - } + function(match, b4, middle, after) {return `${b4}${middle}${after}`} ], [ // Removing parenthesis when content is only multiplied. /(^|[*\/+-] |\()\(([^)(+-]+)\)($| [*\/]|\))/g, - function(match, b4, middle, after) { - return `${b4}${middle}${after}` - } + function(match, b4, middle, after) {return `${b4}${middle}${after}`} ], [// Simplification additions/subtractions. /(^|[^*\/] |\()([-.\d]+) [+-] (\([^)(]+\)|[^)(]+) [+-] ([-.\d]+)($| [^*\/]|\))/g, function(match, b4, n1, op1, middle, op2, n2, after) { let total - if(op2 === "+") { + if(op2 === '+') { total = parseFloat(n1) + parseFloat(n2) } else { total = parseFloat(n1) - parseFloat(n2) @@ -232,14 +224,14 @@ export function simplifyExpression(str) { } ], [// Simplification multiplications/divisions. - /([-.\d]+) [*\/] (\([^)(]+\)|[^)(+-]+) [*\/] ([-.\d]+)/g, + /([-.\d]+) [*\/] (\([^)(]+\)|[^)(+-]+) [*\/] ([-.\d]+)/g, function(match, n1, op1, middle, op2, n2) { - if(parseInt(n1) === n1 && parseInt(n2) === n2 && op2 === "/" && - (parseInt(n1) / parseInt(n2)) % 1 !== 0) { + if(parseInt(n1) === n1 && parseInt(n2) === n2 && op2 === '/' && + (parseInt(n1) / parseInt(n2)) % 1 !== 0) { // Non int result for int division. return `(${n1} / ${n2}) ${op1} ${middle}` } else { - if(op2 === "*") { + if(op2 === '*') { return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}` } else { return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}` @@ -253,17 +245,17 @@ export function simplifyExpression(str) { let str = middle // Replace all groups while(/\([^)(]+\)/g.test(str)) - str = str.replace(/\([^)(]+\)/g, "") + str = str.replace(/\([^)(]+\)/g, '') // There shouldn't be any more parenthesis // If there is, that means the 2 parenthesis are needed. - if(!str.includes(")") && !str.includes("(")) { + if(!str.includes(')') && !str.includes('(')) { return middle } else { return `(${middle})` } - + } - ] + ], // Simple simplifications // [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'], // [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'], @@ -276,7 +268,7 @@ export function simplifyExpression(str) { // [/(^| |\() /g, '$1'], // [/ ($|\))/g, '$1'], ] - + // Replacements let found do { @@ -294,35 +286,24 @@ export function simplifyExpression(str) { /** * Transforms a mathematical expression to make it readable by humans. * NOTE: Will break parsing of expression. - * @deprecated * @param {string} str - Expression to parse. * @returns {string} */ export function makeExpressionReadable(str) { let replacements = [ // letiables - [/pi/g, "π"], - [/Infinity/g, "∞"], - [/inf/g, "∞"], + [/pi/g, 'π'], + [/Infinity/g, '∞'], + [/inf/g, '∞'], // Other - [/ \* /g, "×"], - [/ \^ /g, "^"], - [/\^\(([\d\w+-]+)\)/g, function(match, p1) { - return textsup(p1) - }], - [/\^([\d\w+-]+)/g, function(match, p1) { - return textsup(p1) - }], - [/_\(([\d\w+-]+)\)/g, function(match, p1) { - return textsub(p1) - }], - [/_([\d\w+-]+)/g, function(match, p1) { - return textsub(p1) - }], - [/\[([^\[\]]+)\]/g, function(match, p1) { - return textsub(p1) - }], - [/(\d|\))×/g, "$1"], + [/ \* /g, '×'], + [/ \^ /g, '^'], + [/\^\(([\d\w+-]+)\)/g, function(match, p1) { return textsup(p1) }], + [/\^([\d\w+-]+)/g, function(match, p1) { return textsup(p1) }], + [/_\(([\d\w+-]+)\)/g, function(match, p1) { return textsub(p1) }], + [/_([\d\w+-]+)/g, function(match, p1) { return textsub(p1) }], + [/\[([^\[\]]+)\]/g, function(match, p1) { return textsub(p1) }], + [/(\d|\))×/g, '$1'], [/integral\((.+),\s?(.+),\s?["'](.+)["'],\s?["'](.+)["']\)/g, function(match, a, b, p1, body, p2, p3, by, p4) { if(a.length < b.length) { return `∫${textsub(a)}${textsup(b)} ${body} d${by}` @@ -331,10 +312,10 @@ export function makeExpressionReadable(str) { } }], [/derivative\(?["'](.+)["'], ?["'](.+)["'], ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) { - return `d(${body.replace(new RegExp(by, "g"), "x")})/dx` + return `d(${body.replace(new RegExp(by, 'g'), 'x')})/dx` }] ] - + // str = simplifyExpression(str) // Replacements for(let replacement of replacements) @@ -343,48 +324,6 @@ export function makeExpressionReadable(str) { return str } -/** @type {[RegExp, string][]} */ -const replacements = [ - // Greek letters - [/(\W|^)al(pha)?(\W|$)/g, "$1α$3"], - [/(\W|^)be(ta)?(\W|$)/g, "$1β$3"], - [/(\W|^)ga(mma)?(\W|$)/g, "$1γ$3"], - [/(\W|^)de(lta)?(\W|$)/g, "$1δ$3"], - [/(\W|^)ep(silon)?(\W|$)/g, "$1ε$3"], - [/(\W|^)ze(ta)?(\W|$)/g, "$1ζ$3"], - [/(\W|^)et(a)?(\W|$)/g, "$1η$3"], - [/(\W|^)th(eta)?(\W|$)/g, "$1θ$3"], - [/(\W|^)io(ta)?(\W|$)/g, "$1ι$3"], - [/(\W|^)ka(ppa)?(\W|$)/g, "$1κ$3"], - [/(\W|^)la(mbda)?(\W|$)/g, "$1λ$3"], - [/(\W|^)mu(\W|$)/g, "$1μ$2"], - [/(\W|^)nu(\W|$)/g, "$1ν$2"], - [/(\W|^)xi(\W|$)/g, "$1ξ$2"], - [/(\W|^)rh(o)?(\W|$)/g, "$1ρ$3"], - [/(\W|^)si(gma)?(\W|$)/g, "$1σ$3"], - [/(\W|^)ta(u)?(\W|$)/g, "$1τ$3"], - [/(\W|^)up(silon)?(\W|$)/g, "$1υ$3"], - [/(\W|^)ph(i)?(\W|$)/g, "$1φ$3"], - [/(\W|^)ch(i)?(\W|$)/g, "$1χ$3"], - [/(\W|^)ps(i)?(\W|$)/g, "$1ψ$3"], - [/(\W|^)om(ega)?(\W|$)/g, "$1ω$3"], - // Capital greek letters - [/(\W|^)gga(mma)?(\W|$)/g, "$1Γ$3"], - [/(\W|^)gde(lta)?(\W|$)/g, "$1Δ$3"], - [/(\W|^)gth(eta)?(\W|$)/g, "$1Θ$3"], - [/(\W|^)gla(mbda)?(\W|$)/g, "$1Λ$3"], - [/(\W|^)gxi(\W|$)/g, "$1Ξ$2"], - [/(\W|^)gpi(\W|$)/g, "$1Π$2"], - [/(\W|^)gsi(gma)?(\W|$)/g, "$1Σ$3"], - [/(\W|^)gph(i)?(\W|$)/g, "$1Φ$3"], - [/(\W|^)gps(i)?(\W|$)/g, "$1Ψ$3"], - [/(\W|^)gom(ega)?(\W|$)/g, "$1Ω$3"], - // Array elements - [/\[([^\]\[]+)\]/g, function(match, p1) { - return textsub(p1) - }] -] - /** * Parses a variable name to make it readable by humans. * @@ -393,24 +332,65 @@ const replacements = [ * @returns {string} - The parsed name */ export function parseName(str, removeUnallowed = true) { - for(const replacement of replacements) + let replacements = [ + // Greek letters + [/([^a-z]|^)al(pha)?([^a-z]|$)/g, '$1α$3'], + [/([^a-z]|^)be(ta)?([^a-z]|$)/g, '$1β$3'], + [/([^a-z]|^)ga(mma)?([^a-z]|$)/g, '$1γ$3'], + [/([^a-z]|^)de(lta)?([^a-z]|$)/g, '$1δ$3'], + [/([^a-z]|^)ep(silon)?([^a-z]|$)/g, '$1ε$3'], + [/([^a-z]|^)ze(ta)?([^a-z]|$)/g, '$1ζ$3'], + [/([^a-z]|^)et(a)?([^a-z]|$)/g, '$1η$3'], + [/([^a-z]|^)th(eta)?([^a-z]|$)/g, '$1θ$3'], + [/([^a-z]|^)io(ta)?([^a-z]|$)/g, '$1ι$3'], + [/([^a-z]|^)ka(ppa)([^a-z]|$)?/g, '$1κ$3'], + [/([^a-z]|^)la(mbda)?([^a-z]|$)/g, '$1λ$3'], + [/([^a-z]|^)mu([^a-z]|$)/g, '$1μ$2'], + [/([^a-z]|^)nu([^a-z]|$)/g, '$1ν$2'], + [/([^a-z]|^)xi([^a-z]|$)/g, '$1ξ$2'], + [/([^a-z]|^)rh(o)?([^a-z]|$)/g, '$1ρ$3'], + [/([^a-z]|^)si(gma)?([^a-z]|$)/g, '$1σ$3'], + [/([^a-z]|^)ta(u)?([^a-z]|$)/g, '$1τ$3'], + [/([^a-z]|^)up(silon)?([^a-z]|$)/g, '$1υ$3'], + [/([^a-z]|^)ph(i)?([^a-z]|$)/g, '$1φ$3'], + [/([^a-z]|^)ch(i)?([^a-z]|$)/g, '$1χ$3'], + [/([^a-z]|^)ps(i)?([^a-z]|$)/g, '$1ψ$3'], + [/([^a-z]|^)om(ega)?([^a-z]|$)/g, '$1ω$3'], + // Capital greek letters + [/([^a-z]|^)gga(mma)?([^a-z]|$)/g, '$1Γ$3'], + [/([^a-z]|^)gde(lta)?([^a-z]|$)/g, '$1Δ$3'], + [/([^a-z]|^)gth(eta)?([^a-z]|$)/g, '$1Θ$3'], + [/([^a-z]|^)gla(mbda)?([^a-z]|$)/g, '$1Λ$3'], + [/([^a-z]|^)gxi([^a-z]|$)/g, '$1Ξ$2'], + [/([^a-z]|^)gpi([^a-z]|$)/g, '$1Π$2'], + [/([^a-z]|^)gsi(gma)([^a-z]|$)?/g, '$1Σ$3'], + [/([^a-z]|^)gph(i)?([^a-z]|$)/g, '$1Φ$3'], + [/([^a-z]|^)gps(i)?([^a-z]|$)/g, '$1Ψ$3'], + [/([^a-z]|^)gom(ega)?([^a-z]|$)/g, '$1Ω$3'], + // Underscores + // [/_\(([^_]+)\)/g, function(match, p1) { return textsub(p1) }], + // [/_([^" ]+)/g, function(match, p1) { return textsub(p1) }], + // Array elements + [/\[([^\]\[]+)\]/g, function(match, p1) { return textsub(p1) }], + // Removing + [/[xπℝℕ\\∪∩\]\[ ()^/÷*×+=\d-]/g , ''], + ] + if(!removeUnallowed) replacements.pop() + // Replacements + for(let replacement of replacements) str = str.replace(replacement[0], replacement[1]) - if(removeUnallowed) - str = str.replace(/[xnπℝℕ\\∪∩\]\[ ()^/÷*×+=\d¹²³⁴⁵⁶⁷⁸⁹⁰-]/g, "") - return str } /** * Transforms camel case strings to a space separated one. * - * @deprecated * @param {string} label - Camel case to parse * @returns {string} Parsed label. */ export function camelCase2readable(label) { let parsed = parseName(label, false) - return parsed.charAt(0).toLatinUppercase() + parsed.slice(1).replace(/([A-Z])/g, " $1") + return parsed.charAt(0).toLatinUppercase() + parsed.slice(1).replace(/([A-Z])/g," $1") } /** @@ -418,12 +398,12 @@ export function camelCase2readable(label) { * @returns {string} */ export function getRandomColor() { - let clrs = "0123456789ABCDEF" - let color = "#" + let clrs = '0123456789ABCDEF'; + let color = '#'; for(let i = 0; i < 6; i++) { - color += clrs[Math.floor(Math.random() * (16 - 5 * (i % 2 === 0)))] + color += clrs[Math.floor(Math.random() * (16-5*(i%2===0)))]; } - return color + return color; } /** @@ -432,15 +412,16 @@ export function getRandomColor() { * @returns {string} */ export function escapeHTML(str) { - return str.replace(/&/g, "&").replace(//g, ">") + return str.replace(/&/g,'&').replace(//g,'>') ; } + /** * Parses exponents and replaces them with expression values * @param {string} expression - The expression to replace in. * @return {string} The parsed expression */ export function exponentsToExpression(expression) { - return expression.replace(EXPONENTS_REG, (m, exp) => "^" + exp.split("").map((x) => EXPONENTS.indexOf(x)).join("")) + return expression.replace(exponentReg, (m, exp) => '^' + exp.split('').map((x) => exponents.indexOf(x)).join('')) } diff --git a/common/test/general/events.mjs b/common/test/general/events.mjs deleted file mode 100644 index 15d44aa..0000000 --- a/common/test/general/events.mjs +++ /dev/null @@ -1,122 +0,0 @@ -/** - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { BaseEventEmitter, BaseEvent } from "../../src/events.mjs" - -import { describe, it } from "mocha" -import { expect, use } from "chai" -import spies from "chai-spies" - -// Setting up modules -const { spy } = use(spies) - - -class MockEmitter extends BaseEventEmitter { - static emits = ["example1", "example2"] -} - -class MockEvent1 extends BaseEvent { - constructor() { - super("example1") - } -} - -class MockEvent2 extends BaseEvent { - constructor(parameter) { - super("example2") - this.parameter = parameter - } -} - -const sandbox = spy.sandbox() - -describe("EventsEmitters", function() { - - afterEach(() => { - sandbox.restore() - }) - - it("should forward events to all of its listeners", function() { - const emitter = new MockEmitter() - const listener1 = spy() - const listener2 = spy() - emitter.on("example1", listener1) - emitter.on("example1", listener2) - emitter.emit(new MockEvent1()) - expect(listener1).to.have.been.called.once - expect(listener2).to.have.been.called.once - }) - - it("should forward multiple events to a singular listener", function() { - const emitter = new MockEmitter() - const listener = spy() - const mockEvent1 = new MockEvent1() - const mockEvent2 = new MockEvent2(3) - emitter.on("example1 example2", listener) - emitter.emit(mockEvent1) - emitter.emit(mockEvent2) - expect(listener).to.have.been.called.twice - expect(listener).to.also.have.been.called.with.exactly(mockEvent1) - expect(listener).to.also.have.been.called.with.exactly(mockEvent2) - }) - - it("should be able to have listeners remvoed", function() { - const emitter = new MockEmitter() - const listener = spy() - emitter.on("example1", listener) - const removedFromEventItDoesntListenTo = emitter.off("example2", listener) - const removedFromEventItListensTo = emitter.off("example1", listener) - const removedFromEventASecondTime = emitter.off("example1", listener) - expect(removedFromEventItDoesntListenTo).to.be.false - expect(removedFromEventItListensTo).to.be.true - expect(removedFromEventASecondTime).to.be.false - emitter.on("example1 example2", listener) - const removedFromBothEvents = emitter.off("example1 example2", listener) - expect(removedFromBothEvents).to.be.true - emitter.on("example1", listener) - const removedFromOneOfTheEvents = emitter.off("example1 example2", listener) - expect(removedFromOneOfTheEvents).to.be.true - }) - - it("should be able to remove one listener listening to one event when said listener listens to multiple", function() { - const emitter = new MockEmitter() - const listener = spy() - const mockEvent1 = new MockEvent1() - const mockEvent2 = new MockEvent2(3) - emitter.on("example1 example2", listener) - // Disable listener for example1 - emitter.off("example1", listener) - emitter.emit(mockEvent1) - emitter.emit(mockEvent2) - expect(listener).to.have.been.called.once - expect(listener).to.also.have.been.called.with.exactly(mockEvent2) - }) - - it("shouldn't be able to add listen/unlisten to, or emit inexistant events", function() { - const emitter = new MockEmitter() - const listener = spy() - expect(() => emitter.on("inexistant", listener)).to.throw(Error) - expect(() => emitter.off("inexistant", listener)).to.throw(Error) - expect(() => emitter.emit(new BaseEvent("inexistant"))).to.throw(Error) - }) - - it("shouldn't be able to emit non-events", function() { - const emitter = new MockEmitter() - expect(() => emitter.emit("not-an-event")).to.throw(Error) - }) -}) \ No newline at end of file diff --git a/common/test/general/utils.mjs b/common/test/general/utils.mjs deleted file mode 100644 index 8078645..0000000 --- a/common/test/general/utils.mjs +++ /dev/null @@ -1,183 +0,0 @@ -/** - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { textsup, textsub, parseName, getRandomColor, escapeHTML, exponentsToExpression } from "../../src/utils.mjs" - - -import { describe, it } from "mocha" -import { expect } from "chai" - -describe("Extensions", function() { - describe("#String.toLatinUppercase", function() { - it("should be able to transform latin characters from strings to their uppercase version", function() { - expect("abc".toLatinUppercase()).to.equal("ABC") - expect("abCd".toLatinUppercase()).to.equal("ABCD") - expect("ab123cd456".toLatinUppercase()).to.equal("AB123CD456") - expect("ABC".toLatinUppercase()).to.equal("ABC") - }) - - it("shouldn't transform non latin characters to their uppercase version", function() { - expect("abαπ".toLatinUppercase()).to.equal("ABαπ") - expect("abαπ".toLatinUppercase()).to.not.equal("abαπ".toUpperCase()) - }) - }) - - describe("#String.removeEnclosure", function() { - it("should be able to remove the first and last characters", function() { - expect("[1+t]".removeEnclosure()).to.equal("1+t") - expect('"a+b+c*d"'.removeEnclosure()).to.equal("a+b+c*d") - expect("(pi/2)".removeEnclosure()).to.equal("pi/2") - }) - }) - - describe("#Number.toDecimalPrecision", function() { - it("should be able to round a number to a fixed decimal precision", function() { - expect(123.456789.toDecimalPrecision()).to.equal(123) - expect(123.456789.toDecimalPrecision(1)).to.equal(123.5) - expect(123.456789.toDecimalPrecision(2)).to.equal(123.46) - expect(123.456789.toDecimalPrecision(3)).to.equal(123.457) - expect(123.456789.toDecimalPrecision(4)).to.equal(123.4568) - expect(123.456789.toDecimalPrecision(5)).to.equal(123.45679) - expect(123.456789.toDecimalPrecision(6)).to.equal(123.456789) - expect(123.111111.toDecimalPrecision(5)).to.equal(123.11111) - }) - }) -}) - -describe("Utils", function() { - describe("#textsup", function() { - it("should transform characters which have a sup unicode equivalent", function() { - expect(textsup("-+=()")).to.equal("⁻⁺⁼⁽⁾") - expect(textsup("0123456789")).to.equal("⁰¹²³⁴⁵⁶⁷⁸⁹") - expect(textsup("abcdefghijklmnoprstuvwxyz")).to.equal("ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ") - }) - - it("shouldn't transform characters without a sup equivalent", function() { - expect(textsup("ABCDEFGHIJKLMNOPQRSTUVWXYZq")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZq") - }) - - it("should partially transform strings which only have a few characters with a sup equivalent", function() { - expect(textsup("ABCabcABC")).to.equal("ABCᵃᵇᶜABC") - }) - }) - - describe("#textsub", function() { - it("should transform characters which have a sub unicode equivalent", function() { - expect(textsub("-+=()")).to.equal("₋₊₌₍₎") - expect(textsub("0123456789")).to.equal("₀₁₂₃₄₅₆₇₈₉") - expect(textsub("aehijklmnoprstuvx")).to.equal("ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ") - }) - - it("shouldn't transform characters without a sub equivalent", function() { - expect(textsub("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - expect(textsub("bcdfgqyz")).to.equal("bcdfgqyz") - }) - - it("should partially transform strings which only have a few characters with a sub equivalent", function() { - expect(textsub("ABC123ABC")).to.equal("ABC₁₂₃ABC") - }) - }) - - describe("#parseName", function() { - it("should parse greek letter names", function() { - const shorthands = { - "α": ["al", "alpha"], - "β": ["be", "beta"], - "γ": ["ga", "gamma"], - "δ": ["de", "delta"], - "ε": ["ep", "epsilon"], - "ζ": ["ze", "zeta"], - "η": ["et", "eta"], - "θ": ["th", "theta"], - "κ": ["ka", "kappa"], - "λ": ["la", "lambda"], - "μ": ["mu"], - "ν": ["nu"], - "ξ": ["xi"], - "ρ": ["rh", "rho"], - "σ": ["si", "sigma"], - "τ": ["ta", "tau"], - "υ": ["up", "upsilon"], - "φ": ["ph", "phi"], - "χ": ["ch", "chi"], - "ψ": ["ps", "psi"], - "ω": ["om", "omega"], - "Γ": ["gga", "ggamma"], - "Δ": ["gde", "gdelta"], - "Θ": ["gth", "gtheta"], - "Λ": ["gla", "glambda"], - "Ξ": ["gxi"], - "Π": ["gpi"], - "Σ": ["gsi", "gsigma"], - "Φ": ["gph", "gphi"], - "Ψ": ["gps", "gpsi"], - "Ω": ["gom", "gomega"], - } - for(const [char, shorts] of Object.entries(shorthands)) { - expect(parseName(char)).to.equal(char) - for(const short of shorts) - expect(parseName(short)).to.equal(char) - } - }) - - it("should parse array elements into sub", function() { - expect(parseName("u[n+1]")).to.equal("uₙ₊₁") - expect(parseName("u[(n+x)]")).to.equal("u₍ₙ₊ₓ₎") - expect(parseName("u[A]")).to.equal("uA") - }) - - it("should remove disallowed characters when indicated", function() { - const disallowed = "xnπℝℕ\\∪∩[] ()^/^/÷*×+=1234567890¹²³⁴⁵⁶⁷⁸⁹⁰-" - expect(parseName(disallowed)).to.equal("") - expect(parseName("AA" + disallowed)).to.equal("AA") - expect(parseName(disallowed, false)).to.equal(disallowed) - }) - - it("should be able to do all three at once", function() { - expect(parseName("al[n+1]+n")).to.equal("αₙ₊₁") - expect(parseName("al[n+1]+n", false)).to.equal("αₙ₊₁+n") - }) - }) - - describe("#getRandomColor", function() { - it("should provide a valid color", function() { - const colorReg = /^#[A-F\d]{6}$/ - for(let i = 0; i < 50; i++) - expect(getRandomColor()).to.match(colorReg) - }) - }) - - describe("#escapeHTML", function() { - it("should should escape ampersands", function() { - expect(escapeHTML("&")).to.equal("&") - expect(escapeHTML("High & Mighty")).to.equal("High & Mighty") - }) - - it("should escape injected HTML tags", function() { - expect(escapeHTML("")).to.equal("<script>alert('Injected!')</script>") - expect(escapeHTML('Link')).to.equal('<a href="javascript:alert()">Link</a>') - }) - }) - - describe("#exponentsToExpression", function() { - it("should transform exponents to power expression", function() { - expect(exponentsToExpression("x¹²³⁴⁵⁶⁷⁸⁹⁰")).to.equal("x^1234567890") - expect(exponentsToExpression("x¹²+y³⁴")).to.equal("x^12+y^34") - }) - }) -}) diff --git a/common/test/hooks.mjs b/common/test/hooks.mjs index 0f3ebda..e48525e 100644 --- a/common/test/hooks.mjs +++ b/common/test/hooks.mjs @@ -19,10 +19,12 @@ import * as fs from "./mock/fs.mjs"; import Qt from "./mock/qt.mjs"; import { MockHelper } from "./mock/helper.mjs"; import { MockLatex } from "./mock/latex.mjs"; +import Modules from "../src/module/index.mjs"; function setup() { globalThis.Helper = new MockHelper() globalThis.Latex = new MockLatex() + Modules.Latex.initialize({ latex: Latex, helper: Helper }) } setup() diff --git a/common/test/math/domain.mjs b/common/test/math/domain.mjs index e32dbd1..c00384f 100644 --- a/common/test/math/domain.mjs +++ b/common/test/math/domain.mjs @@ -19,46 +19,46 @@ import { describe, it } from "mocha" import { expect } from "chai" -// import { Domain, parseDomainSimple } from "../../src/math/domain.mjs" -// -// describe("math.domain", function() { -// describe("#parseDomainSimple", function() { -// it("returns predefined domains", function() { -// const predefinedToCheck = [ -// // Real domains -// { domain: Domain.R, shortcuts: ["R", "ℝ"] }, -// // Zero exclusive real domains -// { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] }, -// // Real positive domains -// { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] }, -// // Zero-exclusive real positive domains -// { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] }, -// // Real negative domain -// { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] }, -// // Zero-exclusive real negative domains -// { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] }, -// // Natural integers domain -// { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] }, -// // Zero-exclusive natural integers domain -// { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] }, -// // Logarithmic natural domains -// { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] }, -// // All integers domains -// { domain: Domain.Z, shortcuts: ["Z", "ℤ"] }, -// // Zero-exclusive all integers domain -// { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] }, -// // Negative integers domain -// { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] }, -// // Zero-exclusive negative integers domain -// { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] }, -// ] -// -// // Real domains -// for(const { domain, shortcuts } of predefinedToCheck) -// for(const shortcut of shortcuts) -// expect(parseDomainSimple(shortcut)).to.be.equal(domain) -// }) -// -// it("") -// }) -// }) +import { Domain, parseDomainSimple } from "../../src/math/domain.mjs" + +describe("math.domain", function() { + describe("#parseDomainSimple", function() { + it("returns predefined domains", function() { + const predefinedToCheck = [ + // Real domains + { domain: Domain.R, shortcuts: ["R", "ℝ"] }, + // Zero exclusive real domains + { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] }, + // Real positive domains + { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] }, + // Zero-exclusive real positive domains + { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] }, + // Real negative domain + { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] }, + // Zero-exclusive real negative domains + { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] }, + // Natural integers domain + { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] }, + // Zero-exclusive natural integers domain + { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] }, + // Logarithmic natural domains + { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] }, + // All integers domains + { domain: Domain.Z, shortcuts: ["Z", "ℤ"] }, + // Zero-exclusive all integers domain + { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] }, + // Negative integers domain + { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] }, + // Zero-exclusive negative integers domain + { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] }, + ] + + // Real domains + for(const { domain, shortcuts } of predefinedToCheck) + for(const shortcut of shortcuts) + expect(parseDomainSimple(shortcut)).to.be.equal(domain) + }) + + it("") + }) +})