Skip to content

Commit bb4fa53

Browse files
committed
test: add vanilla test for comparison
1 parent 9416747 commit bb4fa53

File tree

3 files changed

+251
-4
lines changed

3 files changed

+251
-4
lines changed

packages/dev-vue3-firestore/src/App.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { ref } from 'vue'
33
import ButtonToggle from './components/ButtonToggle.vue'
44
import TestFirestorePluginFetch from './components/TestFirestorePluginFetch.vue'
55
import TestFirestorePluginStream from './components/TestFirestorePluginStream.vue'
6+
import TestVanillaJsSDK from './components/TestVanillaJsSDK.vue'
67
7-
const example = ref<'stream' | 'fetch'>('stream')
8+
const example = ref<'stream' | 'fetch' | 'vanilla'>('stream')
89
</script>
910

1011
<template>
@@ -14,12 +15,14 @@ const example = ref<'stream' | 'fetch'>('stream')
1415
:options="[
1516
{ label: 'Stream Example', value: 'stream' },
1617
{ label: 'Fetch Example', value: 'fetch' },
18+
{ label: 'Vanilla JS SDK Test', value: 'vanilla' },
1719
]"
1820
style="margin-bottom: 1rem"
1921
/>
2022

2123
<TestFirestorePluginStream v-if="example === 'stream'" />
2224
<TestFirestorePluginFetch v-if="example === 'fetch'" />
25+
<TestVanillaJsSDK v-if="example === 'vanilla'" />
2326
</div>
2427
</template>
2528

