A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

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

1import { describe, it, expect, vi } from 'vitest'; 2import { act, createElement } from 'react'; 3import { createRoot } from 'react-dom/client'; 4 5( globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; 6import SignInPanel from './SignInPanel'; 7 8function mount( props: Record< string, unknown > ) { 9 const container = document.createElement( 'div' ); 10 document.body.appendChild( container ); 11 act( () => createRoot( container ).render( createElement( SignInPanel, props as never ) ) ); 12 return container; 13} 14 15// React 18 tracks a controlled input's value via a private setter; assigning `.value` 16// directly then dispatching `input` is invisible to React and onChange never fires. Set 17// through the native prototype setter so React's tracker sees the change (standard idiom). 18const setInputValue = ( input: HTMLInputElement, value: string ) => { 19 const setter = Object.getOwnPropertyDescriptor( 20 window.HTMLInputElement.prototype, 21 'value' 22 )!.set!; 23 setter.call( input, value ); 24}; 25 26describe( 'SignInPanel', () => { 27 it( 'for-publish variant frames the CTA around publishing and links out to signup in a new tab', () => { 28 const c = mount( { forPublish: true, error: null, onSubmit: vi.fn() } ); 29 expect( c.textContent?.toLowerCase() ).toContain( 'publish' ); 30 const signup = c.querySelector( 'a[href*="bsky.app"]' ) as HTMLAnchorElement | null; 31 expect( signup ).not.toBe( null ); 32 // Opens in a new tab, hardened, and labelled so the new-tab behaviour is announced. 33 expect( signup!.target ).toBe( '_blank' ); 34 expect( signup!.rel ).toContain( 'noopener' ); 35 expect( signup!.getAttribute( 'aria-label' )?.toLowerCase() ).toContain( 'new tab' ); 36 // Carries the usual external-link icon (not a bare "→"). 37 expect( signup!.querySelector( 'svg' ) ).not.toBe( null ); 38 expect( signup!.textContent ).not.toContain( '→' ); 39 } ); 40 41 it( 'submits the typed handle', () => { 42 const onSubmit = vi.fn(); 43 const c = mount( { forPublish: false, error: null, onSubmit } ); 44 const input = c.querySelector( 'input' )!; 45 const form = c.querySelector( 'form' )!; 46 act( () => { 47 setInputValue( input as HTMLInputElement, 'alice.bsky.social' ); 48 input.dispatchEvent( new Event( 'input', { bubbles: true } ) ); 49 } ); 50 act( () => form.dispatchEvent( new Event( 'submit', { bubbles: true, cancelable: true } ) ) ); 51 expect( onSubmit ).toHaveBeenCalledWith( 'alice.bsky.social' ); 52 } ); 53 54 it( 'shows an error when provided', () => { 55 const c = mount( { forPublish: false, error: 'Bad handle', onSubmit: vi.fn() } ); 56 expect( c.textContent ).toContain( 'Bad handle' ); 57 } ); 58} );