diff --git a/pkg/timeline/model/list.test.ts b/pkg/timeline/model/list.test.ts new file mode 100644 index 00000000..c6a4d49a --- /dev/null +++ b/pkg/timeline/model/list.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest'; + +import type { AccountID } from '../../accounts/model/account.js'; +import { type CreateListArgs, List, type ListID } from './list.js'; + +describe('List', () => { + const args: CreateListArgs = { + id: '1' as ListID, + title: 'My List', + publicity: 'PUBLIC', + ownerId: '2' as AccountID, + memberIds: ['3' as AccountID], + createdAt: new Date(), + } as const; + + it('should create a new list', () => { + const list = List.new(args); + + expect(list.getId()).toBe(args.id); + expect(list.getTitle()).toBe(args.title); + expect(list.isPublic()).toBe(true); + expect(list.getOwnerId()).toBe(args.ownerId); + expect(list.getMemberIds()).toEqual(args.memberIds); + expect(list.getCreatedAt()).toBe(args.createdAt); + }); + + it('should add a member to the list', () => { + const list = List.new(args); + const memberId = '4' as AccountID; + + list.addMember(memberId); + + expect(list.getMemberIds()).toStrictEqual([ + '3' as AccountID, + '4' as AccountID, + ]); + }); + + it('should not add a member if already in the list', () => { + const args: CreateListArgs = { + id: '1' as ListID, + title: 'My List', + publicity: 'PUBLIC', + ownerId: '2' as AccountID, + memberIds: ['3' as AccountID], + createdAt: new Date(), + } as const; + const list3 = List.new(args); + const memberId = '3' as AccountID; + + list3.addMember(memberId); + + expect(list3.getMemberIds()).toStrictEqual(['3' as AccountID]); + }); + + it('should remove member from list', () => { + const args: CreateListArgs = { + id: '1' as ListID, + title: 'My List', + publicity: 'PUBLIC', + ownerId: '2' as AccountID, + memberIds: ['3' as AccountID], + createdAt: new Date(), + } as const; + const list3 = List.new(args); + const memberId = '3' as AccountID; + + list3.removeMember(memberId); + + expect(list3.getMemberIds()).toStrictEqual([]); + }); + + it('should no duplicate member when initialize', () => { + const args: CreateListArgs = { + id: '1' as ListID, + title: 'My List', + publicity: 'PUBLIC', + ownerId: '2' as AccountID, + memberIds: ['3' as AccountID, '3' as AccountID], + createdAt: new Date(), + } as const; + const list3 = List.new(args); + expect(list3.getMemberIds()).toStrictEqual(['3' as AccountID]); + }); +}); diff --git a/pkg/timeline/model/list.ts b/pkg/timeline/model/list.ts new file mode 100644 index 00000000..98e7555e --- /dev/null +++ b/pkg/timeline/model/list.ts @@ -0,0 +1,67 @@ +import type { AccountID } from '../../accounts/model/account.js'; +import type { ID } from '../../id/type.js'; + +export type ListID = ID; +export type CreateListArgs = Readonly<{ + id: ListID; + title: string; + publicity: 'PUBLIC' | 'PRIVATE'; + ownerId: AccountID; + memberIds: readonly AccountID[]; + createdAt: Date; +}>; + +export class List { + private readonly id: ListID; + private readonly title: string; + private readonly publicity: 'PUBLIC' | 'PRIVATE'; + private readonly ownerId: AccountID; + private readonly memberIds: Set; + private readonly createdAt: Date; + + private constructor(args: CreateListArgs) { + this.id = args.id; + this.title = args.title; + this.publicity = args.publicity; + this.ownerId = args.ownerId; + this.memberIds = new Set(args.memberIds); + this.createdAt = args.createdAt; + } + + static new(args: CreateListArgs) { + return new List(args); + } + + getId(): ListID { + return this.id; + } + + getTitle(): string { + return this.title; + } + + isPublic(): boolean { + return this.publicity === 'PUBLIC'; + } + + getOwnerId(): AccountID { + return this.ownerId; + } + + getMemberIds(): readonly AccountID[] { + return [...this.memberIds]; + } + + getCreatedAt(): Date { + return this.createdAt; + } + + addMember(memberId: AccountID): void { + // ToDo: member limit + this.memberIds.add(memberId); + } + + removeMember(memberId: AccountID): void { + this.memberIds.delete(memberId); + } +}