A CLI for publishing standard.site documents to ATProto
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 213 lines 6.4 kB view raw
1import { describe, expect, test } from "bun:test"; 2import { resolveInternalLinks, findPostsWithStaleLinks } from "./remanso"; 3import type { BlogPost } from "../lib/types"; 4 5function makePost( 6 slug: string, 7 atUri?: string, 8 options?: { content?: string; draft?: boolean; filePath?: string }, 9): BlogPost { 10 return { 11 filePath: options?.filePath ?? `content/${slug}.md`, 12 slug, 13 frontmatter: { 14 title: slug, 15 publishDate: "2024-01-01", 16 atUri, 17 draft: options?.draft, 18 }, 19 content: options?.content ?? "", 20 rawContent: "", 21 rawFrontmatter: {}, 22 }; 23} 24 25describe("resolveInternalLinks", () => { 26 test("strips link for unpublished local path", () => { 27 const posts = [makePost("other-post")]; 28 const content = "See [my post](./other-post)"; 29 expect(resolveInternalLinks(content, posts)).toBe("See my post"); 30 }); 31 32 test("rewrites published link to remanso atUri", () => { 33 const posts = [ 34 makePost("other-post", "at://did:plc:abc/site.standard.document/abc123"), 35 ]; 36 const content = "See [my post](./other-post)"; 37 expect(resolveInternalLinks(content, posts)).toBe( 38 "See [my post](at://did:plc:abc/space.remanso.note/abc123)", 39 ); 40 }); 41 42 test("leaves external links unchanged", () => { 43 const posts = [makePost("other-post")]; 44 const content = "See [example](https://example.com)"; 45 expect(resolveInternalLinks(content, posts)).toBe( 46 "See [example](https://example.com)", 47 ); 48 }); 49 50 test("leaves anchor links unchanged", () => { 51 const posts: BlogPost[] = []; 52 const content = "See [section](#heading)"; 53 expect(resolveInternalLinks(content, posts)).toBe( 54 "See [section](#heading)", 55 ); 56 }); 57 58 test("handles .md extension in link path", () => { 59 const posts = [ 60 makePost("guide", "at://did:plc:abc/site.standard.document/guide123"), 61 ]; 62 const content = "Read the [guide](guide.md)"; 63 expect(resolveInternalLinks(content, posts)).toBe( 64 "Read the [guide](at://did:plc:abc/space.remanso.note/guide123)", 65 ); 66 }); 67 68 test("handles nested slug matching", () => { 69 const posts = [ 70 makePost("blog/my-post", "at://did:plc:abc/site.standard.document/rkey1"), 71 ]; 72 const content = "See [post](my-post)"; 73 expect(resolveInternalLinks(content, posts)).toBe( 74 "See [post](at://did:plc:abc/space.remanso.note/rkey1)", 75 ); 76 }); 77 78 test("does not rewrite image embeds", () => { 79 const posts = [ 80 makePost("photo", "at://did:plc:abc/site.standard.document/photo1"), 81 ]; 82 const content = "![alt](photo)"; 83 expect(resolveInternalLinks(content, posts)).toBe("![alt](photo)"); 84 }); 85 86 test("does not rewrite @mention links", () => { 87 const posts = [ 88 makePost("mention", "at://did:plc:abc/site.standard.document/m1"), 89 ]; 90 const content = "@[name](mention)"; 91 expect(resolveInternalLinks(content, posts)).toBe("@[name](mention)"); 92 }); 93 94 test("handles multiple links in same content", () => { 95 const posts = [ 96 makePost("published", "at://did:plc:abc/site.standard.document/pub1"), 97 makePost("unpublished"), 98 ]; 99 const content = 100 "See [a](published) and [b](unpublished) and [c](https://ext.com)"; 101 expect(resolveInternalLinks(content, posts)).toBe( 102 "See [a](at://did:plc:abc/space.remanso.note/pub1) and b and [c](https://ext.com)", 103 ); 104 }); 105 106 test("handles index path normalization", () => { 107 const posts = [ 108 makePost("docs", "at://did:plc:abc/site.standard.document/docs1"), 109 ]; 110 const content = "See [docs](./docs/index)"; 111 expect(resolveInternalLinks(content, posts)).toBe( 112 "See [docs](at://did:plc:abc/space.remanso.note/docs1)", 113 ); 114 }); 115}); 116 117describe("findPostsWithStaleLinks", () => { 118 test("finds published post containing link to a newly created slug", () => { 119 const posts = [ 120 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 121 content: "Check out [post B](./post-b)", 122 }), 123 ]; 124 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 125 expect(result).toHaveLength(1); 126 expect(result[0]!.slug).toBe("post-a"); 127 }); 128 129 test("excludes posts in the exclude set (current batch)", () => { 130 const posts = [ 131 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 132 content: "Check out [post B](./post-b)", 133 }), 134 ]; 135 const result = findPostsWithStaleLinks( 136 posts, 137 ["post-b"], 138 new Set(["content/post-a.md"]), 139 ); 140 expect(result).toHaveLength(0); 141 }); 142 143 test("excludes unpublished posts (no atUri)", () => { 144 const posts = [ 145 makePost("post-a", undefined, { 146 content: "Check out [post B](./post-b)", 147 }), 148 ]; 149 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 150 expect(result).toHaveLength(0); 151 }); 152 153 test("excludes drafts", () => { 154 const posts = [ 155 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 156 content: "Check out [post B](./post-b)", 157 draft: true, 158 }), 159 ]; 160 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 161 expect(result).toHaveLength(0); 162 }); 163 164 test("ignores external links", () => { 165 const posts = [ 166 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 167 content: "Check out [post B](https://example.com/post-b)", 168 }), 169 ]; 170 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 171 expect(result).toHaveLength(0); 172 }); 173 174 test("ignores image embeds", () => { 175 const posts = [ 176 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 177 content: "![post B](./post-b)", 178 }), 179 ]; 180 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 181 expect(result).toHaveLength(0); 182 }); 183 184 test("ignores @mention links", () => { 185 const posts = [ 186 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 187 content: "@[post B](./post-b)", 188 }), 189 ]; 190 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 191 expect(result).toHaveLength(0); 192 }); 193 194 test("handles nested slug matching", () => { 195 const posts = [ 196 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 197 content: "Check out [post](my-post)", 198 }), 199 ]; 200 const result = findPostsWithStaleLinks(posts, ["blog/my-post"], new Set()); 201 expect(result).toHaveLength(1); 202 }); 203 204 test("does not match posts without matching links", () => { 205 const posts = [ 206 makePost("post-a", "at://did:plc:abc/site.standard.document/a1", { 207 content: "Check out [post C](./post-c)", 208 }), 209 ]; 210 const result = findPostsWithStaleLinks(posts, ["post-b"], new Set()); 211 expect(result).toHaveLength(0); 212 }); 213});