mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9052804ab4 | ||
|
|
e95c903e85 | ||
|
|
8a179e6f45 | ||
|
|
e765f99016 | ||
|
|
30548eca31 | ||
|
|
d04d4c77f9 | ||
|
|
5c75928b5b | ||
|
|
abc5631654 | ||
|
|
40e5288ac1 | ||
|
|
6ee72f42e2 | ||
|
|
5cfe7f6b5e | ||
|
|
0404efd5c3 | ||
|
|
cd2904f6a6 | ||
|
|
5633148047 | ||
|
|
330920eae2 | ||
|
|
a94bc0a6da | ||
|
|
f85e01f4d6 |
42
Cargo.lock
generated
42
Cargo.lock
generated
@@ -1734,7 +1734,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||
|
||||
[[package]]
|
||||
name = "leptos"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -1784,7 +1784,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_actix"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-http",
|
||||
@@ -1809,7 +1809,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_axum"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"axum",
|
||||
@@ -1832,7 +1832,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_config"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"config",
|
||||
"regex",
|
||||
@@ -1846,7 +1846,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_dom"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"leptos",
|
||||
@@ -1863,7 +1863,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_hot_reload"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -1879,7 +1879,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_integration_utils"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"hydration_context",
|
||||
@@ -1892,7 +1892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
@@ -1911,7 +1911,7 @@ dependencies = [
|
||||
"rstml",
|
||||
"serde",
|
||||
"server_fn",
|
||||
"server_fn_macro 0.7.7",
|
||||
"server_fn_macro 0.7.8",
|
||||
"syn 2.0.98",
|
||||
"tracing",
|
||||
"trybuild",
|
||||
@@ -1921,7 +1921,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"indexmap",
|
||||
@@ -1936,7 +1936,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"either_of",
|
||||
@@ -1960,7 +1960,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"leptos_macro",
|
||||
"leptos_router",
|
||||
@@ -1972,7 +1972,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_server"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -2658,7 +2658,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-lock",
|
||||
@@ -2680,7 +2680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"guardian",
|
||||
@@ -2697,7 +2697,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"convert_case 0.7.1",
|
||||
"proc-macro-error2",
|
||||
@@ -3155,7 +3155,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"axum",
|
||||
@@ -3211,7 +3211,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"convert_case 0.6.0",
|
||||
@@ -3223,9 +3223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro_default"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
dependencies = [
|
||||
"server_fn_macro 0.7.7",
|
||||
"server_fn_macro 0.7.8",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
@@ -3419,7 +3419,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tachys"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-trait",
|
||||
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -40,7 +40,7 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
@@ -51,16 +51,16 @@ const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
itertools = "0.14.0"
|
||||
leptos = { path = "./leptos", version = "0.7.7" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.7" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.7" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.7" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.7" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.7" }
|
||||
leptos_router = { path = "./router", version = "0.7.7" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.7" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.7" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.7" }
|
||||
leptos = { path = "./leptos", version = "0.7.8" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.8" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.8" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.8" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.8" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.8" }
|
||||
leptos_router = { path = "./router", version = "0.7.8" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.8" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.8" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.8" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
@@ -68,9 +68,9 @@ reactive_graph = { path = "./reactive_graph", version = "0.1.7" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.7" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.7" }
|
||||
serde_json = "1.0.0"
|
||||
server_fn = { path = "./server_fn", version = "0.7.7" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.7" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.7" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.8" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.8" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.8" }
|
||||
tachys = { path = "./tachys", version = "0.1.7" }
|
||||
wasm-bindgen = { version = "0.2.100" }
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn RouterExample() -> impl IntoView {
|
||||
// contexts are passed down through the route tree
|
||||
provide_context(ExampleContext(0));
|
||||
|
||||
// this signal will be ued to set whether we are allowed to access a protected route
|
||||
// this signal will be used to set whether we are allowed to access a protected route
|
||||
let (logged_in, set_logged_in) = signal(true);
|
||||
let (is_routing, set_is_routing) = signal(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
@check_instrumented_issue_3719
|
||||
Feature: Using instrumented counters to test regression from #3502.
|
||||
Check that the suspend/suspense and the underlying resources are
|
||||
called with the expected number of times. If this was already in
|
||||
place by #3502 (5c43c18) it should have caught this regression.
|
||||
For a better minimum demonstration see #3719.
|
||||
|
||||
Background:
|
||||
|
||||
Given I see the app
|
||||
And I select the mode Instrumented
|
||||
|
||||
Scenario: follow all paths via CSR avoids #3502
|
||||
Given I select the following links
|
||||
| Item Listing |
|
||||
| Item 1 |
|
||||
| Inspect path2 |
|
||||
| Inspect path2/field3 |
|
||||
And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Inspect path2/field1 |
|
||||
| Inspect path2/field2 |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 0 |
|
||||
| item_inspect | 2 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 0 |
|
||||
| get_item | 0 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 2 |
|
||||
|
||||
# To show that starting directly from within a param will simply
|
||||
# cause the problem.
|
||||
Scenario: Quicker way to demonstrate regression caused by #3502
|
||||
Given I select the link Target 123
|
||||
# And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Inspect path2/field1 |
|
||||
| Inspect path2/field2 |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 0 |
|
||||
| item_inspect | 3 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 1 |
|
||||
| get_item | 1 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 4 |
|
||||
|
||||
Scenario: Follow paths ordinarily down to a target
|
||||
Given I select the following links
|
||||
| Item Listing |
|
||||
| Item 1 |
|
||||
And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Target 4## |
|
||||
| Target 3## |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 2 |
|
||||
| item_inspect | 0 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 0 |
|
||||
| get_item | 2 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 0 |
|
||||
|
||||
Scenario: Same as above, but add a refresh to test hydration
|
||||
Given I select the following links
|
||||
| Item Listing |
|
||||
| Item 1 |
|
||||
And I refresh the page
|
||||
And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Target 4## |
|
||||
| Target 3## |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 2 |
|
||||
| item_inspect | 0 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 0 |
|
||||
| get_item | 2 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 0 |
|
||||
|
||||
@@ -3,12 +3,28 @@ mod fixtures;
|
||||
use anyhow::Result;
|
||||
use cucumber::World;
|
||||
use fixtures::world::AppWorld;
|
||||
use std::{ffi::OsStr, fs::read_dir};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
AppWorld::cucumber()
|
||||
.fail_on_skipped()
|
||||
.run_and_exit("./features")
|
||||
.await;
|
||||
// Normally the below is done, but it's now gotten to the point of
|
||||
// having a sufficient number of tests where the resource contention
|
||||
// of the concurrently running browsers will cause failures on CI.
|
||||
// AppWorld::cucumber()
|
||||
// .fail_on_skipped()
|
||||
// .run_and_exit("./features")
|
||||
// .await;
|
||||
|
||||
// Mitigate the issue by manually stepping through each feature,
|
||||
// rather than letting cucumber glob them and dispatch all at once.
|
||||
for entry in read_dir("./features")? {
|
||||
let path = entry?.path();
|
||||
if path.extension() == Some(OsStr::new("feature")) {
|
||||
AppWorld::cucumber()
|
||||
.fail_on_skipped()
|
||||
.run_and_exit(path)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use leptos_router::{
|
||||
hooks::use_params,
|
||||
nested_router::Outlet,
|
||||
params::Params,
|
||||
MatchNestedRoutes, ParamSegment, SsrMode, StaticSegment, WildcardSegment,
|
||||
ParamSegment, SsrMode, StaticSegment, WildcardSegment,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -21,6 +21,7 @@ pub(super) mod counter {
|
||||
pub struct Counter(AtomicU32);
|
||||
|
||||
impl Counter {
|
||||
#[allow(dead_code)]
|
||||
pub const fn new() -> Self {
|
||||
Self(AtomicU32::new(0))
|
||||
}
|
||||
@@ -203,20 +204,20 @@ pub struct SuspenseCounters {
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn InstrumentedRoutes() -> impl MatchNestedRoutes + Clone {
|
||||
pub fn InstrumentedRoutes() -> impl leptos_router::MatchNestedRoutes + Clone {
|
||||
// TODO should make this mode configurable via feature flag?
|
||||
let ssr = SsrMode::Async;
|
||||
view! {
|
||||
<ParentRoute path=StaticSegment("instrumented") view=InstrumentedRoot ssr>
|
||||
<Route path=StaticSegment("/") view=InstrumentedTop/>
|
||||
<Route path=StaticSegment("/") view=InstrumentedTop />
|
||||
<ParentRoute path=StaticSegment("item") view=ItemRoot>
|
||||
<Route path=StaticSegment("/") view=ItemListing/>
|
||||
<Route path=StaticSegment("/") view=ItemListing />
|
||||
<ParentRoute path=ParamSegment("id") view=ItemTop>
|
||||
<Route path=StaticSegment("/") view=ItemOverview/>
|
||||
<Route path=WildcardSegment("path") view=ItemInspect/>
|
||||
<Route path=StaticSegment("/") view=ItemOverview />
|
||||
<Route path=WildcardSegment("path") view=ItemInspect />
|
||||
</ParentRoute>
|
||||
</ParentRoute>
|
||||
<Route path=StaticSegment("counters") view=ShowCounters/>
|
||||
<Route path=StaticSegment("counters") view=ShowCounters />
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
@@ -279,32 +280,41 @@ fn InstrumentedRoot() -> impl IntoView {
|
||||
<section id="instrumented">
|
||||
<nav>
|
||||
<a href="/">"Site Root"</a>
|
||||
<A href="./" exact=true>"Instrumented Root"</A>
|
||||
<A href="item/" strict_trailing_slash=true>"Item Listing"</A>
|
||||
<A href="counters" strict_trailing_slash=true>"Counters"</A>
|
||||
<A href="./" exact=true>
|
||||
"Instrumented Root"
|
||||
</A>
|
||||
<A href="item/" strict_trailing_slash=true>
|
||||
"Item Listing"
|
||||
</A>
|
||||
<A href="counters" strict_trailing_slash=true>
|
||||
"Counters"
|
||||
</A>
|
||||
</nav>
|
||||
<FieldNavPortlet/>
|
||||
<Outlet/>
|
||||
<Suspense>{
|
||||
move || Suspend::new(async move {
|
||||
<FieldNavPortlet />
|
||||
<Outlet />
|
||||
<Suspense>
|
||||
{move || Suspend::new(async move {
|
||||
let clear_suspense_counters = move |_| {
|
||||
counters.update(|c| *c = SuspenseCounters::default());
|
||||
};
|
||||
csr_ticket.get().map(|ticket| {
|
||||
let ticket = ticket.0;
|
||||
view! {
|
||||
<ActionForm action=reset_counters>
|
||||
<input type="hidden" name="ticket" value=format!("{ticket}") />
|
||||
<input
|
||||
id="reset-csr-counters"
|
||||
type="submit"
|
||||
value="Reset CSR Counters"
|
||||
on:click=clear_suspense_counters/>
|
||||
</ActionForm>
|
||||
}
|
||||
})
|
||||
})
|
||||
}</Suspense>
|
||||
csr_ticket
|
||||
.get()
|
||||
.map(|ticket| {
|
||||
let ticket = ticket.0;
|
||||
view! {
|
||||
<ActionForm action=reset_counters>
|
||||
<input type="hidden" name="ticket" value=format!("{ticket}") />
|
||||
<input
|
||||
id="reset-csr-counters"
|
||||
type="submit"
|
||||
value="Reset CSR Counters"
|
||||
on:click=clear_suspense_counters
|
||||
/>
|
||||
</ActionForm>
|
||||
}
|
||||
})
|
||||
})}
|
||||
</Suspense>
|
||||
<footer>
|
||||
<nav>
|
||||
<A href="item/3/">"Target 3##"</A>
|
||||
@@ -323,11 +333,17 @@ fn InstrumentedRoot() -> impl IntoView {
|
||||
fn InstrumentedTop() -> impl IntoView {
|
||||
view! {
|
||||
<h1>"Instrumented Tests"</h1>
|
||||
<p>"These tests validates the number of invocations of server functions and suspenses per access."</p>
|
||||
<p>
|
||||
"These tests validates the number of invocations of server functions and suspenses per access."
|
||||
</p>
|
||||
<ul>
|
||||
// not using `A` because currently some bugs with artix
|
||||
<li><a href="item/">"Item Listing"</a></li>
|
||||
<li><a href="item/4/path1/">"Target 41#"</a></li>
|
||||
<li>
|
||||
<a href="item/">"Item Listing"</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="item/4/path1/">"Target 41#"</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
@@ -342,7 +358,7 @@ fn ItemRoot() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h2>"<ItemRoot/>"</h2>
|
||||
<Outlet/>
|
||||
<Outlet />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +376,9 @@ fn ItemListing() -> impl IntoView {
|
||||
// adding an extra `/` in artix; manually construct `a` instead.
|
||||
// <li><A href=format!("./{item}/")>"Item "{item}</A></li>
|
||||
view! {
|
||||
<li><a href=format!("/instrumented/item/{item}/")>"Item "{item}</a></li>
|
||||
<li>
|
||||
<a href=format!("/instrumented/item/{item}/")>"Item "{item}</a>
|
||||
</li>
|
||||
}
|
||||
)
|
||||
.collect_view()
|
||||
@@ -373,9 +391,7 @@ fn ItemListing() -> impl IntoView {
|
||||
view! {
|
||||
<h3>"<ItemListing/>"</h3>
|
||||
<ul>
|
||||
<Suspense>
|
||||
{item_listing}
|
||||
</Suspense>
|
||||
<Suspense>{item_listing}</Suspense>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
@@ -402,7 +418,7 @@ fn ItemTop() -> impl IntoView {
|
||||
));
|
||||
view! {
|
||||
<h4>"<ItemTop/>"</h4>
|
||||
<Outlet/>
|
||||
<Outlet />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,24 +428,29 @@ fn ItemOverview() -> impl IntoView {
|
||||
let resource = expect_context::<Resource<Option<GetItemResult>>>();
|
||||
let item_view = move || {
|
||||
Suspend::new(async move {
|
||||
let result = resource.await.map(|GetItemResult(item, names)| view! {
|
||||
<p>{format!("Viewing {item:?}")}</p>
|
||||
<ul>{
|
||||
names.into_iter()
|
||||
.map(|name| {
|
||||
// FIXME seems like relative link isn't working, it is currently
|
||||
// adding an extra `/` in artix; manually construct `a` instead.
|
||||
// <li><A href=format!("./{name}/")>{format!("Inspect {name}")}</A></li>
|
||||
let id = item.id;
|
||||
view! {
|
||||
<li><a href=format!("/instrumented/item/{id}/{name}/")>
|
||||
"Inspect "{name.clone()}
|
||||
</a></li>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}</ul>
|
||||
});
|
||||
let result = resource.await.map(|GetItemResult(item, names)| {
|
||||
view! {
|
||||
<p>{format!("Viewing {item:?}")}</p>
|
||||
<ul>
|
||||
{names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let id = item.id;
|
||||
// FIXME seems like relative link isn't working, it is currently
|
||||
// adding an extra `/` in artix; manually construct `a` instead.
|
||||
// <li><A href=format!("./{name}/")>{format!("Inspect {name}")}</A></li>
|
||||
view! {
|
||||
<li>
|
||||
<a href=format!(
|
||||
"/instrumented/item/{id}/{name}/",
|
||||
)>"Inspect "{name.clone()}</a>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view()}
|
||||
</ul>
|
||||
}
|
||||
});
|
||||
suspense_counters.update_untracked(|c| c.item_overview += 1);
|
||||
result
|
||||
})
|
||||
@@ -437,9 +458,7 @@ fn ItemOverview() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h5>"<ItemOverview/>"</h5>
|
||||
<Suspense>
|
||||
{item_view}
|
||||
</Suspense>
|
||||
<Suspense>{item_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,8 +492,9 @@ fn ItemInspect() -> impl IntoView {
|
||||
// result
|
||||
},
|
||||
);
|
||||
on_cleanup(|| {
|
||||
if let Some(c) = use_context::<WriteSignal<Option<FieldNavCtx>>>() {
|
||||
let ws = use_context::<WriteSignal<Option<FieldNavCtx>>>();
|
||||
on_cleanup(move || {
|
||||
if let Some(c) = ws {
|
||||
c.set(None);
|
||||
}
|
||||
});
|
||||
@@ -496,23 +516,26 @@ fn ItemInspect() -> impl IntoView {
|
||||
));
|
||||
view! {
|
||||
<p>{format!("Inspecting {item:?}")}</p>
|
||||
<ul>{
|
||||
fields.iter()
|
||||
<ul>
|
||||
{fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
// FIXME seems like relative link to root for a wildcard isn't
|
||||
// working as expected, so manually construct `a` instead.
|
||||
// let text = format!("Inspect {name}/{field}");
|
||||
// view! {
|
||||
// <li><A href=format!("{field}")>{text}</A></li>
|
||||
// <li><A href=format!("{field}")>{text}</A></li>
|
||||
// }
|
||||
view! {
|
||||
<li><a href=format!("/instrumented/item/{id}/{name}/{field}")>{
|
||||
format!("Inspect {name}/{field}")
|
||||
}</a></li>
|
||||
<li>
|
||||
<a href=format!(
|
||||
"/instrumented/item/{id}/{name}/{field}",
|
||||
)>{format!("Inspect {name}/{field}")}</a>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}</ul>
|
||||
.collect_view()}
|
||||
</ul>
|
||||
}
|
||||
});
|
||||
suspense_counters.update_untracked(|c| c.item_inspect += 1);
|
||||
@@ -527,9 +550,7 @@ fn ItemInspect() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h5>"<ItemInspect/>"</h5>
|
||||
<Suspense>
|
||||
{inspect_view}
|
||||
</Suspense>
|
||||
<Suspense>{inspect_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +611,8 @@ fn ShowCounters() -> impl IntoView {
|
||||
id="reset-counters"
|
||||
type="submit"
|
||||
value="Reset Counters"
|
||||
on:click=clear_suspense_counters/>
|
||||
on:click=clear_suspense_counters
|
||||
/>
|
||||
</ActionForm>
|
||||
}
|
||||
})
|
||||
@@ -601,20 +623,23 @@ fn ShowCounters() -> impl IntoView {
|
||||
<h2>"Counters"</h2>
|
||||
|
||||
<h3 id="suspend-calls">"Suspend Calls"</h3>
|
||||
{move || suspense_counters.with(|c| view! {
|
||||
<dl>
|
||||
<dt>"item_listing"</dt>
|
||||
<dd id="item_listing">{c.item_listing}</dd>
|
||||
<dt>"item_overview"</dt>
|
||||
<dd id="item_overview">{c.item_overview}</dd>
|
||||
<dt>"item_inspect"</dt>
|
||||
<dd id="item_inspect">{c.item_inspect}</dd>
|
||||
</dl>
|
||||
})}
|
||||
{move || {
|
||||
suspense_counters
|
||||
.with(|c| {
|
||||
view! {
|
||||
<dl>
|
||||
<dt>"item_listing"</dt>
|
||||
<dd id="item_listing">{c.item_listing}</dd>
|
||||
<dt>"item_overview"</dt>
|
||||
<dd id="item_overview">{c.item_overview}</dd>
|
||||
<dt>"item_inspect"</dt>
|
||||
<dd id="item_inspect">{c.item_inspect}</dd>
|
||||
</dl>
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
||||
<Suspense>
|
||||
{counter_view}
|
||||
</Suspense>
|
||||
<Suspense>{counter_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,17 +667,17 @@ pub fn FieldNavPortlet() -> impl IntoView {
|
||||
view! {
|
||||
<div id="FieldNavPortlet">
|
||||
<span>"FieldNavPortlet:"</span>
|
||||
<nav>{
|
||||
ctx.0.map(|ctx| {
|
||||
ctx.into_iter()
|
||||
.map(|FieldNavItem { href, text }| {
|
||||
view! {
|
||||
<A href=href>{text}</A>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
})
|
||||
}</nav>
|
||||
<nav>
|
||||
{ctx
|
||||
.0
|
||||
.map(|ctx| {
|
||||
ctx.into_iter()
|
||||
.map(|FieldNavItem { href, text }| {
|
||||
view! { <A href=href>{text}</A> }
|
||||
})
|
||||
.collect_view()
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
|
||||
@@ -172,12 +172,6 @@ where
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
|
||||
notifier.notify();
|
||||
} else if cfg!(feature = "ssr") {
|
||||
panic!(
|
||||
"Reading from a LocalResource outside Suspense in `ssr` mode \
|
||||
will cause the response to hang, because LocalResources are \
|
||||
always pending on the server."
|
||||
);
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
@@ -364,12 +358,6 @@ where
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
|
||||
notifier.notify();
|
||||
} else if cfg!(feature = "ssr") {
|
||||
panic!(
|
||||
"Reading from a LocalResource outside Suspense in `ssr` mode \
|
||||
will cause the response to hang, because LocalResources are \
|
||||
always pending on the server."
|
||||
);
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -8,9 +8,9 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { version = "0.7.7", features = ["csr"] }
|
||||
leptos_meta = { version = "0.7.7" }
|
||||
leptos_router = { version = "0.7.7" }
|
||||
leptos = { version = "0.7.8", features = ["csr"] }
|
||||
leptos_meta = { version = "0.7.8" }
|
||||
leptos_router = { version = "0.7.8" }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -60,6 +60,38 @@ pub struct Owner {
|
||||
pub(crate) shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
fn downgrade(&self) -> WeakOwner {
|
||||
WeakOwner {
|
||||
inner: Arc::downgrade(&self.inner),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: self.shared_context.as_ref().map(Arc::downgrade),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WeakOwner {
|
||||
inner: Weak<RwLock<OwnerInner>>,
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: Option<Weak<dyn SharedContext + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl WeakOwner {
|
||||
fn upgrade(&self) -> Option<Owner> {
|
||||
self.inner.upgrade().map(|inner| {
|
||||
#[cfg(feature = "hydration")]
|
||||
let shared_context =
|
||||
self.shared_context.as_ref().and_then(|sc| sc.upgrade());
|
||||
Owner {
|
||||
inner,
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Owner {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
@@ -67,7 +99,7 @@ impl PartialEq for Owner {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static OWNER: RefCell<Option<Owner>> = Default::default();
|
||||
static OWNER: RefCell<Option<WeakOwner>> = Default::default();
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
@@ -107,12 +139,16 @@ impl Owner {
|
||||
/// Creates a new `Owner` and registers it as a child of the current `Owner`, if there is one.
|
||||
pub fn new() -> Self {
|
||||
#[cfg(not(feature = "hydration"))]
|
||||
let parent = OWNER
|
||||
.with(|o| o.borrow().as_ref().map(|o| Arc::downgrade(&o.inner)));
|
||||
let parent = OWNER.with(|o| {
|
||||
o.borrow()
|
||||
.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.map(|o| Arc::downgrade(&o.inner))
|
||||
});
|
||||
#[cfg(feature = "hydration")]
|
||||
let (parent, shared_context) = OWNER
|
||||
.with(|o| {
|
||||
o.borrow().as_ref().map(|o| {
|
||||
o.borrow().as_ref().and_then(|o| o.upgrade()).map(|o| {
|
||||
(Some(Arc::downgrade(&o.inner)), o.shared_context.clone())
|
||||
})
|
||||
})
|
||||
@@ -200,7 +236,7 @@ impl Owner {
|
||||
|
||||
/// Sets this as the current `Owner`.
|
||||
pub fn set(&self) {
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(self.downgrade()));
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
}
|
||||
@@ -208,7 +244,9 @@ impl Owner {
|
||||
/// Runs the given function with this as the current `Owner`.
|
||||
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
|
||||
let prev = {
|
||||
OWNER.with(|o| Option::replace(&mut *o.borrow_mut(), self.clone()))
|
||||
OWNER.with(|o| {
|
||||
Option::replace(&mut *o.borrow_mut(), self.downgrade())
|
||||
})
|
||||
};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
@@ -255,7 +293,7 @@ impl Owner {
|
||||
|
||||
/// Returns the current `Owner`, if any.
|
||||
pub fn current() -> Option<Owner> {
|
||||
OWNER.with(|o| o.borrow().clone())
|
||||
OWNER.with(|o| o.borrow().as_ref().and_then(|n| n.upgrade()))
|
||||
}
|
||||
|
||||
/// Returns the [`SharedContext`] associated with this owner, if any.
|
||||
@@ -269,7 +307,7 @@ impl Owner {
|
||||
/// Removes this from its state as the thread-local owner and drops it.
|
||||
pub fn unset(self) {
|
||||
OWNER.with_borrow_mut(|owner| {
|
||||
if owner.as_ref() == Some(&self) {
|
||||
if owner.as_ref().and_then(|n| n.upgrade()) == Some(self) {
|
||||
mem::take(owner);
|
||||
}
|
||||
})
|
||||
@@ -282,6 +320,7 @@ impl Owner {
|
||||
OWNER.with(|o| {
|
||||
o.borrow()
|
||||
.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
})
|
||||
}
|
||||
@@ -295,6 +334,7 @@ impl Owner {
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
});
|
||||
match sc {
|
||||
@@ -320,6 +360,7 @@ impl Owner {
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
});
|
||||
match sc {
|
||||
|
||||
@@ -48,20 +48,30 @@ impl Arena {
|
||||
fun(&MAP.get_or_init(Default::default).read().or_poisoned())
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
Arena::try_with(fun).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, but no \
|
||||
Arena is active",
|
||||
std::panic::Location::caller()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn try_with<U>(fun: impl FnOnce(&ArenaMap) -> U) -> Option<U> {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
{
|
||||
Some(fun(&MAP.get_or_init(Default::default).read().or_poisoned()))
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&arena
|
||||
arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
but no Arena is active",
|
||||
std::panic::Location::caller()
|
||||
)
|
||||
})
|
||||
.read()
|
||||
.or_poisoned())
|
||||
.map(|n| fun(&n.read().or_poisoned()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -74,20 +84,32 @@ impl Arena {
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
let caller = std::panic::Location::caller();
|
||||
Arena::try_with_mut(fun).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, but no \
|
||||
Arena is active",
|
||||
std::panic::Location::caller()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn try_with_mut<U>(fun: impl FnOnce(&mut ArenaMap) -> U) -> Option<U> {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
{
|
||||
Some(fun(&mut MAP
|
||||
.get_or_init(Default::default)
|
||||
.write()
|
||||
.or_poisoned()))
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&mut arena
|
||||
arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
but no Arena is active",
|
||||
caller
|
||||
)
|
||||
})
|
||||
.write()
|
||||
.or_poisoned())
|
||||
.map(|n| fun(&mut n.write().or_poisoned()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -126,6 +148,7 @@ pub mod sandboxed {
|
||||
/// called.
|
||||
///
|
||||
/// [item]:[crate::owner::ArenaItem]
|
||||
#[track_caller]
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
|
||||
Self { arena, inner }
|
||||
|
||||
@@ -53,7 +53,7 @@ where
|
||||
})
|
||||
};
|
||||
OWNER.with(|o| {
|
||||
if let Some(owner) = &*o.borrow() {
|
||||
if let Some(owner) = o.borrow().as_ref().and_then(|o| o.upgrade()) {
|
||||
owner.register(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,24 +76,26 @@ where
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
Arena::try_with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
Arena::try_with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
Arena::try_with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<T>()) {
|
||||
Some(inner) => {
|
||||
@@ -103,6 +105,7 @@ where
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn take(node: NodeId) -> Option<T> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -100,6 +100,9 @@ where
|
||||
|
||||
let mut full_path = self.path().into_iter().collect::<StorePath>();
|
||||
full_path.pop();
|
||||
|
||||
// build a list of triggers, starting with the full path to this node and ending with the root
|
||||
// this will mean that the root is the final item, and this path is first
|
||||
let mut triggers = Vec::with_capacity(full_path.len());
|
||||
triggers.push(trigger.this.clone());
|
||||
loop {
|
||||
@@ -110,6 +113,17 @@ where
|
||||
}
|
||||
full_path.pop();
|
||||
}
|
||||
|
||||
// when the WriteGuard is dropped, each trigger will be notified, in order
|
||||
// reversing the list will cause the triggers to be notified starting from the root,
|
||||
// then to each child down to this one
|
||||
//
|
||||
// notifying from the root down is important for things like OptionStoreExt::map()/unwrap(),
|
||||
// where it's really important that any effects that subscribe to .is_some() run before effects
|
||||
// that subscribe to the inner value, so that the inner effect can be canceled if the outer switches to `None`
|
||||
// (see https://github.com/leptos-rs/leptos/issues/3704)
|
||||
triggers.reverse();
|
||||
|
||||
let guard = WriteGuard::new(triggers, parent);
|
||||
|
||||
Some(MappedMut::new(guard, self.read, self.write))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -604,9 +604,9 @@ impl ToTokens for PatchModel {
|
||||
let Field {
|
||||
attrs, ident, ..
|
||||
} = &field;
|
||||
let field_name = match &ident {
|
||||
Some(ident) => quote! { #ident },
|
||||
None => quote! { #idx },
|
||||
let locator = match &ident {
|
||||
Some(ident) => Either::Left(ident),
|
||||
None => Either::Right(Index::from(idx)),
|
||||
};
|
||||
let closure = attrs
|
||||
.iter()
|
||||
@@ -639,9 +639,9 @@ impl ToTokens for PatchModel {
|
||||
let params = closure.inputs;
|
||||
let body = closure.body;
|
||||
quote! {
|
||||
if new.#field_name != self.#field_name {
|
||||
if new.#locator != self.#locator {
|
||||
_ = {
|
||||
let (#params) = (&mut self.#field_name, new.#field_name);
|
||||
let (#params) = (&mut self.#locator, new.#locator);
|
||||
#body
|
||||
};
|
||||
notify(&new_path);
|
||||
@@ -651,8 +651,8 @@ impl ToTokens for PatchModel {
|
||||
} else {
|
||||
quote! {
|
||||
#library_path::PatchField::patch_field(
|
||||
&mut self.#field_name,
|
||||
new.#field_name,
|
||||
&mut self.#locator,
|
||||
new.#locator,
|
||||
&new_path,
|
||||
notify
|
||||
);
|
||||
@@ -684,3 +684,17 @@ impl ToTokens for PatchModel {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
impl<A: ToTokens, B: ToTokens> ToTokens for Either<A, B> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Either::Left(a) => a.to_tokens(tokens),
|
||||
Either::Right(b) => b.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -318,6 +318,28 @@ mod tests {
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_before_param() {
|
||||
let path = "/foo/bar";
|
||||
let def = (StaticSegment("foo"), ParamSegment("b"));
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("b".into(), "bar".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_before_optional_param() {
|
||||
let path = "/foo/bar";
|
||||
let def = (StaticSegment("foo"), OptionalParamSegment("b"));
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
let params = matched.params();
|
||||
assert_eq!(params[0], ("b".into(), "bar".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_optional_params_match_first() {
|
||||
let path = "/foo/bar";
|
||||
|
||||
@@ -25,7 +25,10 @@ macro_rules! tuples {
|
||||
let mut p = Vec::new();
|
||||
let mut m = String::new();
|
||||
|
||||
if !$first::OPTIONAL || nth_field < include_optionals {
|
||||
if $first::OPTIONAL {
|
||||
nth_field += 1;
|
||||
}
|
||||
if !$first::OPTIONAL || nth_field <= include_optionals {
|
||||
match $first.test(r) {
|
||||
None => {
|
||||
return None;
|
||||
@@ -43,7 +46,7 @@ macro_rules! tuples {
|
||||
if $ty::OPTIONAL {
|
||||
nth_field += 1;
|
||||
}
|
||||
if !$ty::OPTIONAL || nth_field < include_optionals {
|
||||
if !$ty::OPTIONAL || nth_field <= include_optionals {
|
||||
let PartialPathMatch {
|
||||
remaining,
|
||||
matched,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
4
server_fn/Cargo.lock
generated
4
server_fn/Cargo.lock
generated
@@ -198,7 +198,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
|
||||
dependencies = [
|
||||
@@ -880,7 +880,7 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.7",
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
Reference in New Issue
Block a user