react app

This commit is contained in:
Mario Romano
2016-04-07 15:33:50 +01:00
parent f32e53a835
commit 2a8113835a
55 changed files with 17175 additions and 177 deletions

View File

@@ -0,0 +1,44 @@
{
"name": "iron-ajax",
"version": "1.2.0",
"description": "Makes it easy to make ajax calls and parse the response",
"private": true,
"authors": [
"The Polymer Authors"
],
"keywords": [
"web-components",
"polymer",
"ajax"
],
"main": "iron-ajax.html",
"repository": {
"type": "git",
"url": "git://github.com/PolymerElements/iron-ajax.git"
},
"license": "http://polymer.github.io/LICENSE.txt",
"homepage": "https://github.com/PolymerElements/iron-ajax",
"ignore": [],
"dependencies": {
"promise-polyfill": "polymerlabs/promise-polyfill#^1.0.0",
"polymer": "Polymer/polymer#^1.0.0"
},
"devDependencies": {
"iron-component-page": "polymerelements/iron-component-page#^1.0.0",
"iron-image": "polymerelements/iron-image#^1.0.0",
"paper-styles": "polymerelements/paper-styles#^1.0.0",
"test-fixture": "polymerelements/test-fixture#^1.0.0",
"web-component-tester": "^4.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
},
"_release": "1.2.0",
"_resolution": {
"type": "version",
"tag": "v1.2.0",
"commit": "9523d3768089f9a631bd32cb1650eac8d1be1a4b"
},
"_source": "git://github.com/PolymerElements/iron-ajax.git",
"_target": "~1.2.0",
"_originalSource": "PolymerElements/iron-ajax",
"_direct": true
}

View File

@@ -0,0 +1 @@
bower_components

View File

@@ -0,0 +1,25 @@
language: node_js
sudo: false
before_script:
- npm install -g bower polylint web-component-tester
- bower install
- polylint
env:
global:
- secure: hRC0relOOVO3kqrrnBe1sAaYIMRzcDJTdzemzNxHMt/6f1+4RDt+VsSZtDZ7ax5v6RIUioiCFDgP7GVAzeGkK6AmBoLjzbcUXodS4KDY1oJfhXpuuHrFFN//ZOIsfFXcqf2641cC0vmpg2ztTYfGEVknpmf+9ruWAauBEgzsIhM=
- secure: jH6XJuB05Tkg4433BXfIOeLR6XkkuUz4yUpv+WjOBe2dCsN3sYYg/7u6FFVw4hKrPt58mKfp2KC1DhPws9GR+/F6ky99a27y+Al0AZL+lVdy+lPJ05SjX3sr2mXA8He7y+EnbCZdu73X8R2KZNvj021JbnZufBqUzmzDq/amtU4=
- CXX=g++-4.8
node_js: stable
addons:
firefox: latest
apt:
sources:
- google-chrome
- ubuntu-toolchain-r-test
packages:
- google-chrome-stable
- g++-4.8
sauce_connect: true
script:
- xvfb-run wct
- "if [ \"${TRAVIS_PULL_REQUEST}\" = \"false\" ]; then wct -s 'default'; fi"

View File

