Skip to content

Commit

Permalink
expose secret Context API to use with Hooks in the future, for create…
Browse files Browse the repository at this point in the history
…ConnectedStoreAs
  • Loading branch information
bcherny committed Nov 13, 2018
1 parent d2623f3 commit 57e1f12
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 64 deletions.
6 changes: 4 additions & 2 deletions src/react/createConnectedStoreAs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ export function createConnectedStoreAs<States extends {

return {
Container,
withStores
}
withStores,
/** @private */
__CONTEXT_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: Context
} as ConnectAs<States>
}

function isInitialized<State extends object>(
Expand Down
69 changes: 69 additions & 0 deletions test/react/createConnectedStoreAs-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import test from 'ava'
import * as React from 'react'
import { Simulate } from 'react-dom/test-utils'
import { createConnectedStoreAs, createStore, Store } from '../../src'
import { withElement } from '../testUtils'

test('it should expose an internal Context API', t => {
let S = createConnectedStoreAs({ a: { b: 1 } })
let Context = (S as any)[
'__CONTEXT_DO_NOT_USE_OR_YOU_WILL_BE_FIRED'
] as React.Context<{ a: Store<{ b: number }> }>
let A = () => (
<Context.Consumer>
{store => (
<button onClick={() => store.a.set('b')(store.a.get('b') + 1)}>
{store.a.get('b')}
</button>
)}
</Context.Consumer>
)
let B = () => (
<S.Container>
<A />
</S.Container>
)
withElement(B, _ => {
t.is(_.querySelector('button')!.innerHTML, '1')
Simulate.click(_.querySelector('button')!)
t.is(_.querySelector('button')!.innerHTML, '2')
})
})

test('it should not be usable with a <Provider /> instead of a <Container />', t => {
let S = createConnectedStoreAs({ a: { b: 1 } })
let Context = (S as any)[
'__CONTEXT_DO_NOT_USE_OR_YOU_WILL_BE_FIRED'
] as React.Context<{ a: Store<{ b: number }> }>
let A = () => (
<Context.Consumer>
{store => (
<button onClick={() => store.a.set('b')(store.a.get('b') + 1)}>
{store.a.get('b')}
</button>
)}
</Context.Consumer>
)
let B = () => (
<Context.Provider value={{ a: createStore({ b: 1 }) }}>
<A />
</Context.Provider>
)
withElement(B, _ => {
t.is(_.querySelector('button')!.innerHTML, '1')
Simulate.click(_.querySelector('button')!)
t.is(_.querySelector('button')!.innerHTML, '1')
})
})

test(`it should throw if you don't give it a Provider`, t => {
let S = createConnectedStoreAs({ a: { b: 1 } })
let Context = (S as any)[
'__CONTEXT_DO_NOT_USE_OR_YOU_WILL_BE_FIRED'
] as React.Context<{ a: Store<{ b: number }> }>
let A = () => <Context.Consumer>{store => store.a.get('b')}</Context.Consumer>
t.throws(
() => withElement(A, _ => {}),
/Cannot read property \'get\' of undefined/
)
})
123 changes: 61 additions & 62 deletions test/react/createConnectedStoreAs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,23 @@ import { createConnectedStoreAs } from '../../src/react'
import { withElement } from '../testUtils'

test('it should support combining stores', t => {

let C = createConnectedStoreAs({
A: { a: 1 },
B: { b: 1 }
})

let X = C.withStores(({ A, B }) =>
let X = C.withStores(({ A, B }) => (
<>
<button onClick={() => A.set('a')(A.get('a') + 1)}>
{A.get('a')}
</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>
{B.get('b')}
</button>
<button onClick={() => A.set('a')(A.get('a') + 1)}>{A.get('a')}</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>{B.get('b')}</button>
</>
)
))

let Z = () => <C.Container>
<X />
</C.Container >
let Z = () => (
<C.Container>
<X />
</C.Container>
)

withElement(Z, z => {
assertButtons(z, 1, 1)
Expand All @@ -47,33 +44,36 @@ test('it should support effects for multiple stores', t => {
t.plan(2)

type State = {
A: { a: number },
A: { a: number }
B: { b: number }
}

let withEffects: EffectsAs<State> = ({ A, B }) => {
A.on('a').subscribe(a => t.is(a, 2))
B.on('b').subscribe(b => t.is(b, 2))
return {A, B}
return { A, B }
}

let C = createConnectedStoreAs({
A: { a: 1 },
B: { b: 1 }
}, withEffects)
let C = createConnectedStoreAs(
{
A: { a: 1 },
B: { b: 1 }
},
withEffects
)

let X = C.withStores(({ A, B }) =>
let X = C.withStores(({ A, B }) => (
<>
<button onClick={() => A.set('a')(A.get('a') + 1)}>
{A.get('a')}
</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>
{B.get('b')}
</button>
<button onClick={() => A.set('a')(A.get('a') + 1)}>{A.get('a')}</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>{B.get('b')}</button>
</>
)
))

let Z = () => <C.Container><X /></C.Container >
let Z = () => (
<C.Container>
<X />
</C.Container>
)

withElement(Z, z => {
Simulate.click(z.querySelectorAll('button')[0])
Expand All @@ -86,18 +86,22 @@ test('it should support multiple instances of multiple stores', t => {
A: { a: 1 },
B: { b: 2 }
})
let C = withStores(({ A, B }) =>
let C = withStores(({ A, B }) => (
<>
<button onClick={() => A.set('a')(A.get('a') + 1)}>
{A.get('a')}
</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>
{B.get('b')}
</button>
<button onClick={() => A.set('a')(A.get('a') + 1)}>{A.get('a')}</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>{B.get('b')}</button>
</>
))
let A = () => (
<Container>
<C />
</Container>
)
let B = () => (
<Container>
<C />
</Container>
)
let A = () => <Container><C /></Container>
let B = () => <Container><C /></Container>

withElement(A, a =>
withElement(B, b => {
Expand Down Expand Up @@ -125,31 +129,28 @@ test('it should support multiple instances of multiple stores', t => {
})

test('it should support custom initialStates for multiple stores', t => {

let C = createConnectedStoreAs({
A: { a: 1 },
B: { b: 1 }
})

let X = C.withStores(({ A, B }) =>
let X = C.withStores(({ A, B }) => (
<>
<button onClick={() => A.set('a')(A.get('a') + 1)}>
{A.get('a')}
</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>
{B.get('b')}
</button>
<button onClick={() => A.set('a')(A.get('a') + 1)}>{A.get('a')}</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>{B.get('b')}</button>
</>
)
))

let initialStates = {
A: { a: 3 },
B: { b: 4 }
}

let Z = () => <C.Container initialStates={initialStates}>
<X />
</C.Container >
let Z = () => (
<C.Container initialStates={initialStates}>
<X />
</C.Container>
)

withElement(Z, z => {
assertButtons(z, 3, 4)
Expand All @@ -171,7 +172,7 @@ test('it should support custom effects for multiple stores', t => {
t.plan(2)

type State = {
A: { a: number },
A: { a: number }
B: { b: number }
}

Expand All @@ -180,26 +181,24 @@ test('it should support custom effects for multiple stores', t => {
B: { b: 1 }
})

let X = C.withStores(({ A, B }) =>
let X = C.withStores(({ A, B }) => (
<>
<button onClick={() => A.set('a')(A.get('a') + 1)}>
{A.get('a')}
</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>
{B.get('b')}
</button>
<button onClick={() => A.set('a')(A.get('a') + 1)}>{A.get('a')}</button>
<button onClick={() => B.set('b')(B.get('b') + 1)}>{B.get('b')}</button>
</>
)
))

let effects: EffectsAs<State> = ({ A, B }) => {
A.on('a').subscribe(a => t.is(a, 2))
B.on('b').subscribe(b => t.is(b, 2))
return {A, B}
return { A, B }
}

let Z = () => <C.Container effects={effects}>
<X />
</C.Container >
let Z = () => (
<C.Container effects={effects}>
<X />
</C.Container>
)

withElement(Z, z => {
Simulate.click(z.querySelectorAll('button')[0])
Expand All @@ -208,7 +207,7 @@ test('it should support custom effects for multiple stores', t => {
})

test('it should eagerly throw at runtime when using a consumer without a container', t => {
let {withStores} = createConnectedStoreAs({A: { a: 1 }})
let { withStores } = createConnectedStoreAs({ A: { a: 1 } })
let A = withStores(() => <div />)
t.throws(() => withElement(A, _ => {}), /does not seem to be nested/)
})
1 change: 1 addition & 0 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './immutable'
import './react/createConnectedStore'
import './react/createConnectedStore-context'
import './react/createConnectedStoreAs'
import './react/createConnectedStoreAs-context'
import './stateful'
import './stateless'
import './utils'

0 comments on commit 57e1f12

Please sign in to comment.