diff --git a/examples/regression/Cargo.toml b/examples/regression/Cargo.toml index fb5a79b6c1..b223380ff9 100644 --- a/examples/regression/Cargo.toml +++ b/examples/regression/Cargo.toml @@ -54,6 +54,8 @@ site-root = "target/site" # The site-root relative folder where all compiled output (JS, WASM and CSS) is written # Defaults to pkg site-pkg-dir = "pkg" +# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. +style-file = "style/main.scss" # The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. site-addr = "127.0.0.1:3000" # The port to use for automatic reload monitoring diff --git a/examples/regression/e2e/features/logger.feature b/examples/regression/e2e/features/logger.feature new file mode 100644 index 0000000000..6bc54175e5 --- /dev/null +++ b/examples/regression/e2e/features/logger.feature @@ -0,0 +1,30 @@ +@check_logger +Feature: The simple logger. + + Scenario: Visiting readme should have no log messages. + Given I see the app + When I access (What is this) + Then I see 0 log messages + + Scenario: Visiting readme and using the example link should log a message. + Given I see the app + When I access (What is this) + And I select the link example link + Then I counted 1 log message + And I find the following being the most recent log messages + | Hello world! | + + Scenario: Visiting readme and generate multiple log messages + Given I see the app + When I access (What is this) + And I select the following links + | example link | + | example link | + | other link | + | other link | + | example link | + Then I counted 5 log message + And I find the following being the most recent log messages + | Something else. | + | Something else. | + | Hello world! | diff --git a/examples/regression/e2e/tests/fixtures/check.rs b/examples/regression/e2e/tests/fixtures/check.rs index 8847e224eb..35af24e14f 100644 --- a/examples/regression/e2e/tests/fixtures/check.rs +++ b/examples/regression/e2e/tests/fixtures/check.rs @@ -18,3 +18,29 @@ pub async fn element_exists(client: &Client, id: &str) -> Result<()> { .expect(&format!("could not find element with id `{id}`")); Ok(()) } + +pub async fn count_log_messages(client: &Client, count: usize) -> Result<()> { + let elements = find::log_message_elements(client).await?; + assert_eq!(elements.len(), count); + Ok(()) +} + +pub async fn last_log_messages( + client: &Client, + expected: &[&str], +) -> Result<()> { + let elements = find::log_message_elements(client).await?; + let elements_len = elements.len(); + let expected_len = expected.len(); + assert!( + elements_len >= expected_len, + "the messages available is not equal or greater than what is being expected", + ); + + let mut result = Vec::new(); + for element in elements.into_iter().skip(elements_len - expected_len) { + result.push(element.text().await?); + } + assert_eq!(result, expected); + Ok(()) +} diff --git a/examples/regression/e2e/tests/fixtures/find.rs b/examples/regression/e2e/tests/fixtures/find.rs index d93ea56b81..f3adb4367f 100644 --- a/examples/regression/e2e/tests/fixtures/find.rs +++ b/examples/regression/e2e/tests/fixtures/find.rs @@ -21,3 +21,12 @@ pub async fn link_with_text(client: &Client, text: &str) -> Result { pub async fn element_by_id(client: &Client, id: &str) -> Result { Ok(client.wait().for_element(Locator::Id(id)).await?) } + +pub async fn log_message_elements(client: &Client) -> Result> { + let elements = element_by_id(client, "logs") + .await + .expect("the simple logger must be present") + .find_all(Locator::Css("ul li")) + .await?; + Ok(elements) +} diff --git a/examples/regression/e2e/tests/fixtures/world/action_steps.rs b/examples/regression/e2e/tests/fixtures/world/action_steps.rs index 0bc50bf8e5..3128604741 100644 --- a/examples/regression/e2e/tests/fixtures/world/action_steps.rs +++ b/examples/regression/e2e/tests/fixtures/world/action_steps.rs @@ -13,6 +13,7 @@ async fn i_open_the_app(world: &mut AppWorld) -> Result<()> { #[given(regex = "^I can access regression test (.*)$")] #[when(regex = "^I select the link (.*)$")] +#[when(regex = "^I access (.*)$")] async fn i_select_the_link(world: &mut AppWorld, text: String) -> Result<()> { let client = &world.client; action::click_link(client, &text).await?; diff --git a/examples/regression/e2e/tests/fixtures/world/check_steps.rs b/examples/regression/e2e/tests/fixtures/world/check_steps.rs index 88679891e6..4b9b0ab18e 100644 --- a/examples/regression/e2e/tests/fixtures/world/check_steps.rs +++ b/examples/regression/e2e/tests/fixtures/world/check_steps.rs @@ -1,6 +1,6 @@ use crate::fixtures::{check, world::AppWorld}; use anyhow::{Ok, Result}; -use cucumber::then; +use cucumber::{gherkin::Step, then}; #[then(regex = r"^I see the result is empty$")] async fn i_see_the_result_is_empty(world: &mut AppWorld) -> Result<()> { @@ -25,3 +25,37 @@ async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> { check::element_exists(client, "nav").await?; Ok(()) } + +#[then(regex = r"^I counted ([0-9]+) log message$")] +#[then(regex = r"^I counted ([0-9]+) log messages$")] +#[then(regex = r"^I see ([0-9]+) log message$")] +#[then(regex = r"^I see ([0-9]+) log messages$")] +async fn i_counted_log_messages( + world: &mut AppWorld, + count: usize, +) -> Result<()> { + let client = &world.client; + check::count_log_messages(client, count).await?; + Ok(()) +} + +#[then(regex = r"^I find the following being the most recent log messages$")] +async fn i_find_the_following_being_the_most_recent_log_messages( + world: &mut AppWorld, + step: &Step, +) -> Result<()> { + let client = &world.client; + + let expected = step + .table + .as_ref() + .expect("the table must be present") + .rows + .iter() + .map(|row| row[0].as_str()) + .collect::>(); + + check::last_log_messages(client, &expected).await?; + + Ok(()) +} diff --git a/examples/regression/src/app.rs b/examples/regression/src/app.rs index 402937d6e7..a2125dbb65 100644 --- a/examples/regression/src/app.rs +++ b/examples/regression/src/app.rs @@ -1,4 +1,7 @@ -use crate::{issue_4088::Routes4088, pr_4015::Routes4015, pr_4091::Routes4091}; +use crate::{ + issue_4088::Routes4088, log::SimpleLogger, pr_4015::Routes4015, + pr_4091::Routes4091, +}; use leptos::prelude::*; use leptos_meta::{MetaTags, *}; use leptos_router::{ @@ -27,6 +30,8 @@ pub fn shell(options: LeptosOptions) -> impl IntoView { #[component] pub fn App() -> impl IntoView { provide_meta_context(); + let logger = SimpleLogger::default(); + provide_context(logger); let fallback = || view! { "Page not found." }.into_view(); view! { @@ -34,12 +39,16 @@ pub fn App() -> impl IntoView {
+
+
+
{move || logger.render() }
+
} } @@ -54,6 +63,7 @@ fn HomePage() -> impl IntoView { view! { <h1>"Listing of regression tests"</h1> + <p><a href="/README">(What is this)</a></p> <nav> <ul> <li><a href="/4091/">"4091"</a></li> @@ -63,3 +73,41 @@ fn HomePage() -> impl IntoView { </nav> } } + +static EXAMPLE: &str = "\ +use leptos::prelude::*; +use crate::log::SimpleLogger; +let logger = expect_context::<SimpleLogger>(); +logger.log(\"Hello world!\");"; + +#[component] +fn Readme() -> impl IntoView { + view! { + <h1>"About regression example"</h1> + <p>" + This is a collection of components containing the minimum reproducible example that + should work without issues, but have possibly failed some time in the past in the form + of a regression. The components are self contained in their respective modules and + should be accompanied by an end-to-end test suite written in Gherkin, to allow a human + user to also reproduce and validate the expected behavior from the written instructions. + "</p> + // TODO probably establish some naming conventions here? + <p>" + A logger output pane is provided on the side, which may be invoked within a component + in this example like so: + "</p> + <blockquote><pre><code>{EXAMPLE}</code></pre></blockquote> + <p>" + This "<a href="#" on:click=|_| { + use crate::log::SimpleLogger; + let logger = expect_context::<SimpleLogger>(); + logger.log("Hello world!"); + }>"example link"</a>" is hooked with the above, so accessing that should result in that + message printed, while this "<a href="#" on:click=|_| { + use crate::log::SimpleLogger; + let logger = expect_context::<SimpleLogger>(); + logger.log("Something else."); + }>"other link"</a>" will log something else. "<a href="/">"Return to listing"</a>". + "</p> + } +} diff --git a/examples/regression/src/lib.rs b/examples/regression/src/lib.rs index edba82c8be..140f639d75 100644 --- a/examples/regression/src/lib.rs +++ b/examples/regression/src/lib.rs @@ -1,5 +1,6 @@ pub mod app; mod issue_4088; +pub mod log; mod pr_4015; mod pr_4091; diff --git a/examples/regression/src/log.rs b/examples/regression/src/log.rs new file mode 100644 index 0000000000..6b7535a5b3 --- /dev/null +++ b/examples/regression/src/log.rs @@ -0,0 +1,45 @@ +use leptos::prelude::*; + +#[derive(Clone, Default)] +struct SimpleLoggerInner(Vec<String>); + +// may not be the most efficient but it gets the job done +impl IntoRender for SimpleLoggerInner { + type Output = AnyView; + + fn into_render(self) -> Self::Output { + let entries = self + .0 + .into_iter() + .map(|msg| { + view! { + <li>{msg}</li> + } + }) + .collect_view(); + view! { + <section id="SimpleLogger"> + <h1>"Simple Logger history"</h1> + <div id="logs"> + <ul> + {entries} + </ul> + </div> + </section> + } + .into_any() + } +} + +#[derive(Clone, Copy, Default)] +pub struct SimpleLogger(RwSignal<SimpleLoggerInner>); + +impl SimpleLogger { + pub fn log(&self, msg: impl ToString) { + self.0.update(|vec| vec.0.push(msg.to_string())); + } + + pub fn render(&self) -> AnyView { + self.0.get().into_render() + } +} diff --git a/examples/regression/src/pr_4091.rs b/examples/regression/src/pr_4091.rs index a5ce57d681..f8009a47c4 100644 --- a/examples/regression/src/pr_4091.rs +++ b/examples/regression/src/pr_4091.rs @@ -1,3 +1,4 @@ +use crate::log::SimpleLogger; use leptos::{context::Provider, prelude::*}; use leptos_router::{ components::{ParentRoute, Route, A}, @@ -27,6 +28,13 @@ fn Container() -> impl IntoView { let rw_signal = RwSignal::new(Expectations(Vec::new())); provide_context(rw_signal); + let logger = expect_context::<SimpleLogger>(); + logger.log("Mounting pr_4091 <Container>"); + on_cleanup(move || { + leptos::logging::log!("Leaving <Container>"); + logger.log("Unmounting pr_4091 <Container>"); + }); + view! { <nav id="nav"> <ul> diff --git a/examples/regression/style/main.scss b/examples/regression/style/main.scss index 853ac29a7c..5efb9a8a9d 100644 --- a/examples/regression/style/main.scss +++ b/examples/regression/style/main.scss @@ -1,3 +1,56 @@ body { + margin: 0; + padding: 0; + background: #fff; font-family: sans-serif; + width: 100vw; + height: 100vh; + overflow: hidden; + display: grid; + grid-template-columns: repeat(2, 1fr); +} + +main, +footer { + height: 100vh; + overflow: auto; +} + +main { + box-sizing: border-box; + padding: 0 1em; + width: 70vw; + border-right: 1px solid black; +} + +footer { + width: 30vw; +} + +#SimpleLogger { + height: 100vh; + display: flex; + flex-direction: column; +} + +#SimpleLogger > h1 { + padding-left: 1em; +} + +#SimpleLogger > div#logs { + overflow: auto; + display: flex; + flex-direction: column-reverse; +} + +#SimpleLogger > div#logs > ul { + border-top: 1px #ccc solid; + list-style: none; + padding: 0; + margin: 0; +} + +#SimpleLogger > div#logs > ul > li { + padding-left: 1em; + border-bottom: 1px #ccc solid; }