diff --git a/web/__test__/store/activationCodeData.test.ts b/web/__test__/store/activationCodeData.test.ts new file mode 100644 index 000000000..8bb0e0234 --- /dev/null +++ b/web/__test__/store/activationCodeData.test.ts @@ -0,0 +1,193 @@ +import { ref } from 'vue'; +import { createPinia, setActivePinia } from 'pinia'; +import { useQuery } from '@vue/apollo-composable'; + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + ACTIVATION_CODE_QUERY, + PARTNER_INFO_QUERY, +} from '~/components/Activation/graphql/activationCode.query'; +import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData'; +import { RegistrationState } from '~/composables/gql/graphql'; + +// Create a complete mock of UseQueryReturn with all required properties +const createCompleteQueryMock = (result: T | null = null, loading = false) => ({ + result: ref(result), + loading: ref(loading), + error: ref(null), + networkStatus: ref(7), + called: ref(true), + variables: ref({}), + document: ref(null), + query: ref(null), + forceDisabled: ref(false), + options: { errorPolicy: 'all' as const }, + stop: vi.fn(), + start: vi.fn(), + restart: vi.fn(), + refetch: vi.fn(), + fetchMore: vi.fn(), + onResult: vi.fn(), + onError: vi.fn(), + subscribeToMore: vi.fn(), + updateQuery: vi.fn(), +}); + +vi.mock('@vue/apollo-composable', () => ({ + useQuery: vi.fn(), +})); + +describe('ActivationCodeData Store', () => { + beforeEach(() => { + setActivePinia(createPinia()); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + describe('Computed Properties', () => { + it('should compute loading state when activationCodeLoading is true', () => { + vi.mocked(useQuery).mockImplementation((query) => { + if (query === ACTIVATION_CODE_QUERY) { + return createCompleteQueryMock(null, true); + } + + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.loading).toBe(true); + }); + + it('should compute loading state when partnerInfoLoading is true', () => { + vi.mocked(useQuery).mockImplementation((query) => { + if (query === PARTNER_INFO_QUERY) { + return createCompleteQueryMock(null, true); + } + + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.loading).toBe(true); + }); + + it('should compute loading state when both loadings are false', () => { + vi.mocked(useQuery).mockImplementation(() => createCompleteQueryMock(null, false)); + + const store = useActivationCodeDataStore(); + + expect(store.loading).toBe(false); + }); + + it('should compute activationCode correctly', () => { + const mockActivationCode = 'TEST-CODE-123'; + + vi.mocked(useQuery).mockImplementation((query) => { + if (query === ACTIVATION_CODE_QUERY) { + return createCompleteQueryMock( + { + customization: { activationCode: mockActivationCode }, + }, + false + ); + } + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.activationCode).toBe(mockActivationCode); + }); + + it('should compute isFreshInstall as true when regState is ENOKEYFILE', () => { + vi.mocked(useQuery).mockImplementation((query) => { + if (query === ACTIVATION_CODE_QUERY) { + return createCompleteQueryMock( + { + vars: { regState: RegistrationState.ENOKEYFILE }, + }, + false + ); + } + + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.isFreshInstall).toBe(true); + }); + + it('should compute isFreshInstall as false when regState is not ENOKEYFILE', () => { + vi.mocked(useQuery).mockImplementation((query) => { + if (query === ACTIVATION_CODE_QUERY) { + return createCompleteQueryMock( + { + vars: { regState: 'REGISTERED' as RegistrationState }, + }, + false + ); + } + + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.isFreshInstall).toBe(false); + }); + + it('should use publicPartnerInfo when available', () => { + const mockPublicPartnerInfo = { name: 'Public Partner' }; + vi.mocked(useQuery).mockImplementation((query) => { + if (query === PARTNER_INFO_QUERY) { + return createCompleteQueryMock( + { + publicPartnerInfo: mockPublicPartnerInfo, + }, + false + ); + } + + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.partnerInfo).toEqual(mockPublicPartnerInfo); + }); + + it('should fallback to activationCode partnerInfo when publicPartnerInfo is null', () => { + const mockPartnerInfo = { name: 'Activation Partner' }; + vi.mocked(useQuery).mockImplementation((query) => { + if (query === ACTIVATION_CODE_QUERY) { + return createCompleteQueryMock( + { + customization: { partnerInfo: mockPartnerInfo }, + }, + false + ); + } else if (query === PARTNER_INFO_QUERY) { + return createCompleteQueryMock( + { + publicPartnerInfo: null, + }, + false + ); + } + + return createCompleteQueryMock(null, false); + }); + + const store = useActivationCodeDataStore(); + + expect(store.partnerInfo).toEqual(mockPartnerInfo); + }); + }); +}); diff --git a/web/__test__/store/activationCodeModal.test.ts b/web/__test__/store/activationCodeModal.test.ts new file mode 100644 index 000000000..f21dc3a08 --- /dev/null +++ b/web/__test__/store/activationCodeModal.test.ts @@ -0,0 +1,139 @@ +import { ref } from 'vue'; +import { createPinia, setActivePinia } from 'pinia'; +import { useSessionStorage } from '@vueuse/core'; + +import { ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY } from '~/consts'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData'; +import { useActivationCodeModalStore } from '~/components/Activation/store/activationCodeModal'; +import { useCallbackActionsStore } from '~/store/callbackActions'; + +vi.mock('@vueuse/core', () => ({ + useSessionStorage: vi.fn(), +})); + +vi.mock('~/components/Activation/store/activationCodeData', () => ({ + useActivationCodeDataStore: vi.fn(), +})); + +vi.mock('~/store/callbackActions', () => ({ + useCallbackActionsStore: vi.fn(), +})); + +describe('ActivationCodeModal Store', () => { + let store: ReturnType; + let mockIsHidden: ReturnType; + let mockIsFreshInstall: ReturnType; + let mockCallbackData: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + + // Mock window.location to prevent navigation errors + Object.defineProperty(window, 'location', { + value: { href: '' }, + writable: true, + }); + + mockIsHidden = ref(null); + mockIsFreshInstall = ref(false); + mockCallbackData = ref(null); + + vi.mocked(useSessionStorage).mockReturnValue(mockIsHidden); + vi.mocked(useActivationCodeDataStore).mockReturnValue({ + isFreshInstall: mockIsFreshInstall, + } as unknown as ReturnType); + vi.mocked(useCallbackActionsStore).mockReturnValue({ + callbackData: mockCallbackData, + } as unknown as ReturnType); + + setActivePinia(createPinia()); + store = useActivationCodeModalStore(); + }); + + afterEach(() => { + vi.resetAllMocks(); + mockIsHidden.value = null; + mockIsFreshInstall.value = false; + mockCallbackData.value = null; + }); + + describe('State Management', () => { + it('should initialize with correct storage key', () => { + expect(useSessionStorage).toHaveBeenCalledWith(ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY, null); + }); + + it('should set isHidden value correctly', () => { + store.setIsHidden(true); + expect(mockIsHidden.value).toBe(true); + + store.setIsHidden(false); + expect(mockIsHidden.value).toBe(false); + + store.setIsHidden(null); + expect(mockIsHidden.value).toBe(null); + }); + }); + + describe('Computed Properties', () => { + it('should be visible when explicitly set to show', () => { + mockIsHidden.value = false; + + expect(store.isVisible).toBe(true); + }); + + it('should be visible when fresh install and not explicitly hidden', () => { + mockIsHidden.value = null; + mockIsFreshInstall.value = true; + mockCallbackData.value = null; + + expect(store.isVisible).toBe(true); + }); + + it('should not be visible when explicitly hidden', () => { + mockIsHidden.value = true; + + expect(store.isVisible).toBe(false); + }); + + it('should not be visible when not fresh install', () => { + mockIsHidden.value = null; + mockIsFreshInstall.value = false; + + expect(store.isVisible).toBe(false); + }); + + it('should not be visible when callback data exists', () => { + mockIsHidden.value = null; + mockIsFreshInstall.value = true; + mockCallbackData.value = { someData: 'test' }; + + expect(store.isVisible).toBe(false); + }); + }); + + describe('Konami Code Handling', () => { + const keySequence = [ + 'ArrowUp', + 'ArrowUp', + 'ArrowDown', + 'ArrowDown', + 'ArrowLeft', + 'ArrowRight', + 'ArrowLeft', + 'ArrowRight', + 'b', + 'a', + ]; + + it('should not trigger on partial sequence', () => { + keySequence.slice(0, 3).forEach((key) => { + window.dispatchEvent(new KeyboardEvent('keydown', { key })); + }); + + expect(mockIsHidden.value).toBe(null); + expect(window.location.href).toBe(''); + }); + }); +});