Skip to content

Commit

Permalink
Merge 0016f54 into 5d9d307
Browse files Browse the repository at this point in the history
  • Loading branch information
andon committed Mar 17, 2019
2 parents 5d9d307 + 0016f54 commit 78cf889
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 8 deletions.
5 changes: 4 additions & 1 deletion packages/classic/src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ export const atCursor = R.curry((cursor, action) => {
const keyPath = cursor._keyPath
const kind = data.kindOf(cursor)

return { ...action, [actionMetaProperty]: { keyPath, kind } }
return {
...action,
[actionMetaProperty]: { ...actionMeta(action), keyPath, kind },
}
})
8 changes: 8 additions & 0 deletions packages/classic/src/effect/__tests__/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ describe('effects API', function() {
await sleep(50)

expect(specificEffect3).toHaveBeenCalled()

// assert cause (originalAction) preservation
const action = specificEffect3.mock.calls[0][1]
const actionMeta = actions.actionMeta(action)
const { cause } = actionMeta
expect(cause).toBeDefined()
expect(cause.type).toBe('fireOnSub')
expect(action.type).toBe('toggle3')
})
})

Expand Down
17 changes: 14 additions & 3 deletions packages/classic/src/effect/impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ import { ActionRegistry, findParentEntry } from '../registry/ActionRegistry'
const updateStateAction = '@@skele/_effects.updateState'
const { error } = log

const nonInteractive = action => {
const meta = actions.actionMeta(action)
return {
...action,
[actions.actionMetaProperty]: {
...meta,
interactive:
meta && meta.hasOwnProperty('interactive') ? meta.interactive : false,
},
}
}
export const middleware = config => {
const { kernel, effectsRegistry } = config

Expand All @@ -26,7 +37,7 @@ export const middleware = config => {

const key = ActionRegistry.keyFromAction(action)
let effect = effectFor(key)
let context = kernel.focusOn(actionMeta.keyPath)
let context = kernel.focusOn(actionMeta.keyPath, action)

if (effect == null && action.type.startsWith('.')) {
// global action
Expand All @@ -35,12 +46,12 @@ export const middleware = config => {
if (entry != null) {
const { element, entry: eff } = entry
effect = eff
context = kernel.focusOn(element._keyPath)
context = kernel.focusOn(element._keyPath, action)
}
}

if (effect != null) {
const result = effect(context, action)
const result = effect(context, nonInteractive(action))

if (result && typeof result.then === 'function') {
result
Expand Down
19 changes: 16 additions & 3 deletions packages/classic/src/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,33 @@ class Kernel {
})
}

focusOn(path) {
focusOn(path, originalAction) {
const self = this

return {
dispatch(action) {
self.dispatch(actions.atCursor(self.query(path), action))
if (originalAction) {
const meta = actions.actionMeta(action)
self.dispatch(
actions.atCursor(self.query(path), {
...action,
[actions.actionMetaProperty]: { ...meta, cause: originalAction },
})
)
} else {
self.dispatch(actions.atCursor(self.query(path), action))
}
},

query(subPath) {
return self.query(data.asList(path).concat(data.asList(subPath)))
},

focusOn(subPath) {
return self.focusOn(data.asList(path).concat(data.asList(subPath)))
return self.focusOn(
data.asList(path).concat(data.asList(subPath)),
originalAction
)
},

get config() {
Expand Down
17 changes: 16 additions & 1 deletion packages/classic/src/ui/ElementView.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,28 @@ import PropTypes from 'prop-types'
import { data } from '@skele/core'

import memoizeOne from '../impl/memoize-one'
import { actionMetaProperty, actionMeta } from '../action'

export default R.curry((kind, Component, runtime) => {
const { uiFor: globalUIFor, system } = runtime

const interactive = action => {
const meta = actionMeta(action)
return {
...action,
[actionMetaProperty]: {
...meta,
interactive:
meta && meta.hasOwnProperty('interative') ? meta.interative : true,
},
}
}

const dispatchFor = memoizeOne(element => {
const focused = system.focusOn(element._keyPath)
return focused.dispatch.bind(focused)
const dispatch = focused.dispatch.bind(focused)

return action => dispatch(interactive(action))
})

return class extends React.Component {
Expand Down
91 changes: 91 additions & 0 deletions packages/classic/src/ui/__tests__/ElementView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict'

import { mount } from 'enzyme'

import * as R from 'ramda'
import React from 'react'

import * as Subsystem from '../../subsystem'
import * as Kernel from '../../kernel'

import { EntryPoint } from '../../engine'

import { defaultSubsystems } from '../../core'

import { actionMeta } from '../../action'

describe('ElementView', () => {
const app = Subsystem.create(() => ({
name: 'app',
}))

const Scene = ({ element }) => (
<div>
{element.get('kind')}: {element.get('text')}
</div>
)

app.ui.register('scene', Scene)

const capitalize = jest
.fn()
.mockImplementation(e => e.update('text', R.toUpper))

app.update.register('scene', 'capitalize', capitalize)

let kernel

beforeEach(() => {
kernel = Kernel.create([...defaultSubsystems, app], {
kind: 'nav',
scenes: [
{
kind: 'scene',
text: 'Scene 1',
},
{
kind: 'c2',
text: 'Scene 2',
},
],
})
})

afterEach(() => {
capitalize.mockClear()
})

test('dispatched actions from element dispatch are interactive', () => {
const scene = mount(<EntryPoint kernel={kernel} keyPath={['scenes', 0]} />)

expect(scene).toIncludeText('scene: Scene 1')

scene
.find(Scene)
.at(0)
.props()
.dispatch({ type: 'capitalize' })

expect(scene).toIncludeText('scene: SCENE 1')

// expect that the dispatched action has interactive flag set to true
// the action is the second argument in the update function
expect(capitalize.mock.calls.length).toBe(1)
expect(actionMeta(capitalize.mock.calls[0][1]).interactive).toBeDefined()
expect(actionMeta(capitalize.mock.calls[0][1]).interactive).toBe(true)
})

test('dispatched actions from kernel do not have interactivity defined', () => {
const scene = mount(<EntryPoint kernel={kernel} keyPath={['scenes', 0]} />)

expect(scene).toIncludeText('scene: Scene 1')

kernel.focusOn(['scenes', 0]).dispatch({ type: 'capitalize' })

expect(scene).toIncludeText('scene: SCENE 1')

// expect that the dispatched action does not have the interactive flag
expect(capitalize.mock.calls.length).toBe(1)
expect(actionMeta(capitalize.mock.calls[0][1]).interactive).toBeUndefined()
})
})

0 comments on commit 78cf889

Please sign in to comment.