Skip to content

Commit

Permalink
test: finalize lifecycle tests for Vue
Browse files Browse the repository at this point in the history
  • Loading branch information
crutchcorn committed Sep 13, 2024
1 parent 4dbc3c5 commit d2ec8e4
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 3 deletions.
5 changes: 3 additions & 2 deletions packages/vue-redux/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { UseSelectorOptions } from './compositions/use-selector'
import type { DeepReadonly, Ref, UnwrapRef } from 'vue'

export type EqualityFn<T> = (a: T, b: T) => boolean

Expand All @@ -18,11 +19,11 @@ export interface TypedUseSelectorComposition<TState> {
<TSelected>(
selector: (state: TState) => TSelected,
equalityFn?: EqualityFn<NoInfer<TSelected>>,
): TSelected
): Readonly<Ref<DeepReadonly<UnwrapRef<TSelected>>>>
<Selected = unknown>(
selector: (state: TState) => Selected,
options?: UseSelectorOptions<Selected>,
): Selected
): Readonly<Ref<DeepReadonly<UnwrapRef<Selected>>>>
}

export type NoInfer<T> = [T][T extends any ? 0 : never]
84 changes: 83 additions & 1 deletion packages/vue-redux/tests/use-selector.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { defineComponent, h, inject, watchSyncEffect } from 'vue'
import { createStore } from 'redux'
import { cleanup, render, waitFor } from '@testing-library/vue'
import { ContextKey, provideStore as provideMock, useSelector } from '../src'
import type { Ref } from 'vue'
import type { Subscription } from '../src/utils/Subscription'
import type { TypedUseSelectorComposition } from '../src'
import type { AnyAction, Store } from 'redux'
import { Subscription } from '../src/utils/Subscription'

describe('Vue', () => {
describe('compositions', () => {
Expand Down Expand Up @@ -139,6 +140,87 @@ describe('Vue', () => {
expect(appSubscription!.getListeners().get().length).toBe(2),
)
})

it('unsubscribes when the component is unmounted', async () => {
let appSubscription: Subscription | null = null

const Child = defineComponent(() => {
const count = useNormalSelector((s) => s.count)
return () => <div>{count.value}</div>
})

const Parent = defineComponent(() => {
const contextVal = inject(ContextKey)
appSubscription = contextVal && contextVal.subscription
const count = useNormalSelector((s) => s.count)
return () => (count.value === 0 ? <Child /> : null)
})

const App = defineComponent(() => {
provideMock({ store: normalStore })
return () => <Parent />
})

render(<App />)
// Parent + 1 child component
expect(appSubscription!.getListeners().get().length).toBe(2)

normalStore.dispatch({ type: '' })

// Parent component only
await waitFor(() =>
expect(appSubscription!.getListeners().get().length).toBe(1),
)
})

it('notices store updates between render and store subscription effect', async () => {
const Child = defineComponent(
(props: { count: Ref<number> }) => {
// console.log('Child rendering')
watchSyncEffect(() => {
// console.log('Child layoutEffect: ', props.count.value)
if (props.count.value === 0) {
// console.log('Dispatching store update')
normalStore.dispatch({ type: '' })
}
})
return () => null
},
{
props: ['count'],
},
)

const Comp = defineComponent(() => {
// console.log('Parent rendering, selecting state')
const count = useNormalSelector((s) => s.count)

watchSyncEffect(() => {
// console.log('Parent layoutEffect: ', count)
renderedItems.push(count.value)
})

return () => (
<div>
{count.value}
<Child count={count} />
</div>
)
})

const App = defineComponent(() => {
provideMock({ store: normalStore })
return () => <Comp />
})

// console.log('Starting initial render')
render(<App />)

// With `useSyncExternalStore`, we get three renders of `<Comp>`:
// 1) Initial render, count is 0
// 2) Render due to dispatch, still sync in the initial render's commit phase
expect(renderedItems).toEqual([0, 1])
})
})
})
})
Expand Down

0 comments on commit d2ec8e4

Please sign in to comment.