diff --git a/.jest.json b/.jest.json index 14da031..1f1a919 100644 --- a/.jest.json +++ b/.jest.json @@ -1,3 +1,4 @@ { + "testPathIgnorePatterns": ["/build/"], "collectCoverageFrom": ["src/**/*.js"] } \ No newline at end of file diff --git a/example/peripheral/src/index.js b/example/peripheral/src/index.js index fe0e4b1..2b4610f 100644 --- a/example/peripheral/src/index.js +++ b/example/peripheral/src/index.js @@ -1,5 +1,5 @@ import { createStore } from 'redux'; -import startPeripheral from 'redux-bluetooth/build/peripheral'; +import { connectSyncStore } from 'redux-bluetooth/build/peripheral'; import reducer from './reducer'; import output from './output'; @@ -7,5 +7,4 @@ import output from './output'; const store = createStore(reducer); output(store); -startPeripheral('Counter', store); - +connectSyncStore('Counter', store); diff --git a/src/peripheral/bleno/characteristic.js b/src/peripheral/bleno/characteristic.js index 03544a3..dfb5802 100644 --- a/src/peripheral/bleno/characteristic.js +++ b/src/peripheral/bleno/characteristic.js @@ -6,25 +6,11 @@ export default function Characteristic(uuid, Parent, util, descriptor, { encode, descriptors: [descriptor], }); - this.store = null; + this.state = null; } util.inherits(ReduxCharacteristic, Parent); - ReduxCharacteristic.prototype.connect = function (store) { - this.store = store; - this.store.subscribe(() => { - if (this.updateValueCallback && this.store) { - const state = this.store.getState(); - this.updateValueCallback(encode(state)); - } - }); - }; - - ReduxCharacteristic.prototype.disconnect = function () { - this.store = null; - }; - ReduxCharacteristic.prototype.onWriteRequest = function (data, offset, withoutResponse, callback) { if (offset) { @@ -32,7 +18,7 @@ export default function Characteristic(uuid, Parent, util, descriptor, { encode, return; } - if (this.store) this.store.dispatch(decode(data)); + this.onAction(decode(data)); callback(this.RESULT_SUCCESS); }; @@ -47,7 +33,7 @@ export default function Characteristic(uuid, Parent, util, descriptor, { encode, callback(this.RESULT_SUCCESS, null); return; } - callback(this.RESULT_SUCCESS, encode(this.store.getState())); + callback(this.RESULT_SUCCESS, this.state); }; ReduxCharacteristic.prototype.onSubscribe = function (maxValueSize, updateValueCallback) { @@ -58,5 +44,16 @@ export default function Characteristic(uuid, Parent, util, descriptor, { encode, this.updateValueCallback = null; }; + ReduxCharacteristic.prototype.onAction = function () { + return true; + }; + + ReduxCharacteristic.prototype.updateState = function (state) { + this.state = encode(state); + if (this.updateValueCallback) { + this.updateValueCallback(this.state); + } + }; + return new ReduxCharacteristic(); } diff --git a/src/peripheral/bleno/index.js b/src/peripheral/bleno/index.js index 79795bd..8629533 100644 --- a/src/peripheral/bleno/index.js +++ b/src/peripheral/bleno/index.js @@ -22,27 +22,37 @@ export function Bleno(bleno, encoder, { SERVICE_UUID, CHARACTERISTIC_UUID, DESCR const service = Service(SERVICE_UUID, bleno.PrimaryService, util, characteristic); - const start = (name, store) => { - bleno.on('stateChange', (state) => { - if (state === 'poweredOn') { + const start = (name, state) => { + bleno.on('stateChange', (status) => { + if (status === 'poweredOn') { bleno.startAdvertising(name, [SERVICE_UUID], (err) => { - if (err) console.log('startAdvertising.err: ', err); + if (!err) characteristic.updateState(state); }); } else { bleno.stopAdvertising(); - characteristic.disconnect(); } }); bleno.on('advertisingStart', (err) => { if (!err) { bleno.setServices([service]); - characteristic.connect(store); } }); }; - return { start }; + const handler = (callback) => { + characteristic.onAction = callback; + }; + + const notify = (state) => { + characteristic.updateState(state); + }; + + return { + start, + handler, + notify, + }; } export default new Bleno(BLENO, Encoder(TextEncoding), BLENO_CONFIG); diff --git a/src/peripheral/index.js b/src/peripheral/index.js index 97c4eae..0bfb8ce 100644 --- a/src/peripheral/index.js +++ b/src/peripheral/index.js @@ -1,3 +1,5 @@ -import bleno from './bleno'; +import BLUETOOTH from './bleno'; +import STORE from './store'; -export default (name, store) => bleno.start(name, store); +export const bluetooth = BLUETOOTH; +export const connectSyncStore = STORE(BLUETOOTH); diff --git a/src/peripheral/index.test.js b/src/peripheral/index.test.js index 21f5a54..952d849 100644 --- a/src/peripheral/index.test.js +++ b/src/peripheral/index.test.js @@ -1,11 +1,12 @@ /* global jest, test, expect */ -import peripheral from '.'; +import * as peripheral from '.'; -jest.mock('./bleno', () => ({ - start: jest.fn().mockReturnValue('mockStart'), -})); +jest.mock('./bleno', () => null); +jest.mock('./store', () => () => null); -test('peripheral', () => { - const result = peripheral(null, null); - expect(result).toEqual('mockStart'); +test('actions', () => { + expect(Object.keys(peripheral)).toEqual([ + 'bluetooth', + 'connectSyncStore', + ]); }); diff --git a/src/peripheral/store/index.js b/src/peripheral/store/index.js new file mode 100644 index 0000000..89e9b92 --- /dev/null +++ b/src/peripheral/store/index.js @@ -0,0 +1,16 @@ +export default bluetooth => (name, store) => { + bluetooth.start(name, store.getState()); + + const handleSubscribe = () => { + bluetooth.notify(store.getState()); + }; + + const handleActions = (action) => { + store.dispatch(action); + }; + + store.subscribe(handleSubscribe); + bluetooth.handler(handleActions); + + return { handleSubscribe, handleActions }; +}; diff --git a/src/peripheral/store/index.test.js b/src/peripheral/store/index.test.js new file mode 100644 index 0000000..0917fce --- /dev/null +++ b/src/peripheral/store/index.test.js @@ -0,0 +1,34 @@ +/* global jest, test, expect, beforeEach */ +import connectSyncStore from '.'; + +let store = null; +let bleno = null; + +beforeEach(() => { + bleno = { + start: jest.fn(), + handler: jest.fn(), + notify: jest.fn(), + }; + + store = { + subscribe: jest.fn(), + getState: jest.fn().mockReturnValue('mockState'), + dispatch: jest.fn(), + }; +}); + +test('connectSyncStore', () => { + const { handleSubscribe, handleActions } = connectSyncStore(bleno)('mockName', store); + + expect(bleno.start).toBeCalledWith('mockName', 'mockState'); + expect(store.subscribe).toBeCalled(); + expect(bleno.handler).toBeCalled(); + + handleSubscribe(); + expect(bleno.notify).toBeCalledWith('mockState'); + expect(store.getState.mock.calls.length).toBe(2); + + handleActions('mockAction'); + expect(store.dispatch).toBeCalledWith('mockAction'); +}); diff --git a/src/webapp/central/index.js b/src/webapp/central/index.js index 8dcd158..a61d4c0 100644 --- a/src/webapp/central/index.js +++ b/src/webapp/central/index.js @@ -1,9 +1,11 @@ -/* global navigator TextEncoder TextDecoder */ +/* global window */ import { CENTRAL_CONFIG } from '../../common/config'; import Encoder from '../../common/encoder'; import Central from './central'; +const { navigator, TextDecoder, TextEncoder } = window; + export default new Central( navigator.bluetooth, Encoder({ TextEncoder, TextDecoder }), diff --git a/src/webapp/central/index.test.js b/src/webapp/central/index.test.js new file mode 100644 index 0000000..8092af3 --- /dev/null +++ b/src/webapp/central/index.test.js @@ -0,0 +1,20 @@ +/* global jest, test, expect */ +import central from '.'; +import Central from './central'; +import { CENTRAL_CONFIG } from '../../common/config'; + +jest.mock('../../common/encoder', () => () => true); + +global.TextEncoder = null; +global.TextDecoder = null; +global.navigator = { bluetooth: null }; + +test('central', () => { + const result = new Central( + null, + true, + CENTRAL_CONFIG, + ); + + expect(Object.keys(central)).toEqual(Object.keys(result)); +}); diff --git a/src/webapp/index.test.js b/src/webapp/index.test.js new file mode 100644 index 0000000..c36bb89 --- /dev/null +++ b/src/webapp/index.test.js @@ -0,0 +1,20 @@ +/* global jest, test, expect */ +import * as webapp from '.'; + +jest.mock('./actions/types', () => true); +jest.mock('./central/status', () => true); +jest.mock('./actions', () => true); +jest.mock('./middleware', () => true); +jest.mock('./reducers', () => true); +jest.mock('./store', () => true); + +test('actions', () => { + expect(Object.keys(webapp)).toEqual([ + 'types', + 'status', + 'actions', + 'reducers', + 'middleware', + 'createSyncStore', + ]); +});