Restore photobooth uploader files after sync
This commit is contained in:
33
resources/js/admin/mobile/__tests__/analytics.test.ts
Normal file
33
resources/js/admin/mobile/__tests__/analytics.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveMaxCount, resolveTimelineHours } from '../lib/analytics';
|
||||
|
||||
describe('resolveMaxCount', () => {
|
||||
it('defaults to 1 for empty input', () => {
|
||||
expect(resolveMaxCount([])).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the highest count', () => {
|
||||
expect(resolveMaxCount([2, 5, 3])).toBe(5);
|
||||
});
|
||||
|
||||
it('never returns less than 1', () => {
|
||||
expect(resolveMaxCount([0])).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveTimelineHours', () => {
|
||||
it('uses fallback when data is missing', () => {
|
||||
expect(resolveTimelineHours([], 12)).toBe(12);
|
||||
});
|
||||
|
||||
it('calculates rounded hours from timestamps', () => {
|
||||
const start = new Date('2024-01-01T10:00:00Z').toISOString();
|
||||
const end = new Date('2024-01-01T21:00:00Z').toISOString();
|
||||
expect(resolveTimelineHours([start, end], 12)).toBe(11);
|
||||
});
|
||||
|
||||
it('never returns less than 1', () => {
|
||||
const start = new Date('2024-01-01T10:00:00Z').toISOString();
|
||||
expect(resolveTimelineHours([start, start], 12)).toBe(1);
|
||||
});
|
||||
});
|
||||
42
resources/js/admin/mobile/__tests__/billingCheckout.test.ts
Normal file
42
resources/js/admin/mobile/__tests__/billingCheckout.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
CHECKOUT_STORAGE_KEY,
|
||||
PENDING_CHECKOUT_TTL_MS,
|
||||
isCheckoutExpired,
|
||||
loadPendingCheckout,
|
||||
shouldClearPendingCheckout,
|
||||
storePendingCheckout,
|
||||
} from '../lib/billingCheckout';
|
||||
|
||||
describe('billingCheckout helpers', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear();
|
||||
});
|
||||
it('detects expired pending checkout', () => {
|
||||
const pending = { packageId: 12, startedAt: 0 };
|
||||
expect(isCheckoutExpired(pending, PENDING_CHECKOUT_TTL_MS + 1)).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps pending checkout when active package differs', () => {
|
||||
const pending = { packageId: 12, startedAt: Date.now() };
|
||||
expect(shouldClearPendingCheckout(pending, 18, pending.startedAt)).toBe(false);
|
||||
});
|
||||
|
||||
it('clears pending checkout when active package matches', () => {
|
||||
const now = Date.now();
|
||||
const pending = { packageId: 12, startedAt: now };
|
||||
expect(shouldClearPendingCheckout(pending, 12, now)).toBe(true);
|
||||
});
|
||||
|
||||
it('stores and loads pending checkout from session storage', () => {
|
||||
const pending = { packageId: 7, checkoutSessionId: 'sess_123', startedAt: Date.now() };
|
||||
storePendingCheckout(pending);
|
||||
expect(loadPendingCheckout(pending.startedAt)).toEqual(pending);
|
||||
});
|
||||
|
||||
it('clears pending checkout storage', () => {
|
||||
storePendingCheckout({ packageId: 7, checkoutSessionId: 'sess_123', startedAt: Date.now() });
|
||||
storePendingCheckout(null);
|
||||
expect(sessionStorage.getItem(CHECKOUT_STORAGE_KEY)).toBeNull();
|
||||
});
|
||||
});
|
||||
83
resources/js/admin/mobile/__tests__/packageShop.test.ts
Normal file
83
resources/js/admin/mobile/__tests__/packageShop.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
buildPackageComparisonRows,
|
||||
classifyPackageChange,
|
||||
getEnabledPackageFeatures,
|
||||
selectRecommendedPackageId,
|
||||
} from '../lib/packageShop';
|
||||
|
||||
describe('classifyPackageChange', () => {
|
||||
const active = {
|
||||
id: 1,
|
||||
price: 200,
|
||||
max_photos: 100,
|
||||
max_guests: 50,
|
||||
gallery_days: 30,
|
||||
features: { advanced_analytics: false },
|
||||
} as any;
|
||||
|
||||
it('returns neutral when no active package', () => {
|
||||
expect(classifyPackageChange(active, null)).toEqual({ isUpgrade: false, isDowngrade: false });
|
||||
});
|
||||
|
||||
it('marks upgrade when candidate adds features', () => {
|
||||
const candidate = { ...active, id: 2, price: 150, features: { advanced_analytics: true } } as any;
|
||||
expect(classifyPackageChange(candidate, active)).toEqual({ isUpgrade: true, isDowngrade: false });
|
||||
});
|
||||
|
||||
it('marks downgrade when candidate removes features or limits', () => {
|
||||
const candidate = { ...active, id: 3, max_photos: 50, features: { advanced_analytics: false } } as any;
|
||||
expect(classifyPackageChange(candidate, active)).toEqual({ isUpgrade: false, isDowngrade: true });
|
||||
});
|
||||
|
||||
it('treats mixed changes as downgrade', () => {
|
||||
const candidate = { ...active, id: 4, max_photos: 200, gallery_days: 10, features: { advanced_analytics: false } } as any;
|
||||
expect(classifyPackageChange(candidate, active)).toEqual({ isUpgrade: false, isDowngrade: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectRecommendedPackageId', () => {
|
||||
const packages = [
|
||||
{ id: 1, price: 100, features: { advanced_analytics: false } },
|
||||
{ id: 2, price: 150, features: { advanced_analytics: true } },
|
||||
{ id: 3, price: 200, features: { advanced_analytics: true } },
|
||||
] as any;
|
||||
|
||||
it('returns null when no feature is requested', () => {
|
||||
expect(selectRecommendedPackageId(packages, null, 100)).toBeNull();
|
||||
});
|
||||
|
||||
it('selects the cheapest upgrade with the feature', () => {
|
||||
const active = { id: 10, price: 120, max_photos: 100, max_guests: 50, gallery_days: 30, features: {} } as any;
|
||||
expect(selectRecommendedPackageId(packages, 'advanced_analytics', active)).toBe(2);
|
||||
});
|
||||
|
||||
it('falls back to cheapest feature package if no upgrades exist', () => {
|
||||
const active = { id: 10, price: 250, max_photos: 999, max_guests: 999, gallery_days: 365, features: { advanced_analytics: true } } as any;
|
||||
expect(selectRecommendedPackageId(packages, 'advanced_analytics', active)).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPackageComparisonRows', () => {
|
||||
it('includes limit rows and enabled feature rows', () => {
|
||||
const rows = buildPackageComparisonRows([
|
||||
{ features: { advanced_analytics: true, custom_branding: false } },
|
||||
{ features: { custom_branding: true, watermark_removal: true } },
|
||||
] as any);
|
||||
|
||||
expect(rows.map((row) => row.id)).toEqual([
|
||||
'limit.max_photos',
|
||||
'limit.max_guests',
|
||||
'limit.gallery_days',
|
||||
'feature.advanced_analytics',
|
||||
'feature.custom_branding',
|
||||
'feature.watermark_removal',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEnabledPackageFeatures', () => {
|
||||
it('accepts array payloads', () => {
|
||||
expect(getEnabledPackageFeatures({ features: ['custom_branding', ''] } as any)).toEqual(['custom_branding']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user