@oh-my-pi/pi-coding-agent 5.7.67 → 5.7.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/package.json +8 -7
  3. package/src/migrations.ts +1 -34
  4. package/src/vendor/photon/index.js +4 -2
  5. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
  6. package/src/core/python-executor-display.test.ts +0 -42
  7. package/src/core/python-executor-lifecycle.test.ts +0 -99
  8. package/src/core/python-executor-mapping.test.ts +0 -41
  9. package/src/core/python-executor-per-call.test.ts +0 -49
  10. package/src/core/python-executor-session.test.ts +0 -103
  11. package/src/core/python-executor-streaming.test.ts +0 -77
  12. package/src/core/python-executor-timeout.test.ts +0 -35
  13. package/src/core/python-executor.lifecycle.test.ts +0 -139
  14. package/src/core/python-executor.result.test.ts +0 -49
  15. package/src/core/python-executor.test.ts +0 -180
  16. package/src/core/python-kernel-display.test.ts +0 -54
  17. package/src/core/python-kernel-env.test.ts +0 -138
  18. package/src/core/python-kernel-session.test.ts +0 -87
  19. package/src/core/python-kernel-ws.test.ts +0 -104
  20. package/src/core/python-kernel.lifecycle.test.ts +0 -249
  21. package/src/core/python-kernel.test.ts +0 -461
  22. package/src/core/python-modules.test.ts +0 -102
  23. package/src/core/python-prelude.test.ts +0 -140
  24. package/src/core/settings-manager-python.test.ts +0 -23
  25. package/src/core/streaming-output.test.ts +0 -26
  26. package/src/core/system-prompt.python.test.ts +0 -17
  27. package/src/core/tools/index.test.ts +0 -212
  28. package/src/core/tools/python-execution.test.ts +0 -68
  29. package/src/core/tools/python-fallback.test.ts +0 -72
  30. package/src/core/tools/python-renderer.test.ts +0 -36
  31. package/src/core/tools/python-tool-mode.test.ts +0 -43
  32. package/src/core/tools/python.test.ts +0 -121
  33. package/src/core/tools/schema-validation.test.ts +0 -530
  34. package/src/core/tools/web-scrapers/academic.test.ts +0 -239
  35. package/src/core/tools/web-scrapers/business.test.ts +0 -82
  36. package/src/core/tools/web-scrapers/dev-platforms.test.ts +0 -254
  37. package/src/core/tools/web-scrapers/documentation.test.ts +0 -85
  38. package/src/core/tools/web-scrapers/finance-media.test.ts +0 -144
  39. package/src/core/tools/web-scrapers/git-hosting.test.ts +0 -272
  40. package/src/core/tools/web-scrapers/media.test.ts +0 -138
  41. package/src/core/tools/web-scrapers/package-managers-2.test.ts +0 -199
  42. package/src/core/tools/web-scrapers/package-managers.test.ts +0 -171
  43. package/src/core/tools/web-scrapers/package-registries.test.ts +0 -259
  44. package/src/core/tools/web-scrapers/research.test.ts +0 -107
  45. package/src/core/tools/web-scrapers/security.test.ts +0 -103
  46. package/src/core/tools/web-scrapers/social-extended.test.ts +0 -192
  47. package/src/core/tools/web-scrapers/social.test.ts +0 -259
  48. package/src/core/tools/web-scrapers/stackexchange.test.ts +0 -120
  49. package/src/core/tools/web-scrapers/standards.test.ts +0 -122
  50. package/src/core/tools/web-scrapers/wikipedia.test.ts +0 -73
  51. package/src/core/tools/web-scrapers/youtube.test.ts +0 -198
  52. package/src/discovery/helpers.test.ts +0 -131
