feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,108 @@
/*
* 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.
*/
#pragma once
#include <algorithm>
#include <bitset>
#include <memory>
#include <type_traits>
#include <vector>
#include <react/debug/react_native_assert.h>
#include <react/renderer/css/CSSParser.h>
#include <react/renderer/css/CSSProperties.h>
namespace facebook::react {
/**
* CSSDeclaredStyle represents the set of style declarations on an element set
* by the user. Users should generally not read from CSSDeclaredStyle directly,
* and should instead use the computed style calculated on ShadowTree commit.
*/
class CSSDeclaredStyle {
public:
template <CSSProp Prop>
void set(const CSSDeclaredValue<Prop>& value) {
using DeclaredValueT = std::remove_cvref_t<CSSDeclaredValue<Prop>>;
static_assert(sizeof(value) <= sizeof(PropMapping::value));
static_assert(std::is_trivially_destructible_v<DeclaredValueT>);
if (specifiedProperties_.test(to_underlying(Prop))) {
auto it = std::lower_bound(
properties_.begin(), properties_.end(), PropMapping{Prop, {}});
react_native_assert(it->prop == Prop);
std::construct_at(
reinterpret_cast<DeclaredValueT*>(it->value.data()), value);
} else {
auto it = std::upper_bound(
properties_.begin(), properties_.end(), PropMapping{Prop, {}});
it = properties_.insert(it, {Prop, {}});
std::construct_at(
reinterpret_cast<DeclaredValueT*>(it->value.data()), value);
specifiedProperties_.set(to_underlying(Prop));
}
}
template <CSSProp Prop>
void set(std::string_view value) {
set<Prop>(parseCSSProp<Prop>(value));
}
/**
* Returns the declared value, represented as the "unset" keyword if never
* specified. Additional shorthands can be provided in order
* of precedence if Prop is unset.
*/
template <CSSProp Prop, CSSProp... ShorthandsT>
CSSDeclaredValue<Prop> get() const {
if (specifiedProperties_.test(to_underlying(Prop))) {
auto it = std::lower_bound(
properties_.begin(), properties_.end(), PropMapping{Prop, {}});
react_native_assert(it->prop == Prop);
CSSDeclaredValue<Prop> value{*std::launder(
reinterpret_cast<const CSSDeclaredValue<Prop>*>(it->value.data()))};
if (value) {
return value;
}
}
if constexpr (sizeof...(ShorthandsT) == 0) {
return {};
} else {
return get<ShorthandsT...>();
}
}
bool operator==(const CSSDeclaredStyle& rhs) const = default;
private:
struct PropMapping {
CSSProp prop;
std::array<
std::byte,
sizeof(CSSValueVariant<
CSSWideKeyword,
CSSKeyword,
CSSLength,
CSSNumber,
CSSPercentage,
CSSRatio>)>
value;
constexpr bool operator<(const PropMapping& rhs) const {
return to_underlying(prop) < to_underlying(rhs.prop);
}
};
std::vector<PropMapping> properties_;
std::bitset<kCSSPropCount> specifiedProperties_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,458 @@
/*
* 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.
*/
#pragma once
#include <cstdint>
#include <optional>
#include <string_view>
#include <react/utils/fnv1a.h>
#include <react/utils/to_underlying.h>
namespace facebook::react {
/**
* One of any predefined CSS keywords.
* https://www.w3.org/TR/css-values-4/#keywords
*/
enum class CSSKeyword : uint8_t {
Absolute,
Auto,
Baseline,
Block,
Center,
Clip,
Column,
ColumnReverse,
Content,
Contents,
Dashed,
Dotted,
Double,
End,
Fixed,
Flex,
FlexEnd,
FlexStart,
Grid,
Groove,
Hidden,
Inherit,
Initial,
Inline,
InlineBlock,
InlineFlex,
InlineGrid,
Inset,
Ltr,
MaxContent,
Medium,
MinContent,
None,
Normal,
NoWrap,
Outset,
Relative,
Ridge,
Row,
RowReverse,
Rtl,
Scroll,
Solid,
SpaceAround,
SpaceBetween,
SpaceEvenly,
Start,
Static,
Sticky,
Stretch,
Thick,
Thin,
Unset,
Visible,
Wrap,
WrapReverse,
};
/**
* Represents a contrained set of CSS keywords.
*/
template <typename T>
concept CSSKeywordSet = std::is_enum_v<T> && std::
is_same_v<std::underlying_type_t<T>, std::underlying_type_t<CSSKeyword>>;
/**
* CSS-wide keywords.
* https://www.w3.org/TR/css-values-4/#common-keywords
*/
enum class CSSWideKeyword : std::underlying_type_t<CSSKeyword> {
Inherit = to_underlying(CSSKeyword::Inherit),
Initial = to_underlying(CSSKeyword::Initial),
Unset = to_underlying(CSSKeyword::Unset),
};
/**
* Defines a concept for whether an enum has a given member.
*/
#define CSS_DEFINE_KEYWORD_CONEPTS(name) \
namespace detail { \
template <typename T> \
concept has##name = (CSSKeywordSet<T> && requires() { T::name; }); \
}
CSS_DEFINE_KEYWORD_CONEPTS(Absolute)
CSS_DEFINE_KEYWORD_CONEPTS(Auto)
CSS_DEFINE_KEYWORD_CONEPTS(Baseline)
CSS_DEFINE_KEYWORD_CONEPTS(Block)
CSS_DEFINE_KEYWORD_CONEPTS(Center)
CSS_DEFINE_KEYWORD_CONEPTS(Clip)
CSS_DEFINE_KEYWORD_CONEPTS(Column)
CSS_DEFINE_KEYWORD_CONEPTS(ColumnReverse)
CSS_DEFINE_KEYWORD_CONEPTS(Content)
CSS_DEFINE_KEYWORD_CONEPTS(Contents)
CSS_DEFINE_KEYWORD_CONEPTS(Dashed)
CSS_DEFINE_KEYWORD_CONEPTS(Dotted)
CSS_DEFINE_KEYWORD_CONEPTS(Double)
CSS_DEFINE_KEYWORD_CONEPTS(End)
CSS_DEFINE_KEYWORD_CONEPTS(Fixed)
CSS_DEFINE_KEYWORD_CONEPTS(Flex)
CSS_DEFINE_KEYWORD_CONEPTS(FlexEnd)
CSS_DEFINE_KEYWORD_CONEPTS(FlexStart)
CSS_DEFINE_KEYWORD_CONEPTS(Grid)
CSS_DEFINE_KEYWORD_CONEPTS(Groove)
CSS_DEFINE_KEYWORD_CONEPTS(Hidden)
CSS_DEFINE_KEYWORD_CONEPTS(Inherit)
CSS_DEFINE_KEYWORD_CONEPTS(Initial)
CSS_DEFINE_KEYWORD_CONEPTS(Inline)
CSS_DEFINE_KEYWORD_CONEPTS(InlineBlock)
CSS_DEFINE_KEYWORD_CONEPTS(InlineFlex)
CSS_DEFINE_KEYWORD_CONEPTS(InlineGrid)
CSS_DEFINE_KEYWORD_CONEPTS(Inset)
CSS_DEFINE_KEYWORD_CONEPTS(Ltr)
CSS_DEFINE_KEYWORD_CONEPTS(MaxContent)
CSS_DEFINE_KEYWORD_CONEPTS(Medium)
CSS_DEFINE_KEYWORD_CONEPTS(MinContent)
CSS_DEFINE_KEYWORD_CONEPTS(None)
CSS_DEFINE_KEYWORD_CONEPTS(Normal)
CSS_DEFINE_KEYWORD_CONEPTS(NoWrap)
CSS_DEFINE_KEYWORD_CONEPTS(Outset)
CSS_DEFINE_KEYWORD_CONEPTS(Relative)
CSS_DEFINE_KEYWORD_CONEPTS(Ridge)
CSS_DEFINE_KEYWORD_CONEPTS(Row)
CSS_DEFINE_KEYWORD_CONEPTS(RowReverse)
CSS_DEFINE_KEYWORD_CONEPTS(Rtl)
CSS_DEFINE_KEYWORD_CONEPTS(Scroll)
CSS_DEFINE_KEYWORD_CONEPTS(Solid)
CSS_DEFINE_KEYWORD_CONEPTS(SpaceAround)
CSS_DEFINE_KEYWORD_CONEPTS(SpaceBetween)
CSS_DEFINE_KEYWORD_CONEPTS(SpaceEvenly)
CSS_DEFINE_KEYWORD_CONEPTS(Start)
CSS_DEFINE_KEYWORD_CONEPTS(Static)
CSS_DEFINE_KEYWORD_CONEPTS(Sticky)
CSS_DEFINE_KEYWORD_CONEPTS(Stretch)
CSS_DEFINE_KEYWORD_CONEPTS(Thick)
CSS_DEFINE_KEYWORD_CONEPTS(Thin)
CSS_DEFINE_KEYWORD_CONEPTS(Unset)
CSS_DEFINE_KEYWORD_CONEPTS(Visible)
CSS_DEFINE_KEYWORD_CONEPTS(Wrap)
CSS_DEFINE_KEYWORD_CONEPTS(WrapReverse)
/**
* Parses an ident token, case-insensitive, into a keyword.
*
* Returns KeywordT::Unset if the ident does not match any entries
* in the keyword-set, or CSS-wide keywords.
*/
template <CSSKeywordSet KeywordT>
constexpr std::optional<KeywordT> parseCSSKeyword(std::string_view ident) {
struct LowerCaseTransform {
constexpr char operator()(char c) const {
if (c >= 'A' && c <= 'Z') {
return c + static_cast<char>('a' - 'A');
}
return c;
}
};
switch (fnv1a<LowerCaseTransform>(ident)) {
case fnv1a("absolute"):
if constexpr (detail::hasAbsolute<KeywordT>) {
return KeywordT::Absolute;
}
break;
case fnv1a("auto"):
if constexpr (detail::hasAuto<KeywordT>) {
return KeywordT::Auto;
}
break;
case fnv1a("baseline"):
if constexpr (detail::hasBaseline<KeywordT>) {
return KeywordT::Baseline;
}
break;
case fnv1a("block"):
if constexpr (detail::hasBlock<KeywordT>) {
return KeywordT::Block;
}
break;
case fnv1a("center"):
if constexpr (detail::hasCenter<KeywordT>) {
return KeywordT::Center;
}
break;
case fnv1a("clip"):
if constexpr (detail::hasClip<KeywordT>) {
return KeywordT::Clip;
}
break;
case fnv1a("column"):
if constexpr (detail::hasColumn<KeywordT>) {
return KeywordT::Column;
}
break;
case fnv1a("column-reverse"):
if constexpr (detail::hasColumnReverse<KeywordT>) {
return KeywordT::ColumnReverse;
}
break;
case fnv1a("content"):
if constexpr (detail::hasContent<KeywordT>) {
return KeywordT::Content;
}
break;
case fnv1a("contents"):
if constexpr (detail::hasContents<KeywordT>) {
return KeywordT::Contents;
}
break;
case fnv1a("dashed"):
if constexpr (detail::hasDashed<KeywordT>) {
return KeywordT::Dashed;
}
break;
case fnv1a("dotted"):
if constexpr (detail::hasDotted<KeywordT>) {
return KeywordT::Dotted;
}
break;
case fnv1a("double"):
if constexpr (detail::hasDouble<KeywordT>) {
return KeywordT::Double;
}
break;
case fnv1a("end"):
if constexpr (detail::hasEnd<KeywordT>) {
return KeywordT::End;
}
break;
case fnv1a("fixed"):
if constexpr (detail::hasFixed<KeywordT>) {
return KeywordT::Fixed;
}
case fnv1a("flex"):
if constexpr (detail::hasFlex<KeywordT>) {
return KeywordT::Flex;
}
break;
case fnv1a("flex-end"):
if constexpr (detail::hasFlexEnd<KeywordT>) {
return KeywordT::FlexEnd;
}
break;
case fnv1a("flex-start"):
if constexpr (detail::hasFlexStart<KeywordT>) {
return KeywordT::FlexStart;
}
break;
case fnv1a("grid"):
if constexpr (detail::hasGrid<KeywordT>) {
return KeywordT::Grid;
}
break;
case fnv1a("groove"):
if constexpr (detail::hasGroove<KeywordT>) {
return KeywordT::Groove;
}
break;
case fnv1a("hidden"):
if constexpr (detail::hasHidden<KeywordT>) {
return KeywordT::Hidden;
}
break;
case fnv1a("inherit"):
if constexpr (detail::hasInherit<KeywordT>) {
return KeywordT::Inherit;
}
break;
case fnv1a("inline"):
if constexpr (detail::hasInline<KeywordT>) {
return KeywordT::Inline;
}
break;
case fnv1a("inline-block"):
if constexpr (detail::hasInlineBlock<KeywordT>) {
return KeywordT::InlineBlock;
}
break;
case fnv1a("inline-flex"):
if constexpr (detail::hasInlineFlex<KeywordT>) {
return KeywordT::InlineFlex;
}
break;
case fnv1a("inline-grid"):
if constexpr (detail::hasInlineGrid<KeywordT>) {
return KeywordT::InlineGrid;
}
break;
case fnv1a("ltr"):
if constexpr (detail::hasLtr<KeywordT>) {
return KeywordT::Ltr;
}
break;
case fnv1a("max-content"):
if constexpr (detail::hasMaxContent<KeywordT>) {
return KeywordT::MaxContent;
}
break;
case fnv1a("medium"):
if constexpr (detail::hasMedium<KeywordT>) {
return KeywordT::Medium;
}
break;
case fnv1a("min-content"):
if constexpr (detail::hasMinContent<KeywordT>) {
return KeywordT::MinContent;
}
break;
case fnv1a("none"):
if constexpr (detail::hasNone<KeywordT>) {
return KeywordT::None;
}
break;
case fnv1a("normal"):
if constexpr (detail::hasNormal<KeywordT>) {
return KeywordT::Normal;
}
break;
case fnv1a("nowrap"):
if constexpr (detail::hasNoWrap<KeywordT>) {
return KeywordT::NoWrap;
}
break;
case fnv1a("outset"):
if constexpr (detail::hasOutset<KeywordT>) {
return KeywordT::Outset;
}
break;
case fnv1a("relative"):
if constexpr (detail::hasRelative<KeywordT>) {
return KeywordT::Relative;
}
break;
case fnv1a("ridge"):
if constexpr (detail::hasRidge<KeywordT>) {
return KeywordT::Ridge;
}
break;
case fnv1a("row"):
if constexpr (detail::hasRow<KeywordT>) {
return KeywordT::Row;
}
break;
case fnv1a("row-reverse"):
if constexpr (detail::hasRowReverse<KeywordT>) {
return KeywordT::RowReverse;
}
break;
case fnv1a("rtl"):
if constexpr (detail::hasRtl<KeywordT>) {
return KeywordT::Rtl;
}
break;
case fnv1a("space-between"):
if constexpr (detail::hasSpaceBetween<KeywordT>) {
return KeywordT::SpaceBetween;
}
break;
case fnv1a("space-around"):
if constexpr (detail::hasSpaceAround<KeywordT>) {
return KeywordT::SpaceAround;
}
break;
case fnv1a("space-evenly"):
if constexpr (detail::hasSpaceEvenly<KeywordT>) {
return KeywordT::SpaceEvenly;
}
break;
case fnv1a("scroll"):
if constexpr (detail::hasScroll<KeywordT>) {
return KeywordT::Scroll;
}
break;
case fnv1a("solid"):
if constexpr (detail::hasSolid<KeywordT>) {
return KeywordT::Solid;
}
break;
case fnv1a("start"):
if constexpr (detail::hasStart<KeywordT>) {
return KeywordT::Start;
}
case fnv1a("static"):
if constexpr (detail::hasStatic<KeywordT>) {
return KeywordT::Static;
}
break;
case fnv1a("sticky"):
if constexpr (detail::hasSticky<KeywordT>) {
return KeywordT::Sticky;
}
break;
case fnv1a("stretch"):
if constexpr (detail::hasStretch<KeywordT>) {
return KeywordT::Stretch;
}
break;
case fnv1a("thick"):
if constexpr (detail::hasThick<KeywordT>) {
return KeywordT::Thick;
}
break;
case fnv1a("thin"):
if constexpr (detail::hasThin<KeywordT>) {
return KeywordT::Thin;
}
break;
case fnv1a("unset"):
if constexpr (detail::hasUnset<KeywordT>) {
return KeywordT::Unset;
}
break;
case fnv1a("visible"):
if constexpr (detail::hasVisible<KeywordT>) {
return KeywordT::Visible;
}
break;
case fnv1a("wrap"):
if constexpr (detail::hasWrap<KeywordT>) {
return KeywordT::Wrap;
}
break;
case fnv1a("wrap-reverse"):
if constexpr (detail::hasWrapReverse<KeywordT>) {
return KeywordT::WrapReverse;
}
break;
default:
break;
}
return std::nullopt;
}
} // namespace facebook::react

View File

@@ -0,0 +1,165 @@
/*
* 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.
*/
#pragma once
#include <cstdint>
#include <optional>
#include <string_view>
#include <react/utils/fnv1a.h>
namespace facebook::react {
/**
* Unit for the CSS <length> type.
* https://www.w3.org/TR/css-values-4/#lengths
*/
enum class CSSLengthUnit : uint8_t {
Cap,
Ch,
Cm,
Dvb,
Dvh,
Dvi,
Dvmax,
Dvmin,
Dvw,
Em,
Ex,
Ic,
In,
Lh,
Lvb,
Lvh,
Lvi,
Lvmax,
Lvmin,
Lvw,
Mm,
Pc,
Pt,
Px,
Q,
Rcap,
Rch,
Rem,
Rex,
Ric,
Rlh,
Svb,
Svh,
Svi,
Svmax,
Svmin,
Svw,
Vb,
Vh,
Vi,
Vmax,
Vmin,
Vw,
};
/**
* Parses a unit from a dimension token into a CSS length unit.
*/
constexpr std::optional<CSSLengthUnit> parseCSSLengthUnit(
std::string_view unit) {
switch (fnv1a(unit)) {
case fnv1a("cap"):
return CSSLengthUnit::Cap;
case fnv1a("ch"):
return CSSLengthUnit::Ch;
case fnv1a("cm"):
return CSSLengthUnit::Cm;
case fnv1a("dvb"):
return CSSLengthUnit::Dvb;
case fnv1a("dvh"):
return CSSLengthUnit::Dvh;
case fnv1a("dvi"):
return CSSLengthUnit::Dvi;
case fnv1a("dvmax"):
return CSSLengthUnit::Dvmax;
case fnv1a("dvmin"):
return CSSLengthUnit::Dvmin;
case fnv1a("dvw"):
return CSSLengthUnit::Dvw;
case fnv1a("em"):
return CSSLengthUnit::Em;
case fnv1a("ex"):
return CSSLengthUnit::Ex;
case fnv1a("ic"):
return CSSLengthUnit::Ic;
case fnv1a("in"):
return CSSLengthUnit::In;
case fnv1a("lh"):
return CSSLengthUnit::Lh;
case fnv1a("lvb"):
return CSSLengthUnit::Lvb;
case fnv1a("lvh"):
return CSSLengthUnit::Lvh;
case fnv1a("lvi"):
return CSSLengthUnit::Lvi;
case fnv1a("lvmax"):
return CSSLengthUnit::Lvmax;
case fnv1a("lvmin"):
return CSSLengthUnit::Lvmin;
case fnv1a("lvw"):
return CSSLengthUnit::Lvw;
case fnv1a("mm"):
return CSSLengthUnit::Mm;
case fnv1a("pc"):
return CSSLengthUnit::Pc;
case fnv1a("pt"):
return CSSLengthUnit::Pt;
case fnv1a("px"):
return CSSLengthUnit::Px;
case fnv1a("q"):
return CSSLengthUnit::Q;
case fnv1a("rcap"):
return CSSLengthUnit::Rcap;
case fnv1a("rch"):
return CSSLengthUnit::Rch;
case fnv1a("rem"):
return CSSLengthUnit::Rem;
case fnv1a("rex"):
return CSSLengthUnit::Rex;
case fnv1a("ric"):
return CSSLengthUnit::Ric;
case fnv1a("rlh"):
return CSSLengthUnit::Rlh;
case fnv1a("svb"):
return CSSLengthUnit::Svb;
case fnv1a("svh"):
return CSSLengthUnit::Svh;
case fnv1a("svi"):
return CSSLengthUnit::Svi;
case fnv1a("svmax"):
return CSSLengthUnit::Svmax;
case fnv1a("svmin"):
return CSSLengthUnit::Svmin;
case fnv1a("svw"):
return CSSLengthUnit::Svw;
case fnv1a("vb"):
return CSSLengthUnit::Vb;
case fnv1a("vh"):
return CSSLengthUnit::Vh;
case fnv1a("vi"):
return CSSLengthUnit::Vi;
case fnv1a("vmax"):
return CSSLengthUnit::Vmax;
case fnv1a("vmin"):
return CSSLengthUnit::Vmin;
case fnv1a("vw"):
return CSSLengthUnit::Vw;
default:
return std::nullopt;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,220 @@
/*
* 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.
*/
#pragma once
#include <optional>
#include <react/renderer/css/CSSKeywords.h>
#include <react/renderer/css/CSSLengthUnit.h>
#include <react/renderer/css/CSSProperties.h>
#include <react/renderer/css/CSSTokenizer.h>
#include <react/renderer/css/CSSValue.h>
#include <react/utils/PackTraits.h>
namespace facebook::react {
namespace detail {
class CSSParser {
public:
explicit constexpr CSSParser(std::string_view css)
: tokenizer_{css}, currentToken_(tokenizer_.next()) {}
template <CSSDataType... AllowedTypesT>
constexpr CSSValueVariant<AllowedTypesT...> consumeComponentValue() {
using CSSValueT = CSSValueVariant<AllowedTypesT...>;
switch (peek().type()) {
case CSSTokenType::Ident:
if (auto keywordValue =
consumeIdentToken<CSSValueT, AllowedTypesT...>()) {
return *keywordValue;
}
break;
case CSSTokenType::Dimension:
if (auto dimensionValue =
consumeDimensionToken<CSSValueT, AllowedTypesT...>()) {
return *dimensionValue;
}
break;
case CSSTokenType::Percentage:
if (auto percentageValue =
consumePercentageToken<CSSValueT, AllowedTypesT...>()) {
return *percentageValue;
}
break;
case CSSTokenType::Number:
if (auto numberValue =
consumeNumberToken<CSSValueT, AllowedTypesT...>()) {
return *numberValue;
}
break;
default:
break;
}
consumeToken();
return {};
}
constexpr void consumeWhitespace() {
while (peek().type() == CSSTokenType::WhiteSpace) {
consumeToken();
}
}
constexpr bool hasMoreTokens() const {
return peek().type() != CSSTokenType::EndOfFile;
}
private:
constexpr const CSSToken& peek() const {
return currentToken_;
}
constexpr CSSToken consumeToken() {
auto prevToken = currentToken_;
currentToken_ = tokenizer_.next();
return prevToken;
}
template <typename CSSValueT, CSSDataType... AllowedTypesT>
constexpr std::optional<CSSValueT> consumeIdentToken() {
if constexpr (!std::is_same_v<typename CSSValueT::Keyword, void>) {
if (auto keyword = parseCSSKeyword<typename CSSValueT::Keyword>(
peek().stringValue())) {
consumeToken();
return CSSValueT::keyword(*keyword);
}
}
if constexpr (traits::containsType<CSSWideKeyword, AllowedTypesT...>()) {
if (auto keyword =
parseCSSKeyword<CSSWideKeyword>(peek().stringValue())) {
consumeToken();
return CSSValueT::cssWideKeyword(*keyword);
}
}
return {};
}
template <typename CSSValueT, CSSDataType... AllowedTypesT>
constexpr std::optional<CSSValueT> consumeDimensionToken() {
if constexpr (traits::containsType<CSSLength, AllowedTypesT...>()) {
if (auto unit = parseCSSLengthUnit(peek().unit())) {
return CSSValueT::length(consumeToken().numericValue(), *unit);
}
}
return {};
}
template <typename CSSValueT, CSSDataType... AllowedTypesT>
constexpr std::optional<CSSValueT> consumePercentageToken() {
if constexpr (traits::containsType<CSSPercentage, AllowedTypesT...>()) {
return CSSValueT::percentage(consumeToken().numericValue());
}
return {};
}
template <typename CSSValueT, CSSDataType... AllowedTypesT>
constexpr std::optional<CSSValueT> consumeNumberToken() {
// <ratio> = <number [0,∞]> [ / <number [0,∞]> ]?
// https://www.w3.org/TR/css-values-4/#ratio
if constexpr (traits::containsType<CSSRatio, AllowedTypesT...>()) {
if (isValidRatioPart(peek().numericValue())) {
float numerator = consumeToken().numericValue();
float denominator = 1.0;
consumeWhitespace();
if (peek().type() != CSSTokenType::Ident &&
peek().stringValue() == "/") {
consumeToken();
consumeWhitespace();
if (peek().type() == CSSTokenType::Number &&
isValidRatioPart(peek().numericValue())) {
denominator = consumeToken().numericValue();
} else {
return CSSValueT{};
}
}
return CSSValueT::ratio(numerator, denominator);
}
}
if constexpr (traits::containsType<CSSNumber, AllowedTypesT...>()) {
return CSSValueT::number(consumeToken().numericValue());
}
// For zero lengths the unit identifier is optional (i.e. can be
// syntactically represented as the <number> 0). However, if a 0 could
// be parsed as either a <number> or a <length> in a property (such as
// line-height), it must parse as a <number>.
// https://www.w3.org/TR/css-values-4/#lengths
if constexpr (traits::containsType<CSSLength, AllowedTypesT...>()) {
if (peek().numericValue() == 0) {
return CSSValueT::length(
consumeToken().numericValue(), CSSLengthUnit::Px);
}
}
return {};
}
static constexpr bool isValidRatioPart(float value) {
// If either number in the <ratio> is 0 or infinite, it represents a
// degenerate ratio (and, generally, wont do anything).
// https://www.w3.org/TR/css-values-4/#ratios
return value > 0.0f && value != +std::numeric_limits<float>::infinity() &&
value != -std::numeric_limits<float>::infinity();
}
CSSTokenizer tokenizer_;
CSSToken currentToken_;
};
} // namespace detail
/*
* Parse a single CSS component value as a keyword constrained to those
* allowable by KeywordRepresentationT. Returns a default-constructed
* CSSValueVariant (KeywordT::Unset) on syntax error.
*
* https://www.w3.org/TR/css-syntax-3/#parse-component-value
*/
template <CSSDataType... AllowedTypesT>
constexpr void parseCSSComponentValue(
std::string_view css,
CSSValueVariant<AllowedTypesT...>& value) {
detail::CSSParser parser(css);
parser.consumeWhitespace();
auto componentValue = parser.consumeComponentValue<AllowedTypesT...>();
parser.consumeWhitespace();
if (parser.hasMoreTokens()) {
value = {};
} else {
value = componentValue;
}
};
template <CSSDataType... AllowedTypesT>
CSSValueVariant<AllowedTypesT...> parseCSSComponentValue(std::string_view css) {
CSSValueVariant<AllowedTypesT...> value;
parseCSSComponentValue<AllowedTypesT...>(css, value);
return value;
};
template <CSSProp Prop>
constexpr auto parseCSSProp(std::string_view css) {
// For now we only allow parsing props composed of a single component value.
CSSDeclaredValue<Prop> value;
parseCSSComponentValue(css, value);
return value;
}
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,263 @@
/*
* 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.
*/
#pragma once
#include <cmath>
#include <string_view>
namespace facebook::react {
/**
* One of the tokens defined as part of
* https://www.w3.org/TR/css-syntax-3/#tokenizer-definitions
*/
enum class CSSTokenType {
Delim,
Dimension,
EndOfFile,
Ident,
Number,
Percentage,
WhiteSpace,
};
/*
* Represents one of the syntactic CSS tokens as provided by
* https://www.w3.org/TR/css-syntax-3/#tokenization
*/
class CSSToken {
public:
explicit constexpr CSSToken(CSSTokenType type) : type_(type) {}
constexpr CSSToken(CSSTokenType type, std::string_view value)
: type_{type}, stringValue_{value} {}
constexpr CSSToken(CSSTokenType type, float value)
: type_{type}, numericValue_{value} {}
constexpr CSSToken(CSSTokenType type, float value, std::string_view unit)
: type_{type}, numericValue_{value}, unit_{unit} {}
constexpr CSSToken(const CSSToken& other) = default;
constexpr CSSToken(CSSToken&& other) = default;
constexpr CSSToken& operator=(const CSSToken& other) = default;
constexpr CSSToken& operator=(CSSToken&& other) = default;
constexpr CSSTokenType type() const {
return type_;
}
constexpr std::string_view stringValue() const {
return stringValue_;
}
constexpr float numericValue() const {
return numericValue_;
}
constexpr std::string_view unit() const {
return unit_;
}
constexpr bool operator==(const CSSToken& other) const = default;
private:
CSSTokenType type_;
std::string_view stringValue_;
float numericValue_{0.0f};
std::string_view unit_;
};
/**
* A minimal tokenizer for a subset of CSS syntax.
* `auto`).
*
* This is based on the W3C CSS Syntax specification, with simplifications made
* for syntax which React Native does not attempt to support.
* https://www.w3.org/TR/css-syntax-3/#tokenizing-and-parsing
*/
class CSSTokenizer {
public:
explicit constexpr CSSTokenizer(std::string_view characters)
: remainingCharacters_{characters} {}
constexpr CSSToken next() {
// https://www.w3.org/TR/css-syntax-3/#token-diagrams
char nextChar = peek();
if (isWhitespace(nextChar)) {
return consumeWhitespace();
} else if (nextChar == '+') {
if (isDigit(peekNext())) {
return consumeNumeric();
} else {
return consumeDelim();
}
} else if (nextChar == '-') {
if (isDigit(peekNext())) {
return consumeNumeric();
} else {
return consumeDelim();
}
} else if (isDigit(nextChar)) {
return consumeNumeric();
} else if (isIdentStart(nextChar)) {
return consumeIdent();
} else if (nextChar == '\0') {
return CSSToken{CSSTokenType::EndOfFile};
} else {
return consumeDelim();
}
}
private:
constexpr char peek() const {
auto index = position_;
return index >= remainingCharacters_.size() ? '\0'
: remainingCharacters_[index];
}
constexpr char peekNext() const {
auto index = position_ + 1;
return index >= remainingCharacters_.size() ? '\0'
: remainingCharacters_[index];
}
constexpr void advance() {
position_ += 1;
}
constexpr CSSToken consumeDelim() {
advance();
return {CSSTokenType::Delim, consumeRunningValue()};
}
constexpr CSSToken consumeWhitespace() {
while (isWhitespace(peek())) {
advance();
}
consumeRunningValue();
return CSSToken{CSSTokenType::WhiteSpace};
}
constexpr CSSToken consumeNumber() {
// https://www.w3.org/TR/css-syntax-3/#consume-number
// https://www.w3.org/TR/css-syntax-3/#convert-a-string-to-a-number
int32_t signPart = 1.0;
if (peek() == '+' || peek() == '-') {
if (peek() == '-') {
signPart = -1.0;
}
advance();
}
int32_t intPart = 0;
while (isDigit(peek())) {
intPart = intPart * 10 + (peek() - '0');
advance();
}
int32_t fractionalPart = 0;
int32_t fractionDigits = 0;
if (peek() == '.') {
advance();
while (isDigit(peek())) {
fractionalPart = fractionalPart * 10 + (peek() - '0');
fractionDigits++;
advance();
}
}
int32_t exponentSign = 1.0;
int32_t exponentPart = 0;
if (peek() == 'e' || peek() == 'E') {
advance();
if (peek() == '+' || peek() == '-') {
if (peek() == '-') {
exponentSign = -1.0;
}
advance();
}
while (isDigit(peek())) {
exponentPart = exponentPart * 10 + (peek() - '0');
advance();
}
}
float value;
if (exponentPart == 0 && fractionalPart == 0) {
value = static_cast<float>(signPart * intPart);
} else {
value = static_cast<float>(
signPart *
(intPart + (fractionalPart * std::pow(10, -fractionDigits))) *
std::pow(10, exponentSign * exponentPart));
}
consumeRunningValue();
return {CSSTokenType::Number, value};
}
constexpr CSSToken consumeNumeric() {
// https://www.w3.org/TR/css-syntax-3/#consume-numeric-token
auto numberToken = consumeNumber();
if (isIdent(peek())) {
auto ident = consumeIdent();
return {
CSSTokenType::Dimension,
numberToken.numericValue(),
ident.stringValue()};
} else if (peek() == '%') {
advance();
consumeRunningValue();
return {CSSTokenType::Percentage, numberToken.numericValue()};
} else {
return numberToken;
}
}
constexpr CSSToken consumeIdent() {
// https://www.w3.org/TR/css-syntax-3/#consume-an-ident-sequence
while (isIdent(peek())) {
advance();
}
return {CSSTokenType::Ident, consumeRunningValue()};
}
constexpr std::string_view consumeRunningValue() {
auto next = remainingCharacters_.substr(0, position_);
remainingCharacters_ = remainingCharacters_.substr(next.size());
position_ = 0;
return next;
}
static constexpr bool isDigit(char c) {
// https://www.w3.org/TR/css-syntax-3/#digit
return c >= '0' && c <= '9';
}
static constexpr bool isIdentStart(char c) {
// https://www.w3.org/TR/css-syntax-3/#ident-start-code-point
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' ||
static_cast<unsigned char>(c) > 0x80;
}
static constexpr bool isIdent(char c) {
{
// https://www.w3.org/TR/css-syntax-3/#ident-code-point
return isIdentStart(c) || isDigit(c) || c == '-';
}
}
static constexpr bool isWhitespace(char c) {
// https://www.w3.org/TR/css-syntax-3/#whitespace
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
std::string_view remainingCharacters_;
size_t position_{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,277 @@
/*
* 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.
*/
#pragma once
#include <cstdint>
#include <string_view>
#include <type_traits>
#include <react/renderer/css/CSSKeywords.h>
#include <react/renderer/css/CSSLengthUnit.h>
#include <react/utils/PackTraits.h>
namespace facebook::react {
/**
* Represents a CSS component value type.
* https://www.w3.org/TR/css-values-4/#component-types
*/
enum class CSSValueType : uint8_t {
CSSWideKeyword,
Keyword,
Length,
Number,
Percentage,
Ratio,
};
/**
* Concrete representation for a CSS basic data type, or keywords
* https://www.w3.org/TR/css-values-4/#component-types
*/
template <typename T>
concept CSSDataType = std::is_trivially_destructible_v<T> &&
std::is_copy_constructible_v<T> && std::is_default_constructible_v<T>;
#pragma pack(push, 1)
/**
* Representation of CSS <length> data type
* https://www.w3.org/TR/css-values-4/#lengths
*/
struct CSSLength {
float value{};
CSSLengthUnit unit{CSSLengthUnit::Px};
};
#pragma pack(pop)
/**
* Representation of CSS <percentage> data type
* https://www.w3.org/TR/css-values-4/#percentages
*/
struct CSSPercentage {
float value{};
};
/**
* Representation of CSS <number> data type
* https://www.w3.org/TR/css-values-4/#numbers
*/
struct CSSNumber {
float value{};
};
/**
* Representation of CSS <ratio> data type
* https://www.w3.org/TR/css-values-4/#ratios
*/
struct CSSRatio {
float numerator{};
float denominator{};
};
/**
* CSSValueVariant represents a CSS component value:
* https://www.w3.org/TR/css-values-4/#component-types
*
* A CSSValueVariant must be constrained to the set of possible CSS types it may
* encounter. E.g. a dimension which accepts a CSS-wide keywords, "auto" or a
* <length-percentage> would be modeled as
* `CSSValueVariant<CSSAutoKeyord, CSSLength, CSSPercentage>`. This
* allows for efficient storage, and customizing parsing based on the allowed
* set of values.
*/
#pragma pack(push, 1)
template <CSSDataType... AllowedTypesT>
class CSSValueVariant {
template <CSSDataType ValueT>
static constexpr bool canRepresent() {
return traits::containsType<ValueT, AllowedTypesT...>();
}
template <CSSDataType T, CSSDataType... Rest>
static constexpr bool hasKeywordSet() {
if constexpr (CSSKeywordSet<T> && !std::is_same_v<T, CSSWideKeyword>) {
return true;
} else if constexpr (sizeof...(Rest) == 0) {
return false;
} else {
return hasKeywordSet<Rest...>();
}
}
template <typename... T>
struct PackedKeywordSet {
using Type = void;
};
template <typename T, typename... RestT>
struct PackedKeywordSet<T, RestT...> {
using Type = std::conditional_t<
hasKeywordSet<T>(),
T,
typename PackedKeywordSet<RestT...>::Type>;
};
public:
using Keyword = typename PackedKeywordSet<AllowedTypesT...>::Type;
constexpr CSSValueVariant() requires(canRepresent<CSSWideKeyword>())
: CSSValueVariant(CSSValueType::CSSWideKeyword, CSSWideKeyword::Unset) {}
static constexpr CSSValueVariant cssWideKeyword(CSSWideKeyword keyword) {
return CSSValueVariant(
CSSValueType::CSSWideKeyword, CSSWideKeyword{keyword});
}
template <CSSKeywordSet KeywordT>
static constexpr CSSValueVariant keyword(KeywordT keyword) requires(
canRepresent<KeywordT>()) {
return CSSValueVariant(CSSValueType::Keyword, KeywordT{keyword});
}
static constexpr CSSValueVariant length(
float value,
CSSLengthUnit unit) requires(canRepresent<CSSLength>()) {
return CSSValueVariant(CSSValueType::Length, CSSLength{value, unit});
}
static constexpr CSSValueVariant number(float value) requires(
canRepresent<CSSNumber>()) {
return CSSValueVariant(CSSValueType::Number, CSSNumber{value});
}
static constexpr CSSValueVariant percentage(float value) requires(
canRepresent<CSSPercentage>()) {
return CSSValueVariant(CSSValueType::Percentage, CSSPercentage{value});
}
static constexpr CSSValueVariant ratio(
float numerator,
float denominator) requires(canRepresent<CSSRatio>()) {
return CSSValueVariant(
CSSValueType::Ratio, CSSRatio{numerator, denominator});
}
constexpr CSSValueType type() const {
return type_;
}
constexpr CSSWideKeyword getCSSWideKeyword() const
requires(canRepresent<CSSWideKeyword>()) {
return getIf<CSSValueType::CSSWideKeyword, CSSWideKeyword>();
}
constexpr Keyword getKeyword() const
requires(hasKeywordSet<AllowedTypesT...>()) {
return getIf<CSSValueType::Keyword, Keyword>();
}
constexpr CSSLength getLength() const requires(canRepresent<CSSLength>()) {
return getIf<CSSValueType::Length, CSSLength>();
}
constexpr CSSNumber getNumber() const requires(canRepresent<CSSNumber>()) {
return getIf<CSSValueType::Number, CSSNumber>();
}
constexpr CSSPercentage getPercentage() const
requires(canRepresent<CSSPercentage>()) {
return getIf<CSSValueType::Percentage, CSSPercentage>();
}
constexpr CSSRatio getRatio() const requires(canRepresent<CSSRatio>()) {
return getIf<CSSValueType::Ratio, CSSRatio>();
}
constexpr operator bool() const requires(canRepresent<CSSWideKeyword>()) {
return type() != CSSValueType::CSSWideKeyword ||
getCSSWideKeyword() != CSSWideKeyword::Unset;
}
constexpr bool operator==(const CSSValueVariant& other) const {
if (type() != other.type()) {
return false;
}
switch (type()) {
case CSSValueType::CSSWideKeyword:
return getCSSWideKeyword() == other.getCSSWideKeyword();
case CSSValueType::Keyword:
return getKeyword() == other.getKeyword();
case CSSValueType::Length:
return getLength() == other.getLength();
case CSSValueType::Number:
return getNumber() == other.getNumber();
case CSSValueType::Percentage:
return getPercentage() == other.getPercentage();
case CSSValueType::Ratio:
return getRatio() == other.getRatio();
}
return false;
}
private:
template <CSSValueType Type, CSSDataType ValueT>
constexpr ValueT getIf() const {
if (type_ == Type) {
return getFromUnion<ValueT>(data_);
} else {
return ValueT{};
}
}
template <CSSDataType ValueT, CSSDataType... RestT>
union RecursiveUnion {
ValueT first;
RecursiveUnion<RestT...> rest;
};
template <CSSDataType ValueT>
union RecursiveUnion<ValueT> {
ValueT first;
};
template <CSSDataType ValueT, typename UnionT>
constexpr const ValueT& getFromUnion(const UnionT& u) const {
if constexpr (std::is_same_v<ValueT, decltype(u.first)>) {
return u.first;
} else {
return getFromUnion<ValueT>(u.rest);
}
}
template <CSSDataType DataTypeT>
constexpr CSSValueVariant(CSSValueType type, DataTypeT&& value)
: type_{type},
data_{constructIntoUnion<decltype(data_)>(
std::forward<DataTypeT>(value))} {}
template <typename UnionT, CSSDataType DataTypeT>
constexpr UnionT constructIntoUnion(DataTypeT&& value) {
if constexpr (std::is_same_v<DataTypeT, decltype(UnionT{}.first)>) {
return UnionT{.first = std::forward<DataTypeT>(value)};
} else {
return UnionT{
.rest = constructIntoUnion<decltype(UnionT{}.rest)>(
std::forward<DataTypeT>(value))};
}
}
CSSValueType type_;
RecursiveUnion<AllowedTypesT...> data_;
};
#pragma pack(pop)
static_assert(sizeof(CSSValueVariant<CSSKeyword>) == 2);
static_assert(sizeof(CSSValueVariant<CSSKeyword, CSSLength>) == 6);
static_assert(
sizeof(CSSValueVariant<CSSKeyword, CSSLength, CSSPercentage>) == 6);
static_assert(sizeof(CSSValueVariant<CSSKeyword, CSSNumber>) == 5);
static_assert(sizeof(CSSValueVariant<CSSKeyword, CSSNumber, CSSRatio>) == 9);
} // namespace facebook::react

View File

@@ -0,0 +1,151 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSDeclaredStyle.h>
#include <react/renderer/css/CSSParser.h>
namespace facebook::react {
TEST(CSSDeclaredStyle, unset_keyword) {
CSSDeclaredStyle style;
auto value = style.get<CSSProp::FlexDirection>();
EXPECT_EQ(value.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(value.getCSSWideKeyword(), CSSWideKeyword::Unset);
}
TEST(CSSDeclaredStyle, unset_ratio) {
CSSDeclaredStyle style;
auto value = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(value.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(value.getCSSWideKeyword(), CSSWideKeyword::Unset);
}
TEST(CSSDeclaredStyle, set_keyword) {
CSSDeclaredStyle style;
style.set<CSSProp::FlexDirection>("row");
auto value = style.get<CSSProp::FlexDirection>();
EXPECT_EQ(value.type(), CSSValueType::Keyword);
EXPECT_EQ(
value.getKeyword(), CSSAllowedKeywords<CSSProp::FlexDirection>::Row);
}
TEST(CSSDeclaredStyle, set_ratio) {
CSSDeclaredStyle style;
style.set<CSSProp::AspectRatio>("16 / 9");
auto value = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(value.type(), CSSValueType::Ratio);
EXPECT_EQ(value.getRatio().numerator, 16.0f);
EXPECT_EQ(value.getRatio().denominator, 9.0f);
}
TEST(CSSDeclaredStyle, overwrite_ratio) {
CSSDeclaredStyle style;
style.set<CSSProp::AspectRatio>("16 / 9");
auto value1 = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(value1.type(), CSSValueType::Ratio);
EXPECT_EQ(value1.getRatio().numerator, 16.0f);
EXPECT_EQ(value1.getRatio().denominator, 9.0f);
style.set<CSSProp::AspectRatio>("4/3");
auto value2 = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(value2.type(), CSSValueType::Ratio);
EXPECT_EQ(value2.getRatio().numerator, 4.0f);
EXPECT_EQ(value2.getRatio().denominator, 3.0f);
}
TEST(CSSDeclaredStyle, set_multiple) {
CSSDeclaredStyle style;
style.set<CSSProp::FlexDirection>("row");
style.set<CSSProp::AspectRatio>("16 / 9");
auto flexDirection = style.get<CSSProp::FlexDirection>();
EXPECT_EQ(flexDirection.type(), CSSValueType::Keyword);
EXPECT_EQ(
flexDirection.getKeyword(),
CSSAllowedKeywords<CSSProp::FlexDirection>::Row);
auto aspectRatio = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(aspectRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(aspectRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(aspectRatio.getRatio().denominator, 9.0f);
}
TEST(CSSDeclaredStyle, set_multiple_overwrite) {
CSSDeclaredStyle style;
style.set<CSSProp::FlexDirection>("row");
style.set<CSSProp::AspectRatio>("16 / 9");
style.set<CSSProp::FlexDirection>("column");
auto flexDirection = style.get<CSSProp::FlexDirection>();
EXPECT_EQ(flexDirection.type(), CSSValueType::Keyword);
EXPECT_EQ(
flexDirection.getKeyword(),
CSSAllowedKeywords<CSSProp::FlexDirection>::Column);
auto aspectRatio = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(aspectRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(aspectRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(aspectRatio.getRatio().denominator, 9.0f);
}
TEST(CSSDeclaredStyle, set_multiple_reset) {
CSSDeclaredStyle style;
style.set<CSSProp::FlexDirection>("row");
style.set<CSSProp::AspectRatio>("16 / 9");
style.set<CSSProp::FlexDirection>("");
auto flexDirection = style.get<CSSProp::FlexDirection>();
EXPECT_EQ(flexDirection.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(flexDirection.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto aspectRatio = style.get<CSSProp::AspectRatio>();
EXPECT_EQ(aspectRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(aspectRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(aspectRatio.getRatio().denominator, 9.0f);
}
TEST(CSSDeclaredStyle, get_with_precedence) {
CSSDeclaredStyle style;
style.set<CSSProp::Margin>("1px");
auto margin1 = style.get<
CSSProp::MarginStart,
CSSProp::MarginLeft,
CSSProp::MarginInline,
CSSProp::MarginHorizontal,
CSSProp::Margin>();
EXPECT_EQ(margin1.type(), CSSValueType::Length);
EXPECT_EQ(margin1.getLength().value, 1.0f);
EXPECT_EQ(margin1.getLength().unit, CSSLengthUnit::Px);
style.set<CSSProp::MarginLeft>("3px");
auto margin2 = style.get<
CSSProp::MarginStart,
CSSProp::MarginLeft,
CSSProp::MarginInline,
CSSProp::MarginHorizontal,
CSSProp::Margin>();
EXPECT_EQ(margin2.type(), CSSValueType::Length);
EXPECT_EQ(margin2.getLength().value, 3.0f);
EXPECT_EQ(margin2.getLength().unit, CSSLengthUnit::Px);
}
} // namespace facebook::react

View File

@@ -0,0 +1,317 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSParser.h>
namespace facebook::react {
TEST(CSSParser, keyword_values) {
auto emptyValue = parseCSSComponentValue<CSSWideKeyword, CSSKeyword>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto autoValue = parseCSSComponentValue<CSSWideKeyword, CSSKeyword>("auto");
EXPECT_EQ(autoValue.type(), CSSValueType::Keyword);
EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto);
auto autoCapsValue =
parseCSSComponentValue<CSSWideKeyword, CSSKeyword>("AuTO");
EXPECT_EQ(autoCapsValue.type(), CSSValueType::Keyword);
EXPECT_EQ(autoCapsValue.getKeyword(), CSSKeyword::Auto);
auto autoDisallowedValue = parseCSSComponentValue<CSSWideKeyword>("auto");
EXPECT_EQ(autoDisallowedValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(autoDisallowedValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto whitespaceValue =
parseCSSComponentValue<CSSWideKeyword, CSSKeyword>(" flex-start ");
EXPECT_EQ(whitespaceValue.type(), CSSValueType::Keyword);
EXPECT_EQ(whitespaceValue.getKeyword(), CSSKeyword::FlexStart);
auto badIdentValue =
parseCSSComponentValue<CSSWideKeyword, CSSKeyword>("bad");
EXPECT_EQ(badIdentValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(badIdentValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto pxValue = parseCSSComponentValue<CSSWideKeyword>("20px");
EXPECT_EQ(pxValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(pxValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto multiValue = parseCSSComponentValue<CSSWideKeyword>("auto flex-start");
EXPECT_EQ(multiValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(multiValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
}
TEST(CSSParser, length_values) {
auto emptyValue = parseCSSComponentValue<CSSWideKeyword, CSSLength>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto autoValue =
parseCSSComponentValue<CSSWideKeyword, CSSKeyword, CSSLength>("auto");
EXPECT_EQ(autoValue.type(), CSSValueType::Keyword);
EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto);
auto pxValue = parseCSSComponentValue<CSSWideKeyword, CSSLength>("20px");
EXPECT_EQ(pxValue.type(), CSSValueType::Length);
EXPECT_EQ(pxValue.getLength().value, 20.0f);
EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px);
auto cmValue = parseCSSComponentValue<CSSWideKeyword, CSSLength>("453cm");
EXPECT_EQ(cmValue.type(), CSSValueType::Length);
EXPECT_EQ(cmValue.getLength().value, 453.0f);
EXPECT_EQ(cmValue.getLength().unit, CSSLengthUnit::Cm);
auto unitlessZeroValue =
parseCSSComponentValue<CSSWideKeyword, CSSLength>("0");
EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Length);
EXPECT_EQ(unitlessZeroValue.getLength().value, 0.0f);
EXPECT_EQ(unitlessZeroValue.getLength().unit, CSSLengthUnit::Px);
auto unitlessNonzeroValue =
parseCSSComponentValue<CSSWideKeyword, CSSLength>("123");
EXPECT_EQ(unitlessNonzeroValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(unitlessNonzeroValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto pctValue = parseCSSComponentValue<CSSWideKeyword, CSSLength>("-40%");
EXPECT_EQ(pctValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(pctValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
}
TEST(CSSParser, length_percentage_values) {
auto emptyValue =
parseCSSComponentValue<CSSWideKeyword, CSSLength, CSSPercentage>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto autoValue = parseCSSComponentValue<
CSSWideKeyword,
CSSKeyword,
CSSLength,
CSSPercentage>("auto");
EXPECT_EQ(autoValue.type(), CSSValueType::Keyword);
EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto);
auto pxValue =
parseCSSComponentValue<CSSWideKeyword, CSSLength, CSSPercentage>("20px");
EXPECT_EQ(pxValue.type(), CSSValueType::Length);
EXPECT_EQ(pxValue.getLength().value, 20.0f);
EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px);
auto pctValue =
parseCSSComponentValue<CSSWideKeyword, CSSLength, CSSPercentage>("-40%");
EXPECT_EQ(pctValue.type(), CSSValueType::Percentage);
EXPECT_EQ(pctValue.getPercentage().value, -40.0f);
}
TEST(CSSParser, number_values) {
auto emptyValue = parseCSSComponentValue<CSSWideKeyword, CSSNumber>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto inheritValue =
parseCSSComponentValue<CSSWideKeyword, CSSNumber>("inherit");
EXPECT_EQ(inheritValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(inheritValue.getCSSWideKeyword(), CSSWideKeyword::Inherit);
auto pxValue =
parseCSSComponentValue<CSSWideKeyword, CSSKeyword, CSSNumber>("20px");
EXPECT_EQ(pxValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(pxValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto numberValue =
parseCSSComponentValue<CSSWideKeyword, CSSKeyword, CSSNumber>("123.456");
EXPECT_EQ(numberValue.type(), CSSValueType::Number);
EXPECT_EQ(numberValue.getNumber().value, 123.456f);
auto unitlessZeroValue =
parseCSSComponentValue<CSSWideKeyword, CSSLength, CSSNumber>("0");
EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Number);
EXPECT_EQ(unitlessZeroValue.getNumber().value, 0.0f);
}
TEST(CSSParser, ratio_values) {
auto emptyValue = parseCSSComponentValue<CSSWideKeyword, CSSRatio>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto validRatio = parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16/9");
EXPECT_EQ(validRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(validRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(validRatio.getRatio().denominator, 9.0f);
auto validRatioWithWhitespace =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16 / 9");
EXPECT_EQ(validRatioWithWhitespace.type(), CSSValueType::Ratio);
EXPECT_EQ(validRatioWithWhitespace.getRatio().numerator, 16.0f);
EXPECT_EQ(validRatioWithWhitespace.getRatio().denominator, 9.0f);
auto singleNumberRatio =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16");
EXPECT_EQ(singleNumberRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(singleNumberRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(singleNumberRatio.getRatio().denominator, 1.0f);
auto fractionalNumber =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16.5");
EXPECT_EQ(fractionalNumber.type(), CSSValueType::Ratio);
EXPECT_EQ(fractionalNumber.getRatio().numerator, 16.5f);
EXPECT_EQ(fractionalNumber.getRatio().denominator, 1.0f);
auto negativeNumber = parseCSSComponentValue<CSSWideKeyword, CSSRatio>("-16");
EXPECT_EQ(negativeNumber.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(negativeNumber.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto missingDenominator =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16/");
EXPECT_EQ(missingDenominator.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(missingDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto negativeNumerator =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("-16/9");
EXPECT_EQ(negativeNumerator.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(negativeNumerator.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto negativeDenominator =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16/-9");
EXPECT_EQ(negativeDenominator.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(negativeDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto fractionalNumerator =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16.5/9");
EXPECT_EQ(fractionalNumerator.type(), CSSValueType::Ratio);
EXPECT_EQ(fractionalNumerator.getRatio().numerator, 16.5f);
EXPECT_EQ(fractionalNumerator.getRatio().denominator, 9.0f);
auto fractionalDenominator =
parseCSSComponentValue<CSSWideKeyword, CSSRatio>("16/9.5");
EXPECT_EQ(fractionalDenominator.type(), CSSValueType::Ratio);
EXPECT_EQ(fractionalDenominator.getRatio().numerator, 16.0f);
EXPECT_EQ(fractionalDenominator.getRatio().denominator, 9.5f);
auto degenerateRatio = parseCSSComponentValue<CSSWideKeyword, CSSRatio>("0");
EXPECT_EQ(degenerateRatio.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(degenerateRatio.getCSSWideKeyword(), CSSWideKeyword::Unset);
}
TEST(CSSParser, number_ratio_values) {
auto emptyValue =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto validRatio =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16/9");
EXPECT_EQ(validRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(validRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(validRatio.getRatio().denominator, 9.0f);
auto validRatioWithWhitespace =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16 / 9");
EXPECT_EQ(validRatioWithWhitespace.type(), CSSValueType::Ratio);
EXPECT_EQ(validRatioWithWhitespace.getRatio().numerator, 16.0f);
EXPECT_EQ(validRatioWithWhitespace.getRatio().denominator, 9.0f);
auto singleNumberRatio =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16");
EXPECT_EQ(singleNumberRatio.type(), CSSValueType::Ratio);
EXPECT_EQ(singleNumberRatio.getRatio().numerator, 16.0f);
EXPECT_EQ(singleNumberRatio.getRatio().denominator, 1.0f);
auto fractionalNumber =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16.5");
EXPECT_EQ(fractionalNumber.type(), CSSValueType::Ratio);
EXPECT_EQ(fractionalNumber.getRatio().numerator, 16.5f);
EXPECT_EQ(singleNumberRatio.getRatio().denominator, 1.0f);
auto negativeNumber =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("-16");
EXPECT_EQ(negativeNumber.type(), CSSValueType::Number);
EXPECT_EQ(negativeNumber.getNumber().value, -16.0f);
auto missingDenominator =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16/");
EXPECT_EQ(missingDenominator.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(missingDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto negativeNumerator =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("-16/9");
EXPECT_EQ(negativeNumerator.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(negativeNumerator.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto negativeDenominator =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16/-9");
EXPECT_EQ(negativeDenominator.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(negativeDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto fractionalNumerator =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16.5/9");
EXPECT_EQ(fractionalNumerator.type(), CSSValueType::Ratio);
EXPECT_EQ(fractionalNumerator.getRatio().numerator, 16.5f);
EXPECT_EQ(fractionalNumerator.getRatio().denominator, 9.0f);
auto fractionalDenominator =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("16/9.5");
EXPECT_EQ(fractionalDenominator.type(), CSSValueType::Ratio);
EXPECT_EQ(fractionalDenominator.getRatio().numerator, 16.0f);
EXPECT_EQ(fractionalDenominator.getRatio().denominator, 9.5f);
auto degenerateRatio =
parseCSSComponentValue<CSSWideKeyword, CSSNumber, CSSRatio>("0");
EXPECT_EQ(degenerateRatio.type(), CSSValueType::Number);
EXPECT_EQ(degenerateRatio.getNumber().value, 0.0f);
}
TEST(CSSParser, parse_prop) {
auto emptyValue = parseCSSProp<CSSProp::Width>("");
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto numberWidthValue = parseCSSProp<CSSProp::Width>("50px");
EXPECT_EQ(numberWidthValue.type(), CSSValueType::Length);
EXPECT_EQ(numberWidthValue.getLength().value, 50.0f);
EXPECT_EQ(numberWidthValue.getLength().unit, CSSLengthUnit::Px);
auto percentWidthValue = parseCSSProp<CSSProp::Width>("50%");
EXPECT_EQ(percentWidthValue.type(), CSSValueType::Percentage);
EXPECT_EQ(percentWidthValue.getPercentage().value, 50.0f);
auto autoWidthValue = parseCSSProp<CSSProp::Width>("auto");
EXPECT_EQ(autoWidthValue.type(), CSSValueType::Keyword);
EXPECT_EQ(
autoWidthValue.getKeyword(), CSSAllowedKeywords<CSSProp::Width>::Auto);
auto invalidWidthValue = parseCSSProp<CSSProp::Width>("50");
EXPECT_EQ(invalidWidthValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(invalidWidthValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto invalidKeywordValue = parseCSSProp<CSSProp::Width>("flex-start");
EXPECT_EQ(invalidKeywordValue.type(), CSSValueType::CSSWideKeyword);
EXPECT_EQ(invalidKeywordValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
auto keywordlessValue = parseCSSProp<CSSProp::BorderRadius>("50px");
EXPECT_EQ(keywordlessValue.type(), CSSValueType::Length);
EXPECT_EQ(keywordlessValue.getLength().value, 50.0f);
EXPECT_EQ(keywordlessValue.getLength().unit, CSSLengthUnit::Px);
}
TEST(CSSParser, parse_keyword_prop_constexpr) {
constexpr auto rowValue = parseCSSProp<CSSProp::FlexDirection>("row");
EXPECT_EQ(rowValue.type(), CSSValueType::Keyword);
EXPECT_EQ(
rowValue.getKeyword(), CSSAllowedKeywords<CSSProp::FlexDirection>::Row);
}
TEST(CSSParser, parse_length_prop_constexpr) {
constexpr auto pxValue = parseCSSProp<CSSProp::BorderWidth>("2px");
EXPECT_EQ(pxValue.type(), CSSValueType::Length);
EXPECT_EQ(pxValue.getLength().value, 2.0f);
EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px);
}
} // namespace facebook::react

View File

@@ -0,0 +1,154 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSTokenizer.h>
namespace facebook::react {
static void expectTokens(
std::string_view characters,
std::initializer_list<CSSToken> expectedTokens) {
CSSTokenizer tokenizer{characters};
for (const auto& expectedToken : expectedTokens) {
auto nextToken = tokenizer.next();
EXPECT_EQ(nextToken.type(), expectedToken.type());
EXPECT_EQ(nextToken.stringValue(), expectedToken.stringValue());
EXPECT_EQ(nextToken.numericValue(), expectedToken.numericValue());
EXPECT_EQ(nextToken.unit(), expectedToken.unit());
EXPECT_EQ(nextToken, expectedToken);
}
}
TEST(CSSTokenizer, eof_values) {
expectTokens("", {CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, whitespace_values) {
expectTokens(
" ",
{CSSToken{CSSTokenType::WhiteSpace}, CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
" \t",
{CSSToken{CSSTokenType::WhiteSpace}, CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"\n \t",
{CSSToken{CSSTokenType::WhiteSpace}, CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, ident_values) {
expectTokens(
"auto",
{CSSToken{CSSTokenType::Ident, "auto"},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"inset auto left",
{CSSToken{CSSTokenType::Ident, "inset"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Ident, "auto"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Ident, "left"},
CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, number_values) {
expectTokens(
"12",
{CSSToken{CSSTokenType::Number, 12.0f},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"-5",
{CSSToken{CSSTokenType::Number, -5.0f},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"123.0",
{CSSToken{CSSTokenType::Number, 123.0f},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"4.2E-1",
{CSSToken{CSSTokenType::Number, 4.2e-1},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"6e-10",
{CSSToken{CSSTokenType::Number, 6e-10f},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"+81.07e+0",
{CSSToken{CSSTokenType::Number, +81.07e+0},
CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, dimension_values) {
expectTokens(
"12px",
{CSSToken{CSSTokenType::Dimension, 12.0f, "px"},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"463.2abc",
{CSSToken{CSSTokenType::Dimension, 463.2, "abc"},
CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, percent_values) {
expectTokens(
"12%",
{CSSToken{CSSTokenType::Percentage, 12.0f},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"-28.5%",
{CSSToken{CSSTokenType::Percentage, -28.5f},
CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, mixed_values) {
expectTokens(
"12px -100vh",
{CSSToken{CSSTokenType::Dimension, 12.0f, "px"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Dimension, -100.0f, "vh"},
CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, ratio_values) {
expectTokens(
"16 / 9",
{CSSToken{CSSTokenType::Number, 16.0f},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Delim, "/"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Number, 9.0f},
CSSToken{CSSTokenType::EndOfFile}});
}
TEST(CSSTokenizer, invalid_values) {
expectTokens(
"100*",
{CSSToken{CSSTokenType::Number, 100.0f},
CSSToken{CSSTokenType::Delim, "*"},
CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"+",
{CSSToken{CSSTokenType::Delim, "+"}, CSSToken{CSSTokenType::EndOfFile}});
expectTokens(
"(%",
{CSSToken{CSSTokenType::Delim, "("},
CSSToken{CSSTokenType::Delim, "%"},
CSSToken{CSSTokenType::EndOfFile}});
}
} // namespace facebook::react