ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork

Configure Feed

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

test(web): add parser logic unit tests

byarielm.fyi b4ba282a 45407496

verified
+242
+242
packages/web/src/lib/parsers/parserLogic.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { parseTextOrHtml, parseJson, parseContent } from "./parserLogic"; 3 + import type { ParseRule } from "./platformDefinitions"; 4 + 5 + describe("parserLogic", () => { 6 + describe("parseTextOrHtml", () => { 7 + it("extracts usernames from Instagram HTML", () => { 8 + const html = ` 9 + <a target="_blank" href="https://www.instagram.com/_u/beautyscicomm">beautyscicomm</a> 10 + <a target="_blank" href="https://www.instagram.com/_u/techguru42">techguru42</a> 11 + `; 12 + const regex = 13 + '<a target="_blank" href="https://www.instagram.com/_u/([^"]+)"'; 14 + 15 + const result = parseTextOrHtml(html, regex); 16 + 17 + expect(result).toEqual(["beautyscicomm", "techguru42"]); 18 + }); 19 + 20 + it("extracts usernames from TikTok text format", () => { 21 + const text = `Date: 2024-01-01 22 + Username: user_one 23 + Date: 2024-01-02 24 + Username: user_two`; 25 + const regex = "Username:\\s*([^\\r\\n]+)"; 26 + 27 + const result = parseTextOrHtml(text, regex); 28 + 29 + expect(result).toEqual(["user_one", "user_two"]); 30 + }); 31 + 32 + it("returns empty array when no matches found", () => { 33 + const result = parseTextOrHtml("no matches here", "Username:\\s*(.+)"); 34 + expect(result).toEqual([]); 35 + }); 36 + 37 + it("returns empty array for invalid regex pattern", () => { 38 + const result = parseTextOrHtml("content", "[invalid(regex"); 39 + expect(result).toEqual([]); 40 + }); 41 + 42 + it("filters out empty captured groups", () => { 43 + // Pattern that could match empty groups 44 + const content = "prefix: \nprefix: value"; 45 + const regex = "prefix:\\s*(.*)"; 46 + 47 + const result = parseTextOrHtml(content, regex); 48 + // Both match, but first capture is empty string which gets filtered 49 + expect(result.every((r) => r.length > 0)).toBe(true); 50 + }); 51 + 52 + it("trims whitespace from extracted usernames", () => { 53 + const text = "Username: spacey_user "; 54 + const regex = "Username:\\s*([^\\r\\n]+)"; 55 + 56 + const result = parseTextOrHtml(text, regex); 57 + 58 + expect(result).toEqual(["spacey_user"]); 59 + }); 60 + }); 61 + 62 + describe("parseJson", () => { 63 + it("extracts usernames from Instagram JSON format", () => { 64 + const content = JSON.stringify({ 65 + relationships_following: [ 66 + { title: "user1" }, 67 + { title: "user2" }, 68 + { title: "user3" }, 69 + ], 70 + }); 71 + 72 + const result = parseJson(content, [ 73 + "relationships_following", 74 + "title", 75 + ]); 76 + 77 + expect(result).toEqual(["user1", "user2", "user3"]); 78 + }); 79 + 80 + it("extracts usernames from TikTok nested JSON format", () => { 81 + const content = JSON.stringify({ 82 + "Your Activity": { 83 + Following: { 84 + Following: [ 85 + { UserName: "tiktoker1" }, 86 + { UserName: "tiktoker2" }, 87 + ], 88 + }, 89 + }, 90 + }); 91 + 92 + const result = parseJson(content, [ 93 + "Your Activity", 94 + "Following", 95 + "Following", 96 + "UserName", 97 + ]); 98 + 99 + expect(result).toEqual(["tiktoker1", "tiktoker2"]); 100 + }); 101 + 102 + it("returns empty array for empty JSON object", () => { 103 + const result = parseJson("{}", ["relationships_following", "title"]); 104 + expect(result).toEqual([]); 105 + }); 106 + 107 + it("returns empty array for invalid JSON", () => { 108 + const result = parseJson("not-json", ["key", "value"]); 109 + expect(result).toEqual([]); 110 + }); 111 + 112 + it("returns empty array when path key is not found", () => { 113 + const content = JSON.stringify({ other: [] }); 114 + const result = parseJson(content, ["missing_key", "title"]); 115 + expect(result).toEqual([]); 116 + }); 117 + 118 + it("returns empty array when path keys are less than 2", () => { 119 + const content = JSON.stringify({ key: "value" }); 120 + const result = parseJson(content, ["key"]); 121 + expect(result).toEqual([]); 122 + }); 123 + 124 + it("returns empty array when target array is not an array", () => { 125 + const content = JSON.stringify({ 126 + container: { list: "not-an-array" }, 127 + }); 128 + const result = parseJson(content, ["container", "list", "name"]); 129 + expect(result).toEqual([]); 130 + }); 131 + 132 + it("skips items missing the target key", () => { 133 + const content = JSON.stringify({ 134 + users: [ 135 + { name: "user1" }, 136 + { other: "no-name" }, 137 + { name: "user3" }, 138 + ], 139 + }); 140 + 141 + const result = parseJson(content, ["users", "name"]); 142 + 143 + expect(result).toEqual(["user1", "user3"]); 144 + }); 145 + 146 + it("converts non-string values to string", () => { 147 + const content = JSON.stringify({ 148 + items: [{ id: 123 }, { id: 456 }], 149 + }); 150 + 151 + const result = parseJson(content, ["items", "id"]); 152 + 153 + expect(result).toEqual(["123", "456"]); 154 + }); 155 + 156 + it("handles deeply nested navigation path", () => { 157 + const content = JSON.stringify({ 158 + level1: { 159 + level2: { 160 + items: [{ name: "deep" }], 161 + }, 162 + }, 163 + }); 164 + 165 + const result = parseJson(content, [ 166 + "level1", 167 + "level2", 168 + "items", 169 + "name", 170 + ]); 171 + 172 + expect(result).toEqual(["deep"]); 173 + }); 174 + }); 175 + 176 + describe("parseContent", () => { 177 + it("dispatches HTML format to parseTextOrHtml", () => { 178 + const rule: ParseRule = { 179 + zipPath: "test.html", 180 + format: "HTML", 181 + rule: '<a href="([^"]+)"', 182 + }; 183 + const content = '<a href="username1">'; 184 + 185 + const result = parseContent(content, rule); 186 + 187 + expect(result).toEqual(["username1"]); 188 + }); 189 + 190 + it("dispatches TEXT format to parseTextOrHtml", () => { 191 + const rule: ParseRule = { 192 + zipPath: "test.txt", 193 + format: "TEXT", 194 + rule: "Name: (.+)", 195 + }; 196 + const content = "Name: testuser"; 197 + 198 + const result = parseContent(content, rule); 199 + 200 + expect(result).toEqual(["testuser"]); 201 + }); 202 + 203 + it("dispatches JSON format to parseJson", () => { 204 + const rule: ParseRule = { 205 + zipPath: "test.json", 206 + format: "JSON", 207 + rule: ["users", "name"], 208 + }; 209 + const content = JSON.stringify({ 210 + users: [{ name: "jsonuser" }], 211 + }); 212 + 213 + const result = parseContent(content, rule); 214 + 215 + expect(result).toEqual(["jsonuser"]); 216 + }); 217 + 218 + it("returns empty array for HTML rule with array path (wrong type)", () => { 219 + const rule: ParseRule = { 220 + zipPath: "test.html", 221 + format: "HTML", 222 + rule: ["should", "be", "string"], 223 + }; 224 + 225 + const result = parseContent("content", rule); 226 + 227 + expect(result).toEqual([]); 228 + }); 229 + 230 + it("returns empty array for JSON rule with string regex (wrong type)", () => { 231 + const rule: ParseRule = { 232 + zipPath: "test.json", 233 + format: "JSON", 234 + rule: "should-be-array", 235 + }; 236 + 237 + const result = parseContent("content", rule); 238 + 239 + expect(result).toEqual([]); 240 + }); 241 + }); 242 + });