Skip to content

Commit 9a72e99

Browse files
authored
feat: Add "Add Bookmark" title input and implement in server (#405)
* feat: Add "Add Bookmark" title input and implement in server * test: NewBookmarkCard: Add axios request assertion on submit * test: NewBookmarkCard: Group "Submit" test cases * test: NewBookmarkCard: Fix type errors * test: NewBookmarkCard: Add test for explicit title * test: NewBookmarkCard: Add missing request payload assertion * style: Fix formatting
1 parent 22ad51c commit 9a72e99

4 files changed

Lines changed: 205 additions & 145 deletions

File tree

frontend/__tests__/Bookmark/NewBookmarkCard.test.tsx

Lines changed: 174 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe("New Bookmark Card Renders", () => {
1717
<div className="col-6 col-sm-12 col-md-12 col-lg-4">
1818
<NewBookmarkCard />
1919
</div>
20-
</div>,
20+
</div>
2121
);
2222
});
2323

@@ -37,7 +37,7 @@ describe("Fields logic", () => {
3737
<div className="col-6 col-sm-12 col-md-12 col-lg-4">
3838
<NewBookmarkCard />
3939
</div>
40-
</div>,
40+
</div>
4141
);
4242
});
4343

@@ -46,74 +46,191 @@ describe("Fields logic", () => {
4646
expect(submit).toBeDisabled();
4747
});
4848

