From e95f467133135f94fa20414ea670c7a403a2b8bf Mon Sep 17 00:00:00 2001
From: Tenzin Youdon <94886097+Tech-lo@users.noreply.github.com>
Date: Thu, 12 Feb 2026 16:17:57 +0530
Subject: [PATCH 18/24] Add segment_id to task-related components as per
updated backend (#147)
* Add segment_id to task-related components as per updated backend
* update naming
---
src/components/routes/task/api/taskApi.ts | 2 ++
src/components/routes/task/components/view/TaskForm.tsx | 9 +++++++--
.../ui/molecules/content-sub/ContentTypeSelector.tsx | 3 ++-
src/components/ui/molecules/subtask-card/SubTaskCard.tsx | 1 +
.../molecules/webuddhist-source/SourceSelectorSheet.tsx | 3 ++-
.../ui/molecules/webuddhist-source/sourceItem.tsx | 3 ++-
6 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/src/components/routes/task/api/taskApi.ts b/src/components/routes/task/api/taskApi.ts
index d6fa0a7e..7c37ee27 100644
--- a/src/components/routes/task/api/taskApi.ts
+++ b/src/components/routes/task/api/taskApi.ts
@@ -46,6 +46,7 @@ export const createSubTasks = async (
duration?: string;
source_text_id?: string | null;
pecha_segment_id?: string | null;
+ segment_id?: string | null;
}[],
) => {
const { data } = await axiosInstance.post(
@@ -71,6 +72,7 @@ export const updateSubTasks = async (
duration?: string;
source_text_id?: string | null;
pecha_segment_id?: string | null;
+ segment_id?: string | null;
}[],
) => {
await axiosInstance.put(
diff --git a/src/components/routes/task/components/view/TaskForm.tsx b/src/components/routes/task/components/view/TaskForm.tsx
index d47f6bc9..51575c0d 100644
--- a/src/components/routes/task/components/view/TaskForm.tsx
+++ b/src/components/routes/task/components/view/TaskForm.tsx
@@ -84,6 +84,7 @@ const TaskForm = ({
...(subTask.content_type === "SOURCE_REFERENCE" && {
source_text_id: subTask.source_text_id || null,
pecha_segment_id: subTask.pecha_segment_id || null,
+ segment_id: subTask.segment_id || null,
}),
}));
await createSubTasks(taskResponse.id, subTasksPayload);
@@ -116,6 +117,7 @@ const TaskForm = ({
...(subTask.content_type === "SOURCE_REFERENCE" && {
source_text_id: subTask.source_text_id || null,
pecha_segment_id: subTask.pecha_segment_id || null,
+ segment_id: subTask.segment_id || null,
}),
}));
await updateSubTasks(editingTask.id, subTasksPayload);
@@ -192,6 +194,7 @@ const TaskForm = ({
content: data.content,
source_text_id: data.source_text_id || null,
pecha_segment_id: data.pecha_segment_id || null,
+ segment_id: data.segment_id || null,
};
default:
return {
@@ -207,8 +210,9 @@ const TaskForm = ({
interface SourceData {
content: string;
- segment_id: string;
+ pecha_segment_id: string;
text_id: string;
+ segment_id: string;
}
const handleAddSubTask = (content_type: any, sourceData?: SourceData) => {
@@ -251,7 +255,8 @@ const TaskForm = ({
content_type: "SOURCE_REFERENCE",
content: sourceData?.content || "",
source_text_id: sourceData?.text_id || null,
- pecha_segment_id: sourceData?.segment_id || null,
+ pecha_segment_id: sourceData?.pecha_segment_id || null,
+ segment_id: sourceData?.segment_id || null,
};
break;
}
diff --git a/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx b/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx
index 3e84d409..12c5493e 100644
--- a/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx
+++ b/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx
@@ -8,8 +8,9 @@ import { SourceSelectorSheet } from "../webuddhist-source/SourceSelectorSheet";
interface SourceData {
content: string;
- segment_id: string;
+ pecha_segment_id: string;
text_id: string;
+ segment_id: string;
}
interface ContentTypeSelectorProps {
diff --git a/src/components/ui/molecules/subtask-card/SubTaskCard.tsx b/src/components/ui/molecules/subtask-card/SubTaskCard.tsx
index 646e8aba..92c2b449 100644
--- a/src/components/ui/molecules/subtask-card/SubTaskCard.tsx
+++ b/src/components/ui/molecules/subtask-card/SubTaskCard.tsx
@@ -46,6 +46,7 @@ interface SourceSubTask {
display_order?: number;
source_text_id?: string | null;
pecha_segment_id?: string | null;
+ segment_id?: string | null;
}
export type SubTask =
| VideoSubTask
diff --git a/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx b/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx
index 2e3eaa15..7fe14c64 100644
--- a/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx
+++ b/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx
@@ -12,8 +12,9 @@ import { LANGUAGE } from "@/lib/constant";
interface SourceData {
content: string;
- segment_id: string;
+ pecha_segment_id: string;
text_id: string;
+ segment_id: string;
}
interface SourceSelectorSheetProps {
diff --git a/src/components/ui/molecules/webuddhist-source/sourceItem.tsx b/src/components/ui/molecules/webuddhist-source/sourceItem.tsx
index 2e3082b9..9cc49b62 100644
--- a/src/components/ui/molecules/webuddhist-source/sourceItem.tsx
+++ b/src/components/ui/molecules/webuddhist-source/sourceItem.tsx
@@ -17,8 +17,9 @@ const SourceItem = ({ source, onSegment }: any) => {
onClick={() =>
onSegment?.({
content: segment.content,
- segment_id: segment.pecha_segment_id,
+ pecha_segment_id: segment.pecha_segment_id,
text_id: source.text.text_id,
+ segment_id: segment.segment_id,
})
}
>
From e9d6ea14382e3571f8f399ef09c83650d21dbbf8 Mon Sep 17 00:00:00 2001
From: Tenzin Youdon <94886097+Tech-lo@users.noreply.github.com>
Date: Tue, 24 Feb 2026 17:12:22 +0530
Subject: [PATCH 19/24] simplify (#149)
---
src/components/api/searchApi.ts | 22 -------------------
.../webuddhist-source/SourceSelectorSheet.tsx | 10 +++------
2 files changed, 3 insertions(+), 29 deletions(-)
diff --git a/src/components/api/searchApi.ts b/src/components/api/searchApi.ts
index 9ce325e7..72578403 100644
--- a/src/components/api/searchApi.ts
+++ b/src/components/api/searchApi.ts
@@ -1,42 +1,20 @@
import axiosInstance from "@/config/axios-config";
-import { LANGUAGE } from "@/lib/constant";
type SearchCommon = {
query: string;
- language?: string;
limit?: number;
skip?: number;
};
-const normalizeLanguageForApi = (language?: string | null) => {
- if (!language) return "en";
- const lower = language.toLowerCase();
- if (lower.startsWith("en")) return "en";
- if (lower.startsWith("bo")) return "bo";
- if (lower.startsWith("zh")) return "zh";
- return "en";
-};
-
-const pickSearchLanguage = (query: string, uiLanguage?: string | null) => {
- const normalized = normalizeLanguageForApi(uiLanguage);
- if (/[A-Za-z]/.test(query) && normalized !== "en") {
- return "en";
- }
- return normalized;
-};
-
export const searchSources = async ({
query,
- language = localStorage.getItem(LANGUAGE) || "en",
limit = 10,
skip = 0,
}: SearchCommon) => {
- const lang = pickSearchLanguage(query, language);
const { data } = await axiosInstance.get(`/api/v1/search/multilingual`, {
params: {
query,
search_type: "exact",
- language: lang,
limit,
skip,
},
diff --git a/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx b/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx
index 7fe14c64..5a0ef26c 100644
--- a/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx
+++ b/src/components/ui/molecules/webuddhist-source/SourceSelectorSheet.tsx
@@ -8,7 +8,6 @@ import SourceItem from "./sourceItem";
import pechaIcon from "@/assets/icon/pecha_icon.png";
import { Pagination } from "@/components/ui/molecules/pagination/Pagination";
import { searchSources } from "@/components/api/searchApi";
-import { LANGUAGE } from "@/lib/constant";
interface SourceData {
content: string;
@@ -42,15 +41,12 @@ export const SourceSelectorSheet = ({
pagination.currentPage,
pagination.limit,
],
- queryFn: () => {
- const language = localStorage.getItem(LANGUAGE) || "en";
- return searchSources({
+ queryFn: () =>
+ searchSources({
query: debouncedSearchFilter,
- language,
limit: pagination.limit,
skip,
- });
- },
+ }),
refetchOnWindowFocus: false,
enabled: isOpen,
});
From 24221e89eb115bacc08d87aa290ebf2bd8a8831f Mon Sep 17 00:00:00 2001
From: Tenzin Youdon <94886097+Tech-lo@users.noreply.github.com>
Date: Thu, 26 Feb 2026 09:59:09 +0530
Subject: [PATCH 20/24] slight update for status (#150)
* slight update for status
* lint
---
.../routes/task/PlanDetailsPage.test.tsx | 7 ++++--
.../routes/task/PlanDetailsPage.tsx | 10 ++++-----
.../components/sidebar-component/SideBar.tsx | 22 +++++++++----------
.../sidebar-component/Sidebar.test.tsx | 6 ++---
.../routes/task/components/view/TaskForm.tsx | 14 +++++++-----
.../routes/task/components/view/TaskView.tsx | 16 +++++++-------
.../dropdown-button/DropdownButton.tsx | 2 +-
7 files changed, 41 insertions(+), 36 deletions(-)
diff --git a/src/components/routes/task/PlanDetailsPage.test.tsx b/src/components/routes/task/PlanDetailsPage.test.tsx
index 20e310c7..0baba1e3 100644
--- a/src/components/routes/task/PlanDetailsPage.test.tsx
+++ b/src/components/routes/task/PlanDetailsPage.test.tsx
@@ -93,7 +93,10 @@ Object.defineProperty(window, "sessionStorage", {
writable: true,
});
-const renderWithProviders = (component: React.ReactElement, isDraft = true) => {
+const renderWithProviders = (
+ component: React.ReactElement,
+ isEditable = true,
+) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
@@ -101,7 +104,7 @@ const renderWithProviders = (component: React.ReactElement, isDraft = true) => {
});
queryClient.setQueryData(["planDetails", "test-plan-id"], {
...mockPlanData,
- status: isDraft ? "DRAFT" : "ARCHIVED",
+ status: isEditable ? "DRAFT" : "PUBLISHED",
});
return render(
diff --git a/src/components/routes/task/PlanDetailsPage.tsx b/src/components/routes/task/PlanDetailsPage.tsx
index 2cff7272..d66e6362 100644
--- a/src/components/routes/task/PlanDetailsPage.tsx
+++ b/src/components/routes/task/PlanDetailsPage.tsx
@@ -19,7 +19,7 @@ const PlanDetailsPage = () => {
});
const status = planDetails?.status || "DRAFT";
- const isDraft = status === "DRAFT";
+ const isEditable = status === "DRAFT" || status === "ARCHIVED";
const handleDaySelect = (dayNumber: number) => {
setSelectedDay(dayNumber);
@@ -28,7 +28,7 @@ const PlanDetailsPage = () => {
};
const handleEditTask = (task: any) => {
- if (!isDraft) {
+ if (!isEditable) {
return;
}
setEditingTask(task);
@@ -60,21 +60,21 @@ const PlanDetailsPage = () => {
setSelectedTaskId(taskId);
}}
onTaskDelete={handleTaskDelete}
- isDraft={isDraft}
+ isEditable={isEditable}
/>
{selectedTaskId ? (
) : (
)}
diff --git a/src/components/routes/task/components/sidebar-component/SideBar.tsx b/src/components/routes/task/components/sidebar-component/SideBar.tsx
index d99f46e5..5eb90ecf 100644
--- a/src/components/routes/task/components/sidebar-component/SideBar.tsx
+++ b/src/components/routes/task/components/sidebar-component/SideBar.tsx
@@ -19,7 +19,7 @@ interface SideBarProps {
onDaySelect: (dayNumber: number) => void;
onTaskClick?: (taskId: string) => void;
onTaskDelete?: (taskId: string) => void;
- isDraft?: boolean;
+ isEditable?: boolean;
}
const SideBar = ({
@@ -27,7 +27,7 @@ const SideBar = ({
onDaySelect,
onTaskClick,
onTaskDelete,
- isDraft,
+ isEditable,
}: SideBarProps) => {
const [expandedDay, setExpandedDay] = useState(selectedDay);
const { plan_id } = useParams<{ plan_id: string }>();
@@ -116,7 +116,7 @@ const SideBar = ({
onReorder={(activeId: any, overId: any) => {
handleDayReorder(activeId, overId);
}}
- disabled={!isDraft}
+ disabled={!isEditable}
>
{displayDays.map((day: any) => (
@@ -129,7 +129,7 @@ const SideBar = ({
}}
>
- {isDraft && (
+ {isEditable && (
{
e.stopPropagation();
- if (isDraft) {
+ if (isEditable) {
handleDayClick(day.day_number);
}
}}
/>
- {isDraft && currentPlan?.days.length > 1 && (
+ {isEditable && currentPlan?.days.length > 1 && (
@@ -229,7 +229,7 @@ const SideBar = ({
onReorder={(activeId: any, overId: any) => {
handleTaskReorder(activeId, overId);
}}
- disabled={!isDraft}
+ disabled={!isEditable}
>
{getDisplayTasks(day).map((task: any) => (
{({ listeners }: any) => (
<>
- {isDraft && (
+ {isEditable && (
{task.title}
- {isDraft && (
+ {isEditable && (
diff --git a/src/components/routes/task/components/sidebar-component/Sidebar.test.tsx b/src/components/routes/task/components/sidebar-component/Sidebar.test.tsx
index 907f6788..e47cb0e1 100644
--- a/src/components/routes/task/components/sidebar-component/Sidebar.test.tsx
+++ b/src/components/routes/task/components/sidebar-component/Sidebar.test.tsx
@@ -193,7 +193,7 @@ describe("SideBar Component", () => {
selectedDay={1}
onDaySelect={mockOnDaySelect}
onTaskClick={mockOnTaskClick}
- isDraft={true}
+ isEditable={true}
/>,
);
await waitFor(() => {
@@ -247,7 +247,7 @@ describe("SideBar Component", () => {
selectedDay={1}
onDaySelect={mockOnDaySelect}
onTaskClick={mockOnTaskClick}
- isDraft={true}
+ isEditable={true}
/>,
);
await waitFor(() => {
@@ -279,7 +279,7 @@ describe("SideBar Component", () => {
selectedDay={1}
onDaySelect={mockOnDaySelect}
onTaskClick={mockOnTaskClick}
- isDraft={true}
+ isEditable={true}
/>,
);
await waitFor(() => {
diff --git a/src/components/routes/task/components/view/TaskForm.tsx b/src/components/routes/task/components/view/TaskForm.tsx
index 51575c0d..d4ba6a5b 100644
--- a/src/components/routes/task/components/view/TaskForm.tsx
+++ b/src/components/routes/task/components/view/TaskForm.tsx
@@ -27,7 +27,7 @@ interface TaskFormProps {
selectedDay: number;
editingTask?: any;
onCancel: (newlyCreatedTaskId?: string) => void;
- isDraft?: boolean;
+ isEditable?: boolean;
}
type TaskFormData = z.infer;
@@ -36,7 +36,7 @@ const TaskForm = ({
selectedDay,
editingTask,
onCancel,
- isDraft = true,
+ isEditable = true,
}: TaskFormProps) => {
const { plan_id } = useParams();
const queryClient = useQueryClient();
@@ -344,10 +344,10 @@ const TaskForm = ({
isTitleEditing={isTitleEditing}
formValue={formValues.title}
control={form.control}
- onEdit={() => isDraft && setIsTitleEditing(true)}
+ onEdit={() => isEditable && setIsTitleEditing(true)}
onSave={handleSaveTitle}
onCancel={() => setIsTitleEditing(false)}
- disabled={!isDraft}
+ disabled={!isEditable}
/>
{isEditMode && (
@@ -377,7 +377,9 @@ const TaskForm = ({
{imageUploadError && (
{imageUploadError}
)}
- {isDraft && }
+ {isEditable && (
+
+ )}
@@ -395,7 +397,7 @@ const TaskForm = ({
className="cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
type="submit"
disabled={
- !isDraft ||
+ !isEditable ||
createTaskMutation.isPending ||
updateTaskMutation.isPending ||
subTasks.length === 0
diff --git a/src/components/routes/task/components/view/TaskView.tsx b/src/components/routes/task/components/view/TaskView.tsx
index 5aa3ac74..1a0a67a8 100644
--- a/src/components/routes/task/components/view/TaskView.tsx
+++ b/src/components/routes/task/components/view/TaskView.tsx
@@ -19,7 +19,7 @@ type ContentType = "TEXT" | "IMAGE" | "AUDIO" | "VIDEO" | "SOURCE_REFERENCE";
interface TaskViewProps {
taskId: string;
onEditTask: (task: any) => void;
- isDraft?: boolean;
+ isEditable?: boolean;
}
const fetchTaskDetails = async (task_id: string) => {
@@ -56,11 +56,11 @@ const SubtaskContent = ({
const SubtaskCard = ({
subtask,
listeners,
- isDraft,
+ isEditable,
}: {
subtask: any;
listeners?: any;
- isDraft?: boolean;
+ isEditable?: boolean;
}) => {
return (
{subtask.content_type}
- {listeners && isDraft && (
+ {listeners && isEditable && (
{
+const TaskView = ({ taskId, onEditTask, isEditable }: TaskViewProps) => {
const { data: taskDetails, isLoading } = useQuery({
queryKey: ["taskDetails", taskId],
queryFn: () => fetchTaskDetails(taskId),
@@ -101,7 +101,7 @@ const TaskView = ({ taskId, onEditTask, isDraft }: TaskViewProps) => {
Task
- {isDraft && (
+ {isEditable && (
{
onReorder={(activeId: any, overId: any) => {
handleSubtaskReorder(activeId, overId);
}}
- disabled={!isDraft}
+ disabled={!isEditable}
>
{displaySubtasks.map((subtask: any) => (
@@ -148,7 +148,7 @@ const TaskView = ({ taskId, onEditTask, isDraft }: TaskViewProps) => {
)}
diff --git a/src/components/ui/molecules/dropdown-button/DropdownButton.tsx b/src/components/ui/molecules/dropdown-button/DropdownButton.tsx
index bb9ab935..539dff4f 100644
--- a/src/components/ui/molecules/dropdown-button/DropdownButton.tsx
+++ b/src/components/ui/molecules/dropdown-button/DropdownButton.tsx
@@ -84,7 +84,7 @@ export function DropdownButton({
allowedStatuses.includes(status.value),
);
- const canEdit = currentStatus === "DRAFT";
+ const canEdit = currentStatus === "DRAFT" || currentStatus === "ARCHIVED";
const canDelete = currentStatus === "DRAFT" || currentStatus === "ARCHIVED";
return (
From a164646a57df65fed920887a78aa37adc528483f Mon Sep 17 00:00:00 2001
From: Tenzin Youdon <94886097+Tech-lo@users.noreply.github.com>
Date: Wed, 4 Mar 2026 21:34:17 +0530
Subject: [PATCH 21/24] Tests coverage (#151)
* tests
* lint
---
.../routes/create-plan/CreatePlan.test.tsx | 97 +++++++++++-
.../routes/dashboard/Dashboard.test.tsx | 40 +++++
.../routes/task/PlanDetailsPage.test.tsx | 139 ++++++++++++++++++
.../task/components/view/TaskForm.test.tsx | 24 +++
4 files changed, 295 insertions(+), 5 deletions(-)
diff --git a/src/components/routes/create-plan/CreatePlan.test.tsx b/src/components/routes/create-plan/CreatePlan.test.tsx
index a21c881b..63ead223 100644
--- a/src/components/routes/create-plan/CreatePlan.test.tsx
+++ b/src/components/routes/create-plan/CreatePlan.test.tsx
@@ -83,6 +83,19 @@ describe("CreatePlan Component", () => {
}
return Promise.resolve({ data: {} });
});
+ vi.spyOn(axiosInstance, "get").mockResolvedValue({
+ data: {
+ id: "plan-123",
+ title: "Existing Plan",
+ description: "Existing description",
+ total_days: 14,
+ difficulty_level: "Beginner",
+ plan_image_url: "https://example.com/image.jpg",
+ image_url: "https://example.com/image.jpg",
+ tags: ["meditation"],
+ language: "en",
+ },
+ });
});
it("renders create plan form with main heading", () => {
renderWithProviders();
@@ -162,7 +175,7 @@ describe("CreatePlan Component", () => {
fireEvent.change(titleInput, { target: { value: "Test Plan Title" } });
- expect((titleInput as HTMLInputElement).value).toBe("Test Plan Title");
+ expect(titleInput).toHaveValue("Test Plan Title");
});
it("allows typing in description textarea", () => {
@@ -176,9 +189,7 @@ describe("CreatePlan Component", () => {
target: { value: "Test plan description" },
});
- expect((descriptionTextarea as HTMLTextAreaElement).value).toBe(
- "Test plan description",
- );
+ expect(descriptionTextarea).toHaveValue("Test plan description");
});
it("allows typing in number of days input", () => {
@@ -190,7 +201,7 @@ describe("CreatePlan Component", () => {
fireEvent.change(daysInput, { target: { value: "30" } });
- expect((daysInput as HTMLInputElement).value).toBe("30");
+ expect(daysInput).toHaveValue(30);
});
it("renders image upload area", () => {
@@ -382,4 +393,80 @@ describe("CreatePlan Component", () => {
fireEvent.click(confirmButton);
expect(mockBlocker.proceed).toHaveBeenCalled();
});
+
+ it("fetches and populates form in edit mode", async () => {
+ vi.mocked(useParams).mockReturnValue({ plan_id: "plan-123" });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(
+ screen.getByPlaceholderText("studio.plan.form.placeholder.title"),
+ ).toHaveValue("Existing Plan");
+ });
+
+ expect(
+ screen.getByPlaceholderText("studio.plan.form.placeholder.description"),
+ ).toHaveValue("Existing description");
+
+ expect(
+ screen.getByPlaceholderText(
+ "studio.plan.form.placeholder.number_of_days",
+ ),
+ ).toHaveValue(14);
+
+ expect(screen.getByAltText("Cover preview")).toBeInTheDocument();
+ });
+
+ it("handles navigation cancellation", async () => {
+ const mockBlocker = {
+ state: "blocked" as const,
+ proceed: vi.fn(),
+ reset: vi.fn(),
+ location: {} as any,
+ };
+ vi.mocked(useBlocker).mockReturnValue(mockBlocker);
+ renderWithProviders();
+ await waitFor(() => {
+ expect(
+ screen.getByText("studio.plan.navigation.confirm_title"),
+ ).toBeInTheDocument();
+ });
+ const cancelButtons = screen.getAllByText("common.button.cancel");
+ const dialogCancelButton = cancelButtons.find((btn) =>
+ btn.closest('[role="dialog"]'),
+ );
+ fireEvent.click(dialogCancelButton!);
+ expect(mockBlocker.reset).toHaveBeenCalled();
+ });
+
+ it("submits update in edit mode", async () => {
+ vi.mocked(useParams).mockReturnValue({ plan_id: "plan-123" });
+ vi.spyOn(axiosInstance, "put").mockResolvedValue({ data: {} });
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(
+ screen.getByPlaceholderText("studio.plan.form.placeholder.title"),
+ ).toHaveValue("Existing Plan");
+ });
+
+ fireEvent.change(
+ screen.getByPlaceholderText("studio.plan.form.placeholder.title"),
+ { target: { value: "Updated Plan" } },
+ );
+
+ const submitButton = screen.getByText("studio.plan.update_button");
+ await act(async () => {
+ fireEvent.click(submitButton);
+ });
+
+ await waitFor(() => {
+ expect(axiosInstance.put).toHaveBeenCalledWith(
+ "/api/v1/cms/plans/plan-123",
+ expect.any(Object),
+ expect.any(Object),
+ );
+ });
+ });
});
diff --git a/src/components/routes/dashboard/Dashboard.test.tsx b/src/components/routes/dashboard/Dashboard.test.tsx
index e5a46774..273d5765 100644
--- a/src/components/routes/dashboard/Dashboard.test.tsx
+++ b/src/components/routes/dashboard/Dashboard.test.tsx
@@ -148,4 +148,44 @@ describe("Dashboard Component", () => {
);
});
});
+
+ it("handles toggle featured on a plan", async () => {
+ vi.spyOn(axiosInstance, "get").mockResolvedValue({
+ data: {
+ plans: [
+ {
+ id: "plan-1",
+ image_url: "",
+ title: "Test Plan",
+ description: "Test description",
+ total_days: "7",
+ subscription_count: "10",
+ status: "PUBLISHED",
+ featured: false,
+ language: "EN",
+ },
+ ],
+ total: 1,
+ },
+ });
+ axiosInstance.patch = vi.fn().mockResolvedValue({ data: {} });
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
+ });
+
+ expect(screen.getByText("Test Plan")).toBeInTheDocument();
+
+ const featuredButton = screen.getByText("Not Featured");
+ fireEvent.click(featuredButton);
+
+ await waitFor(() => {
+ expect(axiosInstance.patch).toHaveBeenCalledWith(
+ "/api/v1/cms/plans/plan-1/featured",
+ expect.any(Object),
+ );
+ });
+ });
});
diff --git a/src/components/routes/task/PlanDetailsPage.test.tsx b/src/components/routes/task/PlanDetailsPage.test.tsx
index 0baba1e3..4f7b3d58 100644
--- a/src/components/routes/task/PlanDetailsPage.test.tsx
+++ b/src/components/routes/task/PlanDetailsPage.test.tsx
@@ -1,4 +1,5 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { vi } from "vitest";
import { BrowserRouter } from "react-router-dom";
@@ -233,4 +234,142 @@ describe("PlanDetailsPanel Component", () => {
expect(screen.getByText("Task")).toBeInTheDocument();
});
});
+
+ it("fetches plan details via queryFn when cache is empty", async () => {
+ const { default: axiosInstance } = await import("@/config/axios-config");
+ const mockAxios = axiosInstance as any;
+ mockAxios.get.mockResolvedValue({
+ data: { ...mockPlanData, status: "DRAFT" },
+ });
+
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
+
+ render(
+
+
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(mockAxios.get).toHaveBeenCalledWith(
+ expect.stringContaining("/api/v1/cms/plans/test-plan-id"),
+ expect.any(Object),
+ );
+ });
+ await waitFor(() => {
+ expect(screen.getByText(mockPlanData.title)).toBeInTheDocument();
+ });
+ });
+
+ it("clears selected task when that task is deleted from sidebar", async () => {
+ const user = userEvent.setup();
+ const { default: axiosInstance } = await import("@/config/axios-config");
+ const mockAxios = axiosInstance as any;
+ mockAxios.get.mockImplementation((url: string) => {
+ if (url.includes("/tasks/task1")) {
+ return Promise.resolve({
+ data: {
+ id: "task1",
+ title: "Morning Intention Setting",
+ display_order: 1,
+ estimated_time: 30,
+ subtasks: [],
+ },
+ });
+ }
+ return Promise.resolve({ data: { ...mockPlanData, status: "DRAFT" } });
+ });
+ mockAxios.delete.mockResolvedValue({ data: {} });
+
+ renderWithProviders(, true);
+ await waitFor(() => {
+ expect(screen.getByText(mockPlanData.title)).toBeInTheDocument();
+ });
+
+ fireEvent.click(screen.getByText("Morning Intention Setting"));
+ await waitFor(() => {
+ expect(screen.getByText("Task")).toBeInTheDocument();
+ });
+
+ const taskElements = screen.getAllByText("Morning Intention Setting");
+ const sidebarSpan = taskElements.find(
+ (el) => el.tagName === "SPAN" && el.classList.contains("cursor-pointer"),
+ );
+ const taskRow = sidebarSpan!.parentElement!;
+ const dropdownTrigger = taskRow.querySelector(
+ '[data-slot="dropdown-menu-trigger"]',
+ ) as HTMLElement;
+ await user.click(dropdownTrigger);
+
+ const deleteText = await screen.findByText("Delete");
+ await user.click(deleteText);
+
+ const deleteTaskBtn = await screen.findByText("Delete Task");
+ await user.click(deleteTaskBtn);
+
+ await waitFor(() => {
+ expect(screen.getByText("Add Task")).toBeInTheDocument();
+ });
+ });
+
+ it("clears editing task when that task is deleted from sidebar", async () => {
+ const user = userEvent.setup();
+ const { default: axiosInstance } = await import("@/config/axios-config");
+ const mockAxios = axiosInstance as any;
+ mockAxios.get.mockImplementation((url: string) => {
+ if (url.includes("/tasks/task1")) {
+ return Promise.resolve({
+ data: {
+ id: "task1",
+ title: "Morning Intention Setting",
+ display_order: 1,
+ estimated_time: 30,
+ subtasks: [],
+ },
+ });
+ }
+ return Promise.resolve({ data: { ...mockPlanData, status: "DRAFT" } });
+ });
+ mockAxios.delete.mockResolvedValue({ data: {} });
+
+ renderWithProviders(, true);
+ await waitFor(() => {
+ expect(screen.getByText(mockPlanData.title)).toBeInTheDocument();
+ });
+
+ fireEvent.click(screen.getByText("Morning Intention Setting"));
+ await waitFor(() => {
+ expect(screen.getByText("Task")).toBeInTheDocument();
+ });
+
+ const editBtn = await screen.findByRole("button", { name: /edit/i });
+ fireEvent.click(editBtn);
+ await waitFor(() => {
+ expect(screen.getByText("Edit Task")).toBeInTheDocument();
+ });
+
+ const taskElements = screen.getAllByText("Morning Intention Setting");
+ const sidebarSpan = taskElements.find(
+ (el) => el.tagName === "SPAN" && el.classList.contains("cursor-pointer"),
+ );
+ const taskRow = sidebarSpan!.parentElement!;
+ const dropdownTrigger = taskRow.querySelector(
+ '[data-slot="dropdown-menu-trigger"]',
+ ) as HTMLElement;
+ await user.click(dropdownTrigger);
+
+ const deleteText = await screen.findByText("Delete");
+ await user.click(deleteText);
+
+ const deleteTaskBtn = await screen.findByText("Delete Task");
+ await user.click(deleteTaskBtn);
+
+ await waitFor(() => {
+ expect(screen.getByText("Add Task")).toBeInTheDocument();
+ });
+ });
});
diff --git a/src/components/routes/task/components/view/TaskForm.test.tsx b/src/components/routes/task/components/view/TaskForm.test.tsx
index 0112968e..6f167e7f 100644
--- a/src/components/routes/task/components/view/TaskForm.test.tsx
+++ b/src/components/routes/task/components/view/TaskForm.test.tsx
@@ -530,4 +530,28 @@ describe("TaskForm Component", () => {
).toBeInTheDocument();
});
});
+
+ it("shows error when image exceeds 1MB", async () => {
+ renderWithProviders();
+ fireEvent.click(screen.getByText("Add Image"));
+ await waitFor(() => {
+ expect(screen.getByTestId("image-upload-input")).toBeInTheDocument();
+ });
+ const imageInput = screen.getByTestId("image-upload-input");
+ const largeFile = new File(
+ [new ArrayBuffer(1.5 * 1024 * 1024)],
+ "big.jpg",
+ {
+ type: "image/jpeg",
+ },
+ );
+ fireEvent.change(imageInput, { target: { files: [largeFile] } });
+ await waitFor(() => {
+ expect(
+ screen.getByText(
+ "File size exceeds 1MB limit. Please select a smaller image.",
+ ),
+ ).toBeInTheDocument();
+ });
+ });
});
From 330bc505f91c833cf1cdac381eaee02f16147692 Mon Sep 17 00:00:00 2001
From: Tenzin Youdon <94886097+Tech-lo@users.noreply.github.com>
Date: Mon, 9 Mar 2026 18:40:59 +0530
Subject: [PATCH 22/24] uncomment (#153)
---
.../ui/molecules/content-sub/ContentTypeSelector.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx b/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx
index 12c5493e..62d6e135 100644
--- a/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx
+++ b/src/components/ui/molecules/content-sub/ContentTypeSelector.tsx
@@ -1,6 +1,6 @@
import { Pecha } from "@/components/ui/shadimport";
import { IoMdAdd, IoMdVideocam } from "react-icons/io";
-import { IoTextOutline } from "react-icons/io5";
+import { IoMusicalNotesSharp, IoTextOutline } from "react-icons/io5";
import { MdOutlineImage } from "react-icons/md";
import pechaIcon from "@/assets/icon/pecha_icon.png";
import { useState } from "react";
@@ -27,10 +27,10 @@ const contentTypes = [
key: "IMAGE",
icon: ,
},
- // {
- // key: "AUDIO",
- // icon: ,
- // },
+ {
+ key: "AUDIO",
+ icon: ,
+ },
{
key: "VIDEO",
icon: ,
From 08ba9187396da9e6a5fcb64242500a5b06a471c3 Mon Sep 17 00:00:00 2001
From: TenzDelek
Date: Mon, 30 Mar 2026 16:03:34 +0530
Subject: [PATCH 23/24] build
---
src/components/routes/task/PlanDetailsPage.test.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/routes/task/PlanDetailsPage.test.tsx b/src/components/routes/task/PlanDetailsPage.test.tsx
index 6314fb21..a5a0f34e 100644
--- a/src/components/routes/task/PlanDetailsPage.test.tsx
+++ b/src/components/routes/task/PlanDetailsPage.test.tsx
@@ -94,7 +94,7 @@ Object.defineProperty(window, "sessionStorage", {
writable: true,
});
-const renderWithProviders = (component: React.ReactElement, isDraft = true) => {
+const renderWithProviders = (component: React.ReactElement, isEditable = true) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
From d150fd44d00c4a0837bbcab1bc575a2cf42c17fc Mon Sep 17 00:00:00 2001
From: TenzDelek
Date: Mon, 30 Mar 2026 16:04:24 +0530
Subject: [PATCH 24/24] lint
---
src/components/routes/task/PlanDetailsPage.test.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/routes/task/PlanDetailsPage.test.tsx b/src/components/routes/task/PlanDetailsPage.test.tsx
index a5a0f34e..4f7b3d58 100644
--- a/src/components/routes/task/PlanDetailsPage.test.tsx
+++ b/src/components/routes/task/PlanDetailsPage.test.tsx
@@ -94,7 +94,10 @@ Object.defineProperty(window, "sessionStorage", {
writable: true,
});
-const renderWithProviders = (component: React.ReactElement, isEditable = true) => {
+const renderWithProviders = (
+ component: React.ReactElement,
+ isEditable = true,
+) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },