123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- import { NetworkStatus } from '@apollo/client/core'
- import { useLazyQuery, useQuery } from '@vue/apollo-composable'
- import { effectScope } from 'vue'
- import { SampleTypedQueryDocument } from '#tests/fixtures/graphqlSampleTypes.ts'
- import type {
- SampleQuery,
- SampleQueryVariables,
- } from '#tests/fixtures/graphqlSampleTypes.ts'
- import createMockClient from '#tests/support/mock-apollo-client.ts'
- import { waitForNextTick, waitUntilSpyCalled } from '#tests/support/utils.ts'
- import { useNotifications } from '#shared/components/CommonNotifications/index.ts'
- import { GraphQLErrorTypes } from '#shared/types/error.ts'
- import QueryHandler from '../QueryHandler.ts'
- import type { ApolloError, ApolloQueryResult } from '@apollo/client/core'
- const queryFunctionCallSpy = vi.fn()
- const querySampleResult = {
- Sample: {
- __typename: 'Sample',
- id: 1,
- title: 'Test Title',
- text: 'Test Text',
- },
- }
- const querySampleErrorResult = {
- networkStatus: NetworkStatus.error,
- errors: [
- {
- message: 'GraphQL Error',
- extensions: { type: 'Exceptions::UnknownError' },
- },
- ],
- }
- const querySampleNetworkErrorResult = new Error('GraphQL Network Error')
- const handlerCallSpy = vi.fn()
- const mockClient = (error = false, errorType = 'GraphQL') => {
- handlerCallSpy.mockImplementation(() => {
- if (error) {
- return errorType === 'GraphQL'
- ? Promise.resolve(querySampleErrorResult)
- : Promise.reject(querySampleNetworkErrorResult)
- }
- return Promise.resolve({
- data: querySampleResult,
- })
- })
- createMockClient([
- {
- operationDocument: SampleTypedQueryDocument,
- handler: handlerCallSpy,
- },
- ])
- handlerCallSpy.mockClear()
- queryFunctionCallSpy.mockClear()
- }
- const waitFirstResult = (queryHandler: QueryHandler<any, any>) =>
- new Promise<ApolloQueryResult<any> | ApolloError>((resolve) => {
- queryHandler.onResult((res) => {
- if (res.data) {
- resolve(res)
- }
- })
- queryHandler.onError((err) => {
- resolve(err)
- })
- })
- describe('QueryHandler', () => {
- const sampleQuery = (variables: SampleQueryVariables, options = {}) => {
- queryFunctionCallSpy()
- const query = useQuery<SampleQuery, SampleQueryVariables>(
- SampleTypedQueryDocument,
- variables,
- options,
- )
- return query
- }
- const sampleLazyQuery = (variables: SampleQueryVariables, options = {}) => {
- queryFunctionCallSpy()
- return useLazyQuery<SampleQuery, SampleQueryVariables>(
- SampleTypedQueryDocument,
- variables,
- options,
- )
- }
- const scope = effectScope()
- describe('constructor', () => {
- beforeEach(() => {
- mockClient()
- })
- it('instance can be created', () => {
- scope.run(() => {
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- expect(queryHandlerObject).toBeInstanceOf(QueryHandler)
- })
- })
- it('default handler options can be changed', () => {
- scope.run(() => {
- const errorNotificationMessage = 'A test message.'
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }), {
- errorNotificationMessage,
- })
- expect(queryHandlerObject.handlerOptions.errorNotificationMessage).toBe(
- errorNotificationMessage,
- )
- })
- })
- it('given query function was executed', () => {
- scope.run(() => {
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- expect(queryFunctionCallSpy).toBeCalled()
- expect(queryHandlerObject.operationResult).toBeTruthy()
- })
- })
- })
- describe('loading', () => {
- beforeEach(() => {
- mockClient()
- })
- it('loading state will be updated', async () => {
- await scope.run(async () => {
- expect.assertions(2)
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- const loading = queryHandlerObject.loading()
- expect(loading.value).toBe(true)
- await waitFirstResult(queryHandlerObject)
- expect(loading.value).toBe(false)
- })
- })
- it('supports lazy queries', async () => {
- await scope.run(async () => {
- expect.assertions(3)
- const queryHandlerObject = new QueryHandler(sampleLazyQuery({ id: 1 }))
- expect(queryHandlerObject.loading().value).toBe(false)
- queryHandlerObject.load()
- await waitForNextTick()
- expect(queryHandlerObject.loading().value).toBe(true)
- await queryHandlerObject.query()
- expect(queryHandlerObject.loading().value).toBe(false)
- })
- })
- })
- describe('result', () => {
- beforeEach(() => {
- mockClient()
- })
- it('result is available', async () => {
- await scope.run(async () => {
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- const result = await waitFirstResult(queryHandlerObject)
- expect(result).toMatchObject({
- data: querySampleResult,
- })
- })
- })
- it('loaded result is also resolved after additional result call with active trigger refetch', async () => {
- await scope.run(async () => {
- const queryHandlerObject = new QueryHandler(sampleLazyQuery({ id: 1 }))
- await expect(queryHandlerObject.query()).resolves.toMatchObject({
- data: querySampleResult,
- })
- await expect(queryHandlerObject.query()).resolves.toMatchObject({
- data: querySampleResult,
- })
- expect(handlerCallSpy).toBeCalledTimes(1)
- })
- })
- it('watch on result change', async () => {
- await scope.run(async () => {
- expect.assertions(1)
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- queryHandlerObject.watchOnResult((result) => {
- expect(result).toEqual(querySampleResult)
- })
- await waitFirstResult(queryHandlerObject)
- })
- })
- it('on result trigger', async () => {
- await scope.run(async () => {
- expect.assertions(1)
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- queryHandlerObject.onResult((result) => {
- if (result.data) {
- expect(result.data).toEqual(querySampleResult)
- }
- })
- await waitFirstResult(queryHandlerObject)
- })
- })
- it('receive value immediately in non-reactive way', async () => {
- await scope.run(async () => {
- const queryHandlerObject = new QueryHandler(sampleLazyQuery({ id: 1 }))
- await expect(queryHandlerObject.query()).resolves.toEqual(
- expect.objectContaining({ data: querySampleResult }),
- )
- })
- })
- it('cancels previous attempt, if the new one started', async () => {
- await scope.run(async () => {
- const queryHandlerObject = new QueryHandler(sampleLazyQuery({ id: 1 }))
- const cancelSpy = vi.spyOn(queryHandlerObject, 'cancel')
- expect(cancelSpy).not.toHaveBeenCalled()
- const result1 = queryHandlerObject.query()
- expect(cancelSpy).toHaveBeenCalledTimes(1)
- const result2 = queryHandlerObject.query()
- expect(cancelSpy).toHaveBeenCalledTimes(2)
- // both resolve, because signal is not actually aborted in node
- await expect(result1).resolves.toEqual(
- expect.objectContaining({ data: querySampleResult }),
- )
- await expect(result2).resolves.toEqual(
- expect.objectContaining({ data: querySampleResult }),
- )
- })
- })
- })
- describe('error handling', () => {
- describe('GraphQL errors', () => {
- beforeEach(() => {
- mockClient(true)
- })
- it('notification is triggerd', async () => {
- await scope.run(async () => {
- expect.assertions(1)
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- await waitFirstResult(queryHandlerObject)
- const { notifications } = useNotifications()
- expect(notifications.value.length).toBe(1)
- })
- })
- it('use error callback', async () => {
- await scope.run(async () => {
- expect.assertions(1)
- const errorCallbackSpy = vi.fn()
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }), {
- errorCallback: (error) => {
- errorCallbackSpy(error)
- },
- })
- await waitFirstResult(queryHandlerObject)
- await waitUntilSpyCalled(errorCallbackSpy)
- expect(errorCallbackSpy).toHaveBeenCalledWith({
- type: 'Exceptions::UnknownError',
- message: 'GraphQL Error',
- })
- })
- })
- it('refetch with error', async () => {
- await scope.run(async () => {
- expect.assertions(1)
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- const errorCallbackSpy = vi.fn()
- await waitFirstResult(queryHandlerObject)
- // Refetch after first load again.
- await queryHandlerObject.refetch().catch((error) => {
- errorCallbackSpy(error)
- })
- expect(errorCallbackSpy).toHaveBeenCalled()
- })
- })
- })
- describe('Network errors', () => {
- beforeEach(() => {
- mockClient(true, 'NetworkError')
- })
- it('use error callback', async () => {
- await scope.run(async () => {
- expect.assertions(1)
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }), {
- errorCallback: (error) => {
- expect(error).toEqual({
- type: GraphQLErrorTypes.NetworkError,
- })
- },
- })
- await waitFirstResult(queryHandlerObject)
- })
- })
- })
- })
- describe('use operation result wrapper', () => {
- beforeEach(() => {
- mockClient()
- })
- it('use returned query options', () => {
- scope.run(() => {
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- expect(queryHandlerObject.options()).toBeTruthy()
- })
- })
- it('use fetchMore query function', async () => {
- await scope.run(async () => {
- const queryHandlerObject = new QueryHandler(sampleQuery({ id: 1 }))
- await expect(queryHandlerObject.fetchMore({})).resolves.toEqual(
- querySampleResult,
- )
- })
- })
- })
- })
|