feat: replace desktop membership with permanent point wallet

This commit is contained in:
2026-09-22 11:43:04 +08:00
parent 2f82b9f79c
commit a1cce428af
24 changed files with 1343 additions and 1707 deletions

View File

@@ -1,38 +1,11 @@
import { describe, expect, it } from 'vitest';
import type { WorksTokenPointBalance } from '@/lib/works-square';
import { permanentBalance as pointBalance } from '../fixtures/permanent-points';
import {
formatWorksTokenPointValue,
isWorksTokenPointBalanceExhausted,
} from '@/lib/works-square-token-points';
function pointBalance(
overrides: Partial<WorksTokenPointBalance> = {},
): WorksTokenPointBalance {
return {
plan_code: 'mastery',
plan_name: '精通',
cycle_start: '2026-09-01T00:00:00Z',
cycle_end: '2026-09-08T00:00:00Z',
next_refresh_at: '2026-09-08T00:00:00Z',
weekly_allowance: '500.00',
weekly_used: '100.00',
weekly_reserved: '25.00',
weekly_remaining: '375.00',
permanent_total: '50.00',
permanent_used: '0.00',
permanent_reserved: '0.00',
permanent_remaining: '50.00',
total_remaining: '425.00',
entitlement_source: 'self',
family_shared: false,
can_manage_membership: true,
upgrade_action: 'self_service',
shared_available: null,
...overrides,
};
}
describe('Works Square V2 token points', () => {
describe('permanent token points', () => {
it('formats exact server point strings without losing integer precision', () => {
expect(formatWorksTokenPointValue('1000.00')).toBe('1,000');
expect(formatWorksTokenPointValue('123456789012345678.50')).toBe('123,456,789,012,345,678.5');
@@ -46,18 +19,10 @@ describe('Works Square V2 token points', () => {
expect(isWorksTokenPointBalanceExhausted(pointBalance({ total_remaining: null }))).toBe(false);
});
it('uses the server-provided coarse availability for every non-manager', () => {
const nonManager = pointBalance({
plan_code: null,
plan_name: null,
can_manage_membership: false,
family_shared: false,
upgrade_action: 'contact_family_owner',
shared_available: false,
total_remaining: null,
});
expect(isWorksTokenPointBalanceExhausted(nonManager)).toBe(true);
expect(isWorksTokenPointBalanceExhausted({ ...nonManager, shared_available: true })).toBe(false);
expect(isWorksTokenPointBalanceExhausted({ ...nonManager, family_shared: true })).toBe(true);
it('uses exact personal balance for youth and coarse availability for shared wallets', () => {
expect(isWorksTokenPointBalanceExhausted(pointBalance({ total_remaining: '0.00', can_recharge: false }))).toBe(true);
const shared = pointBalance({ family_shared: true, entitlement_source: 'family_owner', total_remaining: null, shared_available: false, can_recharge: false });
expect(isWorksTokenPointBalanceExhausted(shared)).toBe(true);
expect(isWorksTokenPointBalanceExhausted({ ...shared, shared_available: true })).toBe(false);
});
});