/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include #include namespace facebook::react { TEST(CSSSyntaxParser, simple) { CSSSyntaxParser parser{"1px solid black"}; auto pxValue = parser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Dimension); EXPECT_EQ(token.numericValue(), 1.0f); EXPECT_EQ(token.unit(), "px"); return token.numericValue(); }); EXPECT_EQ(pxValue, 1.0f); auto identValue = parser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "solid"); return token.stringValue(); }); EXPECT_EQ(identValue, "solid"); auto identValue2 = parser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "black"); return token.stringValue(); }); EXPECT_EQ(identValue2, "black"); } TEST(CSSSyntaxParser, single_function_no_args) { CSSSyntaxParser parser{"foo()"}; auto funcName = parser.consumeComponentValue( [](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) { EXPECT_EQ(function.name, "foo"); return function.name; auto hasMoreTokens = blockParser.consumeComponentValue( [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(hasMoreTokens); }); EXPECT_EQ(funcName, "foo"); } TEST(CSSSyntaxParser, single_function_with_whitespace_delimited_args) { CSSSyntaxParser parser{"foo( a b c)"}; auto funcArgs = parser.consumeComponentValue>( [&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) { EXPECT_EQ(function.name, "foo"); std::vector args; args.emplace_back(blockParser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "a"); return token.stringValue(); })); args.emplace_back(blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "b"); return token.stringValue(); })); args.emplace_back(blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "c"); return token.stringValue(); })); auto hasMoreTokens = blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(hasMoreTokens); return args; }); std::vector expectedArgs{"a", "b", "c"}; EXPECT_EQ(funcArgs, expectedArgs); } TEST(CSSSyntaxParser, single_function_with_comma_delimited_args) { CSSSyntaxParser parser{"rgb(100, 200, 50 )"}; auto funcArgs = parser.consumeComponentValue>( [&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) { EXPECT_EQ(function.name, "rgb"); std::array rgb{}; rgb[0] = blockParser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Number); EXPECT_EQ(token.numericValue(), 100); return static_cast(token.numericValue()); }); rgb[1] = blockParser.consumeComponentValue( CSSDelimiter::Comma, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Number); EXPECT_EQ(token.numericValue(), 200); return static_cast(token.numericValue()); }); rgb[2] = blockParser.consumeComponentValue( CSSDelimiter::Comma, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Number); EXPECT_EQ(token.numericValue(), 50); return static_cast(token.numericValue()); }); auto hasMoreTokens = blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(hasMoreTokens); return rgb; }); std::array expectedArgs{{100, 200, 50}}; EXPECT_EQ(funcArgs, expectedArgs); } TEST(CSSSyntaxParser, single_function_with_mixed_delimited_args) { CSSSyntaxParser parser{"rgb(100, 200 50 )"}; auto funcArgs = parser.consumeComponentValue>( [&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) { EXPECT_EQ(function.name, "rgb"); std::array rgb{}; rgb[0] = blockParser.consumeComponentValue( CSSDelimiter::None, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Number); EXPECT_EQ(token.numericValue(), 100); return static_cast(token.numericValue()); }); rgb[1] = blockParser.consumeComponentValue( CSSDelimiter::CommaOrWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Number); EXPECT_EQ(token.numericValue(), 200); return static_cast(token.numericValue()); }); rgb[2] = blockParser.consumeComponentValue( CSSDelimiter::CommaOrWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Number); EXPECT_EQ(token.numericValue(), 50); return static_cast(token.numericValue()); }); auto hasMoreTokens = blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(hasMoreTokens); return rgb; }); std::array expectedArgs{{100, 200, 50}}; EXPECT_EQ(funcArgs, expectedArgs); } TEST(CSSSyntaxParser, complex) { CSSSyntaxParser parser{"foo(a bar())baz() 12px"}; auto fooFunc = parser.consumeComponentValue( [&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) { EXPECT_EQ(function.name, "foo"); auto identArg = blockParser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "a"); return token.stringValue(); }); EXPECT_EQ(identArg, "a"); auto barFunc = blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [&](const CSSFunctionBlock& function, CSSSyntaxParser& nestedBlockParser) { EXPECT_EQ(function.name, "bar"); auto hasMoreTokens = nestedBlockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(hasMoreTokens); return function.name; }); EXPECT_EQ(barFunc, "bar"); auto hasMoreTokens = blockParser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(hasMoreTokens); return function.name; }); EXPECT_EQ(fooFunc, "foo"); auto bazFunc = parser.consumeComponentValue( [&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) { EXPECT_EQ(function.name, "baz"); return function.name; }); EXPECT_EQ(bazFunc, "baz"); auto pxValue = parser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Dimension); EXPECT_EQ(token.numericValue(), 12.0f); EXPECT_EQ(token.unit(), "px"); return token.numericValue(); }); EXPECT_EQ(pxValue, 12.0f); } TEST(CSSSyntaxParser, unterminated_functions) { EXPECT_FALSE(CSSSyntaxParser{"foo("}.consumeComponentValue( [](const CSSFunctionBlock&, CSSSyntaxParser& /*blockParser*/) { return true; })); EXPECT_FALSE(CSSSyntaxParser{"foo(a bar()baz()"}.consumeComponentValue( [](const CSSFunctionBlock&, CSSSyntaxParser& /*blockParser*/) { return true; })); } TEST(CSSSyntaxParser, simple_blocks) { CSSSyntaxParser parser1{"(a)"}; auto identValue = parser1.consumeComponentValue( [&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenParen); return blockParser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); return token.stringValue(); }); }); EXPECT_EQ(identValue, "a"); CSSSyntaxParser parser2{"[b ]"}; auto identValue2 = parser2.consumeComponentValue( [&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenSquare); return blockParser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); return token.stringValue(); }); }); EXPECT_EQ(identValue2, "b"); CSSSyntaxParser parser3{"{c}"}; auto identValue3 = parser3.consumeComponentValue( [&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenCurly); return blockParser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); return token.stringValue(); }); }); EXPECT_EQ(identValue3, "c"); } TEST(CSSSyntaxParser, unterminated_simple_blocks) { CSSSyntaxParser parser1{"(a"}; auto identValue = parser1.consumeComponentValue( [&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenParen); return blockParser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); return token.stringValue(); }); }); EXPECT_EQ(identValue, ""); CSSSyntaxParser parser2{"[b "}; auto identValue2 = parser2.consumeComponentValue( [&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenSquare); return blockParser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); return token.stringValue(); }); }); EXPECT_EQ(identValue2, ""); CSSSyntaxParser parser3{"{c"}; auto identValue3 = parser3.consumeComponentValue( [&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenCurly); return blockParser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); return token.stringValue(); }); }); EXPECT_EQ(identValue3, ""); } TEST(CSSSyntaxParser, unconsumed_function_args) { CSSSyntaxParser parser{"foo(a)"}; auto funcValue = parser.consumeComponentValue>( [&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) { EXPECT_EQ(function.name, "foo"); return function.name; }); EXPECT_EQ(funcValue, std::nullopt); } TEST(CSSSyntaxParser, whitespace_surrounding_function_args) { CSSSyntaxParser parser{"foo( a )"}; auto funcValue = parser.consumeComponentValue( [&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) { EXPECT_EQ(function.name, "foo"); auto identArg = blockParser.consumeComponentValue( CSSDelimiter::None, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "a"); return token.stringValue(); }); EXPECT_EQ(identArg, "a"); return function.name; }); EXPECT_EQ(funcValue, "foo"); } TEST(CSSSyntaxParser, unconsumed_simple_block_args) { CSSSyntaxParser parser{"{a}"}; auto funcValue = parser.consumeComponentValue>( [&](const CSSSimpleBlock& block, CSSSyntaxParser& /*blockParser*/) { EXPECT_EQ(block.openBracketType, CSSTokenType::OpenCurly); return block.openBracketType; }); EXPECT_EQ(funcValue, std::nullopt); } TEST(CSSSyntaxParser, solidus_delimiter) { CSSSyntaxParser parser{"foo / bar"}; auto identValue = parser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "foo"); return token.stringValue(); }); EXPECT_EQ(identValue, "foo"); auto identValue2 = parser.consumeComponentValue( CSSDelimiter::Solidus, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "bar"); return token.stringValue(); }); EXPECT_EQ(identValue2, "bar"); } TEST(CSSSyntaxParser, solidus_delimiter_not_present) { CSSSyntaxParser parser{"foo bar"}; auto identValue = parser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "foo"); return token.stringValue(); }); EXPECT_EQ(identValue, "foo"); auto identValue2 = parser.consumeComponentValue( CSSDelimiter::Solidus, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(identValue2); } TEST(CSSSyntaxParser, required_whitespace_not_present) { CSSSyntaxParser parser{"foo/"}; auto identValue = parser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "foo"); return token.stringValue(); }); EXPECT_EQ(identValue, "foo"); auto delimValue1 = parser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(delimValue1); auto delimValue2 = parser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Delim); EXPECT_EQ(token.stringValue(), "/"); return token.stringValue(); }); EXPECT_EQ(delimValue2, "/"); } TEST(CSSSyntaxParser, solidus_or_whitespace) { CSSSyntaxParser parser{"foo bar / baz potato, papaya"}; auto identValue1 = parser.consumeComponentValue( CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "foo"); return token.stringValue(); }); EXPECT_EQ(identValue1, "foo"); auto identValue2 = parser.consumeComponentValue( CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "bar"); return token.stringValue(); }); EXPECT_EQ(identValue2, "bar"); auto identValue3 = parser.consumeComponentValue( CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "baz"); return token.stringValue(); }); EXPECT_EQ(identValue3, "baz"); auto identValue4 = parser.consumeComponentValue( CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "potato"); return token.stringValue(); }); EXPECT_EQ(identValue4, "potato"); auto delimValue1 = parser.consumeComponentValue( CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) { return true; }); EXPECT_FALSE(delimValue1); } TEST(CSSSyntaxParser, delimeter_not_consumed_when_no_component_value) { CSSSyntaxParser parser{"foo ,"}; auto identValue = parser.consumeComponentValue( [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Ident); EXPECT_EQ(token.stringValue(), "foo"); return token.stringValue(); }); EXPECT_EQ(identValue, "foo"); auto identValue2 = parser.consumeComponentValue( CSSDelimiter::Comma, [](const CSSPreservedToken& /*token*/) { return true; }); EXPECT_FALSE(identValue2); auto hasComma = parser.consumeComponentValue( CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { EXPECT_EQ(token.type(), CSSTokenType::Comma); return true; }); EXPECT_TRUE(hasComma); } TEST(CSSSyntaxParser, component_value_not_consumed_on_visitor_failure) { CSSSyntaxParser parser{"foo"}; bool visitor1Attempted = false; bool visitor1Ret = parser.consumeComponentValue([&](const CSSPreservedToken& token) { EXPECT_EQ(token.stringValue(), "foo"); visitor1Attempted = true; return false; }); EXPECT_TRUE(visitor1Attempted); EXPECT_FALSE(visitor1Ret); bool visitor2Attempted = false; parser.consumeComponentValue([&](const CSSPreservedToken& token) { EXPECT_EQ(token.stringValue(), "foo"); visitor2Attempted = true; return true; }); EXPECT_TRUE(visitor2Attempted); EXPECT_TRUE(visitor2Attempted); bool visitor3Attempted = false; bool visitor3Ret = parser.consumeComponentValue([&](const CSSPreservedToken& token) { visitor3Attempted = true; return true; }); EXPECT_FALSE(visitor3Attempted); EXPECT_FALSE(visitor3Ret); } } // namespace facebook::react