diff --git a/__mocks__/bounty.ts b/__mocks__/bounty.ts new file mode 100644 index 000000000..5a283f580 --- /dev/null +++ b/__mocks__/bounty.ts @@ -0,0 +1,10 @@ +export const mockTrailer = { + summary: "trailer summary", + description: "trailer description", + video: "trailer video", + duration: 4, + info: { + items: ["item 1", "item 2"], + title: "info title", + }, + }; \ No newline at end of file diff --git a/__mocks__/challenge.ts b/__mocks__/challenge.ts new file mode 100644 index 000000000..7e97d0899 --- /dev/null +++ b/__mocks__/challenge.ts @@ -0,0 +1,144 @@ +import { TEAM_FORMATION } from "@/constants/challengeInfo"; +import { metadata, mockCommunity } from "./community"; +import { mockFormat, mockCertificateData, mockCourse, mockLearningModule, mockRatingCriteria } from "./course"; +import { reward } from "./reward"; +import { Submission, User } from "@/types/bounty"; +import { KYCSTATUS } from "@/store/feature/kyc.slice"; +import { Team, TeamMember } from "@/types/challenge"; + +export const fixtureUser = (): User => ({ + id: "user_id", + ref: "ref", + created_at: new Date("2022-05-01T12:00:00Z"), + firstName: "John Doe", + displayName: "John Doe", + uid: "uuid-uuido-232-dex0232-2331", + joined: "14 days ago", + disabled: false, + reputation: 0, + username: "", + lastName: "", + emailVerified: false, + email: "", + kycStatus: KYCSTATUS.PENDING, + }); + +export const challenge = () => ({ + id: "challenge", + ref: "challenge ref", + created_at: new Date("2022-05-01T12:00:00Z"), + updated_at: new Date("2022-05-01T12:00:00Z"), + hint: "Hint", + name: "challenge name", + format: mockFormat, + description: "challenge description", + course: mockCourse, + type: "course", + isTeamChallenge: false, + objectives: ["objectives 1", "Objectives 2", "Objectives 3", "Objectives 4"], + threshold: 8, + community: mockCommunity, + reviewTime: 9, + metadata: metadata, + level: 58, + courses: [mockCourse], + learningModules: [mockLearningModule], + expiresAt: "2025", + reward: reward, + certificateIcon: "certificate", + certificateData: mockCertificateData, + ratingCriteria: [mockRatingCriteria], + timestamp: 6, + maxPoints: 299, + minPoints: 9, + rewards: [reward], + feedbacks: {}, + feedbackInfo: [{}], + bestSubmissions: [{}], + teamLimit: 5, + isHackathon: false, + additionalInfo: { + [TEAM_FORMATION]: { + type: "teamFormation", + text: "Form your team details here", + }, + }, +}); + +export const fixtureSubmission = (): Submission => ({ + length: 0, + id: "submission_id", + ref: "reference", + created_at: new Date("2022-05-01T12:00:00Z"), + updated_at: new Date("2022-05-01T12:00:00Z"), + link: "/submissions/reference", + community: mockCommunity, + user_id: "user_id", + challenge: challenge(), + text: "Submission", + reviewDeadline: new Date(), + metadata: { + evaluation: "", + applicableReward: { + ref: "", + amount: 0, + updated_at: "", + challenge: "", + created_at: "", + id: "", + type: "", + community: "", + timestamp: 0, + token: "", + }, + reviewed: false, + feedbacks: 0, + language: "", + }, + timestamp: 0, + user: fixtureUser(), + reviewable: false, + status: "", + reward: reward, + map: function () { + throw new Error("Function not implemented."); + }, +}); +export const mockInvite = { + created_at: "tuesday", + id: "invite", + ref: "invitation ref", + status: "invitation status", + team_ref: "team reference", + timestamp: 3, + updated_at: "wednesday", + user: fixtureUser(), + user_id: "user id", + } + + export const mockTeamMember: TeamMember = { + created_at: "created_at", + id: "id", + joined_on: "joined_on", + ref: "ref", + team_ref: "team reference", + timestamp: 3, + updated_at: "wednesday", + user: fixtureUser(), + } + + export const mockTeam: Team = { + challenge: challenge(), + challenge_ref: "challenge ref", + created_at: "created at", + id: "id", + locked: true, + name: "Master", + organizer: fixtureUser(), + organizer_id: "organizer id", + invites: [mockInvite], + members: [mockTeamMember], + ref: "", + timestamp: "", + updated_at: "" + } \ No newline at end of file diff --git a/__mocks__/community.ts b/__mocks__/community.ts new file mode 100644 index 000000000..48a4db2fa --- /dev/null +++ b/__mocks__/community.ts @@ -0,0 +1,57 @@ +import { Community } from "@/types/community"; +import { colors } from "./colors"; + +export const metadata = { + invite_id: "abc123", + submissions: 5, + bestSubmissions: ["submission1", "submission2"], + feedbacks: 10, + name: "Project XYZ", + issuedOn: "2024-01-29T12:00:00Z", + image: "image_url", + title: "Title of the project", + description: "Description of the project", + narrative: "Narrative of the project", + recipientName: "John Doe", + issuerName: "Jane Smith", + comment: "This is a comment", + linkToWork: "link_to_work", + submission: "submission_details", +}; + +const reward = { + id: "123456789", + ref: "ref123", + created_at: new Date("2024-01-29T08:00:00Z"), + updated_at: new Date("2024-01-29T08:30:00Z"), + challenge: "Challenge Name", + type: "SUBMISSION", + community: "Community Name", + token: "Token ID", + stable: true, + amount: 100, + timestamp: 1643424000, +}; + +export const mockCommunity: Community = { + id: "ew-43", + ref: "community/ref", + created_at: new Date("2022-05-01T12:00:00Z"), + updated_at: new Date("2022-05-01T12:00:00Z"), + summary: "this is the summary", + icon: "public/img/communities/aeternity.svg", + name: "aeternity", + image: "public/img/communities/aeternity.svg", + colors: colors, + slug: "ae", + active: true, + description: "this is a aeternity community", + metadata, + timestamp: 182044800000, + rewards: [reward], + reward, + courses: 3, + duration: 4, + can_mint_certificates: true, + challenges: 3, +}; \ No newline at end of file diff --git a/__mocks__/course.ts b/__mocks__/course.ts new file mode 100644 index 000000000..cac961e82 --- /dev/null +++ b/__mocks__/course.ts @@ -0,0 +1,134 @@ +import { Course, Format, LearningModule, Material } from "@/types/course"; +import { mockTrailer } from "./bounty"; + + +export const Introduction = { + text: "course intro", +}; + +export const mockCertificateData = { + narrative: "course certificate", + icon: "certificate icon", +}; + +export const Rubric = { + id: "id", + ref: "rubric references", + created_at: "Wednesday", + updated_at: "Thursday", + challenge: "Challenge", + text: "Challenge text", + type: "challenge type", + order: 89, + points: 90, + timestamp: 73, + typeSlug: "slug", +}; + +export const mockRatingCriteria = { + name: "rating criteria", + order: 4, + rubric: [Rubric], + maxPoints: 78, +}; + +enum MaterialType { + ADDITIONAL = "ADDITIONAL", + MARKDOWN = "MARKDOWN", + TEXT = "TEXT", + ARTICLE = "ARTICLE", + "EMBEDDED-VIDEO" = "EMBEDDED-VIDEO", +} +export const mockMaterial: Material = { + duration: 3, + subtitle: "material subtitle", + link: "material link", + description: "material description", + title: "material title", + type: MaterialType.ADDITIONAL, + list: [{ link: "Link 1" }], +}; + +export const InteractiveModule = { + ref: "interactive module ref", + title: "interactive module title", + text: "interative text", + closing: { + text: "closing", + title: "title", + }, + items: [ + { + text: "text", + title: "title", + options: { + text: "text", + isCorrect: true, + }, + question: { + title: "question title", + answers: ["answer 1", "answer 2"], + correct: 2, + }, + }, + ], +}; + +export const mockLearningModule: LearningModule = { + id: "learningModule id", + ref: "learning module reference", + created_at: new Date("2022-05-01T12:00:00Z"), + updated_at: new Date("2022-05-01T12:00:00Z"), + duration: 4, + description: "learning module description", + objectives: ["objective 1, objective 2"], + title: "learning module title", + community: "learning module community", + materials: [mockMaterial], + timestamp: 3, + order: 4, + course: "Learning module course", + interactiveModules: [InteractiveModule], +}; + +export const mockCourse: Course = { + id: "course", + ref: "course ref", + created_at: new Date("2022-05-01T12:00:00Z"), + updated_at: new Date("2022-05-01T12:00:00Z"), + duration: 3, + summary: "Course description", + level: 3, + name: "course name", + description: "Course description", + objectives: ["course description", "course objectives"], + locale: "English", + community: "community", + slug: "course description slug", + introduction: Introduction, + active: true, + certificateIcon: "certificate", + certificateData: mockCertificateData, + timestamp: 0, + learningModules: [mockLearningModule], + trailer: mockTrailer, + disclaimer: "Course", + items: ["item 1", "item 2"], + faq: [ + { + description: "faq description", + title: "faq title", + }, + ], + prerequisite: { + items: ["item 1", "item 2"], + hint: "prerequisite hint", + }, + translations: [] +}; + +export const mockFormat: Format = { + githubLink: true, + text: true, + disclaimer: true, +}; \ No newline at end of file diff --git a/__mocks__/feedback.ts b/__mocks__/feedback.ts new file mode 100644 index 000000000..1620c0478 --- /dev/null +++ b/__mocks__/feedback.ts @@ -0,0 +1,28 @@ +import { Feedback } from "@/types/feedback"; +import { fixtureSubmission, fixtureUser } from "./challenge"; +import { reward } from "./reward"; + +export const fixtureFeedback: Feedback = { + submission: fixtureSubmission(), + id: "feedback id", + ref: "feedback ref", + created_at: new Date("2022-05-01T12:00:00Z"), + updated_at: "wednesday", + criteria: ["feedback 1", "feedback 2", "feedback 3"], + positive: true, + name: "mima", + challenge: "TypeScript", + timestamp: 4, + description: "feedback description", + user: fixtureUser(), + ranking: 3, + text: "feedback text", + metadata: { + evaluation: { + reward: reward, + points: 8, + }, + language: "en", + }, + link: "/feedback" +}; \ No newline at end of file diff --git a/__tests__/components/sections/profile/achievements/LinkField.test.tsx b/__tests__/components/sections/profile/achievements/LinkField.test.tsx new file mode 100644 index 000000000..28805ad2c --- /dev/null +++ b/__tests__/components/sections/profile/achievements/LinkField.test.tsx @@ -0,0 +1,33 @@ +import AchievementLinkField from "@/components/sections/profile/achievements/LinkField"; +import "@testing-library/jest-dom"; +import { fireEvent, render, screen } from "@testing-library/react"; + +describe("AchievementLinkField", () => { + const testLink = "https://dacade.org/some/path"; + it("should render the achievement link", () => { + render(); + expect(screen.getByTestId("achievementLinkId")).toBeInTheDocument(); + const linkElement = screen.getByText(testLink); + expect(linkElement).toBeInTheDocument(); + expect(linkElement.textContent).toBe("https://dacade.org/some/path"); + }); + it("should copy the link to the clipboard when clicked", () => { + render(); + if (navigator.clipboard) { + const mockCopy = async () => await navigator.clipboard.writeText(testLink); + const linkElement = screen.getByText(testLink); + fireEvent.click(linkElement); + expect(mockCopy()).toHaveBeenCalled(); + } + }); + it("should have the link button", () => { + render(); + const mockPathname = testLink.split("https://dacade.org/")[1]; + expect(screen.getByRole("link")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + expect(screen.getByRole("link").hasAttribute("href")).toBeTruthy(); + if (typeof window !== "undefined") { + expect(screen.getByRole("link").getAttribute("href")).toBe(`${window.location.origin}/${mockPathname}`); + } + }); +}); diff --git a/__tests__/components/sections/profile/achievements/ListItem.test.tsx b/__tests__/components/sections/profile/achievements/ListItem.test.tsx new file mode 100644 index 000000000..1ba0be37a --- /dev/null +++ b/__tests__/components/sections/profile/achievements/ListItem.test.tsx @@ -0,0 +1,16 @@ +import AchievementViewItem from "@/components/sections/profile/achievements/ListItem"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; + +describe("AchievementViewItem", () => { + it("should render the achievement view item", () => { + render( + +

Testing achievement view item

+
+ ); + expect(screen.getByTestId("achievementViewItemId")).toBeInTheDocument(); + expect(screen.getByText("viewItem")).toBeInTheDocument(); + expect(screen.getByTestId("achievementViewItemId").textContent).toContain("Testing achievement view item"); + }); +}); diff --git a/__tests__/components/sections/profile/communities/List.test.tsx b/__tests__/components/sections/profile/communities/List.test.tsx new file mode 100644 index 000000000..f350d6d83 --- /dev/null +++ b/__tests__/components/sections/profile/communities/List.test.tsx @@ -0,0 +1,24 @@ +import SubmissionList from "@/components/sections/profile/communities/List"; +import "@testing-library/jest-dom"; +import { screen } from "@testing-library/react"; +import { renderWithRedux } from "../../../../../__mocks__/renderWithRedux"; +import { mockCommunity } from "../../../../../__mocks__/community"; +import { mockCourse } from "../../../../../__mocks__/course"; +import { fixtureSubmission } from "../../../../../__mocks__/challenge"; +import { fixtureFeedback } from "../../../../../__mocks__/feedback"; + +jest.mock("next/router", () => ({ + useRouter: jest.fn(), +})); + +describe("SubmissionList", () => { + const submissions = fixtureSubmission(); + it("should render the achievement view item", () => { + renderWithRedux(, { + community: { current: mockCommunity, list: [mockCommunity], courses: [mockCourse], error: "error message", status: "succeeded" }, + submissions: { current: submissions, list: [submissions], text: "submissions" }, + feedback: { current: fixtureFeedback, list: [fixtureFeedback] }, + }); + expect(screen.getByTestId("SubmissionListId")).toBeInTheDocument(); + }); +}); diff --git a/src/components/sections/profile/achievements/LinkField.tsx b/src/components/sections/profile/achievements/LinkField.tsx index b8106b21f..24a215f1a 100644 --- a/src/components/sections/profile/achievements/LinkField.tsx +++ b/src/components/sections/profile/achievements/LinkField.tsx @@ -11,6 +11,7 @@ import { ReactElement, useEffect, useState } from "react"; */ interface AchievementLinkFieldProps { link: string | null; + testId?: string } /** @@ -23,7 +24,7 @@ interface AchievementLinkFieldProps { } * @returns {*} */ -export default function AchievementLinkField({ link }: AchievementLinkFieldProps): ReactElement { +export default function AchievementLinkField({ link, testId = "achievementLinkId" }: AchievementLinkFieldProps): ReactElement { const { t } = useTranslation(); const [fullLink, setFullink] = useState(""); @@ -37,7 +38,7 @@ export default function AchievementLinkField({ link }: AchievementLinkFieldProps const copy = () => navigator.clipboard.writeText(link as string); return ( -
+

{link}

diff --git a/src/components/sections/profile/achievements/ListItem.tsx b/src/components/sections/profile/achievements/ListItem.tsx index ead49d101..e0e4d57f7 100644 --- a/src/components/sections/profile/achievements/ListItem.tsx +++ b/src/components/sections/profile/achievements/ListItem.tsx @@ -13,6 +13,7 @@ interface AchievementViewItemProps { mobileBlock?: boolean; itemsStart?: boolean; children?: React.ReactNode; + testId?: string; } /** @@ -29,13 +30,13 @@ interface AchievementViewItemProps { } * @returns {*} */ -export default function AchievementViewItem({ name, columns = 3, mobileBlock = false, itemsStart = false, children }: AchievementViewItemProps): ReactElement { +export default function AchievementViewItem({ name, columns = 3, mobileBlock = false, itemsStart = false, children, testId = "achievementViewItemId" }: AchievementViewItemProps): ReactElement { const gridClasses = !mobileBlock ? [`grid`, `grid-cols-${columns}`] : [`grid`, `grid-cols-1 md:grid-cols-${columns}`, `gap-y-3 md:gap-y-0`]; const alignment = !itemsStart ? "items-center" : "items-start"; return ( -
+

{name}

{children}
diff --git a/src/components/sections/profile/communities/List.tsx b/src/components/sections/profile/communities/List.tsx index c0180fe73..91468ea54 100644 --- a/src/components/sections/profile/communities/List.tsx +++ b/src/components/sections/profile/communities/List.tsx @@ -26,7 +26,7 @@ interface SubmissionListMultiSelector { * Submission list component * @returns {ReactElement} */ -export default function SubmissionList(): ReactElement { +export default function SubmissionList({ testId = "SubmissionListId" }: { testId?: string }): ReactElement { const { t } = useTranslation(); const { community, submissions, feedbacks } = useMultiSelector({ community: (state: IRootState) => state.profileCommunities.current, @@ -37,7 +37,7 @@ export default function SubmissionList(): ReactElement { const navigation = useNavigation(); return ( -
+
{submissions && submissions.length !== 0 ? (

{t("communities.submissions")}

diff --git a/src/types/bounty.d.ts b/src/types/bounty.d.ts index 8d08459ab..ef5dc41bc 100644 --- a/src/types/bounty.d.ts +++ b/src/types/bounty.d.ts @@ -154,7 +154,7 @@ export interface User { connected?: boolean; }; kycStatus: KYCSTATUS; - referrals: Referral; + referrals?: Referral; } export interface UserMetadata { diff --git a/src/types/community.d.ts b/src/types/community.d.ts index 00e756ca1..8ddedfd1e 100644 --- a/src/types/community.d.ts +++ b/src/types/community.d.ts @@ -22,8 +22,8 @@ export interface Community { challenges: number; duration: number; items?: any[]; - challenge: Challenge; - submission: Submission; + challenge?: Challenge; + submission?: Submission; can_mint_certificates: boolean; }