This commit is contained in:
Stuart Colville 2016-07-11 13:04:15 +01:00
Родитель ee61073045
Коммит 1a52291801
19 изменённых файлов: 267 добавлений и 100 удалений

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

@ -29,8 +29,9 @@ Generic scripts that don't need env vars. Use these for development:
| Script | Description | | Script | Description |
|------------------------|-----------------------------------------------------| |------------------------|-----------------------------------------------------|
| npm run dev:search | Starts the dev server (search app) | | npm run dev:amo | Starts the dev server (amo) |
| npm run dev:disco | Starts the dev server (discovery pane) | | npm run dev:disco | Starts the dev server (discovery pane) |
| npm run dev:search | Starts the dev server (search app) |
| npm run eslint | Lints the JS | | npm run eslint | Lints the JS |
| npm run stylelint | Lints the SCSS | | npm run stylelint | Lints the SCSS |
| npm run lint | Runs all the JS + SCSS linters | | npm run lint | Runs all the JS + SCSS linters |

3
config/default-amo.js Normal file
Просмотреть файл

@ -0,0 +1,3 @@
module.exports = {
};

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

@ -8,6 +8,7 @@ const path = require('path');
const appName = process.env.NODE_APP_INSTANCE || null; const appName = process.env.NODE_APP_INSTANCE || null;
const validAppNames = [ const validAppNames = [
'amo',
'disco', 'disco',
'search', 'search',
]; ];

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

