import { describe, it, expect, vi, beforeEach } from 'vitest' import { ref, computed } from 'vue' import { MEMBER_STATUSES, MEMBER_STATUS_CONFIG, useMemberStatus } from '../../../app/composables/useMemberStatus.js' // Stub Vue's computed as a global (Nuxt auto-import) vi.stubGlobal('computed', computed) // Shared reactive ref for controlling member status in tests const memberData = ref({ status: 'active' }) vi.stubGlobal('useAuth', () => ({ memberData })) describe('MEMBER_STATUSES', () => { it('has all four status keys', () => { expect(Object.keys(MEMBER_STATUSES)).toEqual([ 'PENDING_PAYMENT', 'ACTIVE', 'SUSPENDED', 'CANCELLED', ]) }) it('maps to expected string values', () => { expect(MEMBER_STATUSES.PENDING_PAYMENT).toBe('pending_payment') expect(MEMBER_STATUSES.ACTIVE).toBe('active') expect(MEMBER_STATUSES.SUSPENDED).toBe('suspended') expect(MEMBER_STATUSES.CANCELLED).toBe('cancelled') }) }) describe('MEMBER_STATUS_CONFIG', () => { const requiredFields = ['label', 'color', 'canRSVP', 'canAccessMembers', 'canPeerSupport'] it('has config for every status value', () => { for (const status of Object.values(MEMBER_STATUSES)) { expect(MEMBER_STATUS_CONFIG).toHaveProperty(status) } }) it('each config has required fields', () => { for (const [key, config] of Object.entries(MEMBER_STATUS_CONFIG)) { for (const field of requiredFields) { expect(config, `${key} missing ${field}`).toHaveProperty(field) } } }) it('active has full permissions', () => { const cfg = MEMBER_STATUS_CONFIG.active expect(cfg.canRSVP).toBe(true) expect(cfg.canAccessMembers).toBe(true) expect(cfg.canPeerSupport).toBe(true) }) it('pending_payment can access members but not RSVP or peer support', () => { const cfg = MEMBER_STATUS_CONFIG.pending_payment expect(cfg.canRSVP).toBe(false) expect(cfg.canAccessMembers).toBe(true) expect(cfg.canPeerSupport).toBe(false) }) it('suspended has all permissions false', () => { const cfg = MEMBER_STATUS_CONFIG.suspended expect(cfg.canRSVP).toBe(false) expect(cfg.canAccessMembers).toBe(false) expect(cfg.canPeerSupport).toBe(false) }) it('cancelled has all permissions false', () => { const cfg = MEMBER_STATUS_CONFIG.cancelled expect(cfg.canRSVP).toBe(false) expect(cfg.canAccessMembers).toBe(false) expect(cfg.canPeerSupport).toBe(false) }) }) describe('useMemberStatus composable', () => { beforeEach(() => { memberData.value = { status: 'active' } }) describe('status detection', () => { it('defaults to pending_payment when memberData has no status', () => { memberData.value = {} const { status } = useMemberStatus() expect(status.value).toBe('pending_payment') }) it('defaults to pending_payment when memberData is null', () => { memberData.value = null const { status } = useMemberStatus() expect(status.value).toBe('pending_payment') }) it('isActive is true when status is active', () => { memberData.value = { status: 'active' } const { isActive } = useMemberStatus() expect(isActive.value).toBe(true) }) it('isActive is false when status is not active', () => { memberData.value = { status: 'suspended' } const { isActive, isInactive } = useMemberStatus() expect(isActive.value).toBe(false) expect(isInactive.value).toBe(true) }) }) describe('permissions', () => { it('canRSVP is true when active', () => { memberData.value = { status: 'active' } const { canRSVP } = useMemberStatus() expect(canRSVP.value).toBe(true) }) it('canRSVP is false when pending_payment', () => { memberData.value = { status: 'pending_payment' } const { canRSVP } = useMemberStatus() expect(canRSVP.value).toBe(false) }) it('canAccessMembers is true for active and pending_payment', () => { for (const status of ['active', 'pending_payment']) { memberData.value = { status } const { canAccessMembers } = useMemberStatus() expect(canAccessMembers.value, `expected true for ${status}`).toBe(true) } }) it('canAccessMembers is false for suspended and cancelled', () => { for (const status of ['suspended', 'cancelled']) { memberData.value = { status } const { canAccessMembers } = useMemberStatus() expect(canAccessMembers.value, `expected false for ${status}`).toBe(false) } }) }) describe('getNextAction', () => { it('returns Complete Payment for pending_payment', () => { memberData.value = { status: 'pending_payment' } const { getNextAction } = useMemberStatus() const action = getNextAction() expect(action.label).toBe('Complete Payment') expect(action.link).toBe('/member/profile#account') }) it('returns Reactivate Membership for cancelled', () => { memberData.value = { status: 'cancelled' } const { getNextAction } = useMemberStatus() const action = getNextAction() expect(action.label).toBe('Reactivate Membership') expect(action.link).toBe('/member/profile#account') }) it('returns Contact Support for suspended', () => { memberData.value = { status: 'suspended' } const { getNextAction } = useMemberStatus() const action = getNextAction() expect(action.label).toBe('Contact Support') expect(action.link).toBe('mailto:support@ghostguild.org') }) it('returns null for active', () => { memberData.value = { status: 'active' } const { getNextAction } = useMemberStatus() expect(getNextAction()).toBeNull() }) }) describe('getBannerMessage', () => { it('returns payment message for pending_payment', () => { memberData.value = { status: 'pending_payment' } const { getBannerMessage } = useMemberStatus() expect(getBannerMessage()).toContain('pending payment') }) it('returns suspended message for suspended', () => { memberData.value = { status: 'suspended' } const { getBannerMessage } = useMemberStatus() expect(getBannerMessage()).toContain('suspended') }) it('returns cancelled message for cancelled', () => { memberData.value = { status: 'cancelled' } const { getBannerMessage } = useMemberStatus() expect(getBannerMessage()).toContain('cancelled') }) it('returns null for active', () => { memberData.value = { status: 'active' } const { getBannerMessage } = useMemberStatus() expect(getBannerMessage()).toBeNull() }) }) describe('getRSVPMessage', () => { it('returns payment message for pending_payment', () => { memberData.value = { status: 'pending_payment' } const { getRSVPMessage } = useMemberStatus() expect(getRSVPMessage()).toContain('payment') }) it('returns restriction message for suspended', () => { memberData.value = { status: 'suspended' } const { getRSVPMessage } = useMemberStatus() expect(getRSVPMessage()).toContain('reactivate') }) it('returns restriction message for cancelled', () => { memberData.value = { status: 'cancelled' } const { getRSVPMessage } = useMemberStatus() expect(getRSVPMessage()).toContain('reactivate') }) it('returns null for active', () => { memberData.value = { status: 'active' } const { getRSVPMessage } = useMemberStatus() expect(getRSVPMessage()).toBeNull() }) }) })