Skip to content

Commit

Permalink
Merge remote-tracking branch 'turbo/main' into frame-history-update
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelpuyol committed Jul 18, 2022
2 parents 93395bc + 837e977 commit eedb214
Show file tree
Hide file tree
Showing 60 changed files with 6,370 additions and 5,881 deletions.
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -7,6 +7,7 @@ jobs:

runs-on: ubuntu-latest


steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand All @@ -18,6 +19,7 @@ jobs:
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}

- run: yarn install
- run: yarn run playwright install --with-deps
- run: yarn build

- name: Set Chrome Version
Expand All @@ -29,8 +31,14 @@ jobs:
- name: Lint
run: yarn lint

- name: Test
run: yarn test
- name: Unit Test
run: yarn test:unit

- name: Chrome Test
run: yarn test:browser --project=chrome

- name: Firefox Test
run: yarn test:browser --project=firefox

- name: Publish dev build
run: .github/scripts/publish-dev-build '${{ secrets.DEV_BUILD_GITHUB_TOKEN }}'
Expand Down
52 changes: 43 additions & 9 deletions CONTRIBUTING.md
Expand Up @@ -34,21 +34,46 @@ Once you are done developing the feature or bug fix you have 2 options:
2. Run a local webserver and checkout your changes manually

### Testing
The library is tested by running the test suite (found in: `src/tests/*`) against headless browsers. The browsers are setup in `intern.json` check it out to see the used browser environments.
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.

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

First, install the drivers to test the suite in browsers:

``bash
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:
```bash
yarn watch
```

To run the tests:
To run the unit tests:

```bash
yarn test:unit
```

To run the browser tests:

```bash
yarn test:browser
```

To run the browser suite against a particular browser (one of
`chrome|firefox`), pass the value as the `--project=$BROWSER` flag:

```bash
yarn test
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
Expand All @@ -58,14 +83,23 @@ The html files needed for the tests are stored in: `src/tests/fixtures/`

### Run single test

To focus on single test grep for it:
```javascript
yarn test --grep TEST_CASE_NAME
To focus on single test, pass its file path:

```bas
yarn test:browser TEST_FILE
```

Where the `TEST_CASE_NAME` is the name of test you want to run. For example:
```javascript
yarn test --grep 'triggers before-render and render events'
Where the `TEST_FILE` is the name of test you want to run. For example:

```base
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
```

### Local webserver
Expand Down
1 change: 0 additions & 1 deletion intern.json
@@ -1,6 +1,5 @@
{
"suites": "dist/tests/unit.js",
"functionalSuites": "dist/tests/functional.js",
"environments": [
{
"browserName": "chrome",
Expand Down
11 changes: 9 additions & 2 deletions package.json
Expand Up @@ -35,12 +35,14 @@
"access": "public"
},
"devDependencies": {
"@playwright/test": "^1.22.2",
"@rollup/plugin-node-resolve": "13.1.3",
"@rollup/plugin-typescript": "8.3.1",
"@types/multer": "^1.4.5",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"arg": "^5.0.1",
"chai": "~4.3.4",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
Expand All @@ -58,10 +60,15 @@
"build:win": "tsc --noEmit false --declaration true --emitDeclarationOnly true --outDir dist/types & rollup -c",
"watch": "rollup -wc",
"start": "node src/tests/runner.js serveOnly",
"test": "NODE_OPTIONS=--inspect node src/tests/runner.js",
"test:win": "SET NODE_OPTIONS=--inspect & node src/tests/runner.js",
"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",
"prerelease": "yarn build && git --no-pager diff && echo && npm pack --dry-run && echo && read -n 1 -p \"Look OK? Press any key to publish and commit v$npm_package_version\" && echo",
"release": "npm publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push",
"lint": "eslint . --ext .ts"
},
"engines": {
"node": ">= 14"
}
}
27 changes: 27 additions & 0 deletions playwright.config.ts
@@ -0,0 +1,27 @@
import { type PlaywrightTestConfig, devices } from "@playwright/test"

const config: PlaywrightTestConfig = {
projects: [
{
name: "chrome",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
],
testDir: "./src/tests/functional",
testMatch: /.*_tests\.ts/,
webServer: {
command: "yarn start",
url: "http://localhost:9000/src/tests/fixtures/test.js",
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: "http://localhost:9000/",
},
}

export default config
22 changes: 0 additions & 22 deletions rollup.config.js
Expand Up @@ -30,28 +30,6 @@ export default [
}
},

{
input: "src/tests/functional/index.ts",
output: [
{
file: "dist/tests/functional.js",
format: "cjs",
sourcemap: true
}
],
plugins: [
resolve(),
typescript()
],
external: [
"http",
"intern"
],
watch: {
include: "src/tests/**"
}
},

{
input: "src/tests/unit/index.ts",
output: [
Expand Down
30 changes: 30 additions & 0 deletions src/core/cache.ts
@@ -0,0 +1,30 @@
import { Session } from "./session"
import { setMetaContent } from "../util"

export class Cache {
readonly session: Session

constructor(session: Session) {
this.session = session
}

clear() {
this.session.clearCache()
}

resetCacheControl() {
this.setCacheControl("")
}

exemptPageFromCache() {
this.setCacheControl("no-cache")
}

exemptPageFromPreview() {
this.setCacheControl("no-preview")
}

private setCacheControl(value: string) {
setMetaContent("turbo-cache-control", value)
}
}
10 changes: 8 additions & 2 deletions src/core/drive/error_renderer.ts
Expand Up @@ -2,15 +2,21 @@ import { PageSnapshot } from "./page_snapshot"
import { Renderer } from "../renderer"

export class ErrorRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
static renderElement(currentElement: HTMLBodyElement, newElement: HTMLBodyElement) {
const { documentElement, body } = document

documentElement.replaceChild(newElement, body)
}

async render() {
this.replaceHeadAndBody()
this.activateScriptElements()
}

replaceHeadAndBody() {
const { documentElement, head, body } = document
const { documentElement, head } = document
documentElement.replaceChild(this.newHead, head)
documentElement.replaceChild(this.newElement, body)
this.renderElement(this.currentElement, this.newElement)
}

activateScriptElements() {
Expand Down
18 changes: 9 additions & 9 deletions src/core/drive/form_submission.ts
@@ -1,7 +1,7 @@
import { FetchRequest, FetchMethod, fetchMethodFromString, FetchRequestHeaders } from "../../http/fetch_request"
import { FetchResponse } from "../../http/fetch_response"
import { expandURL } from "../url"
import { attributeTrue, dispatch } from "../../util"
import { dispatch, getMetaContent } from "../../util"
import { StreamMessage } from "../streams/stream_message"

export interface FormSubmissionDelegate {
Expand Down Expand Up @@ -29,6 +29,11 @@ enum FormEnctype {
plain = "text/plain",
}

export type TurboSubmitStartEvent = CustomEvent<{ formSubmission: FormSubmission }>
export type TurboSubmitEndEvent = CustomEvent<
{ formSubmission: FormSubmission } & { [K in keyof FormSubmissionResult]?: FormSubmissionResult[K] }
>

function formEnctypeFromString(encoding: string): FormEnctype {
switch (encoding.toLowerCase()) {
case FormEnctype.multipart:
Expand Down Expand Up @@ -163,7 +168,7 @@ export class FormSubmission {
requestStarted(_request: FetchRequest) {
this.state = FormSubmissionState.waiting
this.submitter?.setAttribute("disabled", "")
dispatch("turbo:submit-start", {
dispatch<TurboSubmitStartEvent>("turbo:submit-start", {
target: this.formElement,
detail: { formSubmission: this },
})
Expand Down Expand Up @@ -200,7 +205,7 @@ export class FormSubmission {
requestFinished(_request: FetchRequest) {
this.state = FormSubmissionState.stopped
this.submitter?.removeAttribute("disabled")
dispatch("turbo:submit-end", {
dispatch<TurboSubmitEndEvent>("turbo:submit-end", {
target: this.formElement,
detail: { formSubmission: this, ...this.result },
})
Expand All @@ -214,7 +219,7 @@ export class FormSubmission {
}

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

Expand All @@ -241,11 +246,6 @@ function getCookieValue(cookieName: string | null) {
}
}

function getMetaContent(name: string) {
const element: HTMLMetaElement | null = document.querySelector(`meta[name="${name}"]`)
return element && element.content
}

function responseSucceededWithoutRedirect(response: FetchResponse) {
return response.statusCode == 200 && !response.redirected
}
Expand Down
14 changes: 9 additions & 5 deletions src/core/drive/page_renderer.ts
Expand Up @@ -3,6 +3,14 @@ import { PageSnapshot } from "./page_snapshot"
import { ReloadReason } from "../native/browser_adapter"

export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
static renderElement(currentElement: HTMLBodyElement, newElement: HTMLBodyElement) {
if (document.body && newElement instanceof HTMLBodyElement) {
document.body.replaceWith(newElement)
} else {
document.documentElement.appendChild(newElement)
}
}

get shouldRender() {
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical
}
Expand Down Expand Up @@ -105,11 +113,7 @@ export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
}

assignNewBody() {
if (document.body && this.newElement instanceof HTMLBodyElement) {
document.body.replaceWith(this.newElement)
} else {
document.documentElement.appendChild(this.newElement)
}
this.renderElement(this.currentElement, this.newElement)
}

get newHeadStylesheetElements() {
Expand Down
12 changes: 7 additions & 5 deletions src/core/drive/page_view.ts
@@ -1,24 +1,26 @@
import { nextEventLoopTick } from "../../util"
import { View, ViewDelegate } from "../view"
import { View, ViewDelegate, ViewRenderOptions } from "../view"
import { ErrorRenderer } from "./error_renderer"
import { PageRenderer } from "./page_renderer"
import { PageSnapshot } from "./page_snapshot"
import { SnapshotCache } from "./snapshot_cache"
import { Visit } from "./visit"

export interface PageViewDelegate extends ViewDelegate<PageSnapshot> {
export type PageViewRenderOptions = ViewRenderOptions<HTMLBodyElement>

export interface PageViewDelegate extends ViewDelegate<HTMLBodyElement, PageSnapshot> {
viewWillCacheSnapshot(): void
}

type PageViewRenderer = PageRenderer | ErrorRenderer

export class PageView extends View<Element, PageSnapshot, PageViewRenderer, PageViewDelegate> {
export class PageView extends View<HTMLBodyElement, PageSnapshot, PageViewRenderer, PageViewDelegate> {
readonly snapshotCache = new SnapshotCache(10)
lastRenderedLocation = new URL(location.href)
forceReloaded = false

renderPage(snapshot: PageSnapshot, isPreview = false, willRender = true, visit?: Visit) {
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender)
const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender)
if (!renderer.shouldRender) {
this.forceReloaded = true
} else {
Expand All @@ -29,7 +31,7 @@ export class PageView extends View<Element, PageSnapshot, PageViewRenderer, Page

renderError(snapshot: PageSnapshot, visit?: Visit) {
visit?.changeHistory()
const renderer = new ErrorRenderer(this.snapshot, snapshot, false)
const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false)
return this.render(renderer)
}

Expand Down

0 comments on commit eedb214

Please sign in to comment.