penpot/frontend/test/frontend_tests/util_simple_math_test.cljs
AK f2a9dd1a08
🐛 Reject invalid formulas on numeric inputs (#10659)
Relative operators were accepted in operand position, so "10+*3" silently evaluated to 310 instead of being rejected. Make negation a first-class operand so legitimate negative operands ("10 + -3") keep working.

Fixes #9581

Signed-off-by: Akshit Nassa <akshitnassa412@gmail.com>
Co-authored-by: Akshit Nassa <akshitnassa412@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-07-20 16:15:29 +02:00

188 lines
6.8 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns frontend-tests.util-simple-math-test
(:require
[app.common.math :as cm]
[app.util.simple-math :as sm]
[cljs.test :as t :include-macros true]))
(t/deftest test-parser-inst
(t/testing "Evaluate an empty string"
(let [result (sm/expr-eval "" 999)]
(t/is (= result nil))))
(t/testing "Evaluate a single number"
(let [result (sm/expr-eval "10" 999)]
(t/is (= result 10))))
(t/testing "Evaluate an addition"
(let [result (sm/expr-eval "10+3" 999)]
(t/is (= result 13))))
(t/testing "Evaluate an addition with spaces"
(let [result (sm/expr-eval "100 + 35" 999)]
(t/is (= result 135))))
(t/testing "Evaluate some operations"
(let [result (sm/expr-eval "100 + 35 - 10 * 2" 999)]
(t/is (= result 115))))
(t/testing "Evaluate some operations with parentheses"
(let [result (sm/expr-eval "(100 + 35 - 10) * 2" 999)]
(t/is (= result 250))))
(t/testing "Evaluate some operations with nested parentheses"
(let [result (sm/expr-eval "(100 + 35 - (20/2))*2" 999)]
(t/is (= result 250))))
(t/testing "Evaluate a relative addition"
(let [result (sm/expr-eval "+10" 20)]
(t/is (= result 30))))
(t/testing "Evaluate a relative multiplication"
(let [result (sm/expr-eval "*10" 20)]
(t/is (= result 200))))
(t/testing "Evaluate a negative number (not relative subtraction)"
(let [result (sm/expr-eval "-10" 20)]
(t/is (= result -10))))
(t/testing "Evaluate a relative complex operation"
(let [result (sm/expr-eval "+(10*2 - 5)" 20)]
(t/is (= result 35))))
(t/testing "Evaluate a percentual operation"
(let [result (sm/expr-eval "+50%" 20)]
(t/is (= result 30))))
(t/testing "Evaluate a complex operation with percents"
(let [result (sm/expr-eval "5 + (25% * 2)" 100)]
(t/is (= result 55))))
(t/testing "Evaluate a complex operation with percents and relative"
(let [result (sm/expr-eval "+ (25% * 2)" 100)]
(t/is (= result 150))))
(t/testing "Evaluate an addition with decimals"
(let [result1 (sm/expr-eval "10 + 2.5" 999)
result2 (sm/expr-eval "10 + 2,5" 999)]
(t/is (= result1 result2 12.5))))
(t/testing "Evaluate a relative operation with decimals"
(let [result1 (sm/expr-eval "*.5" 20)
result2 (sm/expr-eval "*,5" 20)]
(t/is (= result1 result2 10))))
(t/testing "Evaluate a percentual operation with decimals"
(let [result1 (sm/expr-eval "+10.5%" 20)
result2 (sm/expr-eval "+10,5%" 20)]
(t/is (= result1 result2 22.1))))
(t/testing "Evaluate a complex operation with decimals"
(let [result1 (sm/expr-eval "(20.333 + 10%) * (1 / 3)" 20)
result2 (sm/expr-eval "(20,333 + 10%) * (1 / 3)" 20)]
(t/is (cm/close? result1 result2 7.44433333)))))
(t/deftest test-error-handling
(t/testing "Division by zero should return nil"
(let [result (sm/expr-eval "10/0" 999)]
(t/is (= result nil))))
(t/testing "Expression with division by zero should return nil"
(let [result (sm/expr-eval "(10 + 5) / 0" 999)]
(t/is (= result nil))))
(t/testing "Invalid syntax should return nil"
(let [result (sm/expr-eval "asdasd+2" 999)]
(t/is (= result nil))))
(t/testing "Empty expression with no init-value should return nil"
(let [result (sm/expr-eval "" nil)]
(t/is (= result nil))))
(t/testing "Partial invalid expression should return nil"
(let [result (sm/expr-eval "10 + abc" 100)]
(t/is (= result nil)))))
(t/deftest test-relative-operators-only-at-top-level
(t/testing "Two consecutive operators should return nil"
(t/is (= nil (sm/expr-eval "10+*3" 100)))
(t/is (= nil (sm/expr-eval "10 + *3" 100)))
(t/is (= nil (sm/expr-eval "10+/3" 100)))
(t/is (= nil (sm/expr-eval "10 + +5" 100)))
(t/is (= nil (sm/expr-eval "10 * *5" 100)))
(t/is (= nil (sm/expr-eval "10 * /5" 100))))
(t/testing "Relative operators inside parentheses should return nil"
(t/is (= nil (sm/expr-eval "(*3)" 100)))
(t/is (= nil (sm/expr-eval "10 + (*3)" 100)))
(t/is (= nil (sm/expr-eval "+(10 + *3)" 100))))
(t/testing "A dangling operator should return nil"
(t/is (= nil (sm/expr-eval "10 +" 100)))
(t/is (= nil (sm/expr-eval "+" 100)))
(t/is (= nil (sm/expr-eval "*" 100))))
(t/testing "Valid relative expressions keep working"
(t/is (= 130 (sm/expr-eval "+30" 100)))
(t/is (= 300 (sm/expr-eval "*3" 100)))
(t/is (= -30 (sm/expr-eval "-30" 100)))
(t/is (= 50 (sm/expr-eval "/2" 100)))))
(t/deftest test-negative-operands
(t/testing "A negative number is a valid operand"
(t/is (= 7 (sm/expr-eval "10 + -3" 100)))
(t/is (= 7 (sm/expr-eval "10+-3" 100)))
(t/is (= 15 (sm/expr-eval "10 - -5" 100)))
(t/is (= -30 (sm/expr-eval "10 * -3" 100)))
(t/is (= 4 (sm/expr-eval "10 + -3 * 2" 100)))
(t/is (= -20 (sm/expr-eval "-(10 + 10)" 100)))
(t/is (= 5 (sm/expr-eval "10 - -5 * 1 - 10" 100))))
(t/testing "A negative number is a valid relative operand"
(t/is (= 70 (sm/expr-eval "+ -30" 100)))
(t/is (= -300 (sm/expr-eval "* -3" 100))))
(t/testing "A negative percentage is a valid operand"
(t/is (= 75 (sm/expr-eval "100 + -25%" 100)))
(t/is (= -25 (sm/expr-eval "-25%" 100)))))
(t/deftest test-operator-associativity
(t/testing "Chained subtraction is evaluated left to right"
(t/is (= 55 (sm/expr-eval "100 - 35 - 10" 999)))
(t/is (= 65 (sm/expr-eval "100 - 35 + 10 - 10" 999)))
(t/is (= 15 (sm/expr-eval "1 + 2 + 3 + 4 + 5" 999))))
(t/testing "Chained division is evaluated left to right"
(t/is (= 5 (sm/expr-eval "100 / 10 / 2" 999)))
(t/is (= 20 (sm/expr-eval "100 / 10 * 2" 999)))
(t/is (= 5 (sm/expr-eval "(100 / 10) / 2" 999))))
(t/testing "Precedence and parentheses are preserved"
(t/is (= 7 (sm/expr-eval "1 + 2 * 3" 999)))
(t/is (= 9 (sm/expr-eval "(1 + 2) * 3" 999)))))
(t/deftest test-edge-cases
(t/testing "Relative division by zero returns nil"
(t/is (= nil (sm/expr-eval "/0" 100)))
(t/is (= nil (sm/expr-eval "10 / 5 / 0" 100))))
(t/testing "Relative operators fall back to a zero init-value"
(t/is (= 10 (sm/expr-eval "+10" nil)))
(t/is (= 0 (sm/expr-eval "*10" nil))))
(t/testing "Percentages of the init-value"
(t/is (= 25 (sm/expr-eval "25%" 100)))
(t/is (= 125 (sm/expr-eval "+25%" 100))))
(t/testing "Non numeric input returns nil"
(t/is (= nil (sm/expr-eval " " 100)))
(t/is (= nil (sm/expr-eval "10 20" 100)))
(t/is (= nil (sm/expr-eval "(10" 100)))
(t/is (= nil (sm/expr-eval "10€" 100)))
(t/is (= nil (sm/expr-eval "🙂" 100)))))