packages/dev-vue3-firestore/src/components/TestFirestorePluginStream.vue

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,31 @@ function deleteItem(item: Item) {
7474
itemsModule.delete(item.id)
7575
}
7676
77-
function testModification() {
78-
for (const item of items.value) {
79-
itemsModule.doc(item.id).merge({ title: item.title + '.' })
77+
// function testModification() {
78+
// for (const item of items.value) {
79+
// itemsModule.doc(item.id).merge({ title: item.title + '.' })
80+
// }
81+
// }
82+
async function testModification() {
83+
const itemsToTest = items.value
84+
if (itemsToTest.length === 0) {
85+
console.warn('No items to test')
86+
return
8087
}
88+
89+
console.log(`🧪 Testing ${itemsToTest.length} items - First loop`)
90+
// First loop: update all items with 100ms syncDebounceMs
91+
for (const item of itemsToTest) {
92+
itemsModule.doc(item.id).merge({ title: item.title + '.' }, { syncDebounceMs: 100 })
93+
}
94+
await new Promise((resolve) => setTimeout(resolve, 100))
95+
96+
console.log(`🧪 Testing ${itemsToTest.length} items - Second loop`)
97+
// Second loop: update all items again with 100ms syncDebounceMs
98+
for (const item of itemsToTest) {
99+
itemsModule.doc(item.id).merge({ title: item.title + '..' }, { syncDebounceMs: 100 })
100+
}
101+
await new Promise((resolve) => setTimeout(resolve, 100))
81102
}
82103
</script>
83104

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
<script setup lang="ts">
2+
import { computed, reactive, ref, onBeforeUnmount, onMounted } from 'vue'
3+
import {
4+
collection,
5+
onSnapshot,
6+
query,
7+
QuerySnapshot,
8+
DocumentChange,
9+
doc,
10+
writeBatch,
11+
} from 'firebase/firestore'
12+
import { db } from '../initFirebase.js'
13+
import TodoApp from './TodoApp.vue'
14+
15+
type Item = { title: string; id: string; isDone: boolean }
16+
17+
function defaultsItem(payload: Partial<Item>): Item {
18+
return { title: '', id: '', isDone: false, ...payload }
19+
}
20+
21+
// Use the same structure as plugin-vue3: reactive Map
22+
const data: { [collectionPath: string]: Map<string, { [key: string]: unknown }> } = {}
23+
const collectionPath = 'magnetarTests/dev-firestore/items'
24+
const itemsMap = reactive(new Map<string, { [key: string]: unknown }>())
25+
data[collectionPath] = itemsMap
26+
27+
/** Item count */
28+
const size = computed(() => itemsMap.size)
29+
30+
/** Options to filter items */
31+
const showAll = ref(true)
32+
const alphabetically = ref(false)
33+
34+
/** The items shown based on the filter - same pattern as TestFirestorePluginStream */
35+
const items = computed<Item[]>(() => {
36+
console.log(`items running`)
37+
const _showAll = showAll.value
38+
const _alphabetically = alphabetically.value
39+
40+
let filteredItems = Array.from(itemsMap.values()) as Item[]
41+
42+
if (!_showAll) {
43+
filteredItems = filteredItems.filter((item) => !item.isDone)
44+
}
45+
46+
if (_alphabetically) {
47+
filteredItems = [...filteredItems].sort((a, b) => a.title.localeCompare(b.title))
48+
}
49+
50+
return filteredItems
51+
})
52+
53+
let unsubscribe: (() => void) | null = null
54+
55+
onMounted(() => {
56+
const collectionRef = collection(db, collectionPath)
57+
const q = query(collectionRef)
58+
59+
// Pool querySnapshots that arrive while waiting (like plugin-firestore does)
60+
const pendingSnapshots: QuerySnapshot[] = []
61+
let isProcessing = false
62+
63+
const processDocChanges = (querySnapshot: QuerySnapshot) => {
64+
querySnapshot.docChanges().forEach((docChange: DocumentChange) => {
65+
const docData = docChange.doc.data()
66+
const docId = docChange.doc.id
67+
68+
if (docChange.type === 'added' || docChange.type === 'modified') {
69+
// Mimic what insert does: set empty object, then assign properties
70+
itemsMap.set(docId, {})
71+
const docDataToMutate = itemsMap.get(docId)
72+
if (docDataToMutate) {
73+
Object.entries(defaultsItem({ ...docData, id: docId })).forEach(([key, value]) => {
74+
docDataToMutate[key] = value
75+
})
76+
}
77+
} else if (docChange.type === 'removed') {
78+
itemsMap.delete(docId)
79+
}
80+
})
81+
}
82+
83+
unsubscribe = onSnapshot(
84+
q,
85+
async (querySnapshot: QuerySnapshot) => {
86+
console.log(
87+
`[${new Date().toISOString()}] onSnapshot callback fired with ${querySnapshot.docChanges().length} changes`,
88+
)
89+
// Add to pending pool
90+
pendingSnapshots.push(querySnapshot)
91+
92+
// If already processing, let the current processor handle it
93+
if (isProcessing) return
94+
95+
// Simulate write lock await (like plugin-firestore does)
96+
// This might cause Vue to flush updates before processing all changes
97+
isProcessing = true
98+
await new Promise((resolve) => setTimeout(resolve, 1000))
99+
100+
// Process all pending snapshots synchronously (like plugin-firestore does)
101+
console.log(
102+
`[${new Date().toISOString()}] starting to process ${pendingSnapshots.length} snapshots`,
103+
)
104+
let snapshot = pendingSnapshots.shift()
105+
while (snapshot) {
106+
processDocChanges(snapshot)
107+
// Add heavy synchronous work between processing snapshots (to mimic Magnetar's extra processing)
108+
// This might cause Vue to flush updates between snapshots
109+
let heavyWork = 0
110+
for (let i = 0; i < 100_000_000; i++) {
111+
heavyWork += Math.sqrt(i) * Math.random()
112+
}
113+
snapshot = pendingSnapshots.shift()
114+
}
115+
116+
isProcessing = false
117+
},
118+
(error) => {
119+
console.error('Stream error:', error)
120+
},
121+
)
122+
})
123+
124+
onBeforeUnmount(() => {
125+
if (unsubscribe) {
126+
unsubscribe()
127+
}
128+
})
129+
130+
function addItem(item: Item) {
131+
// Not implemented - just for compatibility with TodoApp
132+
}
133+
134+
function editItem(item: Item) {
135+
// Not implemented - just for compatibility with TodoApp
136+
}
137+
138+
function deleteItem(item: Item) {
139+
// Not implemented - just for compatibility with TodoApp
140+
}
141+
142+
async function testModification() {
143+
const itemsToTest = items.value
144+
if (itemsToTest.length === 0) {
145+
console.warn('No items to test')
146+
return
147+
}
148+
149+
console.log(`🧪 Testing ${itemsToTest.length} items - First loop`)
150+
// First loop: make local updates (optimistic) then prepare batch
151+
// In Magnetar, merges are called synchronously, added to stack, countdown starts async
152+
for (const item of itemsToTest) {
153+
const docData = itemsMap.get(item.id)
154+
if (docData) {
155+
;(docData as any).title = item.title + '.'
156+
}
157+
}
158+
// Prepare first batch (mimicking how merges are added to the stack)
159+
const batch1 = writeBatch(db)
160+
for (const item of itemsToTest) {
161+
const docRef = doc(db, collectionPath, item.id)
162+
batch1.update(docRef, {
163+
title: item.title + '.',
164+
})
165+
}
166+
// In Magnetar, the countdown runs async and commits after 100ms
167+
// We wait 100ms then commit (mimicking the countdown finishing)
168+
const commit1 = new Promise<void>((resolve) => {
169+
setTimeout(async () => {
170+
await batch1.commit()
171+
resolve()
172+
}, 100)
173+
})
174+
175+
// Wait 100ms (matching the test pattern - this happens while countdown is running)
176+
await new Promise((resolve) => setTimeout(resolve, 100))
177+
await commit1
178+
179+
console.log(`🧪 Testing ${itemsToTest.length} items - Second loop`)
180+
// Second loop: make local updates (optimistic) then prepare batch
181+
for (const item of itemsToTest) {
182+
const docData = itemsMap.get(item.id)
183+
if (docData) {
184+
;(docData as any).title = item.title + '..'
185+
}
186+
}
187+
// Prepare second batch (mimicking how merges are added to a new stack)
188+
const batch2 = writeBatch(db)
189+
for (const item of itemsToTest) {
190+
const docRef = doc(db, collectionPath, item.id)
191+
batch2.update(docRef, {
192+
title: item.title + '..',
193+
})
194+
}
195+
// In Magnetar, the countdown runs async and commits after 100ms
196+
const commit2 = new Promise<void>((resolve) => {
197+
setTimeout(async () => {
198+
await batch2.commit()
199+
resolve()
200+
}, 100)
201+
})
202+
203+
// Wait 100ms (matching the test pattern - this happens while countdown is running)
204+
await new Promise((resolve) => setTimeout(resolve, 100))
205+
await commit2
206+
}
207+
</script>
208+
209+
<template>
210+
<div class="_test">
211+
<h6>Vanilla JS SDK Test ({{ size }} items)</h6>
212+
<div style="margin: 1rem">
213+
<label for="all">show done items</label>
214+
<input type="checkbox" name="" v-model="showAll" id="all" />
215+
<label for="order" style="padding-left: 1rem">alphabetically</label>
216+
<input type="checkbox" name="" v-model="alphabetically" id="order" />
217+
</div>
218+
<div style="margin: 1rem">
219+
<button @click="() => testModification()">test updating {{ items.length }} items</button>
220+
</div>
221+
<TodoApp @add="addItem" @edit="editItem" @delete="deleteItem" :items="items" />
222+
</div>
223+
</template>

0 commit comments

Comments
 (0)