From 81378b16b13f0ff916498e276979a99353f67604 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 7 Jun 2024 16:04:14 +0200 Subject: [PATCH 1/3] Step 7: Reorganize domain folder --- src/components/header/header.tsx | 2 +- src/components/shout-list/shout-list.tsx | 4 +++- src/components/shout/shout.tsx | 4 +++- src/domain/index.ts | 29 ------------------------ src/domain/me/index.ts | 1 + src/domain/me/me.ts | 5 ++++ src/domain/media/index.ts | 1 + src/domain/media/media.ts | 4 ++++ src/domain/shout/index.ts | 1 + src/domain/shout/shout.ts | 11 +++++++++ src/domain/user/index.ts | 1 + src/domain/user/user.ts | 8 +++++++ src/infrastructure/media/transform.ts | 2 +- src/infrastructure/shout/transform.ts | 2 +- src/infrastructure/user/transform.ts | 3 ++- src/pages/feed/feed.tsx | 4 +++- src/pages/user-profile/user-info.tsx | 2 +- src/pages/user-profile/user-profile.tsx | 4 +++- 18 files changed, 50 insertions(+), 38 deletions(-) delete mode 100644 src/domain/index.ts create mode 100644 src/domain/me/index.ts create mode 100644 src/domain/me/me.ts create mode 100644 src/domain/media/index.ts create mode 100644 src/domain/media/media.ts create mode 100644 src/domain/shout/index.ts create mode 100644 src/domain/shout/shout.ts create mode 100644 src/domain/user/index.ts create mode 100644 src/domain/user/user.ts diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 679c822..02a1f95 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -3,7 +3,7 @@ import { Link } from "react-router-dom"; import { LoginDialog } from "@/components/login-dialog"; import { Button } from "@/components/ui/button"; -import { Me } from "@/domain"; +import { Me } from "@/domain/me"; import AuthService from "@/infrastructure/auth"; import UserService from "@/infrastructure/user"; diff --git a/src/components/shout-list/shout-list.tsx b/src/components/shout-list/shout-list.tsx index f6e8419..9bb8646 100644 --- a/src/components/shout-list/shout-list.tsx +++ b/src/components/shout-list/shout-list.tsx @@ -1,5 +1,7 @@ import { Shout } from "@/components/shout"; -import { Image, Shout as IShout, User } from "@/domain"; +import { Image } from "@/domain/media"; +import { Shout as IShout } from "@/domain/shout"; +import { User } from "@/domain/user"; interface ShoutListProps { shouts: IShout[]; diff --git a/src/components/shout/shout.tsx b/src/components/shout/shout.tsx index 4c77c4d..aeb2532 100644 --- a/src/components/shout/shout.tsx +++ b/src/components/shout/shout.tsx @@ -10,7 +10,9 @@ import { CardFooter, CardHeader, } from "@/components/ui/card"; -import { Image, Shout as IShout, User } from "@/domain"; +import { Image } from "@/domain/media"; +import { Shout as IShout } from "@/domain/shout"; +import { User } from "@/domain/user"; import { ReplyDialog } from "./reply-dialog"; diff --git a/src/domain/index.ts b/src/domain/index.ts deleted file mode 100644 index 432e89c..0000000 --- a/src/domain/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface User { - id: string; - handle: string; - avatar: string; - info?: string; - blockedUserIds: string[]; - followerIds: string[]; -} - -export interface Me extends User { - numShoutsPastDay: number; -} - -export interface Shout { - id: string; - createdAt: number; - authorId: string; - text: string; - likes: number; - reshouts: number; - imageId?: string; - replies: string[]; - replyTo?: string; -} - -export interface Image { - id: string; - url: string; -} diff --git a/src/domain/me/index.ts b/src/domain/me/index.ts new file mode 100644 index 0000000..3c25512 --- /dev/null +++ b/src/domain/me/index.ts @@ -0,0 +1 @@ +export * from "./me"; diff --git a/src/domain/me/me.ts b/src/domain/me/me.ts new file mode 100644 index 0000000..74ee766 --- /dev/null +++ b/src/domain/me/me.ts @@ -0,0 +1,5 @@ +import { User } from "@/domain/user"; + +export interface Me extends User { + numShoutsPastDay: number; +} diff --git a/src/domain/media/index.ts b/src/domain/media/index.ts new file mode 100644 index 0000000..fa8208c --- /dev/null +++ b/src/domain/media/index.ts @@ -0,0 +1 @@ +export * from "./media"; diff --git a/src/domain/media/media.ts b/src/domain/media/media.ts new file mode 100644 index 0000000..4b4e453 --- /dev/null +++ b/src/domain/media/media.ts @@ -0,0 +1,4 @@ +export interface Image { + id: string; + url: string; +} diff --git a/src/domain/shout/index.ts b/src/domain/shout/index.ts new file mode 100644 index 0000000..65b9aa9 --- /dev/null +++ b/src/domain/shout/index.ts @@ -0,0 +1 @@ +export * from "./shout"; diff --git a/src/domain/shout/shout.ts b/src/domain/shout/shout.ts new file mode 100644 index 0000000..41dc34e --- /dev/null +++ b/src/domain/shout/shout.ts @@ -0,0 +1,11 @@ +export interface Shout { + id: string; + createdAt: number; + authorId: string; + text: string; + likes: number; + reshouts: number; + imageId?: string; + replies: string[]; + replyTo?: string; +} diff --git a/src/domain/user/index.ts b/src/domain/user/index.ts new file mode 100644 index 0000000..7616f9e --- /dev/null +++ b/src/domain/user/index.ts @@ -0,0 +1 @@ +export * from "./user"; diff --git a/src/domain/user/user.ts b/src/domain/user/user.ts new file mode 100644 index 0000000..cc71876 --- /dev/null +++ b/src/domain/user/user.ts @@ -0,0 +1,8 @@ +export interface User { + id: string; + handle: string; + avatar: string; + info?: string; + blockedUserIds: string[]; + followerIds: string[]; +} diff --git a/src/infrastructure/media/transform.ts b/src/infrastructure/media/transform.ts index 444f756..c967ef7 100644 --- a/src/infrastructure/media/transform.ts +++ b/src/infrastructure/media/transform.ts @@ -1,4 +1,4 @@ -import { Image } from "@/domain"; +import { Image } from "@/domain/media"; import { ImageDto } from "./dto"; diff --git a/src/infrastructure/shout/transform.ts b/src/infrastructure/shout/transform.ts index 4a305d0..99f70e3 100644 --- a/src/infrastructure/shout/transform.ts +++ b/src/infrastructure/shout/transform.ts @@ -1,4 +1,4 @@ -import { Shout } from "@/domain"; +import { Shout } from "@/domain/shout"; import { ShoutDto } from "./dto"; diff --git a/src/infrastructure/user/transform.ts b/src/infrastructure/user/transform.ts index bbb697f..411f4f6 100644 --- a/src/infrastructure/user/transform.ts +++ b/src/infrastructure/user/transform.ts @@ -1,4 +1,5 @@ -import { Me, User } from "@/domain"; +import { Me } from "@/domain/me"; +import { User } from "@/domain/user"; import { MeDto, UserDto } from "./dto"; diff --git a/src/pages/feed/feed.tsx b/src/pages/feed/feed.tsx index 1eea6d3..d9a4614 100644 --- a/src/pages/feed/feed.tsx +++ b/src/pages/feed/feed.tsx @@ -2,7 +2,9 @@ import { useEffect, useState } from "react"; import { LoadingView } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; -import { Image, Shout, User } from "@/domain"; +import { Image } from "@/domain/media"; +import { Shout } from "@/domain/shout"; +import { User } from "@/domain/user"; import FeedService from "@/infrastructure/feed"; export function Feed() { diff --git a/src/pages/user-profile/user-info.tsx b/src/pages/user-profile/user-info.tsx index 8a65c23..ea757c9 100644 --- a/src/pages/user-profile/user-info.tsx +++ b/src/pages/user-profile/user-info.tsx @@ -1,4 +1,4 @@ -import { User } from "@/domain"; +import { User } from "@/domain/user"; interface UserInfoProps { user: User; diff --git a/src/pages/user-profile/user-profile.tsx b/src/pages/user-profile/user-profile.tsx index 0ecfece..4dc6327 100644 --- a/src/pages/user-profile/user-profile.tsx +++ b/src/pages/user-profile/user-profile.tsx @@ -4,7 +4,9 @@ import { Navigate, useParams } from "react-router"; import { useGetUserProfile } from "@/application/get-user-profile"; import { LoadingSpinner } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; -import { Image, Shout, User } from "@/domain"; +import { Image } from "@/domain/media"; +import { Shout } from "@/domain/shout"; +import { User } from "@/domain/user"; import { UserInfo } from "./user-info"; From fba64dbcdd017e1cebb1c7be57369b2eecca9e1b Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 7 Jun 2024 16:27:41 +0200 Subject: [PATCH 2/3] Step 7: Extract domain logic from components --- src/components/shout-list/shout-list.tsx | 24 ++++++++-------- src/components/shout/reply-dialog.tsx | 4 ++- src/components/shout/shout.tsx | 13 ++------- src/domain/me/me.test.ts | 24 ++++++++++++++++ src/domain/me/me.ts | 4 +++ src/domain/media/media.test.ts | 32 +++++++++++++++++++++ src/domain/media/media.ts | 5 ++++ src/domain/user/user.test.ts | 36 ++++++++++++++++++++++++ src/domain/user/user.ts | 14 +++++++++ 9 files changed, 131 insertions(+), 25 deletions(-) create mode 100644 src/domain/me/me.test.ts create mode 100644 src/domain/media/media.test.ts create mode 100644 src/domain/user/user.test.ts diff --git a/src/components/shout-list/shout-list.tsx b/src/components/shout-list/shout-list.tsx index 9bb8646..c1cc7a4 100644 --- a/src/components/shout-list/shout-list.tsx +++ b/src/components/shout-list/shout-list.tsx @@ -1,7 +1,7 @@ import { Shout } from "@/components/shout"; -import { Image } from "@/domain/media"; +import { Image, getImageById } from "@/domain/media"; import { Shout as IShout } from "@/domain/shout"; -import { User } from "@/domain/user"; +import { User, getUserById } from "@/domain/user"; interface ShoutListProps { shouts: IShout[]; @@ -12,17 +12,15 @@ interface ShoutListProps { export function ShoutList({ shouts, users, images }: ShoutListProps) { return ( ); } diff --git a/src/components/shout/reply-dialog.tsx b/src/components/shout/reply-dialog.tsx index cfe3829..db7ada6 100644 --- a/src/components/shout/reply-dialog.tsx +++ b/src/components/shout/reply-dialog.tsx @@ -15,6 +15,7 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; +import { isAuthenticated as isUserAuthenticated } from "@/domain/me"; import UserService from "@/infrastructure/user"; interface ReplyFormElements extends HTMLFormControlsCollection { @@ -46,7 +47,8 @@ export function ReplyDialog({ useEffect(() => { UserService.getMe() - .then((me) => setIsAuthenticated(Boolean(me))) + .then(isUserAuthenticated) + .then(setIsAuthenticated) .catch(() => setHasError(true)) .finally(() => setIsLoading(false)); }, []); diff --git a/src/components/shout/shout.tsx b/src/components/shout/shout.tsx index aeb2532..1a760ee 100644 --- a/src/components/shout/shout.tsx +++ b/src/components/shout/shout.tsx @@ -12,7 +12,7 @@ import { } from "@/components/ui/card"; import { Image } from "@/domain/media"; import { Shout as IShout } from "@/domain/shout"; -import { User } from "@/domain/user"; +import { User, fallbackAuthor } from "@/domain/user"; import { ReplyDialog } from "./reply-dialog"; @@ -22,16 +22,7 @@ interface ShoutProps { image?: Image; } -const defaultAuthor: User = { - id: "invalid", - handle: "Deleted", - avatar: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgMGMtNi42MjcgMC0xMiA1LjM3My0xMiAxMnM1LjM3MyAxMiAxMiAxMiAxMi01LjM3MyAxMi0xMi01LjM3My0xMi0xMi0xMnptOSAxMmMwIDEuOTQtLjYyNCAzLjczNS0xLjY3MiA1LjIwN2wtMTIuNTM1LTEyLjUzNWMxLjQ3Mi0xLjA0OCAzLjI2Ny0xLjY3MiA1LjIwNy0xLjY3MiA0Ljk2MiAwIDkgNC4wMzggOSA5em0tMTggMGMwLTEuOTQuNjI0LTMuNzM1IDEuNjcyLTUuMjA3bDEyLjUzNCAxMi41MzRjLTEuNDcxIDEuMDQ5LTMuMjY2IDEuNjczLTUuMjA2IDEuNjczLTQuOTYyIDAtOS00LjAzOC05LTl6Ii8+PC9zdmc+", - blockedUserIds: [], - followerIds: [], -}; - -export function Shout({ shout, author = defaultAuthor, image }: ShoutProps) { +export function Shout({ shout, author = fallbackAuthor, image }: ShoutProps) { return ( diff --git a/src/domain/me/me.test.ts b/src/domain/me/me.test.ts new file mode 100644 index 0000000..9eb33fa --- /dev/null +++ b/src/domain/me/me.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; + +import { isAuthenticated } from "./me"; + +const mockMe = { + id: "1", + handle: "test", + avatar: "test", + numShoutsPastDay: 0, + blockedUserIds: [], + followerIds: [], +}; + +describe("Me domain", () => { + describe("isAuthenticated", () => { + it("should be true is me is defined", () => { + expect(isAuthenticated(mockMe)).toEqual(true); + }); + + it("should be false if me is not defined", () => { + expect(isAuthenticated(undefined)).toEqual(false); + }); + }); +}); diff --git a/src/domain/me/me.ts b/src/domain/me/me.ts index 74ee766..91720b9 100644 --- a/src/domain/me/me.ts +++ b/src/domain/me/me.ts @@ -3,3 +3,7 @@ import { User } from "@/domain/user"; export interface Me extends User { numShoutsPastDay: number; } + +export function isAuthenticated(me?: Me) { + return Boolean(me); +} diff --git a/src/domain/media/media.test.ts b/src/domain/media/media.test.ts new file mode 100644 index 0000000..104c7ab --- /dev/null +++ b/src/domain/media/media.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { getImageById } from "./media"; + +const mockImage = { + id: "1", + url: "test", +}; + +describe("Media domain", () => { + describe("getImageById", () => { + it("should be able to get image by id", () => { + const image = getImageById([mockImage], "1"); + expect(image).toEqual(mockImage); + }); + + it("should return undefined if image is not found", () => { + const image = getImageById([{ ...mockImage, id: "2" }], "1"); + expect(image).toEqual(undefined); + }); + + it("should return undefined if provided images are not defined", () => { + const image = getImageById(undefined, "1"); + expect(image).toEqual(undefined); + }); + + it("should return undefined if provided image id is not defined", () => { + const image = getImageById([mockImage], undefined); + expect(image).toEqual(undefined); + }); + }); +}); diff --git a/src/domain/media/media.ts b/src/domain/media/media.ts index 4b4e453..6a6c7f0 100644 --- a/src/domain/media/media.ts +++ b/src/domain/media/media.ts @@ -2,3 +2,8 @@ export interface Image { id: string; url: string; } + +export function getImageById(images?: Image[], imageId?: string) { + if (!imageId || !images) return; + return images.find((i) => i.id === imageId); +} diff --git a/src/domain/user/user.test.ts b/src/domain/user/user.test.ts new file mode 100644 index 0000000..421dde2 --- /dev/null +++ b/src/domain/user/user.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; + +import { getUserById } from "./user"; + +const mockUser = { + id: "1", + handle: "test", + avatar: "test", + numShoutsPastDay: 0, + blockedUserIds: [], + followerIds: [], +}; + +describe("User domain", () => { + describe("getUserById", () => { + it("should be able to get user by id", () => { + const user = getUserById([mockUser], "1"); + expect(user).toEqual(mockUser); + }); + + it("should return undefined if user is not found", () => { + const user = getUserById([{ ...mockUser, id: "2" }], "1"); + expect(user).toEqual(undefined); + }); + + it("should return undefined if provided users are not defined", () => { + const user = getUserById(undefined, "1"); + expect(user).toEqual(undefined); + }); + + it("should return undefined if provided user id is not defined", () => { + const user = getUserById([mockUser], undefined); + expect(user).toEqual(undefined); + }); + }); +}); diff --git a/src/domain/user/user.ts b/src/domain/user/user.ts index cc71876..8df0f8b 100644 --- a/src/domain/user/user.ts +++ b/src/domain/user/user.ts @@ -6,3 +6,17 @@ export interface User { blockedUserIds: string[]; followerIds: string[]; } + +export const fallbackAuthor: User = { + id: "invalid", + handle: "Deleted", + avatar: + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgMGMtNi42MjcgMC0xMiA1LjM3My0xMiAxMnM1LjM3MyAxMiAxMiAxMiAxMi01LjM3MyAxMi0xMi01LjM3My0xMi0xMi0xMnptOSAxMmMwIDEuOTQtLjYyNCAzLjczNS0xLjY3MiA1LjIwN2wtMTIuNTM1LTEyLjUzNWMxLjQ3Mi0xLjA0OCAzLjI2Ny0xLjY3MiA1LjIwNy0xLjY3MiA0Ljk2MiAwIDkgNC4wMzggOSA5em0tMTggMGMwLTEuOTQuNjI0LTMuNzM1IDEuNjcyLTUuMjA3bDEyLjUzNCAxMi41MzRjLTEuNDcxIDEuMDQ5LTMuMjY2IDEuNjczLTUuMjA2IDEuNjczLTQuOTYyIDAtOS00LjAzOC05LTl6Ii8+PC9zdmc+", + blockedUserIds: [], + followerIds: [], +}; + +export function getUserById(users?: User[], userId?: string) { + if (!userId || !users) return; + return users.find((u) => u.id === userId); +} From 65afcc8181d9971cebdbbea6b0d3737f8968e9de Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 10 Jun 2024 09:02:07 +0200 Subject: [PATCH 3/3] Step 7: Extract domain logic from use cases --- .../reply-to-shout/reply-to-shout.test.ts | 6 ++++- .../reply-to-shout/reply-to-shout.ts | 6 +++-- src/domain/me/me.test.ts | 18 ++++++++++++- src/domain/me/me.ts | 6 +++++ src/domain/user/user.test.ts | 26 ++++++++++++++++++- src/domain/user/user.ts | 5 ++++ 6 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/application/reply-to-shout/reply-to-shout.test.ts b/src/application/reply-to-shout/reply-to-shout.test.ts index fb94a3f..f0c7c0b 100644 --- a/src/application/reply-to-shout/reply-to-shout.test.ts +++ b/src/application/reply-to-shout/reply-to-shout.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vitest } from "vitest"; +import { MAX_NUM_SHOUTS_PER_DAY } from "@/domain/me"; import { createMockFile } from "@/test/create-mock-file"; import { ErrorMessages, replyToShout } from "./reply-to-shout"; @@ -50,7 +51,10 @@ describe("replyToShout", () => { }); it("should return an error if the user has made too many shouts", async () => { - mockGetMe.mockResolvedValueOnce({ ...mockMe, numShoutsPastDay: 5 }); + mockGetMe.mockResolvedValueOnce({ + ...mockMe, + numShoutsPastDay: MAX_NUM_SHOUTS_PER_DAY, + }); const result = await replyToShout( { recipientHandle, shoutId, message, files }, diff --git a/src/application/reply-to-shout/reply-to-shout.ts b/src/application/reply-to-shout/reply-to-shout.ts index 91e655f..e55ac2e 100644 --- a/src/application/reply-to-shout/reply-to-shout.ts +++ b/src/application/reply-to-shout/reply-to-shout.ts @@ -1,5 +1,7 @@ import { useCallback } from "react"; +import { hasExceededShoutLimit } from "@/domain/me"; +import { hasBlockedUser } from "@/domain/user"; import MediaService from "@/infrastructure/media"; import ShoutService from "@/infrastructure/shout"; import UserService from "@/infrastructure/user"; @@ -33,7 +35,7 @@ export async function replyToShout( { getMe, getUser, saveImage, createReply, createShout }: typeof dependencies ) { const me = await getMe(); - if (me.numShoutsPastDay >= 5) { + if (hasExceededShoutLimit(me)) { return { error: ErrorMessages.TooManyShouts }; } @@ -41,7 +43,7 @@ export async function replyToShout( if (!recipient) { return { error: ErrorMessages.RecipientNotFound }; } - if (recipient.blockedUserIds.includes(me.id)) { + if (hasBlockedUser(recipient, me.id)) { return { error: ErrorMessages.AuthorBlockedByRecipient }; } diff --git a/src/domain/me/me.test.ts b/src/domain/me/me.test.ts index 9eb33fa..6da381d 100644 --- a/src/domain/me/me.test.ts +++ b/src/domain/me/me.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { isAuthenticated } from "./me"; +import { isAuthenticated, hasExceededShoutLimit } from "./me"; const mockMe = { id: "1", @@ -21,4 +21,20 @@ describe("Me domain", () => { expect(isAuthenticated(undefined)).toEqual(false); }); }); + + describe("hasExceededShoutLimit", () => { + it("should be false if numShoutsPastDay is less than MAX_NUM_SHOUTS_PER_DAY", () => { + expect(hasExceededShoutLimit(mockMe)).toEqual(false); + }); + + it("should be true if numShoutsPastDay is equal to MAX_NUM_SHOUTS_PER_DAY", () => { + const me = { ...mockMe, numShoutsPastDay: 5 }; + expect(hasExceededShoutLimit(me)).toEqual(true); + }); + + it("should be true if numShoutsPastDay is greater than MAX_NUM_SHOUTS_PER_DAY", () => { + const me = { ...mockMe, numShoutsPastDay: 6 }; + expect(hasExceededShoutLimit(me)).toEqual(true); + }); + }); }); diff --git a/src/domain/me/me.ts b/src/domain/me/me.ts index 91720b9..0431edf 100644 --- a/src/domain/me/me.ts +++ b/src/domain/me/me.ts @@ -1,5 +1,7 @@ import { User } from "@/domain/user"; +export const MAX_NUM_SHOUTS_PER_DAY = 5; + export interface Me extends User { numShoutsPastDay: number; } @@ -7,3 +9,7 @@ export interface Me extends User { export function isAuthenticated(me?: Me) { return Boolean(me); } + +export function hasExceededShoutLimit(me: Me) { + return me.numShoutsPastDay >= MAX_NUM_SHOUTS_PER_DAY; +} diff --git a/src/domain/user/user.test.ts b/src/domain/user/user.test.ts index 421dde2..82f3827 100644 --- a/src/domain/user/user.test.ts +++ b/src/domain/user/user.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { getUserById } from "./user"; +import { getUserById, hasBlockedUser } from "./user"; const mockUser = { id: "1", @@ -33,4 +33,28 @@ describe("User domain", () => { expect(user).toEqual(undefined); }); }); + + describe("hasBlockedUser", () => { + it("should be false if user has not blocked the user", () => { + const user = { ...mockUser, blockedUserIds: ["2"] }; + const hasBlocked = hasBlockedUser(user, "3"); + expect(hasBlocked).toEqual(false); + }); + + it("should be true if user has blocked the user", () => { + const user = { ...mockUser, blockedUserIds: ["2"] }; + const hasBlocked = hasBlockedUser(user, "2"); + expect(hasBlocked).toEqual(true); + }); + + it("should be false if user is not defined", () => { + const hasBlocked = hasBlockedUser(undefined, "2"); + expect(hasBlocked).toEqual(false); + }); + + it("should be false if user id is not defined", () => { + const hasBlocked = hasBlockedUser(mockUser, undefined); + expect(hasBlocked).toEqual(false); + }); + }); }); diff --git a/src/domain/user/user.ts b/src/domain/user/user.ts index 8df0f8b..6685d21 100644 --- a/src/domain/user/user.ts +++ b/src/domain/user/user.ts @@ -20,3 +20,8 @@ export function getUserById(users?: User[], userId?: string) { if (!userId || !users) return; return users.find((u) => u.id === userId); } + +export function hasBlockedUser(user?: User, userId?: string) { + if (!user || !userId) return false; + return user.blockedUserIds.includes(userId); +}