@@ -1,120 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleStackOverflow } from "./stackoverflow";
3
-
4
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
5
-
6
- describe.skipIf(SKIP)("handleStackOverflow", () => {
7
- it("returns null for non-SE URLs", async () => {
8
- const result = await handleStackOverflow("https://example.com", 20);
9
- expect(result).toBeNull();
10
- });
11
-
12
- it("returns null for SE site without question path", async () => {
13
- const result = await handleStackOverflow("https://stackoverflow.com/tags", 20);
14
- expect(result).toBeNull();
15
- });
16
-
17
- it("returns null for SE user profile URLs", async () => {
18
- const result = await handleStackOverflow("https://stackoverflow.com/users/1", 20);
19
- expect(result).toBeNull();
20
- });
21
-
22
- // stackoverflow.com - "What is a NullPointerException" (classic, highly voted)
23
- it("fetches stackoverflow.com question", async () => {
24
- const result = await handleStackOverflow(
25
- "https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it",
26
- 20,
27
- );
28
- expect(result).not.toBeNull();
29
- expect(result?.method).toBe("stackexchange");
30
- expect(result?.content).toContain("NullPointerException");
31
- expect(result?.contentType).toBe("text/markdown");
32
- expect(result?.fetchedAt).toBeTruthy();
33
- expect(result?.truncated).toBeDefined();
34
- expect(result?.notes?.[0]).toContain("site=stackoverflow");
35
- });
36
-
37
- // unix.stackexchange.com - "Why does my shell script choke on whitespace" (classic)
38
- it("fetches unix.stackexchange.com question", async () => {
39
- const result = await handleStackOverflow(
40
- "https://unix.stackexchange.com/questions/131766/why-does-my-shell-script-choke-on-whitespace-or-other-special-characters",
41
- 20,
42
- );
43
- expect(result).not.toBeNull();
44
- expect(result?.method).toBe("stackexchange");
45
- expect(result?.content).toContain("whitespace");
46
- expect(result?.contentType).toBe("text/markdown");
47
- expect(result?.fetchedAt).toBeTruthy();
48
- expect(result?.notes?.[0]).toContain("site=unix");
49
- });
50
-
51
- // superuser.com - "What are PATH and other environment variables" (stable)
52
- it("fetches superuser.com question", async () => {
53
- const result = await handleStackOverflow(
54
- "https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them",
55
- 20,
56
- );
57
- expect(result).not.toBeNull();
58
- expect(result?.method).toBe("stackexchange");
59
- expect(result?.content).toContain("PATH");
60
- expect(result?.contentType).toBe("text/markdown");
61
- expect(result?.fetchedAt).toBeTruthy();
62
- expect(result?.notes?.[0]).toContain("site=superuser");
63
- });
64
-
65
- // askubuntu.com - "What is the difference between apt and apt-get" (iconic)
66
- it("fetches askubuntu.com question", async () => {
67
- const result = await handleStackOverflow(
68
- "https://askubuntu.com/questions/445384/what-is-the-difference-between-apt-and-apt-get",
69
- 20,
70
- );
71
- expect(result).not.toBeNull();
72
- expect(result?.method).toBe("stackexchange");
73
- expect(result?.content).toContain("apt");
74
- expect(result?.contentType).toBe("text/markdown");
75
- expect(result?.fetchedAt).toBeTruthy();
76
- expect(result?.notes?.[0]).toContain("site=askubuntu");
77
- });
78
-
79
- // serverfault.com - "What is a reverse proxy" (stable sysadmin topic)
80
- it("fetches serverfault.com question", async () => {
81
- const result = await handleStackOverflow(
82
- "https://serverfault.com/questions/127021/what-is-the-difference-between-a-proxy-and-a-reverse-proxy",
83
- 20,
84
- );
85
- expect(result).not.toBeNull();
86
- expect(result?.method).toBe("stackexchange");
87
- expect(result?.content).toMatch(/proxy/i);
88
- expect(result?.contentType).toBe("text/markdown");
89
- expect(result?.fetchedAt).toBeTruthy();
90
- expect(result?.notes?.[0]).toContain("site=serverfault");
91
- });
92
-
93
- // Test with www. prefix
94
- it("handles www.stackoverflow.com URLs", async () => {
95
- const result = await handleStackOverflow(
96
- "https://www.stackoverflow.com/questions/218384/what-is-a-nullpointerexception",
97
- 20,
98
- );
99
- expect(result).not.toBeNull();
100
- expect(result?.method).toBe("stackexchange");
101
- });
102
-
103
- // Verify response structure
104
- it("returns complete response structure", async () => {
105
- const result = await handleStackOverflow("https://stackoverflow.com/questions/218384", 20);
106
- expect(result).not.toBeNull();
107
- expect(result).toHaveProperty("url");
108
- expect(result).toHaveProperty("finalUrl");
109
- expect(result).toHaveProperty("contentType", "text/markdown");
110
- expect(result).toHaveProperty("method", "stackexchange");
111
- expect(result).toHaveProperty("content");
112
- expect(result).toHaveProperty("fetchedAt");
113
- expect(result).toHaveProperty("truncated");
114
- expect(result).toHaveProperty("notes");
115
- // Content should have question structure
116
- expect(result?.content).toContain("# ");
117
- expect(result?.content).toContain("Score:");
118
- expect(result?.content).toContain("Tags:");
119
- });
120
- });
@@ -1,122 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleCheatSh } from "./cheatsh";
3
- import { handleRfc } from "./rfc";
4
- import { handleTldr } from "./tldr";
5
-
6
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
7
-
8
- describe.skipIf(SKIP)("handleRfc", () => {
9
- it("returns null for non-RFC URLs", async () => {
10
- const result = await handleRfc("https://example.com", 20);
11
- expect(result).toBeNull();
12
- });
13
-
14
- it("returns null for non-matching RFC domains", async () => {
15
- const result = await handleRfc("https://www.ietf.org/about/", 20);
16
- expect(result).toBeNull();
17
- });
18
-
19
- it("fetches RFC 2616 (HTTP/1.1)", async () => {
20
- const result = await handleRfc("https://www.rfc-editor.org/rfc/rfc2616", 20);
21
- expect(result).not.toBeNull();
22
- expect(result?.method).toBe("rfc");
23
- expect(result?.content).toContain("HTTP/1.1");
24
- expect(result?.content).toContain("Hypertext Transfer Protocol");
25
- expect(result?.contentType).toBe("text/markdown");
26
- expect(result?.fetchedAt).toBeTruthy();
27
- expect(result?.truncated).toBeDefined();
28
- });
29
-
30
- it("fetches RFC 2616 via datatracker URL", async () => {
31
- const result = await handleRfc("https://datatracker.ietf.org/doc/rfc2616/", 20);
32
- expect(result).not.toBeNull();
33
- expect(result?.method).toBe("rfc");
34
- expect(result?.content).toContain("HTTP/1.1");
35
- });
36
-
37
- it("fetches RFC 2616 via tools.ietf.org URL", async () => {
38
- const result = await handleRfc("https://tools.ietf.org/html/rfc2616", 20);
39
- expect(result).not.toBeNull();
40
- expect(result?.method).toBe("rfc");
41
- expect(result?.content).toContain("HTTP/1.1");
42
- });
43
-
44
- it("fetches RFC 793 (TCP)", async () => {
45
- const result = await handleRfc("https://www.rfc-editor.org/rfc/rfc793", 20);
46
- expect(result).not.toBeNull();
47
- expect(result?.method).toBe("rfc");
48
- expect(result?.content).toContain("Transmission Control Protocol");
49
- });
50
- });
51
-
52
- describe.skipIf(SKIP)("handleCheatSh", () => {
53
- it("returns null for non-cheat.sh URLs", async () => {
54
- const result = await handleCheatSh("https://example.com", 20);
55
- expect(result).toBeNull();
56
- });
57
-
58
- it("returns null for empty topic", async () => {
59
- const result = await handleCheatSh("https://cheat.sh/", 20);
60
- expect(result).toBeNull();
61
- });
62
-
63
- it("fetches curl cheatsheet", async () => {
64
- const result = await handleCheatSh("https://cheat.sh/curl", 20);
65
- expect(result).not.toBeNull();
66
- expect(result?.method).toBe("cheat.sh");
67
- expect(result?.content).toContain("curl");
68
- expect(result?.contentType).toBe("text/markdown");
69
- expect(result?.fetchedAt).toBeTruthy();
70
- expect(result?.truncated).toBeDefined();
71
- });
72
-
73
- it("fetches tar cheatsheet", async () => {
74
- const result = await handleCheatSh("https://cheat.sh/tar", 20);
75
- expect(result).not.toBeNull();
76
- expect(result?.method).toBe("cheat.sh");
77
- expect(result?.content).toContain("tar");
78
- });
79
-
80
- it("fetches cheatsheet via cht.sh alias", async () => {
81
- const result = await handleCheatSh("https://cht.sh/curl", 20);
82
- expect(result).not.toBeNull();
83
- expect(result?.method).toBe("cheat.sh");
84
- expect(result?.content).toContain("curl");
85
- });
86
- });
87
-
88
- describe.skipIf(SKIP)("handleTldr", () => {
89
- it("returns null for non-tldr URLs", async () => {
90
- const result = await handleTldr("https://example.com", 20);
91
- expect(result).toBeNull();
92
- });
93
-
94
- it("returns null for nested paths", async () => {
95
- const result = await handleTldr("https://tldr.sh/nested/path", 20);
96
- expect(result).toBeNull();
97
- });
98
-
99
- it("fetches git tldr page", async () => {
100
- const result = await handleTldr("https://tldr.sh/git", 20);
101
- expect(result).not.toBeNull();
102
- expect(result?.method).toBe("tldr");
103
- expect(result?.content).toContain("git");
104
- expect(result?.contentType).toBe("text/markdown");
105
- expect(result?.fetchedAt).toBeTruthy();
106
- expect(result?.truncated).toBeDefined();
107
- });
108
-
109
- it("fetches curl tldr page", async () => {
110
- const result = await handleTldr("https://tldr.sh/curl", 20);
111
- expect(result).not.toBeNull();
112
- expect(result?.method).toBe("tldr");
113
- expect(result?.content).toContain("curl");
114
- });
115
-
116
- it("fetches via tldr.ostera.io alias", async () => {
117
- const result = await handleTldr("https://tldr.ostera.io/git", 20);
118
- expect(result).not.toBeNull();
119
- expect(result?.method).toBe("tldr");
120
- expect(result?.content).toContain("git");
121
- });
122
- });
@@ -1,73 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleWikipedia } from "./wikipedia";
3
-
4
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
5
-
6
- describe.skipIf(SKIP)("handleWikipedia", () => {
7
- it("returns null for non-Wikipedia URLs", async () => {
8
- const result = await handleWikipedia("https://example.com", 10);
9
- expect(result).toBeNull();
10
- });
11
-
12
- it("returns null for Wikipedia URLs without /wiki/ path", async () => {
13
- const result = await handleWikipedia("https://en.wikipedia.org/", 10);
14
- expect(result).toBeNull();
15
- });
16
-
17
- it("fetches a known article with full metadata", async () => {
18
- // "Computer" is a stable, well-established article
19
- const result = await handleWikipedia("https://en.wikipedia.org/wiki/Computer", 20);
20
- expect(result).not.toBeNull();
21
- expect(result?.method).toBe("wikipedia");
22
- expect(result?.contentType).toBe("text/markdown");
23
- expect(result?.content).toContain("Computer");
24
- expect(result?.url).toBe("https://en.wikipedia.org/wiki/Computer");
25
- expect(result?.finalUrl).toBe("https://en.wikipedia.org/wiki/Computer");
26
- expect(result?.truncated).toBe(false);
27
- expect(result?.notes).toContain("Fetched via Wikipedia API");
28
- expect(result?.fetchedAt).toBeDefined();
29
- // Should be a valid ISO timestamp
30
- expect(() => new Date(result?.fetchedAt ?? "")).not.toThrow();
31
- // The handler should filter out References and External links sections
32
- const content = result?.content ?? "";
33
- const hasReferencesHeading = /^## References$/m.test(content);
34
- const hasExternalLinksHeading = /^## External links$/m.test(content);
35
- // At least one of these should be filtered out
36
- expect(hasReferencesHeading || hasExternalLinksHeading).toBe(false);
37
- });
38
-
39
- it("handles different language wikis", async () => {
40
- // German Wikipedia article for "Computer"
41
- const result = await handleWikipedia("https://de.wikipedia.org/wiki/Computer", 20);
42
- expect(result).not.toBeNull();
43
- expect(result?.method).toBe("wikipedia");
44
- expect(result?.contentType).toBe("text/markdown");
45
- expect(result?.content).toContain("Computer");
46
- });
47
-
48
- it("handles article with special characters in title", async () => {
49
- // Article with special characters: "C++"
50
- const result = await handleWikipedia("https://en.wikipedia.org/wiki/C%2B%2B", 20);
51
- expect(result).not.toBeNull();
52
- expect(result?.method).toBe("wikipedia");
53
- expect(result?.contentType).toBe("text/markdown");
54
- expect(result?.content).toMatch(/C\+\+/);
55
- });
56
-
57
- it("handles article with spaces and parentheses in title", async () => {
58
- // Artificial intelligence uses underscores for spaces
59
- const result = await handleWikipedia("https://en.wikipedia.org/wiki/Artificial_intelligence", 20);
60
- expect(result).not.toBeNull();
61
- expect(result?.method).toBe("wikipedia");
62
- expect(result?.contentType).toBe("text/markdown");
63
- expect(result?.content).toMatch(/[Aa]rtificial intelligence/);
64
- });
65
-
66
- it("handles non-existent articles gracefully", async () => {
67
- const result = await handleWikipedia(
68
- "https://en.wikipedia.org/wiki/ThisArticleDefinitelyDoesNotExist123456789",
69
- 20,
70
- );
71
- expect(result).toBeNull();
72
- });
73
- });
@@ -1,198 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleYouTube } from "./youtube";
3
-
4
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
5
-
6
- describe.skipIf(SKIP)("handleYouTube", () => {
7
- it("returns null for non-YouTube URLs", async () => {
8
- const result = await handleYouTube("https://example.com", 10);
9
- expect(result).toBeNull();
10
- });
11
-
12
- it("returns null for invalid YouTube URLs", async () => {
13
- const result = await handleYouTube("https://youtube.com/invalid", 10);
14
- expect(result).toBeNull();
15
- });
16
-
17
- it("handles youtube.com/watch?v= format", async () => {
18
- // Use Rick Astley's "Never Gonna Give You Up" - a stable, well-known video
19
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
20
- expect(result).not.toBeNull();
21
- expect(result?.method).toMatch(/^youtube/);
22
- expect(result?.contentType).toBe("text/markdown");
23
- expect(result?.content).toContain("Video ID");
24
- expect(result?.content).toContain("dQw4w9WgXcQ");
25
- }, 30000);
26
-
27
- it("handles youtu.be/ short format", async () => {
28
- const result = await handleYouTube("https://youtu.be/dQw4w9WgXcQ", 30);
29
- expect(result).not.toBeNull();
30
- expect(result?.method).toMatch(/^youtube/);
31
- expect(result?.content).toContain("dQw4w9WgXcQ");
32
- }, 30000);
33
-
34
- it("handles youtube.com/shorts/ format", async () => {
35
- // Use a stable YouTube Shorts video
36
- const result = await handleYouTube("https://www.youtube.com/shorts/jNQXAC9IVRw", 30);
37
- expect(result).not.toBeNull();
38
- expect(result?.method).toMatch(/^youtube/);
39
- expect(result?.content).toContain("jNQXAC9IVRw");
40
- }, 30000);
41
-
42
- it("handles youtube.com/embed/ format", async () => {
43
- const result = await handleYouTube("https://www.youtube.com/embed/dQw4w9WgXcQ", 30);
44
- expect(result).not.toBeNull();
45
- expect(result?.method).toMatch(/^youtube/);
46
- expect(result?.content).toContain("dQw4w9WgXcQ");
47
- }, 30000);
48
-
49
- it("handles youtube.com/v/ format", async () => {
50
- const result = await handleYouTube("https://www.youtube.com/v/dQw4w9WgXcQ", 30);
51
- expect(result).not.toBeNull();
52
- expect(result?.method).toMatch(/^youtube/);
53
- expect(result?.content).toContain("dQw4w9WgXcQ");
54
- }, 30000);
55
-
56
- it("handles m.youtube.com mobile URLs", async () => {
57
- const result = await handleYouTube("https://m.youtube.com/watch?v=dQw4w9WgXcQ", 30);
58
- expect(result).not.toBeNull();
59
- expect(result?.method).toMatch(/^youtube/);
60
- expect(result?.content).toContain("dQw4w9WgXcQ");
61
- }, 30000);
62
-
63
- it("extracts video metadata when yt-dlp is available", async () => {
64
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
65
- expect(result).not.toBeNull();
66
-
67
- // If yt-dlp is available, should have metadata
68
- if (result?.method === "youtube") {
69
- expect(result.content).toContain("Video ID");
70
- expect(result.content).toContain("Channel");
71
- // May have duration, views, upload date, etc.
72
- }
73
-
74
- // If yt-dlp is not available, should indicate that
75
- if (result?.method === "youtube-no-ytdlp") {
76
- expect(result.content).toContain("yt-dlp could not be installed");
77
- expect(result.notes).toContain("yt-dlp installation failed");
78
- }
79
- }, 30000);
80
-
81
- it("handles videos with transcripts gracefully", async () => {
82
- // This video should have captions
83
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
84
- expect(result).not.toBeNull();
85
-
86
- if (result?.method === "youtube") {
87
- // Either has transcript or explicitly notes it's not available
88
- const hasTranscript = result.content.includes("Transcript");
89
- const noTranscriptNote = result.content.includes("No transcript available");
90
- expect(hasTranscript || noTranscriptNote).toBe(true);
91
- }
92
- }, 30000);
93
-
94
- it("handles videos without transcripts gracefully", async () => {
95
- // Many music videos lack captions, but this is not guaranteed
96
- // Just verify the handler doesn't crash and provides some info
97
- const result = await handleYouTube("https://www.youtube.com/watch?v=kJQP7kiw5Fk", 30);
98
- expect(result).not.toBeNull();
99
-
100
- if (result?.method === "youtube") {
101
- // Should still have basic metadata
102
- expect(result.content).toContain("Video ID");
103
- }
104
- }, 30000);
105
-
106
- it("returns appropriate response when yt-dlp is not available", async () => {
107
- // We can't force yt-dlp to be unavailable in tests, but we can verify
108
- // the return structure matches expectations for both cases
109
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
110
- expect(result).not.toBeNull();
111
-
112
- // Should have one of these two methods
113
- expect(["youtube", "youtube-no-ytdlp"]).toContain(result!.method);
114
-
115
- // Both should have required fields
116
- expect(result?.url).toBe("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
117
- expect(result?.finalUrl).toContain("youtube.com");
118
- expect(result?.fetchedAt).toBeTruthy();
119
- expect(typeof result?.truncated).toBe("boolean");
120
- expect(Array.isArray(result?.notes)).toBe(true);
121
- }, 30000);
122
-
123
- it("normalizes video URLs to canonical format", async () => {
124
- // Different input formats should normalize to same canonical URL
125
- const result = await handleYouTube("https://youtu.be/dQw4w9WgXcQ", 30);
126
- expect(result).not.toBeNull();
127
- expect(result?.finalUrl).toBe("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
128
- }, 30000);
129
-
130
- it("handles playlist URLs by extracting video ID", async () => {
131
- const result = await handleYouTube(
132
- "https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf",
133
- 30,
134
- );
135
- expect(result).not.toBeNull();
136
- expect(result?.content).toContain("dQw4w9WgXcQ");
137
- }, 30000);
138
-
139
- it("includes subtitle source information when available", async () => {
140
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
141
-
142
- if (result?.method === "youtube") {
143
- // If transcript is present, should note the source
144
- const hasManualNote = result.notes.includes("Using manual subtitles");
145
- const hasAutoNote = result.notes.includes("Using auto-generated captions");
146
- const hasNoSubsNote = result.notes.includes("No subtitles/captions available");
147
-
148
- // Should have exactly one of these
149
- const noteCount = [hasManualNote, hasAutoNote, hasNoSubsNote].filter(Boolean).length;
150
- expect(noteCount).toBeGreaterThanOrEqual(1);
151
- }
152
- }, 30000);
153
-
154
- it("formats duration in human readable format", async () => {
155
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
156
-
157
- if (result?.method === "youtube" && result.content.includes("Duration")) {
158
- // Should have duration in M:SS or H:MM:SS format
159
- expect(result.content).toMatch(/Duration.*\d+:\d{2}/);
160
- }
161
- }, 30000);
162
-
163
- it("formats view count in readable format", async () => {
164
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
165
-
166
- if (result?.method === "youtube" && result.content.includes("Views")) {
167
- // Should have views formatted (e.g., 1.5B, 100M, 10.5K)
168
- expect(result.content).toMatch(/Views.*\d+(\.\d+)?[KM]?/);
169
- }
170
- }, 30000);
171
-
172
- it("includes upload date when available", async () => {
173
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
174
-
175
- if (result?.method === "youtube" && result.content.includes("Uploaded")) {
176
- // Should have date in YYYY-MM-DD format
177
- expect(result.content).toMatch(/Uploaded.*\d{4}-\d{2}-\d{2}/);
178
- }
179
- }, 30000);
180
-
181
- it("truncates long descriptions", async () => {
182
- const result = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
183
-
184
- if (result?.method === "youtube" && result.content.includes("Description")) {
185
- // Description section should exist
186
- expect(result.content).toContain("## Description");
187
- }
188
- }, 30000);
189
-
190
- it("handles www prefix variations", async () => {
191
- const withWww = await handleYouTube("https://www.youtube.com/watch?v=dQw4w9WgXcQ", 30);
192
- const withoutWww = await handleYouTube("https://youtube.com/watch?v=dQw4w9WgXcQ", 30);
193
-
194
- expect(withWww).not.toBeNull();
195
- expect(withoutWww).not.toBeNull();
196
- expect(withWww?.finalUrl).toBe(withoutWww?.finalUrl);
197
- }, 30000);
198
- });
@@ -1,131 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { parseFrontmatter } from "../core/frontmatter";
3
-
4
- describe("parseFrontmatter", () => {
5
- const parse = (content: string) => parseFrontmatter(content, { source: "tests:frontmatter", level: "off" });
6
-
7
- test("parses simple key-value pairs", () => {
8
- const content = `---
9
- name: test
10
- enabled: true
11
- ---
12
- Body content`;
13
-
14
- const result = parse(content);
15
- expect(result.frontmatter).toEqual({ name: "test", enabled: true });
16
- expect(result.body).toBe("Body content");
17
- });
18
-
19
- test("parses YAML list syntax", () => {
20
- const content = `---
21
- tags:
22
- - javascript
23
- - typescript
24
- - react
25
- ---
26
- Body content`;
27
-
28
- const result = parse(content);
29
- expect(result.frontmatter).toEqual({
30
- tags: ["javascript", "typescript", "react"],
31
- });
32
- expect(result.body).toBe("Body content");
33
- });
34
-
35
- test("parses multi-line string values", () => {
36
- const content = `---
37
- description: |
38
- This is a multi-line
39
- description block
40
- with several lines
41
- ---
42
- Body content`;
43
-
44
- const result = parse(content);
45
- expect(result.frontmatter).toEqual({
46
- description: "This is a multi-line\ndescription block\nwith several lines\n",
47
- });
48
- expect(result.body).toBe("Body content");
49
- });
50
-
51
- test("parses nested objects", () => {
52
- const content = `---
53
- config:
54
- server:
55
- port: 3000
56
- host: localhost
57
- database:
58
- name: mydb
59
- ---
60
- Body content`;
61
-
62
- const result = parse(content);
63
- expect(result.frontmatter).toEqual({
64
- config: {
65
- server: { port: 3000, host: "localhost" },
66
- database: { name: "mydb" },
67
- },
68
- });
69
- expect(result.body).toBe("Body content");
70
- });
71
-
72
- test("parses mixed complex YAML", () => {
73
- const content = `---
74
- name: complex-test
75
- version: 1.0.0
76
- tags:
77
- - prod
78
- - critical
79
- metadata:
80
- author: tester
81
- created: 2024-01-01
82
- description: |
83
- Multi-line description
84
- with formatting
85
- ---
86
- Body content`;
87
-
88
- const result = parse(content);
89
- expect(result.frontmatter).toEqual({
90
- name: "complex-test",
91
- version: "1.0.0",
92
- tags: ["prod", "critical"],
93
- metadata: {
94
- author: "tester",
95
- created: "2024-01-01",
96
- },
97
- description: "Multi-line description\nwith formatting\n",
98
- });
99
- expect(result.body).toBe("Body content");
100
- });
101
-
102
- test("handles missing frontmatter", () => {
103
- const content = "Just body content";
104
- const result = parse(content);
105
- expect(result.frontmatter).toEqual({});
106
- expect(result.body).toBe("Just body content");
107
- });
108
-
109
- test("handles invalid YAML in frontmatter", () => {
110
- const content = `---
111
- invalid: [unclosed array
112
- ---
113
- Body content`;
114
-
115
- const result = parse(content);
116
- // Simple fallback parser extracts key:value pairs it can parse
117
- expect(result.frontmatter).toEqual({ invalid: "[unclosed array" });
118
- // Body is still extracted even with invalid YAML
119
- expect(result.body).toBe("Body content");
120
- });
121
-
122
- test("handles empty frontmatter", () => {
123
- const content = `---
124
- ---
125
- Body content`;
126
-
127
- const result = parse(content);
128
- expect(result.frontmatter).toEqual({});
129
- expect(result.body).toBe("Body content");
130
- });
131
- });