@@ -0,0 +1,77 @@
<!--
This file is autogenerated based on
https://github.com/PolymerElements/ContributionGuide/blob/master/CONTRIBUTING.md
If you edit that file, it will get updated everywhere else.
If you edit this file, your changes will get overridden :)
You can however override the jsbin link with one that's customized to this
specific element:
jsbin=https://jsbin.com/zeriha/2/edit?html,output
-->
# Polymer Elements
## Guide for Contributors
Polymer Elements are built in the open, and the Polymer authors eagerly encourage any and all forms of community contribution. When contributing, please follow these guidelines:
### Filing Issues
**If you are filing an issue to request a feature**, please provide a clear description of the feature. It can be helpful to describe answers to the following questions:
1. **Who will use the feature?** _“As someone filling out a form…”_
2. **When will they use the feature?** _“When I enter an invalid value…”_
3. **What is the users goal?** _“I want to be visually notified that the value needs to be corrected…”_
**If you are filing an issue to report a bug**, please provide:
1. **A clear description of the bug and related expectations.** Consider using the following example template for reporting a bug:
```markdown
The `paper-foo` element causes the page to turn pink when clicked.
## Expected outcome
The page stays the same color.
## Actual outcome
The page turns pink.
## Steps to reproduce
1. Put a `paper-foo` element in the page.
2. Open the page in a web browser.
3. Click the `paper-foo` element.
```
2. **A reduced test case that demonstrates the problem.** If possible, please include the test case as a JSBin. Start with this template to easily import and use relevant Polymer Elements: [https://jsbin.com/zeriha/2/edit?html,output](https://jsbin.com/zeriha/2/edit?html,output).
3. **A list of browsers where the problem occurs.** This can be skipped if the problem is the same across all browsers.
### Submitting Pull Requests
**Before creating a pull request**, please ensure that an issue exists for the corresponding change in the pull request that you intend to make. **If an issue does not exist, please create one per the guidelines above**. The goal is to discuss the design and necessity of the proposed change with Polymer authors and community before diving into a pull request.
When submitting pull requests, please provide:
1. **A reference to the corresponding issue** or issues that will be closed by the pull request. Please refer to these issues in the pull request description using the following syntax:
```markdown
(For a single issue)
Fixes #20
(For multiple issues)
Fixes #32, fixes #40
```
2. **A succinct description of the design** used to fix any related issues. For example:
```markdown
This fixes #20 by removing styles that leaked which would cause the page to turn pink whenever `paper-foo` is clicked.
```
3. **At least one test for each bug fixed or feature added** as part of the pull request. Pull requests that fix bugs or add features without accompanying tests will not be considered.
If a proposed change contains multiple commits, please [squash commits](https://www.google.com/url?q=http://blog.steveklabnik.com/posts/2012-11-08-how-to-squash-commits-in-a-github-pull-request) to as few as is necessary to succinctly express the change. A Polymer author can help you squash commits, so dont be afraid to ask us if you need help with that!

View File

@@ -0,0 +1,54 @@
<!---
This README is automatically generated from the comments in these files:
iron-ajax.html iron-request.html
Edit those files, and our readme bot will duplicate them over here!
Edit this file, and the bot will squash your changes :)
The bot does some handling of markdown. Please file a bug if it does the wrong
thing! https://github.com/PolymerLabs/tedium/issues
-->
[![Build status](https://travis-ci.org/PolymerElements/iron-ajax.svg?branch=master)](https://travis-ci.org/PolymerElements/iron-ajax)
_[Demo and API docs](https://elements.polymer-project.org/elements/iron-ajax)_
##&lt;iron-ajax&gt;
The `iron-ajax` element exposes network request functionality.
```html
<iron-ajax
auto
url="http://gdata.youtube.com/feeds/api/videos/"
params='{"alt":"json", "q":"chrome"}'
handle-as="json"
on-response="handleResponse"
debounce-duration="300"></iron-ajax>
```
With `auto` set to `true`, the element performs a request whenever
its `url`, `params` or `body` properties are changed. Automatically generated
requests will be debounced in the case that multiple attributes are changed
sequentially.
Note: The `params` attribute must be double quoted JSON.
You can trigger a request explicitly by calling `generateRequest` on the
element.
##&lt;iron-request&gt;
iron-request can be used to perform XMLHttpRequests.
```html
<iron-request id="xhr"></iron-request>
...
this.$.xhr.send({url: url, params: params});
```

View File

@@ -0,0 +1,34 @@
{
"name": "iron-ajax",
"version": "1.2.0",
"description": "Makes it easy to make ajax calls and parse the response",
"private": true,
"authors": [
"The Polymer Authors"
],
"keywords": [
"web-components",
"polymer",
"ajax"
],
"main": "iron-ajax.html",
"repository": {
"type": "git",
"url": "git://github.com/PolymerElements/iron-ajax.git"
},
"license": "http://polymer.github.io/LICENSE.txt",
"homepage": "https://github.com/PolymerElements/iron-ajax",
"ignore": [],
"dependencies": {
"promise-polyfill": "polymerlabs/promise-polyfill#^1.0.0",
"polymer": "Polymer/polymer#^1.0.0"
},
"devDependencies": {
"iron-component-page": "polymerelements/iron-component-page#^1.0.0",
"iron-image": "polymerelements/iron-image#^1.0.0",
"paper-styles": "polymerelements/paper-styles#^1.0.0",
"test-fixture": "polymerelements/test-fixture#^1.0.0",
"web-component-tester": "^4.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
}
}

View File

@@ -0,0 +1,59 @@
<!doctype html>
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<title>iron-ajax</title>
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../iron-ajax.html">
<link rel="import" href="../../iron-image/iron-image.html">
<link rel="import" href="../../paper-styles/demo-pages.html">
<style>
iron-image {
background-color: lightgray;
margin: 1em;
}
.horizontal-section {
max-width: 300px;
margin-bottom: 24px;
}
</style>
</head>
<body unresolved>
<h1>Video Feed</h1>
<div class="horizontal-section-container">
<template is="dom-bind" id="app">
<iron-ajax auto
url="https://www.googleapis.com/youtube/v3/search"
params='{"part":"snippet", "q":"polymer", "key": "AIzaSyAuecFZ9xJXbGDkQYWBmYrtzOGJD-iDIgI", "type": "video"}'
handle-as="json"
last-response="{{ajaxResponse}}"></iron-ajax>
<template is="dom-repeat" items="[[ajaxResponse.items]]">
<div class="horizontal-section">
<h2><a href="[[url(item.id.videoId)]]" target="_blank">[[item.snippet.title]]</a></h2>
<iron-image src="[[item.snippet.thumbnails.high.url]]" width="256" height="256" sizing="cover" preload fade></iron-image>
<p>[[item.snippet.description]]</p>
</div>
</template>
</template>
</div>
<script>
var app = document.querySelector('#app');
app.url = function (videoId) {
return 'https://www.youtube.com/watch?v=' + videoId;
};
</script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 225 126" enable-background="new 0 0 225 126" xml:space="preserve">
<g id="background" display="none">
<rect display="inline" fill="#B0BEC5" width="225" height="126"/>
</g>
<g id="label">
</g>
<g id="art">
<g>
<path d="M42.1,54.6H30.9l-2.6,8.2h-4.5l10.9-32h3.8l10.6,32h-4.5L42.1,54.6z M32.1,50.9H41l-4.4-14.2L32.1,50.9z"/>
<path d="M69.1,30.8h4.4v22.7c0,1.5-0.3,2.9-0.8,4.1s-1.3,2.2-2.2,3.1c-0.9,0.8-2,1.5-3.3,1.9c-1.3,0.4-2.7,0.7-4.2,0.7
c-1.5,0-2.9-0.2-4.2-0.6c-1.3-0.4-2.4-1-3.3-1.7c-0.9-0.8-1.6-1.7-2.1-2.9s-0.7-2.5-0.7-4.1h4.4c0,2,0.5,3.4,1.5,4.4
s2.5,1.4,4.4,1.4c0.8,0,1.6-0.1,2.4-0.4c0.7-0.3,1.4-0.7,1.9-1.3s1-1.2,1.3-2s0.5-1.6,0.5-2.6V30.8z"/>
<path d="M96.1,54.6H84.9l-2.6,8.2h-4.5l10.9-32h3.8l10.6,32h-4.5L96.1,54.6z M86.1,50.9H95l-4.4-14.2L86.1,50.9z"/>
<path d="M117.7,43.2l7-12.3h5.3l-9.7,15.9l9.9,16.1h-5.2l-7.2-12.6l-7.2,12.6h-5.3l9.9-16.1l-9.7-15.9h5.3L117.7,43.2z"/>
</g>
<circle cx="25.7" cy="82.2" r="4"/>
<circle cx="130.7" cy="82.2" r="4"/>
<circle cx="52.7" cy="93.2" r="4"/>
<circle cx="104.7" cy="93.2" r="4"/>
<circle cx="156.7" cy="93.2" r="4"/>
<path d="M81.6,78.3c3.3,4.3-1.6,9.1-5.9,5.9C72.5,79.8,77.4,75,81.6,78.3z"/>
<polygon points="104.6,94.1 78.4,82.1 52.2,94.1 25.6,81.9 26.4,80.1 52.2,91.9 78.4,79.9 104.6,91.9 130.8,79.9 157.4,92.1
156.6,93.9 130.8,82.1 "/>
<g id="ic_x5F_add_x0D_">
</g>
</g>
<g id="Guides">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,26 @@
<!doctype html>
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE
The complete set of authors may be found at http://polymer.github.io/AUTHORS
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS
-->
<html>
<head>
<title>iron-ajax</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../iron-component-page/iron-component-page.html">
</head>
<body>
<iron-component-page></iron-component-page>
</body>
</html>

View File

@@ -0,0 +1,507 @@
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="iron-request.html">
<!--
The `iron-ajax` element exposes network request functionality.
<iron-ajax
auto
url="http://gdata.youtube.com/feeds/api/videos/"
params='{"alt":"json", "q":"chrome"}'
handle-as="json"
on-response="handleResponse"
debounce-duration="300"></iron-ajax>
With `auto` set to `true`, the element performs a request whenever
its `url`, `params` or `body` properties are changed. Automatically generated
requests will be debounced in the case that multiple attributes are changed
sequentially.
Note: The `params` attribute must be double quoted JSON.
You can trigger a request explicitly by calling `generateRequest` on the
element.
@demo demo/index.html
@hero hero.svg
-->
<script>
'use strict';
Polymer({
is: 'iron-ajax',
/**
* Fired when a request is sent.
*
* @event request
*/
/**
* Fired when a response is received.
*
* @event response
*/
/**
* Fired when an error is received.
*
* @event error
*/
hostAttributes: {
hidden: true
},
properties: {
/**
* The URL target of the request.
*/
url: {
type: String
},
/**
* An object that contains query parameters to be appended to the
* specified `url` when generating a request. If you wish to set the body
* content when making a POST request, you should use the `body` property
* instead.
*/
params: {
type: Object,
value: function() {
return {};
}
},
/**
* The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'.
* Default is 'GET'.
*/
method: {
type: String,
value: 'GET'
},
/**
* HTTP request headers to send.
*
* Example:
*
* <iron-ajax
* auto
* url="http://somesite.com"
* headers='{"X-Requested-With": "XMLHttpRequest"}'
* handle-as="json"></iron-ajax>
*
* Note: setting a `Content-Type` header here will override the value
* specified by the `contentType` property of this element.
*/
headers: {
type: Object,
value: function() {
return {};
}
},
/**
* Content type to use when sending data. If the `contentType` property
* is set and a `Content-Type` header is specified in the `headers`
* property, the `headers` property value will take precedence.
*
* Varies the handling of the `body` param.
*/
contentType: {
type: String,
value: null
},
/**
* Body content to send with the request, typically used with "POST"
* requests.
*
* If body is a string it will be sent unmodified.
*
* If Content-Type is set to a value listed below, then
* the body will be encoded accordingly.
*
* * `content-type="application/json"`
* * body is encoded like `{"foo":"bar baz","x":1}`
* * `content-type="application/x-www-form-urlencoded"`
* * body is encoded like `foo=bar+baz&x=1`
*
* Otherwise the body will be passed to the browser unmodified, and it
* will handle any encoding (e.g. for FormData, Blob, ArrayBuffer).
*
* @type (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object)
*/
body: {
type: Object,
value: null
},
/**
* Toggle whether XHR is synchronous or asynchronous. Don't change this
* to true unless You Know What You Are Doing™.
*/
sync: {
type: Boolean,
value: false
},
/**
* Specifies what data to store in the `response` property, and
* to deliver as `event.detail.response` in `response` events.
*
* One of:
*
* `text`: uses `XHR.responseText`.
*
* `xml`: uses `XHR.responseXML`.
*
* `json`: uses `XHR.responseText` parsed as JSON.
*
* `arraybuffer`: uses `XHR.response`.
*
* `blob`: uses `XHR.response`.
*
* `document`: uses `XHR.response`.
*/
handleAs: {
type: String,
value: 'json'
},
/**
* Set the withCredentials flag on the request.
*/
withCredentials: {
type: Boolean,
value: false
},
/**
* Set the timeout flag on the request.
*/
timeout: {
type: Number,
value: 0
},
/**
* If true, automatically performs an Ajax request when either `url` or
* `params` changes.
*/
auto: {
type: Boolean,
value: false
},
/**
* If true, error messages will automatically be logged to the console.
*/
verbose: {
type: Boolean,
value: false
},
/**
* The most recent request made by this iron-ajax element.
*/
lastRequest: {
type: Object,
notify: true,
readOnly: true
},
/**
* True while lastRequest is in flight.
*/
loading: {
type: Boolean,
notify: true,
readOnly: true
},
/**
* lastRequest's response.
*
* Note that lastResponse and lastError are set when lastRequest finishes,
* so if loading is true, then lastResponse and lastError will correspond
* to the result of the previous request.
*
* The type of the response is determined by the value of `handleAs` at
* the time that the request was generated.
*
* @type {Object}
*/
lastResponse: {
type: Object,
notify: true,
readOnly: true
},
/**
* lastRequest's error, if any.
*
* @type {Object}
*/
lastError: {
type: Object,
notify: true,
readOnly: true
},
/**
* An Array of all in-flight requests originating from this iron-ajax
* element.
*/
activeRequests: {
type: Array,
notify: true,
readOnly: true,
value: function() {
return [];
}
},
/**
* Length of time in milliseconds to debounce multiple automatically generated requests.
*/
debounceDuration: {
type: Number,
value: 0,
notify: true
},
/**
* Prefix to be stripped from a JSON response before parsing it.
*
* In order to prevent an attack using CSRF with Array responses
* (http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/)
* many backends will mitigate this by prefixing all JSON response bodies
* with a string that would be nonsensical to a JavaScript parser.
*
*/
jsonPrefix: {
type: String,
value: ''
},
/**
* By default, these events do not bubble largely because the `error` event has special
* meaning in the window object. Setting this attribute will cause iron-ajax's request,
* response, and error events to bubble to the window object.
*/
bubbles: {
type: Boolean,
value: false
},
_boundHandleResponse: {
type: Function,
value: function() {
return this._handleResponse.bind(this);
}
}
},
observers: [
'_requestOptionsChanged(url, method, params.*, headers, contentType, ' +
'body, sync, handleAs, jsonPrefix, withCredentials, timeout, auto)'
],
/**
* The query string that should be appended to the `url`, serialized from
* the current value of `params`.
*
* @return {string}
*/
get queryString () {
var queryParts = [];
var param;
var value;
for (param in this.params) {
value = this.params[param];
param = window.encodeURIComponent(param);
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
queryParts.push(param + '=' + window.encodeURIComponent(value[i]));
}
} else if (value !== null) {
queryParts.push(param + '=' + window.encodeURIComponent(value));
} else {
queryParts.push(param);
}
}
return queryParts.join('&');
},
/**
* The `url` with query string (if `params` are specified), suitable for
* providing to an `iron-request` instance.
*
* @return {string}
*/
get requestUrl() {
var queryString = this.queryString;
if (queryString) {
var bindingChar = this.url.indexOf('?') >= 0 ? '&' : '?';
return this.url + bindingChar + queryString;
}
return this.url;
},
/**
* An object that maps header names to header values, first applying the
* the value of `Content-Type` and then overlaying the headers specified
* in the `headers` property.
*
* @return {Object}
*/
get requestHeaders() {
var headers = {};
var contentType = this.contentType;
if (contentType == null && (typeof this.body === 'string')) {
contentType = 'application/x-www-form-urlencoded';
}
if (contentType) {
headers['content-type'] = contentType;
}
var header;
if (this.headers instanceof Object) {
for (header in this.headers) {
headers[header] = this.headers[header].toString();
}
}
return headers;
},
/**
* Request options suitable for generating an `iron-request` instance based
* on the current state of the `iron-ajax` instance's properties.
*
* @return {{
* url: string,
* method: (string|undefined),
* async: (boolean|undefined),
* body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object),
* headers: (Object|undefined),
* handleAs: (string|undefined),
* jsonPrefix: (string|undefined),
* withCredentials: (boolean|undefined)}}
*/
toRequestOptions: function() {
return {
url: this.requestUrl || '',
method: this.method,
headers: this.requestHeaders,
body: this.body,
async: !this.sync,
handleAs: this.handleAs,
jsonPrefix: this.jsonPrefix,
withCredentials: this.withCredentials,
timeout: this.timeout
};
},
/**
* Performs an AJAX request to the specified URL.
*
* @return {!IronRequestElement}
*/
generateRequest: function() {
var request = /** @type {!IronRequestElement} */ (document.createElement('iron-request'));
var requestOptions = this.toRequestOptions();
this.activeRequests.push(request);
request.completes.then(
this._boundHandleResponse
).catch(
this._handleError.bind(this, request)
).then(
this._discardRequest.bind(this, request)
);
request.send(requestOptions);
this._setLastRequest(request);
this._setLoading(true);
this.fire('request', {
request: request,
options: requestOptions
}, {bubbles: this.bubbles});
return request;
},
_handleResponse: function(request) {
if (request === this.lastRequest) {
this._setLastResponse(request.response);
this._setLastError(null);
this._setLoading(false);
}
this.fire('response', request, {bubbles: this.bubbles});
},
_handleError: function(request, error) {
if (this.verbose) {
console.error(error);
}
if (request === this.lastRequest) {
this._setLastError({
request: request,
error: error
});
this._setLastResponse(null);
this._setLoading(false);
}
this.fire('error', {
request: request,
error: error
}, {bubbles: this.bubbles});
},
_discardRequest: function(request) {
var requestIndex = this.activeRequests.indexOf(request);
if (requestIndex > -1) {
this.activeRequests.splice(requestIndex, 1);
}
},
_requestOptionsChanged: function() {
this.debounce('generate-request', function() {
if (this.url == null) {
return;
}
if (this.auto) {
this.generateRequest();
}
}, this.debounceDuration);
},
});
</script>

View File

@@ -0,0 +1,436 @@
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../promise-polyfill/promise-polyfill-lite.html">
<!--
iron-request can be used to perform XMLHttpRequests.
<iron-request id="xhr"></iron-request>
...
this.$.xhr.send({url: url, params: params});
-->
<script>
'use strict';
Polymer({
is: 'iron-request',
hostAttributes: {
hidden: true
},
properties: {
/**
* A reference to the XMLHttpRequest instance used to generate the
* network request.
*
* @type {XMLHttpRequest}
*/
xhr: {
type: Object,
notify: true,
readOnly: true,
value: function() {
return new XMLHttpRequest();
}
},
/**
* A reference to the parsed response body, if the `xhr` has completely
* resolved.
*
* @type {*}
* @default null
*/
response: {
type: Object,
notify: true,
readOnly: true,
value: function() {
return null;
}
},
/**
* A reference to the status code, if the `xhr` has completely resolved.
*/
status: {
type: Number,
notify: true,
readOnly: true,
value: 0
},
/**
* A reference to the status text, if the `xhr` has completely resolved.
*/
statusText: {
type: String,
notify: true,
readOnly: true,
value: ''
},
/**
* A promise that resolves when the `xhr` response comes back, or rejects
* if there is an error before the `xhr` completes.
*
* @type {Promise}
*/
completes: {
type: Object,
readOnly: true,
notify: true,
value: function() {
return new Promise(function (resolve, reject) {
this.resolveCompletes = resolve;
this.rejectCompletes = reject;
}.bind(this));
}
},
/**
* An object that contains progress information emitted by the XHR if
* available.
*
* @default {}
*/
progress: {
type: Object,
notify: true,
readOnly: true,
value: function() {
return {};
}
},
/**
* Aborted will be true if an abort of the request is attempted.
*/
aborted: {
type: Boolean,
notify: true,
readOnly: true,
value: false,
},
/**
* Errored will be true if the browser fired an error event from the
* XHR object (mainly network errors).
*/
errored: {
type: Boolean,
notify: true,
readOnly: true,
value: false
},
/**
* TimedOut will be true if the XHR threw a timeout event.
*/
timedOut: {
type: Boolean,
notify: true,
readOnly: true,
value: false
}
},
/**
* Succeeded is true if the request succeeded. The request succeeded if it
* loaded without error, wasn't aborted, and the status code is ≥ 200, and
* < 300, or if the status code is 0.
*
* The status code 0 is accepted as a success because some schemes - e.g.
* file:// - don't provide status codes.
*
* @return {boolean}
*/
get succeeded() {
if (this.errored || this.aborted || this.timedOut) {
return false;
}
var status = this.xhr.status || 0;
// Note: if we are using the file:// protocol, the status code will be 0
// for all outcomes (successful or otherwise).
return status === 0 ||
(status >= 200 && status < 300);
},
/**
* Sends an HTTP request to the server and returns the XHR object.
*
* The handling of the `body` parameter will vary based on the Content-Type
* header. See the docs for iron-ajax's `body` param for details.
*
* @param {{
* url: string,
* method: (string|undefined),
* async: (boolean|undefined),
* body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object),
* headers: (Object|undefined),
* handleAs: (string|undefined),
* jsonPrefix: (string|undefined),
* withCredentials: (boolean|undefined)}} options -
* url The url to which the request is sent.
* method The HTTP method to use, default is GET.
* async By default, all requests are sent asynchronously. To send synchronous requests,
* set to true.
* body The content for the request body for POST method.
* headers HTTP request headers.
* handleAs The response type. Default is 'text'.
* withCredentials Whether or not to send credentials on the request. Default is false.
* timeout: (Number|undefined)
* @return {Promise}
*/
send: function (options) {
var xhr = this.xhr;
if (xhr.readyState > 0) {
return null;
}
xhr.addEventListener('progress', function (progress) {
this._setProgress({
lengthComputable: progress.lengthComputable,
loaded: progress.loaded,
total: progress.total
});
}.bind(this))
xhr.addEventListener('error', function (error) {
this._setErrored(true);
this._updateStatus();
this.rejectCompletes(error);
}.bind(this));
xhr.addEventListener('timeout', function (error) {
this._setTimedOut(true);
this._updateStatus();
this.rejectCompletes(error);
}.bind(this));
xhr.addEventListener('abort', function () {
this._updateStatus();
this.rejectCompletes(new Error('Request aborted.'));
}.bind(this));
// Called after all of the above.
xhr.addEventListener('loadend', function () {
this._updateStatus();
if (!this.succeeded) {
this.rejectCompletes(new Error('The request failed with status code: ' + this.xhr.status));
return;
}
this._setResponse(this.parseResponse());
this.resolveCompletes(this);
}.bind(this));
this.url = options.url;
xhr.open(
options.method || 'GET',
options.url,
options.async !== false
);
var acceptType = {
'json': 'application/json',
'text': 'text/plain',
'html': 'text/html',
'xml': 'application/xml',
'arraybuffer': 'application/octet-stream'
}[options.handleAs];
var headers = options.headers || Object.create(null);
var newHeaders = Object.create(null);
for (var key in headers) {
newHeaders[key.toLowerCase()] = headers[key];
}
headers = newHeaders;
if (acceptType && !headers['accept']) {
headers['accept'] = acceptType;
}
Object.keys(headers).forEach(function (requestHeader) {
if (/[A-Z]/.test(requestHeader)) {
console.error('Headers must be lower case, got', requestHeader);
}
xhr.setRequestHeader(
requestHeader,
headers[requestHeader]
);
}, this);
if (options.async !== false) {
var handleAs = options.handleAs;
// If a JSON prefix is present, the responseType must be 'text' or the
// browser wont be able to parse the response.
if (!!options.jsonPrefix || !handleAs) {
handleAs = 'text';
}
// In IE, `xhr.responseType` is an empty string when the response
// returns. Hence, caching it as `xhr._responseType`.
xhr.responseType = xhr._responseType = handleAs;
// Cache the JSON prefix, if it exists.
if (!!options.jsonPrefix) {
xhr._jsonPrefix = options.jsonPrefix;
}
}
xhr.withCredentials = !!options.withCredentials;
xhr.timeout = options.timeout;
var body = this._encodeBodyObject(options.body, headers['content-type']);
xhr.send(
/** @type {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|
null|string|undefined} */
(body));
return this.completes;
},
/**
* Attempts to parse the response body of the XHR. If parsing succeeds,
* the value returned will be deserialized based on the `responseType`
* set on the XHR.
*
* @return {*} The parsed response,
* or undefined if there was an empty response or parsing failed.
*/
parseResponse: function () {
var xhr = this.xhr;
var responseType = xhr.responseType || xhr._responseType;
var preferResponseText = !this.xhr.responseType;
var prefixLen = (xhr._jsonPrefix && xhr._jsonPrefix.length) || 0;
try {
switch (responseType) {
case 'json':
// If the xhr object doesn't have a natural `xhr.responseType`,
// we can assume that the browser hasn't parsed the response for us,
// and so parsing is our responsibility. Likewise if response is
// undefined, as there's no way to encode undefined in JSON.
if (preferResponseText || xhr.response === undefined) {
// Try to emulate the JSON section of the response body section of
// the spec: https://xhr.spec.whatwg.org/#response-body
// That is to say, we try to parse as JSON, but if anything goes
// wrong return null.
try {
return JSON.parse(xhr.responseText);
} catch (_) {
return null;
}
}
return xhr.response;
case 'xml':
return xhr.responseXML;
case 'blob':
case 'document':
case 'arraybuffer':
return xhr.response;
case 'text':
default: {
// If `prefixLen` is set, it implies the response should be parsed
// as JSON once the prefix of length `prefixLen` is stripped from
// it. Emulate the behavior above where null is returned on failure
// to parse.
if (prefixLen) {
try {
return JSON.parse(xhr.responseText.substring(prefixLen));
} catch (_) {
return null;
}
}
return xhr.responseText;
}
}
} catch (e) {
this.rejectCompletes(new Error('Could not parse response. ' + e.message));
}
},
/**
* Aborts the request.
*/
abort: function () {
this._setAborted(true);
this.xhr.abort();
},
/**
* @param {*} body The given body of the request to try and encode.
* @param {?string} contentType The given content type, to infer an encoding
* from.
* @return {*} Either the encoded body as a string, if successful,
* or the unaltered body object if no encoding could be inferred.
*/
_encodeBodyObject: function(body, contentType) {
if (typeof body == 'string') {
return body; // Already encoded.
}
var bodyObj = /** @type {Object} */ (body);
switch(contentType) {
case('application/json'):
return JSON.stringify(bodyObj);
case('application/x-www-form-urlencoded'):
return this._wwwFormUrlEncode(bodyObj);
}
return body;
},
/**
* @param {Object} object The object to encode as x-www-form-urlencoded.
* @return {string} .
*/
_wwwFormUrlEncode: function(object) {
if (!object) {
return '';
}
var pieces = [];
Object.keys(object).forEach(function(key) {
// TODO(rictic): handle array values here, in a consistent way with
// iron-ajax params.
pieces.push(
this._wwwFormUrlEncodePiece(key) + '=' +
this._wwwFormUrlEncodePiece(object[key]));
}, this);
return pieces.join('&');
},
/**
* @param {*} str A key or value to encode as x-www-form-urlencoded.
* @return {string} .
*/
_wwwFormUrlEncodePiece: function(str) {
// Spec says to normalize newlines to \r\n and replace %20 spaces with +.
// jQuery does this as well, so this is likely to be widely compatible.
return encodeURIComponent(str.toString().replace(/\r?\n/g, '\r\n'))
.replace(/%20/g, '+');
},
/**
* Updates the status code and status text.
*/
_updateStatus: function() {
this._setStatus(this.xhr.status);
this._setStatusText((this.xhr.statusText === undefined) ? '' : this.xhr.statusText);
}
});
</script>

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html><!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--><html><head>
<meta charset="utf-8">
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../web-component-tester/browser.js"></script>
</head>
<body>
<script>
WCT.loadSuites([
'iron-request.html',
'iron-ajax.html',
'iron-request.html?dom=shadow',
'iron-ajax.html?dom=shadow'
]);
</script>
</body></html>

View File

@@ -0,0 +1,862 @@
<!doctype html>
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<title>iron-ajax</title>
<script src="../../webcomponentsjs/webcomponents.js"></script>
<script src="../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../promise-polyfill/promise-polyfill.html">
<link rel="import" href="../iron-ajax.html">
</head>
<body>
<test-fixture id="TrivialGet">
<template>
<iron-ajax url="/responds_to_get_with_json"></iron-ajax>
</template>
</test-fixture>
<test-fixture id="ParamsGet">
<template>
<iron-ajax url="/responds_to_get_with_json"
params='{"a": "a"}'></iron-ajax>
</template>
</test-fixture>
<test-fixture id="AutoGet">
<template>
<iron-ajax auto url="/responds_to_get_with_json"></iron-ajax>
</template>
</test-fixture>
<test-fixture id="GetEcho">
<template>
<iron-ajax handle-as="json" url="/echoes_request_url"></iron-ajax>
</template>
</test-fixture>
<test-fixture id="TrivialPost">
<template>
<iron-ajax method="POST"
url="/responds_to_post_with_json"></iron-ajax>
</template>
</test-fixture>
<test-fixture id="DebouncedGet">
<template>
<iron-ajax auto
url="/responds_to_debounced_get_with_json"
debounce-duration="150"></iron-ajax>
</template>
</test-fixture>
<test-fixture id='BlankUrl'>
<template>
<iron-ajax auto handle-as='text'></iron-ajax>
</template>
</test-fixture>
<test-fixture id="RealPost">
<template>
<iron-ajax method="POST" url="/httpbin/post"></iron-ajax>
</template>
</test-fixture>
<test-fixture id="Delay">
<template>
<iron-ajax url="/httpbin/delay/1"></iron-ajax>
</template>
</test-fixture>
<test-fixture id="Bubbles">
<template>
<iron-ajax url="/httpbin/post" method="POST" bubbles></iron-ajax>
<iron-ajax url="/responds_to_get_with_502_error_json" bubbles></iron-ajax>
</template>
</test-fixture>
<script>
'use strict';
suite('<iron-ajax>', function () {
var responseHeaders = {
json: { 'Content-Type': 'application/json' },
plain: { 'Content-Type': 'text/plain' }
};
var ajax;
var request;
var server;
function timePasses(ms) {
return new Promise(function(resolve) {
window.setTimeout(function() {
resolve();
}, ms);
});
}
setup(function() {
server = sinon.fakeServer.create();
server.respondWith(
'GET',
/\/responds_to_get_with_json.*/,
[
200,
responseHeaders.json,
'{"success":true}'
]
);
server.respondWith(
'POST',
'/responds_to_post_with_json',
[
200,
responseHeaders.json,
'{"post_success":true}'
]
);
server.respondWith(
'GET',
'/responds_to_get_with_text',
[
200,
responseHeaders.plain,
'Hello World'
]
);
server.respondWith(
'GET',
'/responds_to_debounced_get_with_json',
[
200,
responseHeaders.json,
'{"success": "true"}'
]
);
server.respondWith(
'GET',
'/responds_to_get_with_502_error_json',
[
502,
responseHeaders.json,
'{"message": "an error has occurred"}'
]
);
ajax = fixture('TrivialGet');
});
teardown(function() {
server.restore();
});
// Echo requests are responded to individually and on demand, unlike the
// others in this file which are responded to with server.respond(),
// which responds to all open requests.
// We don't use server.respondWith here because there's no way to use it
// and only respond to a subset of requests.
// This way we can test for delayed and out of order responses and
// distinquish them by their responses.
function respondToEchoRequest(request) {
request.respond(200, responseHeaders.json, JSON.stringify({
url: request.url
}));
}
suite('when making simple GET requests for JSON', function() {
test('has sane defaults that love you', function() {
request = ajax.generateRequest();
server.respond();
expect(request.response).to.be.ok;
expect(request.response).to.be.an('object');
expect(request.response.success).to.be.equal(true);
});
test('will be asynchronous by default', function() {
expect(ajax.toRequestOptions().async).to.be.eql(true);
});
});
suite('when setting custom headers', function() {
test('are present in the request headers', function() {
ajax.headers['custom-header'] = 'valid';
var options = ajax.toRequestOptions();
expect(options.headers).to.be.ok;
expect(options.headers['custom-header']).to.be.an('string');
expect(options.headers.hasOwnProperty('custom-header')).to.be.equal(
true);
});
test('non-objects in headers are not applied', function() {
ajax.headers = 'invalid';
var options = ajax.toRequestOptions();
expect(Object.keys(options.headers).length).to.be.equal(0);
});
});
suite('when url isn\'t set yet', function() {
test('we don\'t fire any automatic requests', function() {
expect(server.requests.length).to.be.equal(0);
ajax = fixture('BlankUrl');
return timePasses(1).then(function() {
// We don't make any requests.
expect(server.requests.length).to.be.equal(0);
// Explicitly asking for the request to fire works.
ajax.generateRequest();
expect(server.requests.length).to.be.equal(1);
server.requests = [];
// Explicitly setting url to '' works too.
ajax = fixture('BlankUrl');
ajax.url = '';
return timePasses(1);
}).then(function() {
expect(server.requests.length).to.be.equal(1);
});
});
});
suite('when properties are changed', function() {
test('generates simple-request elements that reflect the change', function() {
request = ajax.generateRequest();
expect(request.xhr.method).to.be.equal('GET');
ajax.method = 'POST';
ajax.url = '/responds_to_post_with_json';
request = ajax.generateRequest();
expect(request.xhr.method).to.be.equal('POST');
});
});
suite('when generating a request', function() {
test('yields an iron-request instance', function() {
var IronRequest = document.createElement('iron-request').constructor;
expect(ajax.generateRequest()).to.be.instanceOf(IronRequest);
});
test('correctly adds params to a URL that already has some', function() {
ajax.url += '?a=b';
ajax.params = {'c': 'd'};
expect(ajax.requestUrl).to.be.equal('/responds_to_get_with_json?a=b&c=d')
})
test('encodes params properly', function() {
ajax.params = {'a b,c': 'd e f'};
expect(ajax.queryString).to.be.equal('a%20b%2Cc=d%20e%20f');
});
test('encodes array params properly', function() {
ajax.params = {'a b': ['c','d e', 'f']};
expect(ajax.queryString).to.be.equal('a%20b=c&a%20b=d%20e&a%20b=f');
});
test('reflects the loading state in the `loading` property', function() {
var request = ajax.generateRequest();
expect(ajax.loading).to.be.equal(true);
server.respond();
return request.completes.then(function() {
return timePasses(1);
}).then(function() {
expect(ajax.loading).to.be.equal(false);
});
});
test('the loading-changed event gets fired twice', function() {
var count = 0;
ajax.addEventListener('loading-changed', function() {
count++;
});
var request = ajax.generateRequest();
server.respond();
return request.completes.then(function() {
return timePasses(1);
}).then(function() {
expect(count).to.be.equal(2);
});
});
});
suite('when there are multiple requests', function() {
var requests;
var echoAjax;
var promiseAllComplete;
setup(function() {
echoAjax = fixture('GetEcho');
requests = [];
for (var i = 0; i < 3; ++i) {
echoAjax.params = {'order': i + 1};
requests.push(echoAjax.generateRequest());
}
var allPromises = requests.map(function(r){return r.completes});
promiseAllComplete = Promise.all(allPromises);
});
test('holds all requests in the `activeRequests` Array', function() {
expect(requests).to.deep.eql(echoAjax.activeRequests);
});
test('empties `activeRequests` when requests are completed', function() {
expect(echoAjax.activeRequests.length).to.be.equal(3);
for (var i = 0; i < 3; i++) {
respondToEchoRequest(server.requests[i]);
}
return promiseAllComplete.then(function() {
return timePasses(1);
}).then(function() {
expect(echoAjax.activeRequests.length).to.be.equal(0);
});
});
test('avoids race conditions with last response', function() {
expect(echoAjax.lastResponse).to.be.equal(undefined);
// Resolving the oldest request doesn't update lastResponse.
respondToEchoRequest(server.requests[0]);
return requests[0].completes.then(function() {
expect(echoAjax.lastResponse).to.be.equal(undefined);
// Resolving the most recent request does!
respondToEchoRequest(server.requests[2]);
return requests[2].completes;
}).then(function() {
expect(echoAjax.lastResponse).to.be.deep.eql(
{url: '/echoes_request_url?order=3'});
// Resolving an out of order stale request after does nothing!
respondToEchoRequest(server.requests[1]);
return requests[1].completes;
}).then(function() {
expect(echoAjax.lastResponse).to.be.deep.eql(
{url: '/echoes_request_url?order=3'});
});
});
test('`loading` is true while the last one is loading', function() {
expect(echoAjax.loading).to.be.equal(true);
respondToEchoRequest(server.requests[0]);
return requests[0].completes.then(function() {
// We're still loading because requests[2] is the most recently
// made request.
expect(echoAjax.loading).to.be.equal(true);
respondToEchoRequest(server.requests[2]);
return requests[2].completes;
}).then(function() {
// Now we're done loading.
expect(echoAjax.loading).to.be.eql(false);
// Resolving an out of order stale request after should have
// no effect.
respondToEchoRequest(server.requests[1]);
return requests[1].completes;
}).then(function() {
expect(echoAjax.loading).to.be.eql(false);
});
});
});
suite('when params are changed', function() {
test('generates a request that reflects the change', function() {
ajax = fixture('ParamsGet');
request = ajax.generateRequest();
expect(request.xhr.url).to.be.equal('/responds_to_get_with_json?a=a');
ajax.params = {b: 'b'};
request = ajax.generateRequest();
expect(request.xhr.url).to.be.equal('/responds_to_get_with_json?b=b');
});
});
suite('when `auto` is enabled', function() {
setup(function() {
ajax = fixture('AutoGet');
});
test('automatically generates new requests', function() {
return new Promise(function(resolve) {
ajax.addEventListener('request', function() {
resolve();
});
});
});
test('does not send requests if url is not a string', function() {
return new Promise(function(resolve, reject) {
ajax.addEventListener('request', function() {
reject('A request was generated but url is null!');
});
ajax.url = null;
ajax.handleAs = 'text';
Polymer.Base.async(function() {
resolve();
}, 1);
});
});
test('deduplicates multiple changes to a single request', function() {
return new Promise(function(resolve, reject) {
ajax.addEventListener('request', function() {
server.respond();
});
ajax.addEventListener('response', function() {
try {
expect(ajax.activeRequests.length).to.be.eql(1);
resolve()
} catch (e) {
reject(e);
}
});
ajax.handleas = 'text';
ajax.params = { foo: 'bar' };
ajax.headers = { 'X-Foo': 'Bar' };
});
});
test('automatically generates new request when a sub-property of params is changed', function(done) {
ajax.addEventListener('request', function() {
server.respond();
});
ajax.params = { foo: 'bar' };
ajax.addEventListener('response', function() {
ajax.addEventListener('request', function() {
done();
});
ajax.set('params.foo', 'xyz');
});
});
});
suite('the last response', function() {
setup(function() {
request = ajax.generateRequest();
server.respond();
});
test('is accessible as a readonly property', function() {
return request.completes.then(function (request) {
expect(ajax.lastResponse).to.be.equal(request.response);
});
});
test('updates with each new response', function() {
return request.completes.then(function(request) {
expect(request.response).to.be.an('object');
expect(ajax.lastResponse).to.be.equal(request.response);
ajax.handleAs = 'text';
request = ajax.generateRequest();
server.respond();
return request.completes;
}).then(function(request) {
expect(request.response).to.be.a('string');
expect(ajax.lastResponse).to.be.equal(request.response);
});
});
});
suite('when making POST requests', function() {
setup(function() {
ajax = fixture('TrivialPost');
});
test('POSTs the value of the `body` attribute', function() {
var requestBody = JSON.stringify({foo: 'bar'});
ajax.body = requestBody;
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
expect(server.requests[0].requestBody).to.be.equal(requestBody);
});
test('if `contentType` is set to form encode, the body is encoded',function() {
ajax.body = {foo: 'bar\nbip', 'biz bo': 'baz blar'};
ajax.contentType = 'application/x-www-form-urlencoded';
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
expect(server.requests[0].requestBody).to.be.equal(
'foo=bar%0D%0Abip&biz+bo=baz+blar');
});
test('if `contentType` is json, the body is json encoded', function() {
var requestObj = {foo: 'bar', baz: [1,2,3]}
ajax.body = requestObj;
ajax.contentType = 'application/json';
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
expect(server.requests[0].requestBody).to.be.equal(
JSON.stringify(requestObj));
});
suite('the examples in the documentation work', function() {
test('json content, body attribute is an object', function() {
ajax.setAttribute('body', '{"foo": "bar baz", "x": 1}');
ajax.contentType = 'application/json';
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
expect(server.requests[0].requestBody).to.be.equal(
'{"foo":"bar baz","x":1}');
});
test('form content, body attribute is an object', function() {
ajax.setAttribute('body', '{"foo": "bar baz", "x": 1}');
ajax.contentType = 'application/x-www-form-urlencoded';
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
expect(server.requests[0].requestBody).to.be.equal(
'foo=bar+baz&x=1');
});
});
suite('and `contentType` is explicitly set to form encode', function() {
test('we encode a custom object', function() {
function Foo(bar) { this.bar = bar };
var requestObj = new Foo('baz');
ajax.body = requestObj;
ajax.contentType = 'application/x-www-form-urlencoded';
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
expect(server.requests[0].requestBody).to.be.equal('bar=baz');
});
})
suite('and `contentType` isn\'t set', function() {
test('we don\'t try to encode an ArrayBuffer', function() {
var requestObj = new ArrayBuffer()
ajax.body = requestObj;
ajax.generateRequest();
expect(server.requests[0]).to.be.ok;
// We give the browser the ArrayBuffer directly, without trying
// to encode it.
expect(server.requests[0].requestBody).to.be.equal(requestObj);
});
})
});
suite('when debouncing requests', function() {
setup(function() {
ajax = fixture('DebouncedGet');
});
test('only requests a single resource', function() {
ajax._requestOptionsChanged();
expect(server.requests[0]).to.be.equal(undefined);
ajax._requestOptionsChanged();
return timePasses(200).then(function() {
expect(server.requests[0]).to.be.ok;
});
});
});
suite('when a response handler is bound', function() {
var responseHandler;
setup(function() {
responseHandler = sinon.spy();
ajax.addEventListener('response', responseHandler);
});
test('calls the handler after every response', function() {
ajax.generateRequest();
ajax.generateRequest();
server.respond();
return ajax.lastRequest.completes.then(function() {
expect(responseHandler.callCount).to.be.equal(2);
});
});
});
suite('when the response type is `json`', function() {
setup(function() {
server.restore();
});
test('finds the JSON on any platform', function() {
ajax.url = '../bower.json';
request = ajax.generateRequest();
return request.completes.then(function() {
expect(ajax.lastResponse).to.be.instanceOf(Object);
});
});
});
suite('when handleAs parameter is `text`', function() {
test('response type is string', function () {
ajax.url = '/responds_to_get_with_json';
ajax.handleAs = 'text';
request = ajax.generateRequest();
var promise = request.completes.then(function () {
expect(typeof(ajax.lastResponse)).to.be.equal('string');
});
expect(server.requests.length).to.be.equal(1);
expect(server.requests[0].requestHeaders['accept']).to.be.equal(
'text/plain');
server.respond();
return promise;
});
});
suite('when a request fails', function() {
test('we give an error with useful details', function() {
ajax.url = '/responds_to_get_with_502_error_json';
ajax.handleAs = 'json';
var eventFired = false;
ajax.addEventListener('error', function(event) {
expect(event.detail.request).to.be.okay;
expect(event.detail.error).to.be.okay;
eventFired = true;
});
var request = ajax.generateRequest();
var promise = request.completes.then(function() {
throw new Error('Expected the request to fail!');
}, function(error) {
expect(error).to.be.instanceof(Error);
expect(request.succeeded).to.be.eq(false);
return timePasses(100);
}).then(function() {
expect(eventFired).to.be.eq(true);
expect(ajax.lastError).to.not.be.eq(null);
});
server.respond();
return promise;
});
test('we give a useful error even when the domain doesn\'t resolve', function() {
ajax.url = 'http://nonexistant.example.com/';
server.restore();
var eventFired = false;
ajax.addEventListener('error', function(event) {
expect(event.detail.request).to.be.okay;
expect(event.detail.error).to.be.okay;
eventFired = true;
});
var request = ajax.generateRequest();
var promise = request.completes.then(function() {
throw new Error('Expected the request to fail!');
}, function(error) {
expect(request.succeeded).to.be.eq(false);
expect(error).to.not.be.eq(null);
return timePasses(100);
}).then(function() {
expect(eventFired).to.be.eq(true);
expect(ajax.lastError).to.not.be.eq(null);
});
server.respond();
return promise;
});
});
suite('when handleAs parameter is `json`', function() {
test('response type is string', function () {
ajax.url = '/responds_to_get_with_json';
ajax.handleAs = 'json';
request = ajax.generateRequest();
var promise = request.completes.then(function () {
expect(typeof(ajax.lastResponse)).to.be.equal('object');
});
expect(server.requests.length).to.be.equal(1);
expect(server.requests[0].requestHeaders['accept']).to.be.equal(
'application/json');
server.respond();
return promise;
});
});
suite('when making a POST over the wire', function() {
test('FormData is handled correctly', function() {
server.restore();
var requestBody = new FormData();
requestBody.append('a', 'foo');
requestBody.append('b', 'bar');
var ajax = fixture('RealPost');
ajax.body = requestBody;
return ajax.generateRequest().completes.then(function() {
expect(ajax.lastResponse.headers['Content-Type']).to.match(
/^multipart\/form-data; boundary=.*$/);
expect(ajax.lastResponse.form.a).to.be.equal('foo');
expect(ajax.lastResponse.form.b).to.be.equal('bar');
});
});
test('json is handled correctly', function() {
server.restore();
var ajax = fixture('RealPost');
ajax.body = JSON.stringify({a: 'foo', b: 'bar'});
ajax.contentType = 'application/json';
return ajax.generateRequest().completes.then(function() {
expect(ajax.lastResponse.headers['Content-Type']).to.match(
/^application\/json(;.*)?$/);
expect(ajax.lastResponse.json.a).to.be.equal('foo');
expect(ajax.lastResponse.json.b).to.be.equal('bar');
});
});
test('urlencoded data is handled correctly', function() {
server.restore();
var ajax = fixture('RealPost');
ajax.body = 'a=foo&b=bar';
return ajax.generateRequest().completes.then(function() {
expect(ajax.lastResponse.headers['Content-Type']).to.match(
/^application\/x-www-form-urlencoded(;.*)?$/);
expect(ajax.lastResponse.form.a).to.be.equal('foo');
expect(ajax.lastResponse.form.b).to.be.equal('bar');
});
});
test('xml is handled correctly', function() {
server.restore();
var ajax = fixture('RealPost');
var xmlDoc = document.implementation.createDocument(
null, "foo", null);
var node = xmlDoc.createElement("bar");
node.setAttribute("name" , "baz");
xmlDoc.documentElement.appendChild(node);
ajax.body = xmlDoc;
return ajax.generateRequest().completes.then(function() {
expect(ajax.lastResponse.headers['Content-Type']).to.match(
/^application\/xml(;.*)?$/);
expect(ajax.lastResponse.data).to.match(
/<foo\s*><bar\s+name="baz"\s*\/><\/foo\s*>/);
});
});
});
suite('when setting timeout', function() {
setup(function() {
server.restore();
});
test('it is present in the request xhr object', function () {
ajax.url = '/responds_to_get_with_json';
ajax.timeout = 5000; // 5 Seconds
request = ajax.generateRequest();
expect(request.xhr.timeout).to.be.equal(5000); // 5 Seconds
});
test('it fails once that timeout is reached', function () {
var ajax = fixture('Delay');
ajax.timeout = 1; // 1 Millisecond
request = ajax.generateRequest();
return request.completes.then(function () {
throw new Error('Expected the request to throw an error.');
}, function() {
expect(request.succeeded).to.be.equal(false);
expect(request.xhr.status).to.be.equal(0);
expect(request.timedOut).to.be.equal(true);
return timePasses(1);
}).then(function() {
expect(ajax.loading).to.be.equal(false);
expect(ajax.lastResponse).to.be.equal(null);
expect(ajax.lastError).to.not.be.equal(null);
});
});
});
suite('when using the bubbles attribute', function () {
setup(function() {
server.restore();
});
test('the request and response events should bubble to window', function (done) {
server.restore();
var total = 0;
function incrementTotal(){
total++;
if (total === 2){
done();
}
}
window.addEventListener('request', incrementTotal);
window.addEventListener('response', incrementTotal);
var ajax = fixture('Bubbles')[0];
ajax.generateRequest();
server.respond();
});
test('the request and error events should bubble to window', function (done) {
server.restore();
var total = 0;
function incrementTotal(){
total++;
if (total === 2){
done();
}
}
window.addEventListener('request', incrementTotal);
window.addEventListener('error', incrementTotal);
var ajax = fixture('Bubbles')[1];
ajax.generateRequest();
server.respond();
});
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,284 @@
<!doctype html>
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<title>iron-request</title>
<script src="../../webcomponentsjs/webcomponents.js"></script>
<script src="../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../iron-request.html">
</head>
<body>
<test-fixture id="TrivialRequest">
<template>
<iron-request></iron-request>
</template>
</test-fixture>
<script>
suite('<iron-request>', function () {
var jsonResponseHeaders;
var successfulRequestOptions;
var request;
var server;
setup(function () {
jsonResponseHeaders = {
'Content-Type': 'application/json'
};
server = sinon.fakeServer.create();
server.respondWith('GET', '/responds_to_get_with_json', [
200,
jsonResponseHeaders,
'{"success":true}'
]);
server.respondWith('GET', '/responds_to_get_with_prefixed_json', [
200,
jsonResponseHeaders,
'])}while(1);</x>{"success":true}'
]);
server.respondWith('GET', '/responds_to_get_with_500', [
500,
{},
''
]);
server.respondWith('GET', '/responds_to_get_with_100', [
100,
{},
''
]);
server.respondWith('GET', '/responds_to_get_with_0', [
0,
jsonResponseHeaders,
'{"success":true}'
]);
request = fixture('TrivialRequest');
successfulRequestOptions = {
url: '/responds_to_get_with_json'
};
});
teardown(function () {
server.restore();
});
suite('basic usage', function () {
test('creates network requests, requiring only `url`', function () {
request.send(successfulRequestOptions);
server.respond();
expect(request.response).to.be.ok;
});
test('sets async to true by default', function () {
request.send(successfulRequestOptions);
expect(request.xhr.async).to.be.eql(true);
});
test('can be aborted', function () {
request.send(successfulRequestOptions);
request.abort();
server.respond();
return request.completes.then(function () {
throw new Error('Request did not abort appropriately!');
}).catch(function (e) {
expect(request.response).to.not.be.ok;
});
});
test('default responseType is text', function () {
request.send(successfulRequestOptions);
server.respond();
return request.completes.then(function() {
expect(request.response).to.be.an('string')
});
});
test('default responseType of text is not applied, when async is false', function () {
var options = Object.create(successfulRequestOptions);
options.async = false;
request.send(options);
server.respond();
return request.completes.then(function() {
expect(request.xhr.responseType).to.be.empty;
});
});
test('responseType can be configured via handleAs option', function () {
var options = Object.create(successfulRequestOptions);
options.handleAs = 'json';
request.send(options);
expect(server.requests.length).to.be.equal(1);
expect(server.requests[0].requestHeaders['accept']).to.be.equal(
'application/json');
server.respond();
return request.completes.then(function() {
expect(request.response).to.be.an('object');
});
});
test('setting jsonPrefix correctly strips it from the response', function () {
var options = {
url: '/responds_to_get_with_prefixed_json',
handleAs: 'json',
jsonPrefix: '])}while(1);</x>'
};
request.send(options);
expect(server.requests.length).to.be.equal(1);
expect(server.requests[0].requestHeaders['accept']).to.be.equal(
'application/json');
server.respond();
return request.completes.then(function() {
expect(request.response).to.deep.eq({success: true});
});
});
test('responseType cannot be configured via handleAs option, when async is false', function () {
var options = Object.create(successfulRequestOptions);
options.handleAs = 'json';
options.async = false;
request.send(options);
expect(server.requests.length).to.be.equal(1);
expect(server.requests[0].requestHeaders['accept']).to.be.equal(
'application/json');
server.respond();
return request.completes.then(function() {
expect(request.response).to.be.a('string');
});
});
test('headers are sent up', function() {
var options = Object.create(successfulRequestOptions);
options.headers = {
'foo': 'bar',
'accept': 'this should override the default'
};
request.send(options);
expect(server.requests.length).to.be.equal(1);
var fakeXhr = server.requests[0]
expect(fakeXhr.requestHeaders['foo']).to.be.equal(
'bar');
expect(fakeXhr.requestHeaders['accept']).to.be.equal(
'this should override the default');
});
test('headers are deduped by lowercasing', function() {
var options = Object.create(successfulRequestOptions);
options.headers = {
'foo': 'bar',
'Foo': 'bar',
'fOo': 'bar',
'Accept': 'this should also override the default'
};
request.send(options);
expect(server.requests.length).to.be.equal(1);
var fakeXhr = server.requests[0]
expect(Object.keys(fakeXhr.requestHeaders).length).to.be.equal(2);
expect(fakeXhr.requestHeaders['foo']).to.be.equal(
'bar');
expect(fakeXhr.requestHeaders['accept']).to.be.equal(
'this should also override the default');
});
});
suite('special cases', function() {
test('treats status code 0 as success, though the outcome is ambiguous', function() {
// Note: file:// status code will probably be 0 no matter what happened.
request.send({
url: '/responds_to_get_with_0'
});
server.respond();
expect(request.succeeded).to.be.equal(true);
});
});
suite('errors', function() {
test('treats status codes between 1 and 199 as errors', function() {
request.send({
url: '/responds_to_get_with_100'
});
server.respond();
expect(request.succeeded).to.be.equal(false);
});
test('treats status codes between 300 and ∞ as errors', function() {
request.send({
url: '/responds_to_get_with_500'
});
server.respond();
expect(request.succeeded).to.be.equal(false);
});
});
suite('status codes', function() {
test('status and statusText is set after a ambiguous request', function() {
request.send({
url: '/responds_to_get_with_0'
});
server.respond();
expect(request.status).to.be.equal(0);
expect(request.statusText).to.be.equal('');
});
test('status and statusText is set after a request that succeeded', function() {
request.send({
url: '/responds_to_get_with_json'
});
server.respond();
expect(request.status).to.be.equal(200);
expect(request.statusText).to.be.equal('OK');
});
test('status and statusText is set after a request that failed', function() {
request.send({
url: '/responds_to_get_with_500'
});
server.respond();
expect(request.status).to.be.equal(500);
expect(request.statusText).to.be.equal('Internal Server Error');
});
});
});
</script>
</body>
</html>