49-
it("All required fields are given data and submitted", async () => {
50-
const submit = screen.getByText("Submit");
51-
const tags = screen.getByPlaceholderText("Enter a tag");
52-
const url = screen.getByPlaceholderText(/discover/i);
53-
await user.type(url, "foodnetwork.com");
54-
await user.type(tags, "cooking");
55-
const toggle = screen.getByTestId("https://foodnetwork.com-scrapable-edit");
56-
await user.type(tags, "{enter}");
57-
await user.click(toggle);
58-
expect(submit).not.toBeDisabled();
49+
describe("Submit", () => {
50+
async function testSimpleSubmit(explicitTitle: string | false) {
51+
const submit = screen.getByText("Submit");
52+
const tags = screen.getByPlaceholderText("Enter a tag");
53+
const url = screen.getByPlaceholderText(/discover/i);
54+
const title = screen.getByPlaceholderText(/title:/);
55+
await user.type(url, "foodnetwork.com");
56+
if (explicitTitle) await user.type(title, explicitTitle);
57+
await user.type(tags, "cooking");
58+
const toggle = screen.getByTestId(
59+
"https://foodnetwork.com-scrapable-edit"
60+
);
61+
await user.type(tags, "{enter}");
62+
await user.click(toggle);
63+
expect(submit).not.toBeDisabled();
64+
65+
// Fields should be populated
66+
expect(url).toHaveValue("https://foodnetwork.com");
67+
68+
const axiosMock = new MockAdapter(instance);
69+
const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL;
70+
const tagsAPI = SERVER_URL + "/api/tags";
71+
const bookmarkAPI = SERVER_URL + "/api/bookmark";
72+
73+
const expectedResult: TagReqPayload[] = [
74+
{
75+
id: 1,
76+
title: "cooking",
77+
bookmarks: [],
78+
},
79+
];
5980

60-
// Fields should be populated
61-
expect(url).toHaveValue("https://foodnetwork.com");
81+
const expectedBookmark: Bookmark = {
82+
id: 1,
83+
title: explicitTitle || "foodnetwork.com",
84+
url: "foodnetwork.com",
85+
tags: [
86+
{
87+
id: 1,
88+
title: "cooking",
89+
},
90+
],
91+
screenshotUrl: "",
92+
scrapable: false,
93+
textHighlight: null,
94+
};
95+
96+
axiosMock.onPost(tagsAPI, ["cooking"]).reply(() => {
97+
return [200, JSON.stringify(expectedResult)];
98+
});
6299

63-
const axiosMock = new MockAdapter(instance);
64-
const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL;
65-
const tagsAPI = SERVER_URL + "/api/tags";
66-
const bookmarkAPI = SERVER_URL + "/api/bookmark";
100+
axiosMock
101+
.onPost(bookmarkAPI, {
102+
title: explicitTitle || "https://foodnetwork.com",
103+
url: "https://foodnetwork.com",
104+
tagIds: [1],
105+
})
106+
.reply(() => {
107+
return [200, JSON.stringify(expectedBookmark)];
108+
});
109+
110+
await user.click(submit);
111+
112+
// If submitted correctly, the fields should be reset.
113+
expect(url).toHaveValue("");
114+
expect(tags).toHaveValue("");
115+
expect(submit).toBeDisabled();
116+
expect(axiosMock.history).toHaveLength(2);
117+
// Verify the data sent in the requests
118+
expect(axiosMock.history[0].url).toEqual("tags");
119+
expect(axiosMock.history[0].method).toEqual("post");
120+
expect(JSON.parse(axiosMock.history[0].data)).toEqual(["cooking"]);
121+
expect(axiosMock.history[1].url).toEqual("bookmark");
122+
expect(axiosMock.history[1].method).toEqual("post");
123+
expect(JSON.parse(axiosMock.history[1].data)).toEqual({
124+
title: explicitTitle || "https://foodnetwork.com",
125+
url: "https://foodnetwork.com",
126+
tagIds: [1],
127+
scrapable: false,
128+
});
129+
}
67130

68-
const expectedResult: TagReqPayload[] = [
69-
{
70-
id: 1,
71-
title: "cooking",
72-
bookmarks: [],
73-
},
74-
];
131+
it("All required fields are given data and submitted", async () => {
132+
await testSimpleSubmit(false);
133+
});
75134

76-
const expectedBookmark: Bookmark = {
77-
id: 1,
78-
title: "foodnetwork.com",
79-
url: "foodnetwork.com",
80-
tags: [
135+
it("Title from input is submitted", async () => {
136+
await testSimpleSubmit("Custom Title");
137+
});
138+
139+
it("Field with unsubmitted tag", async () => {
140+
const submit = screen.getByText("Submit");
141+
const tags = screen.getByPlaceholderText("Enter a tag");
142+
const url = screen.getByPlaceholderText(/discover/i);
143+
const title = screen.getByPlaceholderText(/title:/i);
144+
await user.type(url, "foodnetwork.com");
145+
await user.type(tags, "cooking");
146+
await user.type(tags, "{enter}");
147+
hitEnter(tags);
148+
await user.clear(tags);
149+
await user.type(tags, "food");
150+
expect(submit).not.toBeDisabled();
151+
152+
// Fields should be populated
153+
expect(url).toHaveValue("https://foodnetwork.com");
154+
155+
const axiosMock = new MockAdapter(instance);
156+
const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL;
157+
const tagsAPI = SERVER_URL + "/api/tags";
158+
const bookmarkAPI = SERVER_URL + "/api/bookmark";
159+
160+
const expectedResult: TagReqPayload[] = [
81161
{
82162
id: 1,
83163
title: "cooking",
164+
bookmarks: [],
84165
},
85-
],
86-
screenshotUrl: "",
87-
scrapable: false,
88-
};
89-
90-
axiosMock.onPost(tagsAPI, ["cooking"]).reply(() => {
91-
return [200, JSON.stringify(expectedResult)];
92-
});
166+
{
167+
id: 2,
168+
title: "food",
169+
bookmarks: [],
170+
},
171+
];
93172

94-
axiosMock
95-
.onPost(bookmarkAPI, {
173+
const expectedBookmark: Bookmark = {
174+
id: 1,
96175
title: "https://foodnetwork.com",
97176
url: "https://foodnetwork.com",
98-
tagIds: [1],
99-
})
100-
.reply(() => {
101-
return [200, JSON.stringify(expectedBookmark)];
177+
tags: [
178+
{
179+
id: 1,
180+
title: "cooking",
181+
},
182+
{
183+
id: 2,
184+
title: "food",
185+
},
186+
],
187+
screenshotUrl: "",
188+
scrapable: false,
189+
textHighlight: null,
190+
};
191+
192+
axiosMock.onPost(tagsAPI, ["cooking", "food"]).reply(() => {
193+
return [200, JSON.stringify(expectedResult)];
102194
});
103195

104-
await user.click(submit);
105-
106-
// If submitted correctly, the fields should be reset.
107-
expect(url).toHaveValue("");
108-
expect(tags).toHaveValue("");
109-
expect(submit).toBeDisabled();
196+
axiosMock
197+
.onPost(bookmarkAPI, {
198+
title: "https://foodnetwork.com",
199+
url: "https://foodnetwork.com",
200+
tagIds: [1, 2],
201+
})
202+
.reply(() => {
203+
return [200, JSON.stringify(expectedBookmark)];
204+
});
205+
206+
await user.click(submit);
207+
208+
// If submitted correctly, the fields should be reset.
209+
expect(url).toHaveValue("");
210+
expect(title).toHaveValue("");
211+
expect(tags).toHaveValue("");
212+
expect(submit).toBeDisabled();
213+
214+
// Verify the data sent in the requests
215+
expect(axiosMock.history[0].url).toEqual("tags");
216+
expect(axiosMock.history[0].method).toEqual("post");
217+
expect(JSON.parse(axiosMock.history[0].data)).toEqual(["cooking", "food"]);
218+
expect(axiosMock.history[1].url).toEqual("bookmark");
219+
expect(axiosMock.history[1].method).toEqual("post");
220+
expect(JSON.parse(axiosMock.history[1].data)).toEqual({
221+
title: "https://foodnetwork.com",
222+
url: "https://foodnetwork.com",
223+
tagIds: [1, 2],
224+
scrapable: true,
225+
});
226+
});
110227
});
111228

112229
it("Valid Domains", async () => {
113230
async function checkDomain(
114231
url: string,
115232
submit: HTMLElement,
116-
isValid: boolean,
233+
isValid: boolean
117234
) {
118235
await user.type(screen.getByPlaceholderText(/discover/i), url);
119236
isValid ? expect(submit).toBeEnabled() : expect(submit).toBeDisabled();
@@ -150,87 +267,14 @@ describe("Fields logic", () => {
150267
"favs2",
151268
"misc",
152269
],
153-
user,
270+
user
154271
);
155272
await user.type(url, "foodnetwork.com");
156273
await user.click(reset);
157274
expect(url).toHaveValue("");
158275
expect(tags).toHaveValue("");
159276
expect(submit).toBeDisabled();
160277
});
161-
162-
it("Field with unsubmitted tag", async () => {
163-
const submit = screen.getByText("Submit");
164-
const tags = screen.getByPlaceholderText("Enter a tag");
165-
const url = screen.getByPlaceholderText(/discover/i);
166-
await user.type(url, "foodnetwork.com");
167-
await user.type(tags, "cooking");
168-
await user.type(tags, "{enter}");
169-
hitEnter(tags);
170-
await user.clear(tags);
171-
await user.type(tags, "food");
172-
expect(submit).not.toBeDisabled();
173-
174-
// Fields should be populated
175-
expect(url).toHaveValue("https://foodnetwork.com");
176-
177-
const axiosMock = new MockAdapter(instance);
178-
const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL;
179-
const tagsAPI = SERVER_URL + "/api/tags";
180-
const bookmarkAPI = SERVER_URL + "/api/bookmark";
181-
182-
const expectedResult: TagReqPayload[] = [
183-
{
184-
id: 1,
185-
title: "cooking",
186-
bookmarks: [],
187-
},
188-
{
189-
id: 2,
190-
title: "food",
191-
bookmarks: [],
192-
},
193-
];
194-
195-
const expectedBookmark: Bookmark = {
196-
id: 1,
197-
title: "https://foodnetwork.com",
198-
url: "https://foodnetwork.com",
199-
tags: [
200-
{
201-
id: 1,
202-
title: "cooking",
203-
},
204-
{
205-
id: 2,
206-
title: "food",
207-
},
208-
],
209-
screenshotUrl: "",
210-
scrapable: false,
211-
};
212-
213-
axiosMock.onPost(tagsAPI, ["cooking", "food"]).reply(() => {
214-
return [200, JSON.stringify(expectedResult)];
215-
});
216-
217-
axiosMock
218-
.onPost(bookmarkAPI, {
219-
title: "https://foodnetwork.com",
220-
url: "https://foodnetwork.com",
221-
tagIds: [1, 2],
222-
})
223-
.reply(() => {
224-
return [200, JSON.stringify(expectedBookmark)];
225-
});
226-
227-
await user.click(submit);
228-
229-
// If submitted correctly, the fields should be reset.
230-
expect(url).toHaveValue("");
231-
expect(tags).toHaveValue("");
232-
expect(submit).toBeDisabled();
233-
});
234278
});
235279

236280
describe("Tags Operations", () => {
@@ -240,7 +284,7 @@ describe("Tags Operations", () => {
240284
<div className="col-6 col-sm-12 col-md-12 col-lg-4">
241285
<NewBookmarkCard />
242286
</div>
243-
</div>,
287+
</div>
244288
);
245289
});
246290

@@ -257,7 +301,7 @@ describe("Tags Operations", () => {
257301
"favs",
258302
"misc",
259303
],
260-
user,
304+
user
261305
);
262306
expect(screen.getByText(/Too many tags/i)).toBeVisible();
263307
});
@@ -284,7 +328,7 @@ describe("Success Toast", () => {
284328
<div className="col-6 col-sm-12 col-md-12 col-lg-4">
285329
<NewBookmarkCard />
286330
</div>
287-
</div>,
331+
</div>
288332
);
289333

290334
const submit = screen.getByText("Submit");
@@ -325,6 +369,7 @@ describe("Success Toast", () => {
325369
],
326370
screenshotUrl: "",
327371
scrapable: true,
372+
textHighlight: null,
328373
};
329374

330375
axiosMock.onPost(tagsAPI).reply(() => {
@@ -346,7 +391,7 @@ describe("Success Toast", () => {
346391

347392
// Wait for the success toast to appear
348393
expect(
349-
await screen.findByText("Bookmark added successfully!"),
394+
await screen.findByText("Bookmark added successfully!")
350395
).toBeInTheDocument();
351396
});
352397
});

0 commit comments

Comments
 (0)