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

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 (53) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +6 -6
  3. package/package.json +8 -7
  4. package/src/migrations.ts +1 -34
  5. package/src/vendor/photon/index.js +4 -2
  6. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
  7. package/src/core/python-executor-display.test.ts +0 -42
  8. package/src/core/python-executor-lifecycle.test.ts +0 -99
  9. package/src/core/python-executor-mapping.test.ts +0 -41
  10. package/src/core/python-executor-per-call.test.ts +0 -49
  11. package/src/core/python-executor-session.test.ts +0 -103
  12. package/src/core/python-executor-streaming.test.ts +0 -77
  13. package/src/core/python-executor-timeout.test.ts +0 -35
  14. package/src/core/python-executor.lifecycle.test.ts +0 -139
  15. package/src/core/python-executor.result.test.ts +0 -49
  16. package/src/core/python-executor.test.ts +0 -180
  17. package/src/core/python-kernel-display.test.ts +0 -54
  18. package/src/core/python-kernel-env.test.ts +0 -138
  19. package/src/core/python-kernel-session.test.ts +0 -87
  20. package/src/core/python-kernel-ws.test.ts +0 -104
  21. package/src/core/python-kernel.lifecycle.test.ts +0 -249
  22. package/src/core/python-kernel.test.ts +0 -461
  23. package/src/core/python-modules.test.ts +0 -102
  24. package/src/core/python-prelude.test.ts +0 -140
  25. package/src/core/settings-manager-python.test.ts +0 -23
  26. package/src/core/streaming-output.test.ts +0 -26
  27. package/src/core/system-prompt.python.test.ts +0 -17
  28. package/src/core/tools/index.test.ts +0 -212
  29. package/src/core/tools/python-execution.test.ts +0 -68
  30. package/src/core/tools/python-fallback.test.ts +0 -72
  31. package/src/core/tools/python-renderer.test.ts +0 -36
  32. package/src/core/tools/python-tool-mode.test.ts +0 -43
  33. package/src/core/tools/python.test.ts +0 -121
  34. package/src/core/tools/schema-validation.test.ts +0 -530
  35. package/src/core/tools/web-scrapers/academic.test.ts +0 -239
  36. package/src/core/tools/web-scrapers/business.test.ts +0 -82
  37. package/src/core/tools/web-scrapers/dev-platforms.test.ts +0 -254
  38. package/src/core/tools/web-scrapers/documentation.test.ts +0 -85
  39. package/src/core/tools/web-scrapers/finance-media.test.ts +0 -144
  40. package/src/core/tools/web-scrapers/git-hosting.test.ts +0 -272
  41. package/src/core/tools/web-scrapers/media.test.ts +0 -138
  42. package/src/core/tools/web-scrapers/package-managers-2.test.ts +0 -199
  43. package/src/core/tools/web-scrapers/package-managers.test.ts +0 -171
  44. package/src/core/tools/web-scrapers/package-registries.test.ts +0 -259
  45. package/src/core/tools/web-scrapers/research.test.ts +0 -107
  46. package/src/core/tools/web-scrapers/security.test.ts +0 -103
  47. package/src/core/tools/web-scrapers/social-extended.test.ts +0 -192
  48. package/src/core/tools/web-scrapers/social.test.ts +0 -259
  49. package/src/core/tools/web-scrapers/stackexchange.test.ts +0 -120
  50. package/src/core/tools/web-scrapers/standards.test.ts +0 -122
  51. package/src/core/tools/web-scrapers/wikipedia.test.ts +0 -73
  52. package/src/core/tools/web-scrapers/youtube.test.ts +0 -198
  53. 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
- });