Skip to content

Commit c327f02

Browse files
author
Rajat
committed
Corrected drip links; profile update regression fix;
1 parent aeae20e commit c327f02

5 files changed

Lines changed: 233 additions & 4 deletions

File tree

apps/docs/src/pages/en/courses/add-content.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ layout: ../../../layouts/MainLayout.astro
66

77
CourseLit uses the concept of a `Lesson`. It is very similar to what we generally see in books, i.e., a large piece of information is divided into smaller chunks called lessons.
88

9-
Similarly, you can break down your course into `Lessons` and group the lessons into [Sections](/en/products/section).
9+
Similarly, you can break down your course into `Lessons` and group the lessons into [Sections](/en/courses/section).
1010

1111
## Sections
1212

apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/content/section/[section]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ export default function SectionPage(props: {
585585
<Resources
586586
links={[
587587
{
588-
href: "https://docs.courselit.app/en/products/section/#drip-a-section",
588+
href: "https://docs.courselit.app/en/courses/section#drip-a-section",
589589
text: "Drip content",
590590
},
591591
]}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import React from "react";
2+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3+
import "@testing-library/jest-dom";
4+
import ProfilePage from "../page";
5+
import { AddressContext, ProfileContext } from "@components/contexts";
6+
7+
const mockToast = jest.fn();
8+
const mockExec = jest.fn();
9+
const mockSetProfile = jest.fn();
10+
11+
jest.mock("@components/admin/dashboard-content", () => ({
12+
__esModule: true,
13+
default: ({ children }: { children: React.ReactNode }) => (
14+
<div>{children}</div>
15+
),
16+
}));
17+
18+
jest.mock("@courselit/components-library", () => ({
19+
Avatar: ({ children }: { children: React.ReactNode }) => (
20+
<div>{children}</div>
21+
),
22+
AvatarFallback: ({ children }: { children: React.ReactNode }) => (
23+
<div>{children}</div>
24+
),
25+
AvatarImage: ({ src }: { src?: string }) => <img alt="" src={src} />,
26+
Checkbox: ({
27+
checked,
28+
onChange,
29+
}: {
30+
checked: boolean;
31+
onChange: (value: boolean) => void;
32+
}) => (
33+
<input
34+
type="checkbox"
35+
checked={checked}
36+
onChange={(event) => onChange(event.target.checked)}
37+
/>
38+
),
39+
Image: ({ alt, src }: { alt: string; src: string }) => (
40+
<img alt={alt} src={src} />
41+
),
42+
MediaSelector: () => null,
43+
useToast: () => ({
44+
toast: mockToast,
45+
}),
46+
}));
47+
48+
jest.mock("@components/ui/field", () => ({
49+
Field: ({ children }: { children: React.ReactNode }) => (
50+
<div>{children}</div>
51+
),
52+
FieldContent: ({ children }: { children: React.ReactNode }) => (
53+
<div>{children}</div>
54+
),
55+
FieldGroup: ({ children }: { children: React.ReactNode }) => (
56+
<div>{children}</div>
57+
),
58+
FieldLabel: ({
59+
children,
60+
htmlFor,
61+
}: {
62+
children: React.ReactNode;
63+
htmlFor?: string;
64+
}) => <label htmlFor={htmlFor}>{children}</label>,
65+
FieldLegend: ({ children }: { children: React.ReactNode }) => (
66+
<legend>{children}</legend>
67+
),
68+
FieldSet: ({ children }: { children: React.ReactNode }) => (
69+
<fieldset>{children}</fieldset>
70+
),
71+
}));
72+
73+
jest.mock("@components/ui/card", () => ({
74+
Card: ({ children }: { children: React.ReactNode }) => (
75+
<div>{children}</div>
76+
),
77+
CardContent: ({ children }: { children: React.ReactNode }) => (
78+
<div>{children}</div>
79+
),
80+
CardHeader: ({ children }: { children: React.ReactNode }) => (
81+
<div>{children}</div>
82+
),
83+
CardTitle: ({ children }: { children: React.ReactNode }) => (
84+
<div>{children}</div>
85+
),
86+
}));
87+
88+
jest.mock("@components/ui/input", () => ({
89+
Input: (props: React.InputHTMLAttributes<HTMLInputElement>) => (
90+
<input {...props} />
91+
),
92+
}));
93+
94+
jest.mock("@components/ui/textarea", () => ({
95+
Textarea: (props: React.TextareaHTMLAttributes<HTMLTextAreaElement>) => (
96+
<textarea {...props} />
97+
),
98+
}));
99+
100+
jest.mock("@components/ui/button", () => ({
101+
Button: ({
102+
children,
103+
...props
104+
}: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
105+
<button {...props}>{children}</button>
106+
),
107+
}));
108+
109+
jest.mock("@courselit/utils", () => ({
110+
FetchBuilder: jest.fn().mockImplementation(() => ({
111+
setUrl: jest.fn().mockReturnThis(),
112+
setPayload: jest.fn().mockReturnThis(),
113+
setIsGraphQLEndpoint: jest.fn().mockReturnThis(),
114+
build: jest.fn().mockReturnThis(),
115+
exec: mockExec,
116+
})),
117+
}));
118+
119+
function renderPage() {
120+
return render(
121+
<AddressContext.Provider
122+
value={{
123+
backend: "http://localhost:3000",
124+
frontend: "http://localhost:3000",
125+
}}
126+
>
127+
<ProfileContext.Provider
128+
value={{
129+
profile: {
130+
userId: "user-1",
131+
name: "Jane Doe",
132+
email: "jane@example.com",
133+
bio: "Old bio",
134+
permissions: [],
135+
purchases: [],
136+
fetched: true,
137+
subscribedToUpdates: false,
138+
avatar: {
139+
thumbnail: "old-avatar.png",
140+
},
141+
},
142+
setProfile: mockSetProfile,
143+
}}
144+
>
145+
<ProfilePage />
146+
</ProfileContext.Provider>
147+
</AddressContext.Provider>,
148+
);
149+
}
150+
151+
describe("ProfilePage", () => {
152+
beforeEach(() => {
153+
jest.clearAllMocks();
154+
mockExec
155+
.mockResolvedValueOnce({
156+
user: {
157+
name: "Jane Doe",
158+
bio: "Old bio",
159+
email: "jane@example.com",
160+
subscribedToUpdates: false,
161+
avatar: {
162+
thumbnail: "old-avatar.png",
163+
},
164+
},
165+
})
166+
.mockResolvedValueOnce({
167+
user: {
168+
id: "db-user-1",
169+
name: "Jane Updated",
170+
userId: "user-1",
171+
email: "jane@example.com",
172+
permissions: [],
173+
purchases: [],
174+
bio: "Old bio",
175+
avatar: {
176+
thumbnail: "old-avatar.png",
177+
},
178+
},
179+
});
180+
});
181+
182+
it("preserves fetched profile state after saving details", async () => {
183+
renderPage();
184+
185+
const nameInput = await screen.findByDisplayValue("Jane Doe");
186+
fireEvent.change(nameInput, {
187+
target: {
188+
value: "Jane Updated",
189+
},
190+
});
191+
192+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
193+
194+
await waitFor(() => {
195+
expect(mockSetProfile).toHaveBeenCalledTimes(1);
196+
});
197+
198+
const updater = mockSetProfile.mock.calls[0][0];
199+
expect(typeof updater).toBe("function");
200+
201+
expect(
202+
updater({
203+
userId: "user-1",
204+
name: "Jane Doe",
205+
email: "jane@example.com",
206+
bio: "Old bio",
207+
permissions: [],
208+
purchases: [],
209+
fetched: true,
210+
subscribedToUpdates: false,
211+
avatar: {
212+
thumbnail: "old-avatar.png",
213+
},
214+
}),
215+
).toMatchObject({
216+
userId: "user-1",
217+
name: "Jane Updated",
218+
email: "jane@example.com",
219+
bio: "Old bio",
220+
fetched: true,
221+
subscribedToUpdates: false,
222+
});
223+
});
224+
});

apps/web/app/(with-contexts)/dashboard/(sidebar)/profile/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export default function Page() {
188188
certificateId
189189
},
190190
bio,
191+
subscribedToUpdates,
191192
avatar {
192193
mediaId,
193194
originalFileName,
@@ -217,7 +218,11 @@ export default function Page() {
217218
try {
218219
const response = await fetch.exec();
219220
if (response.user) {
220-
setProfile(response.user);
221+
setProfile((currentProfile: Partial<Profile> | null) => ({
222+
...(currentProfile || {}),
223+
...response.user,
224+
fetched: true,
225+
}));
221226
initialDetailsRef.current = {
222227
name,
223228
bio,

apps/web/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/types/routes.d.ts";
3+
import "./.next/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

0 commit comments

Comments
 (0)