1+ import { page } from '@vitest/browser/context' ;
12import { describe , expect , it , vi } from 'vitest' ;
23import { render } from 'vitest-browser-svelte' ;
3- import { page } from '@vitest/browser/context' ;
44import Bluesky from './bluesky.svelte' ;
55
66describe ( 'Bluesky' , ( ) => {
@@ -40,7 +40,7 @@ describe('Bluesky', () => {
4040 } ) ;
4141
4242 const iframe = page . getByTestId ( 'bluesky-embed' ) ;
43- const element = iframe . element ( ) ;
43+ const element = iframe . element ( ) as HTMLElement ;
4444 const style_text = element . style . cssText . toLowerCase ( ) ;
4545 expect ( style_text ) . toContain ( 'border-radius: 8px' ) ;
4646 expect ( style_text ) . toContain ( 'background: rgb(240, 240, 240)' ) ;
@@ -52,7 +52,7 @@ describe('Bluesky', () => {
5252 } ) ;
5353
5454 const iframe = page . getByTestId ( 'bluesky-embed' ) ;
55- const element = iframe . element ( ) ;
55+ const element = iframe . element ( ) as HTMLElement ;
5656 const style_text = element . style . cssText . toLowerCase ( ) ;
5757 expect ( style_text ) . toContain ( 'position: absolute' ) ;
5858 expect ( style_text ) . toContain ( 'top: 0px' ) ;
@@ -72,7 +72,7 @@ describe('Bluesky', () => {
7272 } ) ;
7373
7474 const iframe = page . getByTestId ( 'bluesky-embed' ) ;
75- const element = iframe . element ( ) ;
75+ const element = iframe . element ( ) as HTMLElement ;
7676 const style_text = element . style . cssText . toLowerCase ( ) ;
7777
7878 // Check default styles are preserved
@@ -87,20 +87,47 @@ describe('Bluesky', () => {
8787 } ) ;
8888
8989 it ( 'updates height when receiving message from iframe' , async ( ) => {
90- render ( Bluesky , {
90+ const { container } = render ( Bluesky , {
9191 post_id : test_post_id ,
9292 } ) ;
9393
94+ const wrapper = container . querySelector (
95+ '.bluesky-wrapper' ,
96+ ) as HTMLElement ;
97+ const iframe = container . querySelector (
98+ 'iframe' ,
99+ ) as HTMLIFrameElement ;
100+
101+ // Wait for component to mount and event listener to be added
102+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
103+
104+ // Create a simple message event without the problematic source property
105+ // The component logic will still work since it checks event.origin first
94106 const message_event = new MessageEvent ( 'message' , {
95107 data : { type : 'height' , height : 500 } ,
96108 origin : 'https://embed.bsky.app' ,
97109 } ) ;
98110
111+ // Mock the iframe's contentWindow to match what the component expects
112+ const mockContentWindow = { } as Window ;
113+ Object . defineProperty ( iframe , 'contentWindow' , {
114+ value : mockContentWindow ,
115+ writable : true ,
116+ } ) ;
117+
118+ // Override the event's source property after creation
119+ Object . defineProperty ( message_event , 'source' , {
120+ value : mockContentWindow ,
121+ writable : false ,
122+ } ) ;
123+
99124 window . dispatchEvent ( message_event ) ;
100125
101- const iframe = page . getByTestId ( 'bluesky-embed' ) ;
102- const element = iframe . element ( ) ;
103- expect ( element . style . height ) . toBe ( '100%' ) ;
126+ // Wait for DOM update
127+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
128+
129+ // Check that the wrapper height was updated
130+ expect ( wrapper ?. style . height ) . toBe ( '500px' ) ;
104131 } ) ;
105132
106133 // Edge Cases and Comprehensive Coverage
@@ -145,16 +172,32 @@ describe('Bluesky', () => {
145172 const wrapper = container . querySelector (
146173 '.bluesky-wrapper' ,
147174 ) as HTMLElement ;
175+ const iframe = container . querySelector (
176+ 'iframe' ,
177+ ) as HTMLIFrameElement ;
148178
149179 // Wait for component to mount and event listener to be added
150180 await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
151181
152- // Send message with 'h' property instead of 'height'
182+ // Create a simple message event
153183 const message_event = new MessageEvent ( 'message' , {
154184 data : { h : 350 } ,
155185 origin : 'https://embed.bsky.app' ,
156186 } ) ;
157187
188+ // Mock the iframe's contentWindow
189+ const mockContentWindow = { } as Window ;
190+ Object . defineProperty ( iframe , 'contentWindow' , {
191+ value : mockContentWindow ,
192+ writable : true ,
193+ } ) ;
194+
195+ // Override the event's source property after creation
196+ Object . defineProperty ( message_event , 'source' , {
197+ value : mockContentWindow ,
198+ writable : false ,
199+ } ) ;
200+
158201 window . dispatchEvent ( message_event ) ;
159202
160203 // Wait for DOM update
@@ -172,16 +215,32 @@ describe('Bluesky', () => {
172215 const wrapper = container . querySelector (
173216 '.bluesky-wrapper' ,
174217 ) as HTMLElement ;
218+ const iframe = container . querySelector (
219+ 'iframe' ,
220+ ) as HTMLIFrameElement ;
175221
176222 // Wait for component to mount and event listener to be added
177223 await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
178224
179- // Send message with no height or h property
225+ // Create a simple message event
180226 const invalid_message = new MessageEvent ( 'message' , {
181227 data : { type : 'invalid' } ,
182228 origin : 'https://embed.bsky.app' ,
183229 } ) ;
184230
231+ // Mock the iframe's contentWindow
232+ const mockContentWindow = { } as Window ;
233+ Object . defineProperty ( iframe , 'contentWindow' , {
234+ value : mockContentWindow ,
235+ writable : true ,
236+ } ) ;
237+
238+ // Override the event's source property after creation
239+ Object . defineProperty ( invalid_message , 'source' , {
240+ value : mockContentWindow ,
241+ writable : false ,
242+ } ) ;
243+
185244 window . dispatchEvent ( invalid_message ) ;
186245
187246 // Wait for DOM update
@@ -214,6 +273,191 @@ describe('Bluesky', () => {
214273 removeEventListenerSpy . mockRestore ( ) ;
215274 } ) ;
216275
276+ it ( 'should generate unique iframe IDs for multiple instances' , async ( ) => {
277+ const { container : container1 } = render ( Bluesky , {
278+ post_id : test_post_id ,
279+ } ) ;
280+
281+ const { container : container2 } = render ( Bluesky , {
282+ post_id : 'did:plc:different/app.bsky.feed.post/different' ,
283+ } ) ;
284+
285+ const iframe1 = container1 . querySelector (
286+ 'iframe' ,
287+ ) as HTMLIFrameElement ;
288+ const iframe2 = container2 . querySelector (
289+ 'iframe' ,
290+ ) as HTMLIFrameElement ;
291+
292+ expect ( iframe1 . id ) . toBeTruthy ( ) ;
293+ expect ( iframe2 . id ) . toBeTruthy ( ) ;
294+ expect ( iframe1 . id ) . not . toBe ( iframe2 . id ) ;
295+ expect ( iframe1 . id ) . toMatch ( / ^ b l u e s k y - i f r a m e - [ a - z 0 - 9 ] { 9 } $ / ) ;
296+ expect ( iframe2 . id ) . toMatch ( / ^ b l u e s k y - i f r a m e - [ a - z 0 - 9 ] { 9 } $ / ) ;
297+ } ) ;
298+
299+ it ( 'should only update height for the specific iframe that sent the message' , async ( ) => {
300+ const { container : container1 } = render ( Bluesky , {
301+ post_id : test_post_id ,
302+ } ) ;
303+
304+ const { container : container2 } = render ( Bluesky , {
305+ post_id : 'did:plc:different/app.bsky.feed.post/different' ,
306+ } ) ;
307+
308+ const wrapper1 = container1 . querySelector (
309+ '.bluesky-wrapper' ,
310+ ) as HTMLElement ;
311+ const wrapper2 = container2 . querySelector (
312+ '.bluesky-wrapper' ,
313+ ) as HTMLElement ;
314+ const iframe1 = container1 . querySelector (
315+ 'iframe' ,
316+ ) as HTMLIFrameElement ;
317+ const iframe2 = container2 . querySelector (
318+ 'iframe' ,
319+ ) as HTMLIFrameElement ;
320+
321+ // Wait for components to mount and event listeners to be added
322+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
323+
324+ // Create mock window objects for both iframes
325+ const mockContentWindow1 = { } as Window ;
326+ const mockContentWindow2 = { } as Window ;
327+
328+ Object . defineProperty ( iframe1 , 'contentWindow' , {
329+ value : mockContentWindow1 ,
330+ writable : true ,
331+ } ) ;
332+
333+ Object . defineProperty ( iframe2 , 'contentWindow' , {
334+ value : mockContentWindow2 ,
335+ writable : true ,
336+ } ) ;
337+
338+ // Send message that appears to come from iframe1
339+ const message_event_1 = new MessageEvent ( 'message' , {
340+ data : { height : 300 } ,
341+ origin : 'https://embed.bsky.app' ,
342+ } ) ;
343+
344+ // Override the event's source property
345+ Object . defineProperty ( message_event_1 , 'source' , {
346+ value : mockContentWindow1 ,
347+ writable : false ,
348+ } ) ;
349+
350+ window . dispatchEvent ( message_event_1 ) ;
351+
352+ // Wait for DOM update
353+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
354+
355+ // Only wrapper1 should update, wrapper2 should remain unchanged
356+ expect ( wrapper1 . style . height ) . toBe ( '300px' ) ;
357+ expect ( wrapper2 . style . height ) . toBe ( '174.5px' ) ;
358+
359+ // Now send message that appears to come from iframe2
360+ const message_event_2 = new MessageEvent ( 'message' , {
361+ data : { height : 450 } ,
362+ origin : 'https://embed.bsky.app' ,
363+ } ) ;
364+
365+ // Override the event's source property
366+ Object . defineProperty ( message_event_2 , 'source' , {
367+ value : mockContentWindow2 ,
368+ writable : false ,
369+ } ) ;
370+
371+ window . dispatchEvent ( message_event_2 ) ;
372+
373+ // Wait for DOM update
374+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
375+
376+ // wrapper1 should remain at 300px, wrapper2 should update to 450px
377+ expect ( wrapper1 . style . height ) . toBe ( '300px' ) ;
378+ expect ( wrapper2 . style . height ) . toBe ( '450px' ) ;
379+ } ) ;
380+
381+ it ( 'should ignore messages from unknown iframe sources' , async ( ) => {
382+ const { container } = render ( Bluesky , {
383+ post_id : test_post_id ,
384+ } ) ;
385+
386+ const wrapper = container . querySelector (
387+ '.bluesky-wrapper' ,
388+ ) as HTMLElement ;
389+ const iframe = container . querySelector (
390+ 'iframe' ,
391+ ) as HTMLIFrameElement ;
392+ const initial_height = wrapper . style . height ;
393+
394+ // Wait for component to mount and event listener to be added
395+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
396+
397+ // Mock the iframe's contentWindow
398+ const mockContentWindow = { } as Window ;
399+ Object . defineProperty ( iframe , 'contentWindow' , {
400+ value : mockContentWindow ,
401+ writable : true ,
402+ } ) ;
403+
404+ // Create an unknown window source (different from any iframe's contentWindow)
405+ const unknown_source = { } as Window ;
406+
407+ const message_event = new MessageEvent ( 'message' , {
408+ data : { height : 600 } ,
409+ origin : 'https://embed.bsky.app' ,
410+ } ) ;
411+
412+ // Override the event's source property with unknown source
413+ Object . defineProperty ( message_event , 'source' , {
414+ value : unknown_source ,
415+ writable : false ,
416+ } ) ;
417+
418+ window . dispatchEvent ( message_event ) ;
419+
420+ // Wait for DOM update
421+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
422+
423+ // Height should remain unchanged
424+ expect ( wrapper . style . height ) . toBe ( initial_height ) ;
425+ } ) ;
426+
427+ it ( 'should handle multiple instances with different post IDs correctly' , async ( ) => {
428+ const post_ids = [
429+ 'did:plc:x7cbjcbvndpjb3vyndsqgpsi/app.bsky.feed.post/3lpzm7ynvzs2o' ,
430+ 'did:plc:x7cbjcbvndpjb3vyndsqgpsi/app.bsky.feed.post/3lq2ypghyrk2p' ,
431+ 'did:plc:x7cbjcbvndpjb3vyndsqgpsi/app.bsky.feed.post/3lq2yv3uzjc2p' ,
432+ ] ;
433+
434+ const instances = post_ids . map ( post_id =>
435+ render ( Bluesky , { post_id } ) ,
436+ ) ;
437+
438+ // Wait for all components to mount
439+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
440+
441+ // Verify each instance has unique iframe ID and correct src
442+ instances . forEach ( ( { container } , index ) => {
443+ const iframe = container . querySelector (
444+ 'iframe' ,
445+ ) as HTMLIFrameElement ;
446+ expect ( iframe . id ) . toMatch ( / ^ b l u e s k y - i f r a m e - [ a - z 0 - 9 ] { 9 } $ / ) ;
447+ expect ( iframe . src ) . toBe (
448+ `https://embed.bsky.app/embed/${ post_ids [ index ] } ` ,
449+ ) ;
450+ } ) ;
451+
452+ // Verify all iframe IDs are unique
453+ const iframe_ids = instances . map (
454+ ( { container } ) =>
455+ ( container . querySelector ( 'iframe' ) as HTMLIFrameElement ) . id ,
456+ ) ;
457+ const unique_ids = new Set ( iframe_ids ) ;
458+ expect ( unique_ids . size ) . toBe ( post_ids . length ) ;
459+ } ) ;
460+
217461 it ( 'should handle very long post_id values' , async ( ) => {
218462 const long_post_id =
219463 'did:plc:' +
0 commit comments