diff --git a/README.md b/README.md index d7f2dd3..01d8262 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,12 @@ To learn more about the **Common Services** available visit the [Common Services app/ - Application Root ├── config/ - configuration exposed as environment variables ├── src/ - Node.js web application -│ └── components/ - Components Layer -| └── controllers/ - Controller Layer -| └── middleware/ - Middleware Layer -| └── routes/ - Routes Layer -| └── services/ - Services Layer -│ ├── types/ - Typescript type definitions +│ ├── components/ - Components Layer +│ ├── controllers/ - Controller Layer +│ ├── middleware/ - Middleware Layer +│ ├── routes/ - Routes Layer +│ ├── services/ - Services Layer +│ └── types/ - Typescript type definitions └── tests/ - Node.js web application tests frontend/ - Frontend Root ├── src/ - Node.js web application diff --git a/frontend/tests/unit/components/layout/Footer.spec.ts b/frontend/tests/unit/components/layout/Footer.spec.ts new file mode 100644 index 0000000..1a8548b --- /dev/null +++ b/frontend/tests/unit/components/layout/Footer.spec.ts @@ -0,0 +1,53 @@ +import { createTestingPinia } from '@pinia/testing'; +import { mount, shallowMount } from '@vue/test-utils'; + +import PrimeVue from 'primevue/config'; +import Footer from '@/components/layout/Footer.vue'; +import { StorageKey } from '@/utils/constants'; + +// Mock router calls +vi.mock('vue-router', () => ({ + useRouter: () => ({ + push: vi.fn() + }) +})); + +beforeEach(() => { + sessionStorage.setItem( + StorageKey.CONFIG, + JSON.stringify({ + oidc: { + authority: 'abc', + clientId: '123' + } + }) + ); + + vi.clearAllMocks(); +}); + +afterEach(() => { + sessionStorage.clear(); +}); + +describe('Footer.vue', () => { + it('renders', () => { + const wrapper = shallowMount(Footer, { + global: { + plugins: [createTestingPinia(), PrimeVue] + } + }); + expect(wrapper).toBeTruthy(); + }); + + it('contains 7 buttons', () => { + const wrapper = mount(Footer, { + global: { + plugins: [createTestingPinia(), PrimeVue] + } + }); + + const btn = wrapper.findAll('button'); + expect(btn).toHaveLength(7); + }); +}); diff --git a/frontend/tests/unit/components/layout/Navbar.spec.ts b/frontend/tests/unit/components/layout/Navbar.spec.ts new file mode 100644 index 0000000..a821c39 --- /dev/null +++ b/frontend/tests/unit/components/layout/Navbar.spec.ts @@ -0,0 +1,85 @@ +import { createTestingPinia } from '@pinia/testing'; +import { mount, shallowMount, RouterLinkStub } from '@vue/test-utils'; + +import Navbar from '@/components/layout/Navbar.vue'; +import { StorageKey } from '@/utils/constants'; +import PrimeVue from 'primevue/config'; + +beforeEach(() => { + sessionStorage.setItem( + StorageKey.CONFIG, + JSON.stringify({ + oidc: { + authority: 'abc', + clientId: '123' + } + }) + ); + + vi.clearAllMocks(); +}); + +afterEach(() => { + sessionStorage.clear(); +}); + +describe('Navbar.vue', () => { + it('renders', () => { + const wrapper = shallowMount(Navbar, { + global: { + plugins: [createTestingPinia(), PrimeVue], + stubs: { + RouterLink: RouterLinkStub + } + } + }); + expect(wrapper).toBeTruthy(); + }); + + describe('isAuthenticated', () => { + it('shows correct navbar when true', () => { + const wrapper = mount(Navbar, { + global: { + plugins: [ + createTestingPinia({ + initialState: { + auth: { isAuthenticated: true } + } + }), + PrimeVue + ], + stubs: { + RouterLink: RouterLinkStub + } + } + }); + const linkEle = wrapper.findAll('a'); + expect(linkEle).toHaveLength(3); + expect(linkEle[0].text()).toBe('Home'); + expect(linkEle[1].text()).toBe('Stylings'); + expect(linkEle[2].text()).toBe('Secured'); + }); + + it('shows correct navbar when false', async () => { + const wrapper = mount(Navbar, { + global: { + plugins: [ + createTestingPinia({ + initialState: { + auth: { isAuthenticated: false } + } + }), + PrimeVue + ], + stubs: { + RouterLink: RouterLinkStub + } + } + }); + const linkEle = wrapper.findAll('a'); + expect(linkEle).toHaveLength(2); + expect(linkEle[0].text()).toBe('Home'); + expect(linkEle[1].text()).toBe('Stylings'); + }); + }); +}); diff --git a/frontend/tests/unit/components/layout/ProgressLoader.spec.ts b/frontend/tests/unit/components/layout/ProgressLoader.spec.ts new file mode 100644 index 0000000..98f6f8b --- /dev/null +++ b/frontend/tests/unit/components/layout/ProgressLoader.spec.ts @@ -0,0 +1,24 @@ +import { createTestingPinia } from '@pinia/testing'; +import { mount } from '@vue/test-utils'; + +import { ProgressBar } from '@/lib/primevue'; +import PrimeVue from 'primevue/config'; + +beforeEach(() => { + vi.clearAllMocks(); +}); + +afterEach(() => { + sessionStorage.clear(); +}); + +describe('ProgressBar.vue', () => { + it('renders', () => { + const wrapper = mount(ProgressBar, { + global: { + plugins: [createTestingPinia(), PrimeVue] + } + }); + expect(wrapper).toBeTruthy(); + }); +}); diff --git a/frontend/tests/unit/components/layout/Spinner.spec.ts b/frontend/tests/unit/components/layout/Spinner.spec.ts new file mode 100644 index 0000000..67f6dd1 --- /dev/null +++ b/frontend/tests/unit/components/layout/Spinner.spec.ts @@ -0,0 +1,24 @@ +import { shallowMount } from '@vue/test-utils'; + +import Spinner from '@/components/layout/Spinner.vue'; +import PrimeVue from 'primevue/config'; + +beforeEach(() => { + vi.clearAllMocks(); +}); + +afterEach(() => { + sessionStorage.clear(); +}); + +describe('Spinner.vue', async () => { + it('renders', () => { + const wrapper = shallowMount(Spinner, { + global: { + plugins: [PrimeVue] + } + }); + + expect(wrapper).toBeTruthy(); + }); +}); diff --git a/frontend/tests/unit/store/helloStore.spec.ts b/frontend/tests/unit/store/helloStore.spec.ts new file mode 100644 index 0000000..360ee71 --- /dev/null +++ b/frontend/tests/unit/store/helloStore.spec.ts @@ -0,0 +1,42 @@ +import { setActivePinia, createPinia } from 'pinia'; + +import { helloService } from '@/services'; +import { useAppStore, useHelloStore } from '@/store'; + +import type { StoreGeneric } from 'pinia'; +import type { SpyInstance } from 'vitest'; + +beforeEach(() => { + setActivePinia(createPinia()); + vi.clearAllMocks(); +}); + +describe('Hello Store', () => { + let appStore: StoreGeneric; + let helloStore: StoreGeneric; + + let beginIndeterminateLoadingSpy: SpyInstance; + let endIndeterminateLoadingSpy: SpyInstance; + + let helloWorldSpy: SpyInstance; + + beforeEach(() => { + appStore = useAppStore(); + helloStore = useHelloStore(); + + beginIndeterminateLoadingSpy = vi.spyOn(appStore, 'beginIndeterminateLoading'); + endIndeterminateLoadingSpy = vi.spyOn(appStore, 'endIndeterminateLoading'); + helloWorldSpy = vi.spyOn(helloService, 'helloWorld'); + }); + + it('calls the service', async () => { + helloWorldSpy.mockReturnValue({ data: 'Hello world!' } as any); + + await helloStore.helloWorld(); + + expect(beginIndeterminateLoadingSpy).toHaveBeenCalledTimes(1); + expect(helloWorldSpy).toHaveBeenCalledTimes(1); + expect(helloStore.getHello).toBe('Hello world!'); + expect(endIndeterminateLoadingSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/frontend/tests/unit/utils/formatters.spec.ts b/frontend/tests/unit/utils/formatters.spec.ts index 73d3cb8..bb1b5f6 100644 --- a/frontend/tests/unit/utils/formatters.spec.ts +++ b/frontend/tests/unit/utils/formatters.spec.ts @@ -1,14 +1,36 @@ -import { toKebabCase } from '@/utils/formatters'; +import { formatDate, formatDateLong, toKebabCase } from '@/utils/formatters'; -describe('formatters.ts toKebabCase', () => { - it('returns the expected UUID values', () => { - expect(toKebabCase('descriptive Variable name')).toEqual('descriptive-variable-name'); - expect(toKebabCase('INTERESTING FILE')).toEqual('interesting-file'); - expect(toKebabCase('abc')).toEqual('abc'); +describe('formatters.ts', () => { + describe('formatDate', () => { + it('returns the expected date format', () => { + expect(formatDate(new Date(2023, 10, 1).toISOString())).toEqual('November 1 2023'); + }); + + it('returns empty string if given no value', () => { + expect(formatDate('')).toEqual(''); + }); + + it('returns empty string if given invalid format', () => { + expect(formatDate('asd')).toEqual(''); + }); }); - it('returns blanks if blank provided', () => { - expect(toKebabCase('')).toEqual(''); - expect(toKebabCase(null)).toEqual(''); + describe('formatDateLong', () => { + it('returns the expected date format', () => { + expect(formatDateLong(new Date(2023, 10, 1, 0, 0, 0).toISOString())).toEqual('November 1 2023, 12:00:00 AM'); + }); + }); + + describe('toKebabCase', () => { + it('returns the expected UUID values', () => { + expect(toKebabCase('descriptive Variable name')).toEqual('descriptive-variable-name'); + expect(toKebabCase('INTERESTING FILE')).toEqual('interesting-file'); + expect(toKebabCase('abc')).toEqual('abc'); + }); + + it('returns blanks if blank provided', () => { + expect(toKebabCase('')).toEqual(''); + expect(toKebabCase(null)).toEqual(''); + }); }); });