[READ-ONLY] Mirror of https://github.com/danielroe/zero-vue. Vue bindings for Zero
sync vue zero
0

Configure Feed

Select the types of activity you want to include in your feed.

zero-vue / src / query.test.ts
9.5 kB 412 lines
1import type { TTL } from '@rocicorp/zero' 2import { 3 createBuilder, 4 createSchema, 5 defineMutatorsWithType, 6 defineMutatorWithType, 7 defineQueriesWithType, 8 defineQuery, 9 number, 10 string, 11 table, 12 Zero, 13} from '@rocicorp/zero' 14import { describe, expect, it, onTestFinished, vi } from 'vitest' 15import { nextTick, ref, watchEffect } from 'vue' 16import z from 'zod' 17import { createZeroComposables } from './create-zero-composables' 18import { useQuery } from './query' 19import { VueView, vueViewFactory } from './view' 20 21const schema = createSchema({ 22 tables: [ 23 table('table') 24 .columns({ 25 a: number(), 26 b: string(), 27 }) 28 .primaryKey('a'), 29 ], 30}) 31 32async function setupTestEnvironment() { 33 const userID = ref('asdf') 34 35 const defineMutators = defineMutatorsWithType<typeof schema>() 36 const defineMutator = defineMutatorWithType<typeof schema>() 37 const mutators = defineMutators({ 38 table: { 39 insert: defineMutator( 40 z.object({ a: z.number(), b: z.string() }), 41 async ({ tx, args: { a, b } }) => { 42 return tx.mutate.table.insert({ a, b }) 43 }, 44 ), 45 update: defineMutator( 46 z.object({ a: z.number(), b: z.string() }), 47 async ({ tx, args: { a, b } }) => { 48 return tx.mutate.table.update({ a, b }) 49 }, 50 ), 51 }, 52 }) 53 54 const { useZero, useQuery } = createZeroComposables(() => ({ 55 userID: userID.value, 56 server: null, 57 schema, 58 mutators, 59 kvStore: 'mem', 60 })) 61 62 const zero = useZero() 63 await zero.value.mutate(mutators.table.insert({ a: 1, b: 'a' })).client 64 await zero.value.mutate(mutators.table.insert({ a: 2, b: 'b' })).client 65 66 const zql = createBuilder(schema) 67 const defineQueries = defineQueriesWithType<typeof schema>() 68 const queries = defineQueries({ 69 byId: defineQuery( 70 z.number(), 71 ({ args: a }) => zql.table.where('a', a), 72 ), 73 table: defineQuery( 74 () => zql.table, 75 ), 76 }) 77 78 onTestFinished(async () => { 79 await zero.value.close() 80 }) 81 82 return { zero, queries, useQuery, mutators, userID } 83} 84 85describe('useQuery', () => { 86 it('useQuery', async () => { 87 const { zero, mutators, queries, useQuery } = await setupTestEnvironment() 88 const { data: rows, status } = useQuery(() => queries.table()) 89 expect(rows.value).toMatchInlineSnapshot(`[ 90 { 91 "a": 1, 92 "b": "a", 93 Symbol(rc): 1, 94 }, 95 { 96 "a": 2, 97 "b": "b", 98 Symbol(rc): 1, 99 }, 100]`) 101 expect(status.value).toEqual('unknown') 102 103 await zero.value.mutate(mutators.table.insert({ a: 3, b: 'c' })).client 104 await nextTick() 105 106 expect(rows.value).toMatchInlineSnapshot(`[ 107 { 108 "a": 1, 109 "b": "a", 110 Symbol(rc): 1, 111 }, 112 { 113 "a": 2, 114 "b": "b", 115 Symbol(rc): 1, 116 }, 117 { 118 "a": 3, 119 "b": "c", 120 Symbol(rc): 1, 121 }, 122]`) 123 124 // TODO: this is not working at the moment, possibly because we don't have a server connection in test 125 // expect(resultType.value).toEqual("complete"); 126 }) 127 128 it('useQuery with ttl', async () => { 129 const { zero, queries, useQuery } = await setupTestEnvironment() 130 131 const ttl = ref<TTL>('1m') 132 133 const materializeSpy = vi.spyOn(zero.value, 'materialize') 134 const queryGetter = vi.fn(() => queries.table()) 135 136 useQuery(queryGetter, () => ({ ttl: ttl.value })) 137 expect(queryGetter).toHaveBeenCalledTimes(1) 138 139 expect(materializeSpy).toHaveLastReturnedWith(expect.any(VueView)) 140 expect(materializeSpy).toHaveBeenCalledExactlyOnceWith( 141 expect.any(Object), 142 vueViewFactory, 143 { ttl: '1m' }, 144 ) 145 expect(materializeSpy.mock.calls[0]![0]).toMatchInlineSnapshot(` 146 QueryImpl { 147 "customQueryID": { 148 "args": [], 149 "name": "table", 150 }, 151 "format": { 152 "relationships": {}, 153 "singular": false, 154 }, 155 "limit": [Function], 156 "one": [Function], 157 "orderBy": [Function], 158 "related": [Function], 159 "start": [Function], 160 "where": [Function], 161 "whereExists": [Function], 162 Symbol(): true, 163 } 164 `) 165 166 const view: VueView = materializeSpy.mock.results[0]!.value 167 const updateTTLSpy = vi.spyOn(view, 'updateTTL') 168 169 materializeSpy.mockClear() 170 171 ttl.value = '10m' 172 await nextTick() 173 174 expect(materializeSpy).toHaveBeenCalledTimes(0) 175 expect(updateTTLSpy).toHaveBeenCalledExactlyOnceWith('10m') 176 }) 177 178 it('useQuery supports disabled maybe queries', async () => { 179 const { zero, queries, useQuery } = await setupTestEnvironment() 180 const enabled = ref(false) 181 const materializeSpy = vi.spyOn(zero.value, 'materialize') 182 183 const { data: rows, status } = useQuery(() => enabled.value && queries.table()) 184 185 expect(rows.value).toBeUndefined() 186 expect(status.value).toBe('disabled') 187 expect(materializeSpy).not.toHaveBeenCalled() 188 189 enabled.value = true 190 await nextTick() 191 192 expect(materializeSpy).toHaveBeenCalledTimes(1) 193 expect(rows.value).toMatchInlineSnapshot(` 194[ 195 { 196 "a": 1, 197 "b": "a", 198 Symbol(rc): 1, 199 }, 200 { 201 "a": 2, 202 "b": "b", 203 Symbol(rc): 1, 204 }, 205] 206`) 207 expect(status.value).toEqual('unknown') 208 209 const view: VueView = materializeSpy.mock.results[0]!.value 210 const destroySpy = vi.spyOn(view, 'destroy') 211 212 enabled.value = false 213 await nextTick() 214 215 expect(destroySpy).toHaveBeenCalledTimes(1) 216 expect(rows.value).toBeUndefined() 217 expect(status.value).toBe('disabled') 218 }) 219 220 it('useQuery deps change', async () => { 221 const { queries, useQuery } = await setupTestEnvironment() 222 223 const a = ref(1) 224 225 const { data: rows, status } = useQuery(() => 226 queries.byId(a.value), 227 ) 228 229 const rowLog: unknown[] = [] 230 const resultDetailsLog: unknown[] = [] 231 const resetLogs = () => { 232 rowLog.length = 0 233 resultDetailsLog.length = 0 234 } 235 236 watchEffect(() => { 237 rowLog.push(rows.value) 238 }) 239 240 watchEffect(() => { 241 resultDetailsLog.push(status.value) 242 }) 243 244 expect(rowLog).toMatchInlineSnapshot(`[ 245 [ 246 { 247 "a": 1, 248 "b": "a", 249 Symbol(rc): 1, 250 }, 251 ], 252]`) 253 // expect(resultDetailsLog).toEqual(['unknown']) 254 resetLogs() 255 256 expect(rowLog).toEqual([]) 257 // expect(resultDetailsLog).toEqual(['complete']) 258 resetLogs() 259 260 a.value = 2 261 await nextTick() 262 263 expect(rowLog).toMatchInlineSnapshot(`[ 264 [ 265 { 266 "a": 2, 267 "b": "b", 268 Symbol(rc): 1, 269 }, 270 ], 271]`) 272 // expect(resultDetailsLog).toEqual(["unknown"]); 273 resetLogs() 274 275 expect(rowLog).toEqual([]) 276 // expect(resultDetailsLog).toEqual(["complete"]); 277 }) 278 279 it('useQuery deps change watchEffect', async () => { 280 const { zero, queries, mutators, useQuery } = await setupTestEnvironment() 281 const a = ref(1) 282 const { data: rows } = useQuery(() => queries.byId(a.value)) 283 284 let run = 0 285 286 await new Promise((resolve) => { 287 watchEffect(() => { 288 if (run === 0) { 289 expect(rows.value).toMatchInlineSnapshot( 290 `[ 291 { 292 "a": 1, 293 "b": "a", 294 Symbol(rc): 1, 295 }, 296]`, 297 ) 298 zero.value.mutate(mutators.table.update({ a: 1, b: 'a2' })) 299 } 300 else if (run === 1) { 301 expect(rows.value).toMatchInlineSnapshot( 302 `[ 303 { 304 "a": 1, 305 "b": "a2", 306 Symbol(rc): 1, 307 }, 308]`, 309 ) 310 a.value = 2 311 } 312 else if (run === 2) { 313 expect(rows.value).toMatchInlineSnapshot( 314 `[ 315 { 316 "a": 2, 317 "b": "b", 318 Symbol(rc): 1, 319 }, 320]`, 321 ) 322 resolve(true) 323 } 324 run++ 325 }) 326 }) 327 }) 328 329 it('can still be used without createZero', async () => { 330 const defineMutators = defineMutatorsWithType<typeof schema>() 331 const defineMutator = defineMutatorWithType<typeof schema>() 332 const mutators = defineMutators({ 333 table: { 334 insert: defineMutator( 335 z.object({ a: z.number(), b: z.string() }), 336 async ({ tx, args: { a, b } }) => { 337 return tx.mutate.table.insert({ a, b }) 338 }, 339 ), 340 }, 341 }) 342 const zero = new Zero({ 343 userID: 'test-user', 344 server: null, 345 schema, 346 mutators, 347 kvStore: 'mem' as const, 348 }) 349 await zero.mutate(mutators.table.insert({ a: 1, b: 'a' })).client 350 await zero.mutate(mutators.table.insert({ a: 2, b: 'b' })).client 351 352 const zql = createBuilder(schema) 353 const defineQueries = defineQueriesWithType<typeof schema>() 354 const queries = defineQueries({ 355 table: defineQuery( 356 () => zql.table, 357 ), 358 }) 359 360 const { data: rows, status } = useQuery(zero, () => queries.table()) 361 expect(rows.value).toMatchInlineSnapshot(`[ 362 { 363 "a": 1, 364 "b": "a", 365 Symbol(rc): 1, 366 }, 367 { 368 "a": 2, 369 "b": "b", 370 Symbol(rc): 1, 371 }, 372]`) 373 expect(status.value).toEqual('unknown') 374 }) 375 376 it('still works with legacy queries', async () => { 377 const schema = createSchema({ 378 tables: [ 379 table('table') 380 .columns({ 381 a: number(), 382 b: string(), 383 }) 384 .primaryKey('a'), 385 ], 386 enableLegacyQueries: true, 387 enableLegacyMutators: true, 388 }) 389 390 const zero = new Zero({ 391 userID: 'test-user', 392 server: null, 393 schema, 394 kvStore: 'mem' as const, 395 }) 396 397 const { data } = useQuery(zero, zero.query.table) 398 expect(data.value).toEqual([]) 399 400 await zero.mutate.table.insert({ a: 1, b: 'a2' }) 401 402 expect(data.value).toMatchInlineSnapshot(` 403 [ 404 { 405 "a": 1, 406 "b": "a2", 407 Symbol(rc): 1, 408 }, 409 ] 410 `) 411 }) 412})