Skip to content

Commit

Permalink
Add missing tests and simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
mariuslundgard committed Nov 25, 2017
1 parent 3e960b7 commit ed31e0b
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 71 deletions.
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as lremi from './ops/lremi'
import * as lset from './ops/lset'
import * as set from './ops/set'
import {createFactory, createStore} from './store'
import {IOpHandlers, IStore} from './types'
import {IOperators, IStore} from './types'

// Export op handlers
export {decr, incr, lremi, lset, lpush, set}
Expand All @@ -14,7 +14,7 @@ export {decr, incr, lremi, lset, lpush, set}
export {createFactory, createStore}

// Export types
export {IOpHandlers, IStore}
export {IOperators, IStore}

// Export default
export default {createFactory, createStore}
10 changes: 4 additions & 6 deletions src/ops/decr.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import * as property from 'segmented-property'
import {pathJoin} from '../pathJoin'
import {IStore} from '../types'

export interface IDecrOpMsg {
export interface IDecrMsg {
type: string
key: string
}

export function exec(store: IStore<any>, op: IDecrOpMsg) {
store.state = property.set(store.state, op.key, store.get(op.key) - 1)
store.notifyObservers(op.key ? op.key.split('/') : ['.'])
export function transform(state: any, msg: IDecrMsg) {
return property.set(state, msg.key, property.get(state, msg.key) - 1)
}

export function create(type: string, refKey: string, args: string[]): IDecrOpMsg {
export function create(type: string, refKey: string, args: string[]): IDecrMsg {
let key = refKey

if (args.length === 1) {
Expand Down
10 changes: 4 additions & 6 deletions src/ops/incr.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import * as property from 'segmented-property'
import {pathJoin} from '../pathJoin'
import {IStore} from '../types'

export interface IIncrOpMsg {
export interface IIncrMsg {
type: string
key: string
}

export function exec(store: IStore<any>, op: IIncrOpMsg) {
store.state = property.set(store.state, op.key, store.get(op.key) + 1)
store.notifyObservers(op.key ? op.key.split('/') : ['.'])
export function transform(state: any, msg: IIncrMsg) {
return property.set(state, msg.key, property.get(state, msg.key) + 1)
}

export function create(type: string, refKey: string, args: string[]): IIncrOpMsg {
export function create(type: string, refKey: string, args: string[]): IIncrMsg {
let key = refKey

if (args.length === 1) {
Expand Down
12 changes: 5 additions & 7 deletions src/ops/lpush.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import * as property from 'segmented-property'
import {pathJoin} from '../pathJoin'
import {IStore} from '../types'

export interface ILPushOpMsg {
export interface ILPushMsg {
type: string
key: string
value: any
}

export function exec(store: IStore<any>, op: ILPushOpMsg) {
const currValue = property.get(store.state, op.key)
export function transform(state: any, msg: ILPushMsg) {
const currValue = property.get(state, msg.key)

store.state = property.set(store.state, op.key, currValue.concat([op.value]))
store.notifyObservers(op.key ? op.key.split('/') : ['.'])
return property.set(state, msg.key, currValue.concat([msg.value]))
}

export function create(type: string, refKey: string, args: any[]): ILPushOpMsg {
export function create(type: string, refKey: string, args: any[]): ILPushMsg {
let key = refKey

if (args.length === 2) {
Expand Down
15 changes: 7 additions & 8 deletions src/ops/lremi.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import * as property from 'segmented-property'
import {pathJoin} from '../pathJoin'
import {IStore} from '../types'

export interface ILRemiOpMsg {
export interface ILRemiMsg {
type: string
key: string
index: number
}

export function exec(store: IStore<any>, op: ILRemiOpMsg) {
const currValue: any = property.get(store.state, op.key)
export function transform(state: any, msg: ILRemiMsg) {
const currValue: any = property.get(state, msg.key)
const newValue = currValue.slice()

newValue.splice(op.index, 1)
store.state = property.set(store.state, op.key, newValue)
store.notifyObservers(op.key ? op.key.split('/') : ['.'])
newValue.splice(msg.index, 1)

return property.set(state, msg.key, newValue)
}

export function create(type: string, refKey: string, args: any[]): ILRemiOpMsg {
export function create(type: string, refKey: string, args: any[]): ILRemiMsg {
let key = refKey

if (args.length === 2) {
Expand Down
16 changes: 7 additions & 9 deletions src/ops/lset.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import * as property from 'segmented-property'
import {pathJoin} from '../pathJoin'
import {IStore} from '../types'

export interface ILSetOpMsg {
export interface ILSetMsg {
type: string
key: string
index: number
value: any
}

export function exec(store: IStore<any>, op: ILSetOpMsg) {
const currValue: any[] = property.get(store.state, op.key)
export function transform(state: any, msg: ILSetMsg) {
const currValue: any[] = property.get(state, msg.key)
const newValue = currValue.map((item, index) => {
if (index === op.index) {
return op.value
if (index === msg.index) {
return msg.value
}
return item
})

store.state = property.set(store.state, op.key, newValue)
store.notifyObservers(op.key ? op.key.split('/') : ['.'])
return property.set(state, msg.key, newValue)
}

export function create(type: string, refKey: string, args: any[]): ILSetOpMsg {
export function create(type: string, refKey: string, args: any[]): ILSetMsg {
let key = refKey

if (args.length === 3) {
Expand Down
10 changes: 4 additions & 6 deletions src/ops/set.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import * as property from 'segmented-property'
import {pathJoin} from '../pathJoin'
import {IStore} from '../types'

export interface ISetOpMsg {
export interface ISetMsg {
type: string
key: string
value: any
}

export function exec(store: IStore<any>, op: ISetOpMsg) {
store.state = property.set(store.state, op.key, op.value)
store.notifyObservers(op.key ? op.key.split('/') : ['.'])
export function transform(state: any, msg: ISetMsg) {
return property.set(state, msg.key, msg.value)
}

export function create(type: string, refKey: string, args: any[]): ISetOpMsg {
export function create(type: string, refKey: string, args: any[]): ISetMsg {
let key = refKey

if (args.length === 2) {
Expand Down
21 changes: 12 additions & 9 deletions src/ref.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import {pathJoin} from './pathJoin'
import {IObserver, IOpHandlers, IOps, IRef, IStore} from './types'
import {IObserver, IOperators, IOps, IRef, IStore} from './types'
import {basicAssign} from './utils'

export function createRef<State, SubState>(
refKey: string,
store: IStore<State>,
opHandlers: IOpHandlers<State>
operators: IOperators<State>
): IRef<SubState, any> {
const localOps: IOps = {}

Object.keys(opHandlers).forEach(
opKey => (localOps[opKey] = (...args: any[]) => store.dispatch(opHandlers[opKey].create(opKey, refKey, args)))
Object.keys(operators).forEach(
opKey => (localOps[opKey] = (...args: any[]) => store.dispatch(operators[opKey].create(opKey, refKey, args)))
)

const ref: IRef<SubState, any> = basicAssign({
get: (key: string) => store.get(pathJoin(refKey, key)),
ref: (key: string) => store.ref(pathJoin(refKey, key)),
subscribe: (observer: IObserver<SubState>) => store.subscribe(observer, refKey)
}, localOps)
const ref: IRef<SubState, any> = basicAssign(
{
get: (key: string) => store.get(pathJoin(refKey, key)),
ref: (key: string) => store.ref(pathJoin(refKey, key)),
subscribe: (observer: IObserver<SubState>) => store.subscribe(observer, refKey)
},
localOps
)

return ref
}
20 changes: 20 additions & 0 deletions src/store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,24 @@ describe('opstore/store', () => {

expect(Array.isArray(itemsRef.get())).toEqual(true)
})

it('should not send values when unchanged', () => {
expect.assertions(1)

const store: IStore<any> = opstore.createStore({
bar: 1,
foo: 1
})

const fooRef = store.ref('foo')

fooRef.subscribe({
next: () => {
expect(true).toEqual(true)
}
})

fooRef.set(1)
fooRef.set(2)
})
})
31 changes: 20 additions & 11 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import * as property from 'segmented-property'
import builtinOpHandlers from './ops'
import {createRef} from './ref'
import {IObservers, IOpHandlers, IOpMsg, IRefs, IStore, MiddlewareFn, StoreFactory} from './types'
import {IMsg, IObservers, IOperators, IRefs, IStore, MiddlewareFn, StoreFactory} from './types'

export const createStore: StoreFactory<any> = createFactory(builtinOpHandlers)

export function createFactory<State>(opHandlers: IOpHandlers<State> = {}): StoreFactory<State> {
export function createFactory<State>(operators: IOperators<State> = {}): StoreFactory<State> {
return (initialState: State): IStore<State> => {
const refs: IRefs = {}
const observers: IObservers = {}
const middlewareFns: MiddlewareFn[] = []

const applyOp = (op: IOpMsg) => {
if (opHandlers[op.type]) {
return opHandlers[op.type].exec(store, op)
}
let prevState: State

throw new Error(`Unknown operation: ${op.type}`)
const applyOp = (msg: IMsg) => {
if (operators[msg.type]) {
prevState = store.state
store.state = operators[msg.type].transform(store.state, msg)
store.notifyObservers(msg.key ? msg.key.split('/') : ['.'])
} else {
throw new Error(`Unknown operation: ${msg.type}`)
}
}

const store: IStore<State> = {
state: initialState,

ref: refKey => {
refs[refKey] = createRef(refKey, store, opHandlers)
refs[refKey] = createRef(refKey, store, operators)
return refs[refKey]
},

Expand Down Expand Up @@ -73,9 +77,14 @@ export function createFactory<State>(opHandlers: IOpHandlers<State> = {}): Store
const key = keyArray.join('/')

if (observers[key]) {
observers[key].forEach(observer => {
observer.next(key === '.' ? store.state : property.get(store.state, key))
})
const prevValue = key === '.' ? prevState : property.get(prevState, key)
const currValue = key === '.' ? store.state : property.get(store.state, key)

if (prevValue !== currValue) {
observers[key].forEach(observer => {
observer.next(currValue)
})
}
}

keyArray.pop()
Expand Down
14 changes: 7 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,25 @@ export interface IRefs {
[key: string]: IRef<any, any>
}

export interface IOpMsg {
export interface IMsg {
type: string
[key: string]: any
}

export interface IOpHandler<T> {
create: (...args: any[]) => IOpMsg
exec: (store: IStore<T>, op: IOpMsg) => void
export interface IOperator<T> {
create: (...args: any[]) => IMsg
transform: (state: T, msg: IMsg) => T
}

export interface IOpHandlers<T> {
[key: string]: IOpHandler<T>
export interface IOperators<T> {
[key: string]: IOperator<T>
}

export interface IOps {
[key: string]: Op
}

export type MiddlewareFn = (op: IOpMsg, next: () => void) => void
export type MiddlewareFn = (msfg: IMsg, next: () => void) => void

export interface IStore<T> {
dispatch: (op: any) => void
Expand Down

0 comments on commit ed31e0b

Please sign in to comment.