Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/regression/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions examples/regression/e2e/features/logger.feature
Original file line number Diff line number Diff line change
@@ -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! |
26 changes: 26 additions & 0 deletions examples/regression/e2e/tests/fixtures/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
9 changes: 9 additions & 0 deletions examples/regression/e2e/tests/fixtures/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ pub async fn link_with_text(client: &Client, text: &str) -> Result<Element> {
pub async fn element_by_id(client: &Client, id: &str) -> Result<Element> {
Ok(client.wait().for_element(Locator::Id(id)).await?)
}

pub async fn log_message_elements(client: &Client) -> Result<Vec<Element>> {
let elements = element_by_id(client, "logs")
.await
.expect("the simple logger must be present")
.find_all(Locator::Css("ul li"))
.await?;
Ok(elements)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
36 changes: 35 additions & 1 deletion examples/regression/e2e/tests/fixtures/world/check_steps.rs
Original file line number Diff line number Diff line change
@@ -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<()> {
Expand All @@ -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::<Vec<_>>();

check::last_log_messages(client, &expected).await?;

Ok(())
}
50 changes: 49 additions & 1 deletion examples/regression/src/app.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -27,19 +30,25 @@ 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! {
<Stylesheet id="leptos" href="/pkg/regression.css"/>
<Router>
<main>
<Routes fallback>
<Route path=path!("") view=HomePage/>
<Route path=path!("README") view=Readme/>
<Routes4091/>
<Routes4015/>
<Routes4088/>
</Routes>
</main>
</Router>
<footer>
<section id="log">{move || logger.render() }</section>
</footer>
}
}

Expand All @@ -54,6 +63,7 @@ fn HomePage() -> impl IntoView {
view! {
<Title text="Regression Tests"/>
<h1>"Listing of regression tests"</h1>
<p><a href="/README">(What is this)</a></p>
<nav>
<ul>
<li><a href="/4091/">"4091"</a></li>
Expand All @@ -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>
}
}
1 change: 1 addition & 0 deletions examples/regression/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod app;
mod issue_4088;
pub mod log;
mod pr_4015;
mod pr_4091;

Expand Down
45 changes: 45 additions & 0 deletions examples/regression/src/log.rs
Original file line number Diff line number Diff line change
@@ -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()
}
}
8 changes: 8 additions & 0 deletions examples/regression/src/pr_4091.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::log::SimpleLogger;
use leptos::{context::Provider, prelude::*};
use leptos_router::{
components::{ParentRoute, Route, A},
Expand Down Expand Up @@ -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>
Expand Down
53 changes: 53 additions & 0 deletions examples/regression/style/main.scss
Original file line number Diff line number Diff line change
@@ -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;
}
Loading