Skip to content

Commit 473109b

Browse files
authored
Merge pull request #37 from pheuberger/claude/add-unit-tests-9dnoM
Add comprehensive test coverage for bookmarks, crypto, and signaling
2 parents d9fd44c + 40beb02 commit 473109b

3 files changed

Lines changed: 1063 additions & 0 deletions

File tree

src/services/bookmarks.test.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,4 +309,184 @@ describe("bookmarks service", () => {
309309
expect(result.tags).toEqual(["c++", "c#", "node.js"]);
310310
});
311311
});
312+
313+
describe("inbox items", () => {
314+
it("allows empty title when inbox is true", () => {
315+
const result = validateBookmark({
316+
url: "https://example.com",
317+
title: "",
318+
inbox: true,
319+
});
320+
321+
expect(result.inbox).toBe(true);
322+
expect(result.title).toBe("");
323+
});
324+
325+
it("allows missing title when inbox is true", () => {
326+
const result = validateBookmark({
327+
url: "https://example.com",
328+
inbox: true,
329+
});
330+
331+
expect(result.inbox).toBe(true);
332+
expect(result.title).toBe("");
333+
});
334+
335+
it("still requires title when inbox is false", () => {
336+
expect(() =>
337+
validateBookmark({
338+
url: "https://example.com",
339+
inbox: false,
340+
})
341+
).toThrow("Title is required");
342+
});
343+
344+
it("still requires title when inbox is not set", () => {
345+
expect(() =>
346+
validateBookmark({
347+
url: "https://example.com",
348+
})
349+
).toThrow("Title is required");
350+
});
351+
352+
it("converts inbox to boolean", () => {
353+
const result1 = validateBookmark({
354+
url: "https://example.com",
355+
title: "Test",
356+
inbox: 1,
357+
});
358+
const result2 = validateBookmark({
359+
url: "https://example.com",
360+
title: "Test",
361+
inbox: 0,
362+
});
363+
const result3 = validateBookmark({
364+
url: "https://example.com",
365+
title: "Test",
366+
inbox: "yes",
367+
});
368+
369+
expect(result1.inbox).toBe(true);
370+
expect(result2.inbox).toBe(false);
371+
expect(result3.inbox).toBe(true);
372+
});
373+
374+
it("still validates URL for inbox items", () => {
375+
expect(() =>
376+
validateBookmark({
377+
url: "not a valid url",
378+
inbox: true,
379+
})
380+
).toThrow("Invalid URL format");
381+
});
382+
});
383+
384+
describe("favicon and preview fields", () => {
385+
it("passes through favicon value", () => {
386+
const result = validateBookmark({
387+
url: "https://example.com",
388+
title: "Test",
389+
favicon: "https://example.com/favicon.ico",
390+
});
391+
392+
expect(result.favicon).toBe("https://example.com/favicon.ico");
393+
});
394+
395+
it("defaults favicon to null", () => {
396+
const result = validateBookmark({
397+
url: "https://example.com",
398+
title: "Test",
399+
});
400+
401+
expect(result.favicon).toBeNull();
402+
});
403+
404+
it("passes through preview value", () => {
405+
const result = validateBookmark({
406+
url: "https://example.com",
407+
title: "Test",
408+
preview: "https://example.com/preview.png",
409+
});
410+
411+
expect(result.preview).toBe("https://example.com/preview.png");
412+
});
413+
414+
it("defaults preview to null", () => {
415+
const result = validateBookmark({
416+
url: "https://example.com",
417+
title: "Test",
418+
});
419+
420+
expect(result.preview).toBeNull();
421+
});
422+
});
423+
424+
describe("tag edge cases", () => {
425+
it("handles non-string values in tags array", () => {
426+
const result = validateBookmark({
427+
url: "https://example.com",
428+
title: "Test",
429+
tags: [123, null, undefined, "valid"],
430+
});
431+
432+
// Non-string tags should be converted to empty string and filtered
433+
expect(result.tags).toContain("valid");
434+
expect(result.tags.length).toBe(1);
435+
});
436+
437+
it("handles non-array tags gracefully", () => {
438+
const result = validateBookmark({
439+
url: "https://example.com",
440+
title: "Test",
441+
tags: "not-an-array",
442+
});
443+
444+
expect(result.tags).toEqual([]);
445+
});
446+
447+
it("trims and lowercases each tag", () => {
448+
const result = validateBookmark({
449+
url: "https://example.com",
450+
title: "Test",
451+
tags: [" JavaScript ", "REACT", " node.JS "],
452+
});
453+
454+
expect(result.tags).toEqual(["javascript", "react", "node.js"]);
455+
});
456+
457+
it("deduplication is not enforced at validation level", () => {
458+
const result = validateBookmark({
459+
url: "https://example.com",
460+
title: "Test",
461+
tags: ["dup", "dup", "dup"],
462+
});
463+
464+
// validateBookmark does not deduplicate - it normalizes
465+
expect(result.tags).toEqual(["dup", "dup", "dup"]);
466+
});
467+
});
468+
469+
describe("normalizeUrl edge cases", () => {
470+
it("handles URLs with encoded characters", () => {
471+
const result = normalizeUrl("https://example.com/path%20with%20spaces");
472+
expect(result).toContain("path%20with%20spaces");
473+
});
474+
475+
it("handles URLs with multiple query parameters of same key", () => {
476+
const result = normalizeUrl("https://example.com?a=1&a=2");
477+
expect(result).toContain("a=1&a=2");
478+
});
479+
480+
it("handles internationalized domain names", () => {
481+
// punycode-encoded domains should work
482+
const result = normalizeUrl("https://xn--n3h.example.com");
483+
expect(result).toContain("xn--n3h.example.com");
484+
});
485+
486+
it("handles very long paths", () => {
487+
const longPath = "/a".repeat(500);
488+
const result = normalizeUrl(`https://example.com${longPath}`);
489+
expect(result).toContain(longPath);
490+
});
491+
});
312492
});

0 commit comments

Comments
 (0)