@ -8,6 +8,7 @@
"build": "bin/build-checks.js && better-npm-run build", "build": "bin/build-checks.js && better-npm-run build",
"extract-locales": "better-npm-run extract-locales", "extract-locales": "better-npm-run extract-locales",
"clean": "rimraf './dist/*+(css|js|map|json)' './webpack-assets.json'", "clean": "rimraf './dist/*+(css|js|map|json)' './webpack-assets.json'",
"dev:amo": "better-npm-run dev:amo",
"dev:disco": "better-npm-run dev:disco", "dev:disco": "better-npm-run dev:disco",
"dev:search": "better-npm-run dev:search", "dev:search": "better-npm-run dev:search",
"eslint": "eslint .", "eslint": "eslint .",
@ -28,6 +29,12 @@
"NODE_PATH": "./:./src" "NODE_PATH": "./:./src"
} }
}, },
"dev:amo": {
"command": "better-npm-run start-dev",
"env": {
"NODE_APP_INSTANCE": "amo"
}
},
"dev:disco": { "dev:disco": {
"command": "better-npm-run start-dev", "command": "better-npm-run start-dev",
"env": { "env": {
@ -56,7 +63,7 @@
} }
}, },
"servertest": { "servertest": {
"command": "mocha --compilers js:babel-register --timeout 10000 tests/server/", "command": "mocha --compilers js:babel-register --timeout 10000 --recursive tests/server/",
"env": { "env": {
"NODE_PATH": "./:./src", "NODE_PATH": "./:./src",
"NODE_ENV": "production" "NODE_ENV": "production"

8
src/amo/client.js Normal file
Просмотреть файл

@ -0,0 +1,8 @@
import makeClient from 'core/client/base';
import routes from './routes';
import createStore from './store';
// Initialize the tracking.
import 'core/tracking';
makeClient(routes, createStore);

27
src/amo/containers/App.js Normal file
Просмотреть файл

@ -0,0 +1,27 @@
import React, { PropTypes } from 'react';
import Helmet from 'react-helmet';
import 'amo/css/App.scss';
import translate from 'core/i18n/translate';
export class App extends React.Component {
static propTypes = {
children: PropTypes.node,
i18n: PropTypes.object.isRequired,
}
render() {
const { children, i18n } = this.props;
return (
<div className="amo">
<Helmet
defaultTitle={i18n.gettext('Add-ons for Firefox')}
/>
{children}
</div>
);
}
}
export default translate({ withRef: true })(App);

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

@ -0,0 +1,11 @@
import React from 'react';
export default class Home extends React.Component {
render() {
return (
<div>
<h1>AMO Home Page Hello World</h1>
</div>
);
}
}

8
src/amo/css/App.scss Normal file
Просмотреть файл

@ -0,0 +1,8 @@
@import "~normalize.css";
@import "~core/css/inc/lib";
@import "~core/css/inc/mixins";
html,
body {
background: red;
}

12
src/amo/routes.js Normal file
Просмотреть файл

@ -0,0 +1,12 @@
import React from 'react';
import { IndexRoute, Route } from 'react-router';
import App from './containers/App';
import Home from './containers/Home';
export default (
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="/:lang/" component={Home} />
</Route>
);

14
src/amo/store.js Normal file
Просмотреть файл

@ -0,0 +1,14 @@
import { createStore as _createStore, combineReducers } from 'redux';
import { reducer as reduxAsyncConnect } from 'redux-async-connect';
import { middleware } from 'core/store';
import addons from 'core/reducers/addons';
import api from 'core/reducers/api';
export default function createStore(initialState = {}) {
return _createStore(
combineReducers({ addons, api, reduxAsyncConnect }),
initialState,
middleware(),
);
}

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

@ -0,0 +1,32 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import {
findRenderedComponentWithType,
renderIntoDocument,
} from 'react-addons-test-utils';
import App from 'amo/containers/App';
import I18nProvider from 'core/i18n/Provider';
import { getFakeI18nInst } from 'tests/client/helpers';
describe('App', () => {
it('renders its children', () => {
class MyComponent extends React.Component {
render() {
return <p>The component</p>;
}
}
const root = findRenderedComponentWithType(renderIntoDocument(
<I18nProvider i18n={getFakeI18nInst()}>
<App>
<MyComponent />
</App>
</I18nProvider>), App).getWrappedInstance();
const rootNode = findDOMNode(root);
assert.equal(rootNode.tagName.toLowerCase(), 'div');
assert.equal(rootNode.querySelector('p').textContent, 'The component');
});
});

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

@ -0,0 +1,16 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import {
renderIntoDocument,
} from 'react-addons-test-utils';
import Home from 'amo/containers/Home';
describe('Home', () => {
it('renders a heading', () => {
const root = renderIntoDocument(<Home />);
const rootNode = findDOMNode(root);
assert.include(rootNode.querySelector('h1').textContent, 'AMO Home Page');
});
});

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

@ -0,0 +1,15 @@
import createStore from 'amo/store';
describe('search createStore', () => {
it('sets the reducers', () => {
const store = createStore();
assert.deepEqual(
Object.keys(store.getState()).sort(),
['addons', 'api', 'reduxAsyncConnect']);
});
it('creates an empty store', () => {
const store = createStore();
assert.deepEqual(store.getState().addons, {});
});
});

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

@ -20,7 +20,7 @@ const apiHosts = {
}; };
describe('CSP Config', () => { describe('CSP Config Defaults', () => {
afterEach(() => { afterEach(() => {
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
}); });
@ -85,46 +85,3 @@ describe('CSP Config', () => {
assert.deepEqual(cspConfig.mediaSrc, ["'none'"]); assert.deepEqual(cspConfig.mediaSrc, ["'none'"]);
}); });
}); });
describe('App Specific CSP Config', () => {
afterEach(() => {
process.env.NODE_ENV = 'production';
delete process.env.NODE_APP_INSTANCE;
});
it('should set style-src for disco', () => {
process.env.NODE_APP_INSTANCE = 'disco';
const config = requireUncached('config');
const cspConfig = config.get('CSP').directives;
assert.deepEqual(cspConfig.styleSrc, ['https://addons-discovery.cdn.mozilla.net']);
});
it('should set script-src for disco', () => {
process.env.NODE_APP_INSTANCE = 'disco';
const config = requireUncached('config');
const cspConfig = config.get('CSP').directives;
assert.deepEqual(cspConfig.scriptSrc, [
'https://addons-discovery.cdn.mozilla.net', 'https://www.google-analytics.com']);
});
it('should set media-src for disco', () => {
process.env.NODE_APP_INSTANCE = 'disco';
const config = requireUncached('config');
const cspConfig = config.get('CSP').directives;
assert.deepEqual(cspConfig.mediaSrc, ['https://addons-discovery.cdn.mozilla.net']);
});
it('should set img-src for disco', () => {
process.env.NODE_APP_INSTANCE = 'disco';
const config = requireUncached('config');
const cspConfig = config.get('CSP').directives;
assert.sameMembers(cspConfig.imgSrc, [
"'self'",
'data:',
'https://addons.cdn.mozilla.net',
'https://addons-discovery.cdn.mozilla.net',
'https://www.google-analytics.com',
]);
});
});

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

@ -1,53 +1,59 @@
import { getClientConfig } from 'core/utils'; import { getClientConfig } from 'core/utils';
import { assert } from 'chai'; import { assert } from 'chai';
import requireUncached from 'require-uncached'; import requireUncached from 'require-uncached';
import config from 'config';
const appsList = config.get('validAppNames');
describe('Config', () => { describe('Config', () => {
afterEach(() => { afterEach(() => {
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
delete process.env.NODE_APP_INSTANCE;
}); });
it('should not ever have disableSSR set to true', () => { for (const appName of appsList) {
const config = requireUncached('config'); it(`should not ever have disableSSR set to true for ${appName}`, () => {
assert.equal(config.get('disableSSR'), false); const conf = requireUncached('config');
}); assert.equal(conf.get('disableSSR'), false);
});
it('should provide a production config by default', () => { it(`should provide a production conf by default for ${appName}`, () => {
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
const config = requireUncached('config'); const conf = requireUncached('config');
const clientConfig = getClientConfig(config); const clientConfig = getClientConfig(conf);
assert.equal(config.get('apiHost'), 'https://addons.mozilla.org'); assert.equal(conf.get('apiHost'), 'https://addons.mozilla.org');
assert.equal(clientConfig.apiHost, 'https://addons.mozilla.org'); assert.equal(clientConfig.apiHost, 'https://addons.mozilla.org');
assert.equal(config.util.getEnv('NODE_ENV'), 'production'); assert.equal(conf.util.getEnv('NODE_ENV'), 'production');
}); });
it('should provide a dev config', () => { it(`should provide a dev conf for ${appName}`, () => {
process.env.NODE_ENV = 'dev'; process.env.NODE_ENV = 'dev';
const config = requireUncached('config'); const conf = requireUncached('config');
const clientConfig = getClientConfig(config); const clientConfig = getClientConfig(conf);
assert.equal(config.get('apiHost'), 'https://addons-dev.allizom.org'); assert.equal(conf.get('apiHost'), 'https://addons-dev.allizom.org');
assert.equal(clientConfig.apiHost, 'https://addons-dev.allizom.org'); assert.equal(clientConfig.apiHost, 'https://addons-dev.allizom.org');
assert.equal(config.util.getEnv('NODE_ENV'), 'dev'); assert.equal(conf.util.getEnv('NODE_ENV'), 'dev');
}); });
it('should provide a stage config', () => { it(`should provide a stage conf for ${appName}`, () => {
process.env.NODE_ENV = 'stage'; process.env.NODE_ENV = 'stage';
const config = requireUncached('config'); const conf = requireUncached('config');
const clientConfig = getClientConfig(config); const clientConfig = getClientConfig(conf);
assert.equal(config.get('apiHost'), 'https://addons.allizom.org'); assert.equal(conf.get('apiHost'), 'https://addons.allizom.org');
assert.equal(clientConfig.apiHost, 'https://addons.allizom.org'); assert.equal(clientConfig.apiHost, 'https://addons.allizom.org');
assert.equal(config.util.getEnv('NODE_ENV'), 'stage'); assert.equal(conf.util.getEnv('NODE_ENV'), 'stage');
}); });
it('should provide a development config', () => { it(`should provide a development conf for ${appName}`, () => {
process.env.NODE_ENV = 'development'; process.env.NODE_ENV = 'development';
const config = requireUncached('config'); const conf = requireUncached('config');
const clientConfig = getClientConfig(config); const clientConfig = getClientConfig(conf);
assert.equal(config.get('apiHost'), 'https://addons-dev.allizom.org'); assert.equal(conf.get('apiHost'), 'https://addons-dev.allizom.org');
assert.equal(clientConfig.apiHost, 'https://addons-dev.allizom.org'); assert.equal(clientConfig.apiHost, 'https://addons-dev.allizom.org');
assert.equal(config.util.getEnv('NODE_ENV'), 'development'); assert.equal(conf.util.getEnv('NODE_ENV'), 'development');
}); });
}
}); });
@ -58,18 +64,18 @@ describe('Config Environment Variables', () => {
}); });
it('should allow host overrides', () => { it('should allow host overrides', () => {
let config = requireUncached('config'); let conf = requireUncached('config');
assert.equal(config.get('serverHost'), '127.0.0.1', 'initial host is set'); assert.equal(conf.get('serverHost'), '127.0.0.1', 'initial host is set');
process.env.SERVER_HOST = '0.0.0.0'; process.env.SERVER_HOST = '0.0.0.0';
config = requireUncached('config'); conf = requireUncached('config');
assert.equal(config.get('serverHost'), '0.0.0.0', 'host is overidden'); assert.equal(conf.get('serverHost'), '0.0.0.0', 'host is overidden');
}); });
it('should allow port overrides', () => { it('should allow port overrides', () => {
let config = requireUncached('config'); let conf = requireUncached('config');
assert.equal(config.get('serverPort'), '4000', 'Initial port is set'); assert.equal(conf.get('serverPort'), '4000', 'Initial port is set');
process.env.SERVER_PORT = '5000'; process.env.SERVER_PORT = '5000';
config = requireUncached('config'); conf = requireUncached('config');
assert.equal(config.get('serverPort'), '5000', 'Port is overidden'); assert.equal(conf.get('serverPort'), '5000', 'Port is overidden');
}); });
}); });

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

@ -1,5 +1,8 @@
import { assert } from 'chai'; import { assert } from 'chai';
import requireUncached from 'require-uncached'; import requireUncached from 'require-uncached';
import config from 'config';
const appsList = config.get('validAppNames');
describe('App Specific Frameguard Config', () => { describe('App Specific Frameguard Config', () => {
afterEach(() => { afterEach(() => {
@ -7,10 +10,13 @@ describe('App Specific Frameguard Config', () => {
delete process.env.NODE_APP_INSTANCE; delete process.env.NODE_APP_INSTANCE;
}); });
it('should default frameGuard to "deny"', () => { for (const appName of appsList) {
const config = requireUncached('config'); it(`should default frameGuard to "deny" for ${appName} in production`, () => {
const frameGuardConfig = config.get('frameGuard'); process.env.NODE_APP_INSTANCE = appName;
assert.equal(frameGuardConfig.action, 'deny'); const conf = requireUncached('config');
assert.equal(frameGuardConfig.domain, undefined); const frameGuardConfig = conf.get('frameGuard');
}); assert.equal(frameGuardConfig.action, 'deny');
assert.equal(frameGuardConfig.domain, undefined);
});
}
}); });

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

@ -0,0 +1,43 @@
import { assert } from 'chai';
import requireUncached from 'require-uncached';
describe('Disco App Specific CSP Config', () => {
let config;
afterEach(() => {
process.env.NODE_ENV = 'production';
delete process.env.NODE_APP_INSTANCE;
});
beforeEach(() => {
process.env.NODE_APP_INSTANCE = 'disco';
config = requireUncached('config');
});
it('should set style-src for disco', () => {
const cspConfig = config.get('CSP').directives;
assert.deepEqual(cspConfig.styleSrc, ['https://addons-discovery.cdn.mozilla.net']);
});
it('should set script-src for disco', () => {
const cspConfig = config.get('CSP').directives;
assert.deepEqual(cspConfig.scriptSrc, [
'https://addons-discovery.cdn.mozilla.net', 'https://www.google-analytics.com']);
});
it('should set media-src for disco', () => {
const cspConfig = config.get('CSP').directives;
assert.deepEqual(cspConfig.mediaSrc, ['https://addons-discovery.cdn.mozilla.net']);
});
it('should set img-src for disco', () => {
const cspConfig = config.get('CSP').directives;
assert.sameMembers(cspConfig.imgSrc, [
"'self'",
'data:',
'https://addons.cdn.mozilla.net',
'https://addons-discovery.cdn.mozilla.net',
'https://www.google-analytics.com',
]);
});
});

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

@ -6,10 +6,10 @@ import { assert } from 'chai';
import { runServer } from 'core/server/base'; import { runServer } from 'core/server/base';
import Policy from 'csp-parse'; import Policy from 'csp-parse';
import { checkSRI } from './helpers'; import { checkSRI } from '../helpers';
describe('GET requests', () => { describe('Discovery Pane GET requests', () => {
let app; let app;
before(() => runServer({ listen: false, app: 'disco' }) before(() => runServer({ listen: false, app: 'disco' })

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

@ -6,9 +6,9 @@ import { assert } from 'chai';
import { runServer } from 'core/server/base'; import { runServer } from 'core/server/base';
import Policy from 'csp-parse'; import Policy from 'csp-parse';
import { checkSRI } from './helpers'; import { checkSRI } from '../helpers';
describe('GET requests', () => { describe('Seach App GET requests', () => {
let app; let app;
before(() => runServer({ listen: false, app: 'search' }) before(() => runServer({ listen: false, app: 'search' })