Sifa professional network frontend (Next.js, React, TailwindCSS) sifa.id/
at main 277 lines 8.7 kB view raw
1import { describe, it, expect, vi, beforeEach } from 'vitest'; 2 3describe('embed.js', function () { 4 let initSifaEmbeds: () => Promise<void[]>; 5 6 beforeEach(async function () { 7 document.body.innerHTML = ''; 8 vi.restoreAllMocks(); 9 vi.resetModules(); 10 11 const mod = await import('../../public/embed.js'); 12 initSifaEmbeds = mod.initSifaEmbeds; 13 }); 14 15 it('replaces script tag with shadow DOM container containing profile card', async function () { 16 const script = document.createElement('script'); 17 script.setAttribute('src', 'https://sifa.id/embed.js'); 18 script.setAttribute('data-did', 'did:plc:test123'); 19 document.body.appendChild(script); 20 21 global.fetch = vi.fn().mockResolvedValue({ 22 ok: true, 23 json: function () { 24 return Promise.resolve({ 25 did: 'did:plc:test123', 26 handle: 'alice.bsky.social', 27 displayName: 'Alice', 28 headline: 'Engineer', 29 avatar: null, 30 location: { country: 'Amsterdam' }, 31 profileUrl: 'https://sifa.id/p/alice.bsky.social', 32 trustStats: [], 33 verifiedAccounts: [], 34 openTo: [], 35 claimed: true, 36 }); 37 }, 38 }); 39 40 await initSifaEmbeds(); 41 42 const container = document.querySelector('.sifa-embed'); 43 expect(container).not.toBeNull(); 44 expect(container?.shadowRoot).not.toBeNull(); 45 const html = container?.shadowRoot?.innerHTML ?? ''; 46 expect(html).toContain('Alice'); 47 expect(html).toContain('alice.bsky.social'); 48 expect(html).toContain('sifa.id/p/alice.bsky.social'); 49 }); 50 51 it('uses data-handle when data-did is absent', async function () { 52 const script = document.createElement('script'); 53 script.setAttribute('src', 'https://sifa.id/embed.js'); 54 script.setAttribute('data-handle', 'bob.bsky.social'); 55 document.body.appendChild(script); 56 57 global.fetch = vi.fn().mockResolvedValue({ 58 ok: true, 59 json: function () { 60 return Promise.resolve({ 61 did: 'did:plc:bob', 62 handle: 'bob.bsky.social', 63 displayName: 'Bob', 64 profileUrl: 'https://sifa.id/p/bob.bsky.social', 65 trustStats: [], 66 verifiedAccounts: [], 67 openTo: [], 68 claimed: true, 69 }); 70 }, 71 }); 72 73 await initSifaEmbeds(); 74 75 expect(global.fetch).toHaveBeenCalledWith( 76 expect.stringContaining('/api/embed/bob.bsky.social/data'), 77 ); 78 }); 79 80 it('shows error state when profile not found', async function () { 81 const script = document.createElement('script'); 82 script.setAttribute('src', 'https://sifa.id/embed.js'); 83 script.setAttribute('data-did', 'did:plc:invalid'); 84 document.body.appendChild(script); 85 86 global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 404 }); 87 88 await initSifaEmbeds(); 89 90 const container = document.querySelector('.sifa-embed'); 91 const html = container?.shadowRoot?.innerHTML ?? ''; 92 expect(html).toContain('Profile not found'); 93 }); 94 95 it('renders activity data (followers, PDS, app badges)', async function () { 96 const script = document.createElement('script'); 97 script.setAttribute('src', 'https://sifa.id/embed.js'); 98 script.setAttribute('data-did', 'did:plc:test'); 99 document.body.appendChild(script); 100 101 global.fetch = vi.fn().mockResolvedValue({ 102 ok: true, 103 json: function () { 104 return Promise.resolve({ 105 did: 'did:plc:test', 106 handle: 'test.bsky.social', 107 displayName: 'Test', 108 profileUrl: 'https://sifa.id/p/test.bsky.social', 109 trustStats: [], 110 verifiedAccounts: [], 111 openTo: [], 112 followersCount: 1234, 113 pdsProvider: 'Bluesky', 114 activeApps: [{ id: 'bluesky', name: 'Bluesky' }], 115 claimed: true, 116 }); 117 }, 118 }); 119 120 await initSifaEmbeds(); 121 122 const html = document.querySelector('.sifa-embed')?.shadowRoot?.innerHTML ?? ''; 123 expect(html).toContain('1.2K followers'); 124 expect(html).toContain('Bluesky'); 125 expect(html).toContain('app-badge'); 126 }); 127 128 it('prefers atprotoFollowersCount over followersCount', async function () { 129 const script = document.createElement('script'); 130 script.setAttribute('src', 'https://sifa.id/embed.js'); 131 script.setAttribute('data-did', 'did:plc:test'); 132 document.body.appendChild(script); 133 134 global.fetch = vi.fn().mockResolvedValue({ 135 ok: true, 136 json: function () { 137 return Promise.resolve({ 138 did: 'did:plc:test', 139 handle: 'test.bsky.social', 140 displayName: 'Test', 141 profileUrl: 'https://sifa.id/p/test.bsky.social', 142 trustStats: [], 143 verifiedAccounts: [], 144 openTo: [], 145 followersCount: 5, 146 atprotoFollowersCount: 5678, 147 claimed: true, 148 }); 149 }, 150 }); 151 152 await initSifaEmbeds(); 153 154 const html = document.querySelector('.sifa-embed')?.shadowRoot?.innerHTML ?? ''; 155 expect(html).toContain('5.7K followers on Bluesky'); 156 expect(html).not.toContain('5 followers'); 157 }); 158 159 it('renders open-to pills', async function () { 160 const script = document.createElement('script'); 161 script.setAttribute('src', 'https://sifa.id/embed.js'); 162 script.setAttribute('data-did', 'did:plc:test'); 163 document.body.appendChild(script); 164 165 global.fetch = vi.fn().mockResolvedValue({ 166 ok: true, 167 json: function () { 168 return Promise.resolve({ 169 did: 'did:plc:test', 170 handle: 'test', 171 displayName: 'Test', 172 profileUrl: 'https://sifa.id/p/test', 173 trustStats: [], 174 verifiedAccounts: [], 175 openTo: ['Mentoring', 'Speaking'], 176 claimed: true, 177 }); 178 }, 179 }); 180 181 await initSifaEmbeds(); 182 183 const html = document.querySelector('.sifa-embed')?.shadowRoot?.innerHTML ?? ''; 184 expect(html).toContain('Mentoring'); 185 expect(html).toContain('Speaking'); 186 }); 187 188 it('applies dark theme styles', async function () { 189 const script = document.createElement('script'); 190 script.setAttribute('src', 'https://sifa.id/embed.js'); 191 script.setAttribute('data-did', 'did:plc:test'); 192 script.setAttribute('data-theme', 'dark'); 193 document.body.appendChild(script); 194 195 global.fetch = vi.fn().mockResolvedValue({ 196 ok: true, 197 json: function () { 198 return Promise.resolve({ 199 did: 'did:plc:test', 200 handle: 'test', 201 displayName: 'Test', 202 profileUrl: 'https://sifa.id/p/test', 203 trustStats: [], 204 verifiedAccounts: [], 205 openTo: [], 206 claimed: true, 207 }); 208 }, 209 }); 210 211 await initSifaEmbeds(); 212 213 const style = 214 document.querySelector('.sifa-embed')?.shadowRoot?.querySelector('style')?.textContent ?? ''; 215 expect(style).toContain('#1a1a2e'); 216 }); 217 218 it('renders avatar image when avatar URL is provided', async function () { 219 const script = document.createElement('script'); 220 script.setAttribute('src', 'https://sifa.id/embed.js'); 221 script.setAttribute('data-did', 'did:plc:test'); 222 document.body.appendChild(script); 223 224 global.fetch = vi.fn().mockResolvedValue({ 225 ok: true, 226 json: function () { 227 return Promise.resolve({ 228 did: 'did:plc:test', 229 handle: 'test', 230 displayName: 'Test User', 231 avatar: 'https://cdn.example.com/avatar.jpg', 232 profileUrl: 'https://sifa.id/p/test', 233 trustStats: [], 234 verifiedAccounts: [], 235 openTo: [], 236 claimed: true, 237 }); 238 }, 239 }); 240 241 await initSifaEmbeds(); 242 243 const html = document.querySelector('.sifa-embed')?.shadowRoot?.innerHTML ?? ''; 244 expect(html).toContain('avatar.jpg'); 245 expect(html).toContain('<img'); 246 }); 247 248 it('renders letter placeholder when no avatar', async function () { 249 const script = document.createElement('script'); 250 script.setAttribute('src', 'https://sifa.id/embed.js'); 251 script.setAttribute('data-did', 'did:plc:test'); 252 document.body.appendChild(script); 253 254 global.fetch = vi.fn().mockResolvedValue({ 255 ok: true, 256 json: function () { 257 return Promise.resolve({ 258 did: 'did:plc:test', 259 handle: 'test', 260 displayName: 'Alice', 261 avatar: null, 262 profileUrl: 'https://sifa.id/p/test', 263 trustStats: [], 264 verifiedAccounts: [], 265 openTo: [], 266 claimed: true, 267 }); 268 }, 269 }); 270 271 await initSifaEmbeds(); 272 273 const html = document.querySelector('.sifa-embed')?.shadowRoot?.innerHTML ?? ''; 274 expect(html).toContain('avatar-placeholder'); 275 expect(html).toContain('A'); 276 }); 277});