test_style.py 17 KB

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