Initial commit
This commit is contained in:
commit
c60d8ba706
4
.babelrc
Normal file
4
.babelrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"presets": ["react", "es2015"],
|
||||||
|
"plugins": ["transform-class-properties", "transform-object-assign", "transform-object-rest-spread"]
|
||||||
|
}
|
125
.eslintrc
Normal file
125
.eslintrc
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
|
||||||
|
parser: babel-eslint
|
||||||
|
|
||||||
|
plugins: [ "react" ]
|
||||||
|
|
||||||
|
# enable ECMAScript features
|
||||||
|
ecmaFeatures:
|
||||||
|
arrowFunctions: true
|
||||||
|
binaryLiterals: true
|
||||||
|
blockBindings: true
|
||||||
|
classes: true
|
||||||
|
defaultParams: true
|
||||||
|
destructuring: true
|
||||||
|
forOf: true
|
||||||
|
generators: true
|
||||||
|
jsx: true
|
||||||
|
modules: true
|
||||||
|
objectLiteralShorthandMethods: true
|
||||||
|
objectLiteralShorthandProperties: true
|
||||||
|
octalLiterals: true
|
||||||
|
spread: true
|
||||||
|
templateStrings: true
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# Possible Errors
|
||||||
|
# https://github.com/eslint/eslint/tree/master/docs/rules#possible-errors
|
||||||
|
no-control-regex: 2
|
||||||
|
no-debugger: 2
|
||||||
|
no-dupe-args: 2
|
||||||
|
no-dupe-keys: 2
|
||||||
|
no-duplicate-case: 2
|
||||||
|
no-empty-character-class: 2
|
||||||
|
no-ex-assign: 2
|
||||||
|
no-extra-boolean-cast : 2
|
||||||
|
no-extra-semi: 2
|
||||||
|
no-invalid-regexp: 2
|
||||||
|
no-irregular-whitespace: 2
|
||||||
|
no-proto: 2
|
||||||
|
no-unexpected-multiline: 2
|
||||||
|
no-unreachable: 2
|
||||||
|
valid-typeof: 2
|
||||||
|
|
||||||
|
# Best Practices
|
||||||
|
# https://github.com/eslint/eslint/tree/master/docs/rules#best-practices
|
||||||
|
no-fallthrough: 2
|
||||||
|
no-redeclare: 2
|
||||||
|
|
||||||
|
# Stylistic Issues
|
||||||
|
# https://github.com/eslint/eslint/tree/master/docs/rules#stylistic-issues
|
||||||
|
comma-spacing: 2
|
||||||
|
eol-last: 2
|
||||||
|
indent: [2, 2, {SwitchCase: 1}]
|
||||||
|
max-len: [2, 160, 2]
|
||||||
|
new-parens: 2
|
||||||
|
no-mixed-spaces-and-tabs: 2
|
||||||
|
no-multiple-empty-lines: [2, {max: 2}]
|
||||||
|
no-trailing-spaces: 2
|
||||||
|
quotes: [2, "single", "avoid-escape"]
|
||||||
|
semi: 2
|
||||||
|
space-after-keywords: 2
|
||||||
|
space-before-blocks: [2, "always"]
|
||||||
|
space-before-function-paren: [2, "never"]
|
||||||
|
space-in-parens: [2, "never"]
|
||||||
|
space-infix-ops: 2
|
||||||
|
space-return-throw-case: 2
|
||||||
|
space-unary-ops: 2
|
||||||
|
|
||||||
|
# ECMAScript 6
|
||||||
|
# http://eslint.org/docs/rules/#ecmascript-6
|
||||||
|
arrow-parens: [2, "always"]
|
||||||
|
arrow-spacing: [2, {"before": true, "after": true}]
|
||||||
|
no-arrow-condition: 2
|
||||||
|
prefer-const: 2
|
||||||
|
|
||||||
|
# Strict Mode
|
||||||
|
# https://github.com/eslint/eslint/tree/master/docs/rules#strict-mode
|
||||||
|
strict: [2, "global"]
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
# https://github.com/eslint/eslint/tree/master/docs/rules#variables
|
||||||
|
no-undef: 2
|
||||||
|
no-unused-vars: [2, {"args": "none"}]
|
||||||
|
|
||||||
|
|
||||||
|
react/forbid-prop-types: 1
|
||||||
|
react/jsx-boolean-value: 1
|
||||||
|
react/jsx-closing-bracket-location: 1
|
||||||
|
react/jsx-curly-spacing: 1
|
||||||
|
react/jsx-equals-spacing: 1
|
||||||
|
react/jsx-handler-names: 1
|
||||||
|
react/jsx-indent-props: 1
|
||||||
|
react/jsx-indent: [2, 2]
|
||||||
|
react/jsx-no-bind: 1
|
||||||
|
react/jsx-no-duplicate-props: 1
|
||||||
|
react/jsx-no-undef: 1
|
||||||
|
react/jsx-pascal-case: 1
|
||||||
|
react/jsx-sort-prop-types: 1
|
||||||
|
react/jsx-uses-react: 1
|
||||||
|
react/jsx-uses-vars: 1
|
||||||
|
react/no-danger: 1
|
||||||
|
react/no-deprecated: 1
|
||||||
|
react/no-did-mount-set-state: 1
|
||||||
|
react/no-did-update-set-state: 1
|
||||||
|
react/no-direct-mutation-state: 1
|
||||||
|
react/no-is-mounted: 1
|
||||||
|
react/no-multi-comp: 1
|
||||||
|
react/no-string-refs: 1
|
||||||
|
react/no-unknown-property: 1
|
||||||
|
react/prefer-es6-class: 1
|
||||||
|
react/react-in-jsx-scope: 1
|
||||||
|
react/require-extension: 1
|
||||||
|
react/self-closing-comp: 1
|
||||||
|
react/sort-comp: 1
|
||||||
|
|
||||||
|
# Global scoped method and vars
|
||||||
|
globals:
|
||||||
|
netlify: true
|
||||||
|
describe: true
|
||||||
|
it: true
|
||||||
|
require: true
|
||||||
|
process: true
|
||||||
|
module: true
|
||||||
|
CMS_ENV: true
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
56
example/config.yml
Normal file
56
example/config.yml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
backend:
|
||||||
|
name: test-repo
|
||||||
|
delay: 0.1
|
||||||
|
|
||||||
|
media_folder: "assets/uploads"
|
||||||
|
|
||||||
|
collections: # A list of collections the CMS should be able to edit
|
||||||
|
- name: "posts" # Used in routes, ie.: /admin/collections/:slug/edit
|
||||||
|
label: "Post" # Used in the UI, ie.: "New Post"
|
||||||
|
folder: "_posts"
|
||||||
|
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
|
||||||
|
create: true # Allow users to create new documents in this collection
|
||||||
|
fields: # The fields each document in this collection have
|
||||||
|
- {label: "Title", name: "title", widget: "string", tagname: "h1"}
|
||||||
|
- {label: "Cover Image", name: "image", widget: "image", optional: true, tagname: ""}
|
||||||
|
- {label: "Body", name: "body", widget: "markdown"}
|
||||||
|
meta:
|
||||||
|
- {label: "Publish Date", name: "date", widget: "datetime", format: "YYYY-MM-DD hh:mma"}
|
||||||
|
- {label: "SEO Description", name: "description", widget: "text"}
|
||||||
|
|
||||||
|
- name: "faq" # Used in routes, ie.: /admin/collections/:slug/edit
|
||||||
|
label: "FAQ" # Used in the UI, ie.: "New Post"
|
||||||
|
folder: "_faqs"
|
||||||
|
create: true # Allow users to create new documents in this collection
|
||||||
|
fields: # The fields each document in this collection have
|
||||||
|
- {label: "Question", name: "title", widget: "string", tagname: "h1"}
|
||||||
|
- {label: "Answer", name: "body", widget: "markdown"}
|
||||||
|
|
||||||
|
- name: "settings"
|
||||||
|
label: "Settings"
|
||||||
|
files:
|
||||||
|
- name: "general"
|
||||||
|
label: "Site Settings"
|
||||||
|
file: "_data/settings.json"
|
||||||
|
description: "General Site Settings"
|
||||||
|
fields:
|
||||||
|
- {label: "Global title", name: site_title, widget: "string"}
|
||||||
|
- label: "Post Settings"
|
||||||
|
name: posts
|
||||||
|
widget: "object"
|
||||||
|
fields:
|
||||||
|
- {label: "Number of posts on frontpage", name: front_limit, widget: number}
|
||||||
|
- {label: "Default Author", name: author, widget: string}
|
||||||
|
- {label: "Default Thumbnail", name: thumb, widget: image, class: "thumb"}
|
||||||
|
|
||||||
|
- name: "authors"
|
||||||
|
label: "Authors"
|
||||||
|
file: "_data/authors.yml"
|
||||||
|
description: "Author descriptions"
|
||||||
|
fields:
|
||||||
|
- name: authors
|
||||||
|
label: Authors
|
||||||
|
widget: list
|
||||||
|
fields:
|
||||||
|
- {label: "Name", name: "name", widget: "string"}
|
||||||
|
- {label: "Description", name: "description", widget: "markdown"}
|
10
example/index.html
Normal file
10
example/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>This is an example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script src='/cms.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
57
package.json
Normal file
57
package.json
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "netlify-react-cms",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Netlify CMS lets content editors work on structured content stored in git",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack-dev-server -d --inline --config webpack.config.js",
|
||||||
|
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require ./test/setup.js",
|
||||||
|
"test:watch": "npm test -- --watch"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"netlify",
|
||||||
|
"cms"
|
||||||
|
],
|
||||||
|
"author": "Netlify",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^6.3.3",
|
||||||
|
"babel-core": "^6.5.1",
|
||||||
|
"babel-eslint": "^4.1.8",
|
||||||
|
"babel-loader": "^6.2.2",
|
||||||
|
"babel-plugin-transform-class-properties": "^6.5.2",
|
||||||
|
"babel-plugin-transform-object-assign": "^6.5.0",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.5.0",
|
||||||
|
"babel-preset-es2015": "^6.5.0",
|
||||||
|
"babel-preset-react": "^6.5.0",
|
||||||
|
"babel-register": "^6.5.2",
|
||||||
|
"babel-runtime": "^6.5.0",
|
||||||
|
"eslint": "^1.10.3",
|
||||||
|
"eslint-loader": "^1.2.1",
|
||||||
|
"eslint-plugin-react": "^3.16.1",
|
||||||
|
"exports-loader": "^0.6.3",
|
||||||
|
"express": "^4.13.4",
|
||||||
|
"file-loader": "^0.8.5",
|
||||||
|
"immutable": "^3.7.6",
|
||||||
|
"imports-loader": "^0.6.5",
|
||||||
|
"js-yaml": "^3.5.3",
|
||||||
|
"mocha": "^2.4.5",
|
||||||
|
"normalizr": "^2.0.0",
|
||||||
|
"react": "^0.14.7",
|
||||||
|
"react-dom": "^0.14.7",
|
||||||
|
"react-immutable-proptypes": "^1.6.0",
|
||||||
|
"react-lazy-load": "^3.0.3",
|
||||||
|
"react-pure-render": "^1.0.2",
|
||||||
|
"react-redux": "^4.4.0",
|
||||||
|
"react-router": "^2.0.0",
|
||||||
|
"react-router-redux": "^3.0.0",
|
||||||
|
"redux": "^3.3.1",
|
||||||
|
"redux-thunk": "^1.0.3",
|
||||||
|
"style-loader": "^0.13.0",
|
||||||
|
"url-loader": "^0.5.7",
|
||||||
|
"webpack": "^1.12.13",
|
||||||
|
"webpack-dev-server": "^1.14.1",
|
||||||
|
"webpack-postcss-tools": "^1.1.1",
|
||||||
|
"whatwg-fetch": "^0.11.0"
|
||||||
|
}
|
||||||
|
}
|
60
src/actions/config.js
Normal file
60
src/actions/config.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
|
export const CONFIG = {
|
||||||
|
REQUEST: 'REQUEST',
|
||||||
|
SUCCESS: 'SUCCESS',
|
||||||
|
FAILURE: 'FAILURE'
|
||||||
|
};
|
||||||
|
|
||||||
|
export function configLoaded(config) {
|
||||||
|
return {
|
||||||
|
type: CONFIG.SUCCESS,
|
||||||
|
payload: config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configLoading() {
|
||||||
|
return {
|
||||||
|
type: CONFIG.REQUEST
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configFailed(err) {
|
||||||
|
return {
|
||||||
|
type: CONFIG.FAILURE,
|
||||||
|
error: 'Error loading config',
|
||||||
|
payload: err
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadConfig(config) {
|
||||||
|
if (window.CMS_CONFIG) {
|
||||||
|
return configLoaded(window.CMS_CONFIG);
|
||||||
|
}
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(configLoading());
|
||||||
|
|
||||||
|
fetch('config.yml').then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw `Failed to load config.yml (${response.status})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.text().then(parseConfig).then((config) => dispatch(configLoaded(config)));
|
||||||
|
}).catch((err) => {
|
||||||
|
dispatch(configFailed(err));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseConfig(data) {
|
||||||
|
const config = yaml.safeLoad(data);
|
||||||
|
|
||||||
|
if (typeof CMS_ENV === 'string' && config[CMS_ENV]) {
|
||||||
|
for (var key in config[CMS_ENV]) {
|
||||||
|
if (config[CMS_ENV].hasOwnProperty(key)) {
|
||||||
|
config[key] = config[CMS_ENV][key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
58
src/containers/App.js
Normal file
58
src/containers/App.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { loadConfig } from '../actions/config';
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(loadConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
configError(config) {
|
||||||
|
return <div>
|
||||||
|
<h1>Error loading the CMS configuration</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>The "config.yml" file could not be loaded or failed to parse properly.</p>
|
||||||
|
<p><strong>Error message:</strong> {config.get('error')}</p>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
configLoading() {
|
||||||
|
return <div>
|
||||||
|
<h1>Loading configuration...</h1>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { config, children } = this.props;
|
||||||
|
|
||||||
|
if (config === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.get('error')) {
|
||||||
|
return this.configError(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.get('isFetching')) {
|
||||||
|
return this.configLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>{children}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
config: state.config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(App);
|
26
src/containers/DashboardPage.js
Normal file
26
src/containers/DashboardPage.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
class DashboardPage extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { collections } = this.props;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
{collections && collections.map((collection) => (
|
||||||
|
<div key={collection.get('name')}>
|
||||||
|
<Link to={`/collections/${collection.get('name')}`}>{collection.get('name')}</Link>
|
||||||
|
</div>
|
||||||
|
)).toArray()}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
collections: state.collections
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(DashboardPage);
|
17
src/index.js
Normal file
17
src/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import configureStore from './store/configureStore';
|
||||||
|
import Routes from './routes/routes';
|
||||||
|
import 'file?name=index.html!../example/index.html';
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
|
||||||
|
const el = document.createElement('div');
|
||||||
|
document.body.appendChild(el);
|
||||||
|
|
||||||
|
render((
|
||||||
|
<Provider store={store}>
|
||||||
|
<Routes/>
|
||||||
|
</Provider>
|
||||||
|
), el);
|
16
src/reducers/collections.js
Normal file
16
src/reducers/collections.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Immutable from 'immutable';
|
||||||
|
import { CONFIG } from '../actions/config';
|
||||||
|
|
||||||
|
export function collections(state = null, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case CONFIG.SUCCESS:
|
||||||
|
const collections = action.payload && action.payload.collections;
|
||||||
|
return Immutable.OrderedMap().withMutations((map) => {
|
||||||
|
(collections || []).forEach(function(collection) {
|
||||||
|
map.set(collection.name, Immutable.fromJS(collection));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
15
src/reducers/config.js
Normal file
15
src/reducers/config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Immutable from 'immutable';
|
||||||
|
import { CONFIG } from '../actions/config';
|
||||||
|
|
||||||
|
export function config(state = null, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case CONFIG.REQUEST:
|
||||||
|
return Immutable.Map({isFetching: true});
|
||||||
|
case CONFIG.SUCCESS:
|
||||||
|
return Immutable.fromJS(action.payload);
|
||||||
|
case CONFIG.FAILURE:
|
||||||
|
return Immutable.Map({error: action.payload.toString()});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
12
src/routes/routes.js
Normal file
12
src/routes/routes.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
|
||||||
|
import App from '../containers/App';
|
||||||
|
import DashboardPage from '../containers/DashboardPage';
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<Router history={browserHistory}>
|
||||||
|
<Route path="/" component={App}>
|
||||||
|
<IndexRoute component={DashboardPage}/>
|
||||||
|
</Route>
|
||||||
|
</Router>
|
||||||
|
);
|
21
src/store/configureStore.js
Normal file
21
src/store/configureStore.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
|
||||||
|
import thunkMiddleware from 'redux-thunk';
|
||||||
|
import { browserHistory } from 'react-router';
|
||||||
|
import { syncHistory, routeReducer } from 'react-router-redux';
|
||||||
|
import { config } from '../reducers/config';
|
||||||
|
import { collections } from '../reducers/collections';
|
||||||
|
|
||||||
|
const reducer = combineReducers({
|
||||||
|
config,
|
||||||
|
collections,
|
||||||
|
router: routeReducer
|
||||||
|
});
|
||||||
|
|
||||||
|
const createStoreWithMiddleware = compose(
|
||||||
|
applyMiddleware(thunkMiddleware, syncHistory(browserHistory)),
|
||||||
|
window.devToolsExtension ? window.devToolsExtension() : (f) => f
|
||||||
|
)(createStore);
|
||||||
|
|
||||||
|
export default (initialState) => (
|
||||||
|
createStoreWithMiddleware(reducer, initialState)
|
||||||
|
);
|
26
test/reducers/collections.spec.js
Normal file
26
test/reducers/collections.spec.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import expect from 'expect';
|
||||||
|
import Immutable from 'immutable';
|
||||||
|
import { configLoaded } from '../../src/actions/config';
|
||||||
|
import { collections } from '../../src/reducers/collections';
|
||||||
|
|
||||||
|
describe('collections', () => {
|
||||||
|
it('should handle an empty state', () => {
|
||||||
|
expect(
|
||||||
|
collections(undefined, {})
|
||||||
|
).toEqual(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load the collections from the config', () => {
|
||||||
|
expect(
|
||||||
|
collections(undefined, configLoaded({collections: [
|
||||||
|
{name: 'posts', folder: '_posts', fields: [{name: 'title', widget: 'string'}]}
|
||||||
|
]}))
|
||||||
|
).toEqual(
|
||||||
|
Immutable.OrderedMap({
|
||||||
|
posts: Immutable.fromJS({name: 'posts', folder: '_posts', fields: [{name: 'title', widget: 'string'}]})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
38
test/reducers/config.spec.js
Normal file
38
test/reducers/config.spec.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import expect from 'expect';
|
||||||
|
import Immutable from 'immutable';
|
||||||
|
import { configLoaded, configLoading, configFailed } from '../../src/actions/config';
|
||||||
|
import { config } from '../../src/reducers/config';
|
||||||
|
|
||||||
|
describe('config', () => {
|
||||||
|
it('should handle an empty state', () => {
|
||||||
|
expect(
|
||||||
|
config(undefined, {})
|
||||||
|
).toEqual(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an update', () => {
|
||||||
|
expect(
|
||||||
|
config(Immutable.Map({'a': 'b', 'c': 'd'}), configLoaded({'a': 'changed', 'e': 'new'}))
|
||||||
|
).toEqual(
|
||||||
|
Immutable.Map({'a': 'changed', 'e': 'new'})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mark the config as loading', () => {
|
||||||
|
expect(
|
||||||
|
config(undefined, configLoading())
|
||||||
|
).toEqual(
|
||||||
|
Immutable.Map({isFetching: true})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an error', () => {
|
||||||
|
expect(
|
||||||
|
config(Immutable.Map({isFetching: true}), configFailed(new Error('Config could not be loaded')))
|
||||||
|
).toEqual(
|
||||||
|
Immutable.Map({error: 'Error: Config could not be loaded'})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
0
test/setup.js
Normal file
0
test/setup.js
Normal file
45
webpack.config.js
Normal file
45
webpack.config.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/* global module, __dirname, require */
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
|
loader: 'file?name=/[hash].[ext]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'babel',
|
||||||
|
test: /\.js?$/,
|
||||||
|
exclude: /(node_modules|bower_components)/,
|
||||||
|
query: {
|
||||||
|
cacheDirectory: true,
|
||||||
|
presets: ['react', 'es2015'],
|
||||||
|
plugins: ['transform-class-properties', 'transform-object-assign', 'transform-object-rest-spread']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
context: path.join(__dirname, 'src'),
|
||||||
|
entry: {
|
||||||
|
cms: './index',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
externals: [/^vendor\/.+\.js$/],
|
||||||
|
devServer: {
|
||||||
|
contentBase: 'example/',
|
||||||
|
historyApiFallback: true,
|
||||||
|
devTool: 'source-map'
|
||||||
|
},
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user