test_style.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. from __future__ import annotations
  2. from typing import Any
  3. import pytest
  4. import reflex as rx
  5. from reflex import style
  6. from reflex.components.component import evaluate_style_namespaces
  7. from reflex.style import Style
  8. from reflex.vars import Var
  9. test_style = [
  10. ({"a": 1}, {"a": 1}),
  11. ({"a": Var.create("abc")}, {"a": "abc"}),
  12. ({"test_case": 1}, {"testCase": 1}),
  13. ({"test_case": {"a": 1}}, {"testCase": {"a": 1}}),
  14. ({":test_case": {"a": 1}}, {":testCase": {"a": 1}}),
  15. ({"::test_case": {"a": 1}}, {"::testCase": {"a": 1}}),
  16. (
  17. {"::-webkit-scrollbar": {"display": "none"}},
  18. {"::-webkit-scrollbar": {"display": "none"}},
  19. ),
  20. ({"margin_y": "2rem"}, {"marginBottom": "2rem", "marginTop": "2rem"}),
  21. ({"marginY": "2rem"}, {"marginBottom": "2rem", "marginTop": "2rem"}),
  22. (
  23. {"::-webkit-scrollbar": {"bgColor": "red"}},
  24. {"::-webkit-scrollbar": {"backgroundColor": "red"}},
  25. ),
  26. (
  27. {"paddingX": ["2rem", "3rem"]},
  28. {
  29. "paddingInlineStart": ["2rem", "3rem"],
  30. "paddingInlineEnd": ["2rem", "3rem"],
  31. },
  32. ),
  33. ]
  34. @pytest.mark.parametrize(
  35. "style_dict,expected",
  36. test_style,
  37. )
  38. def test_convert(style_dict, expected):
  39. """Test Format a style dictionary.
  40. Args:
  41. style_dict: The style to check.
  42. expected: The expected formatted style.
  43. """
  44. converted_dict, _var_data = style.convert(style_dict)
  45. assert converted_dict == expected
  46. @pytest.mark.parametrize(
  47. "style_dict,expected",
  48. test_style,
  49. )
  50. def test_create_style(style_dict, expected):
  51. """Test style dictionary.
  52. Args:
  53. style_dict: The style to check.
  54. expected: The expected formatted style.
  55. """
  56. assert style.Style(style_dict) == expected
  57. def compare_dict_of_var(d1: dict[str, Any], d2: dict[str, Any]):
  58. """Compare two dictionaries of Var objects.
  59. Args:
  60. d1: The first dictionary.
  61. d2: The second dictionary.
  62. """
  63. assert len(d1) == len(d2)
  64. for key, value in d1.items():
  65. assert key in d2
  66. if isinstance(value, dict):
  67. compare_dict_of_var(value, d2[key])
  68. elif isinstance(value, Var):
  69. assert value.equals(d2[key])
  70. else:
  71. assert value == d2[key]
  72. @pytest.mark.parametrize(
  73. ("kwargs", "style_dict", "expected_get_style"),
  74. [
  75. ({}, {}, {"css": None}),
  76. ({"color": "hotpink"}, {}, {"css": Var.create(Style({"color": "hotpink"}))}),
  77. ({}, {"color": "red"}, {"css": Var.create(Style({"color": "red"}))}),
  78. (
  79. {"color": "hotpink"},
  80. {"color": "red"},
  81. {"css": Var.create(Style({"color": "hotpink"}))},
  82. ),
  83. (
  84. {"_hover": {"color": "hotpink"}},
  85. {},
  86. {"css": Var.create(Style({"&:hover": {"color": "hotpink"}}))},
  87. ),
  88. (
  89. {},
  90. {"_hover": {"color": "red"}},
  91. {"css": Var.create(Style({"&:hover": {"color": "red"}}))},
  92. ),
  93. (
  94. {},
  95. {":hover": {"color": "red"}},
  96. {"css": Var.create(Style({"&:hover": {"color": "red"}}))},
  97. ),
  98. (
  99. {},
  100. {"::-webkit-scrollbar": {"display": "none"}},
  101. {"css": Var.create(Style({"&::-webkit-scrollbar": {"display": "none"}}))},
  102. ),
  103. (
  104. {},
  105. {"::-moz-progress-bar": {"background_color": "red"}},
  106. {
  107. "css": Var.create(
  108. Style({"&::-moz-progress-bar": {"backgroundColor": "red"}})
  109. )
  110. },
  111. ),
  112. (
  113. {"color": ["#111", "#222", "#333", "#444", "#555"]},
  114. {},
  115. {
  116. "css": Var.create(
  117. Style(
  118. {
  119. "@media screen and (min-width: 0)": {"color": "#111"},
  120. "@media screen and (min-width: 30em)": {"color": "#222"},
  121. "@media screen and (min-width: 48em)": {"color": "#333"},
  122. "@media screen and (min-width: 62em)": {"color": "#444"},
  123. "@media screen and (min-width: 80em)": {"color": "#555"},
  124. }
  125. )
  126. )
  127. },
  128. ),
  129. (
  130. {
  131. "color": ["#111", "#222", "#333", "#444", "#555"],
  132. "background_color": "#FFF",
  133. },
  134. {},
  135. {
  136. "css": Var.create(
  137. Style(
  138. {
  139. "@media screen and (min-width: 0)": {"color": "#111"},
  140. "@media screen and (min-width: 30em)": {"color": "#222"},
  141. "@media screen and (min-width: 48em)": {"color": "#333"},
  142. "@media screen and (min-width: 62em)": {"color": "#444"},
  143. "@media screen and (min-width: 80em)": {"color": "#555"},
  144. "backgroundColor": "#FFF",
  145. }
  146. )
  147. )
  148. },
  149. ),
  150. (
  151. {
  152. "color": ["#111", "#222", "#333", "#444", "#555"],
  153. "background_color": ["#FFF", "#EEE", "#DDD", "#CCC", "#BBB"],
  154. },
  155. {},
  156. {
  157. "css": Var.create(
  158. Style(
  159. {
  160. "@media screen and (min-width: 0)": {
  161. "color": "#111",
  162. "backgroundColor": "#FFF",
  163. },
  164. "@media screen and (min-width: 30em)": {
  165. "color": "#222",
  166. "backgroundColor": "#EEE",
  167. },
  168. "@media screen and (min-width: 48em)": {
  169. "color": "#333",
  170. "backgroundColor": "#DDD",
  171. },
  172. "@media screen and (min-width: 62em)": {
  173. "color": "#444",
  174. "backgroundColor": "#CCC",
  175. },
  176. "@media screen and (min-width: 80em)": {
  177. "color": "#555",
  178. "backgroundColor": "#BBB",
  179. },
  180. }
  181. )
  182. )
  183. },
  184. ),
  185. (
  186. {
  187. "_hover": [
  188. {"color": "#111"},
  189. {"color": "#222"},
  190. {"color": "#333"},
  191. {"color": "#444"},
  192. {"color": "#555"},
  193. ]
  194. },
  195. {},
  196. {
  197. "css": Var.create(
  198. Style(
  199. {
  200. "&:hover": {
  201. "@media screen and (min-width: 0)": {"color": "#111"},
  202. "@media screen and (min-width: 30em)": {
  203. "color": "#222"
  204. },
  205. "@media screen and (min-width: 48em)": {
  206. "color": "#333"
  207. },
  208. "@media screen and (min-width: 62em)": {
  209. "color": "#444"
  210. },
  211. "@media screen and (min-width: 80em)": {
  212. "color": "#555"
  213. },
  214. }
  215. }
  216. )
  217. )
  218. },
  219. ),
  220. (
  221. {"_hover": {"color": ["#111", "#222", "#333", "#444", "#555"]}},
  222. {},
  223. {
  224. "css": Var.create(
  225. Style(
  226. {
  227. "&:hover": {
  228. "@media screen and (min-width: 0)": {"color": "#111"},
  229. "@media screen and (min-width: 30em)": {
  230. "color": "#222"
  231. },
  232. "@media screen and (min-width: 48em)": {
  233. "color": "#333"
  234. },
  235. "@media screen and (min-width: 62em)": {
  236. "color": "#444"
  237. },
  238. "@media screen and (min-width: 80em)": {
  239. "color": "#555"
  240. },
  241. }
  242. }
  243. )
  244. )
  245. },
  246. ),
  247. (
  248. {
  249. "_hover": {
  250. "color": ["#111", "#222", "#333", "#444", "#555"],
  251. "background_color": ["#FFF", "#EEE", "#DDD", "#CCC", "#BBB"],
  252. }
  253. },
  254. {},
  255. {
  256. "css": Var.create(
  257. Style(
  258. {
  259. "&:hover": {
  260. "@media screen and (min-width: 0)": {
  261. "color": "#111",
  262. "backgroundColor": "#FFF",
  263. },
  264. "@media screen and (min-width: 30em)": {
  265. "color": "#222",
  266. "backgroundColor": "#EEE",
  267. },
  268. "@media screen and (min-width: 48em)": {
  269. "color": "#333",
  270. "backgroundColor": "#DDD",
  271. },
  272. "@media screen and (min-width: 62em)": {
  273. "color": "#444",
  274. "backgroundColor": "#CCC",
  275. },
  276. "@media screen and (min-width: 80em)": {
  277. "color": "#555",
  278. "backgroundColor": "#BBB",
  279. },
  280. }
  281. }
  282. )
  283. )
  284. },
  285. ),
  286. (
  287. {
  288. "_hover": {
  289. "color": ["#111", "#222", "#333", "#444", "#555"],
  290. "background_color": "#FFF",
  291. }
  292. },
  293. {},
  294. {
  295. "css": Var.create(
  296. Style(
  297. {
  298. "&:hover": {
  299. "@media screen and (min-width: 0)": {"color": "#111"},
  300. "@media screen and (min-width: 30em)": {
  301. "color": "#222"
  302. },
  303. "@media screen and (min-width: 48em)": {
  304. "color": "#333"
  305. },
  306. "@media screen and (min-width: 62em)": {
  307. "color": "#444"
  308. },
  309. "@media screen and (min-width: 80em)": {
  310. "color": "#555"
  311. },
  312. "backgroundColor": "#FFF",
  313. }
  314. }
  315. )
  316. )
  317. },
  318. ),
  319. ],
  320. )
  321. def test_style_via_component(
  322. kwargs: dict[str, Any],
  323. style_dict: dict[str, Any],
  324. expected_get_style: dict[str, Any],
  325. ):
  326. """Pass kwargs and style_dict to a component and assert the final, combined style dict.
  327. Args:
  328. kwargs: The kwargs to pass to the component.
  329. style_dict: The style_dict to pass to the component.
  330. expected_get_style: The expected style dict.
  331. """
  332. comp = rx.el.div(style=style_dict, **kwargs) # type: ignore
  333. compare_dict_of_var(comp._get_style(), expected_get_style)
  334. class StyleState(rx.State):
  335. """Style vars in a substate."""
  336. color: str = "hotpink"
  337. color2: str = "red"
  338. @pytest.mark.parametrize(
  339. ("kwargs", "expected_get_style"),
  340. [
  341. (
  342. {"color": StyleState.color},
  343. {"css": Var.create(Style({"color": StyleState.color}))},
  344. ),
  345. (
  346. {"color": f"dark{StyleState.color}"},
  347. {
  348. "css": Var.create_safe(f'{{"color": `dark{StyleState.color}`}}').to(
  349. Style
  350. )
  351. },
  352. ),
  353. (
  354. {"color": StyleState.color, "_hover": {"color": StyleState.color2}},
  355. {
  356. "css": Var.create(
  357. Style(
  358. {
  359. "color": StyleState.color,
  360. "&:hover": {"color": StyleState.color2},
  361. }
  362. )
  363. )
  364. },
  365. ),
  366. (
  367. {"color": [StyleState.color, "gray", StyleState.color2, "yellow", "blue"]},
  368. {
  369. "css": Var.create(
  370. Style(
  371. {
  372. "@media screen and (min-width: 0)": {
  373. "color": StyleState.color
  374. },
  375. "@media screen and (min-width: 30em)": {"color": "gray"},
  376. "@media screen and (min-width: 48em)": {
  377. "color": StyleState.color2
  378. },
  379. "@media screen and (min-width: 62em)": {"color": "yellow"},
  380. "@media screen and (min-width: 80em)": {"color": "blue"},
  381. }
  382. )
  383. )
  384. },
  385. ),
  386. (
  387. {
  388. "_hover": [
  389. {"color": StyleState.color},
  390. {"color": StyleState.color2},
  391. {"color": "#333"},
  392. {"color": "#444"},
  393. {"color": "#555"},
  394. ]
  395. },
  396. {
  397. "css": Var.create(
  398. Style(
  399. {
  400. "&:hover": {
  401. "@media screen and (min-width: 0)": {
  402. "color": StyleState.color
  403. },
  404. "@media screen and (min-width: 30em)": {
  405. "color": StyleState.color2
  406. },
  407. "@media screen and (min-width: 48em)": {
  408. "color": "#333"
  409. },
  410. "@media screen and (min-width: 62em)": {
  411. "color": "#444"
  412. },
  413. "@media screen and (min-width: 80em)": {
  414. "color": "#555"
  415. },
  416. }
  417. }
  418. )
  419. )
  420. },
  421. ),
  422. (
  423. {
  424. "_hover": {
  425. "color": [
  426. StyleState.color,
  427. StyleState.color2,
  428. "#333",
  429. "#444",
  430. "#555",
  431. ]
  432. }
  433. },
  434. {
  435. "css": Var.create(
  436. Style(
  437. {
  438. "&:hover": {
  439. "@media screen and (min-width: 0)": {
  440. "color": StyleState.color
  441. },
  442. "@media screen and (min-width: 30em)": {
  443. "color": StyleState.color2
  444. },
  445. "@media screen and (min-width: 48em)": {
  446. "color": "#333"
  447. },
  448. "@media screen and (min-width: 62em)": {
  449. "color": "#444"
  450. },
  451. "@media screen and (min-width: 80em)": {
  452. "color": "#555"
  453. },
  454. }
  455. }
  456. )
  457. )
  458. },
  459. ),
  460. ],
  461. )
  462. def test_style_via_component_with_state(
  463. kwargs: dict[str, Any],
  464. expected_get_style: dict[str, Any],
  465. ):
  466. """Pass kwargs to a component with state vars and assert the final, combined style dict.
  467. Args:
  468. kwargs: The kwargs to pass to the component.
  469. expected_get_style: The expected style dict.
  470. """
  471. comp = rx.el.div(**kwargs)
  472. assert comp.style._var_data == expected_get_style["css"]._var_data
  473. # Assert that style values are equal.
  474. compare_dict_of_var(comp._get_style(), expected_get_style)
  475. def test_evaluate_style_namespaces():
  476. """Test that namespaces get converted to component create functions."""
  477. style_dict = {rx.text: {"color": "blue"}}
  478. assert rx.text.__call__ not in style_dict
  479. style_dict = evaluate_style_namespaces(style_dict) # type: ignore
  480. assert rx.text.__call__ in style_dict