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

Reorganize Turbo Events and declare events on global event map #800

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

marcoroth
Copy link
Member

@marcoroth marcoroth commented Nov 21, 2022

This Pull Request reorganizes the events Turbo knows about and puts them into a single src/events.ts file.

Additionally it declares the Turbo events on GlobalEventHandlersEventMap and ElementEventMap so that the callbacks-arguments of event-listeners can be typed.

For example

Before:

import { TurboBeforeFetchRequestEvent } from "@hotwired/turbo"

document.addEventListener("turbo:before-fetch-request", (e) => {
  // `e` is typed as `Event`

  const event = e as TurboBeforeFetchRequestEvent
  // `event` is now typed as `TurboBeforeFetchRequestEvent`
})

After:

import { TurboBeforeFetchRequestEvent } from "@hotwired/turbo"

document.addEventListener("turbo:before-fetch-request", (event: TurboBeforeFetchRequestEvent) => {
  // `event` is typed as `TurboBeforeFetchRequestEvent`
})

Previously you would get a No overload matches this call error:

error TS2769: No overload matches this call.
  Overload 1 of 2, '(type: keyof WindowEventMap, listener: (this: Window, ev: MessageEvent<any> | Event | UIEvent | AnimationEvent | MouseEvent | ... 22 more ... | StorageEvent) => any, options?: boolean | ... 1 more ... | undefined): void', gave the following error.

Additional note: this came up as part of hotwired/turbo-rails#392

Copy link
Contributor

@seanpdoyle seanpdoyle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this! Thank you for taking it on.

There are a bunch of event listeners in the test suite, would you be able to remove their type casting and type guards as part of this change?

@marcoroth
Copy link
Member Author

Good point, will do 👍🏼

@marcoroth marcoroth force-pushed the reorganize-events branch 2 times, most recently from 79323fc to 26d6c8c Compare November 22, 2022 19:55
@marcoroth
Copy link
Member Author

I'm kinda confused on why and how the tests are failing. I get the same errors locally, trying to figure out what's going on.

seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Dec 29, 2022
Introduce a beat-long delay to reduce test flakiness from a race
condition between the initial `page.click` and the subsequent
`page.evaluate` calls.

