Files

105 lines
5.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import ConjureKit
import Foundation
import Testing
@Test
func calculatorHonorsPrecedenceUnaryAndRightAssociativePowers() {
let examples: [(String, String)] = [
("2 + 3 * 4", "14"), ("(2 + 3) * 4", "20"),
("2^3^2", "512"), ("-2^2", "-4"), ("(-2)^2", "4"),
("2^-2", "0.25"), ("--2 + +3", "5"),
("8 ÷ 2 × 3", "12"), ("2x3", "6"), ("2 x (3 + 1)", "8"),
("10 % 3", "1"), ("10 mod -3", "1"),
]
for (query, expected) in examples {
let result = Calculator.evaluate(query)
#expect(result?.displayValue == expected, "\(query)")
#expect(result?.copyValue == expected, "\(query)")
}
}
@Test
func calculatorSupportsFunctionsPercentagesAndScientificNotation() {
let examples: [(String, String)] = [
("sqrt(81) + abs(-3)", "12"), ("round(2.5) + floor(2.9) + ceil(2.1)", "8"),
("sin(pi/2) + cos(0) + tan(0)", "2"), ("log(100) + ln(e)", "3"),
("50%", "0.5"), ("200 * 10%", "20"), ("50% + 2", "2.5"),
("52% of 900", "468"), ("20% off 80", "64"), ("20% off (80 + 20)", "80"),
(".5e2 + 1E-2", "50.01"), ("π - pi", "0"),
("pi", "3.14159265359"), ("e", "2.71828182846"), ("1 / 3", "0.333333333333"),
]
for (query, expected) in examples {
#expect(Calculator.evaluate(query)?.copyValue == expected, "\(query)")
}
}
@Test
func calculatorConvertsDimensionsAndTemperatureOffsets() throws {
let examples: [(String, Double, String)] = [
("10 km to mi", 6.21371192237, "mi"), ("10ft in m", 3.048, "m"),
("29 inches to cm", 73.66, "cm"), ("2 * 5 cm to inches", 3.93700787402, "in"),
("1 kilograms to pounds", 2.20462262185, "lb"), ("16oz to lb", 1, "lb"),
("32 f to c", 0, "°C"), ("23C to F", 73.4, "°F"),
("100°C to °F", 212, "°F"), ("0 c to kelvin", 273.15, "K"),
("2 hours in minutes", 120, "min"), ("1 week as seconds", 604800, "s"),
("1000 milliseconds to s", 1, "s"),
("36 km/h to m/s", 10, "m/s"), ("60 mph to km/h", 96.56064, "km/h"),
("1 square m to square cm", 10000, "cm²"), ("144in² to ft²", 1, "ft²"),
("2 liters to ml", 2000, "mL"), ("1 US gallon to l", 3.785411784, "L"),
("1 tbsp to tsp", 3, "tsp"), ("1 cup to fl oz", 8, "fl oz"),
]
for (query, expected, unit) in examples {
let result = try #require(Calculator.evaluate(query), "\(query)")
let copied = try #require(result.copyValue, "\(query): \(result.displayValue)")
#expect(copied.hasSuffix(" \(unit)"), "\(query)")
let value = try #require(Double(copied.dropLast(unit.count + 1)), "\(query)")
// Foundation's built-in physical-unit coefficients have limited decimal precision.
#expect(abs(value - expected) < max(1e-9, abs(expected) * 1e-5), "\(query): \(copied)")
}
#expect(Calculator.evaluate("32 f to c")?.copyValue == "0 °C")
#expect(Calculator.evaluate("0 c to f")?.copyValue == "32 °F")
}
@Test
func calculatorDistinguishesDecimalBinaryAndBitsFromBytes() {
let examples: [(String, String)] = [
("1 GB to MB", "1000 MB"), ("1GB to B", "1000000000 B"),
("1 GiB to MiB", "1024 MiB"), ("1 KiB to B", "1024 B"),
("1 MB to KiB", "976.5625 KiB"), ("8b to B", "1 B"),
("1 B to b", "8 b"), ("1 Mb to MB", "0.125 MB"), ("8 bits to bytes", "1 B"),
("1 kilobyte to bytes", "1000 B"), ("1 mebibyte to KiB", "1024 KiB"),
("8 megabits to MB", "1 MB"),
]
for (query, expected) in examples {
#expect(Calculator.evaluate(query)?.copyValue == expected, "\(query)")
}
}
@Test
func calculatorLeavesApplicationQueriesAndIncompleteInputAlone() {
for query in ["", "Safari", "Xcode", "1Password", "Calculator", "123", "-123", "1e3",
"2 +", "sqrt(", "2^", "1e+", "(1+2", "2 3", "2 ** 3", "pi app", "sin 2",
"alert(1)", "foo(2)", "1/0 Safari", "10 km to", "10 km to widgets", "2 of 3", "20 off 80"] {
#expect(Calculator.evaluate(query) == nil, "\(query)")
}
}
@Test
func calculatorReportsRecognizedErrorsWithoutCopyableResults() throws {
for query in ["1/0", "1 mod 0", "sqrt(-1)", "ln(0)", "log(-2)", "(-2)^0.5",
"1e308 * 1e308", "1e999 + 1", "1e308 TB to B", "10 m to kg", "1 GB to seconds"] {
let result = try #require(Calculator.evaluate(query), "\(query)")
#expect(result.copyValue == nil, "\(query)")
}
}
@Test
func calculatorBoundsLiveSearchInputAndRecursiveGrammar() {
#expect(Calculator.evaluate(String(repeating: "(", count: 70) + "1" + String(repeating: ")", count: 70)) == nil)
#expect(Calculator.evaluate(String(repeating: "(", count: 16) + "1" + String(repeating: ")", count: 16)) == nil)
#expect(Calculator.evaluate(String(repeating: "-", count: 70) + "1 + 2") == nil)
#expect(Calculator.evaluate(String(repeating: "2^", count: 70) + "2") == nil)
#expect(Calculator.evaluate(String(repeating: "1+", count: 300) + "1") == nil)
#expect(Calculator.evaluate(String(repeating: "(", count: 15) + "1+2" + String(repeating: ")", count: 15))?.copyValue == "3")
}