Files
create/tests/src/Gui/StyleParameters/ParserTest.cpp
Kacper Donat 322ed2c7bc Gui: Rename StyleParameters::Length to StyleParameters::Numeric
This change gives a better name to the parameter type that is used to
store numeric values. Before it was called length, but it can store
other quantities as well so the name is no longer fitting.
2025-08-12 21:25:41 +02:00

618 lines
19 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <gtest/gtest.h>
#include <Gui/Utilities.h>
#include <Gui/StyleParameters/Parser.h>
#include <Gui/StyleParameters/ParameterManager.h>
using namespace Gui::StyleParameters;
class ParserTest: public ::testing::Test
{
protected:
void SetUp() override
{
// Create a simple parameter manager for testing
auto source = std::make_unique<InMemoryParameterSource>(
std::list<Parameter> {
{"TestParam", "10px"},
{"TestColor", "#ff0000"},
{"TestNumber", "5"},
},
ParameterSource::Metadata {"Test Source"});
manager.addSource(source.get());
sources.push_back(std::move(source));
}
Gui::StyleParameters::ParameterManager manager;
std::vector<std::unique_ptr<ParameterSource>> sources;
};
// Test number parsing
TEST_F(ParserTest, ParseNumbers)
{
{
Parser parser("42");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 42.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10.5px");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 10.5);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("2.5em");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 2.5);
EXPECT_EQ(length.unit, "em");
}
{
Parser parser("100%");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 100.0);
EXPECT_EQ(length.unit, "%");
}
}
// Test color parsing
TEST_F(ParserTest, ParseColors)
{
{
Parser parser("#ff0000");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 1);
EXPECT_EQ(color.g, 0);
EXPECT_EQ(color.b, 0);
}
{
Parser parser("#00ff00");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 0);
EXPECT_EQ(color.g, 1);
EXPECT_EQ(color.b, 0);
}
{
Parser parser("#0000ff");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 0);
EXPECT_EQ(color.g, 0);
EXPECT_EQ(color.b, 1);
}
{
Parser parser("rgb(255, 0, 0)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 1);
EXPECT_EQ(color.g, 0);
EXPECT_EQ(color.b, 0);
}
{
Parser parser("rgba(255, 0, 0, 128)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_DOUBLE_EQ(color.r, 1);
EXPECT_DOUBLE_EQ(color.g, 0);
EXPECT_DOUBLE_EQ(color.b, 0);
EXPECT_NEAR(color.a, 128 / 255.0, 1e-6);
}
}
// Test parameter reference parsing
TEST_F(ParserTest, ParseParameterReferences)
{
{
Parser parser("@TestParam");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 10.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("@TestColor");
auto expr = parser.parse();
auto result = expr->evaluate({.manager = &manager, .context = {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 1);
EXPECT_EQ(color.g, 0);
EXPECT_EQ(color.b, 0);
}
{
Parser parser("@TestNumber");
auto expr = parser.parse();
auto result = expr->evaluate({.manager = &manager, .context = {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 5.0);
EXPECT_EQ(length.unit, "");
}
}
// Test arithmetic operations
TEST_F(ParserTest, ParseArithmeticOperations)
{
{
Parser parser("10 + 5");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 15.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10px + 5px");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 15.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("10 - 5");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 5.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10px - 5px");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 5.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("10 * 5");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 50.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10px * 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 20.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("10 / 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 5.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10px / 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 5.0);
EXPECT_EQ(length.unit, "px");
}
}
// Test complex expressions
TEST_F(ParserTest, ParseComplexExpressions)
{
{
Parser parser("(10 + 5) * 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 30.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("(10px + 5px) * 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 30.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("@TestParam + 5px");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 15.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("@TestParam * @TestNumber");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 50.0);
EXPECT_EQ(length.unit, "px");
}
}
// Test unary operations
TEST_F(ParserTest, ParseUnaryOperations)
{
{
Parser parser("+10");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 10.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("-10");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, -10.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("-10px");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, -10.0);
EXPECT_EQ(length.unit, "px");
}
}
// Test function calls
TEST_F(ParserTest, ParseFunctionCalls)
{
{
Parser parser("lighten(#ff0000, 20)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result).asValue<QColor>();
// The result should be lighter than the original red
EXPECT_GT(color.lightness(), QColor(0xff0000).lightness());
}
{
Parser parser("darken(#ff0000, 20)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result).asValue<QColor>();
// The result should be darker than the original red
EXPECT_LT(color.lightness(), QColor(0xff0000).lightness());
}
{
Parser parser("lighten(@TestColor, 20)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result).asValue<QColor>();
// The result should be lighter than the original red
EXPECT_GT(color.lightness(), QColor(0xff0000).lightness());
}
}
// Test error cases
TEST_F(ParserTest, ParseErrors)
{
// Invalid color format
EXPECT_THROW(
{
Parser parser("#invalid");
parser.parse();
},
Base::ParserError);
// Invalid RGB format
EXPECT_THROW(
{
Parser parser("rgb(invalid)");
parser.parse();
},
Base::ParserError);
// Missing closing parenthesis
EXPECT_THROW(
{
Parser parser("(10 + 5");
parser.parse();
},
Base::ParserError);
// Invalid function
EXPECT_THROW(
{
Parser parser("invalid()");
auto expr = parser.parse();
expr->evaluate({&manager, {}});
},
Base::ExpressionError);
// Division by zero
EXPECT_THROW(
{
Parser parser("10 / 0");
auto expr = parser.parse();
expr->evaluate({&manager, {}});
},
Base::RuntimeError);
// Unit mismatch
EXPECT_THROW(
{
Parser parser("10px + 5em");
auto expr = parser.parse();
expr->evaluate({&manager, {}});
},
Base::RuntimeError);
// Unary operation on color
EXPECT_THROW(
{
Parser parser("-@TestColor");
auto expr = parser.parse();
expr->evaluate({&manager, {}});
},
Base::ExpressionError);
// Function with wrong number of arguments
EXPECT_THROW(
{
Parser parser("lighten(#ff0000)");
auto expr = parser.parse();
expr->evaluate({&manager, {}});
},
Base::ExpressionError);
// Function with wrong argument type
EXPECT_THROW(
{
Parser parser("lighten(10px, 20)");
auto expr = parser.parse();
expr->evaluate({&manager, {}});
},
Base::ExpressionError);
}
// Test whitespace handling
TEST_F(ParserTest, ParseWhitespace)
{
{
Parser parser(" 10 + 5 ");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 15.0);
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10px+5px");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 15.0);
EXPECT_EQ(length.unit, "px");
}
{
Parser parser("rgb(255,0,0)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 1);
EXPECT_EQ(color.g, 0);
EXPECT_EQ(color.b, 0);
}
}
// Test edge cases
TEST_F(ParserTest, ParseEdgeCases)
{
// Empty input
EXPECT_THROW(
{
Parser parser("");
parser.parse();
},
Base::ParserError);
// Just whitespace
EXPECT_THROW(
{
Parser parser(" ");
parser.parse();
},
Base::ParserError);
// Single number
{
Parser parser("42");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 42.0);
EXPECT_EQ(length.unit, "");
}
// Single color
{
Parser parser("#ff0000");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Base::Color>(result));
auto color = std::get<Base::Color>(result);
EXPECT_EQ(color.r, 1);
EXPECT_EQ(color.g, 0);
EXPECT_EQ(color.b, 0);
}
// Single parameter reference
{
Parser parser("@TestParam");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 10.0);
EXPECT_EQ(length.unit, "px");
}
}
// Test operator precedence
TEST_F(ParserTest, ParseOperatorPrecedence)
{
{
Parser parser("2 + 3 * 4");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 14.0); // 2 + (3 * 4) = 2 + 12 = 14
EXPECT_EQ(length.unit, "");
}
{
Parser parser("10 - 3 * 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 4.0); // 10 - (3 * 2) = 10 - 6 = 4
EXPECT_EQ(length.unit, "");
}
{
Parser parser("20 / 4 + 3");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 8.0); // (20 / 4) + 3 = 5 + 3 = 8
EXPECT_EQ(length.unit, "");
}
}
// Test nested parentheses
TEST_F(ParserTest, ParseNestedParentheses)
{
{
Parser parser("((2 + 3) * 4)");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 20.0); // (5) * 4 = 20
EXPECT_EQ(length.unit, "");
}
{
Parser parser("(10 - (3 + 2)) * 2");
auto expr = parser.parse();
auto result = expr->evaluate({&manager, {}});
EXPECT_TRUE(std::holds_alternative<Numeric>(result));
auto length = std::get<Numeric>(result);
EXPECT_DOUBLE_EQ(length.value, 10.0); // (10 - 5) * 2 = 5 * 2 = 10
EXPECT_EQ(length.unit, "");
}
}