Skip to content

Commit cc01b84

Browse files
committed
fix(firestore): skip ref extraction in non pojo
Fix #1257
1 parent ed59873 commit cc01b84

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

src/firestore/utils.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
FirestoreDataConverter,
88
Timestamp,
99
} from 'firebase/firestore'
10-
import { isObject, isDocumentRef, TODO } from '../shared'
10+
import { isObject, isDocumentRef, TODO, isPOJO } from '../shared'
1111
import { VueFirestoreDocumentData } from '.'
1212

1313
export type FirestoreReference = Query | DocumentReference | CollectionReference
@@ -42,7 +42,7 @@ export function extractRefs(
4242
oldDoc: DocumentData | void,
4343
subs: Record<string, { path: string; data: () => DocumentData | null }>
4444
): [DocumentData, Record<string, DocumentReference>] {
45-
if (!isObject(doc)) return [doc, {}]
45+
if (!isPOJO(doc)) return [doc, {}]
4646

4747
const dataAndRefs: [DocumentData, Record<string, DocumentReference>] = [
4848
{},
@@ -115,6 +115,7 @@ export function extractRefs(
115115
refs,
116116
])
117117
} else if (isObject(ref)) {
118+
// dive into nested refs
118119
data[key] = {}
119120
recursiveExtract(ref, oldDoc[key], path + key + '.', [data[key], refs])
120121
} else {

src/shared.ts

+10
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ export function isObject(o: unknown): o is Record<any, unknown> {
9393
return !!o && typeof o === 'object'
9494
}
9595

96+
const ObjectPrototype = Object.prototype
97+
/**
98+
* Check if an object is a plain js object. Differently from `isObject()`, this excludes class instances.
99+
*
100+
* @param obj - object to check
101+
*/
102+
export function isPOJO(obj: unknown): obj is Record<any, unknown> {
103+
return isObject(obj) && Object.getPrototypeOf(obj) === ObjectPrototype
104+
}
105+
96106
/**
97107
* Checks if a variable is a Firestore Document Reference
98108
* @param o

tests/firestore/document.spec.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { mount } from '@vue/test-utils'
22
import { describe, expect, it } from 'vitest'
33
import {
4+
addDoc,
45
doc as originalDoc,
56
DocumentData,
67
DocumentReference,
78
FirestoreError,
89
} from 'firebase/firestore'
910
import { expectType, setupFirestoreRefs, tds, firestore } from '../utils'
1011
import { nextTick, ref, shallowRef, unref, type Ref } from 'vue'
11-
import { _MaybeRef, _Nullable } from '../../src/shared'
12+
import { isPOJO, _MaybeRef, _Nullable } from '../../src/shared'
1213
import {
1314
useDocument,
1415
VueFirestoreDocumentData,
@@ -147,6 +148,39 @@ describe(
147148
expect(error.value).toBeUndefined()
148149
})
149150

151+
it('can use a custom converter', async () => {
152+
class MyName {
153+
private _name: string
154+
constructor(name: string) {
155+
this._name = name
156+
}
157+
158+
get name() {
159+
return this._name
160+
}
161+
162+
set name(_newName: string) {
163+
// do nothing
164+
}
165+
}
166+
const itemRef = doc().withConverter<MyName>({
167+
toFirestore: (data) => ({ name: data.name }),
168+
fromFirestore: (snap) => new MyName(snap.get('name')),
169+
})
170+
await setDoc(itemRef, new MyName('a'))
171+
172+
const { wrapper, data, promise } = factory({ ref: itemRef })
173+
174+
await promise.value
175+
176+
expect(wrapper.vm.item).toHaveProperty('name', 'a')
177+
expect(isPOJO(wrapper.vm.item)).toBe(false)
178+
179+
// should respect the setter
180+
wrapper.vm.item!.name = 'b'
181+
expect(wrapper.vm.item).toHaveProperty('name', 'a')
182+
})
183+
150184
describe('reset option', () => {
151185
it('resets the value when specified', async () => {
152186
const { wrapper, itemRef, data } = factory({

0 commit comments

Comments
 (0)