personal-lifecycle-tracker / frontend / src / services / __tests__ / lifecycleService.test.ts
lifecycleService.test.ts
Raw
import { beforeEach, describe, expect, it } from 'vitest';
import { MockLifecycleService } from '../mockLifecycleService';

describe('MockLifecycleService', () => {
  let service: MockLifecycleService;

  beforeEach(async () => {
    service = new MockLifecycleService();
    await service.resetToDefaultSeed();
  });

  describe('Authentication & User Management', () => {
    it('initializes with seed user Alex Taylor', async () => {
      const user = await service.getCurrentUser();
      expect(user).not.toBeNull();
      expect(user?.email).toBe('alex@example.com');
    });

    it('signs up and logs in a new user with email and password', async () => {
      const newUser = await service.signup('clara@example.com', 'secret123', 'Clara Barton');
      expect(newUser.email).toBe('clara@example.com');
      expect(newUser.name).toBe('Clara Barton');

      const current = await service.getCurrentUser();
      expect(current?.id).toBe(newUser.id);
    });

    it('switches between multiple users', async () => {
      const users = await service.listUsers();
      const sarah = users.find((u) => u.email === 'sarah@example.com');
      expect(sarah).toBeDefined();

      if (sarah) {
        await service.switchUser(sarah.id);
        const current = await service.getCurrentUser();
        expect(current?.email).toBe('sarah@example.com');
      }
    });
  });

  describe('Lifecycle Items CRUD & Common Fields', () => {
    it('creates an item with guided lifecycle type and common fields only', async () => {
      const item = await service.createItem({
        name: 'Annual Dental Checkup',
        lifecycleType: 'renewal',
        dueDate: '2026-11-01',
        status: 'active',
      });

      expect(item.id).toBeDefined();
      expect(item.name).toBe('Annual Dental Checkup');
      expect(item.lifecycleType).toBe('renewal');
      expect(item.dueDate).toBe('2026-11-01');
      expect(item.status).toBe('active');
    });

    it('supports missing due date per Decision 29', async () => {
      const item = await service.createItem({
        name: 'Smoke Detector Battery Test',
        lifecycleType: 'maintenance',
        dueDate: undefined,
        status: 'active',
      });

      expect(item.dueDate).toBeUndefined();
    });

    it('retains completed items in main list per Decision 8 & 28', async () => {
      const item = await service.createItem({
        name: 'Coffee Maker Descaling',
        lifecycleType: 'maintenance',
        dueDate: '2026-10-15',
        status: 'active',
      });

      const toggled = await service.toggleItemCompletion(item.id);
      expect(toggled.status).toBe('completed');

      const allItems = await service.getItems();
      const found = allItems.find((i) => i.id === item.id);
      expect(found).toBeDefined();
      expect(found?.status).toBe('completed');
    });

    it('permanently deletes an item per Decision 19', async () => {
      const item = await service.createItem({
        name: 'Temporary item',
        lifecycleType: 'warranty',
      });

      await service.deleteItem(item.id);
      const allItems = await service.getItems();
      expect(allItems.find((i) => i.id === item.id)).toBeUndefined();
    });
  });

  describe('Groups & Shared Access', () => {
    it('allows a user to create a group and invite members by email', async () => {
      const group = await service.createGroup('Vacation Cabin Care');
      expect(group.name).toBe('Vacation Cabin Care');
      expect(group.memberEmails).toContain('alex@example.com');

      const updatedGroup = await service.inviteMember(group.id, 'sarah@example.com');
      expect(updatedGroup.memberEmails).toContain('sarah@example.com');
    });

    it('allows all group members to manage shared items per Decision 14', async () => {
      // Alex creates group item
      const group = await service.createGroup('Co-op Garden');
      await service.inviteMember(group.id, 'sarah@example.com');

      const sharedItem = await service.createItem({
        name: 'Irrigation Winterization',
        lifecycleType: 'maintenance',
        dueDate: '2026-11-15',
        groupId: group.id,
      });

      // Switch to Sarah
      const users = await service.listUsers();
      const sarah = users.find((u) => u.email === 'sarah@example.com')!;
      await service.switchUser(sarah.id);

      // Sarah can edit or complete the shared item
      const updatedBySarah = await service.updateItem(sharedItem.id, {
        name: 'Irrigation Winterization & Valve Drain',
      });
      expect(updatedBySarah.name).toBe('Irrigation Winterization & Valve Drain');

      const completed = await service.toggleItemCompletion(sharedItem.id);
      expect(completed.status).toBe('completed');
    });

    it('deleting a group deletes the group and all its shared items per Decision 30', async () => {
      const group = await service.createGroup('Cabin Maintenance');
      const item = await service.createItem({
        name: 'Chimney Sweep',
        lifecycleType: 'maintenance',
        groupId: group.id,
      });

      await service.deleteGroup(group.id);

      const groups = await service.getGroups();
      expect(groups.find((g) => g.id === group.id)).toBeUndefined();

      const items = await service.getItems();
      expect(items.find((i) => i.id === item.id)).toBeUndefined();
    });
  });

  describe('Account Deletion cascading per Decision 23', () => {
    it('removes everything associated with the account', async () => {
      // Create user Clara
      const clara = await service.signup('clara2@example.com', 'pass', 'Clara Barton');
      const group = await service.createGroup('Clara Group');
      await service.createItem({
        name: 'Clara Personal Item',
        lifecycleType: 'renewal',
      });
      await service.createItem({
        name: 'Clara Group Item',
        lifecycleType: 'warranty',
        groupId: group.id,
      });

      // Delete Clara's account
      await service.deleteAccount(clara.id);

      const users = await service.listUsers();
      expect(users.find((u) => u.id === clara.id)).toBeUndefined();

      // Log in as Alex and verify Clara's owned group is gone
      const alex = users.find((u) => u.email === 'alex@example.com')!;
      await service.switchUser(alex.id);
      const alexGroups = await service.getGroups();
      expect(alexGroups.find((g) => g.id === group.id)).toBeUndefined();
    });
  });

  describe('Reminders and Daily Digest', () => {
    it('computes reminders summary containing overdue and upcoming items', async () => {
      const reminders = await service.getRemindersSummary();
      expect(reminders.overdue.length).toBeGreaterThan(0);
      expect(reminders.upcoming.length).toBeGreaterThan(0);
    });

    it('generates daily digest email with upcoming and overdue items per Decision 32 & 33', async () => {
      const digest = await service.generateAndSendDailyDigest('alex@example.com');
      expect(digest.recipientEmail).toBe('alex@example.com');
      expect(digest.overdueItems.length).toBeGreaterThan(0);
      expect(digest.upcomingItems.length).toBeGreaterThan(0);
      expect(digest.htmlContent).toContain('Personal Lifecycle Tracker');
      expect(digest.textContent).toContain('DAILY DIGEST');
    });

    it('allows updating daily digest delivery time per Question 34', async () => {
      const updated = await service.updateDigestConfig({ deliveryTime: '06:00' });
      expect(updated.deliveryTime).toBe('06:00');
    });
  });
});