test_var_operations.py 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. """Integration tests for var operations."""
  2. from collections.abc import Generator
  3. import pytest
  4. from selenium.webdriver.common.by import By
  5. from reflex.testing import AppHarness
  6. def VarOperations():
  7. """App with var operations."""
  8. from typing import TypedDict
  9. import reflex as rx
  10. from reflex.vars.base import LiteralVar
  11. from reflex.vars.sequence import ArrayVar
  12. class Object(rx.Base):
  13. name: str = "hello"
  14. optional_none: str | None = None
  15. optional_str: str | None = "hello"
  16. class Person(TypedDict):
  17. name: str
  18. age: int
  19. class VarOperationState(rx.State):
  20. int_var1: rx.Field[int] = rx.field(10)
  21. int_var2: rx.Field[int] = rx.field(5)
  22. int_var3: rx.Field[int] = rx.field(7)
  23. float_var1: rx.Field[float] = rx.field(10.5)
  24. float_var2: rx.Field[float] = rx.field(5.5)
  25. long_float: rx.Field[float] = rx.field(13212312312.1231231)
  26. list1: rx.Field[list] = rx.field([1, 2])
  27. list2: rx.Field[list] = rx.field([3, 4])
  28. list3: rx.Field[list] = rx.field(["first", "second", "third"])
  29. list4: rx.Field[list] = rx.field([Object(name="obj_1"), Object(name="obj_2")])
  30. optional_list: rx.Field[list | None] = rx.field(None)
  31. optional_dict: rx.Field[dict[str, str] | None] = rx.field(None)
  32. optional_list_value: rx.Field[list[str] | None] = rx.field(["red", "yellow"])
  33. optional_dict_value: rx.Field[dict[str, str] | None] = rx.field({"name": "red"})
  34. str_var1: rx.Field[str] = rx.field("first")
  35. str_var2: rx.Field[str] = rx.field("second")
  36. str_var3: rx.Field[str] = rx.field("ThIrD")
  37. str_var4: rx.Field[str] = rx.field("a long string")
  38. dict1: rx.Field[dict[int, int]] = rx.field({1: 2})
  39. dict2: rx.Field[dict[int, int]] = rx.field({3: 4})
  40. html_str: rx.Field[str] = rx.field("<div>hello</div>")
  41. people: rx.Field[list[Person]] = rx.field(
  42. [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
  43. )
  44. obj: rx.Field[Object] = rx.field(Object())
  45. app = rx.App()
  46. @rx.memo
  47. def memo_comp(list1: list[int], int_var1: int, id: str):
  48. return rx.text(list1, int_var1, id=id)
  49. @rx.memo
  50. def memo_comp_nested(int_var2: int, id: str):
  51. return memo_comp(list1=[3, 4], int_var1=int_var2, id=id)
  52. @app.add_page
  53. def index():
  54. return rx.vstack(
  55. None, # Testing that None doesn't break everything
  56. rx.el.input(
  57. id="token",
  58. value=VarOperationState.router.session.client_token,
  59. is_read_only=True,
  60. ),
  61. # INT INT
  62. rx.text(
  63. VarOperationState.int_var1 + VarOperationState.int_var2,
  64. id="int_add_int",
  65. ),
  66. rx.text(
  67. VarOperationState.int_var1 * VarOperationState.int_var2,
  68. id="int_mult_int",
  69. ),
  70. rx.text(
  71. VarOperationState.int_var1 - VarOperationState.int_var2,
  72. id="int_sub_int",
  73. ),
  74. rx.text(
  75. VarOperationState.int_var1**VarOperationState.int_var2,
  76. id="int_exp_int",
  77. ),
  78. rx.text(
  79. VarOperationState.int_var1 / VarOperationState.int_var2,
  80. id="int_div_int",
  81. ),
  82. rx.text(
  83. VarOperationState.int_var1 // VarOperationState.int_var3,
  84. id="int_floor_int",
  85. ),
  86. rx.text(
  87. VarOperationState.int_var1 % VarOperationState.int_var2,
  88. id="int_mod_int",
  89. ),
  90. rx.text(
  91. VarOperationState.int_var1 | VarOperationState.int_var2,
  92. id="int_or_int",
  93. ),
  94. rx.text(
  95. (VarOperationState.int_var1 > VarOperationState.int_var2).to_string(),
  96. id="int_gt_int",
  97. ),
  98. rx.text(
  99. (VarOperationState.int_var1 < VarOperationState.int_var2).to_string(),
  100. id="int_lt_int",
  101. ),
  102. rx.text(
  103. (VarOperationState.int_var1 >= VarOperationState.int_var2).to_string(),
  104. id="int_gte_int",
  105. ),
  106. rx.text(
  107. (VarOperationState.int_var1 <= VarOperationState.int_var2).to_string(),
  108. id="int_lte_int",
  109. ),
  110. rx.text(
  111. VarOperationState.int_var1 & VarOperationState.int_var2,
  112. id="int_and_int",
  113. ),
  114. rx.text(
  115. (VarOperationState.int_var1 | VarOperationState.int_var2).to_string(),
  116. id="int_or_int",
  117. ),
  118. rx.text(
  119. (VarOperationState.int_var1 == VarOperationState.int_var2).to_string(),
  120. id="int_eq_int",
  121. ),
  122. rx.text(
  123. (VarOperationState.int_var1 != VarOperationState.int_var2).to_string(),
  124. id="int_neq_int",
  125. ),
  126. # INT FLOAT OR FLOAT INT
  127. rx.text(
  128. VarOperationState.float_var1 + VarOperationState.int_var2,
  129. id="float_add_int",
  130. ),
  131. rx.text(
  132. VarOperationState.float_var1 * VarOperationState.int_var2,
  133. id="float_mult_int",
  134. ),
  135. rx.text(
  136. VarOperationState.float_var1 - VarOperationState.int_var2,
  137. id="float_sub_int",
  138. ),
  139. rx.text(
  140. VarOperationState.float_var1**VarOperationState.int_var2,
  141. id="float_exp_int",
  142. ),
  143. rx.text(
  144. VarOperationState.float_var1 / VarOperationState.int_var2,
  145. id="float_div_int",
  146. ),
  147. rx.text(
  148. VarOperationState.float_var1 // VarOperationState.int_var3,
  149. id="float_floor_int",
  150. ),
  151. rx.text(
  152. VarOperationState.float_var1 % VarOperationState.int_var2,
  153. id="float_mod_int",
  154. ),
  155. rx.text(
  156. (VarOperationState.float_var1 > VarOperationState.int_var2).to_string(),
  157. id="float_gt_int",
  158. ),
  159. rx.text(
  160. (VarOperationState.float_var1 < VarOperationState.int_var2).to_string(),
  161. id="float_lt_int",
  162. ),
  163. rx.text(
  164. (
  165. VarOperationState.float_var1 >= VarOperationState.int_var2
  166. ).to_string(),
  167. id="float_gte_int",
  168. ),
  169. rx.text(
  170. (
  171. VarOperationState.float_var1 <= VarOperationState.int_var2
  172. ).to_string(),
  173. id="float_lte_int",
  174. ),
  175. rx.text(
  176. (
  177. VarOperationState.float_var1 == VarOperationState.int_var2
  178. ).to_string(),
  179. id="float_eq_int",
  180. ),
  181. rx.text(
  182. (
  183. VarOperationState.float_var1 != VarOperationState.int_var2
  184. ).to_string(),
  185. id="float_neq_int",
  186. ),
  187. rx.text(
  188. (VarOperationState.float_var1 & VarOperationState.int_var2).to_string(),
  189. id="float_and_int",
  190. ),
  191. rx.text(
  192. (VarOperationState.float_var1 | VarOperationState.int_var2).to_string(),
  193. id="float_or_int",
  194. ),
  195. # INT, DICT
  196. rx.text(
  197. (VarOperationState.int_var1 | VarOperationState.dict1).to_string(),
  198. id="int_or_dict",
  199. ),
  200. rx.text(
  201. (VarOperationState.int_var1 & VarOperationState.dict1).to_string(),
  202. id="int_and_dict",
  203. ),
  204. rx.text(
  205. (VarOperationState.int_var1 == VarOperationState.dict1).to_string(),
  206. id="int_eq_dict",
  207. ),
  208. rx.text(
  209. (VarOperationState.int_var1 != VarOperationState.dict1).to_string(),
  210. id="int_neq_dict",
  211. ),
  212. # FLOAT FLOAT
  213. rx.text(
  214. VarOperationState.float_var1 + VarOperationState.float_var2,
  215. id="float_add_float",
  216. ),
  217. rx.text(
  218. VarOperationState.float_var1 * VarOperationState.float_var2,
  219. id="float_mult_float",
  220. ),
  221. rx.text(
  222. VarOperationState.float_var1 - VarOperationState.float_var2,
  223. id="float_sub_float",
  224. ),
  225. rx.text(
  226. VarOperationState.float_var1**VarOperationState.float_var2,
  227. id="float_exp_float",
  228. ),
  229. rx.text(
  230. VarOperationState.float_var1 / VarOperationState.float_var2,
  231. id="float_div_float",
  232. ),
  233. rx.text(
  234. VarOperationState.float_var1 // VarOperationState.float_var2,
  235. id="float_floor_float",
  236. ),
  237. rx.text(
  238. VarOperationState.float_var1 % VarOperationState.float_var2,
  239. id="float_mod_float",
  240. ),
  241. rx.text(
  242. (
  243. VarOperationState.float_var1 > VarOperationState.float_var2
  244. ).to_string(),
  245. id="float_gt_float",
  246. ),
  247. rx.text(
  248. (
  249. VarOperationState.float_var1 < VarOperationState.float_var2
  250. ).to_string(),
  251. id="float_lt_float",
  252. ),
  253. rx.text(
  254. (
  255. VarOperationState.float_var1 >= VarOperationState.float_var2
  256. ).to_string(),
  257. id="float_gte_float",
  258. ),
  259. rx.text(
  260. (
  261. VarOperationState.float_var1 <= VarOperationState.float_var2
  262. ).to_string(),
  263. id="float_lte_float",
  264. ),
  265. rx.text(
  266. (
  267. VarOperationState.float_var1 == VarOperationState.float_var2
  268. ).to_string(),
  269. id="float_eq_float",
  270. ),
  271. rx.text(
  272. (
  273. VarOperationState.float_var1 != VarOperationState.float_var2
  274. ).to_string(),
  275. id="float_neq_float",
  276. ),
  277. rx.text(
  278. (
  279. VarOperationState.float_var1 & VarOperationState.float_var2
  280. ).to_string(),
  281. id="float_and_float",
  282. ),
  283. rx.text(
  284. (
  285. VarOperationState.float_var1 | VarOperationState.float_var2
  286. ).to_string(),
  287. id="float_or_float",
  288. ),
  289. # FLOAT STR
  290. rx.text(
  291. VarOperationState.float_var1 | VarOperationState.str_var1,
  292. id="float_or_str",
  293. ),
  294. rx.text(
  295. VarOperationState.float_var1 & VarOperationState.str_var1,
  296. id="float_and_str",
  297. ),
  298. rx.text(
  299. (
  300. VarOperationState.float_var1 == VarOperationState.str_var1
  301. ).to_string(),
  302. id="float_eq_str",
  303. ),
  304. rx.text(
  305. (
  306. VarOperationState.float_var1 != VarOperationState.str_var1
  307. ).to_string(),
  308. id="float_neq_str",
  309. ),
  310. # FLOAT LIST
  311. rx.text(
  312. (VarOperationState.float_var1 | VarOperationState.list1).to_string(),
  313. id="float_or_list",
  314. ),
  315. rx.text(
  316. (VarOperationState.float_var1 & VarOperationState.list1).to_string(),
  317. id="float_and_list",
  318. ),
  319. rx.text(
  320. (VarOperationState.float_var1 == VarOperationState.list1).to_string(),
  321. id="float_eq_list",
  322. ),
  323. rx.text(
  324. (VarOperationState.float_var1 != VarOperationState.list1).to_string(),
  325. id="float_neq_list",
  326. ),
  327. # FLOAT DICT
  328. rx.text(
  329. (VarOperationState.float_var1 | VarOperationState.dict1).to_string(),
  330. id="float_or_dict",
  331. ),
  332. rx.text(
  333. (VarOperationState.float_var1 & VarOperationState.dict1).to_string(),
  334. id="float_and_dict",
  335. ),
  336. rx.text(
  337. (VarOperationState.float_var1 == VarOperationState.dict1).to_string(),
  338. id="float_eq_dict",
  339. ),
  340. rx.text(
  341. (VarOperationState.float_var1 != VarOperationState.dict1).to_string(),
  342. id="float_neq_dict",
  343. ),
  344. # STR STR
  345. rx.text(
  346. VarOperationState.str_var1 + VarOperationState.str_var2,
  347. id="str_add_str",
  348. ),
  349. rx.text(
  350. (VarOperationState.str_var1 > VarOperationState.str_var2).to_string(),
  351. id="str_gt_str",
  352. ),
  353. rx.text(
  354. (VarOperationState.str_var1 < VarOperationState.str_var2).to_string(),
  355. id="str_lt_str",
  356. ),
  357. rx.text(
  358. (VarOperationState.str_var1 >= VarOperationState.str_var2).to_string(),
  359. id="str_gte_str",
  360. ),
  361. rx.text(
  362. (VarOperationState.str_var1 <= VarOperationState.str_var2).to_string(),
  363. id="str_lte_str",
  364. ),
  365. rx.text(
  366. (
  367. VarOperationState.float_var1 == VarOperationState.float_var2
  368. ).to_string(),
  369. id="str_eq_str",
  370. ),
  371. rx.text(
  372. (
  373. VarOperationState.float_var1 != VarOperationState.float_var2
  374. ).to_string(),
  375. id="str_neq_str",
  376. ),
  377. rx.text(
  378. VarOperationState.str_var1.contains("fir").to_string(),
  379. id="str_contains",
  380. ),
  381. rx.text(
  382. VarOperationState.str_var1 | VarOperationState.str_var1,
  383. id="str_or_str",
  384. ),
  385. rx.text(
  386. VarOperationState.str_var1 & VarOperationState.str_var2,
  387. id="str_and_str",
  388. ),
  389. # STR, INT
  390. rx.text(
  391. VarOperationState.str_var1 * VarOperationState.int_var2,
  392. id="str_mult_int",
  393. ),
  394. rx.text(
  395. VarOperationState.str_var1 & VarOperationState.int_var2,
  396. id="str_and_int",
  397. ),
  398. rx.text(
  399. VarOperationState.str_var1 | VarOperationState.int_var2,
  400. id="str_or_int",
  401. ),
  402. rx.text(
  403. (VarOperationState.str_var1 == VarOperationState.int_var1).to_string(),
  404. id="str_eq_int",
  405. ),
  406. rx.text(
  407. (VarOperationState.str_var1 != VarOperationState.int_var1).to_string(),
  408. id="str_neq_int",
  409. ),
  410. # STR, LIST
  411. rx.text(
  412. VarOperationState.str_var1 | VarOperationState.list1,
  413. id="str_or_list",
  414. ),
  415. rx.text(
  416. (VarOperationState.str_var1 & VarOperationState.list1).to_string(),
  417. id="str_and_list",
  418. ),
  419. rx.text(
  420. (VarOperationState.str_var1 == VarOperationState.list1).to_string(),
  421. id="str_eq_list",
  422. ),
  423. rx.text(
  424. (VarOperationState.str_var1 != VarOperationState.list1).to_string(),
  425. id="str_neq_list",
  426. ),
  427. # STR, DICT
  428. rx.text(
  429. VarOperationState.str_var1 | VarOperationState.dict1,
  430. id="str_or_dict",
  431. ),
  432. rx.text(
  433. (VarOperationState.str_var1 & VarOperationState.dict1).to_string(),
  434. id="str_and_dict",
  435. ),
  436. rx.text(
  437. (VarOperationState.str_var1 == VarOperationState.dict1).to_string(),
  438. id="str_eq_dict",
  439. ),
  440. rx.text(
  441. (VarOperationState.str_var1 != VarOperationState.dict1).to_string(),
  442. id="str_neq_dict",
  443. ),
  444. # LIST, LIST
  445. rx.text(
  446. (VarOperationState.list1 + VarOperationState.list2).to_string(),
  447. id="list_add_list",
  448. ),
  449. rx.text(
  450. (VarOperationState.list1 & VarOperationState.list2).to_string(),
  451. id="list_and_list",
  452. ),
  453. rx.text(
  454. (VarOperationState.list1 | VarOperationState.list2).to_string(),
  455. id="list_or_list",
  456. ),
  457. rx.text(
  458. (VarOperationState.list1 > VarOperationState.list2).to_string(),
  459. id="list_gt_list",
  460. ),
  461. rx.text(
  462. (VarOperationState.list1 < VarOperationState.list2).to_string(),
  463. id="list_lt_list",
  464. ),
  465. rx.text(
  466. (VarOperationState.list1 >= VarOperationState.list2).to_string(),
  467. id="list_gte_list",
  468. ),
  469. rx.text(
  470. (VarOperationState.list1 <= VarOperationState.list2).to_string(),
  471. id="list_lte_list",
  472. ),
  473. rx.text(
  474. (VarOperationState.list1 == VarOperationState.list2).to_string(),
  475. id="list_eq_list",
  476. ),
  477. rx.text(
  478. (VarOperationState.list1 != VarOperationState.list2).to_string(),
  479. id="list_neq_list",
  480. ),
  481. rx.text(
  482. VarOperationState.list1.contains(1).to_string(),
  483. id="list_contains",
  484. ),
  485. rx.text(VarOperationState.list4.pluck("name").to_string(), id="list_pluck"),
  486. rx.text(VarOperationState.list1.reverse().to_string(), id="list_reverse"),
  487. # LIST, INT
  488. rx.text(
  489. (VarOperationState.list1 * VarOperationState.int_var2).to_string(),
  490. id="list_mult_int",
  491. ),
  492. rx.text(
  493. (VarOperationState.list1 | VarOperationState.int_var1).to_string(),
  494. id="list_or_int",
  495. ),
  496. rx.text(
  497. (VarOperationState.list1 & VarOperationState.int_var1).to_string(),
  498. id="list_and_int",
  499. ),
  500. rx.text(
  501. (VarOperationState.list1 == VarOperationState.int_var1).to_string(),
  502. id="list_eq_int",
  503. ),
  504. rx.text(
  505. (VarOperationState.list1 != VarOperationState.int_var1).to_string(),
  506. id="list_neq_int",
  507. ),
  508. # LIST, DICT
  509. rx.text(
  510. (VarOperationState.list1 | VarOperationState.dict1).to_string(),
  511. id="list_or_dict",
  512. ),
  513. rx.text(
  514. (VarOperationState.list1 & VarOperationState.dict1).to_string(),
  515. id="list_and_dict",
  516. ),
  517. rx.text(
  518. (VarOperationState.list1 == VarOperationState.dict1).to_string(),
  519. id="list_eq_dict",
  520. ),
  521. rx.text(
  522. (VarOperationState.list1 != VarOperationState.dict1).to_string(),
  523. id="list_neq_dict",
  524. ),
  525. # DICT, DICT
  526. rx.text(
  527. (VarOperationState.dict1 | VarOperationState.dict2).to_string(),
  528. id="dict_or_dict",
  529. ),
  530. rx.text(
  531. (VarOperationState.dict1 & VarOperationState.dict2).to_string(),
  532. id="dict_and_dict",
  533. ),
  534. rx.text(
  535. (VarOperationState.dict1 == VarOperationState.dict2).to_string(),
  536. id="dict_eq_dict",
  537. ),
  538. rx.text(
  539. (VarOperationState.dict1 != VarOperationState.dict2).to_string(),
  540. id="dict_neq_dict",
  541. ),
  542. rx.text(
  543. VarOperationState.dict1.contains(1).to_string(),
  544. id="dict_contains",
  545. ),
  546. rx.text(VarOperationState.str_var3.lower(), id="str_lower"),
  547. rx.text(VarOperationState.str_var3.upper(), id="str_upper"),
  548. rx.text(VarOperationState.str_var4.split(" ").to_string(), id="str_split"),
  549. rx.text(VarOperationState.list3.join(""), id="list_join"),
  550. rx.text(VarOperationState.list3.join(","), id="list_join_comma"),
  551. # Index from an op var
  552. rx.text(
  553. VarOperationState.list3[VarOperationState.int_var1 % 3],
  554. id="list_index_mod",
  555. ),
  556. rx.html(
  557. VarOperationState.html_str,
  558. id="html_str",
  559. ),
  560. rx.el.mark("second"),
  561. rx.text(ArrayVar.range(2, 5).join(","), id="list_join_range1"),
  562. rx.text(ArrayVar.range(2, 10, 2).join(","), id="list_join_range2"),
  563. rx.text(ArrayVar.range(5, 0, -1).join(","), id="list_join_range3"),
  564. rx.text(ArrayVar.range(0, 3).join(","), id="list_join_range4"),
  565. rx.box(
  566. rx.foreach(
  567. ArrayVar.range(0, 2),
  568. lambda x: rx.text(VarOperationState.list1[x], as_="p"),
  569. ),
  570. id="foreach_list_arg",
  571. ),
  572. rx.box(
  573. rx.foreach(
  574. ArrayVar.range(0, 2),
  575. lambda x, ix: rx.text(VarOperationState.list1[ix], as_="p"),
  576. ),
  577. id="foreach_list_ix",
  578. ),
  579. rx.box(
  580. rx.foreach(
  581. LiteralVar.create(list(range(0, 3))).to(ArrayVar, list[int]),
  582. lambda x: rx.foreach(
  583. ArrayVar.range(x),
  584. lambda y: rx.text(VarOperationState.list1[y], as_="p"),
  585. ),
  586. ),
  587. id="foreach_list_nested",
  588. ),
  589. rx.box(
  590. rx.foreach(
  591. ArrayVar.range(0, 2),
  592. lambda x: VarOperationState.list1[x],
  593. ),
  594. id="foreach_list_arg2",
  595. ),
  596. memo_comp(
  597. list1=VarOperationState.list1,
  598. int_var1=VarOperationState.int_var1,
  599. id="memo_comp",
  600. ),
  601. memo_comp_nested(
  602. int_var2=VarOperationState.int_var2,
  603. id="memo_comp_nested",
  604. ),
  605. # length
  606. rx.box(
  607. rx.text(VarOperationState.list3.length()),
  608. id="list_length",
  609. ),
  610. rx.box(
  611. rx.text(VarOperationState.obj.length()),
  612. id="obj_length",
  613. ),
  614. # foreach in a match
  615. rx.box(
  616. rx.match(
  617. VarOperationState.list3.length(),
  618. (0, rx.text("No choices")),
  619. (1, rx.text("One choice")),
  620. rx.foreach(VarOperationState.list3, lambda choice: rx.text(choice)),
  621. ),
  622. id="foreach_in_match",
  623. ),
  624. # Literal range var in a foreach
  625. rx.box(rx.foreach(range(42, 80, 27), rx.text.span), id="range_in_foreach1"),
  626. rx.box(rx.foreach(range(42, 80, 3), rx.text.span), id="range_in_foreach2"),
  627. rx.box(rx.foreach(range(42, 20, -6), rx.text.span), id="range_in_foreach3"),
  628. rx.box(rx.foreach(range(42, 43, 5), rx.text.span), id="range_in_foreach4"),
  629. # Literal dict in a foreach
  630. rx.box(rx.foreach({"a": 1, "b": 2}, rx.text.span), id="dict_in_foreach1"),
  631. # State Var dict in a foreach
  632. rx.box(
  633. rx.foreach(VarOperationState.dict1, rx.text.span),
  634. id="dict_in_foreach2",
  635. ),
  636. rx.box(
  637. rx.foreach(
  638. VarOperationState.dict1.merge(VarOperationState.dict2),
  639. rx.text.span,
  640. ),
  641. id="dict_in_foreach3",
  642. ),
  643. rx.box(
  644. rx.foreach("abcdef", lambda x: rx.text.span(x + " ")),
  645. id="str_in_foreach",
  646. ),
  647. rx.box(
  648. rx.foreach(VarOperationState.str_var1, lambda x: rx.text.span(x + " ")),
  649. id="str_var_in_foreach",
  650. ),
  651. rx.box(
  652. rx.foreach(
  653. VarOperationState.people,
  654. lambda person: rx.text.span(
  655. "Hello " + person["name"], person["age"] + 3
  656. ),
  657. ),
  658. id="typed_dict_in_foreach",
  659. ),
  660. rx.box(
  661. rx.foreach(VarOperationState.optional_list, rx.text.span),
  662. id="optional_list",
  663. ),
  664. rx.box(
  665. rx.foreach(VarOperationState.optional_dict, rx.text.span),
  666. id="optional_dict",
  667. ),
  668. rx.box(
  669. rx.foreach(VarOperationState.optional_list_value, rx.text.span),
  670. id="optional_list_value",
  671. ),
  672. rx.box(
  673. rx.foreach(VarOperationState.optional_dict_value, rx.text.span),
  674. id="optional_dict_value",
  675. ),
  676. rx.box(
  677. rx.text.span(f"{rx.Var.create(13212312312.1231231)}"),
  678. id="float_format",
  679. ),
  680. rx.box(
  681. rx.text.span(f"{rx.Var.create(13212312312.1231231):.0f}"),
  682. id="float_format_0f",
  683. ),
  684. rx.box(
  685. rx.text.span(f"{rx.Var.create(13212312312.1231231):.1f}"),
  686. id="float_format_1f",
  687. ),
  688. rx.box(
  689. rx.text.span(f"{rx.Var.create(13212312312.1231231):.2f}"),
  690. id="float_format_2f",
  691. ),
  692. rx.box(
  693. rx.text.span(f"{rx.Var.create(13212312312.1231231):,}"),
  694. id="float_format_comma",
  695. ),
  696. rx.box(
  697. rx.text.span(f"{rx.Var.create(13212312312.1231231):_}"),
  698. id="float_format_underscore",
  699. ),
  700. rx.box(
  701. rx.text.span(f"{rx.Var.create(13212312312.1231231):,.0f}"),
  702. id="float_format_comma_0f",
  703. ),
  704. rx.box(
  705. rx.text.span(f"{rx.Var.create(13212312312.1231231):,.1f}"),
  706. id="float_format_comma_1f",
  707. ),
  708. rx.box(
  709. rx.text.span(f"{rx.Var.create(13212312312.1231231):,.2f}"),
  710. id="float_format_comma_2f",
  711. ),
  712. rx.box(
  713. rx.text.span(f"{rx.Var.create(13212312312.1231231):_.0f}"),
  714. id="float_format_underscore_0f",
  715. ),
  716. rx.box(
  717. rx.text.span(f"{rx.Var.create(13212312312.1231231):_.1f}"),
  718. id="float_format_underscore_1f",
  719. ),
  720. rx.box(
  721. rx.text.span(f"{rx.Var.create(13212312312.1231231):_.2f}"),
  722. id="float_format_underscore_2f",
  723. ),
  724. # ObjectVar
  725. rx.box(
  726. rx.text.span(VarOperationState.obj.name),
  727. id="obj_name",
  728. ),
  729. rx.box(
  730. rx.text.span(VarOperationState.obj.optional_none),
  731. id="obj_optional_none",
  732. ),
  733. rx.box(
  734. rx.text.span(VarOperationState.obj.optional_str),
  735. id="obj_optional_str",
  736. ),
  737. rx.box(
  738. rx.text.span(VarOperationState.obj.get("optional_none")),
  739. id="obj_optional_none_get_none",
  740. ),
  741. rx.box(
  742. rx.text.span(VarOperationState.obj.get("optional_none", "foo")),
  743. id="obj_optional_none_get_foo",
  744. ),
  745. rx.box(
  746. rx.text.span(round(VarOperationState.long_float)),
  747. id="float_round",
  748. ),
  749. rx.box(
  750. rx.text.span(round(VarOperationState.long_float, 2)),
  751. id="float_round_2",
  752. ),
  753. )
  754. @pytest.fixture(scope="module")
  755. def var_operations(tmp_path_factory) -> Generator[AppHarness, None, None]:
  756. """Start VarOperations app at tmp_path via AppHarness.
  757. Args:
  758. tmp_path_factory: pytest tmp_path_factory fixture
  759. Yields:
  760. running AppHarness instance
  761. """
  762. with AppHarness.create(
  763. root=tmp_path_factory.mktemp("var_operations"),
  764. app_source=VarOperations,
  765. ) as harness:
  766. assert harness.app_instance is not None, "app is not running"
  767. yield harness
  768. @pytest.fixture
  769. def driver(var_operations: AppHarness):
  770. """Get an instance of the browser open to the var operations app.
  771. Args:
  772. var_operations: harness for VarOperations app
  773. Yields:
  774. WebDriver instance.
  775. """
  776. driver = var_operations.frontend()
  777. try:
  778. token_input = driver.find_element(By.ID, "token")
  779. assert token_input
  780. # wait for the backend connection to send the token
  781. token = var_operations.poll_for_value(token_input)
  782. assert token is not None
  783. yield driver
  784. finally:
  785. driver.quit()
  786. def test_var_operations(driver, var_operations: AppHarness):
  787. """Test that the var operations produce the right results.
  788. Args:
  789. driver: selenium WebDriver open to the app
  790. var_operations: AppHarness for the var operations app
  791. """
  792. tests = [
  793. # int, int
  794. ("int_add_int", "15"),
  795. ("int_mult_int", "50"),
  796. ("int_sub_int", "5"),
  797. ("int_exp_int", "100000"),
  798. ("int_div_int", "2"),
  799. ("int_floor_int", "1"),
  800. ("int_mod_int", "0"),
  801. ("int_gt_int", "true"),
  802. ("int_lt_int", "false"),
  803. ("int_gte_int", "true"),
  804. ("int_lte_int", "false"),
  805. ("int_and_int", "5"),
  806. ("int_or_int", "10"),
  807. ("int_eq_int", "false"),
  808. ("int_neq_int", "true"),
  809. # int, float
  810. ("float_add_int", "15.5"),
  811. ("float_mult_int", "52.5"),
  812. ("float_sub_int", "5.5"),
  813. ("float_exp_int", "127628.15625"),
  814. ("float_div_int", "2.1"),
  815. ("float_floor_int", "1"),
  816. ("float_mod_int", "0.5"),
  817. ("float_gt_int", "true"),
  818. ("float_lt_int", "false"),
  819. ("float_gte_int", "true"),
  820. ("float_lte_int", "false"),
  821. ("float_eq_int", "false"),
  822. ("float_neq_int", "true"),
  823. ("float_and_int", "5"),
  824. ("float_or_int", "10.5"),
  825. # int, dict
  826. ("int_or_dict", "10"),
  827. ("int_and_dict", '{"1":2}'),
  828. ("int_eq_dict", "false"),
  829. ("int_neq_dict", "true"),
  830. # float, float
  831. ("float_add_float", "16"),
  832. ("float_mult_float", "57.75"),
  833. ("float_sub_float", "5"),
  834. ("float_exp_float", "413562.49323606625"),
  835. ("float_div_float", "1.9090909090909092"),
  836. ("float_floor_float", "1"),
  837. ("float_mod_float", "5"),
  838. ("float_gt_float", "true"),
  839. ("float_lt_float", "false"),
  840. ("float_gte_float", "true"),
  841. ("float_lte_float", "false"),
  842. ("float_eq_float", "false"),
  843. ("float_neq_float", "true"),
  844. ("float_and_float", "5.5"),
  845. ("float_or_float", "10.5"),
  846. # float, str
  847. ("float_or_str", "10.5"),
  848. ("float_and_str", "first"),
  849. ("float_eq_str", "false"),
  850. ("float_neq_str", "true"),
  851. # float, list
  852. ("float_or_list", "10.5"),
  853. ("float_and_list", "[1,2]"),
  854. ("float_eq_list", "false"),
  855. ("float_neq_list", "true"),
  856. # float, dict
  857. ("float_or_dict", "10.5"),
  858. ("float_and_dict", '{"1":2}'),
  859. ("float_eq_dict", "false"),
  860. ("float_neq_dict", "true"),
  861. # str, str
  862. ("str_add_str", "firstsecond"),
  863. ("str_gt_str", "false"),
  864. ("str_lt_str", "true"),
  865. ("str_gte_str", "false"),
  866. ("str_lte_str", "true"),
  867. ("str_eq_str", "false"),
  868. ("str_neq_str", "true"),
  869. ("str_and_str", "second"),
  870. ("str_or_str", "first"),
  871. ("str_contains", "true"),
  872. ("str_lower", "third"),
  873. ("str_upper", "THIRD"),
  874. ("str_split", '["a","long","string"]'),
  875. # str, int
  876. ("str_mult_int", "firstfirstfirstfirstfirst"),
  877. ("str_and_int", "5"),
  878. ("str_or_int", "first"),
  879. ("str_eq_int", "false"),
  880. ("str_neq_int", "true"),
  881. # str, list
  882. ("str_and_list", "[1,2]"),
  883. ("str_or_list", "first"),
  884. ("str_eq_list", "false"),
  885. ("str_neq_list", "true"),
  886. # str, dict
  887. ("str_or_dict", "first"),
  888. ("str_and_dict", '{"1":2}'),
  889. ("str_eq_dict", "false"),
  890. ("str_neq_dict", "true"),
  891. # list, list
  892. ("list_add_list", "[1,2,3,4]"),
  893. ("list_gt_list", "false"),
  894. ("list_lt_list", "true"),
  895. ("list_gte_list", "false"),
  896. ("list_lte_list", "true"),
  897. ("list_eq_list", "false"),
  898. ("list_neq_list", "true"),
  899. ("list_and_list", "[3,4]"),
  900. ("list_or_list", "[1,2]"),
  901. ("list_contains", "true"),
  902. ("list_pluck", '["obj_1","obj_2"]'),
  903. ("list_reverse", "[2,1]"),
  904. ("list_join", "firstsecondthird"),
  905. ("list_join_comma", "first,second,third"),
  906. ("list_join_range1", "2,3,4"),
  907. ("list_join_range2", "2,4,6,8"),
  908. ("list_join_range3", "5,4,3,2,1"),
  909. ("list_join_range4", "0,1,2"),
  910. # list, int
  911. ("list_mult_int", "[1,2,1,2,1,2,1,2,1,2]"),
  912. ("list_or_int", "[1,2]"),
  913. ("list_and_int", "10"),
  914. ("list_eq_int", "false"),
  915. ("list_neq_int", "true"),
  916. # list, dict
  917. ("list_and_dict", '{"1":2}'),
  918. ("list_or_dict", "[1,2]"),
  919. ("list_eq_dict", "false"),
  920. ("list_neq_dict", "true"),
  921. # dict, dict
  922. ("dict_or_dict", '{"1":2}'),
  923. ("dict_and_dict", '{"3":4}'),
  924. ("dict_eq_dict", "false"),
  925. ("dict_neq_dict", "true"),
  926. ("dict_contains", "true"),
  927. # index from an op var
  928. ("list_index_mod", "second"),
  929. # html component with var
  930. ("html_str", "hello"),
  931. # index into list with foreach
  932. ("foreach_list_arg", "1\n2"),
  933. ("foreach_list_ix", "1\n2"),
  934. ("foreach_list_nested", "1\n1\n2"),
  935. ("foreach_list_arg2", "12"),
  936. # rx.memo component with state
  937. ("memo_comp", "1210"),
  938. ("memo_comp_nested", "345"),
  939. # length
  940. ("list_length", "3"),
  941. ("obj_length", "3"),
  942. # foreach in a match
  943. ("foreach_in_match", "first\nsecond\nthird"),
  944. # literal range in a foreach
  945. ("range_in_foreach1", "4269"),
  946. ("range_in_foreach2", "42454851545760636669727578"),
  947. ("range_in_foreach3", "42363024"),
  948. ("range_in_foreach4", "42"),
  949. ("dict_in_foreach1", "a1b2"),
  950. ("dict_in_foreach2", "12"),
  951. ("dict_in_foreach3", "1234"),
  952. ("str_in_foreach", "a b c d e f"),
  953. ("str_var_in_foreach", "f i r s t"),
  954. ("typed_dict_in_foreach", "Hello Alice33Hello Bob28"),
  955. # fstring operations
  956. ("float_format", "13212312312.123123"),
  957. ("float_format_0f", "13212312312"),
  958. ("float_format_1f", "13212312312.1"),
  959. ("float_format_2f", "13212312312.12"),
  960. ("float_format_comma", "13,212,312,312.123"),
  961. ("float_format_underscore", "13_212_312_312.123"),
  962. ("float_format_comma_0f", "13,212,312,312"),
  963. ("float_format_comma_1f", "13,212,312,312.1"),
  964. ("float_format_comma_2f", "13,212,312,312.12"),
  965. ("float_format_underscore_0f", "13_212_312_312"),
  966. ("float_format_underscore_1f", "13_212_312_312.1"),
  967. ("float_format_underscore_2f", "13_212_312_312.12"),
  968. ("obj_name", "hello"),
  969. ("obj_optional_none", ""),
  970. ("obj_optional_str", "hello"),
  971. ("obj_optional_none_get_none", ""),
  972. ("obj_optional_none_get_foo", "foo"),
  973. ("float_round", "13212312312"),
  974. ("float_round_2", "13212312312.12"),
  975. ]
  976. for tag, expected in tests:
  977. existing = driver.find_element(By.ID, tag).text
  978. assert existing == expected, (
  979. f"Failed on {tag}, expected {expected} but got {existing}"
  980. )
  981. # Highlight component with var query (does not plumb ID)
  982. assert driver.find_element(By.TAG_NAME, "mark").text == "second"