conftest.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. """Test fixtures."""
  2. import contextlib
  3. import os
  4. import platform
  5. from pathlib import Path
  6. from typing import Dict, Generator, List, Set, Union
  7. import pytest
  8. import reflex as rx
  9. from reflex.app import App
  10. from reflex.event import EventSpec
  11. @pytest.fixture
  12. def app() -> App:
  13. """A base app.
  14. Returns:
  15. The app.
  16. """
  17. return App()
  18. @pytest.fixture(scope="session")
  19. def windows_platform() -> Generator:
  20. """Check if system is windows.
  21. Yields:
  22. whether system is windows.
  23. """
  24. yield platform.system() == "Windows"
  25. @pytest.fixture
  26. def list_mutation_state():
  27. """Create a state with list mutation features.
  28. Returns:
  29. A state with list mutation features.
  30. """
  31. class TestState(rx.State):
  32. """The test state."""
  33. # plain list
  34. plain_friends = ["Tommy"]
  35. def make_friend(self):
  36. self.plain_friends.append("another-fd")
  37. def change_first_friend(self):
  38. self.plain_friends[0] = "Jenny"
  39. def unfriend_all_friends(self):
  40. self.plain_friends.clear()
  41. def unfriend_first_friend(self):
  42. del self.plain_friends[0]
  43. def remove_last_friend(self):
  44. self.plain_friends.pop()
  45. def make_friends_with_colleagues(self):
  46. colleagues = ["Peter", "Jimmy"]
  47. self.plain_friends.extend(colleagues)
  48. def remove_tommy(self):
  49. self.plain_friends.remove("Tommy")
  50. # list in dict
  51. friends_in_dict = {"Tommy": ["Jenny"]}
  52. def remove_jenny_from_tommy(self):
  53. self.friends_in_dict["Tommy"].remove("Jenny")
  54. def add_jimmy_to_tommy_friends(self):
  55. self.friends_in_dict["Tommy"].append("Jimmy")
  56. def tommy_has_no_fds(self):
  57. self.friends_in_dict["Tommy"].clear()
  58. # nested list
  59. friends_in_nested_list = [["Tommy"], ["Jenny"]]
  60. def remove_first_group(self):
  61. self.friends_in_nested_list.pop(0)
  62. def remove_first_person_from_first_group(self):
  63. self.friends_in_nested_list[0].pop(0)
  64. def add_jimmy_to_second_group(self):
  65. self.friends_in_nested_list[1].append("Jimmy")
  66. return TestState()
  67. @pytest.fixture
  68. def dict_mutation_state():
  69. """Create a state with dict mutation features.
  70. Returns:
  71. A state with dict mutation features.
  72. """
  73. class TestState(rx.State):
  74. """The test state."""
  75. # plain dict
  76. details = {"name": "Tommy"}
  77. def add_age(self):
  78. self.details.update({"age": 20}) # type: ignore
  79. def change_name(self):
  80. self.details["name"] = "Jenny"
  81. def remove_last_detail(self):
  82. self.details.popitem()
  83. def clear_details(self):
  84. self.details.clear()
  85. def remove_name(self):
  86. del self.details["name"]
  87. def pop_out_age(self):
  88. self.details.pop("age")
  89. # dict in list
  90. address = [{"home": "home address"}, {"work": "work address"}]
  91. def remove_home_address(self):
  92. self.address[0].pop("home")
  93. def add_street_to_home_address(self):
  94. self.address[0]["street"] = "street address"
  95. # nested dict
  96. friend_in_nested_dict = {"name": "Nikhil", "friend": {"name": "Alek"}}
  97. def change_friend_name(self):
  98. self.friend_in_nested_dict["friend"]["name"] = "Tommy"
  99. def remove_friend(self):
  100. self.friend_in_nested_dict.pop("friend")
  101. def add_friend_age(self):
  102. self.friend_in_nested_dict["friend"]["age"] = 30
  103. return TestState()
  104. class UploadState(rx.State):
  105. """The base state for uploading a file."""
  106. async def handle_upload1(self, files: List[rx.UploadFile]):
  107. """Handle the upload of a file.
  108. Args:
  109. files: The uploaded files.
  110. """
  111. pass
  112. class BaseState(rx.State):
  113. """The test base state."""
  114. pass
  115. class SubUploadState(BaseState):
  116. """The test substate."""
  117. img: str
  118. async def handle_upload(self, files: List[rx.UploadFile]):
  119. """Handle the upload of a file.
  120. Args:
  121. files: The uploaded files.
  122. """
  123. pass
  124. @pytest.fixture
  125. def upload_sub_state_event_spec():
  126. """Create an event Spec for a substate.
  127. Returns:
  128. Event Spec.
  129. """
  130. return EventSpec(handler=SubUploadState.handle_upload, upload=True) # type: ignore
  131. @pytest.fixture
  132. def upload_event_spec():
  133. """Create an event Spec for a multi-upload base state.
  134. Returns:
  135. Event Spec.
  136. """
  137. return EventSpec(handler=UploadState.handle_upload1, upload=True) # type: ignore
  138. @pytest.fixture
  139. def upload_state(tmp_path):
  140. """Create upload state.
  141. Args:
  142. tmp_path: pytest tmp_path
  143. Returns:
  144. The state
  145. """
  146. class FileUploadState(rx.State):
  147. """The base state for uploading a file."""
  148. img_list: List[str]
  149. async def handle_upload2(self, files):
  150. """Handle the upload of a file.
  151. Args:
  152. files: The uploaded files.
  153. """
  154. for file in files:
  155. upload_data = await file.read()
  156. outfile = f"{tmp_path}/{file.filename}"
  157. # Save the file.
  158. with open(outfile, "wb") as file_object:
  159. file_object.write(upload_data)
  160. # Update the img var.
  161. self.img_list.append(file.filename)
  162. async def multi_handle_upload(self, files: List[rx.UploadFile]):
  163. """Handle the upload of a file.
  164. Args:
  165. files: The uploaded files.
  166. """
  167. for file in files:
  168. upload_data = await file.read()
  169. outfile = f"{tmp_path}/{file.filename}"
  170. # Save the file.
  171. with open(outfile, "wb") as file_object:
  172. file_object.write(upload_data)
  173. # Update the img var.
  174. assert file.filename is not None
  175. self.img_list.append(file.filename)
  176. return FileUploadState
  177. @pytest.fixture
  178. def upload_sub_state(tmp_path):
  179. """Create upload substate.
  180. Args:
  181. tmp_path: pytest tmp_path
  182. Returns:
  183. The state
  184. """
  185. class FileState(rx.State):
  186. """The base state."""
  187. pass
  188. class FileUploadState(FileState):
  189. """The substate for uploading a file."""
  190. img_list: List[str]
  191. async def handle_upload2(self, files):
  192. """Handle the upload of a file.
  193. Args:
  194. files: The uploaded files.
  195. """
  196. for file in files:
  197. upload_data = await file.read()
  198. outfile = f"{tmp_path}/{file.filename}"
  199. # Save the file.
  200. with open(outfile, "wb") as file_object:
  201. file_object.write(upload_data)
  202. # Update the img var.
  203. self.img_list.append(file.filename)
  204. async def multi_handle_upload(self, files: List[rx.UploadFile]):
  205. """Handle the upload of a file.
  206. Args:
  207. files: The uploaded files.
  208. """
  209. for file in files:
  210. upload_data = await file.read()
  211. outfile = f"{tmp_path}/{file.filename}"
  212. # Save the file.
  213. with open(outfile, "wb") as file_object:
  214. file_object.write(upload_data)
  215. # Update the img var.
  216. assert file.filename is not None
  217. self.img_list.append(file.filename)
  218. return FileUploadState
  219. @pytest.fixture
  220. def upload_grand_sub_state(tmp_path):
  221. """Create upload grand-state.
  222. Args:
  223. tmp_path: pytest tmp_path
  224. Returns:
  225. The state
  226. """
  227. class BaseFileState(rx.State):
  228. """The base state."""
  229. pass
  230. class FileSubState(BaseFileState):
  231. """The substate."""
  232. pass
  233. class FileUploadState(FileSubState):
  234. """The grand-substate for uploading a file."""
  235. img_list: List[str]
  236. async def handle_upload2(self, files):
  237. """Handle the upload of a file.
  238. Args:
  239. files: The uploaded files.
  240. """
  241. for file in files:
  242. upload_data = await file.read()
  243. outfile = f"{tmp_path}/{file.filename}"
  244. # Save the file.
  245. with open(outfile, "wb") as file_object:
  246. file_object.write(upload_data)
  247. # Update the img var.
  248. assert file.filename is not None
  249. self.img_list.append(file.filename)
  250. async def multi_handle_upload(self, files: List[rx.UploadFile]):
  251. """Handle the upload of a file.
  252. Args:
  253. files: The uploaded files.
  254. """
  255. for file in files:
  256. upload_data = await file.read()
  257. outfile = f"{tmp_path}/{file.filename}"
  258. # Save the file.
  259. with open(outfile, "wb") as file_object:
  260. file_object.write(upload_data)
  261. # Update the img var.
  262. assert file.filename is not None
  263. self.img_list.append(file.filename)
  264. return FileUploadState
  265. @pytest.fixture
  266. def base_config_values() -> Dict:
  267. """Get base config values.
  268. Returns:
  269. Dictionary of base config values
  270. """
  271. return {"app_name": "app"}
  272. @pytest.fixture
  273. def base_db_config_values() -> Dict:
  274. """Get base DBConfig values.
  275. Returns:
  276. Dictionary of base db config values
  277. """
  278. return {"database": "db"}
  279. @pytest.fixture
  280. def sqlite_db_config_values(base_db_config_values) -> Dict:
  281. """Get sqlite DBConfig values.
  282. Args:
  283. base_db_config_values: Base DBConfig fixture.
  284. Returns:
  285. Dictionary of sqlite DBConfig values
  286. """
  287. base_db_config_values["engine"] = "sqlite"
  288. return base_db_config_values
  289. class GenState(rx.State):
  290. """A state with event handlers that generate multiple updates."""
  291. value: int
  292. def go(self, c: int):
  293. """Increment the value c times and update each time.
  294. Args:
  295. c: The number of times to increment.
  296. Yields:
  297. After each increment.
  298. """
  299. for _ in range(c):
  300. self.value += 1
  301. yield
  302. @pytest.fixture
  303. def gen_state() -> GenState:
  304. """A state.
  305. Returns:
  306. A test state.
  307. """
  308. return GenState # type: ignore
  309. @pytest.fixture
  310. def router_data_headers() -> Dict[str, str]:
  311. """Router data headers.
  312. Returns:
  313. client headers
  314. """
  315. return {
  316. "host": "localhost:8000",
  317. "connection": "Upgrade",
  318. "pragma": "no-cache",
  319. "cache-control": "no-cache",
  320. "user-agent": "Mock Agent",
  321. "upgrade": "websocket",
  322. "origin": "http://localhost:3000",
  323. "sec-websocket-version": "13",
  324. "accept-encoding": "gzip, deflate, br",
  325. "accept-language": "en-US,en;q=0.9",
  326. "cookie": "csrftoken=mocktoken; "
  327. "name=reflex;"
  328. " list_cookies=%5B%22some%22%2C%20%22random%22%2C%20%22cookies%22%5D;"
  329. " dict_cookies=%7B%22name%22%3A%20%22reflex%22%7D; val=true",
  330. "sec-websocket-key": "mock-websocket-key",
  331. "sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
  332. }
  333. @pytest.fixture
  334. def router_data(router_data_headers) -> Dict[str, str]:
  335. """Router data.
  336. Args:
  337. router_data_headers: Headers fixture.
  338. Returns:
  339. Dict of router data.
  340. """
  341. return { # type: ignore
  342. "pathname": "/",
  343. "query": {},
  344. "token": "b181904c-3953-4a79-dc18-ae9518c22f05",
  345. "sid": "9fpxSzPb9aFMb4wFAAAH",
  346. "headers": router_data_headers,
  347. "ip": "127.0.0.1",
  348. }
  349. # borrowed from py3.11
  350. class chdir(contextlib.AbstractContextManager):
  351. """Non thread-safe context manager to change the current working directory."""
  352. def __init__(self, path):
  353. """Prepare contextmanager.
  354. Args:
  355. path: the path to change to
  356. """
  357. self.path = path
  358. self._old_cwd = []
  359. def __enter__(self):
  360. """Save current directory and perform chdir."""
  361. self._old_cwd.append(Path(".").resolve())
  362. os.chdir(self.path)
  363. def __exit__(self, *excinfo):
  364. """Change back to previous directory on stack.
  365. Args:
  366. excinfo: sys.exc_info captured in the context block
  367. """
  368. os.chdir(self._old_cwd.pop())
  369. @pytest.fixture
  370. def tmp_working_dir(tmp_path):
  371. """Create a temporary directory and chdir to it.
  372. After the test executes, chdir back to the original working directory.
  373. Args:
  374. tmp_path: pytest tmp_path fixture creates per-test temp dir
  375. Yields:
  376. subdirectory of tmp_path which is now the current working directory.
  377. """
  378. working_dir = tmp_path / "working_dir"
  379. working_dir.mkdir()
  380. with chdir(working_dir):
  381. yield working_dir
  382. @pytest.fixture
  383. def mutable_state():
  384. """Create a Test state containing mutable types.
  385. Returns:
  386. A state object.
  387. """
  388. class OtherBase(rx.Base):
  389. bar: str = ""
  390. class CustomVar(rx.Base):
  391. foo: str = ""
  392. array: List[str] = []
  393. hashmap: Dict[str, str] = {}
  394. test_set: Set[str] = set()
  395. custom: OtherBase = OtherBase()
  396. class MutableTestState(rx.State):
  397. """A test state."""
  398. array: List[Union[str, List, Dict[str, str]]] = [
  399. "value",
  400. [1, 2, 3],
  401. {"key": "value"},
  402. ]
  403. hashmap: Dict[str, Union[List, str, Dict[str, str]]] = {
  404. "key": ["list", "of", "values"],
  405. "another_key": "another_value",
  406. "third_key": {"key": "value"},
  407. }
  408. test_set: Set[Union[str, int]] = {1, 2, 3, 4, "five"}
  409. custom: CustomVar = CustomVar()
  410. _be_custom: CustomVar = CustomVar()
  411. def reassign_mutables(self):
  412. self.array = ["modified_value", [1, 2, 3], {"mod_key": "mod_value"}]
  413. self.hashmap = {
  414. "mod_key": ["list", "of", "values"],
  415. "mod_another_key": "another_value",
  416. "mod_third_key": {"key": "value"},
  417. }
  418. self.test_set = {1, 2, 3, 4, "five"}
  419. return MutableTestState()
  420. @pytest.fixture
  421. def duplicate_substate():
  422. """Create a Test state that has duplicate child substates.
  423. Returns:
  424. The test state.
  425. """
  426. class TestState(rx.State):
  427. pass
  428. class ChildTestState(TestState): # type: ignore # noqa
  429. pass
  430. class ChildTestState(TestState): # type: ignore # noqa
  431. pass
  432. return TestState