For an example of the flakiness, this message was copied from a [CI
failure on hotwired#430][]:

```
  1) [chrome] › rendering_tests.ts:68:1 › test reloads when tracked elements change due to failed form submission

    page.evaluate: Execution context was destroyed, most likely because of a navigation

      88 |   await page.click("#tracked-asset-change-form button")
      89 |
    > 90 |   const reason = await page.evaluate(() => localStorage.getItem("reason"))
         |                             ^
      91 |   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
      92 |
      93 |   assert.equal(pathname(page.url()), "/src/tests/fixtures/rendering.html")
```

Another occurrence can be cited from a [CI failure on hotwired#800][]:

```
  1) [chrome] › rendering_tests.ts:77:1 › test reloads when tracked elements change due to failed form submission

    page.evaluate: Execution context was destroyed, most likely because of a navigation

       97 |   await page.click("#tracked-asset-change-form button")
       98 |
    >  99 |   const reason = await page.evaluate(() => localStorage.getItem("reason"))
          |                             ^
      100 |   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
      101 |
      102 |   assert.equal(pathname(page.url()), "/src/tests/fixtures/rendering.html")
```

Troubleshooting this flakiness, uncovered a `console.error` message from
the `rendering.html` test fixture:

```
rendering.html:1 An import map is added after module script load was triggered.
```

To resolve that issue, this commit also re-orders the `<head>` elements
so that the `script[type="importmap"]` element is rendered before the
`script[type="module"]`.

[CI failure on hotwired#430]: https://github.com/hotwired/turbo/actions/runs/3797607273/jobs/6458693682#step:11:17
[CI failure on hotwired#800]: https://github.com/hotwired/turbo/actions/runs/3526641057/jobs/5914800548#step:11:16
@seanpdoyle
Copy link
Contributor

@marcoroth I've encountered the CI flakiness as well, and have opened #833 to resolve it. Could you try pulling in that change and seeing if you have better luck in CI?

seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Dec 29, 2022
Introduce a beat-long delay to reduce test flakiness from a race
condition between the initial `page.click` and the subsequent
`page.evaluate` calls.

For an example of the flakiness, this message was copied from a [CI
failure on hotwired#430][]:

```
  1) [chrome] › rendering_tests.ts:68:1 › test reloads when tracked elements change due to failed form submission

    page.evaluate: Execution context was destroyed, most likely because of a navigation

      88 |   await page.click("#tracked-asset-change-form button")
      89 |
    > 90 |   const reason = await page.evaluate(() => localStorage.getItem("reason"))
         |                             ^
      91 |   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
      92 |
      93 |   assert.equal(pathname(page.url()), "/src/tests/fixtures/rendering.html")
```

Another occurrence can be cited from a [CI failure on hotwired#800][]:

```
  1) [chrome] › rendering_tests.ts:77:1 › test reloads when tracked elements change due to failed form submission

    page.evaluate: Execution context was destroyed, most likely because of a navigation

       97 |   await page.click("#tracked-asset-change-form button")
       98 |
    >  99 |   const reason = await page.evaluate(() => localStorage.getItem("reason"))
          |                             ^
      100 |   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
      101 |
      102 |   assert.equal(pathname(page.url()), "/src/tests/fixtures/rendering.html")
```

Troubleshooting this flakiness, uncovered a `console.error` message from
the `rendering.html` test fixture:

```
rendering.html:1 An import map is added after module script load was triggered.
```

To resolve that issue, this commit also re-orders the `<head>` elements
so that the `script[type="importmap"]` element is rendered before the
`script[type="module"]`.

[CI failure on hotwired#430]: https://github.com/hotwired/turbo/actions/runs/3797607273/jobs/6458693682#step:11:17
[CI failure on hotwired#800]: https://github.com/hotwired/turbo/actions/runs/3526641057/jobs/5914800548#step:11:16
dhh pushed a commit that referenced this pull request Dec 31, 2022
Introduce a beat-long delay to reduce test flakiness from a race
condition between the initial `page.click` and the subsequent
`page.evaluate` calls.

For an example of the flakiness, this message was copied from a [CI
failure on #430][]:

```
  1) [chrome] › rendering_tests.ts:68:1 › test reloads when tracked elements change due to failed form submission

    page.evaluate: Execution context was destroyed, most likely because of a navigation

      88 |   await page.click("#tracked-asset-change-form button")
      89 |
    > 90 |   const reason = await page.evaluate(() => localStorage.getItem("reason"))
         |                             ^
      91 |   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
      92 |
      93 |   assert.equal(pathname(page.url()), "/src/tests/fixtures/rendering.html")
```

Another occurrence can be cited from a [CI failure on #800][]:

```
  1) [chrome] › rendering_tests.ts:77:1 › test reloads when tracked elements change due to failed form submission

    page.evaluate: Execution context was destroyed, most likely because of a navigation

       97 |   await page.click("#tracked-asset-change-form button")
       98 |
    >  99 |   const reason = await page.evaluate(() => localStorage.getItem("reason"))
          |                             ^
      100 |   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
      101 |
      102 |   assert.equal(pathname(page.url()), "/src/tests/fixtures/rendering.html")
```

Troubleshooting this flakiness, uncovered a `console.error` message from
the `rendering.html` test fixture:

```
rendering.html:1 An import map is added after module script load was triggered.
```

To resolve that issue, this commit also re-orders the `<head>` elements
so that the `script[type="importmap"]` element is rendered before the
`script[type="module"]`.

[CI failure on #430]: https://github.com/hotwired/turbo/actions/runs/3797607273/jobs/6458693682#step:11:17
[CI failure on #800]: https://github.com/hotwired/turbo/actions/runs/3526641057/jobs/5914800548#step:11:16
@seanpdoyle
Copy link
Contributor

@marcoroth if you rebase the latest, you should be able to pass CI.

@marcoroth
Copy link
Member Author

Thanks @seanpdoyle! It seems like the failures I was seeing are gone now. Now it just fails with the same failures as on main

@seanpdoyle
Copy link
Contributor

@marcoroth 🎉 ! I've opened #838 to try and fix main.

@seanpdoyle
Copy link
Contributor

@marcoroth if you want to get this branch passing while we wait for #838 to be merged, you can apply this diff:

diff --git a/src/tests/fixtures/navigation.html b/src/tests/fixtures/navigation.html
index 48953b7..f2fd53d 100644
--- a/src/tests/fixtures/navigation.html
+++ b/src/tests/fixtures/navigation.html
@@ -15,7 +15,7 @@
 
     <a href="#ignored-link" id="ignored-link">Skipped Content</a>
 
-    <section id="main" style="height: 200vh">
+    <section id="main">
       <h1>Navigation</h1>
       <p><a id="same-origin-unannotated-link" href="/src/tests/fixtures/one.html">Same-origin unannotated link</a></p>
       <p><a id="same-origin-unannotated-link-search-params" href="/src/tests/fixtures/one.html?key=value">Same-origin unannotated link ?key=value</a></p>
diff --git a/src/tests/functional/rendering_tests.ts b/src/tests/functional/rendering_tests.ts
index ba5555e..2ac873d 100644
--- a/src/tests/functional/rendering_tests.ts
+++ b/src/tests/functional/rendering_tests.ts
@@ -88,6 +88,7 @@ test("test reloads when tracked elements change due to failed form submission",
   })
 
   await page.click("#tracked-asset-change-form button")
+  await nextBeat()
 
   const reason = await page.evaluate(() => localStorage.getItem("reason"))
   const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))

@marcoroth
Copy link
Member Author

Thanks @seanpdoyle 🙏🏼

Looking good now!

src/events.ts Outdated Show resolved Hide resolved
@seanpdoyle
Copy link
Contributor

if you think we just just limit it to HTMLElement let me know.

I was thinking it'd make sense since the custom elements that dispatch those events descend from HTMLElement. Thinking more on it now, the type of element that dispatches the event matters a lot less than the type of element that listens for the event.

Having said that, I'm not even sure which tags in a Real World application descend from Element instead of HTMLElement, and HTMLElement descends from Element, so it might all be a moot point.

@stof
Copy link

stof commented Jan 17, 2023

Given that querySelector and querySelectorAll are typed as returning Element, adding those events only for HTMLElement would force apps to refine the type of the element to be able to register the events with those type benefits. So I would prefer registering them on Element.

@marcoroth
Copy link
Member Author

marcoroth commented Jan 18, 2023

I think it makes sense to keep them on Element since HTMLElement is anyway inheriting from Element, so we are not really loosing anything by doing that.

That being said, I'm still looking to incorporate @seanpdoyle's suggestion to use GlobalEventHandlersEventMap.

@marcoroth marcoroth changed the title Reorganize Turbo Events and declare events on WindowEventMap Reorganize Turbo Events and declare events on GlobalEventHandlersEventMap Jan 18, 2023
@marcoroth marcoroth changed the title Reorganize Turbo Events and declare events on GlobalEventHandlersEventMap Reorganize Turbo Events and declare events on global event map Jan 18, 2023
@jdelStrother
Copy link

Anything we can do that would help get this merged?

@brunoprietog
Copy link
Collaborator

Does this still make sense with the switch to JavaScript?

@jdelStrother
Copy link

@marcoroth hope it's ok, I reused a lot of your work here to update @types/hotwired__turbo at DefinitelyTyped/DefinitelyTyped#69392 .

@marcoroth
Copy link
Member Author

@jdelStrother this is great, thank you!

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

Successfully merging this pull request may close these issues.

5 participants