test_style.py 17 KB

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