├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── HISTORY.md ├── LICENSE ├── README.md ├── build.js ├── fetch-browser.js ├── fetch-node.js ├── index.d.ts ├── package-lock.json ├── package.json ├── testem.json └── tests ├── .eslintrc.json ├── fetch-browser.spec.html ├── fetch-browser.spec.js └── fetch-node.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "amd": true 6 | }, 7 | "extends": "qubyte", 8 | "rules": { 9 | "indent": ["error", 2], 10 | "no-extra-parens": "off", 11 | "no-nested-ternary": "off", 12 | "no-sync": "off", 13 | "no-unused-vars": "off", 14 | "strict": "off" 15 | }, 16 | "globals": { 17 | "Promise": true, 18 | "XMLHttpRequest": true, 19 | "globalThis": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | test-node: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node_version: [12, 14, 16] 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@main 18 | - name: use node ${{ matrix.node_version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node_version }} 22 | - run: npm ci 23 | - run: npm test 24 | test-browser: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | bundler: [browserify, webpack, "webpack:node"] 29 | steps: 30 | - name: checkout 31 | uses: actions/checkout@main 32 | - name: use node 14 33 | uses: actions/setup-node@v1 34 | with: 35 | node-version: 14.x 36 | - run: npm ci 37 | - run: npm run test:${{ matrix.bundler }} 38 | lint: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: checkout 42 | uses: actions/checkout@master 43 | - name: use node 14 44 | uses: actions/setup-node@v1 45 | with: 46 | node-version: 14.x 47 | - run: npm ci 48 | - run: npm run lint 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # Release assets 31 | build 32 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at mark.s.everitt+contributor-covenant@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | For both issues and pull requests, please begin with the following: 4 | 5 | - Read the [code of conduct](CODE_OF_CONDUCT.md). 6 | - Read about [ponyfills](https://ponyfill.com/). 7 | 8 | Pull requests are welcome! A pull request must include tests to accompany code 9 | changes. 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | Many thanks to the contributors to this project! 4 | 5 | - [Mark S. Everitt](https://github.com/qubyte) 6 | - [kPherox](https://github.com/kPherox) 7 | - [Moritz Peters](https://github.com/maritz) 8 | - [Paul Melnikow](https://github.com/paulmelnikow) 9 | - [Duc Tri Le](https://github.com/duclet) 10 | - [Daniel Bickett](https://github.com/freen) 11 | - [Hongbo LU](https://github.com/gitawego) 12 | - [Samuel Reed](https://github.com/STRML) 13 | - [Sindre Sorhus](https://github.com/sindresorhus) 14 | - [Robin Millette](https://github.com/millette) 15 | - [Friedel Ziegelmayer](https://github.com/dignifiedquire) 16 | - Artur Ciocanu 17 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | # 7.1.0 4 | 5 | - Updates whatwg-fetch to 3.5.0 to address an issue with IE11. 6 | 7 | ## 7.0.0 8 | 9 | - Drops support for node 8. Adds node 14 to tests. 10 | - Updates whatwg-fetch to ~3.4.1. 11 | - Exposes `DOMException` from whatwg-fetch. 12 | - Tests against webpack 5 and browserify 17. 13 | 14 | ## 6.1.0 15 | 16 | - Adds a types field to package.json for TypeScript integration. 17 | - Updates node-fetch to 2.6.0 18 | 19 | ## 6.0.2 20 | 21 | - Fixes WHATWG URL object handling when this module is used in Node. 22 | 23 | ## 6.0.1 24 | 25 | - Fixes to the TypeScript declaration file. 26 | 27 | ## 6.0.0 28 | 29 | - Adjusts the way the global object is sniffed for use with Metro. 30 | 31 | ## 5.0.0 32 | 33 | - Bumps node-fetch from ~1.7.1 to ~2.0.0. This is a potentially breaking 34 | change. Refer to the node-fetch [upgrade guide](https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md) 35 | for details. 36 | 37 | ## 4.1.0 38 | 39 | - Bumps node fetch from ~1.6.0 to ~1.7.1. 40 | - Bumps whatwg-fetch from ~2.0.1 to ~2.0.3. 41 | 42 | ## 4.0.0 43 | 44 | This release: 45 | 46 | - Bumps whatwg-fetch from ~1.0.0 to ~2.0.1. 47 | - Better handling of self/this for browser fetch (more testing friendly). 48 | 49 | ## 3.0.2 50 | 51 | Dependencies now use tilde to allow patch versions to be tracked (this was 52 | waiting for whatwg-fetch to reach version 1). 53 | 54 | ## 3.0.1 55 | 56 | A link was added to the README to point to the ponyfill definition. 57 | 58 | ## 3.0.0 59 | 60 | Fixes an issue with detection of features like `URLSearchParams`. This is a 61 | major version bump since apparent behaviour could change in a breaking way in 62 | browsers which support detected features. 63 | 64 | ## 2.0.0 65 | 66 | Now exposes associated constructors along with `fetch` like: 67 | 68 | ```javascript 69 | const {fetch, Request, Response, Headers} = require('fetch-ponyfill')(options); 70 | ``` 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Mark Stanley Everitt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fetch Ponyfill 2 | 3 | > WHATWG `fetch` [ponyfill](https://ponyfill.com) 4 | 5 | This module wraps the [github/fetch](https://github.com/github/fetch) polyfill in a CommonJS module 6 | for browserification, and avoids appending anything to the window, instead returning a setup 7 | function when `fetch-ponyfill` is required. Inspired by 8 | [object-assign](https://github.com/sindresorhus/object-assign). 9 | 10 | When used in Node, delegates to `node-fetch` instead. 11 | 12 | ## Usage 13 | 14 | ### Browserify 15 | 16 | ```javascript 17 | const {fetch, Request, Response, Headers} = require('fetch-ponyfill')(options); 18 | ``` 19 | 20 | ### Webpack 21 | 22 | ```javascript 23 | import fetchPonyfill from 'fetch-ponyfill'; 24 | const {fetch, Request, Response, Headers} = fetchPonyfill(options); 25 | ``` 26 | 27 | ### Options 28 | 29 | Where `options` is an object with the following optional properties: 30 | 31 | | option | description | 32 | | ------ | ----------- | 33 | | `Promise` | An A+ Promise implementation. Defaults to `window.Promise` in the browser, and `global.Promise` in Node. | 34 | | `XMLHttpRequest` | The XMLHttpRequest constructor. This is useful to feed in when working with Firefox OS. Defaults to `window.XMLHttpRequest`. Has no effect in Node. | 35 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function indent(line) { 4 | return line === '' ? '' : ' ' + line; 5 | } 6 | 7 | var fs = require('fs'); 8 | 9 | // Get the fetch source as a string. 10 | var whatwgFetchSource = fs.readFileSync(require.resolve('whatwg-fetch'), 'utf8'); 11 | 12 | // Get the wrapper source as a string. 13 | var wrapperSource = fs.readFileSync(require.resolve('./fetch-browser'), 'utf8'); 14 | 15 | // Indent and place the fetch source inside the wrapper. 16 | var indented = whatwgFetchSource 17 | .split('\n') 18 | .map(indent) 19 | .join('\n'); 20 | 21 | var builtSource = wrapperSource.replace(' // {{whatwgFetch}}', indented); 22 | 23 | console.log(builtSource); // eslint-disable-line no-console 24 | -------------------------------------------------------------------------------- /fetch-browser.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | 'use strict'; 3 | 4 | function fetchPonyfill(options) { 5 | var Promise = options && options.Promise || global.Promise; 6 | var XMLHttpRequest = options && options.XMLHttpRequest || global.XMLHttpRequest; 7 | 8 | return (function () { 9 | var globalThis = Object.create(global, { 10 | fetch: { 11 | value: undefined, 12 | writable: true 13 | } 14 | }); 15 | 16 | // {{whatwgFetch}} 17 | 18 | return { 19 | fetch: globalThis.fetch, 20 | Headers: globalThis.Headers, 21 | Request: globalThis.Request, 22 | Response: globalThis.Response, 23 | DOMException: globalThis.DOMException 24 | }; 25 | }()); 26 | } 27 | 28 | if (typeof define === 'function' && define.amd) { 29 | define(function () { 30 | return fetchPonyfill; 31 | }); 32 | } else if (typeof exports === 'object') { 33 | module.exports = fetchPonyfill; 34 | } else { 35 | global.fetchPonyfill = fetchPonyfill; 36 | } 37 | }(typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : typeof global !== 'undefined' ? global : this)); 38 | -------------------------------------------------------------------------------- /fetch-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fetch = require('node-fetch'); 4 | 5 | function wrapFetchForNode(fetch) { 6 | // Support schemaless URIs on the server for parity with the browser. 7 | // https://github.com/matthew-andrews/isomorphic-fetch/pull/10 8 | return function (u, options) { 9 | if (typeof u === 'string' && u.slice(0, 2) === '//') { 10 | return fetch('https:' + u, options); 11 | } 12 | 13 | return fetch(u, options); 14 | }; 15 | } 16 | 17 | module.exports = function (context) { 18 | // Support webpack module import weirdness. 19 | var fetchFn = fetch.default ? fetch.default : fetch; 20 | 21 | // This modifies the global `node-fetch` object, which isn't great, since 22 | // different callers to `fetch-ponyfill` which pass a different Promise 23 | // implementation would each expect to have their implementation used. But, 24 | // given the way `node-fetch` is implemented, this is the only way to make 25 | // it work at all. 26 | if (context && context.Promise) { 27 | fetchFn.Promise = context.Promise; 28 | } 29 | 30 | return { 31 | fetch: wrapFetchForNode(fetchFn), 32 | Headers: fetch.Headers, 33 | Request: fetch.Request, 34 | Response: fetch.Response 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export = fetchPonyfill; 2 | 3 | declare function fetchPonyfill(options?: fetchPonyfill.BootstrapOptions): fetchPonyfill.BootstrapRetVal; 4 | 5 | declare namespace fetchPonyfill { 6 | interface BootstrapOptions { 7 | Promise?: Function; 8 | XMLHttpRequest?: Function; 9 | } 10 | 11 | interface BootstrapRetVal { 12 | fetch: typeof fetch; 13 | Headers: typeof Headers; 14 | Request: typeof Request; 15 | Response: typeof Response; 16 | DOMException: typeof DOMException; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-ponyfill", 3 | "version": "7.1.0", 4 | "description": "A ponyfill (doesn't overwrite the native fetch) for the Fetch specification https://fetch.spec.whatwg.org.", 5 | "main": "fetch-node.js", 6 | "browser": "build/fetch-browser.js", 7 | "types": "./index.d.ts", 8 | "config": { 9 | "web_port": "8088" 10 | }, 11 | "scripts": { 12 | "test": "mocha tests/fetch-node.spec.js", 13 | "lint": "eslint .", 14 | "pretest:browserify": "npm run build && browserify tests/fetch-browser.spec.js --outfile build/browser-test/main.js", 15 | "test:browserify": "testem ci", 16 | "pretest:webpack": "npm run build && webpack --mode development --entry ./tests/fetch-browser.spec.js -o build/browser-test", 17 | "test:webpack": "testem ci", 18 | "pretest:webpack:node": "webpack --mode development --target node --entry ./tests/fetch-node.spec.js -o build/node-test", 19 | "test:webpack:node": "mocha build/node-test/main.js", 20 | "build": "rimraf build && mkdirp build && node build.js > build/fetch-browser.js", 21 | "prepublishOnly": "npm run build" 22 | }, 23 | "author": "Mark Stanley Everitt", 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/qubyte/fetch-ponyfill.git" 27 | }, 28 | "license": "MIT", 29 | "keywords": [ 30 | "fetch", 31 | "ponyfill" 32 | ], 33 | "dependencies": { 34 | "node-fetch": "~2.6.1" 35 | }, 36 | "devDependencies": { 37 | "browserify": "^17.0.0", 38 | "eslint": "^8.7.0", 39 | "eslint-config-qubyte": "^4.1.0", 40 | "mkdirp": "^1.0.4", 41 | "mocha": "^9.2.0", 42 | "nock": "^13.1.0", 43 | "promise": "^8.1.0", 44 | "rimraf": "^3.0.2", 45 | "sinon": "^12.0.1", 46 | "testem": "^3.4.2", 47 | "webpack": "^5.38.1", 48 | "webpack-cli": "^4.7.0", 49 | "whatwg-fetch": "^3.6.2" 50 | }, 51 | "files": [ 52 | "fetch-node.js", 53 | "build/fetch-browser.js", 54 | "index.d.ts" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_page": "tests/fetch-browser.spec.html", 3 | "launch_in_ci": [ 4 | "Chrome" 5 | ], 6 | "launch_in_dev": [ 7 | "Chrome" 8 | ], 9 | "browser_args": { 10 | "Chrome": [ 11 | "--headless", 12 | "--disable-gpu", 13 | "--remote-debugging-port=9222", 14 | "--privileged", 15 | "--no-sandbox" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/fetch-browser.spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fetch-browser tests 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/fetch-browser.spec.js: -------------------------------------------------------------------------------- 1 | /* global sinon */ 2 | 3 | var assert = require('assert'); 4 | var ThenPromise = require('promise'); 5 | var fetchWrapper = require('../build/fetch-browser'); 6 | 7 | function responseToText(response) { 8 | return response.text(); 9 | } 10 | 11 | describe('fetch in browser', function () { 12 | var nativeFetch = self.fetch; 13 | var nativeBlob = self.Blob; 14 | 15 | var server; 16 | 17 | beforeEach(function () { 18 | server = sinon.createFakeServer(); 19 | server.autoRespond = true; 20 | self.fetch = sinon.stub(); 21 | }); 22 | 23 | afterEach(function () { 24 | self.fetch = nativeFetch; 25 | self.Blob = nativeBlob; 26 | server.restore(); 27 | }); 28 | 29 | describe('when called without a context', function () { 30 | var promise; 31 | var fetch; 32 | 33 | beforeEach(function () { 34 | fetch = fetchWrapper(); 35 | server.respondWith('https://blah.com/hello.world', 'Some response text.'); 36 | server.respondWith('https://blah.com/goodbye.world', 'Some other response text.'); 37 | promise = fetch.fetch('https://blah.com/hello.world'); 38 | }); 39 | 40 | it('exposes fetch, and Request, Response, and Headers methods', function () { 41 | assert.deepEqual(Object.keys(fetch).sort(), ['DOMException', 'Headers', 'Request', 'Response', 'fetch']); 42 | }); 43 | 44 | it('does not expose native fetch when available', function () { 45 | assert.notEqual(self.fetch, fetch.fetch); 46 | }); 47 | 48 | it('returns a native promise instance which resolves to an instance of Response', function () { 49 | assert.ok(promise instanceof Promise); 50 | 51 | return promise.then(function (res) { 52 | assert.ok(res instanceof fetch.Response); 53 | }); 54 | }); 55 | 56 | it('makes requests', function () { 57 | assert.equal(server.requests.length, 1); 58 | }); 59 | 60 | it('resolves when the server responds', function () { 61 | return promise 62 | .then(responseToText) 63 | .then(function (data) { 64 | assert.equal(data, 'Some response text.'); 65 | }); 66 | }); 67 | 68 | it('can consume Request instances', function () { 69 | return fetch.fetch(new fetch.Request('https://blah.com/goodbye.world')) 70 | .then(responseToText) 71 | .then(function (data) { 72 | assert.equal(data, 'Some other response text.'); 73 | }); 74 | }); 75 | 76 | it('can consume URL instances', function () { 77 | return fetch.fetch(new URL('https://blah.com/goodbye.world')) 78 | .then(responseToText) 79 | .then(function (data) { 80 | assert.equal(data, 'Some other response text.'); 81 | }); 82 | }); 83 | 84 | it('allows whatwg-fetch to feature detect properly', function () { 85 | self.Blob = function () {}; 86 | 87 | var fetch = fetchWrapper(); 88 | 89 | return fetch.fetch('https://blah.com/goodbye.world') 90 | .then(function (res) { 91 | return res.blob(); 92 | }) 93 | .then(function (blob) { 94 | assert.ok(blob instanceof self.Blob); 95 | }); 96 | }); 97 | }); 98 | 99 | describe('when called with a context without a Promise field', function () { 100 | var promise; 101 | var fetch; 102 | 103 | beforeEach(function () { 104 | fetch = fetchWrapper({}); 105 | server.respondWith('https://blah.com/hello.world', 'Some response text.'); 106 | server.respondWith('https://blah.com/goodbye.world', 'Some other response text.'); 107 | promise = fetch.fetch('https://blah.com/hello.world'); 108 | }); 109 | 110 | it('exposes fetch, and Request, Response, and Headers methods', function () { 111 | assert.deepEqual(Object.keys(fetch).sort(), ['DOMException', 'Headers', 'Request', 'Response', 'fetch']); 112 | }); 113 | 114 | it('does not expose native fetch when available', function () { 115 | assert.notEqual(self.fetch, fetch.fetch); 116 | }); 117 | 118 | it('returns a native promise instance which resolves to an instance of Response', function () { 119 | assert.ok(promise instanceof Promise); 120 | 121 | return promise.then(function (res) { 122 | assert.ok(res instanceof fetch.Response); 123 | }); 124 | }); 125 | 126 | it('makes requests', function () { 127 | assert.equal(server.requests.length, 1); 128 | }); 129 | 130 | it('resolves when the server responds', function () { 131 | return promise 132 | .then(responseToText) 133 | .then(function (data) { 134 | assert.equal(data, 'Some response text.'); 135 | }); 136 | }); 137 | 138 | it('can consume Request instances', function () { 139 | return fetch.fetch(new fetch.Request('https://blah.com/goodbye.world')) 140 | .then(responseToText) 141 | .then(function (data) { 142 | assert.equal(data, 'Some other response text.'); 143 | }); 144 | }); 145 | 146 | it('can consume URL instances', function () { 147 | return fetch.fetch(new URL('https://blah.com/goodbye.world')) 148 | .then(responseToText) 149 | .then(function (data) { 150 | assert.equal(data, 'Some other response text.'); 151 | }); 152 | }); 153 | 154 | it('allows whatwg-fetch to feature detect properly', function () { 155 | self.Blob = function () {}; 156 | 157 | var fetch = fetchWrapper({}); 158 | 159 | return fetch.fetch('https://blah.com/goodbye.world') 160 | .then(function (res) { 161 | return res.blob(); 162 | }) 163 | .then(function (blob) { 164 | assert.ok(blob instanceof self.Blob); 165 | }); 166 | }); 167 | }); 168 | 169 | describe('when called with a context with a Promise field', function () { 170 | var promise; 171 | var fetch; 172 | 173 | beforeEach(function () { 174 | fetch = fetchWrapper({ Promise: ThenPromise }); 175 | server.respondWith('https://blah.com/hello.world', 'Some response text.'); 176 | server.respondWith('https://blah.com/goodbye.world', 'Some other response text.'); 177 | promise = fetch.fetch('https://blah.com/hello.world'); 178 | }); 179 | 180 | it('exposes fetch, and Request, Response, and Headers methods', function () { 181 | assert.deepEqual(Object.keys(fetch).sort(), ['DOMException', 'Headers', 'Request', 'Response', 'fetch']); 182 | }); 183 | 184 | it('does not expose native fetch when available', function () { 185 | assert.notEqual(self.fetch, fetch.fetch); 186 | }); 187 | 188 | it('returns an instance of the given promise constructor which resolves to an instance of Response', function () { 189 | assert.ok(promise instanceof ThenPromise); 190 | 191 | return promise.then(function (res) { 192 | assert.ok(res instanceof fetch.Response); 193 | }); 194 | }); 195 | 196 | it('makes requests', function () { 197 | assert.equal(server.requests.length, 1); 198 | }); 199 | 200 | it('resolves when the server responds', function () { 201 | return promise 202 | .then(responseToText) 203 | .then(function (data) { 204 | assert.equal(data, 'Some response text.'); 205 | }); 206 | }); 207 | 208 | it('can consume Request instances', function () { 209 | return fetch.fetch(new fetch.Request('https://blah.com/goodbye.world')) 210 | .then(responseToText) 211 | .then(function (data) { 212 | assert.equal(data, 'Some other response text.'); 213 | }); 214 | }); 215 | 216 | it('can consume URL instances', function () { 217 | return fetch.fetch(new URL('https://blah.com/goodbye.world')) 218 | .then(responseToText) 219 | .then(function (data) { 220 | assert.equal(data, 'Some other response text.'); 221 | }); 222 | }); 223 | 224 | it('allows whatwg-fetch to feature detect properly', function () { 225 | self.Blob = function () {}; 226 | 227 | var fetch = fetchWrapper({ Promise: ThenPromise }); 228 | 229 | return fetch.fetch('https://blah.com/goodbye.world') 230 | .then(function (res) { 231 | return res.blob(); 232 | }) 233 | .then(function (blob) { 234 | assert.ok(blob instanceof self.Blob); 235 | }); 236 | }); 237 | }); 238 | }); 239 | -------------------------------------------------------------------------------- /tests/fetch-node.spec.js: -------------------------------------------------------------------------------- 1 | // Originally derived From isomorphic-fetch tests github.com/matthew-andrews/isomorphic-fetch/ 2 | 3 | 'use strict'; 4 | 5 | var fetchWrapper = require('../fetch-node'); 6 | var URL = require('url').URL; 7 | var nock = require('nock'); 8 | var assert = require('assert'); 9 | var ThenPromise = require('promise'); 10 | 11 | var good = 'hello world. 你好世界。'; 12 | var bad = 'good bye cruel world. 再见残酷的世界。'; 13 | 14 | function responseToText(response) { 15 | if (response.status >= 400) { 16 | throw new Error('Bad server response'); 17 | } 18 | 19 | return response.text(); 20 | } 21 | 22 | describe('fetch in Node', function () { 23 | beforeEach(function () { 24 | nock('https://mattandre.ws') 25 | .get('/succeed.txt') 26 | .reply(200, good); 27 | 28 | nock('https://mattandre.ws') 29 | .get('/fail.txt') 30 | .reply(404, bad); 31 | }); 32 | 33 | afterEach(function () { 34 | nock.cleanAll(); 35 | }); 36 | 37 | describe('when called without a context', function () { 38 | var fetch; 39 | 40 | before(function () { 41 | fetch = fetchWrapper(); 42 | }); 43 | 44 | it('uses the built-in promise implementation', function () { 45 | assert.ok(fetch.fetch('https://mattandre.ws/succeed.txt') instanceof Promise); 46 | }); 47 | 48 | it('exposes fetch, and Request, Response, and Headers methods', function () { 49 | assert.deepEqual(Object.keys(fetch).sort(), ['Headers', 'Request', 'Response', 'fetch']); 50 | }); 51 | 52 | it('returns a promise which resolves to an instance of Response', function () { 53 | return fetch.fetch('https://mattandre.ws/succeed.txt').then(function (res) { 54 | assert.ok(res instanceof fetch.Response); 55 | }); 56 | }); 57 | 58 | it('makes requests', function () { 59 | return fetch.fetch('https://mattandre.ws/succeed.txt') 60 | .then(responseToText) 61 | .then(function (data) { 62 | assert.equal(data, good); 63 | }); 64 | }); 65 | 66 | it('rejects with an error on a bad request', function () { 67 | return fetch.fetch('https://mattandre.ws/fail.txt') 68 | .then(responseToText) 69 | .then( 70 | function () { 71 | throw new Error('Promise should reject.'); 72 | }, 73 | function (err) { 74 | assert.equal(err.message, 'Bad server response'); 75 | } 76 | ); 77 | }); 78 | 79 | it('supports schemaless URIs', function () { 80 | return fetch.fetch('//mattandre.ws/succeed.txt') 81 | .then(responseToText) 82 | .then(function (data) { 83 | assert.equal(data, good); 84 | }); 85 | }); 86 | 87 | it('supports request instances', function () { 88 | return fetch.fetch(new fetch.Request('https://mattandre.ws/succeed.txt')) 89 | .then(responseToText) 90 | .then(function (data) { 91 | assert.equal(data, good); 92 | }); 93 | }); 94 | 95 | it('supports URL instances', function () { 96 | return fetch.fetch(new URL('https://mattandre.ws/succeed.txt')) 97 | .then(responseToText) 98 | .then(function (data) { 99 | assert.equal(data, good); 100 | }); 101 | }); 102 | }); 103 | 104 | describe('when called with a context with no Promise field', function () { 105 | var fetch; 106 | 107 | before(function () { 108 | fetch = fetchWrapper({}); 109 | }); 110 | 111 | it('exposes fetch, and Request, Response, and Headers methods', function () { 112 | assert.deepEqual(Object.keys(fetch).sort(), ['Headers', 'Request', 'Response', 'fetch']); 113 | }); 114 | 115 | it('returns a promise which resolves to an instance of Response', function () { 116 | return fetch.fetch('https://mattandre.ws/succeed.txt').then(function (res) { 117 | assert.ok(res instanceof fetch.Response); 118 | }); 119 | }); 120 | 121 | it('uses the built-in promise implementation', function () { 122 | assert.ok(fetch.fetch('https://mattandre.ws/succeed.txt') instanceof Promise); 123 | }); 124 | 125 | it('makes requests', function () { 126 | return fetch.fetch('https://mattandre.ws/succeed.txt') 127 | .then(responseToText) 128 | .then(function (data) { 129 | assert.equal(data, good); 130 | }); 131 | }); 132 | 133 | it('rejects with an error on a bad request', function () { 134 | return fetch.fetch('https://mattandre.ws/fail.txt') 135 | .then(responseToText) 136 | .then( 137 | function () { 138 | throw new Error('Promise should reject.'); 139 | }, 140 | function (err) { 141 | assert.equal(err.message, 'Bad server response'); 142 | } 143 | ); 144 | }); 145 | 146 | it('supports schemaless URIs', function () { 147 | return fetch.fetch('//mattandre.ws/succeed.txt') 148 | .then(responseToText) 149 | .then(function (data) { 150 | assert.equal(data, good); 151 | }); 152 | }); 153 | 154 | it('supports request instances', function () { 155 | return fetch.fetch(new fetch.Request('https://mattandre.ws/succeed.txt')) 156 | .then(responseToText) 157 | .then(function (data) { 158 | assert.equal(data, good); 159 | }); 160 | }); 161 | 162 | it('supports URL instances', function () { 163 | return fetch.fetch(new URL('https://mattandre.ws/succeed.txt')) 164 | .then(responseToText) 165 | .then(function (data) { 166 | assert.equal(data, good); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('when called with a context with a Promise field', function () { 172 | var fetch; 173 | 174 | before(function () { 175 | fetch = fetchWrapper({ Promise: ThenPromise }); 176 | }); 177 | 178 | it('exposes fetch, and Request, Response, and Headers methods', function () { 179 | assert.deepEqual(Object.keys(fetch).sort(), ['Headers', 'Request', 'Response', 'fetch']); 180 | }); 181 | 182 | it('returns a promise which resolves to an instance of Response', function () { 183 | return fetch.fetch('https://mattandre.ws/succeed.txt').then(function (res) { 184 | assert.ok(res instanceof fetch.Response); 185 | }); 186 | }); 187 | 188 | it('uses the a given promise implementation', function () { 189 | assert.ok(fetch.fetch('https://mattandre.ws/succeed.txt') instanceof ThenPromise); 190 | }); 191 | 192 | it('makes requests', function () { 193 | return fetch.fetch('https://mattandre.ws/succeed.txt') 194 | .then(responseToText) 195 | .then(function (data) { 196 | assert.equal(data, good); 197 | }); 198 | }); 199 | 200 | it('rejects with an error on a bad request', function () { 201 | return fetch.fetch('https://mattandre.ws/fail.txt') 202 | .then(responseToText) 203 | .then( 204 | function () { 205 | throw new Error('Promise should reject.'); 206 | }, 207 | function (err) { 208 | assert.equal(err.message, 'Bad server response'); 209 | } 210 | ); 211 | }); 212 | 213 | it('supports schemaless URIs', function () { 214 | return fetch.fetch('//mattandre.ws/succeed.txt') 215 | .then(responseToText) 216 | .then(function (data) { 217 | assert.equal(data, good); 218 | }); 219 | }); 220 | 221 | it('supports request instances', function () { 222 | return fetch.fetch(new fetch.Request('https://mattandre.ws/succeed.txt')) 223 | .then(responseToText) 224 | .then(function (data) { 225 | assert.equal(data, good); 226 | }); 227 | }); 228 | 229 | it('supports URL instances', function () { 230 | return fetch.fetch(new URL('https://mattandre.ws/succeed.txt')) 231 | .then(responseToText) 232 | .then(function (data) { 233 | assert.equal(data, good); 234 | }); 235 | }); 236 | }); 237 | }); 238 | --------------------------------------------------------------------------------