Member/Ecology revamp.
This commit is contained in:
parent
fc7ec52574
commit
59d6e97787
31 changed files with 1763 additions and 1010 deletions
|
|
@ -1,218 +1,235 @@
|
|||
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'
|
||||
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)
|
||||
vi.stubGlobal("computed", computed);
|
||||
|
||||
// Shared reactive ref for controlling member status in tests
|
||||
const memberData = ref({ status: 'active' })
|
||||
vi.stubGlobal('useAuth', () => ({ memberData }))
|
||||
const memberData = ref({ status: "active" });
|
||||
vi.stubGlobal("useAuth", () => ({ memberData }));
|
||||
|
||||
describe('MEMBER_STATUSES', () => {
|
||||
it('has all four status keys', () => {
|
||||
describe("MEMBER_STATUSES", () => {
|
||||
it("has all four status keys", () => {
|
||||
expect(Object.keys(MEMBER_STATUSES)).toEqual([
|
||||
'PENDING_PAYMENT', 'ACTIVE', 'SUSPENDED', 'CANCELLED',
|
||||
])
|
||||
})
|
||||
"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')
|
||||
})
|
||||
})
|
||||
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']
|
||||
describe("MEMBER_STATUS_CONFIG", () => {
|
||||
const requiredFields = [
|
||||
"label",
|
||||
"color",
|
||||
"canRSVP",
|
||||
"canAccessMembers",
|
||||
"canPeerSupport",
|
||||
];
|
||||
|
||||
it('has config for every status value', () => {
|
||||
it("has config for every status value", () => {
|
||||
for (const status of Object.values(MEMBER_STATUSES)) {
|
||||
expect(MEMBER_STATUS_CONFIG).toHaveProperty(status)
|
||||
expect(MEMBER_STATUS_CONFIG).toHaveProperty(status);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('each config has required fields', () => {
|
||||
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)
|
||||
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("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("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("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)
|
||||
})
|
||||
})
|
||||
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', () => {
|
||||
describe("useMemberStatus composable", () => {
|
||||
beforeEach(() => {
|
||||
memberData.value = { status: 'active' }
|
||||
})
|
||||
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')
|
||||
})
|
||||
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("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 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)
|
||||
})
|
||||
})
|
||||
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)
|
||||
})
|
||||
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("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 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)
|
||||
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')
|
||||
})
|
||||
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/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 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/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 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()
|
||||
})
|
||||
})
|
||||
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')
|
||||
})
|
||||
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 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 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()
|
||||
})
|
||||
})
|
||||
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')
|
||||
})
|
||||
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 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 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
it("returns null for active", () => {
|
||||
memberData.value = { status: "active" };
|
||||
const { getRSVPMessage } = useMemberStatus();
|
||||
expect(getRSVPMessage()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue