Merge pull request #227 from muffinresearch/replace-rewire
Replace rewire + convert to ES6+ (fixes #211, fixes #219, fixes #218)
This commit is contained in:
Коммит
6b74403d43
|
@ -5,13 +5,19 @@
|
|||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"ecmaFeatures": {
|
||||
"arrowFunctions": true,
|
||||
"blockBindings": true,
|
||||
"classes": true,
|
||||
"destructuring": true,
|
||||
"defaultParams": true,
|
||||
"jsx": true,
|
||||
"modules": true,
|
||||
"restParams": true,
|
||||
"spread": true,
|
||||
},
|
||||
|
@ -40,6 +46,7 @@
|
|||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-loop-func": 2,
|
||||
|
@ -55,7 +62,7 @@
|
|||
"no-unused-vars": 2,
|
||||
"no-with": 2,
|
||||
"quotes": [2, "single"],
|
||||
"react/display-name": 1,
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 1,
|
||||
"react/jsx-quotes": 1,
|
||||
"react/jsx-no-undef": 1,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
var karmaConfig = require('./karma.shared');
|
||||
|
||||
module.exports = function (config) {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
var defaults = require('lodash.defaults');
|
||||
var karmaConfig = require('./karma.shared');
|
||||
var browsers = require('mozilla-payments-saucelabs-browsers');
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
"redux": "0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "5.6.15",
|
||||
"babel-eslint": "3.1.20",
|
||||
"babel-loader": "5.3.0",
|
||||
"babel-core": "5.8.3",
|
||||
"babel-eslint": "3.1.26",
|
||||
"babel-loader": "5.3.2",
|
||||
"chai": "3.0.0",
|
||||
"cog": "git://github.com/muffinresearch/cog.git#87e7c4f3",
|
||||
"eslint-plugin-react": "2.6.3",
|
||||
"eslint-plugin-react": "3.0.0",
|
||||
"grunt": "0.4.5",
|
||||
"grunt-concurrent": "2.0.0",
|
||||
"grunt-contrib-clean": "0.6.0",
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": "true",
|
||||
},
|
||||
"rules": {
|
||||
"global-strict": 0,
|
||||
"strict": [2, "never"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
export function error(debugMessage) {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
import $ from 'jquery';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
// TODO: expand these actions to encapsulate the Ajax
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
'use strict';
|
||||
import $ from 'jquery';
|
||||
|
||||
var $ = require('jquery');
|
||||
|
||||
var appActions = require('./app');
|
||||
var actionTypes = require('constants/action-types');
|
||||
import * as appActions from './app';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
|
||||
export function signIn(accessToken) {
|
||||
export function signIn(accessToken, jquery=$) {
|
||||
return function(dispatch) {
|
||||
$.ajax({
|
||||
jquery.ajax({
|
||||
data: {
|
||||
access_token: accessToken,
|
||||
},
|
||||
|
@ -19,7 +17,7 @@ export function signIn(accessToken) {
|
|||
|
||||
console.log('setting CSRF token for subsequent requests:',
|
||||
data.csrf_token);
|
||||
$.ajaxSetup({
|
||||
jquery.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRFToken': data.csrf_token,
|
||||
},
|
||||
|
|
|
@ -1,35 +1,28 @@
|
|||
'use strict';
|
||||
import 'shims';
|
||||
|
||||
require('shims');
|
||||
import React, { Component } from 'react';
|
||||
import { Provider, Connector } from 'redux/react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
var React = require('react');
|
||||
var Provider = require('redux/react').Provider;
|
||||
var Connector = require('redux/react').Connector;
|
||||
var bindActionCreators = require('redux').bindActionCreators;
|
||||
import reduxConfig from 'redux-config';
|
||||
import * as managementActions from 'actions/management';
|
||||
|
||||
var reduxConfig = require('redux-config');
|
||||
var managementActions = require('actions/management');
|
||||
|
||||
var ModalError = require('views/modal-error');
|
||||
var Management = require('views/management');
|
||||
var ManageCards = require('views/manage-cards');
|
||||
import ModalError from 'views/modal-error';
|
||||
import Management from 'views/management';
|
||||
import ManageCards from 'views/manage-cards';
|
||||
|
||||
|
||||
export default class ManagementApp extends Component {
|
||||
|
||||
var App = React.createClass({
|
||||
|
||||
displayName: 'ManagementApp',
|
||||
|
||||
selectData: function(state) {
|
||||
selectData(state) {
|
||||
return {
|
||||
management: state.management,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
renderChild(connector) {
|
||||
var actions = bindActionCreators(managementActions, connector.dispatch);
|
||||
var children = [];
|
||||
|
||||
if (connector.management.error) {
|
||||
children.push(
|
||||
<ModalError {...actions} error={connector.management.error} />
|
||||
|
@ -42,9 +35,9 @@ var App = React.createClass({
|
|||
}
|
||||
children.push(<Management {...actions} />);
|
||||
return <div>{children}</div>;
|
||||
},
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<main>
|
||||
<Connector select={this.selectData}>
|
||||
|
@ -52,19 +45,16 @@ var App = React.createClass({
|
|||
</Connector>
|
||||
</main>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
component: App,
|
||||
init: function() {
|
||||
React.render((
|
||||
<Provider redux={reduxConfig.default}>
|
||||
{function() {
|
||||
return <App/>;
|
||||
}}
|
||||
</Provider>
|
||||
), document.body);
|
||||
},
|
||||
};
|
||||
export function init() {
|
||||
React.render((
|
||||
<Provider redux={reduxConfig}>
|
||||
{function() {
|
||||
return <ManagementApp/>;
|
||||
}}
|
||||
</Provider>
|
||||
), document.body);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
var App = require('./app');
|
||||
import $ from 'jquery';
|
||||
import { init as initApp } from './app';
|
||||
|
||||
// Common ajax settings.
|
||||
$.ajaxSetup({
|
||||
dataType: 'json',
|
||||
});
|
||||
|
||||
App.init();
|
||||
initApp();
|
||||
|
|
|
@ -1,42 +1,54 @@
|
|||
'use strict';
|
||||
import 'shims';
|
||||
|
||||
require('shims');
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
var Provider = require('redux/react').Provider;
|
||||
var Connector = require('redux/react').Connector;
|
||||
var bindActionCreators = require('redux').bindActionCreators;
|
||||
import { Connector, Provider } from 'redux/react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
var reduxConfig = require('redux-config');
|
||||
var ErrorMessage = require('components/error');
|
||||
var Login = require('views/login');
|
||||
var Purchase = require('views/purchase');
|
||||
var userActions = require('actions/user');
|
||||
var utils = require('utils');
|
||||
import reduxConfig from 'redux-config';
|
||||
import ErrorMessage from 'components/error';
|
||||
import DefaultLogin from 'views/login';
|
||||
import DefaultPurchase from 'views/purchase';
|
||||
import * as userActions from 'actions/user';
|
||||
import { parseQuery } from 'utils';
|
||||
|
||||
|
||||
var App = React.createClass({
|
||||
export default class PaymentApp extends Component {
|
||||
|
||||
displayName: 'PaymentApp',
|
||||
static propTypes = {
|
||||
Login: PropTypes.node,
|
||||
Purchase: PropTypes.node,
|
||||
win: PropTypes.object,
|
||||
}
|
||||
|
||||
getInitialState: function() {
|
||||
var qs = utils.parseQuery(window.location.href);
|
||||
static defaultProps = {
|
||||
Login: DefaultLogin,
|
||||
Purchase: DefaultPurchase,
|
||||
win: window,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
var qs = parseQuery(props.win.location.href);
|
||||
// TODO: we should validate/clean this input to raise early errors.
|
||||
return {
|
||||
this.state = {
|
||||
accessToken: qs.access_token,
|
||||
productId: qs.product,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
selectData: function(state) {
|
||||
selectData(state) {
|
||||
return {
|
||||
app: state.app,
|
||||
user: state.user,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
var state = this.state;
|
||||
var Login = this.props.Login;
|
||||
var Purchase = this.props.Purchase;
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Connector select={this.selectData}>
|
||||
|
@ -62,19 +74,16 @@ var App = React.createClass({
|
|||
</Connector>
|
||||
</main>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
component: App,
|
||||
init: function() {
|
||||
React.render((
|
||||
<Provider redux={reduxConfig.default}>
|
||||
{function() {
|
||||
return <App/>;
|
||||
}}
|
||||
</Provider>
|
||||
), document.body);
|
||||
},
|
||||
};
|
||||
export function init() {
|
||||
React.render((
|
||||
<Provider redux={reduxConfig}>
|
||||
{function() {
|
||||
return <PaymentApp/>;
|
||||
}}
|
||||
</Provider>
|
||||
), document.body);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
var $ = require('jquery');
|
||||
var tracking = require('tracking');
|
||||
var App = require('./app');
|
||||
import $ from 'jquery';
|
||||
import tracking from 'tracking';
|
||||
import { init as initApp } from './app';
|
||||
|
||||
// Common ajax settings.
|
||||
$.ajaxSetup({
|
||||
|
@ -10,4 +8,4 @@ $.ajaxSetup({
|
|||
});
|
||||
|
||||
tracking.init();
|
||||
App.init();
|
||||
initApp();
|
||||
|
|
|
@ -1,31 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
/*eslint react/no-multi-comp: 0 */
|
||||
|
||||
var React = require('react');
|
||||
var cx = require('classnames');
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
|
||||
var Accordion = React.createClass({
|
||||
export class Accordion extends Component {
|
||||
|
||||
displayName: 'Accordion',
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
var sections = [];
|
||||
React.Children.forEach(this.props.children, function() {
|
||||
sections.push({isActive: false});
|
||||
});
|
||||
sections[0].isActive = true;
|
||||
return {
|
||||
this.state = {
|
||||
sections: sections,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
activate: function(sectionIdx, e) {
|
||||
activate(sectionIdx, e) {
|
||||
if (e.target.getAttribute('data-activate') !== null) {
|
||||
e.preventDefault();
|
||||
var sections = this.state.sections;
|
||||
|
@ -34,14 +30,13 @@ var Accordion = React.createClass({
|
|||
});
|
||||
this.setState({sections: sections});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
var that = this;
|
||||
var children = React.Children.map(this.props.children, function(child, idx){
|
||||
render() {
|
||||
var children = React.Children.map(this.props.children, (child, idx) => {
|
||||
return React.cloneElement(child, {
|
||||
activate: that.activate.bind(that, idx),
|
||||
isActive: that.state.sections[idx].isActive,
|
||||
activate: this.activate.bind(this, idx),
|
||||
isActive: this.state.sections[idx].isActive,
|
||||
});
|
||||
});
|
||||
return (
|
||||
|
@ -49,21 +44,19 @@ var Accordion = React.createClass({
|
|||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var AccordionSection = React.createClass({
|
||||
|
||||
displayName: 'AccordionSection',
|
||||
export class AccordionSection extends Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
activate: React.PropTypes.func.isRequired,
|
||||
children: React.PropTypes.node.isRequired,
|
||||
isActive: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
}
|
||||
|
||||
render() {
|
||||
var classes = cx(
|
||||
'ac-section', {'active': this.props.isActive});
|
||||
|
||||
|
@ -73,29 +66,21 @@ var AccordionSection = React.createClass({
|
|||
{this.props.children}
|
||||
</section>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var AccordionContent = React.createClass({
|
||||
export class AccordionContent extends Component {
|
||||
|
||||
displayName: 'AccordionContent',
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<div className="ac-content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Accordion: Accordion,
|
||||
AccordionContent: AccordionContent,
|
||||
AccordionSection: AccordionSection,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,39 @@
|
|||
'use strict';
|
||||
import $ from 'jquery';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var $ = require('jquery');
|
||||
var React = require('react');
|
||||
import CardList from 'components/card-list';
|
||||
import SubmitButton from 'components/submit-button';
|
||||
|
||||
var CardList = require('components/card-list');
|
||||
var SubmitButton = require('components/submit-button');
|
||||
import { gettext } from 'utils';
|
||||
import * as purchaseActions from 'actions/purchase';
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
var purchaseActions = require('actions/purchase');
|
||||
var reduxConfig = require('redux-config');
|
||||
import reduxConfig from 'redux-config';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CardChoice',
|
||||
export default class CardChoice extends Component {
|
||||
|
||||
propTypes: {
|
||||
cards: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
id: React.PropTypes.number,
|
||||
resource_uri: React.PropTypes.string,
|
||||
truncated_id: React.PropTypes.string,
|
||||
type_name: React.PropTypes.string,
|
||||
static propTypes = {
|
||||
cards: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
resource_uri: PropTypes.string,
|
||||
truncated_id: PropTypes.string,
|
||||
type_name: PropTypes.string,
|
||||
}
|
||||
)).isRequired,
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
},
|
||||
productId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isSubmitting: false,
|
||||
card: (this.props.cards.length === 1 ?
|
||||
this.props.cards[0].resource_uri : null),
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
handleSubmit: function(e) {
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({isSubmitting: true});
|
||||
|
@ -51,20 +50,20 @@ module.exports = React.createClass({
|
|||
}).done(function() {
|
||||
console.log('Successfully subscribed with existing card');
|
||||
|
||||
reduxConfig.default.dispatch(
|
||||
reduxConfig.dispatch(
|
||||
purchaseActions.complete()
|
||||
);
|
||||
|
||||
}).fail(function() {
|
||||
// TODO: handler errors.
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
handleCardChange: function(e) {
|
||||
handleCardChange = (e) => {
|
||||
this.setState({card: e.target.value});
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
var cardData = this.props.cards;
|
||||
for (var i = 0; i < cardData.length; i += 1) {
|
||||
var card = cardData[i];
|
||||
|
@ -75,12 +74,14 @@ module.exports = React.createClass({
|
|||
|
||||
return (
|
||||
<form id="card-listing" onSubmit={this.handleSubmit}>
|
||||
<CardList cards={cardData} onCardChange={this.handleCardChange} />
|
||||
<CardList
|
||||
cards={cardData}
|
||||
onCardChange={this.handleCardChange} />
|
||||
<SubmitButton isDisabled={!formIsValid}
|
||||
showSpinner={this.state.isSubmitting}
|
||||
text={gettext('Subscribe')}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
'use strict';
|
||||
import $ from 'jquery';
|
||||
import CardValidator from 'card-validator';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import braintree from 'braintree-web';
|
||||
|
||||
var $ = require('jquery');
|
||||
var CardValidator = require('card-validator');
|
||||
var React = require('react');
|
||||
var braintree = require('braintree-web');
|
||||
import { gettext } from 'utils';
|
||||
|
||||
var utils = require('utils');
|
||||
var gettext = utils.gettext;
|
||||
|
||||
var CardInput = require('components/card-input');
|
||||
var SubmitButton = require('components/submit-button');
|
||||
var purchaseActions = require('actions/purchase');
|
||||
var reduxConfig = require('redux-config');
|
||||
import CardInput from 'components/card-input';
|
||||
import SubmitButton from 'components/submit-button';
|
||||
import * as purchaseActions from 'actions/purchase';
|
||||
import reduxConfig from 'redux-config';
|
||||
|
||||
|
||||
var defaultFieldAttrs = {
|
||||
const defaultFieldAttrs = {
|
||||
'autoComplete': 'off',
|
||||
// inputMode is not yet supported in React.
|
||||
// See https://github.com/facebook/react/pull/3798
|
||||
|
@ -22,22 +19,73 @@ var defaultFieldAttrs = {
|
|||
'type': 'tel',
|
||||
};
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
displayName: 'CardForm',
|
||||
|
||||
propTypes: {
|
||||
card: React.PropTypes.object,
|
||||
cvv: React.PropTypes.object,
|
||||
'data-token': React.PropTypes.string.isRequired,
|
||||
expiration: React.PropTypes.object,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
const errorKeyToFieldMap = {
|
||||
'__all__': {
|
||||
field: 'card',
|
||||
error: 'declined',
|
||||
},
|
||||
'fraud': {
|
||||
field: 'card',
|
||||
error: 'declined',
|
||||
},
|
||||
'cvv': {
|
||||
field: 'cvv',
|
||||
error: 'invalid',
|
||||
},
|
||||
};
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
const fieldProps = {
|
||||
card: {
|
||||
'attrs': defaultFieldAttrs,
|
||||
'classNames': ['card'],
|
||||
'errors': {
|
||||
invalid: gettext('Incorrect card number'),
|
||||
declined: gettext('Card was declined'),
|
||||
},
|
||||
'id': 'card',
|
||||
'placeholder': gettext('Card number'),
|
||||
'validator': CardValidator.number,
|
||||
},
|
||||
expiration: {
|
||||
'attrs': defaultFieldAttrs,
|
||||
'classNames': ['expiration'],
|
||||
'errors': {
|
||||
invalid: gettext('Invalid expiry date'),
|
||||
},
|
||||
'id': 'expiration',
|
||||
// Expiration pattern doesn't change based on card type.
|
||||
'pattern': '11/11',
|
||||
'placeholder': 'MM/YY',
|
||||
'validator': CardValidator.expirationDate,
|
||||
},
|
||||
cvv: {
|
||||
'attrs': defaultFieldAttrs,
|
||||
'autocomplete': 'off',
|
||||
'classNames': ['cvv'],
|
||||
'errors': {
|
||||
invalid: gettext('Invalid CVV'),
|
||||
},
|
||||
'errorModifier': 'right',
|
||||
'id': 'cvv',
|
||||
'validator': CardValidator.cvv,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export default class CardForm extends Component {
|
||||
|
||||
static propTypes = {
|
||||
card: PropTypes.object,
|
||||
cvv: PropTypes.object,
|
||||
'data-token': PropTypes.string.isRequired,
|
||||
expiration: PropTypes.object,
|
||||
id: PropTypes.string.isRequired,
|
||||
productId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isSubmitting: false,
|
||||
cardType: null,
|
||||
errors: {},
|
||||
|
@ -45,55 +93,18 @@ module.exports = React.createClass({
|
|||
expiration: '',
|
||||
cvv: '',
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
fieldProps: {
|
||||
card: {
|
||||
'attrs': defaultFieldAttrs,
|
||||
'classNames': ['card'],
|
||||
'errors': {
|
||||
invalid: gettext('Incorrect card number'),
|
||||
declined: gettext('Card was declined'),
|
||||
},
|
||||
'id': 'card',
|
||||
'placeholder': gettext('Card number'),
|
||||
'validator': CardValidator.number,
|
||||
},
|
||||
expiration: {
|
||||
'attrs': defaultFieldAttrs,
|
||||
'classNames': ['expiration'],
|
||||
'errors': {
|
||||
invalid: gettext('Invalid expiry date'),
|
||||
},
|
||||
'id': 'expiration',
|
||||
// Expiration pattern doesn't change based on card type.
|
||||
'pattern': '11/11',
|
||||
'placeholder': 'MM/YY',
|
||||
'validator': CardValidator.expirationDate,
|
||||
},
|
||||
cvv: {
|
||||
'attrs': defaultFieldAttrs,
|
||||
'autocomplete': 'off',
|
||||
'classNames': ['cvv'],
|
||||
'errors': {
|
||||
invalid: gettext('Invalid CVV'),
|
||||
},
|
||||
'errorModifier': 'right',
|
||||
'id': 'cvv',
|
||||
'validator': CardValidator.cvv,
|
||||
},
|
||||
},
|
||||
|
||||
handleChange: function(e) {
|
||||
handleChange = (e) => {
|
||||
var fieldId = e.target.id;
|
||||
var val = e.target.value;
|
||||
|
||||
var fieldProps = this.fieldProps[fieldId];
|
||||
var valData = fieldProps.validator(this.stripPlaceholder(val));
|
||||
fieldProps.hasVal = val.length > 0 || false;
|
||||
fieldProps.isValid = valData.isValid === true;
|
||||
fieldProps.showError = !valData.isValid && !valData.isPotentiallyValid;
|
||||
fieldProps.errorMessage = fieldProps.errors.invalid;
|
||||
var fieldData = fieldProps[fieldId];
|
||||
var valData = fieldData.validator(this.stripPlaceholder(val));
|
||||
fieldData.hasVal = val.length > 0 || false;
|
||||
fieldData.isValid = valData.isValid === true;
|
||||
fieldData.showError = !valData.isValid && !valData.isPotentiallyValid;
|
||||
fieldData.errorMessage = fieldData.errors.invalid;
|
||||
|
||||
var newState = {
|
||||
[e.target.id]: e.target.value,
|
||||
|
@ -106,9 +117,9 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
}
|
||||
|
||||
handleSubmit: function(e) {
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({isSubmitting: true});
|
||||
var that = this;
|
||||
|
@ -136,7 +147,7 @@ module.exports = React.createClass({
|
|||
}).done(function() {
|
||||
console.log('Successfully subscribed + completed payment');
|
||||
|
||||
reduxConfig.default.dispatch(
|
||||
reduxConfig.dispatch(
|
||||
purchaseActions.complete()
|
||||
);
|
||||
|
||||
|
@ -145,25 +156,9 @@ module.exports = React.createClass({
|
|||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
errorKeyToFieldMap: {
|
||||
'__all__': {
|
||||
field: 'card',
|
||||
error: 'declined',
|
||||
},
|
||||
'fraud': {
|
||||
field: 'card',
|
||||
error: 'declined',
|
||||
},
|
||||
'cvv': {
|
||||
field: 'cvv',
|
||||
error: 'invalid',
|
||||
},
|
||||
},
|
||||
|
||||
processApiErrors: function(errors) {
|
||||
var that = this;
|
||||
processApiErrors(errors) {
|
||||
if (errors.error_response && errors.error_response.braintree) {
|
||||
var apiErrors = errors.error_response.braintree;
|
||||
// Iterate over the error object and create a new data
|
||||
|
@ -171,10 +166,10 @@ module.exports = React.createClass({
|
|||
// a list of generic errors.
|
||||
Object.keys(apiErrors).forEach(function(key) {
|
||||
console.log('API ErrorMessage: ' + JSON.stringify(apiErrors[key]));
|
||||
var errorData = that.errorKeyToFieldMap[key] || {};
|
||||
var errorData = errorKeyToFieldMap[key] || {};
|
||||
var field = errorData.field;
|
||||
if (field) {
|
||||
var fieldData = that.fieldProps[field];
|
||||
var fieldData = fieldProps[field];
|
||||
fieldData.isValid = false;
|
||||
fieldData.showError = true;
|
||||
fieldData.errorMessage = fieldData.errors[errorData.error];
|
||||
|
@ -184,33 +179,31 @@ module.exports = React.createClass({
|
|||
this.setState({
|
||||
isSubmitting: false,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
stripPlaceholder: function(val) {
|
||||
stripPlaceholder(val) {
|
||||
return val ? val.replace(/_/g, '') : '';
|
||||
},
|
||||
|
||||
render: function() {
|
||||
}
|
||||
|
||||
render() {
|
||||
var formIsValid = true;
|
||||
var that = this;
|
||||
|
||||
// Update form validity based on fieldProps.
|
||||
Object.keys(this.fieldProps).forEach(function(field) {
|
||||
if (!that.fieldProps[field].isValid) {
|
||||
Object.keys(fieldProps).forEach(function(field) {
|
||||
if (!fieldProps[field].isValid) {
|
||||
formIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<form {...this.props} onSubmit={this.handleSubmit}>
|
||||
<CardInput {...this.fieldProps.card}
|
||||
<CardInput {...fieldProps.card}
|
||||
cardType={this.state.cardType}
|
||||
onChangeHandler={this.handleChange} />
|
||||
<CardInput {...this.fieldProps.expiration}
|
||||
<CardInput {...fieldProps.expiration}
|
||||
cardType={this.state.cardType}
|
||||
onChangeHandler={this.handleChange} />
|
||||
<CardInput {...this.fieldProps.cvv}
|
||||
<CardInput {...fieldProps.cvv}
|
||||
cardType={this.state.cardType}
|
||||
onChangeHandler={this.handleChange} />
|
||||
<SubmitButton isDisabled={!formIsValid}
|
||||
|
@ -218,5 +211,5 @@ module.exports = React.createClass({
|
|||
text={gettext('Subscribe')} />
|
||||
</form>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
'use strict';
|
||||
import cx from 'classnames';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var cx = require('classnames');
|
||||
var React = require('react');
|
||||
// Just a convenience mapping for cards from card-validator
|
||||
// to shorted classes used in CSS.
|
||||
const cardTypeMap = {
|
||||
'american-express': 'amex',
|
||||
'diners-club': 'diners',
|
||||
'master-card': 'mastercard',
|
||||
};
|
||||
|
||||
export default class CardIcon extends Component {
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CardIcon',
|
||||
|
||||
propTypes: {
|
||||
cardType: React.PropTypes.oneOf([
|
||||
static propTypes = {
|
||||
cardType: PropTypes.oneOf([
|
||||
'amex',
|
||||
'american-express',
|
||||
'diners-club',
|
||||
'discover',
|
||||
|
@ -18,23 +23,16 @@ module.exports = React.createClass({
|
|||
'master-card',
|
||||
'visa',
|
||||
]),
|
||||
},
|
||||
}
|
||||
|
||||
// Just a convenience mapping for cards from card-validator
|
||||
// to shorted classes used in CSS.
|
||||
cardTypeMap: {
|
||||
'american-express': 'amex',
|
||||
'diners-club': 'diners',
|
||||
'master-card': 'mastercard',
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
// This is only displayed if a cardType is passed-in.
|
||||
var cardType = this.props.cardType;
|
||||
var cardClassName = cx([
|
||||
'card-icon',
|
||||
'cctype-' + (this.cardTypeMap[cardType] || cardType),
|
||||
'cctype-' + (cardTypeMap[cardType] || cardType),
|
||||
]);
|
||||
return cardType ? <span className={cardClassName} /> : null;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +1,74 @@
|
|||
'use strict';
|
||||
import classNames from 'classnames';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var classNames = require('classnames');
|
||||
var React = require('react');
|
||||
import CardIcon from 'components/card-icon';
|
||||
import InputError from 'components/input-error';
|
||||
import MaskedInput from 'react-maskedinput';
|
||||
|
||||
var CardIcon = require('components/card-icon');
|
||||
var InputError = require('components/input-error');
|
||||
var MaskedInput = require('react-maskedinput');
|
||||
import { defaults, gettext } from 'utils';
|
||||
|
||||
var utils = require('utils');
|
||||
var gettext = utils.gettext;
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
displayName: 'CardInput',
|
||||
|
||||
propTypes: {
|
||||
attrs: React.PropTypes.object,
|
||||
cardType: React.PropTypes.string,
|
||||
classNames: React.PropTypes.array,
|
||||
errorMessage: React.PropTypes.string,
|
||||
errorModifier: React.PropTypes.string,
|
||||
hasVal: React.PropTypes.bool,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
isValid: React.PropTypes.bool,
|
||||
label: React.PropTypes.string,
|
||||
onChangeHandler: React.PropTypes.func.isRequired,
|
||||
pattern: React.PropTypes.string,
|
||||
placeholder: React.PropTypes.string,
|
||||
showError: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
cardPatterns: {
|
||||
'default': {
|
||||
card: {
|
||||
pattern: '1111 1111 1111 1111',
|
||||
},
|
||||
cvv: {
|
||||
pattern: '111',
|
||||
placeholder: gettext('CVV'),
|
||||
},
|
||||
const cardPatterns = {
|
||||
'default': {
|
||||
card: {
|
||||
pattern: '1111 1111 1111 1111',
|
||||
},
|
||||
'american-express': {
|
||||
card: {
|
||||
pattern: '1111 111111 11111',
|
||||
},
|
||||
cvv: {
|
||||
pattern: '1111',
|
||||
placeholder: gettext('CID'),
|
||||
},
|
||||
},
|
||||
'diners-club': {
|
||||
card: {
|
||||
pattern: '1111 111111 1111',
|
||||
},
|
||||
cvv: {
|
||||
pattern: '111',
|
||||
placeholder: gettext('CVV'),
|
||||
},
|
||||
cvv: {
|
||||
pattern: '111',
|
||||
placeholder: gettext('CVV'),
|
||||
},
|
||||
},
|
||||
'american-express': {
|
||||
card: {
|
||||
pattern: '1111 111111 11111',
|
||||
},
|
||||
cvv: {
|
||||
pattern: '1111',
|
||||
placeholder: gettext('CID'),
|
||||
},
|
||||
},
|
||||
'diners-club': {
|
||||
card: {
|
||||
pattern: '1111 111111 1111',
|
||||
},
|
||||
cvv: {
|
||||
pattern: '111',
|
||||
placeholder: gettext('CVV'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
updatePattern: function(fieldId, cardType) {
|
||||
|
||||
export default class CardInput extends Component {
|
||||
|
||||
static propTypes = {
|
||||
attrs: PropTypes.object,
|
||||
cardType: PropTypes.string,
|
||||
classNames: PropTypes.array,
|
||||
errorMessage: PropTypes.string,
|
||||
errorModifier: PropTypes.string,
|
||||
hasVal: PropTypes.bool,
|
||||
id: PropTypes.string.isRequired,
|
||||
isValid: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
onChangeHandler: PropTypes.func.isRequired,
|
||||
pattern: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
showError: PropTypes.bool,
|
||||
}
|
||||
|
||||
updatePattern(fieldId, cardType) {
|
||||
// Update the pattern for card + cvv field if card was detected.
|
||||
if (cardType && this.cardPatterns[cardType]) {
|
||||
return utils.defaults(
|
||||
this.cardPatterns[cardType][fieldId] || {},
|
||||
this.cardPatterns.default[fieldId]
|
||||
if (cardType && cardPatterns[cardType]) {
|
||||
return defaults(
|
||||
cardPatterns[cardType][fieldId] || {},
|
||||
cardPatterns.default[fieldId]
|
||||
);
|
||||
} else {
|
||||
return this.cardPatterns.default[fieldId] || {};
|
||||
return cardPatterns.default[fieldId] || {};
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
|
||||
var labelClassNames = this.props.classNames || [];
|
||||
// Use a copy of the list to avoid appending ad infinitum.
|
||||
|
@ -95,8 +90,6 @@ module.exports = React.createClass({
|
|||
|
||||
var showCardIcon = this.props.id === 'card';
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<label className={labelClass} htmlFor={this.props.id} >
|
||||
<span className="vh">{label}</span>
|
||||
|
@ -114,5 +107,5 @@ module.exports = React.createClass({
|
|||
/>
|
||||
</label>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var CardIcon = require('components/card-icon');
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import CardIcon from 'components/card-icon';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class CardItem extends Component {
|
||||
|
||||
displayName: 'CardItem',
|
||||
static propTypes = {
|
||||
checked: PropTypes.bool.isRequired,
|
||||
id: PropTypes.number.isRequired,
|
||||
onChangeHandler: PropTypes.func.isRequired,
|
||||
resource_uri: PropTypes.string.isRequired,
|
||||
truncated_id: PropTypes.string.isRequired,
|
||||
type_name: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
checked: React.PropTypes.bool.isRequired,
|
||||
id: React.PropTypes.number.isRequired,
|
||||
onChangeHandler: React.PropTypes.func.isRequired,
|
||||
resource_uri: React.PropTypes.string.isRequired,
|
||||
truncated_id: React.PropTypes.string.isRequired,
|
||||
type_name: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
var cardType = this.props.type_name.toLowerCase();
|
||||
var cardText = '●●●● ●●●● ●●●● ' + this.props.truncated_id;
|
||||
var inputId = 'card-' + this.props.id;
|
||||
|
@ -33,6 +29,5 @@ module.exports = React.createClass({
|
|||
className="text">{cardText}</label>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,34 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import CardItem from 'components/card-item';
|
||||
|
||||
var React = require('react');
|
||||
var CardItem = require('components/card-item');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CardList',
|
||||
export default class CardList extends Component {
|
||||
|
||||
propTypes: {
|
||||
cards: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
id: React.PropTypes.number,
|
||||
resource_uri: React.PropTypes.string,
|
||||
truncated_id: React.PropTypes.string,
|
||||
type_name: React.PropTypes.string,
|
||||
static propTypes = {
|
||||
cards: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
resource_uri: PropTypes.string,
|
||||
truncated_id: PropTypes.string,
|
||||
type_name: PropTypes.string,
|
||||
}
|
||||
)).isRequired,
|
||||
onCardChange: React.PropTypes.func.isRequired,
|
||||
},
|
||||
onCardChange: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
var cardData = this.props.cards;
|
||||
var cardList = [];
|
||||
|
||||
for (var i = 0; i < cardData.length; i += 1) {
|
||||
var { checked, ...card } = cardData[i];
|
||||
cardList.push(<CardItem {...card} key={'ci-' + i}
|
||||
onChangeHandler={this.props.onCardChange}
|
||||
checked={checked} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="card-listing">
|
||||
{cardList}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var utils = require('utils');
|
||||
|
||||
var gettext = utils.gettext;
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { gettext } from 'utils';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Error',
|
||||
export default class ErrorMessage extends Component {
|
||||
|
||||
propTypes: {
|
||||
error: React.PropTypes.object.isRequired,
|
||||
},
|
||||
static propTypes = {
|
||||
error: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
console.log('rendering app error:', this.props.error);
|
||||
return (
|
||||
<div className="app-error">
|
||||
|
@ -23,6 +17,5 @@ module.exports = React.createClass({
|
|||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
var cx = require('classnames');
|
||||
var React = require('react');
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'InputError',
|
||||
export default class InputError extends Component {
|
||||
|
||||
propTypes: {
|
||||
errorMessage: React.PropTypes.string.isRequired,
|
||||
errorModifier: React.PropTypes.oneOf(['center', 'right', 'left']),
|
||||
},
|
||||
static propTypes = {
|
||||
errorMessage: PropTypes.string.isRequired,
|
||||
errorModifier: PropTypes.oneOf(['center', 'right', 'left']),
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
var { errorMessage, ...toolTipAttrs } = this.props;
|
||||
var errorClass = cx([
|
||||
'tooltip',
|
||||
|
@ -20,7 +17,8 @@ module.exports = React.createClass({
|
|||
]);
|
||||
return (
|
||||
<span {...toolTipAttrs}
|
||||
className={errorClass}>{errorMessage}</span>
|
||||
className={errorClass}>{errorMessage}</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var gettext = require('utils').gettext;
|
||||
var cx = require('classnames');
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { gettext } from 'utils';
|
||||
import cx from 'classnames';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class Modal extends Component {
|
||||
|
||||
displayName: 'Modal',
|
||||
static propTypes = {
|
||||
children: PropTypes.object.isRequired,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
title: PropTypes.string,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.object.isRequired,
|
||||
handleClose: React.PropTypes.func.isRequired,
|
||||
title: React.PropTypes.string,
|
||||
},
|
||||
|
||||
onClose: function(e) {
|
||||
onClose = (e) => {
|
||||
var targetClassName = e.target.getAttribute('class') || '';
|
||||
var classes = targetClassName.split(' ');
|
||||
// Only deal with closing the window if the event
|
||||
|
@ -27,9 +23,9 @@ module.exports = React.createClass({
|
|||
e.stopPropagation();
|
||||
this.props.handleClose();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
var classes = cx(['modal', {'active': true}]);
|
||||
|
||||
return (
|
||||
|
@ -47,5 +43,5 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import products from 'products';
|
||||
import { gettext } from 'utils';
|
||||
|
||||
var React = require('react');
|
||||
var products = require('products');
|
||||
var gettext = require('utils').gettext;
|
||||
export default class ProductDetail extends Component {
|
||||
|
||||
module.exports = React.createClass({
|
||||
static propTypes = {
|
||||
productId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
displayName: 'ProductDetail',
|
||||
|
||||
propTypes: {
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
|
||||
var productId = this.props.productId;
|
||||
var productData = products[productId];
|
||||
|
@ -29,5 +25,5 @@ module.exports = React.createClass({
|
|||
<div>{gettext('per month')}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { gettext } from 'utils';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
export default class Spinner extends Component {
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Spinner',
|
||||
static propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
},
|
||||
static defaultProps = {
|
||||
text: gettext('Loading'),
|
||||
}
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
text: gettext('Loading'),
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<div className="spinner-cont">
|
||||
<div className="spinner" />
|
||||
<span className="text">{this.props.text}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
var cx = require('classnames');
|
||||
var React = require('react');
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'SubmitButton',
|
||||
export default class SubmitButton extends Component {
|
||||
|
||||
propTypes: {
|
||||
isDisabled: React.PropTypes.bool,
|
||||
showSpinner: React.PropTypes.bool,
|
||||
text: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
static propTypes = {
|
||||
isDisabled: PropTypes.bool,
|
||||
showSpinner: PropTypes.bool,
|
||||
text: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
var { isDisabled, text, showSpinner, ...buttonAttrs } = this.props;
|
||||
|
||||
var buttonClassNames = cx({
|
||||
|
@ -33,5 +29,5 @@ module.exports = React.createClass({
|
|||
disabled={isDisabled}
|
||||
type="submit">{text}</button>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
export const APP_ERROR = 'APP_ERROR';
|
||||
export const USER_SIGNED_IN = 'USER_SIGNED_IN';
|
||||
export const COMPLETE_PURCHASE = 'COMPLETE_PURCHASE';
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
'mozilla-concrete-brick': require('json!mozilla-concrete-brick'),
|
||||
'mozilla-concrete-mortar': require('json!mozilla-concrete-mortar'),
|
||||
};
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
'use strict';
|
||||
import { createRedux } from 'redux';
|
||||
import * as stores from 'stores';
|
||||
|
||||
var redux = require('redux');
|
||||
var dataStore = require('stores/index');
|
||||
console.log(dataStore);
|
||||
|
||||
function createRedux() {
|
||||
return redux.createRedux(dataStore);
|
||||
export function create() {
|
||||
return createRedux(stores);
|
||||
}
|
||||
|
||||
exports.create = createRedux;
|
||||
exports.default = createRedux();
|
||||
export default create();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
'supportedLanguages': [
|
||||
'ca',
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
import assign from 'object.assign';
|
||||
|
||||
assign.shim();
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
export default function app(state, action) {
|
||||
if (action.type === actionTypes.APP_ERROR) {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
export { default as app } from './app';
|
||||
export { default as management } from './management';
|
||||
export { default as purchase } from './purchase';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
|
||||
export default function management(state, action) {
|
||||
console.log('management store: got action', action);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
|
||||
export default function purchase(state, action) {
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
|
||||
export default function user(state, action) {
|
||||
if (action.type === actionTypes.USER_SIGNED_IN) {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/* global ga */
|
||||
|
||||
var settings = require('settings');
|
||||
import settings from 'settings';
|
||||
|
||||
|
||||
class Tracking {
|
||||
export class Tracking {
|
||||
|
||||
constructor(opts) {
|
||||
opts = opts || {};
|
||||
|
@ -86,7 +84,7 @@ class Tracking {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = new Tracking({
|
||||
export default new Tracking({
|
||||
enabled: settings.tracking.enabled,
|
||||
trackingId: settings.tracking.id,
|
||||
});
|
||||
|
|
|
@ -1,37 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Populates an object with defaults if the key is not yet defined.
|
||||
* Similar to _.defaults except this takes only a single defaults object.
|
||||
* @param {object} object - the object to populate defaults on
|
||||
* @param {object} defaults - the defaults to use
|
||||
* @param {object} opt - the defaults to use
|
||||
* @returns {object}
|
||||
*/
|
||||
|
||||
exports.defaults = (object, defaults) => {
|
||||
export function defaults(object, opt) {
|
||||
object = object || {};
|
||||
defaults = defaults || {};
|
||||
Object.keys(defaults).forEach(function(key) {
|
||||
opt = opt || {};
|
||||
Object.keys(opt).forEach(function(key) {
|
||||
if (typeof object[key] === 'undefined') {
|
||||
object[key] = defaults[key];
|
||||
object[key] = opt[key];
|
||||
}
|
||||
});
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
exports.getMountNode = (node) => {
|
||||
export function getMountNode(node){
|
||||
return node || document.getElementById('view');
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
exports.gettext = (string) => {
|
||||
export function gettext(string){
|
||||
// Initial no-op gettext stand-in.
|
||||
return string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
exports.parseQuery = (url) => {
|
||||
export function parseQuery(url){
|
||||
//
|
||||
// Given a complete URL, parse the query string and return an
|
||||
// object of parameters->values. This doesn't bother with repeated
|
||||
|
@ -51,4 +49,4 @@ exports.parseQuery = (url) => {
|
|||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,33 +1,32 @@
|
|||
'use strict';
|
||||
import $ from 'jquery';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var $ = require('jquery');
|
||||
var React = require('react');
|
||||
import CardForm from 'components/card-form';
|
||||
import ProductDetail from 'components/product-detail';
|
||||
import Spinner from 'components/spinner';
|
||||
|
||||
var CardForm = require('components/card-form');
|
||||
var ProductDetail = require('components/product-detail');
|
||||
var Spinner = require('components/spinner');
|
||||
|
||||
var tracking = require('tracking');
|
||||
import { default as tracking } from 'tracking';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class CardDetailsView extends Component {
|
||||
|
||||
displayName: 'CardDetailsView',
|
||||
static propTypes = {
|
||||
productId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
braintree_token: false,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount() {
|
||||
console.log('Requesting braintree token');
|
||||
// TODO: move this to a purchase action.
|
||||
|
||||
this.mounted = true;
|
||||
|
||||
tracking.setPage('/card-details');
|
||||
|
||||
$.ajax({
|
||||
|
@ -35,16 +34,20 @@ module.exports = React.createClass({
|
|||
url: '/api/braintree/token/generate/',
|
||||
context: this,
|
||||
}).then(function(data) {
|
||||
if (this.isMounted()) {
|
||||
if (this.mounted) {
|
||||
this.setState({'braintree_token': data.token}); // eslint-disable-line
|
||||
}
|
||||
}).fail(function() {
|
||||
// TODO: some error state.
|
||||
console.log('failed to get braintree token');
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (this.state.braintree_token) {
|
||||
return (
|
||||
|
@ -61,5 +64,5 @@ module.exports = React.createClass({
|
|||
} else {
|
||||
return <Spinner />;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
import CardChoice from 'components/card-choice';
|
||||
import ProductDetail from 'components/product-detail';
|
||||
|
||||
var CardChoice = require('components/card-choice');
|
||||
var ProductDetail = require('components/product-detail');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
var tracking = require('tracking');
|
||||
import { gettext } from 'utils';
|
||||
import tracking from 'tracking';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class CardListing extends Component {
|
||||
|
||||
displayName: 'CardListing',
|
||||
static propTypes = {
|
||||
payWithNewCard: PropTypes.func.isRequired,
|
||||
paymentMethods: PropTypes.array.isRequired,
|
||||
productId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
payWithNewCard: React.PropTypes.func.isRequired,
|
||||
paymentMethods: React.PropTypes.array.isRequired,
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount() {
|
||||
tracking.setPage('/card-listing');
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<div className="card-listing">
|
||||
<ProductDetail productId={this.props.productId} />
|
||||
|
@ -37,5 +33,5 @@ module.exports = React.createClass({
|
|||
</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,29 @@
|
|||
'use strict';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
import ProductDetail from 'components/product-detail';
|
||||
import SubmitButton from 'components/submit-button';
|
||||
|
||||
var ProductDetail = require('components/product-detail');
|
||||
var SubmitButton = require('components/submit-button');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
var tracking = require('tracking');
|
||||
import { gettext } from 'utils';
|
||||
import { default as tracking } from 'tracking';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class CompletePayment extends Component {
|
||||
|
||||
displayName: 'CompleteView',
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
userEmail: React.PropTypes.string.isRequired,
|
||||
win: React.PropTypes.object,
|
||||
},
|
||||
}
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
win: window,
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
win: window,
|
||||
}
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount() {
|
||||
tracking.setPage('/complete-payment');
|
||||
},
|
||||
}
|
||||
|
||||
handleClick: function(e) {
|
||||
handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
var win = this.props.win;
|
||||
if (win.parent !== window) {
|
||||
|
@ -45,21 +39,20 @@ module.exports = React.createClass({
|
|||
} else {
|
||||
console.log('Not iframed. No-op');
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
var component = this;
|
||||
render() {
|
||||
return (
|
||||
<div className="complete">
|
||||
<ProductDetail productId={component.props.productId} />
|
||||
<ProductDetail productId={this.props.productId} />
|
||||
<p className="accepted">{gettext('Payment Accepted')}</p>
|
||||
<p className="receipt">
|
||||
{gettext('Your receipt has been sent to')}
|
||||
<span className="email">{this.props.userEmail}</span>
|
||||
</p>
|
||||
<SubmitButton text={gettext('OK')}
|
||||
onClick={component.handleClick} />
|
||||
onClick={this.handleClick} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
import Spinner from 'components/spinner';
|
||||
|
||||
var Spinner = require('components/spinner');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
var tracking = require('tracking');
|
||||
import { gettext } from 'utils';
|
||||
import tracking from 'tracking';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class Login extends Component {
|
||||
|
||||
displayName: 'Login',
|
||||
static propTypes = {
|
||||
accessToken: PropTypes.string.isRequired,
|
||||
signIn: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
accessToken: React.PropTypes.string.isRequired,
|
||||
signIn: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount() {
|
||||
tracking.setPage('/login');
|
||||
this.props.signIn(this.props.accessToken);
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return <Spinner text={gettext('Signing in')}/>;
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
import Modal from 'components/modal';
|
||||
import CardList from 'components/card-list';
|
||||
|
||||
var Modal = require('components/modal');
|
||||
var CardList = require('components/card-list');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
import { gettext } from 'utils';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class ManageCards extends Component {
|
||||
|
||||
displayName: 'ManageCards',
|
||||
static propTypes = {
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
paymentMethods: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
paymentMethods: React.PropTypes.array.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
handleClose={this.props.closeModal}
|
||||
|
@ -25,7 +21,6 @@ module.exports = React.createClass({
|
|||
<CardList cards={this.props.paymentMethods} />
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var {
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionSection,
|
||||
} = require('components/accordion');
|
||||
AccordionSection } from 'components/accordion';
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
import { gettext } from 'utils';
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
displayName: 'ManagementApp',
|
||||
export default class Management extends Component {
|
||||
|
||||
propTypes: {
|
||||
getPayMethods: React.PropTypes.func.isRequired,
|
||||
},
|
||||
static propTypes = {
|
||||
getPayMethods: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
|
||||
<div>
|
||||
|
@ -75,5 +71,5 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
var Modal = require('components/modal');
|
||||
import Modal from 'components/modal';
|
||||
import ErrorMessage from 'components/error';
|
||||
|
||||
var ErrorMessage = require('components/error');
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class ModalError extends Component {
|
||||
|
||||
displayName: 'ModalError',
|
||||
static propTypes = {
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
error: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
error: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<Modal handleClose={this.props.closeModal}>
|
||||
<ErrorMessage error={this.props.error} />
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +1,50 @@
|
|||
'use strict';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
var React = require('react');
|
||||
var bindActionCreators = require('redux').bindActionCreators;
|
||||
var Connector = require('redux/react').Connector;
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Connector } from 'redux/react';
|
||||
|
||||
var CardDetails = require('views/card-details');
|
||||
var CardListing = require('views/card-listing');
|
||||
var CompletePayment = require('views/complete-payment');
|
||||
var purchaseActions = require('actions/purchase');
|
||||
import * as purchaseActions from 'actions/purchase';
|
||||
|
||||
import DefaultCardDetails from 'views/card-details';
|
||||
import DefaultCardListing from 'views/card-listing';
|
||||
import DefaultCompletePayment from 'views/complete-payment';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
export default class Purchase extends Component {
|
||||
|
||||
displayName: 'Purchase',
|
||||
static propTypes = {
|
||||
CardDetails: PropTypes.object,
|
||||
CardListing: PropTypes.object,
|
||||
CompletePayment: PropTypes.object,
|
||||
productId: PropTypes.string.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
productId: React.PropTypes.string.isRequired,
|
||||
user: React.PropTypes.object.isRequired,
|
||||
},
|
||||
static defaultProps = {
|
||||
CardDetails: DefaultCardDetails,
|
||||
CardListing: DefaultCardListing,
|
||||
CompletePayment: DefaultCompletePayment,
|
||||
}
|
||||
|
||||
selectData: function(state) {
|
||||
selectData(state) {
|
||||
return {
|
||||
purchase: state.purchase,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
render () {
|
||||
var props = this.props;
|
||||
var CompletePayment = this.props.CompletePayment;
|
||||
var CardListing = this.props.CardListing;
|
||||
var CardDetails = this.props.CardDetails;
|
||||
return (
|
||||
<Connector select={this.selectData}>
|
||||
{function(result) {
|
||||
if (result.purchase.completed) {
|
||||
return (
|
||||
<CompletePayment productId={props.productId}
|
||||
userEmail={props.user.email} />
|
||||
<CompletePayment
|
||||
productId={props.productId}
|
||||
userEmail={props.user.email} />
|
||||
);
|
||||
} else if (result.purchase.payment_methods.length > 0) {
|
||||
console.log('rendering card listing');
|
||||
|
@ -51,5 +62,5 @@ module.exports = React.createClass({
|
|||
}}
|
||||
</Connector>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
"mocha": true,
|
||||
"node": true,
|
||||
},
|
||||
"rules": {
|
||||
"strict": 0,
|
||||
"global-strict": 0,
|
||||
},
|
||||
"globals": {
|
||||
"assert": false,
|
||||
"sinon": false,
|
||||
|
|
|
@ -1,145 +1,152 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var TestUtils = require('react/lib/ReactTestUtils');
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
module.exports = {
|
||||
testCards: {
|
||||
amex: '378282246310005',
|
||||
discover: '6011111111111117',
|
||||
jcb: '3530111333300000',
|
||||
maestro: '6304000000000000',
|
||||
mastercard: '5555555555554444',
|
||||
visa: '4111111111111111',
|
||||
invalidVisa: '4111111111111113',
|
||||
},
|
||||
|
||||
declinedError: {
|
||||
error_response: {
|
||||
braintree: {
|
||||
'__all__': [
|
||||
{'message': 'Do Not Honor', 'code': '2000'},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
cvvError: {
|
||||
error_response: {
|
||||
braintree: {
|
||||
'cvv': [
|
||||
{'message': 'Gateway Rejected: cvv', 'code': 'cvv'},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
findByClass: function(component, className){
|
||||
return TestUtils.findRenderedDOMComponentWithClass(component, className);
|
||||
},
|
||||
|
||||
findAllByClass: function(component, className){
|
||||
return TestUtils.scryRenderedDOMComponentsWithClass(component, className);
|
||||
},
|
||||
|
||||
findByTag: function(component, tag){
|
||||
return TestUtils.findRenderedDOMComponentWithTag(component, tag);
|
||||
},
|
||||
|
||||
findAllByTag: function(component, tag){
|
||||
return TestUtils.scryRenderedDOMComponentsWithTag(component, tag);
|
||||
},
|
||||
|
||||
getFluxContainer: function(redux) {
|
||||
//
|
||||
// Get a container component to set context stubs so you can use it
|
||||
// to wrap a component for testing.
|
||||
// You'd only need this to test a component that uses the
|
||||
// redux Connector component.
|
||||
//
|
||||
|
||||
var FluxContainer = React.createClass({
|
||||
|
||||
displayName: 'FluxContainer',
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
redux: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {redux: redux};
|
||||
},
|
||||
|
||||
render () {
|
||||
return this.props.children();
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return FluxContainer;
|
||||
},
|
||||
|
||||
fakeJquery: function(opt) {
|
||||
//
|
||||
// Return a context to work with a fake jquery object in a test.
|
||||
//
|
||||
var componentContext;
|
||||
opt = opt || {};
|
||||
opt.returnedData = opt.returnedData || {};
|
||||
// Must be one of 'success', 'fail'
|
||||
opt.result = opt.result || 'success';
|
||||
|
||||
var jqueryStubResponse = {
|
||||
fail: function() {
|
||||
return this;
|
||||
},
|
||||
then: function() {
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
if (opt.result === 'success') {
|
||||
jqueryStubResponse.then = function(callback) {
|
||||
console.log('jquery stub: executing success');
|
||||
callback.call(componentContext, opt.returnedData);
|
||||
return this;
|
||||
};
|
||||
} else if (opt.result === 'fail') {
|
||||
jqueryStubResponse.fail = function(callback) {
|
||||
callback.call(componentContext);
|
||||
return this;
|
||||
};
|
||||
} else {
|
||||
throw new Error('unexpected jquery stub result: ' + opt.result);
|
||||
}
|
||||
|
||||
var jqueryStub = {
|
||||
ajax: function(ajaxOpt) {
|
||||
console.log('jquery stub: executing ajax()', ajaxOpt);
|
||||
componentContext = ajaxOpt.context;
|
||||
return jqueryStubResponse;
|
||||
},
|
||||
ajaxSetup: function() {},
|
||||
};
|
||||
|
||||
return {
|
||||
ajaxSpy: sinon.spy(jqueryStub, 'ajax'),
|
||||
ajaxSetupSpy: sinon.spy(jqueryStub, 'ajaxSetup'),
|
||||
stub: jqueryStub,
|
||||
};
|
||||
},
|
||||
|
||||
stubComponent: function() {
|
||||
return React.createClass({
|
||||
displayName: 'StubComponent',
|
||||
render: function() {
|
||||
return <div></div>;
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
export const testCards = {
|
||||
amex: '378282246310005',
|
||||
discover: '6011111111111117',
|
||||
jcb: '3530111333300000',
|
||||
maestro: '6304000000000000',
|
||||
mastercard: '5555555555554444',
|
||||
visa: '4111111111111111',
|
||||
invalidVisa: '4111111111111113',
|
||||
};
|
||||
|
||||
|
||||
export const declinedError = {
|
||||
error_response: {
|
||||
braintree: {
|
||||
'__all__': [
|
||||
{'message': 'Do Not Honor', 'code': '2000'},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const cvvError = {
|
||||
error_response: {
|
||||
braintree: {
|
||||
'cvv': [
|
||||
{'message': 'Gateway Rejected: cvv', 'code': 'cvv'},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export function findByClass(component, className){
|
||||
return TestUtils.findRenderedDOMComponentWithClass(component, className);
|
||||
}
|
||||
|
||||
|
||||
export function findAllByClass (component, className){
|
||||
return TestUtils.scryRenderedDOMComponentsWithClass(component, className);
|
||||
}
|
||||
|
||||
|
||||
export function findByTag(component, tag){
|
||||
return TestUtils.findRenderedDOMComponentWithTag(component, tag);
|
||||
}
|
||||
|
||||
|
||||
export function findAllByTag(component, tag){
|
||||
return TestUtils.scryRenderedDOMComponentsWithTag(component, tag);
|
||||
}
|
||||
|
||||
|
||||
export function getFluxContainer(redux) {
|
||||
//
|
||||
// Get a container component to set context stubs so you can use it
|
||||
// to wrap a component for testing.
|
||||
// You'd only need this to test a component that uses the
|
||||
// redux Connector component.
|
||||
//
|
||||
|
||||
var FluxContainer = React.createClass({
|
||||
|
||||
displayName: 'FluxContainer',
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
redux: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {redux: redux};
|
||||
},
|
||||
|
||||
render () {
|
||||
return this.props.children();
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return FluxContainer;
|
||||
}
|
||||
|
||||
|
||||
export function fakeJquery(opt) {
|
||||
//
|
||||
// Return a context to work with a fake jquery object in a test.
|
||||
//
|
||||
var componentContext;
|
||||
opt = opt || {};
|
||||
opt.returnedData = opt.returnedData || {};
|
||||
// Must be one of 'success', 'fail'
|
||||
opt.result = opt.result || 'success';
|
||||
|
||||
var jqueryStubResponse = {
|
||||
fail: function() {
|
||||
return this;
|
||||
},
|
||||
then: function() {
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
if (opt.result === 'success') {
|
||||
jqueryStubResponse.then = function(callback) {
|
||||
console.log('jquery stub: executing success');
|
||||
callback.call(componentContext, opt.returnedData);
|
||||
return this;
|
||||
};
|
||||
} else if (opt.result === 'fail') {
|
||||
jqueryStubResponse.fail = function(callback) {
|
||||
callback.call(componentContext);
|
||||
return this;
|
||||
};
|
||||
} else {
|
||||
throw new Error('unexpected jquery stub result: ' + opt.result);
|
||||
}
|
||||
|
||||
var jqueryStub = {
|
||||
ajax: function(ajaxOpt) {
|
||||
console.log('jquery stub: executing ajax()', ajaxOpt);
|
||||
componentContext = ajaxOpt.context;
|
||||
return jqueryStubResponse;
|
||||
},
|
||||
ajaxSetup: function() {},
|
||||
};
|
||||
|
||||
return {
|
||||
ajaxSpy: sinon.spy(jqueryStub, 'ajax'),
|
||||
ajaxSetupSpy: sinon.spy(jqueryStub, 'ajaxSetup'),
|
||||
stub: jqueryStub,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function stubComponent() {
|
||||
return React.createClass({
|
||||
displayName: 'StubComponent',
|
||||
render: function() {
|
||||
return <div></div>;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
// Webpack tests entry point. Bundles all the test files
|
||||
// into a single file.
|
||||
// See: https://github.com/webpack/karma-webpack#alternative-usage
|
||||
|
||||
require('shims');
|
||||
import 'shims';
|
||||
|
||||
var context = require.context('.', true, /test\..*?.jsx?$/);
|
||||
context.keys().forEach(context);
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React = require('react');
|
||||
var TestUtils = require('react/lib/ReactTestUtils');
|
||||
var helpers = require('./helpers');
|
||||
import { Accordion,
|
||||
AccordionContent,
|
||||
AccordionSection } from 'components/accordion';
|
||||
|
||||
var {Accordion,
|
||||
AccordionContent,
|
||||
AccordionSection} = require('components/accordion');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
|
||||
describe('Accordion', function() {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
var appActions = require('actions/app');
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
import * as appActions from 'actions/app';
|
||||
|
||||
|
||||
describe('appActions', function() {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
import CardChoice from 'components/card-choice';
|
||||
|
||||
var CardChoice = require('components/card-choice');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
var helpers = require('./helpers');
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
|
||||
describe('Card Choice', function() {
|
||||
|
||||
|
@ -53,8 +51,6 @@ describe('Card Choice', function() {
|
|||
];
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
this.CardChoice = TestUtils.renderIntoDocument(
|
||||
<CardChoice cards={cardListData} />
|
||||
);
|
||||
|
@ -129,8 +125,6 @@ describe('Single Card Choice', function() {
|
|||
];
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
this.CardChoice = TestUtils.renderIntoDocument(
|
||||
<CardChoice cards={cardListData} />
|
||||
);
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
'use strict';
|
||||
import React, { findDOMNode } from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
import * as helpers from './helpers';
|
||||
|
||||
var CardForm = require('components/card-form');
|
||||
import CardForm from 'components/card-form';
|
||||
|
||||
var helpers = require('./helpers');
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
|
||||
describe('Card Details', function() {
|
||||
|
||||
|
@ -20,8 +18,6 @@ describe('Card Details', function() {
|
|||
];
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
this.CardForm = TestUtils.renderIntoDocument(
|
||||
<CardForm data-token="whatever" id="something"/>
|
||||
);
|
||||
|
@ -45,12 +41,12 @@ describe('Card Details', function() {
|
|||
});
|
||||
|
||||
it('renders a token', function() {
|
||||
var formNode = this.CardForm.getDOMNode();
|
||||
var formNode = findDOMNode(this.CardForm);
|
||||
assert.equal(formNode.getAttribute('data-token'), 'whatever');
|
||||
});
|
||||
|
||||
it('renders an id', function() {
|
||||
var formNode = this.CardForm.getDOMNode();
|
||||
var formNode = findDOMNode(this.CardForm);
|
||||
assert.equal(formNode.getAttribute('id'), 'something');
|
||||
});
|
||||
|
||||
|
@ -100,7 +96,7 @@ describe('Card Details', function() {
|
|||
it('should not have a name attr on any input', function() {
|
||||
var inputs = helpers.findAllByTag(this.CardForm, 'input');
|
||||
for (var i = 0; i < inputs.length; i += 1) {
|
||||
var input = inputs[i].getDOMNode();
|
||||
var input = findDOMNode(inputs[i]);
|
||||
if (input.getAttribute('name') !== null) {
|
||||
throw new Error('A name attr should not be set on any cc form fields');
|
||||
}
|
||||
|
@ -110,7 +106,7 @@ describe('Card Details', function() {
|
|||
it('should have type=tel and autocomplete=off on all fields', function() {
|
||||
var inputs = helpers.findAllByTag(this.CardForm, 'input');
|
||||
for (var i = 0; i < inputs.length; i += 1) {
|
||||
var input = inputs[i].getDOMNode();
|
||||
var input = findDOMNode(inputs[i]);
|
||||
assert.equal(input.getAttribute('autocomplete'),
|
||||
'off', 'autocomplete attr should be "off"');
|
||||
assert.equal(input.getAttribute('type'),
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
|
||||
var CardIcon = require('components/card-icon');
|
||||
import React, { findDOMNode } from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
import CardIcon from 'components/card-icon';
|
||||
|
||||
describe('Card Icon', function() {
|
||||
|
||||
var cardIcon;
|
||||
var cards = [
|
||||
'amex',
|
||||
'discover',
|
||||
|
@ -18,18 +14,12 @@ describe('Card Icon', function() {
|
|||
'visa',
|
||||
];
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
cardIcon = TestUtils.renderIntoDocument(
|
||||
<CardIcon cardType="american-express" />
|
||||
);
|
||||
});
|
||||
|
||||
function testCard(cardType) {
|
||||
return function() {
|
||||
cardIcon.setProps({'cardType': cardType});
|
||||
var cardIconNode = cardIcon.getDOMNode();
|
||||
var CardIcon_ = TestUtils.renderIntoDocument(
|
||||
<CardIcon cardType={cardType} />
|
||||
);
|
||||
var cardIconNode = findDOMNode(CardIcon_);
|
||||
assert.include(
|
||||
cardIconNode.getAttribute('class'), 'cctype-' + cardType);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
|
||||
var CardChoice = require('components/card-choice');
|
||||
var CardListing = require('views/card-listing');
|
||||
import CardChoice from 'components/card-choice';
|
||||
import CardListing from 'views/card-listing';
|
||||
|
||||
|
||||
describe('CardListingView', function() {
|
||||
|
@ -14,8 +12,6 @@ describe('CardListingView', function() {
|
|||
var savedVisa = {provider_id: '3vr3ym', type_name: 'Visa'};
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
|
||||
payWithNewCardSpy = sinon.spy();
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
import React, { findDOMNode } from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
import * as helpers from './helpers';
|
||||
|
||||
var CompletePayment = require('views/complete-payment');
|
||||
|
||||
var helpers = require('./helpers');
|
||||
import CompletePayment from 'views/complete-payment';
|
||||
|
||||
|
||||
describe('CompletePayment', function() {
|
||||
|
@ -18,8 +16,6 @@ describe('CompletePayment', function() {
|
|||
postMessage: this.sandbox.stub(),
|
||||
},
|
||||
};
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
this.CompletePayment = TestUtils.renderIntoDocument(
|
||||
<CompletePayment productId='mozilla-concrete-brick'
|
||||
userEmail={this.email}
|
||||
|
@ -33,7 +29,7 @@ describe('CompletePayment', function() {
|
|||
|
||||
it('should show where the receipt was emailed', function() {
|
||||
var email = helpers.findByClass(this.CompletePayment, 'email');
|
||||
assert.equal(email.getDOMNode().textContent, this.email);
|
||||
assert.equal(findDOMNode(email).textContent, this.email);
|
||||
});
|
||||
|
||||
it('should fire handleClick when OK button is clicked', function() {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
var appActions = require('actions/app');
|
||||
var purchaseActions = require('actions/purchase');
|
||||
var dataStore = require('stores');
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
import * as appActions from 'actions/app';
|
||||
import * as purchaseActions from 'actions/purchase';
|
||||
import * as dataStore from 'stores';
|
||||
|
||||
|
||||
describe('app', function() {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
var ghPagesConfig = require('../tasks/gh-pages');
|
||||
import ghPagesConfig from '../tasks/gh-pages';
|
||||
|
||||
describe('grunt-gh-pages config', function() {
|
||||
it('Docker builds should be silent=true', function() {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
|
||||
var Login = require('views/login');
|
||||
import Login from 'views/login';
|
||||
|
||||
|
||||
describe('Login', function() {
|
||||
|
@ -11,11 +9,6 @@ describe('Login', function() {
|
|||
var accessToken = 'some-oauth-access-token';
|
||||
var signInSpy = sinon.spy();
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
});
|
||||
|
||||
function mountView() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
<Login accessToken={accessToken} signIn={signInSpy} />
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
import React, { findDOMNode } from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React = require('react');
|
||||
var TestUtils = require('react/lib/ReactTestUtils');
|
||||
import Modal from 'components/modal';
|
||||
|
||||
var Modal = require('components/modal');
|
||||
|
||||
var helpers = require('./helpers');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
|
||||
describe('Modal', function() {
|
||||
|
@ -28,17 +26,17 @@ describe('Modal', function() {
|
|||
|
||||
it('should have a title', function() {
|
||||
var title = helpers.findByTag(this.Modal, 'h2');
|
||||
assert.equal(title.getDOMNode().firstChild.nodeValue, 'whatever');
|
||||
assert.equal(findDOMNode(title).firstChild.nodeValue, 'whatever');
|
||||
});
|
||||
|
||||
it('should have child content', function() {
|
||||
var content = helpers.findByClass(this.Modal, 'm-content');
|
||||
assert.equal(content.getDOMNode().nodeName.toLowerCase(), 'div');
|
||||
assert.equal(findDOMNode(content).nodeName.toLowerCase(), 'div');
|
||||
});
|
||||
|
||||
it('should have child paragraph', function() {
|
||||
var text = helpers.findByClass(this.Modal, 'm-text');
|
||||
assert.equal(text.getDOMNode().firstChild.nodeValue,
|
||||
assert.equal(findDOMNode(text).firstChild.nodeValue,
|
||||
'Just some noddy content');
|
||||
});
|
||||
|
||||
|
@ -52,7 +50,7 @@ describe('Modal', function() {
|
|||
|
||||
it('should call close func when clicking the close link', function() {
|
||||
var close = helpers.findByClass(this.Modal, 'close');
|
||||
TestUtils.Simulate.click(close.getDOMNode(), this.eventStub);
|
||||
TestUtils.Simulate.click(findDOMNode(close), this.eventStub);
|
||||
assert.ok(this.eventStub.preventDefault.called);
|
||||
assert.ok(this.eventStub.stopPropagation.called);
|
||||
assert.ok(this.closeFunc.called);
|
||||
|
@ -60,7 +58,7 @@ describe('Modal', function() {
|
|||
|
||||
it('should not call close func when clicking other content.', function() {
|
||||
var otherLink = helpers.findByClass(this.Modal, 'other-link');
|
||||
TestUtils.Simulate.click(otherLink.getDOMNode(), this.eventStub);
|
||||
TestUtils.Simulate.click(findDOMNode(otherLink), this.eventStub);
|
||||
assert.notOk(this.eventStub.preventDefault.called);
|
||||
assert.notOk(this.eventStub.stopPropagation.called);
|
||||
assert.notOk(this.closeFunc.called);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React = require('react');
|
||||
var TestUtils;
|
||||
var rewire = require('rewire');
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
import * as appActions from 'actions/app';
|
||||
import { create as reduxCreate } from 'redux-config';
|
||||
import ErrorMessage from 'components/error';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
var appActions = require('actions/app');
|
||||
var reduxConfig = require('redux-config');
|
||||
var ErrorMessage = require('components/error');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
var helpers = require('./helpers');
|
||||
import PaymentApp from 'apps/payment/app';
|
||||
|
||||
describe('App', function() {
|
||||
|
||||
describe('Payment App', function() {
|
||||
|
||||
var accessToken = 'some-oauth-token';
|
||||
var productId = 'mozilla-concrete-brick';
|
||||
|
@ -20,35 +20,31 @@ describe('App', function() {
|
|||
var redux;
|
||||
|
||||
beforeEach(function() {
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
redux = reduxConfig.create();
|
||||
redux = reduxCreate();
|
||||
});
|
||||
|
||||
function mountView() {
|
||||
var FluxContainer = helpers.getFluxContainer(redux);
|
||||
|
||||
var app = rewire('apps/payment/app');
|
||||
app.__set__({
|
||||
'Login': FakeLogin,
|
||||
'Purchase': FakePurchase,
|
||||
'window': {
|
||||
'location': {
|
||||
'href': ('http://pay.dev/?access_token=' + accessToken +
|
||||
'&product=' + productId),
|
||||
},
|
||||
var fakeWin = {
|
||||
'location': {
|
||||
'href': ('http://pay.dev/?access_token=' + accessToken +
|
||||
'&product=' + productId),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
var App = app.component;
|
||||
var container = TestUtils.renderIntoDocument(
|
||||
<FluxContainer>
|
||||
{function() {
|
||||
return <App />;
|
||||
return (
|
||||
<PaymentApp
|
||||
Login={FakeLogin} Purchase={FakePurchase} win={fakeWin} />
|
||||
);
|
||||
}}
|
||||
</FluxContainer>
|
||||
);
|
||||
return TestUtils.findRenderedComponentWithType(
|
||||
container, App
|
||||
container, PaymentApp
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
'use strict';
|
||||
import products from 'products';
|
||||
|
||||
|
||||
var products = require('products');
|
||||
|
||||
describe('products', function() {
|
||||
|
||||
it('should have concrete brick data', function() {
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
|
||||
var ProductDetail = require('components/product-detail');
|
||||
|
||||
var helpers = require('./helpers');
|
||||
import ProductDetail from 'components/product-detail';
|
||||
import * as helpers from './helpers';
|
||||
|
||||
|
||||
describe('ProductDetail', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
this.ProductDetail = TestUtils.renderIntoDocument(
|
||||
<ProductDetail productId="mozilla-concrete-brick" />
|
||||
);
|
||||
|
@ -29,8 +24,6 @@ describe('ProductDetail', function() {
|
|||
describe('ProductDetail Exceptions', function() {
|
||||
|
||||
it('should throw error with invalid product', function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
assert.throws(function() {
|
||||
TestUtils.renderIntoDocument(
|
||||
<ProductDetail productId='not-real-product' />
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
'use strict';
|
||||
import React from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React;
|
||||
var TestUtils;
|
||||
var rewire = require('rewire');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
var reduxConfig = require('redux-config');
|
||||
var purchaseActions = require('actions/purchase');
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
import * as purchaseActions from 'actions/purchase';
|
||||
import { create as reduxCreate } from 'redux-config';
|
||||
|
||||
import Purchase from 'views/purchase';
|
||||
|
||||
var helpers = require('./helpers');
|
||||
|
||||
describe('Purchase', function() {
|
||||
|
||||
|
@ -23,26 +23,23 @@ describe('Purchase', function() {
|
|||
var redux;
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('react');
|
||||
TestUtils = require('react/lib/ReactTestUtils');
|
||||
redux = reduxConfig.create();
|
||||
redux = reduxCreate();
|
||||
});
|
||||
|
||||
function mountView(userOverrides) {
|
||||
var user = Object.assign({}, defaultUser, userOverrides);
|
||||
var FluxContainer = helpers.getFluxContainer(redux);
|
||||
|
||||
var Purchase = rewire('views/purchase');
|
||||
Purchase.__set__({
|
||||
'CompletePayment': FakeCompletePayment,
|
||||
'CardListing': FakeCardListing,
|
||||
'CardDetails': FakeCardDetails,
|
||||
});
|
||||
|
||||
var container = TestUtils.renderIntoDocument(
|
||||
<FluxContainer>
|
||||
{function() {
|
||||
return <Purchase user={user} productId={productId} />;
|
||||
return (
|
||||
<Purchase
|
||||
CardDetails={FakeCardDetails}
|
||||
CardListing={FakeCardListing}
|
||||
CompletePayment={FakeCompletePayment}
|
||||
user={user} productId={productId} />
|
||||
);
|
||||
}}
|
||||
</FluxContainer>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
'use strict';
|
||||
import React, { findDOMNode } from 'react';
|
||||
import TestUtils from 'react/lib/ReactTestUtils';
|
||||
|
||||
var React = require('react');
|
||||
var TestUtils = require('react/lib/ReactTestUtils');
|
||||
var helpers = require('./helpers');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
var Spinner = require('components/spinner');
|
||||
import Spinner from 'components/spinner';
|
||||
|
||||
|
||||
describe('Spinner', function() {
|
||||
|
@ -12,13 +11,13 @@ describe('Spinner', function() {
|
|||
it('Uses loading as the default text', function() {
|
||||
var Spinner_ = TestUtils.renderIntoDocument(<Spinner />);
|
||||
var textNode = helpers.findByClass(Spinner_, 'text');
|
||||
assert.equal(textNode.getDOMNode().firstChild.nodeValue, 'Loading');
|
||||
assert.equal(findDOMNode(textNode).firstChild.nodeValue, 'Loading');
|
||||
});
|
||||
|
||||
it('Uses custom text as supplied', function() {
|
||||
var Spinner_ = TestUtils.renderIntoDocument(<Spinner text="whatever" />);
|
||||
var textNode = helpers.findByClass(Spinner_, 'text');
|
||||
assert.equal(textNode.getDOMNode().firstChild.nodeValue, 'whatever');
|
||||
assert.equal(findDOMNode(textNode).firstChild.nodeValue, 'whatever');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
var rewire = require('rewire');
|
||||
var tracking = rewire('tracking');
|
||||
|
||||
// Get at the top level var in the tracking module
|
||||
// using rewire.
|
||||
var Tracking = tracking.__get__('Tracking');
|
||||
import { Tracking } from 'tracking';
|
||||
|
||||
|
||||
describe('Tracking uninitialized', function() {
|
||||
|
@ -42,9 +35,9 @@ describe('Tracking functions', function() {
|
|||
});
|
||||
|
||||
it('should throw if page not set', function() {
|
||||
assert.throws(function() {
|
||||
assert.throws(() => {
|
||||
this.t.setPage();
|
||||
}.bind(this), Error, /page is required/);
|
||||
}, Error, /page is required/);
|
||||
});
|
||||
|
||||
it('should call ga', function() {
|
||||
|
@ -53,17 +46,17 @@ describe('Tracking functions', function() {
|
|||
});
|
||||
|
||||
it('should throw if category not set', function() {
|
||||
assert.throws(function() {
|
||||
assert.throws(() => {
|
||||
this.t.sendEvent({});
|
||||
}.bind(this), Error, /opts\.category is required/);
|
||||
}, Error, /opts\.category is required/);
|
||||
});
|
||||
|
||||
it('should throw if action not set', function() {
|
||||
assert.throws(function() {
|
||||
assert.throws(() => {
|
||||
this.t.sendEvent({
|
||||
category: 'whatever',
|
||||
});
|
||||
}.bind(this), Error, /opts\.action is required/);
|
||||
}, Error, /opts\.action is required/);
|
||||
});
|
||||
|
||||
it('should call _ga', function() {
|
||||
|
|
|
@ -1,25 +1,16 @@
|
|||
'use strict';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
import * as appActions from 'actions/app';
|
||||
import * as userActions from 'actions/user';
|
||||
|
||||
var rewire = require('rewire');
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
var appActions = require('actions/app');
|
||||
|
||||
var helpers = require('./helpers');
|
||||
import * as helpers from './helpers';
|
||||
|
||||
|
||||
describe('userActions', function() {
|
||||
|
||||
var dispatchSpy;
|
||||
var userActions;
|
||||
|
||||
beforeEach(function() {
|
||||
dispatchSpy = sinon.spy();
|
||||
userActions = rewire('actions/user');
|
||||
userActions.__set__({
|
||||
// Replace with a non-functioning stub by default until overidden.
|
||||
'$': {},
|
||||
});
|
||||
});
|
||||
|
||||
function fakeSignInResult() {
|
||||
|
@ -38,45 +29,47 @@ describe('userActions', function() {
|
|||
opt.jqueryOpt.returnedData = opt.data;
|
||||
|
||||
var jquery = helpers.fakeJquery(opt.jqueryOpt);
|
||||
userActions.__set__('$', jquery.stub);
|
||||
|
||||
return opt.data;
|
||||
return {
|
||||
data: opt.data,
|
||||
jquery: jquery.stub,
|
||||
};
|
||||
}
|
||||
|
||||
it('should dispatch sign-in action', function() {
|
||||
setApiSignInResult();
|
||||
var setup = setApiSignInResult();
|
||||
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', setup.jquery)(dispatchSpy);
|
||||
|
||||
var action = dispatchSpy.firstCall.args[0];
|
||||
assert.equal(action.type, actionTypes.USER_SIGNED_IN);
|
||||
});
|
||||
|
||||
it('should set email from sign-in', function() {
|
||||
var data = setApiSignInResult();
|
||||
var setup = setApiSignInResult();
|
||||
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', setup.jquery)(dispatchSpy);
|
||||
|
||||
var action = dispatchSpy.firstCall.args[0];
|
||||
assert.equal(action.user.email, data.buyer_email);
|
||||
assert.equal(action.user.email, setup.data.buyer_email);
|
||||
});
|
||||
|
||||
it('should set saved payment methods from sign-in', function() {
|
||||
var data = fakeSignInResult();
|
||||
var payMethods = [{'provider_id': '3vr3ym'}];
|
||||
data.payment_methods = payMethods;
|
||||
setApiSignInResult({data: data});
|
||||
var setup = setApiSignInResult({data: data});
|
||||
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', setup.jquery)(dispatchSpy);
|
||||
|
||||
var action = dispatchSpy.firstCall.args[0];
|
||||
assert.equal(action.user.payment_methods, payMethods);
|
||||
});
|
||||
|
||||
it('should set empty payment methods', function() {
|
||||
setApiSignInResult();
|
||||
var setup = setApiSignInResult();
|
||||
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', setup.jquery)(dispatchSpy);
|
||||
|
||||
var action = dispatchSpy.firstCall.args[0];
|
||||
assert.deepEqual(action.user.payment_methods, []);
|
||||
|
@ -84,8 +77,7 @@ describe('userActions', function() {
|
|||
|
||||
it('should sign-in with access token', function() {
|
||||
var jquery = helpers.fakeJquery({returnedData: fakeSignInResult()});
|
||||
userActions.__set__('$', jquery.stub);
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', jquery.stub)(dispatchSpy);
|
||||
|
||||
assert.equal(jquery.ajaxSpy.firstCall.args[0].data.access_token,
|
||||
'access-token');
|
||||
|
@ -94,17 +86,16 @@ describe('userActions', function() {
|
|||
it('should configure CSRF headers on sign-in', function() {
|
||||
var data = fakeSignInResult();
|
||||
var jquery = helpers.fakeJquery({returnedData: data});
|
||||
userActions.__set__('$', jquery.stub);
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', jquery.stub)(dispatchSpy);
|
||||
|
||||
assert.deepEqual(jquery.ajaxSetupSpy.firstCall.args[0].headers,
|
||||
{'X-CSRFToken': data.csrf_token});
|
||||
});
|
||||
|
||||
it('should set app error on failure', function() {
|
||||
setApiSignInResult({jqueryOpt: {result: 'fail'}});
|
||||
var setup = setApiSignInResult({jqueryOpt: {result: 'fail'}});
|
||||
|
||||
userActions.signIn('access-token')(dispatchSpy);
|
||||
userActions.signIn('access-token', setup.jquery)(dispatchSpy);
|
||||
|
||||
var action = dispatchSpy.firstCall.args[0];
|
||||
assert.deepEqual(action, appActions.error('user login failed'));
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
var utils = require('utils');
|
||||
import * as utils from 'utils';
|
||||
|
||||
|
||||
describe('utils.parseQuery', function() {
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = {
|
|||
test: /\.jsx?$/,
|
||||
// es7.objectRestSpread to enable ES7 rest spread operators
|
||||
// eg: let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
|
||||
loaders: ['babel?optional[]=es7.objectRestSpread&stage=2'],
|
||||
loaders: ['babel?optional[]=es7.objectRestSpread&optional[]=es7.classProperties&stage=2'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче