Merge pull request #227 from muffinresearch/replace-rewire

Replace rewire + convert to ES6+ (fixes #211, fixes #219, fixes #218)
This commit is contained in:
Stuart Colville 2015-07-24 18:00:54 +01:00
Родитель f7af61af32 16a5e7d254
Коммит 6b74403d43
69 изменённых файлов: 887 добавлений и 1063 удалений

Просмотреть файл

@ -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'],
},
],
},