Eslint: airbnb configuration

This commit is contained in:
Jeronimo Vallelunga 2017-07-11 00:14:39 -03:00
parent 7c047f8231
commit 9981a25367
24 changed files with 204 additions and 228 deletions

6
.eslintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "airbnb",
"rules": {
"func-names": ["error", "never"]
}
}

View File

@ -1,8 +1,8 @@
import { createStore } from 'redux'; import { createStore } from 'redux';
import { startPeripheral } from 'redux-bluetooth/build/peripheral'; import startPeripheral from 'redux-bluetooth/build/peripheral';
import reducer from './reducer'; import reducer from './reducer';
let store = createStore(reducer); const store = createStore(reducer);
startPeripheral('Counter', store); startPeripheral('Counter', store);

View File

@ -1,13 +1,13 @@
export default function counter(state = 0, { type }) { export default function counter(state = 0, { type }) {
console.log('Counter: ---------------------------'); console.log('Counter: ---------------------------');
console.log(type) console.log(type);
console.log(state) console.log(state);
switch (type) { switch (type) {
case 'INCREMENT': case 'INCREMENT':
return state + 1 return state + 1;
case 'DECREMENT': case 'DECREMENT':
return state - 1 return state - 1;
default: default:
return state return state;
} }
} }

View File

@ -10,38 +10,38 @@ export default class App extends PureComponent {
this.handlerConnect = this.handlerConnect.bind(this); this.handlerConnect = this.handlerConnect.bind(this);
} }
handlerConnect(){ handlerConnect() {
const { onConnect } = this.props; const { onConnect } = this.props;
onConnect('Counter'); onConnect('Counter');
} }
render() { render() {
const { const { store, status, onIncrement, onDecrement } = this.props;
store, status,
onIncrement, onDecrement
} = this.props;
return ( return (
<div className="app"> <div className="app">
{status === 'CONNECTED' &&
{ (status === 'CONNECTED') && <div className="app-counter">
<div className="app-counter">{store}</div> {store}
} </div>}
<div className="app-actions"> <div className="app-actions">
{ (status !== 'CONNECTED') && {status !== 'CONNECTED' &&
<button className="app-actions__buton" onClick={this.handlerConnect}>Connect</button> <button className="app-actions__buton" onClick={this.handlerConnect}>
} Connect
{ (status === 'CONNECTED') && </button>}
<button className="app-actions__buton" onClick={onIncrement}>+</button> {status === 'CONNECTED' &&
} <button className="app-actions__buton" onClick={onIncrement}>
{ (status === 'CONNECTED') && +
<button className="app-actions__buton" onClick={onDecrement}>-</button> </button>}
} {status === 'CONNECTED' &&
<button className="app-actions__buton" onClick={onDecrement}>
-
</button>}
</div> </div>
</div> </div>
); );
} }
}; }
App.propTypes = { App.propTypes = {
store: PropTypes.number, store: PropTypes.number,
@ -52,6 +52,8 @@ App.propTypes = {
}; };
App.defaultProps = { App.defaultProps = {
store: 0,
status: '',
onConnect: () => true, onConnect: () => true,
onIncrement: () => true, onIncrement: () => true,
onDecrement: () => true, onDecrement: () => true,

View File

@ -5,9 +5,7 @@ import { increment, decrement } from '../actions';
import Component from './component'; import Component from './component';
const mapState = (state) => { const mapState = state => state;
return state;
}
const mapAction = { const mapAction = {
onConnect: actions.connectStore, onConnect: actions.connectStore,

View File

@ -1,3 +1,4 @@
/* global document */
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
@ -11,11 +12,12 @@ import App from './app';
const ACTIONS = Object.keys(TYPES); const ACTIONS = Object.keys(TYPES);
/* eslint-disable react/jsx-filename-extension */
const store = createSyncStore(ACTIONS); const store = createSyncStore(ACTIONS);
render( render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider>, </Provider>,
document.getElementById('root') document.getElementById('root'),
); );
/* eslint-enable */

View File

@ -5,6 +5,8 @@
"main": "build/index.js", "main": "build/index.js",
"scripts": { "scripts": {
"dev": "watch 'npm run build' src", "dev": "watch 'npm run build' src",
"eslint": "eslint src",
"prebuild": "npm run eslint",
"build": "babel src -d build", "build": "babel src -d build",
"test": "jest", "test": "jest",
"test:watch": "npm test -- --watch", "test:watch": "npm test -- --watch",
@ -39,12 +41,19 @@
"devDependencies": { "devDependencies": {
"babel-cli": "^6.24.1", "babel-cli": "^6.24.1",
"babel-preset-latest": "^6.24.1", "babel-preset-latest": "^6.24.1",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.2",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-react": "^7.1.0",
"jest": "^20.0.4", "jest": "^20.0.4",
"np": "^2.16.0", "np": "^2.16.0",
"watch": "^1.0.2" "watch": "^1.0.2"
}, },
"dependencies": { "dependencies": {
"bleno": "^0.4.2", "bleno": "^0.4.2",
"redux": "^3.7.1",
"redux-thunk": "^2.2.0",
"text-encoding": "^0.6.4" "text-encoding": "^0.6.4"
} }
} }

View File

@ -1,11 +1,11 @@
const CONFIG = { const CONFIG = {
SERVICE_UUID: '13333333-3333-3333-3333-333333333337', SERVICE_UUID: '13333333-3333-3333-3333-333333333337',
CHARACTERISTIC_UUID: '13333333-3333-3333-3333-333333330001' CHARACTERISTIC_UUID: '13333333-3333-3333-3333-333333330001',
}; };
export const CENTRAL_CONFIG = CONFIG; export const CENTRAL_CONFIG = CONFIG;
export const BLENO_CONFIG = { export const BLENO_CONFIG = {
SERVICE_UUID: CONFIG.SERVICE_UUID.replace(/-/g, ''), SERVICE_UUID: CONFIG.SERVICE_UUID.replace(/-/g, ''),
CHARACTERISTIC_UUID: CONFIG.CHARACTERISTIC_UUID.replace(/-/g, ''), CHARACTERISTIC_UUID: CONFIG.CHARACTERISTIC_UUID.replace(/-/g, ''),
DESCRIPTOR_UUID: '2901' DESCRIPTOR_UUID: '2901',
}; };

View File

@ -1,17 +1,17 @@
export default function Encoder({ TextEncoder, TextDecoder}) { export default function Encoder({ TextEncoder, TextDecoder }) {
const encoder = new TextEncoder('utf-8'); const encoder = new TextEncoder('utf-8');
const decoder = new TextDecoder('utf-8'); const decoder = new TextDecoder('utf-8');
const encode = (json) => { const encode = (json) => {
const string = JSON.stringify(json); const string = JSON.stringify(json);
return encoder.encode(string); return encoder.encode(string);
} };
const decode = (data) => { const decode = (data) => {
const string = decoder.decode(data); const string = decoder.decode(data);
const json = JSON.parse(string); const json = JSON.parse(string);
return typeof json === 'string' ? JSON.parse(json) : json; return typeof json === 'string' ? JSON.parse(json) : json;
} };
return { encode, decode }; return { encode, decode };
} }

View File

@ -1,8 +1,9 @@
/* global test, expect */
import TextEncoding from 'text-encoding'; import TextEncoding from 'text-encoding';
import Encoder from '.'; import Encoder from '.';
const { encode , decode } = new Encoder(TextEncoding); const { encode, decode } = new Encoder(TextEncoding);
test('encode / decode', () => { test('encode / decode', () => {
const data = encode({ type: 'TEST', payload: 'PAYLOAD' }); const data = encode({ type: 'TEST', payload: 'PAYLOAD' });

View File

@ -1,4 +1,4 @@
import * as peripheral from './peripheral'; import peripheral from './peripheral';
import * as webapp from './webapp'; import * as webapp from './webapp';
export default { peripheral, webapp }; export default { peripheral, webapp };

View File

@ -1,15 +1,9 @@
export default function Characteristic( export default function Characteristic(uuid, Parent, util, descriptor, { encode, decode }) {
uuid,
Parent,
util,
descriptor,
{ encode, decode } ) {
function ReduxCharacteristic() { function ReduxCharacteristic() {
Parent.call(this, { Parent.call(this, {
uuid, uuid,
properties: ['read', 'write', 'notify'], properties: ['read', 'write', 'notify'],
descriptors: [ descriptor ] descriptors: [descriptor],
}); });
this.store = null; this.store = null;
@ -17,51 +11,52 @@ export default function Characteristic(
util.inherits(ReduxCharacteristic, Parent); util.inherits(ReduxCharacteristic, Parent);
ReduxCharacteristic.prototype.connect = function(store) { ReduxCharacteristic.prototype.connect = function (store) {
this.store = store; this.store = store;
this.store.subscribe(() => { this.store.subscribe(() => {
if ( this.updateValueCallback && this.store ) { if (this.updateValueCallback && this.store) {
const state = this.store.getState(); const state = this.store.getState();
this.updateValueCallback(encode(state)); this.updateValueCallback(encode(state));
} }
}); });
} };
ReduxCharacteristic.prototype.disconnect = function() { ReduxCharacteristic.prototype.disconnect = function () {
this.store = null; this.store = null;
} };
ReduxCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) { ReduxCharacteristic.prototype.onWriteRequest =
function (data, offset, withoutResponse, callback) {
if (offset) { if (offset) {
callback(this.RESULT_ATTR_NOT_LONG); callback(this.RESULT_ATTR_NOT_LONG);
return; return;
} }
this.store && this.store.dispatch(decode(data)); if (this.store) this.store.dispatch(decode(data));
callback(this.RESULT_SUCCESS); callback(this.RESULT_SUCCESS);
}; };
ReduxCharacteristic.prototype.onReadRequest = function(offset, callback) { ReduxCharacteristic.prototype.onReadRequest = function (offset, callback) {
if (offset) { if (offset) {
callback(this.RESULT_ATTR_NOT_LONG, null); callback(this.RESULT_ATTR_NOT_LONG, null);
return; return;
} }
if ( !this.store ) { if (!this.store) {
callback(this.RESULT_SUCCESS, null); callback(this.RESULT_SUCCESS, null);
return; return;
} }
callback(this.RESULT_SUCCESS, encode(this.store.getState())); callback(this.RESULT_SUCCESS, encode(this.store.getState()));
}; };
ReduxCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) { ReduxCharacteristic.prototype.onSubscribe = function (maxValueSize, updateValueCallback) {
this.updateValueCallback = updateValueCallback; this.updateValueCallback = updateValueCallback;
} };
ReduxCharacteristic.prototype.onUnsubscribe = function() { ReduxCharacteristic.prototype.onUnsubscribe = function () {
this.updateValueCallback = null; this.updateValueCallback = null;
} };
return new ReduxCharacteristic(); return new ReduxCharacteristic();
} }

View File

@ -1,5 +1,5 @@
import util from 'util'; import util from 'util';
import bleno from 'bleno'; import BLENO from 'bleno';
import TextEncoding from 'text-encoding'; import TextEncoding from 'text-encoding';
import { BLENO_CONFIG } from '../../common/config'; import { BLENO_CONFIG } from '../../common/config';
@ -7,36 +7,26 @@ import Encoder from '../../common/encoder';
import Service from './service'; import Service from './service';
import Characteristic from './characteristic'; import Characteristic from './characteristic';
import Descriptor from './Descriptor'; import Descriptor from './descriptor';
export function Bleno( export function Bleno(bleno, encoder, { SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID }) {
bleno, const descriptor = Descriptor(DESCRIPTOR_UUID, bleno.Descriptor);
encoder,
{ SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID }) {
const descriptor = Descriptor(
DESCRIPTOR_UUID,
bleno.Descriptor
);
const characteristic = Characteristic( const characteristic = Characteristic(
CHARACTERISTIC_UUID, CHARACTERISTIC_UUID,
bleno.Characteristic, bleno.Characteristic,
util, util,
descriptor, descriptor,
encoder); encoder,
);
const service = Service( const service = Service(SERVICE_UUID, bleno.PrimaryService, util, characteristic);
SERVICE_UUID,
bleno.PrimaryService,
util,
characteristic);
const start = (name, store) => { const start = (name, store) => {
bleno.on('stateChange', function(state) { bleno.on('stateChange', (state) => {
if (state === 'poweredOn') { if (state === 'poweredOn') {
bleno.startAdvertising(name, [SERVICE_UUID], function(err) { bleno.startAdvertising(name, [SERVICE_UUID], (err) => {
err && console.log('startAdvertising.err: ', err); if (err) console.log('startAdvertising.err: ', err);
}); });
} else { } else {
bleno.stopAdvertising(); bleno.stopAdvertising();
@ -44,19 +34,15 @@ export function Bleno(
} }
}); });
bleno.on('advertisingStart', function(err) { bleno.on('advertisingStart', (err) => {
if (!err) { if (!err) {
bleno.setServices([ service ]); bleno.setServices([service]);
characteristic.connect(store); characteristic.connect(store);
} }
}); });
} };
return { start }; return { start };
} }
export default new Bleno( export default new Bleno(BLENO, Encoder(TextEncoding), BLENO_CONFIG);
bleno,
Encoder(TextEncoding),
BLENO_CONFIG
);

View File

@ -1,9 +1,4 @@
export default function Service( export default function Service(uuid, Parent, util, characteristic) {
uuid,
Parent,
util,
characteristic) {
function ReduxService() { function ReduxService() {
Parent.call(this, { Parent.call(this, {
uuid, uuid,

View File

@ -1,3 +1,3 @@
import bleno from './bleno'; import bleno from './bleno';
export const startPeripheral = (name, store) => bleno.start(name, store); export default (name, store) => bleno.start(name, store);

View File

@ -1,31 +1,26 @@
export default function Actions(central, TYPES) { export default function Actions(central, TYPES) {
const syncState = state => ({
const syncState = (state) => ({
type: TYPES.BLUETOOTH_SYNC, type: TYPES.BLUETOOTH_SYNC,
payload: state payload: state,
}); });
const connectStore = (name) => dispatch => { const syncStore = () => dispatch => central.read().then(state => dispatch(syncState(state)));
const connectStore = name => (dispatch) => {
dispatch({ type: TYPES.BLUETOOTH_CONNECTING }); dispatch({ type: TYPES.BLUETOOTH_CONNECTING });
return central.connect(name) return central
.then(() => central.handler((state) => dispatch(syncState(state)))) .connect(name)
.then(() => central.handler(state => dispatch(syncState(state))))
.then(() => dispatch({ type: TYPES.BLUETOOTH_CONNECTED })) .then(() => dispatch({ type: TYPES.BLUETOOTH_CONNECTED }))
.then(() => dispatch(syncStore())); .then(() => dispatch(syncStore()));
}; };
const syncStore = () => dispatch => { const sendAction = action => () => central.write(action);
return central.read()
.then(state => dispatch(syncState(state)));
};
const sendAction = (action) => _ => {
return central.write(action);
};
return { return {
connectStore, connectStore,
syncStore, syncStore,
syncState, syncState,
sendAction, sendAction,
} };
} }

View File

@ -1,3 +1,4 @@
/* global jest, beforeEach, afterEach, test, expect */
import * as TYPES from './types'; import * as TYPES from './types';
import Actions from './actions'; import Actions from './actions';
@ -25,7 +26,7 @@ test('syncState', () => {
expect(action).toEqual({ expect(action).toEqual({
type: TYPES.BLUETOOTH_SYNC, type: TYPES.BLUETOOTH_SYNC,
payload: 'mockState' payload: 'mockState',
}); });
}); });
@ -33,7 +34,7 @@ test('connectStore', () => {
const { connectStore } = Actions(central, TYPES); const { connectStore } = Actions(central, TYPES);
expect.assertions(6); expect.assertions(6);
const promise = connectStore('mockName')(dispatch).then(_ => { const promise = connectStore('mockName')(dispatch).then(() => {
expect(central.connect).toBeCalled(); expect(central.connect).toBeCalled();
expect(central.handler).toBeCalled(); expect(central.handler).toBeCalled();
@ -51,7 +52,7 @@ test('syncStore', () => {
const { syncStore } = Actions(central, TYPES); const { syncStore } = Actions(central, TYPES);
expect.assertions(3); expect.assertions(3);
const promise = syncStore()(dispatch).then(_ => { const promise = syncStore()(dispatch).then(() => {
expect(central.read).toBeCalled(); expect(central.read).toBeCalled();
expect(dispatch).toBeCalledWith({ type: TYPES.BLUETOOTH_SYNC, payload: 'mockState' }); expect(dispatch).toBeCalledWith({ type: TYPES.BLUETOOTH_SYNC, payload: 'mockState' });
@ -65,7 +66,7 @@ test('sendAction', () => {
const { sendAction } = Actions(central, TYPES); const { sendAction } = Actions(central, TYPES);
expect.assertions(2); expect.assertions(2);
const promise = sendAction('mockAction')(dispatch).then(_ => { const promise = sendAction('mockAction')(dispatch).then(() => {
expect(central.write).toBeCalledWith('mockAction'); expect(central.write).toBeCalledWith('mockAction');
return true; return true;

View File

@ -1,69 +1,59 @@
import { CENTRAL_CONFIG } from '../../common/config'; import { CENTRAL_CONFIG } from '../../common/config';
import Encoder from '../../common/encoder'; import Encoder from '../../common/encoder';
export function Central( export function Central(bluetooth, { encode, decode }, { SERVICE_UUID, CHARACTERISTIC_UUID }) {
bluetooth,
{ encode, decode },
{ SERVICE_UUID, CHARACTERISTIC_UUID }) {
const state = { const state = {
server: null, server: null,
characteristic: null, characteristic: null,
}
const connect = (name) => {
return bluetooth.requestDevice({
filters: [{ services: [ SERVICE_UUID ], name: name }]
})
.then((device) => device.gatt.connect())
.then((server) => {
state.server = server;
return server.getPrimaryService(SERVICE_UUID)
})
.then((service) => service.getCharacteristic(CHARACTERISTIC_UUID))
.then((characteristic) => {
state.characteristic = characteristic;
});
}; };
const handler = ( callback ) => { const connect = name => bluetooth
return state.characteristic.startNotifications() .requestDevice({
.then(() => { filters: [{ services: [SERVICE_UUID], name }],
state.characteristic.addEventListener('characteristicvaluechanged', (event) => { })
callback(decode(event.target.value)); .then(device => device.gatt.connect())
.then((server) => {
state.server = server;
return server.getPrimaryService(SERVICE_UUID);
})
.then(service => service.getCharacteristic(CHARACTERISTIC_UUID))
.then((characteristic) => {
state.characteristic = characteristic;
}); });
const handler = callback => state.characteristic.startNotifications().then(() => {
state.characteristic.addEventListener('characteristicvaluechanged', (event) => {
callback(decode(event.target.value));
}); });
} });
const read = () => { const read = () => {
if ( state.server && state.server.connected && state.characteristic ) { if (state.server && state.server.connected && state.characteristic) {
return state.characteristic.readValue().then(data => { return state.characteristic.readValue().then(data => decode(data));
return decode(data);
});
} }
return Promise.reject(new Error('Bluetooth: Not Connected')); return Promise.reject(new Error('Bluetooth: Not Connected'));
} };
const write = (action) => { const write = (action) => {
if ( state.server && state.server.connected && state.characteristic ) { if (!state.server || !state.server.connected || !state.characteristic) return null;
const stringify = JSON.stringify(action); const stringify = JSON.stringify(action);
const serialized = encode(stringify); const serialized = encode(stringify);
return state.characteristic.writeValue(serialized); return state.characteristic.writeValue(serialized);
} };
}
return { return {
connected: state.server && state.server.connected, connected: state.server && state.server.connected,
connect, connect,
handler, handler,
read, read,
write write,
} };
} }
/* global navigator TextEncoder TextDecoder */
export default new Central( export default new Central(
navigator.bluetooth, navigator.bluetooth,
Encoder({ TextEncoder, TextDecoder }), Encoder({ TextEncoder, TextDecoder }),
CENTRAL_CONFIG CENTRAL_CONFIG,
); );

View File

@ -1,8 +1,9 @@
import actions from '../actions'; import ACTIONS from '../actions';
const { sendAction } = actions;
export default (actions = []) => store => next => action => { const { sendAction } = ACTIONS;
export default (actions = []) => store => next => (action) => {
const { type } = action; const { type } = action;
actions.includes(type) && store.dispatch(sendAction(action)); if (actions.includes(type)) store.dispatch(sendAction(action));
return next(action); return next(action);
} };

View File

@ -2,7 +2,7 @@ import * as TYPES from '../actions/types';
import * as STATUS from '../central/status'; import * as STATUS from '../central/status';
const initial = { const initial = {
status: STATUS.INIT status: STATUS.INIT,
}; };
export default (autosync = true) => (state = initial, { type, payload }) => { export default (autosync = true) => (state = initial, { type, payload }) => {

View File

@ -1,3 +1,4 @@
/* global window */
import { createStore, applyMiddleware, compose } from 'redux'; import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
@ -5,20 +6,14 @@ import middleware from '../middleware';
import reducers from '../reducers'; import reducers from '../reducers';
export default (actions) => { export default (actions) => {
const middlewares = [ const middlewares = [middleware(actions), thunk];
middleware(actions), const enhancers = [applyMiddleware(...middlewares)];
thunk
];
const enhancers = [
applyMiddleware(...middlewares),
];
/* eslint-disable no-underscore-dangle */
if (typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION__) { if (typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION__) {
enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__());
} }
/* eslint-enable */
return createStore( return createStore(reducers(), compose(...enhancers));
reducers(), };
compose(...enhancers),
);
}