Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hotwired/turbo
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.2.5
Choose a base ref
...
head repository: hotwired/turbo
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.3.0
Choose a head ref
  • 15 commits
  • 37 files changed
  • 7 contributors

Commits on Jan 31, 2023

  1. Fix test failure on Firefox for Mac

    This test was consistently failing on my Mac machine.
    
    It was because Firefox for Mac doesn't focus on links on TAB presses.
    
    Ref. https://stackoverflow.com/a/11713537
    afcapel committed Jan 31, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0571735 View commit details

Commits on Feb 1, 2023

  1. Copy the full SHA
    e108616 View commit details

Commits on Feb 2, 2023

  1. Merge pull request #862 from hotwired/fix-firefox-mac-test-failure

    Fix test failure on Firefox for Mac
    afcapel authored Feb 2, 2023
    Copy the full SHA
    ac2f78a View commit details

Commits on Feb 4, 2023

  1. Migrate from intern to web-test-runner (#866)

    * migrate to WTR
    
    * migrate to web-test-runner
    
    * remove intern dependency
    
    * remove intern_test_case
    
    * chore: prettier
    
    * update to tdd
    KonnorRogers authored Feb 4, 2023
    Copy the full SHA
    a7d6566 View commit details

Commits on Feb 7, 2023

  1. Don't break out of frames when frame missing (#863)

    Don't break out of frames when frame missing
    
    This changes the default behaviour when a frame response is missing its
    expected `<turbo-frame>` element.
    
    Previously, when the response was missing its frame, we would trigger a
    `turbo:frame-missing` event, and then (provided that event wasn't
    cancelled) perform a full page visit to the requested URL.
    
    However there are cases where the full reload makes things worse:
    
    - If the frame contents were non-critical, reloading the page can turn a
      minor bug into a major one.
    - It can mask some bugs where frames were intend to explicitly navigate
      out of the frame (`target="_top"`), by incurring a second request that
      loads the page that makes it seem as if it's working corrects.
    - It leaves the user at a URL that may never be capable of rendering a
      valid response (since that URL was only intended to serve a particular
      frame). That means refreshing the page is no help in getting back to a
      working state.
    - It can lose other temporary state on a page, like form values.
    
    With this change, we no longer perform the full page visit. Instead, we
    handle a missing frame by doing two things:
    
    - Write a short error message into the frame, so that the problem is
      visible on the page.
    - Throw an exception, which should make the problem quite obvious in
      development, and which allows it to be easily gathered by exception
      monitoring tools in production.
    
    We keep the `turbo:frame-missing` event exactly as before, so
    applications can still hook in to perform alternative behaviour if they
    want.
    kevinmcconnell authored Feb 7, 2023
    Copy the full SHA
    91ee8f6 View commit details
  2. Respect turbo-visit-control for frame requests (#867)

    Turbo normally performs a fill page reload whenever a response contains
    the appropriate `turbo-visit-control` meta tag:
    
        <meta name="turbo-visit-control" content="reload">
    
    Such responses are considered "not visitable".
    
    For frame requests, we have previously been ignoring any
    `turbo-visit-control` set in the response, and instead treating all
    valid frame responses as "visitable".
    
    This commit changes this behaviour so that `turbo-visit-control` will be
    treated consistently for both frame and non-frame requests.
    
    As well as being more consistent, this provides a useful escape hatch
    for situations where a frame request redirects to something that should
    be a full page reload, but which would be prevented due to that content
    missing the expected frame. The class example of this is when an expired
    session causes a frame request to be redirected to a login page. By
    including `turbo-visit-control` on that login page, we can ensure that
    it is always rendered as a full page, and never hidden by a failed frame
    request.
    kevinmcconnell authored Feb 7, 2023
    1
    Copy the full SHA
    1e78f3b View commit details

Commits on Feb 14, 2023

  1. Copy the full SHA
    61201b9 View commit details
  2. Merge pull request #870 from hotwired/jh/contributing

    Freshen and copy-edit the contributing documentation
    packagethief authored Feb 14, 2023
    1
    Copy the full SHA
    3351d38 View commit details

Commits on Feb 15, 2023

  1. Deprecate [data-turbo-cache=false] in favor of [data-turbo-temporary] (

    …#871)
    
    Renames the `[data-turbo-cache=false]` attribute (used to denote temporary
    elements that should be removed before caching) to `[data-turbo-temporary]` for
    better similarity with `[data-turbo-permanent]`, its conceptual opposite.
    
    This is a superficial change, but worth it in terms of cohesion, I think. The
    pairing of "temporary" with "permanent" is just too good to ignore. Also, given
    the existence of `turbo-cache-*` as the namespace for page-level cache control,
    a unique name is less likely to confuse.
    
    References:
    - #238
    packagethief authored Feb 15, 2023
    1
    Copy the full SHA
    e013072 View commit details

Commits on Feb 23, 2023

  1. Allow changing the submitter text during form submission (#869)

    This change introduces a new optional data-turbo-submits-with text
    attribute that can be set on submit elements (inputs or buttons) in
    Turbo forms.
    
          <form action="/" method="post">
            <input type="submit" value="Save" data-turbo-submits-with="Saving...">
          </form>
    
    When the form is in a submitting state Turbo will change the submitter
    content -the input value for inputs, and the innerHTML for buttons- with
    the data-turbo-submits-with value, and restore the original value when the
    submission ends.
    
    This is a common requirement in many apps, to style the form submitting
    state and give feedback to the user that a click is already being
    processed.
    afcapel authored Feb 23, 2023
    1
    Copy the full SHA
    455ffe0 View commit details

Commits on Feb 24, 2023

  1. 1
    Copy the full SHA
    133271f View commit details
  2. 1
    Copy the full SHA
    483ef32 View commit details

Commits on Feb 27, 2023

  1. Form submissions from frames should clear cache (#882)

    Usually when submitting a form with a method other than `GET`, we clear
    the snapshot cache. This helps to avoid cases where we might show a
    stale view of some data that we just mutated.
    
    Form submissions from inside frames were not doing this. But they ought
    to, for the same reason: that form submission may have mutated some data
    that causes other recent pages to become stale.
    
    To avoid this, we can clear the cache after processing a non-`GET` form,
    or after any failed form submission, to mirror what we do when the form
    is outside of any frames.
    kevinmcconnell authored Feb 27, 2023
    1
    Copy the full SHA
    39affe5 View commit details

Commits on Feb 28, 2023

  1. Rename isIdempotent to isSafe (#883)

    The method name was a bit misleading, as what we're actually checking
    for here is whether the method is [safe][0].
    
    We can also now make use of this method in a couple of places where we
    clear the snapshot cache after form submissions. The logic there is to
    clear the cache when performing an operation that is unsafe.
    
    [0]: https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
    kevinmcconnell authored Feb 28, 2023
    1
    Copy the full SHA
    7bd2ce8 View commit details

Commits on Mar 1, 2023

  1. Turbo v7.3.0

    dhh committed Mar 1, 2023
    1
    Copy the full SHA
    4593d06 View commit details
Showing with 2,342 additions and 1,507 deletions.
  1. +34 −63 CONTRIBUTING.md
  2. +0 −20 intern.json
  3. +16 −10 package.json
  4. +0 −50 rollup.config.js
  5. +36 −5 src/core/drive/form_submission.ts
  6. +1 −2 src/core/drive/navigator.ts
  7. +1 −0 src/core/errors.ts
  8. +50 −31 src/core/frames/frame_controller.ts
  9. +2 −2 src/core/frames/frame_view.ts
  10. +5 −4 src/core/index.ts
  11. +1 −1 src/core/view.ts
  12. +1 −1 src/elements/stream_source_element.ts
  13. +4 −4 src/http/fetch_request.ts
  14. +1 −1 src/http/index.ts
  15. +23 −6 src/observers/cache_observer.ts
  16. +3 −0 src/polyfills/custom-elements-native-shim.ts
  17. +1 −3 src/polyfills/submit-event.ts
  18. +2 −1 src/tests/fixtures/cache_observer.html
  19. +11 −0 src/tests/fixtures/form.html
  20. +4 −1 src/tests/fixtures/frames.html
  21. +16 −0 src/tests/fixtures/frames/unvisitable.html
  22. +18 −7 src/tests/functional/cache_observer_tests.ts
  23. +34 −0 src/tests/functional/form_submission_tests.ts
  24. +50 −23 src/tests/functional/frame_tests.ts
  25. +1 −4 src/tests/functional/navigation_tests.ts
  26. +2 −3 src/tests/functional/rendering_tests.ts
  27. +1 −3 src/tests/helpers/dom_test_case.ts
  28. +0 −70 src/tests/helpers/intern_test_case.ts
  29. +0 −50 src/tests/runner.js
  30. +14 −1 src/tests/server.ts
  31. +24 −30 src/tests/unit/{deprecated_adapter_support_test.ts → deprecated_adapter_support_tests.ts}
  32. +18 −21 src/tests/unit/export_tests.ts
  33. +0 −3 src/tests/unit/index.ts
  34. +151 −156 src/tests/unit/stream_element_tests.ts
  35. +1 −0 tsconfig.json
  36. +21 −0 web-test-runner.config.mjs
  37. +1,795 −931 yarn.lock
97 changes: 34 additions & 63 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -3,125 +3,96 @@

# Contributing

Please note we have a [code of conduct](https://github.com/hotwired/turbo/blob/main/CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
Note that we have a [code of conduct](https://github.com/hotwired/turbo/blob/main/CODE_OF_CONDUCT.md). Please follow it in your interactions with this project.

## Sending a Pull Request

The core team is monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation.

Before submitting a pull request, please make sure the following is done:
Before submitting a pull request, please:

1. Fork the repository and create your branch from main.
2. Run `yarn` in the repository root.
3. If you’ve fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn build && yarn test`).
1. Fork the repository and create your branch.
2. Follow the setup instructions in this file.
3. If you’re fixing a bug or adding code that should be tested, add tests!
4. Ensure the test suite passes.

## Developing locally

During the process of developing the library locally we first have to check out the repository and create a branch from main.
First, clone the `hotwired/turbo` repository and install dependencies:

```bash
git clone https://github.com/hotwired/turbo.git
cd turbo
yarn
```

```bash
git checkout -b '<your_branch_name>'
cd turbo
yarn install
```

Once you are done developing the feature or bug fix you have 2 options:
Then create a branch for your changes:

1. Run the test suite
2. Run a local webserver and checkout your changes manually
```bash
git checkout -b <your_branch_name>
```

### Testing

The library is tested by running the test suite (found in: `src/tests/*`) against headless browsers. The browsers are setup in [intern.json](./intern.json) and [playwright.config.ts](./playwright.config.ts). Check them out to see the used browser environments.
Tests are run through `yarn` using [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) with [Playwright](https://github.com/microsoft/playwright) for browser testing. Browser and runtime configuration can be found in [`web-test-runner.config.mjs`](./web-test-runner.config.mjs) and [`playwright.config.ts`](./playwright.config.ts).

To override the ChromeDriver version, declare the `CHROMEVER` environment
variable.

First, install the drivers to test the suite in browsers:
To begin testing, install the browser drivers:

```bash
yarn playwright install --with-deps
yarn playwright install --with-deps
```

The tests are using the compiled version of the library and they are themselves also compiled. To compile the tests and library and watch for changes:
Then build the source. Because tests are run against the compiled source (and are themselves compiled) be sure to run `yarn build` prior to testing. Alternatively, you can run `yarn watch` to build and watch for changes.

```bash
yarn watch
yarn build
```

To run the unit tests:
### Running the test suite

The test suite can be run with `yarn`, using the test commands defined in [`package.json`](./package.json). To run all tests in all configured browsers:

```bash
yarn test:unit
yarn test
```

To run the browser tests:
To run just the unit or browser tests:

```bash
yarn test:unit
yarn test:browser
```

To run the browser suite against a particular browser (one of
`chrome|firefox`), pass the value as the `--project=$BROWSER` flag:
By default, tests are run in "headless" mode against all configured browsers (currently `chrome` and `firefox`). Use the `--headed` flag to run in normal mode. Use the `--project` flag to run against a particular browser.

```bash
yarn test:browser --project=firefox
yarn test:browser --project=chrome
```

To run the browser tests in a "headed" browser, pass the `--headed` flag:

```bash
yarn test:browser --project=chrome --headed
```

### Test files

Please add your tests in the test files closely related to the feature itself. For example when touching the `src/core/drive/page_renderer.ts` your test will probably endup in the `src/tests/functional/rendering_tests.ts`.

The html files needed for the tests are stored in: `src/tests/fixtures/`
### Running a single test

### Run single test

To focus on single test, pass its file path:

```bash
yarn test:browser TEST_FILE
```

Where the `TEST_FILE` is the name of test you want to run. For example:
To run a single test file, pass its path as an argument. To run a particular test case, append its starting line number after a colon.

```bash
yarn test:browser src/tests/functional/drive_tests.ts
```

To execute a particular test, append `:LINE` where `LINE` is the line number of
the call to `test("...")`:

```bash
yarn test:browser src/tests/functional/drive_tests.ts:11
yarn test:browser src/tests/functional/drive_tests.ts:11 --project=chrome
```

### Local webserver
### Running the local web server

Since the tests are running in headless browsers it's not easy to debug them easily without using the debugger. Sometimes it's easier to run the supplied webserver and manually click through the test fixtures.
Because tests are running headless in browsers, debugging can be difficult. Sometimes the simplest thing to do is load the test fixtures into the browser and navigate manually. To make this easier, a local web server is included.

To run the webserver:
To run the web server, ensure the source is built and start the server with `yarn`:

```bash
yarn build
yarn start
```

This requires a build (via `yarn build`), or a separate process running `yarn watch`.

The webserver is available on port 9000. Since the webserver is run from the root of the project the fixtures can be found using the same path as they have in the project itself, so `src/tests/fixtures/rendering.html` makes: http://localhost:9000/src/tests/fixtures/rendering.html

Depending on your operating system you are able to open the page using:

```bash
open http://localhost:9000/src/tests/fixtures/rendering.html
```
The web server is available on port 9000, serving from the project root. Fixture files are accessible by path. For example, the file at `src/tests/fixtures/rendering.html` will be accessible at <http://localhost:9000/src/tests/fixtures/rendering.html>.
20 changes: 0 additions & 20 deletions intern.json

This file was deleted.

26 changes: 16 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hotwired/turbo",
"version": "7.2.5",
"version": "7.3.0",
"description": "The speed of a single-page web application without having to write any JavaScript",
"module": "dist/turbo.es2017-esm.js",
"main": "dist/turbo.es2017-umd.js",
@@ -35,35 +35,41 @@
"access": "public"
},
"devDependencies": {
"@open-wc/testing": "^3.1.7",
"@playwright/test": "^1.28.0",
"@rollup/plugin-node-resolve": "13.1.3",
"@rollup/plugin-typescript": "^8.5.0",
"@rollup/plugin-typescript": "^11.0.0",
"@types/multer": "^1.4.5",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"@web/dev-server-esbuild": "^0.3.3",
"@web/test-runner": "^0.15.0",
"@web/test-runner-playwright": "^0.9.0",
"arg": "^5.0.1",
"body-parser": "^1.20.1",
"chai": "~4.3.4",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"intern": "^4.9.0",
"express": "^4.18.2",
"multer": "^1.4.2",
"prettier": "2.6.2",
"rollup": "^2.35.1",
"tslib": "^2.4.0",
"typescript": "^4.8.2"
"ts-node": "^10.9.1",
"tslib": "^2.5.0",
"typescript": "^4.9.5"
},
"scripts": {
"clean": "rm -fr dist",
"clean:win": "rmdir /s /q dist",
"build": "tsc --noEmit false --declaration true --emitDeclarationOnly true --outDir dist/types && rollup -c",
"build:win": "tsc --noEmit false --declaration true --emitDeclarationOnly true --outDir dist/types & rollup -c",
"watch": "rollup -wc",
"start": "node src/tests/runner.js serveOnly",
"start": "ts-node -O '{\"module\":\"commonjs\"}' src/tests/server.ts",
"test": "yarn test:unit && yarn test:browser",
"test:browser": "playwright test",
"test:unit": "NODE_OPTIONS=--inspect node src/tests/runner.js",
"test:unit:win": "SET NODE_OPTIONS=--inspect & node src/tests/runner.js",
"test:unit": "NODE_OPTIONS=--inspect web-test-runner",
"test:unit:win": "SET NODE_OPTIONS=--inspect & web-test-runner",
"release": "yarn build && npm publish",
"lint": "eslint . --ext .ts"
},
50 changes: 0 additions & 50 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -28,55 +28,5 @@ export default [
watch: {
include: "src/**"
}
},

{
input: "src/tests/unit/index.ts",
output: [
{
name: "tests_unit",
file: "dist/tests/unit.js",
format: "iife",
sourcemap: true,
globals: {
intern: "intern"
}
}
],
plugins: [
resolve(),
typescript()
],
external: [
"intern"
],
watch: {
include: "src/tests/**"
}
},

{
input: "src/tests/server.ts",
output: [
{
file: "dist/tests/server.js",
format: "cjs",
sourcemap: true
}
],
plugins: [
resolve(),
typescript()
],
external: [
"express",
"multer",
"path",
"url",
"fs"
],
watch: {
include: "src/tests/**"
}
}
]
41 changes: 36 additions & 5 deletions src/core/drive/form_submission.ts
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@ export class FormSubmission {
readonly mustRedirect: boolean
state = FormSubmissionState.initialized
result?: FormSubmissionResult
originalSubmitText?: string

static confirmMethod(
message: string,
@@ -109,8 +110,8 @@ export class FormSubmission {
return formEnctypeFromString(this.submitter?.getAttribute("formenctype") || this.formElement.enctype)
}

get isIdempotent() {
return this.fetchRequest.isIdempotent
get isSafe() {
return this.fetchRequest.isSafe
}

get stringFormData() {
@@ -150,7 +151,7 @@ export class FormSubmission {
// Fetch request delegate

prepareRequest(request: FetchRequest) {
if (!request.isIdempotent) {
if (!request.isSafe) {
const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token")
if (token) {
request.headers["X-CSRF-Token"] = token
@@ -165,6 +166,7 @@ export class FormSubmission {
requestStarted(_request: FetchRequest) {
this.state = FormSubmissionState.waiting
this.submitter?.setAttribute("disabled", "")
this.setSubmitsWith()
dispatch<TurboSubmitStartEvent>("turbo:submit-start", {
target: this.formElement,
detail: { formSubmission: this },
@@ -202,6 +204,7 @@ export class FormSubmission {
requestFinished(_request: FetchRequest) {
this.state = FormSubmissionState.stopped
this.submitter?.removeAttribute("disabled")
this.resetSubmitterText()
dispatch<TurboSubmitEndEvent>("turbo:submit-end", {
target: this.formElement,
detail: { formSubmission: this, ...this.result },
@@ -211,12 +214,40 @@ export class FormSubmission {

// Private

setSubmitsWith() {
if (!this.submitter || !this.submitsWith) return

if (this.submitter.matches("button")) {
this.originalSubmitText = this.submitter.innerHTML
this.submitter.innerHTML = this.submitsWith
} else if (this.submitter.matches("input")) {
const input = this.submitter as HTMLInputElement
this.originalSubmitText = input.value
input.value = this.submitsWith
}
}

resetSubmitterText() {
if (!this.submitter || !this.originalSubmitText) return

if (this.submitter.matches("button")) {
this.submitter.innerHTML = this.originalSubmitText
} else if (this.submitter.matches("input")) {
const input = this.submitter as HTMLInputElement
input.value = this.originalSubmitText
}
}

requestMustRedirect(request: FetchRequest) {
return !request.isIdempotent && this.mustRedirect
return !request.isSafe && this.mustRedirect
}

requestAcceptsTurboStreamResponse(request: FetchRequest) {
return !request.isIdempotent || hasAttribute("data-turbo-stream", this.submitter, this.formElement)
return !request.isSafe || hasAttribute("data-turbo-stream", this.submitter, this.formElement)
}

get submitsWith() {
return this.submitter?.getAttribute("data-turbo-submits-with")
}
}

3 changes: 1 addition & 2 deletions src/core/drive/navigator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Action } from "../types"
import { getVisitAction } from "../../util"
import { FetchMethod } from "../../http/fetch_request"
import { FetchResponse } from "../../http/fetch_response"
import { FormSubmission } from "./form_submission"
import { expandURL, getAnchor, getRequestURL, Locatable, locationIsVisitable } from "../url"
@@ -85,7 +84,7 @@ export class Navigator {
if (formSubmission == this.formSubmission) {
const responseHTML = await fetchResponse.responseHTML
if (responseHTML) {
const shouldCacheSnapshot = formSubmission.method == FetchMethod.get
const shouldCacheSnapshot = formSubmission.isSafe
if (!shouldCacheSnapshot) {
this.view.clearSnapshotCache()
}
Loading