提交 8f8b077e 编写于 作者: R Roman Yakubuk

Merge branch 'master' into CrOrc-fix-resolve-IE-11

{
"parserOptions": {
"sourceType": "module"
},
"globals": {
"WHATWGFetch": true,
"ArrayBuffer": true,
"DataView": true,
"Promise": true,
"Symbol": true,
"Uint8Array": true
},
"extends": [
"plugin:github/browser"
],
"rules": {
"object-shorthand": "off"
},
"overrides": [
{
"files": ["test/*.js"],
"env": {
"browser": true,
"mocha": true
},
"globals": {
"assert": true,
"chai": true,
"FileReaderSync": true,
"Mocha": true
}
},
{
"files": ["test/{karma,server}*.js"],
"env": {
"node": true
}
},
{
"files": ["test/worker.js"],
"env": {
"worker": true
}
}
]
}
.env
package-lock.json
dist/
bower_components/
node_modules/
sauce_connect/
......
{
"curly": true,
"eqeqeq": true,
"es3": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"asi": true,
"boss": true,
"esnext": true,
"eqnull": true,
"browser": true,
"worker": true,
"globals": {
"JSON": false,
"URLSearchParams": false
}
}
sudo: false
language: node_js
node_js:
- "node"
dist: trusty
addons:
chrome: stable
firefox: latest
cache:
directories:
- phantomjs
- node_modules
deploy:
provider: npm
email: mislav.marohnic@gmail.com
api_key:
secure: gt9g5/bXhxSKjxfFSPCdpWGJKBrSG8zdGRYgPouUgRqNeD2Ff4Nc8HGQTxp0OLKnP/jJ5FIru5jUur6LWzJCyEd+aNUEvFf5J078m3pzHN9AP2fiWUkKXcc5lKV0PQnI+JDRxJwd/PggtjubrneGfCzyFoys9apRrd/TzTGEtGw=
secure: ZEyP/T3jWwvrMp/rdZoMkyc3T7SoFJpGFFCjd7rOG76k/DTRxcYVTAPES8mEq/Xk0fF8qM4ZY//izuxEtFEuJmdRFuvJcIptVrAlbEiqlyNiN1EkwRPayd3qte/UYLvD9bGrfKRVrXeohW0GGDZH48CsHVo3yNrX6NWS7Nxxze4=
on:
tags: true
repo: github/fetch
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource+fetch@github.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
# Contributing
Thank you for your interest in contributing to our `fetch` polyfill!
Note that we only accept features that are also described in the official [fetch
specification][]. However, the aim of this project is not to implement the
complete specification; just the parts that are feasible to emulate using
XMLHttpRequest. See [Caveats][] for some examples of features that we are
unlikely to implement.
Contributions to this project are [released][tos] to the public under the
[project's open source license](LICENSE).
## Running tests
Running `npm test` will:
1. Build the `dist/` files;
1. Run the test suite in headless Chrome & Firefox;
1. Run the same test suite in Web Worker mode.
When editing tests or implementation, keep `npm run karma` running:
- You can connect additional browsers by navigating to `http://localhost:9876/`;
- Changes to [test.js](test/test.js) will automatically re-run the tests in all
connected browsers;
- When changing [fetch.js](fetch.js), re-run tests by executing `make`;
- Re-run specific tests with `./node_modules/.bin/karma run -- --grep=<PATTERN>`.
## Submitting a pull request
1. [Fork][fork] and clone the repository;
1. Create a new branch: `git checkout -b my-branch-name`;
1. Make your change, push to your fork and [submit a pull request][pr];
1. Pat your self on the back and wait for your pull request to be reviewed.
Here are a few things you can do that will increase the likelihood of your pull
request being accepted:
- Keep your change as focused as possible. If there are multiple changes you
would like to make that are not dependent upon each other, consider submitting
them as separate pull requests.
- Write a [good commit message][].
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
[fetch specification]: https://fetch.spec.whatwg.org
[tos]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license
[fork]: https://github.com/github/fetch/fork
[pr]: https://github.com/github/fetch/compare
[good commit message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[caveats]: https://github.github.io/fetch/#caveats
# Maintaining
## Releasing a new version
This project follows [semver](http://semver.org/). So if you are making a bug
fix, only increment the patch level "1.0.x". If any new files are added, a
minor version "1.x.x" bump is in order.
### Make a release commit
To prepare the release commit:
1. Update the npm [package.json](https://github.com/github/fetch/blob/master/package.json)
`version` value.
2. Make a single commit with the description as "Fetch 2.x.x".
3. Finally, tag the commit with `v2.x.x`.
```
$ git pull
$ vim package.json
$ git add package.json
$ git commit -m "Fetch 1.x.x"
$ git tag v1.x.x
$ git push
$ git push --tags
```
test: node_modules/ lint
./script/test
test: lint dist/fetch.umd.js
lint: node_modules/
./node_modules/.bin/jshint *.js test/*.js
./node_modules/.bin/eslint --report-unused-disable-directives *.js test/*.js
dist/fetch.umd.js: fetch.js rollup.config.js node_modules/
./node_modules/.bin/rollup -c
dist/fetch.umd.js.flow: fetch.js.flow
cp $< $@
node_modules/:
npm install
......@@ -10,20 +15,4 @@ node_modules/:
clean:
rm -rf ./bower_components ./node_modules
ifeq ($(shell uname -s),Darwin)
sauce_connect/bin/sc:
wget https://saucelabs.com/downloads/sc-4.3.16-osx.zip
unzip sc-4.3.16-osx.zip
mv sc-4.3.16-osx sauce_connect
rm sc-4.3.16-osx.zip
else
sauce_connect/bin/sc:
mkdir -p sauce_connect
curl -fsSL http://saucelabs.com/downloads/sc-4.3.16-linux.tar.gz | tar xz -C sauce_connect --strip-components 1
endif
phantomjs/bin/phantomjs:
mkdir -p phantomjs
wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O- | tar xj -C phantomjs --strip-components 1
.PHONY: clean lint test
......@@ -5,14 +5,12 @@ web requests in the browser. This project is a polyfill that implements a subset
of the standard [Fetch specification][], enough to make `fetch` a viable
replacement for most uses of XMLHttpRequest in traditional web applications.
This project adheres to the [Open Code of Conduct][]. By participating, you are
expected to uphold this code.
## Table of Contents
* [Read this first](#read-this-first)
* [Installation](#installation)
* [Usage](#usage)
* [Importing](#importing)
* [HTML](#html)
* [JSON](#json)
* [Response metadata](#response-metadata)
......@@ -24,15 +22,17 @@ expected to uphold this code.
* [Sending cookies](#sending-cookies)
* [Receiving cookies](#receiving-cookies)
* [Obtaining the Response URL](#obtaining-the-response-url)
* [Aborting requests](#aborting-requests)
* [Browser Support](#browser-support)
## Read this first
* If you believe you found a bug with how `fetch` behaves in Chrome or Firefox,
please **don't open an issue in this repository**. This project is a
_polyfill_, and since Chrome and Firefox both implement the `window.fetch`
function natively, no code from this project actually takes any effect in
these browsers. See [Browser support](#browser-support) for detailed
* If you believe you found a bug with how `fetch` behaves in your browser,
please **don't open an issue in this repository** unless you are testing in
an old version of a browser that doesn't support `window.fetch` natively.
This project is a _polyfill_, and since all modern browsers now implement the
`fetch` function natively, **no code from this project** actually takes any
effect there. See [Browser support](#browser-support) for detailed
information.
* If you have trouble **making a request to another domain** (a different
......@@ -43,11 +43,6 @@ expected to uphold this code.
exclusively handled by the browser's internal mechanisms which this polyfill
cannot influence.
* If you have trouble **maintaining the user's session** or [CSRF][] protection
through `fetch` requests, please ensure that you've read and understood the
[Sending cookies](#sending-cookies) section. `fetch` doesn't send cookies
unless you ask it to.
* This project **doesn't work under Node.js environments**. It's meant for web
browsers only. You should ensure that your application doesn't try to package
and run this on the server.
......@@ -58,33 +53,53 @@ expected to uphold this code.
## Installation
* `npm install whatwg-fetch --save`; or
* `bower install fetch`; or
```
npm install whatwg-fetch --save
```
* `yarn add whatwg-fetch`.
As an alternative to using npm, you can obtain `fetch.umd.js` from the
[Releases][] section. The UMD distribution is compatible with AMD and CommonJS
module loaders, as well as loading directly into a page via `<script>` tag.
You will also need a Promise polyfill for [older browsers](http://caniuse.com/#feat=promises).
We recommend [taylorhakes/promise-polyfill](https://github.com/taylorhakes/promise-polyfill)
for its small size and Promises/A+ compatibility.
For use with webpack, add this package in the `entry` configuration option
before your application entry point:
## Usage
For a more comprehensive API reference that this polyfill supports, refer to
https://github.github.io/fetch/.
### Importing
Importing will automatically polyfill `window.fetch` and related APIs:
```javascript
entry: ['whatwg-fetch', ...]
import 'whatwg-fetch'
window.fetch(...)
```
For Babel and ES2015+, make sure to import the file:
If for some reason you need to access the polyfill implementation, it is
available via exports:
```javascript
import 'whatwg-fetch'
import {fetch as fetchPolyfill} from 'whatwg-fetch'
window.fetch(...) // use native browser version
fetchPolyfill(...) // use polyfill implementation
```
## Usage
This approach can be used to, for example, use [abort
functionality](#aborting-requests) in browsers that implement a native but
outdated version of fetch that doesn't support aborting.
For a more comprehensive API reference that this polyfill supports, refer to
https://github.github.io/fetch/.
For use with webpack, add this package in the `entry` configuration option
before your application entry point:
```javascript
entry: ['whatwg-fetch', ...]
```
### HTML
......@@ -164,18 +179,14 @@ fetch('/avatars', {
### Caveats
The `fetch` specification differs from `jQuery.ajax()` in mainly two ways that
bear keeping in mind:
* The Promise returned from `fetch()` **won't reject on HTTP error status**
even if the response is an HTTP 404 or 500. Instead, it will resolve normally,
and it will only reject on network failure or if anything prevented the
request from completing.
* By default, `fetch` **won't send or receive any cookies** from the server,
resulting in unauthenticated requests if the site relies on maintaining a user
session. See [Sending cookies](#sending-cookies) for how to opt into cookie
handling.
* For maximum browser compatibility when it comes to sending & receiving
cookies, always supply the `credentials: 'same-origin'` option instead of
relying on the default. See [Sending cookies](#sending-cookies).
#### Handling HTTP error statuses
......@@ -209,25 +220,41 @@ fetch('/users')
#### Sending cookies
To automatically send cookies for the current domain, the `credentials` option
must be provided:
For [CORS][] requests, use `credentials: 'include'` to allow sending credentials
to other domains:
```javascript
fetch('https://example.com:1234/users', {
credentials: 'include'
})
```
To disable sending or receiving cookies for requests to any domain, including
the current one, use the "omit" value:
```javascript
fetch('/users', {
credentials: 'same-origin'
credentials: 'omit'
})
```
The "same-origin" value makes `fetch` behave similarly to XMLHttpRequest with
regards to cookies. Otherwise, cookies won't get sent, resulting in these
requests not preserving the authentication session.
The default value for `credentials` is "same-origin".
The default for `credentials` wasn't always the same, though. The following
versions of browsers implemented an older version of the fetch specification
where the default was "omit":
For [CORS][] requests, use the "include" value to allow sending credentials to
other domains:
* Firefox 39-60
* Chrome 42-67
* Safari 10.1-11.1.2
If you target these browsers, it's advisable to always specify `credentials:
'same-origin'` explicitly with all fetch requests instead of relying on the
default:
```javascript
fetch('https://example.com:1234/users', {
credentials: 'include'
fetch('/users', {
credentials: 'same-origin'
})
```
......@@ -239,10 +266,6 @@ read with `response.headers.get()`. Instead, it's the browser's responsibility
to handle new cookies being set (if applicable to the current URL). Unless they
are HTTP-only, new cookies will be available through `document.cookie`.
Bear in mind that the default behavior of `fetch` is to ignore the `Set-Cookie`
header completely. To opt into accepting cookies from the server, you must use
the `credentials` option.
#### Obtaining the Response URL
Due to limitations of XMLHttpRequest, the `response.url` value might not be
......@@ -260,6 +283,39 @@ response.headers['X-Request-URL'] = request.url
This server workaround is necessary if you need reliable `response.url` in
Firefox < 32, Chrome < 37, Safari, or IE.
#### Aborting requests
This polyfill supports
[the abortable fetch API](https://developers.google.com/web/updates/2017/09/abortable-fetch).
However, aborting a fetch requires use of two additional DOM APIs:
[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and
[AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).
Typically, browsers that do not support fetch will also not support
AbortController or AbortSignal. Consequently, you will need to include
[an additional polyfill](https://github.com/mo/abortcontroller-polyfill#readme)
for these APIs to abort fetches:
```js
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
import {fetch} from 'whatwg-fetch'
// use native browser implementation if it supports aborting
const abortableFetch = ('signal' in new Request('')) ? window.fetch : fetch
const controller = new AbortController()
abortableFetch('/avatars', {
signal: controller.signal
}).catch(function(ex) {
if (ex.name === 'AbortError') {
console.log('request aborted')
}
})
// some time later...
controller.abort()
```
## Browser Support
- Chrome
......@@ -275,9 +331,9 @@ an issue with that browser vendor instead of this project.
[fetch specification]: https://fetch.spec.whatwg.org
[open code of conduct]: http://todogroup.org/opencodeofconduct/#fetch/opensource@github.com
[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
"Cross-origin resource sharing"
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
"Cross-site request forgery"
[forbidden header name]: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
[releases]: https://github.com/github/fetch/releases
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../fetch.js"></script>
</head>
<body>
<script>
var result = fetch('https://api.github.com')
result.then(function(response) {
console.log('response', response)
console.log('header', response.headers.get('Content-Type'))
return response.text()
}).then(function(text) {
console.log('got text', text)
}).catch(function(ex) {
console.log('failed', ex)
})
</script>
</body>
</html>
此差异已折叠。
/* @flow strict */
type CredentialsType = 'omit' | 'same-origin' | 'include'
type ResponseType = 'default' | 'error'
type BodyInit = string | URLSearchParams | FormData | Blob | ArrayBuffer | $ArrayBufferView
type RequestInfo = Request | URL | string
type RequestOptions = {|
body?: ?BodyInit;
credentials?: CredentialsType;
headers?: HeadersInit;
method?: string;
mode?: string;
referrer?: string;
signal?: ?AbortSignal;
|}
type ResponseOptions = {|
status?: number;
statusText?: string;
headers?: HeadersInit;
|}
type HeadersInit = Headers | {[string]: string}
// https://github.com/facebook/flow/blob/f68b89a5012bd995ab3509e7a41b7325045c4045/lib/bom.js#L902-L914
declare class Headers {
@@iterator(): Iterator<[string, string]>;
constructor(init?: HeadersInit): void;
append(name: string, value: string): void;
delete(name: string): void;
entries(): Iterator<[string, string]>;
forEach((value: string, name: string, headers: Headers) => any, thisArg?: any): void;
get(name: string): null | string;
has(name: string): boolean;
keys(): Iterator<string>;
set(name: string, value: string): void;
values(): Iterator<string>;
}
// https://github.com/facebook/flow/pull/6548
interface AbortSignal {
aborted: boolean;
addEventListener(type: string, listener: (Event) => mixed, options?: EventListenerOptionsOrUseCapture): void;
removeEventListener(type: string, listener: (Event) => mixed, options?: EventListenerOptionsOrUseCapture): void;
}
// https://github.com/facebook/flow/blob/f68b89a5012bd995ab3509e7a41b7325045c4045/lib/bom.js#L994-L1018
// unsupported in polyfill:
// - cache
// - integrity
// - redirect
// - referrerPolicy
declare class Request {
constructor(input: RequestInfo, init?: RequestOptions): void;
clone(): Request;
url: string;
credentials: CredentialsType;
headers: Headers;
method: string;
mode: ModeType;
referrer: string;
signal: ?AbortSignal;
// Body methods and attributes
bodyUsed: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
}
// https://github.com/facebook/flow/blob/f68b89a5012bd995ab3509e7a41b7325045c4045/lib/bom.js#L968-L992
// unsupported in polyfill:
// - body
// - redirected
// - trailer
declare class Response {
constructor(input?: ?BodyInit, init?: ResponseOptions): void;
clone(): Response;
static error(): Response;
static redirect(url: string, status?: number): Response;
type: ResponseType;
url: string;
ok: boolean;
status: number;
statusText: string;
headers: Headers;
// Body methods and attributes
bodyUsed: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
}
declare class DOMException extends Error {
constructor(message?: string, name?: string): void;
}
declare module.exports: {
fetch(input: RequestInfo, init?: RequestOptions): Promise<Response>;
Headers: typeof Headers;
Request: typeof Request;
Response: typeof Response;
DOMException: typeof DOMException;
}
{
"name": "whatwg-fetch",
"description": "A window.fetch polyfill.",
"version": "2.0.3",
"main": "fetch.js",
"version": "3.0.0",
"main": "./dist/fetch.umd.js",
"module": "./fetch.js",
"repository": "github/fetch",
"license": "MIT",
"devDependencies": {
"chai": "1.10.0",
"jshint": "2.8.0",
"mocha": "2.1.0",
"mocha-phantomjs-core": "2.0.1",
"abortcontroller-polyfill": "^1.1.9",
"chai": "^4.1.2",
"eslint": "^4.19.1",
"eslint-plugin-github": "^1.6.0",
"karma": "^3.0.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^2.2.0",
"karma-detect-browsers": "^2.3.2",
"karma-firefox-launcher": "^1.1.0",
"karma-mocha": "^1.3.0",
"karma-safari-launcher": "^1.0.0",
"karma-safaritechpreview-launcher": "0.0.6",
"mocha": "^4.0.1",
"promise-polyfill": "6.0.2",
"rollup": "^0.59.1",
"url-search-params": "0.6.1"
},
"files": [
"LICENSE",
"fetch.js"
"dist/fetch.umd.js",
"dist/fetch.umd.js.flow",
"fetch.js",
"fetch.js.flow"
],
"scripts": {
"test": "make"
"karma": "karma start ./test/karma.config.js --no-single-run --auto-watch",
"prepare": "make dist/fetch.umd.js dist/fetch.umd.js.flow",
"pretest": "make",
"test": "karma start ./test/karma.config.js && karma start ./test/karma-worker.config.js"
}
}
module.exports = require('eslint-plugin-github/prettier.config')
export default {
input: 'fetch.js',
output: {
file: 'dist/fetch.umd.js',
format: 'umd',
name: 'WHATWGFetch'
}
}
#!/bin/bash
set -e
port=3900
# Find next available port
while lsof -i :$((++port)) >/dev/null; do true; done
# Spin a test server in the background
node ./script/server $port &>/dev/null &
server_pid=$!
trap "kill $server_pid" INT EXIT
STATUS=0
reporter=dot
[ -z "$CI" ] || reporter=spec
if [ -n "$TRAVIS" ]; then
make phantomjs/bin/phantomjs
export PATH="$PWD/phantomjs/bin:$PATH"
fi
run() {
phantomjs ./node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js \
"$1" $reporter "{\"useColors\":true, \"hooks\":\"$PWD/test/mocha-phantomjs-hooks.js\"}" \
|| STATUS=$?
}
[ -z "$CI" ] || echo "phantomjs $(phantomjs -v)"
run "http://localhost:$port/"
run "http://localhost:$port/test/test-worker.html"
exit $STATUS
#!/bin/bash
set -e
port=8080
# Spin a test server in the background
node ./script/server $port &>/dev/null &
server_pid=$!
trap "kill $server_pid" INT EXIT
make sauce_connect/bin/sc
sauce_ready="${TMPDIR:-/tmp}/sauce-ready.$$"
sauce_connect/bin/sc -u "$SAUCE_USERNAME" -k "$SAUCE_ACCESS_KEY" \
-i "$TRAVIS_JOB_NUMBER" -l sauce_connect.log -f "$sauce_ready" &>/dev/null &
sauce_pid=$!
trap "kill $sauce_pid" INT EXIT
sauce_waited=0
while [ ! -f "$sauce_ready" ]; do
if [ "$sauce_waited" -gt 60000 ]; then
echo "sauce_connect failed to start within 60 seconds" >&2
exit 1
fi
sleep .01
sauce_waited=$((sauce_waited + 10))
done
echo "sauce_connect started within $sauce_waited ms"
rm -f "$sauce_ready"
job="$(./script/saucelabs-api --raw "js-tests" <<JSON
{ "public": "public",
"build": "$TRAVIS_BUILD_NUMBER",
"tags": ["$TRAVIS_PULL_REQUEST", "$TRAVIS_BRANCH"],
"tunnel-identifier": "$TRAVIS_JOB_NUMBER",
"platforms": [["$SAUCE_PLATFORM", "$SAUCE_BROWSER", "$SAUCE_VERSION"]],
"url": "http://localhost:$port/",
"framework": "mocha"
}
JSON
)"
while sleep 5; do
result=$(./script/saucelabs-api "js-tests/status" <<<"$job")
if grep -q '.status: test error' <<<"$result"; then
echo
echo "$result" >&2
exit 1
fi
grep -q "^completed: true" <<<"$result" && break
echo -n "."
done
echo
awk '
/result\.tests:/ { tests+=$(NF) }
/result\.passes:/ { passes+=$(NF) }
/result\.pending:/ { pending+=$(NF) }
/result\.failures:/ { failures+=$(NF) }
/\.url:/ { print $(NF) }
END {
printf "%d passed, %d pending, %d failures\n", passes, pending, failures
if (failures > 0 || tests != passes + pending || tests == 0) exit 1
}
' <<<"$result"
#!/bin/bash
set -e
set -o pipefail
raw=""
if [ "$1" = "--raw" ]; then
raw="1"
shift 1
fi
endpoint="$1"
curl -fsS -X POST "https://saucelabs.com/rest/v1/$SAUCE_USERNAME/${endpoint}" \
-u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \
-H "Content-Type: application/json" -d "@-" | \
{
if [ -n "$raw" ]; then
cat
else
ruby -rjson -e '
dump = lambda do |obj, ns|
case obj
when Array then obj.each_with_index { |v, i| dump.call(v, [ns, i]) }
when Hash then obj.each { |k, v| dump.call(v, [ns, k]) }
else puts "%s: %s" % [ ns.flatten.compact.join("."), obj.to_s ]
end
end
dump.call JSON.parse(STDIN.read), nil
'
fi
}
#!/bin/bash
set -e
if [ -n "$SAUCE_BROWSER" ]; then
./script/saucelabs
else
./script/phantomjs
fi
{
"extends": "../.jshintrc",
"es3": false,
"strict": false,
"sub": true,
"globals": {
"fetch": false,
"Headers": false,
"Request": false,
"Response": false,
"mocha": false,
"chai": false,
"suite": false,
"setup": false,
"suiteSetup": false,
"test": false,
"assert": false
}
}
const parentConfig = require('./karma.config')
module.exports = function(config) {
parentConfig(config)
config.set({
frameworks: ['detectBrowsers', 'mocha'],
files: [
'test/worker-adapter.js',
{
pattern: '{test,dist}/*.js',
included: false
},
{
pattern: 'node_modules/{mocha,chai,abortcontroller-polyfill/dist}/*.js',
included: false,
watched: false
}
]
})
}
const serverEndpoints = require('./server')
module.exports = function(config) {
config.set({
basePath: '..',
frameworks: ['detectBrowsers', 'mocha', 'chai'],
detectBrowsers: {
preferHeadless: true,
usePhantomJS: false,
postDetection: availableBrowsers =>
availableBrowsers
.filter(
browser =>
!process.env.CI || !browser.startsWith('Chromium') || !availableBrowsers.some(b => b.startsWith('Chrome'))
)
.map(browser => (browser.startsWith('Chrom') ? `${browser}NoSandbox` : browser))
},
client: {
mocha: {
ui: 'tdd'
}
},
files: [
'node_modules/promise-polyfill/promise.js',
'node_modules/abortcontroller-polyfill/dist/abortcontroller-polyfill-only.js',
'node_modules/url-search-params/build/url-search-params.max.js',
'dist/fetch.umd.js',
'test/test.js'
],
reporters: process.env.CI ? ['dots'] : ['progress'],
port: 9876,
colors: true,
logLevel: process.env.CI ? config.LOG_WARN : config.LOG_INFO,
autoWatch: false,
singleRun: true,
concurrency: Infinity,
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
},
ChromiumHeadlessNoSandbox: {
base: 'ChromiumHeadless',
flags: ['--no-sandbox']
}
},
beforeMiddleware: ['custom'],
plugins: [
'karma-*',
{
'middleware:custom': ['value', serverEndpoints]
}
]
})
}
/* globals exports */
exports.beforeStart = function(context) {
var originalResourceError = context.page.onResourceError
context.page.onResourceError = function(resErr) {
if (!/\/boom$/.test(resErr.url)) {
originalResourceError(resErr)
}
}
}
#!/usr/bin/env node
const url = require('url')
const querystring = require('querystring')
var port = Number(process.argv[2] || 3000)
var fs = require('fs')
var http = require('http');
var url = require('url');
var querystring = require('querystring');
var routes = {
const routes = {
'/request': function(res, req) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.writeHead(200, {'Content-Type': 'application/json'})
var data = ''
req.on('data', function(c) { data += c })
req.on('data', function(c) {
data += c
})
req.on('end', function() {
res.end(JSON.stringify({
method: req.method,
url: req.url,
headers: req.headers,
data: data
}));
res.end(
JSON.stringify({
method: req.method,
url: req.url,
headers: req.headers,
data: data
})
)
})
},
'/hello': function(res, req) {
res.writeHead(200, {
'Content-Type': 'text/plain',
'X-Request-URL': 'http://' + req.headers.host + req.url
});
res.end('hi');
})
res.end('hi')
},
'/hello/utf8': function(res) {
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
});
})
// "hello"
var buf = new Buffer([104, 101, 108, 108, 111]);
res.end(buf);
var buf = Buffer.from([104, 101, 108, 108, 111])
res.end(buf)
},
'/hello/utf16le': function(res) {
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-16le'
});
})
// "hello"
var buf = new Buffer([104, 0, 101, 0, 108, 0, 108, 0, 111, 0]);
res.end(buf);
var buf = Buffer.from([104, 0, 101, 0, 108, 0, 108, 0, 111, 0])
res.end(buf)
},
'/binary': function(res) {
res.writeHead(200, {'Content-Type': 'application/octet-stream'});
var buf = new Buffer(256);
res.writeHead(200, {'Content-Type': 'application/octet-stream'})
var buf = Buffer.alloc(256)
for (var i = 0; i < 256; i++) {
buf[i] = i;
buf[i] = i
}
res.end(buf);
res.end(buf)
},
'/redirect/301': function(res) {
res.writeHead(301, {'Location': '/hello'});
res.end();
res.writeHead(301, {Location: '/hello'})
res.end()
},
'/redirect/302': function(res) {
res.writeHead(302, {'Location': '/hello'});
res.end();
res.writeHead(302, {Location: '/hello'})
res.end()
},
'/redirect/303': function(res) {
res.writeHead(303, {'Location': '/hello'});
res.end();
res.writeHead(303, {Location: '/hello'})
res.end()
},
'/redirect/307': function(res) {
res.writeHead(307, {'Location': '/hello'});
res.end();
res.writeHead(307, {Location: '/hello'})
res.end()
},
'/redirect/308': function(res) {
res.writeHead(308, {'Location': '/hello'});
res.end();
res.writeHead(308, {Location: '/hello'})
res.end()
},
'/boom': function(res) {
res.writeHead(500, {'Content-Type': 'text/plain'});
res.end('boom');
res.writeHead(500, {'Content-Type': 'text/plain'})
res.end('boom')
},
'/empty': function(res) {
res.writeHead(204);
res.end();
res.writeHead(204)
res.end()
},
'/slow': function(res) {
setTimeout(function() {
res.writeHead(200, {'Cache-Control': 'no-cache, must-revalidate'})
res.end()
}, 100)
},
'/error': function(res) {
res.destroy();
res.destroy()
},
'/form': function(res) {
res.writeHead(200, {'Content-Type': 'application/x-www-form-urlencoded'});
res.end('number=1&space=one+two&empty=&encoded=a%2Bb&');
res.writeHead(200, {'Content-Type': 'application/x-www-form-urlencoded'})
res.end('number=1&space=one+two&empty=&encoded=a%2Bb&')
},
'/json': function(res) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({name: 'Hubot', login: 'hubot'}));
res.writeHead(200, {'Content-Type': 'application/json'})
res.end(JSON.stringify({name: 'Hubot', login: 'hubot'}))
},
'/json-error': function(res) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end('not json {');
res.writeHead(200, {'Content-Type': 'application/json'})
res.end('not json {')
},
'/cookie': function(res, req) {
var setCookie, cookie
var params = querystring.parse(url.parse(req.url).query);
var params = querystring.parse(url.parse(req.url).query)
if (params.name && params.value) {
setCookie = [params.name, params.value].join('=');
setCookie = [params.name, params.value].join('=')
}
if (params.name) {
cookie = querystring.parse(req.headers['cookie'], '; ')[params.name];
cookie = querystring.parse(req.headers['cookie'], '; ')[params.name]
}
res.writeHead(200, {'Content-Type': 'text/plain', 'Set-Cookie': setCookie});
res.end(cookie);
res.writeHead(200, {
'Content-Type': 'text/plain',
'Set-Cookie': setCookie || ''
})
res.end(cookie)
},
'/headers': function(res) {
res.writeHead(200, {
'Date': 'Mon, 13 Oct 2014 21:02:27 GMT',
Date: 'Mon, 13 Oct 2014 21:02:27 GMT',
'Content-Type': 'text/html; charset=utf-8'
});
res.end();
})
res.end()
}
};
}
var types = {
js: 'application/javascript',
css: 'text/css',
html: 'text/html',
txt: 'text/plain'
};
server = http.createServer(function(req, res) {
var pathname = url.parse(req.url).pathname;
var route = routes[pathname];
module.exports = function(req, res, next) {
const path = url.parse(req.url).pathname
const route = routes[path]
if (route) {
route(res, req);
route(res, req)
} else {
if (pathname == '/') pathname = '/test/test.html'
fs.readFile(__dirname + '/..' + pathname, function(err, data) {
if (err) {
res.writeHead(404, {'Content-Type': types.txt});
res.end('Not Found');
} else {
var ext = (pathname.match(/\.([^\/]+)$/) || [])[1]
res.writeHead(200, {'Content-Type': types[ext] || types.txt});
res.end(data);
}
});
next()
}
});
console.warn("Started test server on localhost:" + port);
server.listen(port);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Fetch Worker Tests</title>
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="/node_modules/url-search-params/build/url-search-params.js"></script>
<script src="/node_modules/mocha/mocha.js"></script>
<script>
if (self.initMochaPhantomJS) {
self.initMochaPhantomJS()
}
mocha.setup('tdd')
mocha.suite.suites.unshift(Mocha.Suite.create(mocha.suite, "worker"))
var worker = new Worker('/test/worker.js')
worker.addEventListener('message', function(e) {
switch (e.data.name) {
case 'pass':
test(e.data.title, function() {})
break
case 'pending':
test(e.data.title)
break
case 'fail':
test(e.data.title, function() {
var err = new Error(e.data.message)
err.stack = e.data.stack
throw err
})
break
case 'end':
mocha.run()
break
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Fetch Tests</title>
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script>
window.onerror = function(err) {
var container = document.getElementById('mocha')
var el = document.createElement('p')
el.textContent = err.toString()
el.style = 'color:#c00'
container.insertBefore(el, container.firstChild)
}
</script>
<script src="/node_modules/url-search-params/build/url-search-params.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/node_modules/mocha/mocha.js"></script>
<script>
if (self.initMochaPhantomJS) {
self.initMochaPhantomJS()
}
if (self.mocha && mocha.setup) {
mocha.setup('tdd')
self.assert = chai.assert
} else {
document.write('<p>Error: please run <code>make</code> to install dependencies and try again.</p>')
}
</script>
<script src="/node_modules/promise-polyfill/promise.js"></script>
<script src="/test/test.js"></script>
<script src="/fetch.js"></script>
<script>
var runner = mocha.run();
var failedTests = [];
runner.on('end', function(){
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
runner.on('fail', function(test, err){
function flattenTitles(test){
var titles = [];
while (test.parent.title){
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};
failedTests.push({name: test.title, result: false, message: err.message, stack: err.stack, titles: flattenTitles(test) });
});
</script>
</body>
</html>
此差异已折叠。
var mochaRun = mocha.run
mocha.run = function() {}
mocha.suite.suites.unshift(Mocha.Suite.create(mocha.suite, 'worker'))
var worker = new Worker('/base/test/worker.js')
worker.addEventListener('message', function(e) {
switch (e.data.name) {
case 'pass':
test(e.data.title, function() {})
break
case 'pending':
test(e.data.title)
break
case 'fail':
test(e.data.title, function() {
var err = new Error(e.data.message)
err.stack = e.data.stack
throw err
})
break
case 'end':
mochaRun()
break
}
})
importScripts('/node_modules/chai/chai.js')
importScripts('/node_modules/mocha/mocha.js')
importScripts('/base/node_modules/mocha/mocha.js')
importScripts('/base/node_modules/chai/chai.js')
mocha.setup('tdd')
self.assert = chai.assert
importScripts('/node_modules/promise-polyfill/promise.js')
importScripts('/test/test.js')
importScripts('/fetch.js')
importScripts('/base/node_modules/abortcontroller-polyfill/dist/abortcontroller-polyfill-only.js')
importScripts('/base/dist/fetch.umd.js')
importScripts('/base/test/test.js')
function title(test) {
return test.fullTitle().replace(/#/g, '');
return test.fullTitle().replace(/#/g, '')
}
function reporter(runner) {
runner.on('pending', function(test){
self.postMessage({name: 'pending', title: title(test)});
});
runner.on('pending', function(test) {
self.postMessage({name: 'pending', title: title(test)})
})
runner.on('pass', function(test){
self.postMessage({name: 'pass', title: title(test)});
});
runner.on('pass', function(test) {
self.postMessage({name: 'pass', title: title(test)})
})
runner.on('fail', function(test, err){
runner.on('fail', function(test, err) {
self.postMessage({
name: 'fail',
title: title(test),
message: err.message,
stack: err.stack
});
});
})
})
runner.on('end', function(){
self.postMessage({name: 'end'});
});
runner.on('end', function() {
self.postMessage({name: 'end'})
})
}
mocha.reporter(reporter).run()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册