link.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. use dioxus::prelude::*;
  2. use dioxus_history::{History, MemoryHistory};
  3. use dioxus_router::components::HistoryProvider;
  4. use std::rc::Rc;
  5. fn prepare<R: Routable>() -> String {
  6. prepare_at::<R>("/")
  7. }
  8. fn prepare_at<R: Routable>(at: impl ToString) -> String {
  9. prepare_at_with_base_path::<R>(at, "")
  10. }
  11. fn prepare_at_with_base_path<R: Routable>(at: impl ToString, base_path: impl ToString) -> String {
  12. let mut vdom = VirtualDom::new_with_props(
  13. App,
  14. AppProps::<R> {
  15. at: at.to_string(),
  16. base_path: base_path.to_string(),
  17. phantom: std::marker::PhantomData,
  18. },
  19. );
  20. vdom.rebuild_in_place();
  21. return dioxus_ssr::render(&vdom);
  22. #[derive(Props)]
  23. struct AppProps<R: Routable> {
  24. at: String,
  25. base_path: String,
  26. phantom: std::marker::PhantomData<R>,
  27. }
  28. impl<R: Routable> Clone for AppProps<R> {
  29. fn clone(&self) -> Self {
  30. Self {
  31. at: self.at.clone(),
  32. base_path: self.base_path.clone(),
  33. phantom: std::marker::PhantomData,
  34. }
  35. }
  36. }
  37. impl<R: Routable> PartialEq for AppProps<R> {
  38. fn eq(&self, _other: &Self) -> bool {
  39. false
  40. }
  41. }
  42. #[allow(non_snake_case)]
  43. fn App<R: Routable>(props: AppProps<R>) -> Element {
  44. rsx! {
  45. h1 { "App" }
  46. HistoryProvider {
  47. history: move |_| Rc::new(MemoryHistory::with_initial_path(props.at.clone()).with_prefix(props.base_path.clone())) as Rc<dyn History>,
  48. Router::<R> {}
  49. }
  50. }
  51. }
  52. }
  53. #[test]
  54. fn href_internal() {
  55. #[derive(Routable, Clone)]
  56. enum Route {
  57. #[route("/")]
  58. Root {},
  59. #[route("/test")]
  60. Test {},
  61. }
  62. #[component]
  63. fn Test() -> Element {
  64. unimplemented!()
  65. }
  66. #[component]
  67. fn Root() -> Element {
  68. rsx! {
  69. Link {
  70. to: Route::Test {},
  71. "Link"
  72. }
  73. }
  74. }
  75. let expected = format!("<h1>App</h1><a {href}>Link</a>", href = r#"href="/test""#,);
  76. assert_eq!(prepare::<Route>(), expected);
  77. // The base path should be added to the front of internal links
  78. let base_path = "/deeply/nested/path";
  79. let expected = format!(
  80. "<h1>App</h1><a {href}>Link</a>",
  81. href = r#"href="/deeply/nested/path/test""#,
  82. );
  83. assert_eq!(prepare_at_with_base_path::<Route>("/", base_path), expected);
  84. }
  85. #[test]
  86. fn href_external() {
  87. #[derive(Routable, Clone)]
  88. enum Route {
  89. #[route("/")]
  90. Root {},
  91. #[route("/test")]
  92. Test {},
  93. }
  94. #[component]
  95. fn Test() -> Element {
  96. unimplemented!()
  97. }
  98. #[component]
  99. fn Root() -> Element {
  100. rsx! {
  101. Link {
  102. to: "https://dioxuslabs.com/",
  103. "Link"
  104. }
  105. }
  106. }
  107. let expected = format!(
  108. "<h1>App</h1><a {href} {rel}>Link</a>",
  109. href = r#"href="https://dioxuslabs.com/""#,
  110. rel = r#"rel="noopener noreferrer""#,
  111. );
  112. assert_eq!(prepare::<Route>(), expected);
  113. // The base path should not effect external links
  114. assert_eq!(
  115. prepare_at_with_base_path::<Route>("/", "/deeply/nested/path"),
  116. expected
  117. );
  118. }
  119. #[test]
  120. fn with_class() {
  121. #[derive(Routable, Clone)]
  122. enum Route {
  123. #[route("/")]
  124. Root {},
  125. #[route("/test")]
  126. Test {},
  127. }
  128. #[component]
  129. fn Test() -> Element {
  130. unimplemented!()
  131. }
  132. #[component]
  133. fn Root() -> Element {
  134. rsx! {
  135. Link {
  136. to: Route::Test {},
  137. class: "test_class",
  138. "Link"
  139. }
  140. }
  141. }
  142. let expected = format!(
  143. "<h1>App</h1><a {href} {class}>Link</a>",
  144. href = r#"href="/test""#,
  145. class = r#"class="test_class""#,
  146. );
  147. assert_eq!(prepare::<Route>(), expected);
  148. }
  149. #[test]
  150. fn with_active_class_active() {
  151. #[derive(Routable, Clone)]
  152. enum Route {
  153. #[route("/")]
  154. Root {},
  155. }
  156. #[component]
  157. fn Root() -> Element {
  158. rsx! {
  159. Link {
  160. to: Route::Root {},
  161. active_class: "active_class".to_string(),
  162. class: "test_class",
  163. "Link"
  164. }
  165. }
  166. }
  167. let expected = format!(
  168. "<h1>App</h1><a {href} {class} {aria}>Link</a>",
  169. href = r#"href="/""#,
  170. class = r#"class="test_class active_class""#,
  171. aria = r#"aria-current="page""#,
  172. );
  173. assert_eq!(prepare::<Route>(), expected);
  174. }
  175. #[test]
  176. fn with_active_class_inactive() {
  177. #[derive(Routable, Clone)]
  178. enum Route {
  179. #[route("/")]
  180. Root {},
  181. #[route("/test")]
  182. Test {},
  183. }
  184. #[component]
  185. fn Test() -> Element {
  186. unimplemented!()
  187. }
  188. #[component]
  189. fn Root() -> Element {
  190. rsx! {
  191. Link {
  192. to: Route::Test {},
  193. active_class: "active_class".to_string(),
  194. class: "test_class",
  195. "Link"
  196. }
  197. }
  198. }
  199. let expected = format!(
  200. "<h1>App</h1><a {href} {class}>Link</a>",
  201. href = r#"href="/test""#,
  202. class = r#"class="test_class""#,
  203. );
  204. assert_eq!(prepare::<Route>(), expected);
  205. }
  206. #[test]
  207. fn with_id() {
  208. #[derive(Routable, Clone)]
  209. enum Route {
  210. #[route("/")]
  211. Root {},
  212. #[route("/test")]
  213. Test {},
  214. }
  215. #[component]
  216. fn Test() -> Element {
  217. unimplemented!()
  218. }
  219. #[component]
  220. fn Root() -> Element {
  221. rsx! {
  222. Link {
  223. to: Route::Test {},
  224. id: "test_id",
  225. "Link"
  226. }
  227. }
  228. }
  229. let expected = format!(
  230. "<h1>App</h1><a {href} {id}>Link</a>",
  231. href = r#"href="/test""#,
  232. id = r#"id="test_id""#,
  233. );
  234. assert_eq!(prepare::<Route>(), expected);
  235. }
  236. #[test]
  237. fn with_new_tab() {
  238. #[derive(Routable, Clone)]
  239. enum Route {
  240. #[route("/")]
  241. Root {},
  242. #[route("/test")]
  243. Test {},
  244. }
  245. #[component]
  246. fn Test() -> Element {
  247. unimplemented!()
  248. }
  249. #[component]
  250. fn Root() -> Element {
  251. rsx! {
  252. Link {
  253. to: Route::Test {},
  254. new_tab: true,
  255. "Link"
  256. }
  257. }
  258. }
  259. let expected = format!(
  260. "<h1>App</h1><a {href} {target}>Link</a>",
  261. href = r#"href="/test""#,
  262. target = r#"target="_blank""#
  263. );
  264. assert_eq!(prepare::<Route>(), expected);
  265. }
  266. #[test]
  267. fn with_new_tab_external() {
  268. #[derive(Routable, Clone)]
  269. enum Route {
  270. #[route("/")]
  271. Root {},
  272. }
  273. #[component]
  274. fn Root() -> Element {
  275. rsx! {
  276. Link {
  277. to: "https://dioxuslabs.com/",
  278. new_tab: true,
  279. "Link"
  280. }
  281. }
  282. }
  283. let expected = format!(
  284. "<h1>App</h1><a {href} {rel} {target}>Link</a>",
  285. href = r#"href="https://dioxuslabs.com/""#,
  286. rel = r#"rel="noopener noreferrer""#,
  287. target = r#"target="_blank""#
  288. );
  289. assert_eq!(prepare::<Route>(), expected);
  290. }
  291. #[test]
  292. fn with_rel() {
  293. #[derive(Routable, Clone)]
  294. enum Route {
  295. #[route("/")]
  296. Root {},
  297. #[route("/test")]
  298. Test {},
  299. }
  300. #[component]
  301. fn Test() -> Element {
  302. unimplemented!()
  303. }
  304. #[component]
  305. fn Root() -> Element {
  306. rsx! {
  307. Link {
  308. to: Route::Test {},
  309. rel: "test_rel".to_string(),
  310. "Link"
  311. }
  312. }
  313. }
  314. let expected = format!(
  315. "<h1>App</h1><a {href} {rel}>Link</a>",
  316. href = r#"href="/test""#,
  317. rel = r#"rel="test_rel""#,
  318. );
  319. assert_eq!(prepare::<Route>(), expected);
  320. }
  321. #[test]
  322. fn with_child_route() {
  323. #[derive(Routable, Clone, PartialEq, Debug)]
  324. enum ChildRoute {
  325. #[route("/")]
  326. ChildRoot {},
  327. #[route("/:not_static")]
  328. NotStatic { not_static: String },
  329. }
  330. #[derive(Routable, Clone, PartialEq, Debug)]
  331. enum Route {
  332. #[route("/")]
  333. Root {},
  334. #[route("/test")]
  335. Test {},
  336. #[child("/child")]
  337. Nested { child: ChildRoute },
  338. }
  339. #[component]
  340. fn Test() -> Element {
  341. unimplemented!()
  342. }
  343. #[component]
  344. fn Root() -> Element {
  345. rsx! {
  346. Link {
  347. to: Route::Test {},
  348. "Parent Link"
  349. }
  350. Link {
  351. to: Route::Nested { child: ChildRoute::NotStatic { not_static: "this-is-a-child-route".to_string() } },
  352. "Child Link"
  353. }
  354. }
  355. }
  356. #[component]
  357. fn ChildRoot() -> Element {
  358. rsx! {
  359. Link {
  360. to: Route::Test {},
  361. "Parent Link"
  362. }
  363. Link {
  364. to: ChildRoute::NotStatic { not_static: "this-is-a-child-route".to_string() },
  365. "Child Link 1"
  366. }
  367. Link {
  368. to: Route::Nested { child: ChildRoute::NotStatic { not_static: "this-is-a-child-route".to_string() } },
  369. "Child Link 2"
  370. }
  371. }
  372. }
  373. #[component]
  374. fn NotStatic(not_static: String) -> Element {
  375. unimplemented!()
  376. }
  377. assert_eq!(
  378. prepare_at::<Route>("/"),
  379. "<h1>App</h1><a href=\"/test\">Parent Link</a><a href=\"/child/this-is-a-child-route\">Child Link</a>"
  380. );
  381. assert_eq!(
  382. prepare_at::<Route>("/child"),
  383. "<h1>App</h1><a href=\"/test\">Parent Link</a><a href=\"/child/this-is-a-child-route\">Child Link 1</a><a href=\"/child/this-is-a-child-route\">Child Link 2</a>"
  384. );
  385. }
  386. #[test]
  387. fn with_hash_segment() {
  388. #[derive(Routable, Clone)]
  389. enum Route {
  390. #[route("/#:data")]
  391. Root { data: String },
  392. }
  393. #[component]
  394. fn Root(data: String) -> Element {
  395. rsx! {
  396. Link {
  397. to: Route::Root { data: "test".to_string() },
  398. "Link"
  399. }
  400. Link {
  401. to: Route::Root { data: "".to_string() },
  402. "Empty"
  403. }
  404. }
  405. }
  406. assert_eq!(
  407. prepare_at::<Route>("/#test"),
  408. "<h1>App</h1><a href=\"/#test\" aria-current=\"page\">Link</a><a href=\"/\">Empty</a>"
  409. );
  410. }