Skip to content

Commit

Permalink
feat(types): allow generics in useCollection
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Oct 17, 2022
1 parent ab3c9cb commit 57dbbc8
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 9 deletions.
31 changes: 27 additions & 4 deletions src/vuefire/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,23 @@ export interface UseCollectionOptions {}
* @param options - optional options
* @returns
*/
export function useCollection<
// explicit generic as unknown to allow arbitrary types
// TODO: check if it's actually possible to use something like `number` as the generic, if not, remove these constrains
R extends CollectionReference<unknown> | Query<unknown>
>(
collectionRef: R,
options?: UseCollectionOptions
): Ref<_InferReferenceType<R>[]>
export function useCollection<T>(
collectionRef: CollectionReference<T> | Query<T>,
collectionRef: CollectionReference | Query,
options?: UseCollectionOptions
) {
const data = ref<T[]>()
): Ref<T[]>
export function useCollection<T>(
collectionRef: CollectionReference<unknown> | Query<unknown>,
options?: UseCollectionOptions
): Ref<_InferReferenceType<T>[]> | Ref<T[]> {
const data = ref<T[]>([])

let unbind!: ReturnType<typeof bindCollection>
const promise = new Promise((resolve, reject) => {
Expand All @@ -323,8 +335,19 @@ export function useCollection<T>(
})
}

return data
// no unwrapRef to have a simpler type
return data as Ref<T[]>
}

export const unbind = (target: Ref, reset?: FirestoreOptions['reset']) =>
internalUnbind('', firestoreUnbinds.get(target), reset)

/**
* Infers the type from a firestore reference.
*/
export type _InferReferenceType<R> = R extends
| CollectionReference<infer T>
| Query<infer T>
| DocumentReference<infer T>
? T
: R
24 changes: 22 additions & 2 deletions tests/firestore/collection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import { useCollection } from '../../src'
import { addDoc } from 'firebase/firestore'
import { setupRefs } from '../utils'
import { addDoc, collection, DocumentData } from 'firebase/firestore'
import { expectType, setupRefs, tds, firestore } from '../utils'
import { usePendingPromises } from '../../src/vuefire/firestore'
import { type Ref } from 'vue'

describe('Firestore collections', () => {
const { itemRef, listRef, orderedListRef } = setupRefs()
Expand Down Expand Up @@ -35,4 +36,23 @@ describe('Firestore collections', () => {
{ name: 'c' },
])
})

tds(() => {
const db = firestore
expectType<Ref<DocumentData[]>>(useCollection(collection(db, 'todos')))
// @ts-expect-error
expectType<Ref<number[]>>(useCollection(collection(db, 'todos')))

expectType<Ref<number[]>>(useCollection<number>(collection(db, 'todos')))
// @ts-expect-error
expectType<Ref<string[]>>(useCollection<number>(collection(db, 'todos')))

const refWithConverter = collection(db, 'todos').withConverter<number>({
toFirestore: data => ({ n: data }),
fromFirestore: (snap, options) => snap.data(options).n as number,
})
expectType<Ref<number[]>>(useCollection(refWithConverter))
// @ts-expect-error
expectType<Ref<string[]>>(useCollection(refWithConverter))
})
})
11 changes: 9 additions & 2 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
import { afterAll } from 'vitest'
import { isCollectionRef, isDocumentRef } from '../src/shared'

const firebaseApp = initializeApp({ projectId: 'vue-fire-store' })
const firestore = getFirestore(firebaseApp)
export const firebaseApp = initializeApp({ projectId: 'vue-fire-store' })
export const firestore = getFirestore(firebaseApp)
connectFirestoreEmulator(firestore, 'localhost', 8080)

let _id = 0
Expand Down Expand Up @@ -67,3 +67,10 @@ export async function recursiveDeleteDoc(doc: QueryDocumentSnapshot) {

export const sleep = (ms: number) =>
new Promise(resolve => setTimeout(resolve, ms))

// type testing utils

export function tds(_fn: () => any) {}
export function expectType<T>(_value: T): void {}
export function expectError<T>(_value: T): void {}
export function expectAssignable<T, T2 extends T = T>(_value: T2): void {}
2 changes: 1 addition & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default defineConfig({
coverage: {
include: ['src/**/*.ts'],
reporter: ['text', 'json', 'html'],
exclude: ['src/**/*.spec.ts', 'src/index.ts', 'src/**/index.ts'],
exclude: ['src/**/*.spec.ts', 'src/index.ts'],
},
},
})

0 comments on commit 57dbbc8

Please sign in to comment.