From c7a6410df0e03a1d07bfe1000c5df5b722723a89 Mon Sep 17 00:00:00 2001 From: deetz99 Date: Wed, 4 Sep 2024 16:04:17 -0700 Subject: [PATCH 1/7] test affiliations store --- .../app/stores/affiliations.ts | 2 +- .../tests/unit/stores/affiliations.test.ts | 629 ++++++++++++++++++ .../unit/utils/handleFormInputVariant.test.ts | 6 +- 3 files changed, 633 insertions(+), 4 deletions(-) create mode 100644 business-registry-dashboard/tests/unit/stores/affiliations.test.ts diff --git a/business-registry-dashboard/app/stores/affiliations.ts b/business-registry-dashboard/app/stores/affiliations.ts index 946eafa..b218ebb 100644 --- a/business-registry-dashboard/app/stores/affiliations.ts +++ b/business-registry-dashboard/app/stores/affiliations.ts @@ -188,7 +188,7 @@ export const useAffiliationsStore = defineStore('brd-affiliations-store', () => function setColumns () { visibleColumns.value = [ nameColumn, - ...optionalColumns.filter(col => selectedColumns.value.includes(col)), + ...optionalColumns.filter(col => selectedColumns.value.some(sel => sel.key === col.key)), actionColumn ] } diff --git a/business-registry-dashboard/tests/unit/stores/affiliations.test.ts b/business-registry-dashboard/tests/unit/stores/affiliations.test.ts new file mode 100644 index 0000000..8de1cb8 --- /dev/null +++ b/business-registry-dashboard/tests/unit/stores/affiliations.test.ts @@ -0,0 +1,629 @@ +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' +import { mockNuxtImport } from '@nuxt/test-utils/runtime' +import { flushPromises } from '@vue/test-utils' +import { setActivePinia, createPinia } from 'pinia' +import { mockAffiliationResponse } from '~~/tests/mocks/mockedData' + +let mockAuthenticated = true +const mockAuthApi = vi.fn() +mockNuxtImport('useNuxtApp', () => { + return () => ( + { + $keycloak: { + authenticated: mockAuthenticated, + token: 'mock-token' + }, + $authApi: mockAuthApi, + $legalApi: vi.fn() + } + ) +}) + +mockNuxtImport('useToast', () => { + return () => ( + { + add: vi.fn() + } + ) +}) + +mockNuxtImport('useRuntimeConfig', () => { + return () => ( + { + public: { + authApiUrl: 'https://authApiUrl.example.com/' + } + } + ) +}) + +const mockWindowSize = vi.fn() +mockNuxtImport('useWindowSize', () => { + return () => ( + { + width: mockWindowSize + } + ) +}) + +mockNuxtImport('useI18n', () => { + return () => ( + { + t: (key: string) => key, + locale: { + value: 'en-CA' + } + } + ) +}) + +const mockEntities = { + entities: [ + { identifier: '123' }, + { identifier: '456' }, + { identifier: '789' }, + { identifier: '321' } + ] +} + +describe('useAffiliationsStore', () => { + let store: any + + beforeEach(() => { + const pinia = createPinia() + setActivePinia(pinia) + store = useConnectAccountStore() + + store.currentAccount = { + id: '123', + accountType: 'basic', + accountStatus: '', + label: 'some label', + type: '', + urlpath: '', + urlorigin: '' + } + }) + + afterEach(() => { + vi.unstubAllGlobals() + vi.clearAllMocks() + vi.resetAllMocks() + mockAuthenticated = true + }) + + describe('loadAffiliations', () => { + it('should fetch and set affiliations correctly', async () => { + mockAuthApi.mockResolvedValue(mockEntities) + + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + // wait for getAffiliatedEntities to finish + await flushPromises() + + expect(mockAuthApi).toHaveBeenCalledOnce() + // results mapped to business object + expect(affStore.affiliations.results).toEqual([ + { + businessIdentifier: '123', + corpType: { code: undefined }, + adminFreeze: false, + goodStanding: true + }, + { + businessIdentifier: '456', + corpType: { code: undefined }, + adminFreeze: false, + goodStanding: true + }, + { + businessIdentifier: '789', + corpType: { code: undefined }, + adminFreeze: false, + goodStanding: true + }, + { + businessIdentifier: '321', + corpType: { code: undefined }, + adminFreeze: false, + goodStanding: true + } + ]) + expect(affStore.affiliations.count).toEqual(4) + }) + + it('should handle an empty API response correctly', async () => { + mockAuthApi.mockResolvedValue({ entities: [] }) + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + await flushPromises() + + expect(mockAuthApi).toHaveBeenCalledOnce() + expect(affStore.affiliations.results).toEqual([]) + expect(affStore.affiliations.count).toEqual(0) + }) + + it('should not fetch affiliations if the user is not authenticated', async () => { + mockAuthApi.mockResolvedValue({ entities: [] }) + mockAuthenticated = false + + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + await flushPromises() + + expect(mockAuthApi).not.toHaveBeenCalled() + }) + + it('should not fetch affiliations if currentAccount ID is missing', async () => { + mockAuthApi.mockResolvedValue({ entities: [] }) + store.currentAccount.id = '' + + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + await flushPromises() + + expect(mockAuthApi).not.toHaveBeenCalled() + }) + + it.skip('should handle fetch errors', async () => { // TODO: implement after adding better error handling + const _fetch = vi.fn().mockRejectedValue(new Error('API Error')) + vi.stubGlobal('$fetch', _fetch) + + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + await flushPromises() + + expect(_fetch).toHaveBeenCalledOnce() + expect(affStore.affiliations.results).toEqual([]) // Should remain empty + expect(affStore.affiliations.count).toEqual(0) + }) + + it.skip('should reset affiliations', async () => { // TODO: figure out why spy isnt being called + mockAuthApi.mockResolvedValue({ entities: [] }) + + const affStore = useAffiliationsStore() + + const resetAffiliationsSpy = vi.spyOn(affStore, 'resetAffiliations') + + await affStore.loadAffiliations() + + expect(resetAffiliationsSpy).toHaveBeenCalledTimes(1) + }) + + it('should set loading to true initially and false after API call', async () => { + mockAuthApi.mockResolvedValue({ entities: [] }) + + const affStore = useAffiliationsStore() + + const loadingSpy = vi.spyOn(affStore.affiliations, 'loading', 'set') + await affStore.loadAffiliations() + + expect(loadingSpy).toHaveBeenCalledWith(true) + expect(loadingSpy).toHaveBeenCalledWith(false) + }) + }) + + // it.only('should call test2', () => { + // const testStore = useTestStore() + // const test2Spy = vi.spyOn(testStore, 'test2') + // testStore.test1() + // expect(test2Spy).toHaveBeenCalledOnce() + // }) + + it('resetAffiliations should reset affiliations correctly', async () => { + mockAuthApi.mockResolvedValue(mockEntities) + + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + await flushPromises() + + expect(mockAuthApi).toHaveBeenCalledOnce() + + expect(affStore.affiliations.results).toHaveLength(4) + expect(affStore.affiliations.count).toEqual(4) + + affStore.resetAffiliations() + + expect(affStore.affiliations.results).toEqual([]) + expect(affStore.affiliations.count).toEqual(0) + }) + + it('should call loadAffiliations when currentAccount ID changes', async () => { + mockAuthApi.mockResolvedValue(mockEntities) + + const affStore = useAffiliationsStore() + + await affStore.loadAffiliations() + + await flushPromises() + expect(mockAuthApi).toHaveBeenCalledOnce() + + // trigger the watcher by changing currentAccount.id + store.currentAccount.id = '456' + + await flushPromises() + + expect(mockAuthApi).toHaveBeenCalledTimes(2) + }) + + describe('affiliation columns', () => { + it('should set default columns on large screens', async () => { + mockWindowSize.mockReturnValue(1480) + + const affStore = useAffiliationsStore() + + await new Promise((resolve) => { // required to wait for watcher to run + setTimeout(() => { + expect(affStore.visibleColumns).toEqual([ + { key: 'legalName', label: 'Name' }, + { key: 'identifier', label: 'labels.number' }, + { key: 'legalType', label: 'labels.type' }, + { key: 'state', label: 'labels.status' }, + { key: 'actions', label: 'Actions' } + ]) + expect(affStore.selectedColumns).toEqual(affStore.optionalColumns) + resolve() + }, 1000) + }) + }) + + it('should set default columns on medium screens', async () => { + mockWindowSize.mockReturnValue(1000) + + const affStore = useAffiliationsStore() + + await new Promise((resolve) => { // required to wait for watcher to run + setTimeout(() => { + expect(affStore.visibleColumns).toEqual([ + { key: 'legalName', label: 'Name' }, + { key: 'identifier', label: 'labels.number' }, + { key: 'actions', label: 'Actions' } + ]) + expect(affStore.selectedColumns).toEqual([affStore.optionalColumns[0]]) + resolve() + }, 1000) + }) + }) + + it('should set default columns on small screens', async () => { + mockWindowSize.mockReturnValue(600) + + const affStore = useAffiliationsStore() + + await new Promise((resolve) => { // required to wait for watcher to run + setTimeout(() => { + expect(affStore.visibleColumns).toEqual([ + { key: 'legalName', label: 'Name' }, + { key: 'actions', label: 'Actions' } + ]) + expect(affStore.selectedColumns).toEqual([]) + resolve() + }, 1000) + }) + }) + + describe('setColumns', () => { + it('should set all columns when all optional columns are selected', () => { + const affStore = useAffiliationsStore() + + affStore.setColumns() + + expect(affStore.visibleColumns).toEqual([ + { key: 'legalName', label: 'Name' }, + ...affStore.optionalColumns, + { key: 'actions', label: 'Actions' } + ]) + }) + + it('should set only name and actions columns when no optional columns are selected', () => { + const affStore = useAffiliationsStore() + + affStore.selectedColumns = [] + + affStore.setColumns() + + expect(affStore.visibleColumns).toEqual([ + { key: 'legalName', label: 'Name' }, + { key: 'actions', label: 'Actions' } + ]) + }) + + it('should set only selected optional columns', () => { + const affStore = useAffiliationsStore() + + affStore.selectedColumns = [affStore.optionalColumns[0]!, affStore.optionalColumns[2]!] // Select first and last optional column + + affStore.setColumns() + + expect(affStore.visibleColumns).toEqual([ + { key: 'legalName', label: 'Name' }, + affStore.optionalColumns[0], // identifier + affStore.optionalColumns[2], // state + { key: 'actions', label: 'Actions' } + ]) + }) + }) + }) + + describe('affiliation filtering', () => { + describe('filter by name', () => { + it('should filter a name request object', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.businessName = 'SOME TEST NAME 321' + + expect(affStore.filteredResults).toHaveLength(1) + expect(affStore.filteredResults[0]?.nameRequest?.names?.some(item => item.name === 'SOME TEST NAME 321')).toEqual(true) + }) + + it('should filter a business object', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.businessName = '0871505 B.C. LTD.' + + expect(affStore.filteredResults).toHaveLength(1) + expect(affStore.filteredResults).toEqual([ + { + businessIdentifier: 'BC0871505', + name: '0871505 B.C. LTD.', + corpType: { code: 'BEN' }, + corpSubType: { code: 'BEN' }, + adminFreeze: false, + goodStanding: true, + status: 'ACTIVE' + } + ]) + }) + }) + + describe('filter by number', () => { + it('should filter a name request object', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.businessNumber = 'NR 3819593' + + expect(affStore.filteredResults).toHaveLength(1) + expect(affStore.filteredResults[0]?.nameRequest?.names?.some(item => item.name === 'SOME TEST NAME 321')).toEqual(true) + }) + + it('should filter a business object', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.businessNumber = 'BC0871227' + + expect(affStore.filteredResults).toHaveLength(1) + expect(affStore.filteredResults).toEqual([ + { + businessIdentifier: 'BC0871227', + name: 'SEVERIN LIMITED COMPANY CORP.', + corpType: { code: 'BC' }, + corpSubType: { code: 'BC' }, + adminFreeze: false, + goodStanding: true, + status: 'HISTORICAL' + } + ]) + }) + }) + + describe('filter by type', () => { + it('should filter an Amalgamation Application', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.type = 'Amalgamation Application' + + expect(affStore.filteredResults).toHaveLength(1) + expect(affStore.filteredResults.every(item => affiliationType(item) === 'Amalgamation Application')).toEqual(true) + }) + + it('should filter a BC Limited Company', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.type = 'BC Limited Company' + + expect(affStore.filteredResults).toHaveLength(3) + expect(affStore.filteredResults.every(item => affiliationType(item) === 'BC Limited Company')).toEqual(true) + }) + }) + + describe('filter by status', () => { + it('should filter by draft', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.status = 'Draft' + + expect(affStore.filteredResults).toHaveLength(2) + expect(affStore.filteredResults.every(item => affiliationStatus(item) === 'Draft')).toEqual(true) + }) + + it('should filter by historical', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + affStore.affiliations.filters.status = 'Historical' + + expect(affStore.filteredResults).toHaveLength(2) + expect(affStore.filteredResults.every(item => affiliationStatus(item) === 'Historical')).toEqual(true) + }) + }) + + describe('statusOptions', () => { + it('should be created based off the results objects', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.statusOptions).toEqual(['Draft', 'Historical', 'Active']) + }) + }) + + describe('typeOptions', () => { + it('should be created based off the results objects', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.typeOptions).toEqual([ + 'Amalgamation Application', + 'BC Limited Company', + 'BC Benefit Company', + 'Registration' + ]) + }) + }) + + describe('hasFilters', () => { + it('should be false by default', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.hasFilters).toEqual(false) + }) + + it('should be true with a name filter active', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.hasFilters).toEqual(false) + + affStore.affiliations.filters.businessName = 'test' + + expect(affStore.hasFilters).toEqual(true) + }) + + it('should be true with a number filter active', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.hasFilters).toEqual(false) + + affStore.affiliations.filters.businessNumber = 'test' + + expect(affStore.hasFilters).toEqual(true) + }) + + it('should be true with a type filter active', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.hasFilters).toEqual(false) + + affStore.affiliations.filters.type = 'test' + + expect(affStore.hasFilters).toEqual(true) + }) + + it('should be true with a status filter active', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.hasFilters).toEqual(false) + + affStore.affiliations.filters.status = 'test' + + expect(affStore.hasFilters).toEqual(true) + }) + }) + + describe('resetFilters', () => { + it('should reset all filters', async () => { + mockAuthApi.mockResolvedValue(mockAffiliationResponse) + + const affStore = useAffiliationsStore() + await affStore.loadAffiliations() + + await flushPromises() + + expect(affStore.hasFilters).toEqual(false) + + affStore.affiliations.filters.businessName = 'test' + affStore.affiliations.filters.businessNumber = 'test' + affStore.affiliations.filters.type = 'test' + affStore.affiliations.filters.status = 'test' + + expect(affStore.hasFilters).toEqual(true) + + affStore.resetFilters() + + expect(affStore.hasFilters).toEqual(false) + }) + }) + }) +}) diff --git a/business-registry-dashboard/tests/unit/utils/handleFormInputVariant.test.ts b/business-registry-dashboard/tests/unit/utils/handleFormInputVariant.test.ts index 4d89213..02cb1f6 100644 --- a/business-registry-dashboard/tests/unit/utils/handleFormInputVariant.test.ts +++ b/business-registry-dashboard/tests/unit/utils/handleFormInputVariant.test.ts @@ -8,9 +8,9 @@ describe('handleFormInputVariant', () => { ] const tests = [ - ['random', 'bcGov'], + ['random', 'bcGovLg'], ['email', 'error'], - ['no match', 'bcGov'], + ['no match', 'bcGovLg'], ['password', 'error'] ] @@ -24,6 +24,6 @@ describe('handleFormInputVariant', () => { it('returns bcGov if formErrors is undefined', () => { const result = handleFormInputVariant('some path', undefined) - expect(result).toBe('bcGov') + expect(result).toBe('bcGovLg') }) }) From f8851a7d23b87a85d7def2c5d17c4316d85f3a03 Mon Sep 17 00:00:00 2001 From: deetz99 Date: Wed, 4 Sep 2024 16:06:22 -0700 Subject: [PATCH 2/7] remove unused files --- .../app/stores/alert.ts | 24 - .../app/stores/launchdarkly.ts | 67 -- .../unit/composables/useAffiliations.test.ts | 573 ------------------ 3 files changed, 664 deletions(-) delete mode 100644 business-registry-dashboard/app/stores/alert.ts delete mode 100644 business-registry-dashboard/app/stores/launchdarkly.ts delete mode 100644 business-registry-dashboard/tests/unit/composables/useAffiliations.test.ts diff --git a/business-registry-dashboard/app/stores/alert.ts b/business-registry-dashboard/app/stores/alert.ts deleted file mode 100644 index 0a1ac0d..0000000 --- a/business-registry-dashboard/app/stores/alert.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const useAlertStore = defineStore('bar-sbc-alert-store', () => { - const alerts = ref([]) - - function $reset () { - alerts.value = [] - } - - function addAlert (alert: Alert) { - alerts.value.push(alert) - } - - const hasAlerts = computed(() => { - return alerts.value.length > 0 - }) - - return { - alerts, - hasAlerts, - addAlert, - $reset - } -}, -{ persist: true } -) diff --git a/business-registry-dashboard/app/stores/launchdarkly.ts b/business-registry-dashboard/app/stores/launchdarkly.ts deleted file mode 100644 index a0100da..0000000 --- a/business-registry-dashboard/app/stores/launchdarkly.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { initialize, type LDClient, type LDFlagSet, type LDOptions, type LDMultiKindContext } from 'launchdarkly-js-client-sdk' - -export const useLaunchdarklyStore = defineStore('bar-sbc-launchdarkly', () => { - const { $keycloak } = useNuxtApp() - const accountStore = useAccountStore() - const ldClient: Ref = ref(null) - const ldContext = ref({ - kind: 'multi', - org: { key: 'anonymous' }, - user: { key: 'anonymous' } - } as LDMultiKindContext) - const ldFlagSet: Ref = ref({}) - const ldInitialized = ref(false) - - function init () { - if (ldInitialized.value) { - console.info('Launchdarkly already initialized.') - return - } - const ldClientId = useRuntimeConfig().public.ldClientId - if (!ldClientId) { - console.info('No launchdarkly sdk variable set. Aborting launchdarkly setup.') - return - } - let user: any = { key: 'anonymous' } - let org: any = { key: 'anonymous' } - if ($keycloak.authenticated) { - user = { - key: $keycloak.tokenParsed?.sub || '', - firstName: $keycloak.tokenParsed?.firstName, - lastName: $keycloak.tokenParsed?.lastName, - email: $keycloak.tokenParsed?.email, - roles: $keycloak.tokenParsed?.roles, // should this be realm_access.roles? - loginSource: $keycloak.tokenParsed?.loginSource - } - } - if (accountStore.currentAccount.id) { - org = { - key: accountStore.currentAccount.id, - accountType: accountStore.currentAccount.orgType, - accountStatus: accountStore.currentAccount.orgStatus - // type: accountStore.currentAccount.type, - // label: accountStore.currentAccount.label - } - } - ldContext.value = { kind: 'multi', org, user } - const options: LDOptions = { - streaming: true, - useReport: true, - diagnosticOptOut: true - } - ldClient.value = initialize(ldClientId, ldContext.value, options) - ldClient.value.on('initialized', () => { - ldFlagSet.value = ldClient.value?.allFlags() || {} - ldInitialized.value = true - console.info('launchdarkly initialization complete.') - }) - } - - return { - ldClient, - ldContext, - ldFlagSet, - ldInitialized, - init - } -}) diff --git a/business-registry-dashboard/tests/unit/composables/useAffiliations.test.ts b/business-registry-dashboard/tests/unit/composables/useAffiliations.test.ts deleted file mode 100644 index ea6f9b2..0000000 --- a/business-registry-dashboard/tests/unit/composables/useAffiliations.test.ts +++ /dev/null @@ -1,573 +0,0 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' -import { mockNuxtImport } from '@nuxt/test-utils/runtime' -import { flushPromises } from '@vue/test-utils' -import { setActivePinia, createPinia } from 'pinia' -import { mockAffiliationResponse } from '~~/tests/mocks/mockedData' - -let mockAuthenticated = true -mockNuxtImport('useNuxtApp', () => { - return () => ( - { - $keycloak: { - authenticated: mockAuthenticated, - token: 'mock-token' - } - } - ) -}) - -mockNuxtImport('useRuntimeConfig', () => { - return () => ( - { - public: { - authApiUrl: 'https://authApiUrl.example.com/' - } - } - ) -}) - -const mockWindowSize = vi.fn() -mockNuxtImport('useWindowSize', () => { - return () => ( - { - width: mockWindowSize - } - ) -}) - -mockNuxtImport('useI18n', () => { - return () => ( - { - t: (key: string) => key, - locale: { - value: 'en-CA' - } - } - ) -}) - -describe('useAffiliations', () => { - let store: any - beforeEach(() => { - setActivePinia(createPinia()) - store = useConnectAccountStore() - - store.currentAccount = { - id: '123', - accountType: 'basic', - accountStatus: '', - label: 'some label', - type: '', - urlpath: '', - urlorigin: '' - } - }) - - afterEach(() => { - vi.unstubAllGlobals() - vi.clearAllMocks() - vi.resetAllMocks() - mockAuthenticated = true - }) - - it('should fetch and set affiliations correctly', async () => { - const _fetch = vi.fn().mockResolvedValue({ - entities: [ - { identifier: '123' }, - { identifier: '456' }, - { identifier: '789' }, - { identifier: '321' } - ] - }) - vi.stubGlobal('$fetch', _fetch) - - // watch should trigger getAffiliatedEntities - const { affiliations } = useAffiliations() - - // wait for getAffiliatedEntities to finish - await flushPromises() - - expect(_fetch).toHaveBeenCalledOnce() - // results mapped to business object - expect(affiliations.results).toEqual([ - { - businessIdentifier: '123', - corpType: { code: undefined }, - adminFreeze: false, - goodStanding: true - }, - { - businessIdentifier: '456', - corpType: { code: undefined }, - adminFreeze: false, - goodStanding: true - }, - { - businessIdentifier: '789', - corpType: { code: undefined }, - adminFreeze: false, - goodStanding: true - }, - { - businessIdentifier: '321', - corpType: { code: undefined }, - adminFreeze: false, - goodStanding: true - } - ]) - expect(affiliations.count).toEqual(4) - }) - - it('should handle an empty API response correctly', async () => { - const _fetch = vi.fn().mockResolvedValue({ entities: [] }) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations } = useAffiliations() - - await flushPromises() - - expect(_fetch).toHaveBeenCalledOnce() - expect(affiliations.results).toEqual([]) - expect(affiliations.count).toEqual(0) - }) - - it('should not fetch affiliations if the user is not authenticated', async () => { - mockAuthenticated = false - - const _fetch = vi.fn() - vi.stubGlobal('$fetch', _fetch) - - useAffiliations() - - await flushPromises() - - expect(_fetch).not.toHaveBeenCalled() - }) - - it('should not fetch affiliations if currentAccount ID is missing', async () => { - const _fetch = vi.fn() - vi.stubGlobal('$fetch', _fetch) - - store.currentAccount.id = '' - - useAffiliations() - - await flushPromises() - - expect(_fetch).not.toHaveBeenCalled() - }) - - it.skip('should handle fetch errors', async () => { // TODO: implement after adding better error handling - const _fetch = vi.fn().mockRejectedValue(new Error('API Error')) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations } = useAffiliations() - - await flushPromises() - - expect(_fetch).toHaveBeenCalledOnce() - expect(affiliations.results).toEqual([]) // Should remain empty - expect(affiliations.count).toEqual(0) - }) - - it('should reset affiliations correctly', async () => { - const _fetch = vi.fn().mockResolvedValue({ - entities: [ - { identifier: '123' }, - { identifier: '456' }, - { identifier: '789' }, - { identifier: '321' } - ] - }) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, resetAffiliations } = useAffiliations() - - await flushPromises() - - expect(_fetch).toHaveBeenCalledOnce() - - expect(affiliations.results).toHaveLength(4) - - resetAffiliations() - - expect(affiliations.results).toEqual([]) - expect(affiliations.count).toEqual(0) - }) - - it('should call getAffiliatedEntities when currentAccount ID changes', async () => { - const _fetch = vi.fn().mockResolvedValue({ entities: [] }) - vi.stubGlobal('$fetch', _fetch) - - useAffiliations() - - await flushPromises() - expect(_fetch).toHaveBeenCalledOnce() - - // trigger the watcher by changing currentAccount.id - store.currentAccount.id = '456' - - await flushPromises() - - expect(_fetch).toHaveBeenCalledTimes(2) - }) - - describe('affiliation columns', () => { - it('should set default columns on large screens', async () => { - const _fetch = vi.fn().mockResolvedValue({ entities: [] }) - vi.stubGlobal('$fetch', _fetch) - mockWindowSize.mockReturnValue(1480) - - const { visibleColumns, optionalColumns, selectedColumns } = useAffiliations() - - await new Promise((resolve) => { // required to wait for watcher to run - setTimeout(() => { - expect(visibleColumns.value).toEqual([ - { key: 'legalName', label: 'Name' }, - { key: 'identifier', label: 'labels.number' }, - { key: 'legalType', label: 'labels.type' }, - { key: 'state', label: 'labels.status' }, - { key: 'actions', label: 'Actions' } - ]) - expect(selectedColumns.value).toEqual(optionalColumns) - resolve() - }, 1000) - }) - }) - - it('should set default columns on medium screens', async () => { - const _fetch = vi.fn().mockResolvedValue({ entities: [] }) - vi.stubGlobal('$fetch', _fetch) - mockWindowSize.mockReturnValue(1000) - - const { visibleColumns, optionalColumns, selectedColumns } = useAffiliations() - - await new Promise((resolve) => { // required to wait for watcher to run - setTimeout(() => { - expect(visibleColumns.value).toEqual([ - { key: 'legalName', label: 'Name' }, - { key: 'identifier', label: 'labels.number' }, - { key: 'actions', label: 'Actions' } - ]) - expect(selectedColumns.value).toEqual([optionalColumns[0]]) - resolve() - }, 1000) - }) - }) - - it('should set default columns on small screens', async () => { - const _fetch = vi.fn().mockResolvedValue({ entities: [] }) - vi.stubGlobal('$fetch', _fetch) - mockWindowSize.mockReturnValue(600) - - const { visibleColumns, selectedColumns } = useAffiliations() - - await new Promise((resolve) => { // required to wait for watcher to run - setTimeout(() => { - expect(visibleColumns.value).toEqual([ - { key: 'legalName', label: 'Name' }, - { key: 'actions', label: 'Actions' } - ]) - expect(selectedColumns.value).toEqual([]) - resolve() - }, 1000) - }) - }) - - describe('setColumns', () => { - it('should set all columns when all optional columns are selected', () => { - const { visibleColumns, optionalColumns, setColumns } = useAffiliations() - - setColumns() - - expect(visibleColumns.value).toEqual([ - { key: 'legalName', label: 'Name' }, - ...optionalColumns, - { key: 'actions', label: 'Actions' } - ]) - }) - - it('should set only name and actions columns when no optional columns are selected', () => { - const { visibleColumns, selectedColumns, setColumns } = useAffiliations() - - selectedColumns.value = [] - - setColumns() - - expect(visibleColumns.value).toEqual([ - { key: 'legalName', label: 'Name' }, - { key: 'actions', label: 'Actions' } - ]) - }) - - it('should set only selected optional columns', () => { - const { visibleColumns, optionalColumns, selectedColumns, setColumns } = useAffiliations() - - selectedColumns.value = [optionalColumns[0]!, optionalColumns[2]!] // Select first and last optional column - - setColumns() - - expect(visibleColumns.value).toEqual([ - { key: 'legalName', label: 'Name' }, - optionalColumns[0], // identifier - optionalColumns[2], // state - { key: 'actions', label: 'Actions' } - ]) - }) - }) - }) - - describe('affiliation filtering', () => { - describe('filter by name', () => { - it('should filter a name request object', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.businessName = 'SOME TEST NAME 321' - - expect(filteredResults.value).toHaveLength(1) - expect(filteredResults.value[0]?.nameRequest?.names?.some(item => item.name === 'SOME TEST NAME 321')).toEqual(true) - }) - - it('should filter a business object', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.businessName = '0871505 B.C. LTD.' - - expect(filteredResults.value).toHaveLength(1) - expect(filteredResults.value).toEqual([ - { - businessIdentifier: 'BC0871505', - name: '0871505 B.C. LTD.', - corpType: { code: 'BEN' }, - corpSubType: { code: 'BEN' }, - adminFreeze: false, - goodStanding: true, - status: 'ACTIVE' - } - ]) - }) - }) - - describe('filter by number', () => { - it('should filter a name request object', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.businessNumber = 'NR 3819593' - - expect(filteredResults.value).toHaveLength(1) - expect(filteredResults.value[0]?.nameRequest?.names?.some(item => item.name === 'SOME TEST NAME 321')).toEqual(true) - }) - - it('should filter a business object', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.businessNumber = 'BC0871227' - - expect(filteredResults.value).toHaveLength(1) - expect(filteredResults.value).toEqual([ - { - businessIdentifier: 'BC0871227', - name: 'SEVERIN LIMITED COMPANY CORP.', - corpType: { code: 'BC' }, - corpSubType: { code: 'BC' }, - adminFreeze: false, - goodStanding: true, - status: 'HISTORICAL' - } - ]) - }) - }) - - describe('filter by type', () => { - it('should filter an Amalgamation Application', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.type = 'Amalgamation Application' - - expect(filteredResults.value).toHaveLength(1) - expect(filteredResults.value.every(item => affiliationType(item) === 'Amalgamation Application')).toEqual(true) - }) - - it('should filter a BC Limited Company', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.type = 'BC Limited Company' - - expect(filteredResults.value).toHaveLength(3) - expect(filteredResults.value.every(item => affiliationType(item) === 'BC Limited Company')).toEqual(true) - }) - }) - - describe('filter by status', () => { - it('should filter by draft', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.status = 'Draft' - - expect(filteredResults.value).toHaveLength(2) - expect(filteredResults.value.every(item => affiliationStatus(item) === 'Draft')).toEqual(true) - }) - - it('should filter by historical', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { affiliations, filteredResults } = useAffiliations() - await flushPromises() - - affiliations.filters.status = 'Historical' - - expect(filteredResults.value).toHaveLength(2) - expect(filteredResults.value.every(item => affiliationStatus(item) === 'Historical')).toEqual(true) - }) - }) - - describe('statusOptions', () => { - it('should be created based off the results objects', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { statusOptions } = useAffiliations() - await flushPromises() - - expect(statusOptions.value).toEqual(['Draft', 'Historical', 'Active']) - }) - }) - - describe('typeOptions', () => { - it('should be created based off the results objects', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { typeOptions } = useAffiliations() - await flushPromises() - - expect(typeOptions.value).toEqual([ - 'Amalgamation Application', - 'BC Limited Company', - 'BC Benefit Company', - 'Registration' - ]) - }) - }) - - describe('hasFilters', () => { - it('should be false by default', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { hasFilters } = useAffiliations() - await flushPromises() - - expect(hasFilters.value).toEqual(false) - }) - - it('should be true with a name filter active', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { hasFilters, affiliations } = useAffiliations() - await flushPromises() - - expect(hasFilters.value).toEqual(false) - - affiliations.filters.businessName = 'test' - - expect(hasFilters.value).toEqual(true) - }) - - it('should be true with a number filter active', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { hasFilters, affiliations } = useAffiliations() - await flushPromises() - - expect(hasFilters.value).toEqual(false) - - affiliations.filters.businessNumber = 'test' - - expect(hasFilters.value).toEqual(true) - }) - - it('should be true with a type filter active', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { hasFilters, affiliations } = useAffiliations() - await flushPromises() - - expect(hasFilters.value).toEqual(false) - - affiliations.filters.type = 'test' - - expect(hasFilters.value).toEqual(true) - }) - - it('should be true with a status filter active', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { hasFilters, affiliations } = useAffiliations() - await flushPromises() - - expect(hasFilters.value).toEqual(false) - - affiliations.filters.status = 'test' - - expect(hasFilters.value).toEqual(true) - }) - }) - - describe('resetFilters', () => { - it('should reset all filters', async () => { - const _fetch = vi.fn().mockResolvedValue(mockAffiliationResponse) - vi.stubGlobal('$fetch', _fetch) - - const { hasFilters, affiliations, resetFilters } = useAffiliations() - await flushPromises() - - expect(hasFilters.value).toEqual(false) - - affiliations.filters.businessName = 'test' - affiliations.filters.businessNumber = 'test' - affiliations.filters.type = 'test' - affiliations.filters.status = 'test' - - expect(hasFilters.value).toEqual(true) - - resetFilters() - - expect(hasFilters.value).toEqual(false) - }) - }) - }) -}) From f9bda0499215b40d46a4554957d7a397328cc955 Mon Sep 17 00:00:00 2001 From: deetz99 Date: Thu, 5 Sep 2024 12:37:33 -0700 Subject: [PATCH 3/7] test affiliatiosn store --- .../app/plugins/launchDarkly.ts | 5 - .../app/stores/affiliations.ts | 5 + .../tests/unit/stores/affiliations.test.ts | 421 +++++++++++++++++- 3 files changed, 424 insertions(+), 7 deletions(-) delete mode 100644 business-registry-dashboard/app/plugins/launchDarkly.ts diff --git a/business-registry-dashboard/app/plugins/launchDarkly.ts b/business-registry-dashboard/app/plugins/launchDarkly.ts deleted file mode 100644 index 4bab3da..0000000 --- a/business-registry-dashboard/app/plugins/launchDarkly.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { LDPlugin } from 'launchdarkly-vue-client-sdk' - -export default defineNuxtPlugin((nuxtApp) => { - nuxtApp.vueApp.use(LDPlugin, { deferInitialization: true }) -}) diff --git a/business-registry-dashboard/app/stores/affiliations.ts b/business-registry-dashboard/app/stores/affiliations.ts index b218ebb..f09074e 100644 --- a/business-registry-dashboard/app/stores/affiliations.ts +++ b/business-registry-dashboard/app/stores/affiliations.ts @@ -290,6 +290,7 @@ export const useAffiliationsStore = defineStore('brd-affiliations-store', () => visibleColumns, optionalColumns, selectedColumns, + isStaffOrSbcStaff, setColumns, filteredResults, statusOptions, @@ -299,6 +300,10 @@ export const useAffiliationsStore = defineStore('brd-affiliations-store', () => createNRAffiliation, handleManageBusinessOrNameRequest, removeBusiness, + removeAffiliation, + getFilings, + deleteBusinessFiling, + addNameRequestForStaffSilently, $reset } } diff --git a/business-registry-dashboard/tests/unit/stores/affiliations.test.ts b/business-registry-dashboard/tests/unit/stores/affiliations.test.ts index 8de1cb8..7526b19 100644 --- a/business-registry-dashboard/tests/unit/stores/affiliations.test.ts +++ b/business-registry-dashboard/tests/unit/stores/affiliations.test.ts @@ -6,6 +6,7 @@ import { mockAffiliationResponse } from '~~/tests/mocks/mockedData' let mockAuthenticated = true const mockAuthApi = vi.fn() +const mockLegalApi = vi.fn() mockNuxtImport('useNuxtApp', () => { return () => ( { @@ -14,15 +15,55 @@ mockNuxtImport('useNuxtApp', () => { token: 'mock-token' }, $authApi: mockAuthApi, - $legalApi: vi.fn() + $legalApi: mockLegalApi } ) }) +let mockKeycloakRoles: any[] = [] +mockNuxtImport('useKeycloak', () => { + return () => ( + { + kcUser: { + roles: mockKeycloakRoles + } + } + ) +}) + +const mockAddToast = vi.fn() mockNuxtImport('useToast', () => { return () => ( { - add: vi.fn() + add: mockAddToast + } + ) +}) + +const mockOpenModal = vi.fn() +mockNuxtImport('useModal', () => { + return () => ( + { + open: mockOpenModal + } + ) +}) + +const mockOpenManageNameRequest = vi.fn() +const mockOpenManageNRError = vi.fn() +const mockOpenBusinessAddError = vi.fn() +const mockOpenBusinessUnavailableError = vi.fn() +const mockOpenBusinessRemovalConfirmation = vi.fn() +const mockClose = vi.fn() +mockNuxtImport('useBrdModals', () => { + return () => ( + { + openManageNameRequest: mockOpenManageNameRequest, + openManageNRError: mockOpenManageNRError, + openBusinessAddError: mockOpenBusinessAddError, + openBusinessUnavailableError: mockOpenBusinessUnavailableError, + openBusinessRemovalConfirmation: mockOpenBusinessRemovalConfirmation, + close: mockClose } ) }) @@ -626,4 +667,380 @@ describe('useAffiliationsStore', () => { }) }) }) + + describe('removeAffiliation', () => { + it('should call $authApi with the correct arguments', async () => { + const orgIdentifier = 123 + const incorporationNumber = 'INC001234' + const passcodeResetEmail = 'test@example.com' + const resetPasscode = true + + const affStore = useAffiliationsStore() + await affStore.removeAffiliation(orgIdentifier, incorporationNumber, passcodeResetEmail, resetPasscode) + + expect(mockAuthApi).toHaveBeenCalledWith( + `/orgs/${orgIdentifier}/affiliations/${incorporationNumber}`, + { + method: 'DELETE', + body: { + data: { + passcodeResetEmail, + resetPasscode, + logDeleteDraft: true + } + } + } + ) + }) + + it('should call $authApi with the correct default arguments', async () => { + const orgIdentifier = 123 + const incorporationNumber = 'INC001234' + + const { removeAffiliation } = useAffiliationsStore() + await removeAffiliation(orgIdentifier, incorporationNumber) + + expect(mockAuthApi).toHaveBeenCalledWith( + `/orgs/${orgIdentifier}/affiliations/${incorporationNumber}`, + { + method: 'DELETE', + body: { + data: { + passcodeResetEmail: undefined, + resetPasscode: undefined, + logDeleteDraft: true + } + } + } + ) + }) + }) + + describe('getFilings', () => { + it('should call $legalApi with correct options', async () => { + const businessNumber = 'BN123456' + const affStore = useAffiliationsStore() + + await affStore.getFilings(businessNumber) + expect(mockLegalApi).toHaveBeenCalledWith(`/businesses/${businessNumber}/filings`) + }) + }) + + describe('deleteBusinessFiling', () => { + it('should call $legalApi with correct options', async () => { + const businessNumber = 'BN123456' + const filingId = 'FILING001' + const affStore = useAffiliationsStore() + + await affStore.deleteBusinessFiling(businessNumber, filingId) + + expect(mockLegalApi).toHaveBeenCalledWith( + `/businesses/${businessNumber}/filings/${filingId}`, + { method: 'DELETE' } + ) + }) + }) + + describe('removeBusiness', () => { + let affStore: any + + beforeEach(() => { + affStore = useAffiliationsStore() + }) + + it('should remove business filing if business is an INCORPORATION_APPLICATION and a filing exists', async () => { + mockLegalApi.mockResolvedValueOnce({ status: 200, filing: { header: { filingId: 'filing-123' } } }) // mock getFiling api call + mockAuthApi.mockResolvedValueOnce(undefined) // mock removeAffiliation api call + + const payload = { + business: { + corpType: { code: CorpTypes.INCORPORATION_APPLICATION }, + businessIdentifier: 'biz-123' + }, + orgIdentifier: 'org-123', + passcodeResetEmail: 'email@example.com', + resetPasscode: true + } + + await affStore.removeBusiness(payload) + + expect(mockLegalApi).toHaveBeenCalledWith('/businesses/biz-123/filings') // should call getFilings + expect(mockLegalApi).toHaveBeenCalledWith('/businesses/biz-123/filings/filing-123', { method: 'DELETE' }) // should call deleteBusinessFiling + expect(mockAuthApi).not.toHaveBeenCalled() + }) + + it('should remove affiliation if business is an INCORPORATION_APPLICATION and no filing exists', async () => { + mockLegalApi.mockResolvedValueOnce({ status: 200, filing: null }) // mock getFilings api call + mockAuthApi.mockResolvedValueOnce(undefined) // mock removeAffiliation api call + + const payload = { + business: { + corpType: { code: CorpTypes.INCORPORATION_APPLICATION }, + businessIdentifier: 'biz-123' + }, + orgIdentifier: 'org-123', + passcodeResetEmail: 'email@example.com', + resetPasscode: true + } + + await affStore.removeBusiness(payload) + + expect(mockLegalApi).toHaveBeenCalledWith('/businesses/biz-123/filings') // should call getFilings + expect(mockAuthApi).toHaveBeenCalledWith('/orgs/org-123/affiliations/biz-123', { // should call removeAffiliation + method: 'DELETE', + body: { data: { passcodeResetEmail: 'email@example.com', resetPasscode: true, logDeleteDraft: true } } + }) + }) + + it('should remove affiliation if business is not an INCORPORATION_APPLICATION', async () => { + mockAuthApi.mockResolvedValueOnce(undefined) + + const payload = { + business: { + corpType: { code: 'OTHER' }, + businessIdentifier: 'biz-123' + }, + orgIdentifier: 'org-123', + passcodeResetEmail: 'email@example.com', + resetPasscode: true + } + + await affStore.removeBusiness(payload) + + expect(mockAuthApi).toHaveBeenCalledWith('/orgs/org-123/affiliations/biz-123', { // should call removeAffiliation + method: 'DELETE', + body: { data: { passcodeResetEmail: 'email@example.com', resetPasscode: true, logDeleteDraft: true } } + }) + expect(mockLegalApi).not.toHaveBeenCalled() + }) + + it('should remove affiliation with name request nrNumber if businessIdentifier is missing', async () => { + mockAuthApi.mockResolvedValue({ status: 200 }) + mockLegalApi.mockResolvedValue({ status: 200, filing: { header: { filingId: 'filing-123' } } }) + + const payload = { + business: { + corpType: { code: 'OTHER_TYPE' }, + businessIdentifier: undefined, // missing businessIdentifier + nameRequest: { nrNumber: 'NR123' } // fallback to nr number + }, + orgIdentifier: 'org-123', + passcodeResetEmail: 'email@example.com', + resetPasscode: true + } + + await affStore.removeBusiness(payload) + + expect(mockAuthApi).toHaveBeenCalledWith( // should call removeAffiliation + '/orgs/org-123/affiliations/NR123', + expect.objectContaining({ + method: 'DELETE', + body: { + data: { passcodeResetEmail: 'email@example.com', resetPasscode: true, logDeleteDraft: true } + } + }) + ) + expect(mockLegalApi).not.toHaveBeenCalled() // should not call deleteBusinessFiling + }) + }) + + describe('removeInvite', () => { + it('should call $authApi with the correct URL and method', async () => { + const affStore = useAffiliationsStore() + + const inviteId = 123 + + await affStore.removeInvite(inviteId) + + expect(mockAuthApi).toHaveBeenCalledWith( + `/affiliationInvitations/${inviteId}`, + expect.objectContaining({ + method: 'DELETE' + }) + ) + expect(mockAuthApi).toHaveBeenCalledTimes(1) + }) + }) + + describe('removeAcceptedAffiliationInvitations', () => { + it('should call removeInvite for invitations with Accepted status', async () => { + mockAuthApi.mockResolvedValue({ status: 200 }) + + const business = { + affiliationInvites: [ + { id: 1, status: 'ACCEPTED' }, + { id: 2, status: 'Pending' }, + { id: 3, status: 'ACCEPTED' }, + { id: 4, status: 'Failed' } + ] + } + + const affStore = useAffiliationsStore() + + // @ts-expect-error - expect arg to not match param type + await affStore.removeAcceptedAffiliationInvitations(business) + + // should only be called twice + expect(mockAuthApi).toHaveBeenCalledTimes(2) + + // only called with status = accepted + expect(mockAuthApi).toHaveBeenCalledWith('/affiliationInvitations/1', { method: 'DELETE' }) + expect(mockAuthApi).toHaveBeenCalledWith('/affiliationInvitations/3', { method: 'DELETE' }) + + // not called with other status + expect(mockAuthApi).not.toHaveBeenCalledWith('/affiliationInvitations/2', expect.anything()) + expect(mockAuthApi).not.toHaveBeenCalledWith('/affiliationInvitations/4', expect.anything()) + }) + }) + + describe('createNRAffiliation', () => { + it('should call $authApi with the correct URL, method, and body', async () => { + const mockAffiliation = { + businessIdentifier: 'NR123456', + orgIdentifier: 'org-123' + } + + const affStore = useAffiliationsStore() + + await affStore.createNRAffiliation(mockAffiliation) + + expect(mockAuthApi).toHaveBeenCalledOnce() + expect(mockAuthApi).toHaveBeenCalledWith('/orgs/123/affiliations?newBusiness=true', { + method: 'POST', + body: mockAffiliation + }) + }) + }) + + describe('isStaffOrSbcStaff', () => { + beforeEach(() => { + // reset before each test + mockAuthenticated = true + mockKeycloakRoles = [] + store.currentAccount.accountType = 'basic' + }) + + it('should return false when the user is not authenticated', () => { + mockAuthenticated = false + + const affStore = useAffiliationsStore() + + expect(affStore.isStaffOrSbcStaff).toBe(false) + }) + + it('should return false when the user is authenticated but has no staff roles', () => { + mockAuthenticated = true + mockKeycloakRoles = [] // No roles + + const affStore = useAffiliationsStore() + + expect(affStore.isStaffOrSbcStaff).toBe(false) + }) + + it('should return true when the current organization is a staff account', () => { + store.currentAccount.accountType = 'STAFF' + + const affStore = useAffiliationsStore() + + expect(affStore.isStaffOrSbcStaff).toBe(true) + }) + + it('should return true when the current organization is an SBC_STAFF account', () => { + store.currentAccount.accountType = 'SBC_STAFF' + + const affStore = useAffiliationsStore() + + expect(affStore.isStaffOrSbcStaff).toBe(true) + }) + + it('should return true when the user has a Staff role', () => { + mockKeycloakRoles = ['staff'] + + const affStore = useAffiliationsStore() + + expect(affStore.isStaffOrSbcStaff).toBe(true) + }) + + it('should return false when the user has no Staff role and the account is not a staff account', () => { + mockKeycloakRoles = [] + store.currentAccount.accountType = 'basic' + + const affStore = useAffiliationsStore() + + expect(affStore.isStaffOrSbcStaff).toBe(false) + }) + }) + + describe('handleManageBusinessOrNameRequest', () => { + let affStore: any + + beforeEach(() => { + affStore = useAffiliationsStore() + }) + + it('should open manage business modal when searchType is "reg"', () => { + const consoleSpy = vi.spyOn(console, 'log') + + affStore.handleManageBusinessOrNameRequest('reg', { names: ['test'], nrNum: 'NR123' }) + + expect(consoleSpy).toHaveBeenCalledWith('open manage business modal') + }) + + it.skip('should call addNameRequestForStaffSilently when isStaffOrSbcStaff is true', async () => { // TODO: figure out why auth api isnt being called + mockAuthApi.mockResolvedValue({ status: 200 }) + mockKeycloakRoles = ['staff'] // set staff role + + const event = { names: ['test'], nrNum: 'NR123' } + + await affStore.handleManageBusinessOrNameRequest('someType', event) + + expect(mockAuthApi).toHaveBeenCalledTimes(2) // once for creating affiliation and once for reloading affiliations + + expect(mockAddToast).toHaveBeenCalledOnce() + }) + + it('should open the manage name request modal when isStaffOrSbcStaff is false', () => { + const event = { names: ['test'], nrNum: 'NR123' } + + affStore.handleManageBusinessOrNameRequest('someType', event) + + expect(mockOpenManageNameRequest).toHaveBeenCalledOnce() + }) + }) + + describe('addNameRequestForStaffSilently', () => { + it('should successfully add a name request for staff and reload affiliations', async () => { + mockAuthApi.mockResolvedValue(undefined) + const affStore = useAffiliationsStore() + + const businessIdentifier = 'NR123' + await affStore.addNameRequestForStaffSilently(businessIdentifier) + + // Assert that the API was called + expect(mockAuthApi).toHaveBeenCalledWith('/orgs/123/affiliations?newBusiness=true', { + body: { + businessIdentifier: 'NR123' + }, + method: 'POST' + }) + + // should have success toast + expect(mockAddToast).toHaveBeenCalledOnce() + + expect(mockAuthApi).toHaveBeenCalledTimes(2) // once for affiliation once for reload + }) + + it('should handle error by opening the manage name request error modal', async () => { + mockAuthApi.mockRejectedValueOnce(new Error('API Error')) // throw error + const affStore = useAffiliationsStore() + + const businessIdentifier = 'NR123' + await affStore.addNameRequestForStaffSilently(businessIdentifier) + + expect(mockAuthApi).toHaveBeenCalledOnce() // auth api only called once when error is thrown + + expect(mockAddToast).not.toHaveBeenCalled() // success toast should not be called + expect(mockOpenManageNRError).toHaveBeenCalledOnce() // manage nr error modal should be called + }) + }) }) From 7c6162e72e4c8898783206586e8c9815cdb24681 Mon Sep 17 00:00:00 2001 From: deetz99 Date: Thu, 5 Sep 2024 14:05:56 -0700 Subject: [PATCH 4/7] add translations and test useBrdModals composable --- .../app/composables/useBrdModals.ts | 18 +- .../app/locales/en-CA.ts | 14 ++ .../app/locales/fr-CA.ts | 4 + .../unit/composables/useBrdModals.test.ts | 233 ++++++++++++++++++ 4 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 business-registry-dashboard/tests/unit/composables/useBrdModals.test.ts diff --git a/business-registry-dashboard/app/composables/useBrdModals.ts b/business-registry-dashboard/app/composables/useBrdModals.ts index 3b0df4f..33e841d 100644 --- a/business-registry-dashboard/app/composables/useBrdModals.ts +++ b/business-registry-dashboard/app/composables/useBrdModals.ts @@ -24,25 +24,27 @@ export const useBrdModals = () => { function openBusinessAddError () { modal.open(ModalBase, { error: { - title: 'Error Adding Existing Business', // TODO: add translations - description: 'An error occurred adding your business. Please try again.' + title: t('error.businessAdd.title'), + description: t('error.businessAdd.description') }, actions: [{ label: t('btn.close'), handler: () => close() }] }) } - function openBusinessUnavailableError (action: string) { // TODO: add translations - let description = 'You are not authorized to access the business' + function openBusinessUnavailableError (action: string) { + let title: string + let description: string if (action === 'change name') { - description += ' to change its name' + title = t('error.businessUnavailable.changeName.title') + description = t('error.businessUnavailable.changeName.description') } else { - description += ' you wish to ' + action + title = t('error.businessUnavailable.generic.title') + description = t('error.businessUnavailable.generic.description', { action }) } - description += '. Please add this business to your table to continue.' modal.open(ModalBase, { error: { - title: 'Business Unavailable', + title, description, showContactInfo: true }, diff --git a/business-registry-dashboard/app/locales/en-CA.ts b/business-registry-dashboard/app/locales/en-CA.ts index c44e47f..057ffe2 100644 --- a/business-registry-dashboard/app/locales/en-CA.ts +++ b/business-registry-dashboard/app/locales/en-CA.ts @@ -147,6 +147,20 @@ export default { generic: { title: 'Something Went Wrong', description: 'An error occurred, please try again. If this error persists, please contact us.' + }, + businessAdd: { + title: 'Error Adding Existing Business', + description: 'An error occurred adding your business. Please try again.' + }, + businessUnavailable: { + changeName: { + title: 'Business Unavailable', + description: 'You are not authorized to access the business to change its name. Please add this business to your table to continue.' + }, + generic: { + title: 'Business Unavailable', + description: 'You are not authorized to access the business you wish to {action}. Please add this business to your table to continue.' + } } }, entityAlertTypes: { diff --git a/business-registry-dashboard/app/locales/fr-CA.ts b/business-registry-dashboard/app/locales/fr-CA.ts index 6cac45e..b18f409 100644 --- a/business-registry-dashboard/app/locales/fr-CA.ts +++ b/business-registry-dashboard/app/locales/fr-CA.ts @@ -147,6 +147,10 @@ export default { generic: { title: "Quelque Chose S'est Mal Passé", description: "Une erreur s'est produite, veuillez réessayer. Si cette erreur persiste, veuillez nous contacter." + }, + businessAdd: { + title: "Error lors de l'ajout de l'Entreprise Existante", + description: "Une erreur s'est produite lors de l'ajout de votre entreprise. Veuillez réessayer." } }, entityAlertTypes: { diff --git a/business-registry-dashboard/tests/unit/composables/useBrdModals.test.ts b/business-registry-dashboard/tests/unit/composables/useBrdModals.test.ts new file mode 100644 index 0000000..50b7d5e --- /dev/null +++ b/business-registry-dashboard/tests/unit/composables/useBrdModals.test.ts @@ -0,0 +1,233 @@ +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' +import { mockNuxtImport } from '@nuxt/test-utils/runtime' + +const mockOpenModal = vi.fn() +const mockCloseModal = vi.fn() +mockNuxtImport('useModal', () => { + return () => ( + { + open: mockOpenModal, + close: mockCloseModal + } + ) +}) + +mockNuxtImport('useI18n', () => { + return () => ( + { + t: (key: string) => key, + locale: { + value: 'en-CA' + } + } + ) +}) + +describe('useBrdModals', () => { + let brdModal: ReturnType + + beforeEach(() => { + brdModal = useBrdModals() + }) + + afterEach(() => { + vi.unstubAllGlobals() + vi.clearAllMocks() + vi.resetAllMocks() + }) + + describe('openManageNameRequest', () => { + it('should open ModalManageNameRequest with correct props', () => { + const nr = { names: ['Test Name'], nrNum: 'NR123' } + brdModal.openManageNameRequest(nr) + + const callArgs = mockOpenModal.mock.calls[0] + + const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + expect(modalComponent.__name).toBe('ManageNameRequest') + expect(modalProps).toEqual( + expect.objectContaining({ + nameRequest: { + names: ['Test Name'], + nrNum: 'NR123' + } + })) + }) + }) + + describe('openManageNRError', () => { + it('should open ModalBase with correct props', () => { + brdModal.openManageNRError() + + const callArgs = mockOpenModal.mock.calls[0] + + const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + expect(modalComponent.__name).toBe('Base') + expect(modalProps).toEqual( + expect.objectContaining({ + actions: [{ + handler: expect.any(Function), + label: 'btn.close' + }], + error: { + description: 'form.manageNR.error.default.description', + title: 'form.manageNR.error.default.title' + } + })) + }) + }) + + describe('openBusinessAddError', () => { + it('should open ModalBase with correct props', () => { + brdModal.openBusinessAddError() + + const callArgs = mockOpenModal.mock.calls[0] + + const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + expect(modalComponent.__name).toBe('Base') + expect(modalProps).toEqual( + expect.objectContaining({ + actions: [{ + handler: expect.any(Function), + label: 'btn.close' + }], + error: { + description: 'error.businessAdd.description', + title: 'error.businessAdd.title' + } + })) + }) + }) + + describe('openBusinessUnavailableError', () => { + it('should open ModalBase with correct props when action is change name', () => { + const action = 'change name' + brdModal.openBusinessUnavailableError(action) + + const callArgs = mockOpenModal.mock.calls[0] + + const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + expect(modalComponent.__name).toBe('Base') + expect(modalProps).toEqual( + expect.objectContaining({ + actions: [{ + handler: expect.any(Function), + label: 'btn.ok' + }], + error: { + description: 'error.businessUnavailable.changeName.description', + title: 'error.businessUnavailable.changeName.title', + showContactInfo: true + } + })) + }) + + it('should open ModalBase with correct props when action is not change name', () => { + const action = 'restore' + brdModal.openBusinessUnavailableError(action) + + const callArgs = mockOpenModal.mock.calls[0] + + const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + expect(modalComponent.__name).toBe('Base') + expect(modalProps).toEqual( + expect.objectContaining({ + actions: [{ + handler: expect.any(Function), + label: 'btn.ok' + }], + error: { + description: 'error.businessUnavailable.generic.description', + title: 'error.businessUnavailable.generic.title', + showContactInfo: true + } + })) + }) + }) + + describe('openBusinessRemovalConfirmation', () => { + it('should open ModalRemoveBusiness for generic type', () => { + [CorpTypes.NAME_REQUEST, + CorpTypes.INCORPORATION_APPLICATION, + CorpTypes.AMALGAMATION_APPLICATION, + CorpTypes.REGISTRATION, + CorpTypes.PARTNERSHIP, + CorpTypes.SOLE_PROP].forEach((item) => { + const payload = { + business: { corpType: { code: item } } + } + + // @ts-expect-error - payload arg doesnt match function param + brdModal.openBusinessRemovalConfirmation(payload) + + const callArgs = mockOpenModal.mock.calls[0] + + // const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + // expect(modalComponent.__name).toBe('RemoveBusiness') // TODO: why is this returning index instead of RemoveBusiness + expect(modalProps).toEqual( + expect.objectContaining({ + removeBusinessPayload: { + business: { + corpType: { + code: item + } + } + }, + type: 'generic' + }) + ) + + vi.clearAllMocks() + vi.restoreAllMocks() + }) + }) + + it('should open ModalRemoveBusiness for passcode type', () => { + const payload = { + business: { corpType: { code: 'some other code' } } + } + + // @ts-expect-error - payload arg doesnt match function param + brdModal.openBusinessRemovalConfirmation(payload) + + const callArgs = mockOpenModal.mock.calls[0] + + // const modalComponent = callArgs[0] + const modalProps = callArgs[1] + + // expect(modalComponent.__name).toBe('RemoveBusiness') // TODO: why is this returning index instead of RemoveBusiness + expect(modalProps).toEqual( + expect.objectContaining({ + removeBusinessPayload: { + business: { + corpType: { + code: 'some other code' + } + } + }, + type: 'passcode' + }) + ) + }) + }) + + describe('close', () => { + it('should call the close method', () => { + brdModal.close() + + expect(mockCloseModal).toHaveBeenCalledOnce() + }) + }) +}) From 65ab307c6621ec8855245c5a2907fcc2fb0f6fcf Mon Sep 17 00:00:00 2001 From: deetz99 Date: Thu, 5 Sep 2024 14:09:09 -0700 Subject: [PATCH 5/7] remove unused components --- .../app/components/Sbc/Alert.vue | 34 ----- .../app/components/Sbc/DatePicker.vue | 136 ------------------ 2 files changed, 170 deletions(-) delete mode 100644 business-registry-dashboard/app/components/Sbc/Alert.vue delete mode 100644 business-registry-dashboard/app/components/Sbc/DatePicker.vue diff --git a/business-registry-dashboard/app/components/Sbc/Alert.vue b/business-registry-dashboard/app/components/Sbc/Alert.vue deleted file mode 100644 index f0ffb5b..0000000 --- a/business-registry-dashboard/app/components/Sbc/Alert.vue +++ /dev/null @@ -1,34 +0,0 @@ - - diff --git a/business-registry-dashboard/app/components/Sbc/DatePicker.vue b/business-registry-dashboard/app/components/Sbc/DatePicker.vue deleted file mode 100644 index 930425b..0000000 --- a/business-registry-dashboard/app/components/Sbc/DatePicker.vue +++ /dev/null @@ -1,136 +0,0 @@ - - - From b5d1025ee792001fe50b52e5ba60a6e37e2c4813 Mon Sep 17 00:00:00 2001 From: deetz99 Date: Thu, 5 Sep 2024 14:23:13 -0700 Subject: [PATCH 6/7] test sbci18nbold compoennt --- .../app/locales/en-CA.ts | 6 ++++ .../tests/unit/components/SbcI18nBold.test.ts | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts diff --git a/business-registry-dashboard/app/locales/en-CA.ts b/business-registry-dashboard/app/locales/en-CA.ts index 057ffe2..0702b6b 100644 --- a/business-registry-dashboard/app/locales/en-CA.ts +++ b/business-registry-dashboard/app/locales/en-CA.ts @@ -535,5 +535,11 @@ export default { error: 'Error retrieving search results, please try again later.', resultListLabel: 'Search Results', noResults: 'No results found' + }, + test: { + i18nBold: { + strong: 'This should have {boldStart} bold {boldEnd} text', + strongWithProps: 'This should have {boldStart} bold {boldEnd} text and allow a {prop}' + } } } diff --git a/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts b/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts new file mode 100644 index 0000000..c763df8 --- /dev/null +++ b/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi } from 'vitest' +import { mountSuspended } from '@nuxt/test-utils/runtime' +import { SbcI18nBold } from '#components' +import { enI18n } from '~~/tests/mocks/i18n' + +describe('', () => { + it('should add bold tags around text', async () => { + const wrapper = await mountSuspended(SbcI18nBold, { + props: { + translationPath: 'test.i18nBold.strong' + }, + global: { + plugins: [enI18n] + } + }) + + expect(wrapper.html()).toContain('This should have bold text') + }) + + it('should allow other props', async () => { + const wrapper = await mountSuspended(SbcI18nBold, { + props: { + translationPath: 'test.i18nBold.strongWithProps', + prop: 'prop to be added' + }, + global: { + plugins: [enI18n] + } + }) + + expect(wrapper.html()).toContain('This should have bold text and allow a prop to be added') + }) +}) From 0e10b40e5fcb4a35b9953505cbce07a7cc767456 Mon Sep 17 00:00:00 2001 From: deetz99 Date: Thu, 5 Sep 2024 14:25:51 -0700 Subject: [PATCH 7/7] fix bold test --- .../tests/unit/components/SbcI18nBold.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts b/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts index c763df8..c1fd995 100644 --- a/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts +++ b/business-registry-dashboard/tests/unit/components/SbcI18nBold.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest' +import { describe, it, expect } from 'vitest' import { mountSuspended } from '@nuxt/test-utils/runtime' import { SbcI18nBold } from '#components' import { enI18n } from '~~/tests/mocks/i18n'