Skip to content

Commit

Permalink
feat: mapWritableState
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Apr 9, 2021
1 parent 2d8b7b6 commit 3218bdb
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 8 deletions.
57 changes: 57 additions & 0 deletions __tests__/mapHelpers.spec.ts
Expand Up @@ -5,6 +5,7 @@ import {
mapGetters,
mapState,
mapStores,
mapWritableState,
setMapStoreSuffix,
} from '../src'
import { mount } from '@vue/test-utils'
Expand Down Expand Up @@ -240,4 +241,60 @@ describe('Map Helpers', () => {
expect(wrapper.vm.set(4)).toBe(4)
})
})

describe('mapWritableState', () => {
async function testComponent(
computedProperties: any,
template: string,
expectedText: string,
expectedText2: string
) {
const pinia = createPinia()
const Component = defineComponent({
template: `<p>${template}</p>`,
computed: {
...computedProperties,
},
methods: Object.keys(computedProperties).reduce((methods, name) => {
// @ts-ignore
methods['set_' + name] = function (v: any) {
// @ts-ignore
this[name] = v
}
return methods
}, {}),
})

const wrapper = mount(Component, { global: { plugins: [pinia] } })

expect(wrapper.text()).toBe(expectedText)

for (const key in computedProperties) {
// @ts-ignore
wrapper.vm['set_' + key]('replaced')
}

await nextTick()

expect(wrapper.text()).toBe(expectedText2)
}

it('array', async () => {
await testComponent(
mapWritableState(useStore, ['n', 'a']),
`{{ n }} {{ a }}`,
`0 true`,
'replaced replaced'
)
})

it('object', async () => {
await testComponent(
mapWritableState(useStore, { count: 'n', myA: 'a' }),
`{{ count }} {{ myA }}`,
`0 true`,
'replaced replaced'
)
})
})
})
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -19,6 +19,7 @@ export {
mapActions,
mapStores,
mapState,
mapWritableState,
mapGetters,
MapStoresCustomization,
setMapStoreSuffix,
Expand Down
40 changes: 33 additions & 7 deletions src/mapHelpers.ts
Expand Up @@ -358,6 +358,23 @@ export function mapActions<
}, {} as MapActionsObjectReturn<A, KeyMapper>)
}

type MapWritableStateReturn<S extends StateTree> = {
[key in keyof S]: {
get: () => Store<string, S, {}, {}>[key]
set: (value: Store<string, S, {}, {}>[key]) => any
}
}

type MapWritableStateObjectReturn<
S extends StateTree,
T extends Record<string, keyof S>
> = {
[key in keyof T]: {
get: () => Store<string, S, {}, {}>[T[key]]
set: (value: Store<string, S, {}, {}>[T[key]]) => any
}
}

/**
* Same as `mapState()` but creates computed setters as well so the state can be
* modified. Differently from `mapState()`, only `state` properties can be
Expand All @@ -375,7 +392,7 @@ export function mapWritableState<
>(
useStore: StoreDefinition<Id, S, G, A>,
keyMapper: KeyMapper
): MapStateObjectReturn<Id, S, G, A, KeyMapper>
): MapWritableStateObjectReturn<S, KeyMapper>
/**
* Allows using state and getters from one store without using the composition
* API (`setup()`) by generating an object to be spread in the `computed` field
Expand All @@ -387,7 +404,7 @@ export function mapWritableState<
export function mapWritableState<Id extends string, S extends StateTree, G, A>(
useStore: StoreDefinition<Id, S, G, A>,
keys: Array<keyof S>
): MapStateReturn<S, G>
): MapWritableStateReturn<S>
/**
* Allows using state and getters from one store without using the composition
* API (`setup()`) by generating an object to be spread in the `computed` field
Expand All @@ -405,24 +422,33 @@ export function mapWritableState<
>(
useStore: StoreDefinition<Id, S, G, A>,
keysOrMapper: Array<keyof S> | KeyMapper
): MapStateReturn<S, G> | MapStateObjectReturn<Id, S, G, A, KeyMapper> {
): MapWritableStateReturn<S> | MapWritableStateObjectReturn<S, KeyMapper> {
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
reduced[key] = {
get(this: ComponentInstance) {
return getCachedStore(this, useStore)[key]
},
set() {},
set(value) {
// it's easier to type it here as any
return (getCachedStore(this, useStore)[key] = value as any)
},
}
return reduced
}, {} as MapStateReturn<S, G>)
}, {} as MapWritableStateReturn<S>)
: Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
// @ts-ignore
reduced[key] = {
get(this: ComponentInstance) {
return getCachedStore(this, useStore)[keysOrMapper[key]]
},
set() {},
set(value) {
// it's easier to type it here as any
return (getCachedStore(this, useStore)[
keysOrMapper[key]
] = value as any)
},
}
return reduced
}, {} as MapStateObjectReturn<Id, S, G, A, KeyMapper>)
}, {} as MapWritableStateObjectReturn<S, KeyMapper>)
}
28 changes: 27 additions & 1 deletion test-dts/mapHelpers.test-d.ts
@@ -1,4 +1,11 @@
import { defineStore, expectType, mapStores, mapActions, mapState } from '.'
import {
defineStore,
expectType,
mapStores,
mapActions,
mapState,
mapWritableState,
} from '.'

const useStore = defineStore({
id: 'name',
Expand Down Expand Up @@ -73,3 +80,22 @@ expectType<{
newSetToggle: (a: 'on' | 'off') => 'on' | 'off'
newToggleA: () => void
}>(mapActions(useStore, { newSetToggle: 'setToggle', newToggleA: 'toggleA' }))

expectType<{
a: {
get: () => 'on' | 'off'
set: (v: 'on' | 'off') => any
}
}>(mapWritableState(useStore, ['a']))

expectType<{
newA: {
get: () => 'on' | 'off'
set: (v: 'on' | 'off') => any
}
}>(mapWritableState(useStore, { newA: 'a' }))

// @ts-expect-error: cannot use a getter
mapWritableState(useStore, ['upper'])
// @ts-expect-error: cannot use a getter
mapWritableState(useStore, { up: 'upper' })

0 comments on commit 3218bdb

Please sign in to comment.