mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-29 17:34:19 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aafcc3947 | ||
|
|
5c2482fadc | ||
|
|
81cff63455 | ||
|
|
61186c2432 | ||
|
|
75e42ccea5 | ||
|
|
85c7cc94ad | ||
|
|
270536adb1 | ||
|
|
cec0fb8d85 | ||
|
|
764b9cd57d | ||
|
|
6de2b4006a | ||
|
|
65940cbefa |
2
.github/workflows/run-cargo-make-task.yml
vendored
2
.github/workflows/run-cargo-make-task.yml
vendored
@@ -103,7 +103,7 @@ jobs:
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
if: contains(inputs.directory, 'examples')
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
|
||||
137
Cargo.lock
generated
137
Cargo.lock
generated
@@ -471,9 +471,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitcode"
|
||||
version = "0.6.7"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "648bd963d2e5d465377acecfb4b827f9f553b6bc97a8f61715779e9ed9e52b74"
|
||||
checksum = "0a6ed1b54d8dc333e7be604d00fa9262f4635485ffea923647b6521a5fff045d"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitcode_derive",
|
||||
@@ -484,9 +484,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bitcode_derive"
|
||||
version = "0.6.7"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffebfc2d28a12b262c303cb3860ee77b91bd83b1f20f0bd2a9693008e2f55a9e"
|
||||
checksum = "238b90427dfad9da4a9abd60f3ec1cdee6b80454bde49ed37f1781dd8e9dc7f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -531,9 +531,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
@@ -599,9 +599,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
version = "1.2.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -708,7 +708,7 @@ dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"pathdiff",
|
||||
"serde_core",
|
||||
"toml 0.9.8",
|
||||
"toml",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -1074,7 +1074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1316,14 +1316,14 @@ dependencies = [
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.30.8"
|
||||
version = "0.30.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460"
|
||||
checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46"
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
@@ -1447,12 +1447,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1797,13 +1798,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.44.3"
|
||||
version = "1.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698"
|
||||
checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c"
|
||||
dependencies = [
|
||||
"console",
|
||||
"once_cell",
|
||||
"similar",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2047,7 +2049,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
@@ -2313,9 +2315,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "minicov"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
|
||||
checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"walkdir",
|
||||
@@ -2418,7 +2420,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2654,7 +2656,7 @@ version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.7",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3153,7 +3155,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3172,9 +3174,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.13.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
|
||||
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
@@ -3377,15 +3379,6 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.3"
|
||||
@@ -3747,14 +3740,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "7.0.5"
|
||||
version = "7.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb"
|
||||
checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml 0.8.23",
|
||||
"toml",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
@@ -3829,7 +3822,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4043,18 +4036,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.8"
|
||||
@@ -4063,22 +4044,13 @@ checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.3",
|
||||
"toml_datetime 0.7.3",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
@@ -4088,19 +4060,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.7"
|
||||
@@ -4108,25 +4067,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 0.7.3",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
@@ -4186,9 +4145,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.43"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -4209,9 +4168,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
@@ -4234,7 +4193,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"target-triple",
|
||||
"termcolor",
|
||||
"toml 0.9.8",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4347,9 +4306,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-width"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
|
||||
checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
@@ -4382,9 +4341,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
|
||||
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@@ -4608,7 +4567,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -86,7 +86,7 @@ rustc_version = { default-features = false, version = "0.4.1" }
|
||||
guardian = { default-features = false, version = "1.3.0" }
|
||||
rustc-hash = { default-features = false, version = "2.1.1" }
|
||||
actix-web = { default-features = false, version = "4.12.1" }
|
||||
tracing = { default-features = false, version = "0.1.43" }
|
||||
tracing = { default-features = false, version = "0.1.44" }
|
||||
slotmap = { default-features = false, version = "1.1.1" }
|
||||
futures = { default-features = false, version = "0.3.31" }
|
||||
dashmap = { default-features = false, version = "6.1.0" }
|
||||
@@ -131,7 +131,7 @@ inventory = { default-features = false, version = "0.3.21" }
|
||||
config = { default-features = false, version = "0.15.19" }
|
||||
camino = { default-features = false, version = "1.2.2" }
|
||||
ciborium = { default-features = false, version = "0.2.2" }
|
||||
bitcode = { default-features = false, version = "0.6.7" }
|
||||
bitcode = { default-features = false, version = "0.6.9" }
|
||||
multer = { default-features = false, version = "3.1.0" }
|
||||
leptos-spin-macro = { default-features = false, version = "0.2.0" }
|
||||
sledgehammer_utils = { default-features = false, version = "0.3.1" }
|
||||
@@ -157,7 +157,7 @@ rmp-serde = { default-features = false, version = "1.3.0" }
|
||||
reqwest = { default-features = false, version = "0.12.26" }
|
||||
tower-layer = { default-features = false, version = "0.3.3" }
|
||||
attribute-derive = { default-features = false, version = "0.10.5" }
|
||||
insta = { default-features = false, version = "1.44.3" }
|
||||
insta = { default-features = false, version = "1.45.0" }
|
||||
codee = { default-features = false, version = "0.3.5" }
|
||||
actix-http = { default-features = false, version = "3.11.2" }
|
||||
wasm-bindgen-test = { default-features = false, version = "0.3.56" }
|
||||
|
||||
@@ -17,7 +17,7 @@ leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen = "0.2.106"
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
|
||||
38
examples/regression/e2e/features/issue_4492.feature
Normal file
38
examples/regression/e2e/features/issue_4492.feature
Normal file
@@ -0,0 +1,38 @@
|
||||
@check_issue_4492
|
||||
Feature: Regression test for issue #4492
|
||||
|
||||
Scenario: Scenario A should show Loading once on first load.
|
||||
Given I see the app
|
||||
And I can access regression test 4492
|
||||
When I click the button a-toggle
|
||||
Then I see a-result has the text Loading...
|
||||
When I wait 100ms
|
||||
Then I see a-result has the text 0
|
||||
When I click the button a-button
|
||||
Then I see a-result has the text 0
|
||||
When I wait 100ms
|
||||
Then I see a-result has the text 1
|
||||
|
||||
Scenario: Scenario B should never show Loading
|
||||
Given I see the app
|
||||
And I can access regression test 4492
|
||||
When I click the button b-toggle
|
||||
Then I see b-result has the text 0
|
||||
When I click the button b-button
|
||||
Then I see b-result has the text 0
|
||||
When I wait 100ms
|
||||
Then I see b-result has the text 1
|
||||
When I click the button b-button
|
||||
Then I see b-result has the text 1
|
||||
When I wait 100ms
|
||||
Then I see b-result has the text 2
|
||||
|
||||
Scenario: Scenario C should never show Loading
|
||||
Given I see the app
|
||||
And I can access regression test 4492
|
||||
When I click the button c-toggle
|
||||
Then I see c-result has the text 0
|
||||
When I click the button c-button
|
||||
Then I see c-result has the text 42
|
||||
When I wait 100ms
|
||||
Then I see c-result has the text 1
|
||||
@@ -15,3 +15,9 @@ pub async fn click_link(client: &Client, text: &str) -> Result<()> {
|
||||
link.click().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn click_button(client: &Client, id: &str) -> Result<()> {
|
||||
let btn = find::element_by_id(&client, &id).await?;
|
||||
btn.click().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
10
examples/regression/e2e/tests/fixtures/check.rs
vendored
10
examples/regression/e2e/tests/fixtures/check.rs
vendored
@@ -7,7 +7,15 @@ pub async fn result_text_is(
|
||||
client: &Client,
|
||||
expected_text: &str,
|
||||
) -> Result<()> {
|
||||
let actual = find::text_at_id(client, "result").await?;
|
||||
element_text_is(client, "result", expected_text).await
|
||||
}
|
||||
|
||||
pub async fn element_text_is(
|
||||
client: &Client,
|
||||
id: &str,
|
||||
expected_text: &str,
|
||||
) -> Result<()> {
|
||||
let actual = find::text_at_id(client, id).await?;
|
||||
assert_eq!(&actual, expected_text);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -20,6 +20,14 @@ async fn i_select_the_link(world: &mut AppWorld, text: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[when(regex = "^I click the button (.*)$")]
|
||||
async fn i_click_the_button(world: &mut AppWorld, id: String) -> Result<()> {
|
||||
let client = &world.client;
|
||||
action::click_button(client, &id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[given(expr = "I select the following links")]
|
||||
#[when(expr = "I select the following links")]
|
||||
async fn i_select_the_following_links(
|
||||
@@ -54,3 +62,10 @@ async fn i_go_back(world: &mut AppWorld) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[when(regex = r"^I wait (\d+)ms$")]
|
||||
async fn i_wait_ms(_world: &mut AppWorld, ms: u64) -> Result<()> {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,6 +19,17 @@ async fn i_see_the_result_is_the_string(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see ([\w-]+) has the text (.*)$")]
|
||||
async fn i_see_element_has_text(
|
||||
world: &mut AppWorld,
|
||||
id: String,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::element_text_is(client, &id, &text).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see the navbar$")]
|
||||
async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
|
||||
let client = &world.client;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
|
||||
issue_4285::Routes4285, issue_4296::Routes4296, issue_4324::Routes4324,
|
||||
pr_4015::Routes4015, pr_4091::Routes4091,
|
||||
issue_4492::Routes4492, pr_4015::Routes4015, pr_4091::Routes4091,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::{MetaTags, *};
|
||||
@@ -48,6 +48,7 @@ pub fn App() -> impl IntoView {
|
||||
<Routes4285/>
|
||||
<Routes4296/>
|
||||
<Routes4324/>
|
||||
<Routes4492/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -75,6 +76,7 @@ fn HomePage() -> impl IntoView {
|
||||
<li><a href="/4285/">"4285"</a></li>
|
||||
<li><a href="/4296/">"4296"</a></li>
|
||||
<li><a href="/4324/">"4324"</a></li>
|
||||
<li><a href="/4492/">"4492"</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
|
||||
114
examples/regression/src/issue_4492.rs
Normal file
114
examples/regression/src/issue_4492.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use leptos::prelude::*;
|
||||
#[allow(unused_imports)]
|
||||
use leptos_router::{
|
||||
components::Route, path, MatchNestedRoutes, NavigateOptions,
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn Routes4492() -> impl MatchNestedRoutes + Clone {
|
||||
view! {
|
||||
<Route path=path!("4492") view=Issue4492/>
|
||||
}
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Issue4492() -> impl IntoView {
|
||||
let show_a = RwSignal::new(false);
|
||||
let show_b = RwSignal::new(false);
|
||||
let show_c = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<button id="a-toggle" on:click=move |_| show_a.set(!show_a.get())>"Toggle A"</button>
|
||||
<button id="b-toggle" on:click=move |_| show_b.set(!show_b.get())>"Toggle B"</button>
|
||||
<button id="c-toggle" on:click=move |_| show_c.set(!show_c.get())>"Toggle C"</button>
|
||||
|
||||
<Show when=move || show_a.get()>
|
||||
<ScenarioA/>
|
||||
</Show>
|
||||
<Show when=move || show_b.get()>
|
||||
<ScenarioB/>
|
||||
</Show>
|
||||
<Show when=move || show_c.get()>
|
||||
<ScenarioC/>
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ScenarioA() -> impl IntoView {
|
||||
// scenario A: one truly-async resource is read on click
|
||||
let counter = RwSignal::new(0);
|
||||
let resource = Resource::new(
|
||||
move || counter.get(),
|
||||
|count| async move {
|
||||
sleep(50).await.unwrap();
|
||||
count
|
||||
},
|
||||
);
|
||||
view! {
|
||||
<Transition fallback=|| view! { <p id="a-result">"Loading..."</p> }>
|
||||
<p id="a-result">{resource}</p>
|
||||
</Transition>
|
||||
<button id="a-button" on:click=move |_| *counter.write() += 1>"+1"</button>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ScenarioB() -> impl IntoView {
|
||||
// scenario B: resource immediately available first time, then after 250ms
|
||||
let counter = RwSignal::new(0);
|
||||
let resource = Resource::new(
|
||||
move || counter.get(),
|
||||
|count| async move {
|
||||
if count == 0 {
|
||||
count
|
||||
} else {
|
||||
sleep(50).await.unwrap();
|
||||
count
|
||||
}
|
||||
},
|
||||
);
|
||||
view! {
|
||||
<Transition fallback=|| view! { <p id="b-result">"Loading..."</p> }>
|
||||
<p id="b-result">{resource}</p>
|
||||
</Transition>
|
||||
<button id="b-button" on:click=move |_| *counter.write() += 1>"+1"</button>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ScenarioC() -> impl IntoView {
|
||||
// scenario C: not even a resource on the first run, just a value
|
||||
// see https://github.com/leptos-rs/leptos/issues/3868
|
||||
let counter = RwSignal::new(0);
|
||||
let s_res = StoredValue::new(None::<ArcLocalResource<i32>>);
|
||||
let resource = move || {
|
||||
let count = counter.get();
|
||||
if count == 0 {
|
||||
count
|
||||
} else {
|
||||
let r = s_res.get_value().unwrap_or_else(|| {
|
||||
let res = ArcLocalResource::new(move || async move {
|
||||
sleep(50).await.unwrap();
|
||||
count
|
||||
});
|
||||
s_res.set_value(Some(res.clone()));
|
||||
res
|
||||
});
|
||||
r.get().unwrap_or(42)
|
||||
}
|
||||
};
|
||||
view! {
|
||||
<Transition fallback=|| view! { <p id="c-result">"Loading..."</p> }>
|
||||
<p id="c-result">{resource}</p>
|
||||
</Transition>
|
||||
<button id="c-button" on:click=move |_| *counter.write() += 1>"+1"</button>
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn sleep(ms: u64) -> Result<(), ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod issue_4217;
|
||||
mod issue_4285;
|
||||
mod issue_4296;
|
||||
mod issue_4324;
|
||||
mod issue_4492;
|
||||
mod pr_4015;
|
||||
mod pr_4091;
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ use reactive_graph::{
|
||||
effect::RenderEffect,
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Dispose, Get, Read, ReadUntracked, Track, With, WriteValue},
|
||||
traits::{
|
||||
Dispose, Get, Read, ReadUntracked, Track, With, WithUntracked,
|
||||
WriteValue,
|
||||
},
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -119,14 +122,19 @@ where
|
||||
provide_context(SuspenseContext {
|
||||
tasks: tasks.clone(),
|
||||
});
|
||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
let none_pending = ArcMemo::new({
|
||||
let tasks = tasks.clone();
|
||||
move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
}
|
||||
}
|
||||
});
|
||||
let has_tasks =
|
||||
Arc::new(move || !tasks.with_untracked(SlotMap::is_empty));
|
||||
|
||||
OwnedView::new(SuspenseBoundary::<false, _, _> {
|
||||
id,
|
||||
@@ -134,6 +142,7 @@ where
|
||||
fallback,
|
||||
children,
|
||||
error_boundary_parent,
|
||||
has_tasks,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -156,6 +165,7 @@ pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
|
||||
pub fallback: Fal,
|
||||
pub children: Chil,
|
||||
pub error_boundary_parent: Option<ErrorBoundarySuspendedChildren>,
|
||||
pub has_tasks: Arc<dyn Fn() -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, Fal, Chil> Render
|
||||
@@ -192,12 +202,26 @@ where
|
||||
outer_owner.clone(),
|
||||
);
|
||||
|
||||
if let Some(mut state) = prev {
|
||||
let state = if let Some(mut state) = prev {
|
||||
this.rebuild(&mut state);
|
||||
state
|
||||
} else {
|
||||
this.build()
|
||||
};
|
||||
|
||||
if nth_run == 1 && !(self.has_tasks)() {
|
||||
// if this is the first run, and there are no pending resources at this point,
|
||||
// it means that there were no actually-async resources read while rendering the children
|
||||
// this means that we're effectively on the settled second run: none_pending
|
||||
// won't change false => true and cause this to rerender (and therefore increment nth_run)
|
||||
//
|
||||
// we increment it manually here so that future resource changes won't cause the transition fallback
|
||||
// to be displayed for the first time
|
||||
// see https://github.com/leptos-rs/leptos/issues/3868, https://github.com/leptos-rs/leptos/issues/4492
|
||||
nth_run += 1;
|
||||
}
|
||||
|
||||
state
|
||||
})
|
||||
}
|
||||
|
||||
@@ -235,6 +259,7 @@ where
|
||||
fallback,
|
||||
children,
|
||||
error_boundary_parent,
|
||||
has_tasks,
|
||||
} = self;
|
||||
SuspenseBoundary {
|
||||
id,
|
||||
@@ -242,6 +267,7 @@ where
|
||||
fallback,
|
||||
children: children.add_any_attr(attr),
|
||||
error_boundary_parent,
|
||||
has_tasks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ use reactive_graph::{
|
||||
effect::Effect,
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Get, Set, Track, With},
|
||||
traits::{Get, Set, Track, With, WithUntracked},
|
||||
wrappers::write::SignalSetter,
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::Arc;
|
||||
use tachys::reactive_graph::OwnedView;
|
||||
|
||||
/// If any [`Resource`](crate::prelude::Resource) is read in the `children` of this
|
||||
@@ -104,14 +105,19 @@ where
|
||||
provide_context(SuspenseContext {
|
||||
tasks: tasks.clone(),
|
||||
});
|
||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
let none_pending = ArcMemo::new({
|
||||
let tasks = tasks.clone();
|
||||
move |prev: Option<&bool>| {
|
||||
tasks.track();
|
||||
if prev.is_none() && starts_local {
|
||||
false
|
||||
} else {
|
||||
tasks.with(SlotMap::is_empty)
|
||||
}
|
||||
}
|
||||
});
|
||||
let has_tasks =
|
||||
Arc::new(move || !tasks.with_untracked(SlotMap::is_empty));
|
||||
if let Some(set_pending) = set_pending {
|
||||
Effect::new_isomorphic({
|
||||
let none_pending = none_pending.clone();
|
||||
@@ -127,6 +133,7 @@ where
|
||||
fallback,
|
||||
children,
|
||||
error_boundary_parent,
|
||||
has_tasks,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -177,7 +177,7 @@ pub(crate) fn component_to_tokens(
|
||||
if cfg!(feature = "__internal_erase_components") {
|
||||
quote! {
|
||||
.add_any_attr({
|
||||
vec![#(::leptos::html::attribute::any_attribute::IntoAnyAttribute::into_any_attr(#spreads),)*]
|
||||
vec![#(::leptos::attr::any_attribute::IntoAnyAttribute::into_any_attr(#spreads),)*]
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@ use codee::{
|
||||
Decoder, Encoder,
|
||||
};
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use futures::Future;
|
||||
use futures::{Future, FutureExt};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{
|
||||
@@ -258,11 +258,17 @@ where
|
||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||
if self.value.read().or_poisoned().is_none() {
|
||||
let handle = suspense_context.task_id();
|
||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
||||
reactive_graph::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
let mut ready =
|
||||
Box::pin(SpecialNonReactiveFuture::new(self.ready()));
|
||||
match ready.as_mut().now_or_never() {
|
||||
Some(_) => drop(handle),
|
||||
None => {
|
||||
reactive_graph::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.suspenses.write().or_poisoned().push(suspense_context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,12 +632,29 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||
// create a handle to register it with suspense
|
||||
let handle = suspense_context.task_id();
|
||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
||||
crate::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
|
||||
// check if the task is *already* ready
|
||||
let mut ready =
|
||||
Box::pin(SpecialNonReactiveFuture::new(self.ready()));
|
||||
match ready.as_mut().now_or_never() {
|
||||
Some(_) => {
|
||||
// if it's already ready, drop the handle immediately
|
||||
// this will immediately notify the suspense context that it's complete
|
||||
drop(handle);
|
||||
}
|
||||
None => {
|
||||
// otherwise, spawn a task to wait for it to be ready, then drop the handle,
|
||||
// which will notify the suspense
|
||||
crate::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// register the suspense context with our list of them, to be notified later if this re-runs
|
||||
self.inner
|
||||
.write()
|
||||
.or_poisoned()
|
||||
|
||||
@@ -176,6 +176,7 @@ where
|
||||
{
|
||||
inner: KeyedSubfield<Inner, Prev, K, T>,
|
||||
guard: Option<Guard>,
|
||||
untracked: bool,
|
||||
}
|
||||
|
||||
impl<Inner, Prev, K, T, Guard> Deref
|
||||
@@ -227,6 +228,7 @@ where
|
||||
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
|
||||
{
|
||||
fn untrack(&mut self) {
|
||||
self.untracked = true;
|
||||
if let Some(inner) = self.guard.as_mut() {
|
||||
inner.untrack();
|
||||
}
|
||||
@@ -251,7 +253,10 @@ where
|
||||
// now that the write lock is release, we can get a read lock to refresh this keyed field
|
||||
// based on the new value
|
||||
self.inner.update_keys();
|
||||
self.inner.notify();
|
||||
|
||||
if !self.untracked {
|
||||
self.inner.notify();
|
||||
}
|
||||
|
||||
// reactive updates happen on the next tick
|
||||
}
|
||||
@@ -344,6 +349,7 @@ where
|
||||
Some(KeyedSubfieldWriteGuard {
|
||||
inner: self.clone(),
|
||||
guard: Some(guard),
|
||||
untracked: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -355,6 +361,7 @@ where
|
||||
Some(KeyedSubfieldWriteGuard {
|
||||
inner: self.clone(),
|
||||
guard: Some(guard),
|
||||
untracked: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -879,4 +886,41 @@ mod tests {
|
||||
assert_eq!(b_count.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(c_count.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn untracked_write_on_keyed_subfield_shouldnt_notify() {
|
||||
_ = any_spawner::Executor::init_tokio();
|
||||
|
||||
let store = Store::new(data());
|
||||
assert_eq!(store.read_untracked().todos.len(), 3);
|
||||
|
||||
// create an effect to read from the keyed subfield
|
||||
let todos_count = Arc::new(AtomicUsize::new(0));
|
||||
Effect::new_sync({
|
||||
let todos_count = Arc::clone(&todos_count);
|
||||
move || {
|
||||
store.todos().track();
|
||||
todos_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
tick().await;
|
||||
assert_eq!(todos_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
// writing to keyed subfield notifies the iterator
|
||||
store.todos().write().push(Todo {
|
||||
id: 13,
|
||||
label: "D".into(),
|
||||
});
|
||||
tick().await;
|
||||
assert_eq!(todos_count.load(Ordering::Relaxed), 2);
|
||||
|
||||
// but an untracked write doesn't
|
||||
store.todos().write_untracked().push(Todo {
|
||||
id: 14,
|
||||
label: "E".into(),
|
||||
});
|
||||
tick().await;
|
||||
assert_eq!(todos_count.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,7 +833,7 @@ mod tests {
|
||||
use reactive_graph::{
|
||||
effect::Effect,
|
||||
owner::StoredValue,
|
||||
traits::{Read, ReadUntracked, Set, Update, Write},
|
||||
traits::{Read, ReadUntracked, Set, Track, Update, Write},
|
||||
};
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
@@ -1375,4 +1375,34 @@ mod tests {
|
||||
|
||||
assert_eq!(combined_count.load(Ordering::Relaxed), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn untracked_write_on_subfield_shouldnt_notify() {
|
||||
_ = any_spawner::Executor::init_tokio();
|
||||
|
||||
let name_count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let store = Store::new(data());
|
||||
|
||||
let tracked_field = store.user();
|
||||
|
||||
Effect::new_sync({
|
||||
let name_count = Arc::clone(&name_count);
|
||||
move |_| {
|
||||
tracked_field.track();
|
||||
name_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
tick().await;
|
||||
assert_eq!(name_count.load(Ordering::Relaxed), 1);
|
||||
|
||||
tracked_field.write().push('!');
|
||||
tick().await;
|
||||
assert_eq!(name_count.load(Ordering::Relaxed), 2);
|
||||
|
||||
tracked_field.write_untracked().push('!');
|
||||
tick().await;
|
||||
assert_eq!(name_count.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,13 +693,18 @@ impl Render for AnyViewWithAttrs {
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.view.rebuild(&mut state.view);
|
||||
|
||||
let elements = state.elements();
|
||||
// FIXME this seems wrong but I think the previous version was also broken!
|
||||
if let Some(element) = elements.first() {
|
||||
self.attrs.rebuild(&mut (
|
||||
element.clone(),
|
||||
std::mem::take(&mut state.attrs),
|
||||
));
|
||||
// at this point, we have rebuilt the inner view
|
||||
// now we need to update attributes that were spread onto this
|
||||
// this approach is not ideal, but it avoids two edge cases:
|
||||
// 1) merging attributes from two unrelated views (https://github.com/leptos-rs/leptos/issues/4268)
|
||||
// 2) failing to re-create attributes from the same view (https://github.com/leptos-rs/leptos/issues/4512)
|
||||
for element in state.elements() {
|
||||
// first, remove the previous set of attributes
|
||||
self.attrs
|
||||
.clone()
|
||||
.rebuild(&mut (element.clone(), Vec::new()));
|
||||
// then, add the new set of attributes
|
||||
self.attrs.clone().build(&element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -824,6 +829,7 @@ impl AddAnyAttr for AnyViewWithAttrs {
|
||||
/// State for any view with attributes spread onto it.
|
||||
pub struct AnyViewWithAttrsState {
|
||||
view: AnyViewState,
|
||||
#[allow(dead_code)] // keeps attribute states alive until dropped
|
||||
attrs: Vec<AnyAttributeState>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user