test_style.py 19 KB

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