Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[layout tests] Function for rendering a single node and getting the resulting web_sys::Element #3579

Closed

Conversation

aDogCalledSpot
Copy link

@aDogCalledSpot aDogCalledSpot commented Jan 4, 2024

Description

Following up from #3578 but important bits are reexplained here.
Pulls in code from #3463.

This PR adds a function with which a single component is rendered once and the resulting web_sys::Element is returned. This allows then querying the resulting element to run verification on only a subset of the resulting HTML code instead of all of it. This means that tests can be more fine-tuned resulting in less breakage through things such as style changes.

Limitations

Interactions with the DOM aren't possible with this since the scheduler doesn't run again. I would be interested in suggestions on how to implement that, so that users can get an experience similar to the JavaScript "DOM Testing Library" where the following is possible:

import {render, fireEvent, screen} from '@testing-library/react'

test('loads items eventually', async () => {
  render(<Page />)

  // Click button
  fireEvent.click(screen.getByText('Load'))

  // Wait for page to update with query text
  const items = await screen.findAllByText(/Item #[0-9]: /)
  expect(items).toHaveLength(10)
})

A crate called frontest was able to achieve a similar result with Yew 0.19 the following way:

pub async fn render(content: Html) -> Element {
    let div = gloo::utils::document().create_element("div").unwrap();
    gloo::utils::body().append_child(&div).unwrap();
    let res = div.clone();
    ::yew::start_app_with_props_in_element::<Wrapper>(div, WrapperProps { content });
    res
}

However, using Yew 0.21's Renderer::<Wrapper>::with_root_and_props(...).render() does not lead to the same results - the div created div is simply empty. I assume that the scheduler isn't started but I don't know enough about the internals of Yew.

Being able to offer this functionality would make testing Yew apps significantly easier.

Checklist

  • I have reviewed my own code
  • I have added tests

Copy link

github-actions bot commented Jan 4, 2024

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.779 ns      │ 5.838 ns      │ 2.785 ns      │ 2.851 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.78 ns       │ 4.881 ns      │ 4.016 ns      │ 3.759 ns      │ 100     │ 1000000000

Copy link

github-actions bot commented Jan 4, 2024

Visit the preview URL for this PR (updated for commit 6f7d0af):

https://yew-rs-api--pr3579-render-single-compon-9x572jwg.web.app

(expires Thu, 11 Jan 2024 13:16:07 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Copy link

github-actions bot commented Jan 4, 2024

Benchmark - SSR

Yew Master

Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 270.654 282.581 277.161 3.344
Hello World 10 471.637 490.093 479.120 5.988
Function Router 10 1526.713 1575.294 1553.140 16.210
Concurrent Task 10 1005.031 1006.239 1005.596 0.425
Many Providers 10 1108.494 1162.911 1137.033 17.926

Pull Request

Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 268.838 318.169 282.830 15.503
Hello World 10 476.503 526.242 498.851 15.401
Function Router 10 1527.817 1639.270 1573.512 35.882
Concurrent Task 10 1005.060 1006.236 1005.553 0.415
Many Providers 10 1132.968 1206.999 1166.485 25.047

Copy link

github-actions bot commented Jan 4, 2024

Size Comparison

examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 101.096 101.096 0 0.000%
boids 174.752 174.752 0 0.000%
communication_child_to_parent 93.484 93.484 0 0.000%
communication_grandchild_with_grandparent 106.468 106.468 0 0.000%
communication_grandparent_to_grandchild 101.727 101.727 0 0.000%
communication_parent_to_child 89.817 89.817 0 0.000%
contexts 106.589 106.589 0 0.000%
counter 86.863 86.863 0 0.000%
counter_functional 87.269 87.269 0 0.000%
dyn_create_destroy_apps 89.709 89.709 0 0.000%
file_upload 100.738 100.738 0 0.000%
function_memory_game 173.508 173.508 0 0.000%
function_router 349.796 349.796 0 0.000%
function_todomvc 162.216 162.216 0 0.000%
futures 230.420 230.420 0 0.000%
game_of_life 111.120 111.120 0 0.000%
immutable 188.416 188.416 0 0.000%
inner_html 80.562 80.562 0 0.000%
js_callback 110.333 110.333 0 0.000%
keyed_list 200.077 200.077 0 0.000%
mount_point 83.454 83.454 0 0.000%
nested_list 114.679 114.679 0 0.000%
node_refs 90.978 90.978 0 0.000%
password_strength 1751.463 1751.463 0 0.000%
portals 94.211 94.211 0 0.000%
router 318.613 318.613 0 0.000%
simple_ssr 141.336 141.336 0 0.000%
ssr_router 387.974 387.974 0 0.000%
suspense 116.555 116.555 0 0.000%
timer 89.378 89.378 0 0.000%
timer_functional 98.737 98.737 0 0.000%
todomvc 142.524 142.524 0 0.000%
two_apps 86.582 86.582 0 0.000%
web_worker_fib 135.593 135.593 0 0.000%
web_worker_prime 185.986 185.986 0 0.000%
webgl 83.178 83.178 0 0.000%

✅ None of the examples has changed their size significantly.

@aDogCalledSpot
Copy link
Author

The clippy complaints don't result from the changes made in this PR.

@futursolo
Copy link
Member

futursolo commented Jan 4, 2024

the div created div is simply empty.

This is because the initial render is delayed and no longer happens immediately. The renderer now renders right before the event loop is returned to the browser so render only happens once regardless of the number of state updates that triggers a render.

You should yield to the browser / renderer with yew::platform::time::sleep(std::time::Duration::ZERO).await; so a render can happen.

@aDogCalledSpot
Copy link
Author

You should yield to the browser with yew::platform::time::sleep(std::time::Duration::ZERO).await; so events can be processed.

This works! I do wonder if there might not be a way to support handling the events immediately in #[cfg(test)]. Definitely out of scope for this PR but might be worth a discussion thread.

I guess I can close this PR since there's an easier workaround.

@aDogCalledSpot aDogCalledSpot deleted the render_single_component branch January 4, 2024 13:40
@futursolo
Copy link
Member

I do wonder if there might not be a way to support handling the events immediately in #[cfg(test)].

I think the better way might be to make the tests to search for its desired state until it reaches what its desired state (queries and assertions are awaited), like react-testing-library.

A couple years ago, I made an experimental attempt to write a wrapper for dom-testing-library so tests can run with wasm-pack test --node. However, I never made this a complete implementation since wasm-pack test is kinda buggy in some situation and most of time users can use playwright / cypress and they are more feature complete, works already and tests written in these framework are easier to debug (as you can see it in the browser).

See: https://github.com/futursolo/reality-rs/blob/master/crates/reality-yew/src/lib.rs#L12

@aDogCalledSpot
Copy link
Author

I would like to avoid using cypress for smaller components or very simple interactions. If Cypress component testing would work for Yew, then that would of course be perfect but using end-to-end tests for everything seems a bit nuked.

I think the better way might be to make the tests to search for its desired state until it reaches what its desired state (queries and assertions are awaited), like react-testing-library.

Agreed. But if this (naive) approach already works in a lot of cases then I'll be happy with that until something better comes along 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants