From 15c76253aa885ecf5b10590728f618f04cc4833a Mon Sep 17 00:00:00 2001 From: Anbraten Date: Mon, 9 May 2022 20:26:15 +0200 Subject: [PATCH] feat: add error --- package.json | 1 + pnpm-lock.yaml | 7 ++++ src/useFind.ts | 20 ++++++++--- src/useGet.ts | 19 +++++++--- test/useFind.test.ts | 82 +++++++++++++++++++++++++++++++++++++++++++- test/useGet.test.ts | 82 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 200 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index a42158c..4c60f86 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@feathersjs/adapter-commons": "5.0.0-pre.15", "@feathersjs/feathers": "5.0.0-pre.15", + "@feathersjs/errors": "5.0.0-pre.15", "@geprog/eslint-config": "1.0.2", "@geprog/semantic-release-config": "1.0.0", "@jest/types": "27.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93e8f8b..8cf50f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,7 @@ lockfileVersion: 5.3 specifiers: '@feathersjs/adapter-commons': 5.0.0-pre.15 + '@feathersjs/errors': 5.0.0-pre.15 '@feathersjs/feathers': 5.0.0-pre.15 '@geprog/eslint-config': 1.0.2 '@geprog/semantic-release-config': 1.0.0 @@ -25,6 +26,7 @@ dependencies: devDependencies: '@feathersjs/adapter-commons': 5.0.0-pre.15 + '@feathersjs/errors': 5.0.0-pre.15 '@feathersjs/feathers': 5.0.0-pre.15 '@geprog/eslint-config': 1.0.2_ecf83b2648d3110d9036a9b8069097d8 '@geprog/semantic-release-config': 1.0.0_semantic-release@19.0.2 @@ -410,6 +412,11 @@ packages: engines: {node: '>= 12'} dev: true + /@feathersjs/errors/5.0.0-pre.15: + resolution: {integrity: sha512-MuNZWN9FI8J2zBx+02fp3UlRxcZwvEaUrshTEiyFiEyb0PPY4m7h53K/0ps0IINimSha03LGwnNf1gxDJWkyDQ==} + engines: {node: '>= 12'} + dev: true + /@feathersjs/errors/5.0.0-pre.16: resolution: {integrity: sha512-hp41eSglMpRsPuwWGAk7H3j2M80RIbn65/QOWsfu8ZmIQDQIEAcmqDWnzu80lPzXoh01deOdcBdz3+6YQEZBQA==} engines: {node: '>= 12'} diff --git a/src/useFind.ts b/src/useFind.ts index 0ec4388..b3c277f 100644 --- a/src/useFind.ts +++ b/src/useFind.ts @@ -1,3 +1,4 @@ +import type { FeathersError } from '@feathersjs/errors'; import type { Application, FeathersService, Params, ServiceMethods } from '@feathersjs/feathers'; import sift from 'sift'; import { getCurrentInstance, onBeforeUnmount, Ref, ref, watch } from 'vue'; @@ -73,6 +74,7 @@ function loadServiceEventHandlers< export type UseFind = { data: Ref; isLoading: Ref; + error: Ref; unload: () => void; }; @@ -94,22 +96,30 @@ export default (feathers: CustomApplicati // type cast is fine here (source: https://github.com/vuejs/vue-next/issues/2136#issuecomment-693524663) const data = ref([]) as Ref; const isLoading = ref(false); + const error = ref(); const service = feathers.service(serviceName as string); const unloadEventHandlers = loadServiceEventHandlers(service, params, data); const find = async () => { isLoading.value = true; + error.value = undefined; + if (!params.value) { data.value = []; isLoading.value = false; return; } - // TODO: the typecast below is necessary due to the prerelease state of feathers v5. The problem there is - // that the AdapterService interface is not yet updated and is not compatible with the ServiceMethods interface. - const res = await (service as unknown as ServiceMethods).find(params.value); - data.value = Array.isArray(res) ? res : [res]; + try { + // TODO: the typecast below is necessary due to the prerelease state of feathers v5. The problem there is + // that the AdapterService interface is not yet updated and is not compatible with the ServiceMethods interface. + const res = await (service as unknown as ServiceMethods).find(params.value); + data.value = Array.isArray(res) ? res : [res]; + } catch (_error) { + error.value = _error as FeathersError; + } + isLoading.value = false; }; @@ -129,5 +139,5 @@ export default (feathers: CustomApplicati onBeforeUnmount(unload); } - return { data, isLoading, unload }; + return { data, isLoading, unload, error }; }; diff --git a/src/useGet.ts b/src/useGet.ts index 2ad4260..3d5b475 100644 --- a/src/useGet.ts +++ b/src/useGet.ts @@ -1,3 +1,4 @@ +import type { FeathersError } from '@feathersjs/errors'; import type { Application, FeathersService, Id, Params, ServiceMethods } from '@feathersjs/feathers'; import { getCurrentInstance, onBeforeUnmount, Ref, ref, watch } from 'vue'; @@ -56,6 +57,7 @@ function loadServiceEventHandlers< export type UseGet = { data: Ref; isLoading: Ref; + error: Ref; unload: () => void; }; @@ -77,6 +79,7 @@ export default (feathers: CustomApplicati ): UseGet => { const data = ref(); const isLoading = ref(false); + const error = ref(); const service = feathers.service(serviceName as string); @@ -84,14 +87,22 @@ export default (feathers: CustomApplicati const get = async () => { isLoading.value = true; + error.value = undefined; + if (!_id.value) { data.value = undefined; isLoading.value = false; return; } - // TODO: the typecast below is necessary due to the prerelease state of feathers v5. The problem there is - // that the AdapterService interface is not yet updated and is not compatible with the ServiceMethods interface. - data.value = await (service as unknown as ServiceMethods).get(_id.value, params.value); + + try { + // TODO: the typecast below is necessary due to the prerelease state of feathers v5. The problem there is + // that the AdapterService interface is not yet updated and is not compatible with the ServiceMethods interface. + data.value = await (service as unknown as ServiceMethods).get(_id.value, params.value); + } catch (_error) { + error.value = _error as FeathersError; + } + isLoading.value = false; }; @@ -111,5 +122,5 @@ export default (feathers: CustomApplicati onBeforeUnmount(unload); } - return { isLoading, data, unload }; + return { isLoading, data, error, unload }; }; diff --git a/test/useFind.test.ts b/test/useFind.test.ts index d920e02..40739ba 100644 --- a/test/useFind.test.ts +++ b/test/useFind.test.ts @@ -1,3 +1,4 @@ +import { FeathersError, GeneralError } from '@feathersjs/errors'; import type { Application, Params } from '@feathersjs/feathers'; import { nextTick, ref } from 'vue'; @@ -107,7 +108,7 @@ describe('Find composition', () => { }); it('should indicate data finished loading', async () => { - expect.assertions(2); + expect.assertions(3); // given let serviceFindPromiseResolve: (value: TestModel[] | PromiseLike) => void = jest.fn(); @@ -132,12 +133,53 @@ describe('Find composition', () => { findComposition = useFind('testModels'); }); + // before when + expect(findComposition).toBeTruthy(); + expect(findComposition && findComposition.isLoading.value).toBeTruthy(); + // when serviceFindPromiseResolve(testModels); await nextTick(); // then + expect(findComposition && findComposition.isLoading.value).toBeFalsy(); + }); + + it('should indicate data finished loading even if an error occurred', async () => { + expect.assertions(3); + + // given + let serviceFindPromiseReject: (reason: FeathersError) => void = jest.fn(); + const serviceFind = jest.fn( + () => + new Promise((resolve, reject) => { + serviceFindPromiseReject = reject; + }), + ); + const feathersMock = { + service: () => ({ + find: serviceFind, + on: jest.fn(), + off: jest.fn(), + }), + on: jest.fn(), + off: jest.fn(), + } as unknown as Application; + const useFind = useFindOriginal(feathersMock); + let findComposition = null as UseFind | null; + mountComposition(() => { + findComposition = useFind('testModels'); + }); + + // before when expect(findComposition).toBeTruthy(); + expect(findComposition && findComposition.isLoading.value).toBeTruthy(); + + // when + serviceFindPromiseReject(new GeneralError('test error')); + await nextTick(); + + // then expect(findComposition && findComposition.isLoading.value).toBeFalsy(); }); @@ -341,6 +383,44 @@ describe('Find composition', () => { expect(findComposition && findComposition.data.value).toStrictEqual([additionalTestModel]); }); + it('should set an error if something failed', async () => { + expect.assertions(3); + + // given + let serviceFindPromiseReject: (reason: FeathersError) => void = jest.fn(); + const serviceFind = jest.fn( + () => + new Promise((resolve, reject) => { + serviceFindPromiseReject = reject; + }), + ); + const feathersMock = { + service: () => ({ + find: serviceFind, + on: jest.fn(), + off: jest.fn(), + }), + on: jest.fn(), + off: jest.fn(), + } as unknown as Application; + const useFind = useFindOriginal(feathersMock); + let findComposition = null as UseFind | null; + mountComposition(() => { + findComposition = useFind('testModels'); + }); + + // before when + expect(findComposition).toBeTruthy(); + expect(findComposition && findComposition.error.value).toBeFalsy(); + + // when + serviceFindPromiseReject(new GeneralError('test error')); + await nextTick(); + + // then + expect(findComposition && findComposition.error.value).toBeTruthy(); + }); + describe('Event Handlers', () => { it('should listen to "create" events', () => { expect.assertions(2); diff --git a/test/useGet.test.ts b/test/useGet.test.ts index 083b3ea..630a917 100644 --- a/test/useGet.test.ts +++ b/test/useGet.test.ts @@ -1,3 +1,4 @@ +import { FeathersError, GeneralError } from '@feathersjs/errors'; import type { Application } from '@feathersjs/feathers'; import { nextTick, ref } from 'vue'; @@ -104,7 +105,7 @@ describe('Get composition', () => { }); it('should indicate data finished loading', async () => { - expect.assertions(2); + expect.assertions(3); // given let serviceGetPromiseResolve: (value: TestModel | PromiseLike) => void = jest.fn(); @@ -129,12 +130,53 @@ describe('Get composition', () => { getComposition = useGet('testModels', ref(testModel._id)); }); + // before when + expect(getComposition).toBeTruthy(); + expect(getComposition && getComposition.isLoading.value).toBeTruthy(); + // when serviceGetPromiseResolve(testModel); await nextTick(); // then + expect(getComposition && getComposition.isLoading.value).toBeFalsy(); + }); + + it('should indicate data finished loading even if an error occurred', async () => { + expect.assertions(3); + + // given + let serviceGetPromiseReject: (reason: FeathersError) => void = jest.fn(); + const serviceGet = jest.fn( + () => + new Promise((resolve, reject) => { + serviceGetPromiseReject = reject; + }), + ); + const feathersMock = { + service: () => ({ + get: serviceGet, + on: jest.fn(), + off: jest.fn(), + }), + on: jest.fn(), + off: jest.fn(), + } as unknown as Application; + const useGet = useGetOriginal(feathersMock); + let getComposition = null as UseGet | null; + mountComposition(() => { + getComposition = useGet('testModels', ref(testModel._id)); + }); + + // before when expect(getComposition).toBeTruthy(); + expect(getComposition && getComposition.isLoading.value).toBeTruthy(); + + // when + serviceGetPromiseReject(new GeneralError('test error')); + await nextTick(); + + // then expect(getComposition && getComposition.isLoading.value).toBeFalsy(); }); @@ -260,6 +302,44 @@ describe('Get composition', () => { expect(getComposition && getComposition.data.value).toStrictEqual(additionalTestModel); }); + it('should set an error if something failed', async () => { + expect.assertions(3); + + // given + let serviceGetPromiseReject: (reason: FeathersError) => void = jest.fn(); + const serviceGet = jest.fn( + () => + new Promise((resolve, reject) => { + serviceGetPromiseReject = reject; + }), + ); + const feathersMock = { + service: () => ({ + get: serviceGet, + on: jest.fn(), + off: jest.fn(), + }), + on: jest.fn(), + off: jest.fn(), + } as unknown as Application; + const useGet = useGetOriginal(feathersMock); + let getComposition = null as UseGet | null; + mountComposition(() => { + getComposition = useGet('testModels', ref(testModel._id)); + }); + + // before when + expect(getComposition).toBeTruthy(); + expect(getComposition && getComposition.error.value).toBeFalsy(); + + // when + serviceGetPromiseReject(new GeneralError('test error')); + await nextTick(); + + // then + expect(getComposition && getComposition.error.value).toBeTruthy(); + }); + describe('Event Handlers', () => { it('should listen to "create" events', async () => { expect.assertions(3);