diff --git a/packages/vue-urql/src/useQuery.test.ts b/packages/vue-urql/src/useQuery.test.ts index 62abb2d90c..396831d12a 100644 --- a/packages/vue-urql/src/useQuery.test.ts +++ b/packages/vue-urql/src/useQuery.test.ts @@ -1,5 +1,9 @@ -import { OperationResult, OperationResultSource } from '@urql/core'; -import { nextTick, reactive, ref } from 'vue'; +import { + OperationResult, + OperationResultSource, + RequestPolicy, +} from '@urql/core'; +import { computed, nextTick, reactive, ref } from 'vue'; import { vi, expect, it, describe } from 'vitest'; vi.mock('./useClient.ts', async () => ({ @@ -10,10 +14,28 @@ vi.mock('./useClient.ts', async () => ({ import { pipe, makeSubject, fromValue, delay } from 'wonka'; import { createClient } from '@urql/core'; -import { useQuery } from './useQuery'; +import { useQuery, UseQueryArgs } from './useQuery'; const client = createClient({ url: '/graphql', exchanges: [] }); +const createQuery = (args: UseQueryArgs) => { + const executeQuery = vi + .spyOn(client, 'executeQuery') + .mockImplementation(request => { + return pipe( + fromValue({ operation: request, data: { test: true } }), + delay(1) + ) as any; + }); + + const query$ = useQuery(args); + + return { + query$, + executeQuery, + }; +}; + describe('useQuery', () => { it('runs a query and updates data', async () => { const subject = makeSubject(); @@ -79,18 +101,9 @@ describe('useQuery', () => { }); it('runs queries as a promise-like that resolves even when the query changes', async () => { - const executeQuery = vi - .spyOn(client, 'executeQuery') - .mockImplementation(request => { - return pipe( - fromValue({ operation: request, data: { test: true } }), - delay(1) - ) as any; - }); - const doc = ref('{ test }'); - const query$ = useQuery({ + const { executeQuery, query$ } = createQuery({ query: doc, }); @@ -108,36 +121,194 @@ describe('useQuery', () => { ); }); - it('reacts to variables changing', async () => { - const executeQuery = vi - .spyOn(client, 'executeQuery') - .mockImplementation(request => { - return pipe( - fromValue({ operation: request, data: { test: true } }), - delay(1) - ) as any; - }); + it('reacts to ref variables changing', async () => { + const variables = ref({ prop: 1 }); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + variables, + }); + + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + expect(query$.operation.value).toHaveProperty('variables.prop', 1); + + variables.value.prop++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + expect(query$.operation.value).toHaveProperty('variables.prop', 2); + + variables.value = { prop: 3 }; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(3); + expect(query$.operation.value).toHaveProperty('variables.prop', 3); + }); + + it('reacts to nested ref variables changing', async () => { + const prop = ref(1); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + variables: { prop }, + }); + + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + expect(query$.operation.value).toHaveProperty('variables.prop', 1); + + prop.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + expect(query$.operation.value).toHaveProperty('variables.prop', 2); + }); + + it('reacts to deep nested ref variables changing', async () => { + const prop = ref(1); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + variables: { deep: { nested: { prop } } }, + }); + + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + expect(query$.operation.value).toHaveProperty( + 'variables.deep.nested.prop', + 1 + ); + + prop.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + expect(query$.operation.value).toHaveProperty( + 'variables.deep.nested.prop', + 2 + ); + }); + + it('reacts to reactive variables changing', async () => { + const prop = ref(1); + const variables = reactive({ prop: 1, deep: { nested: { prop } } }); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + variables, + }); + + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + expect(query$.operation.value).toHaveProperty('variables.prop', 1); + + variables.prop++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + expect(query$.operation.value).toHaveProperty('variables.prop', 2); + + prop.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(3); + expect(query$.operation.value).toHaveProperty( + 'variables.deep.nested.prop', + 2 + ); + }); + + it('reacts to computed variables changing', async () => { + const prop = ref(1); + const prop2 = ref(1); + const variables = computed(() => ({ + prop: prop.value, + deep: { nested: { prop2 } }, + })); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + variables, + }); + + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + expect(query$.operation.value).toHaveProperty('variables.prop', 1); + + prop.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + expect(query$.operation.value).toHaveProperty('variables.prop', 2); + + prop2.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(3); + expect(query$.operation.value).toHaveProperty( + 'variables.deep.nested.prop2', + 2 + ); + }); - const variables = { - test: ref(1), - }; - const query$ = useQuery({ - query: '{ test }', + it('reacts to callback variables changing', async () => { + const prop = ref(1); + const prop2 = ref(1); + const variables = () => ({ + prop: prop.value, + deep: { nested: { prop2 } }, + }); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), variables, }); await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + expect(query$.operation.value).toHaveProperty('variables.prop', 1); + + prop.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + expect(query$.operation.value).toHaveProperty('variables.prop', 2); + prop2.value++; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(3); + expect(query$.operation.value).toHaveProperty( + 'variables.deep.nested.prop2', + 2 + ); + }); + + it('reacts to reactive context argument', async () => { + const context = ref<{ requestPolicy: RequestPolicy }>({ + requestPolicy: 'cache-only', + }); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + context, + }); + + await query$; expect(executeQuery).toHaveBeenCalledTimes(1); - expect(query$.operation.value).toHaveProperty('variables.test', 1); + context.value.requestPolicy = 'network-only'; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + }); + + it('reacts to callback context argument', async () => { + const requestPolicy = ref('cache-only'); - variables.test.value = 2; + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + context: () => ({ + requestPolicy: requestPolicy.value, + }), + }); await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + requestPolicy.value = 'network-only'; + await query$; expect(executeQuery).toHaveBeenCalledTimes(2); - expect(query$.operation.value).toHaveProperty('variables.test', 2); }); it('pauses query when asked to do so', async () => { @@ -148,17 +319,15 @@ describe('useQuery', () => { () => subject.source as OperationResultSource ); - const pause = ref(true); - const _query = useQuery({ query: `{ test }`, - pause: () => pause.value, + pause: true, }); const query = reactive(_query); expect(executeQuery).not.toHaveBeenCalled(); - pause.value = false; + query.resume(); await nextTick(); expect(query.fetching).toBe(true); @@ -167,4 +336,70 @@ describe('useQuery', () => { expect(query.fetching).toBe(false); expect(query.data).toEqual({ test: true }); }); + + it('pauses query with ref variable', async () => { + const pause = ref(true); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + pause, + }); + + await query$; + expect(executeQuery).not.toHaveBeenCalled(); + + pause.value = false; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + + query$.pause(); + query$.resume(); + await query$; + expect(executeQuery).toHaveBeenCalledTimes(2); + }); + + it('pauses query with computed variable', async () => { + const pause = ref(true); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + pause: computed(() => pause.value), + }); + + await query$; + expect(executeQuery).not.toHaveBeenCalled(); + + pause.value = false; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + + query$.pause(); + query$.resume(); + await query$; + // this shouldn't be called, as pause/resume functionality should works in sync with passed `pause` variable, e.g.: + // if we pass readonly computed variable, then we want to make sure that its value fully controls the state of the request. + expect(executeQuery).toHaveBeenCalledTimes(1); + }); + + it('pauses query with callback', async () => { + const pause = ref(true); + + const { executeQuery, query$ } = createQuery({ + query: ref('{ test }'), + pause: () => pause.value, + }); + + await query$; + expect(executeQuery).not.toHaveBeenCalled(); + + pause.value = false; + await query$; + expect(executeQuery).toHaveBeenCalledTimes(1); + + query$.pause(); + query$.resume(); + await query$; + // the same as computed variable example - user has full control over the request state if using callback + expect(executeQuery).toHaveBeenCalledTimes(1); + }); });