A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1// src/components/WriteStudio.test.tsx
2import { describe, it, expect, vi, beforeEach } from 'vitest';
3import { act, createElement } from 'react';
4import { createRoot } from 'react-dom/client';
5
6( globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true;
7
8// Stub the heavy editor: render a marker, ignore props.
9vi.mock( './SkyEditor', () => ( { default: () => createElement( 'div', { 'data-testid': 'sky-editor' } ) } ) );
10// Stub AppBar to avoid pulling chrome we don't assert on.
11vi.mock( './AppBar', () => ( { default: () => null } ) );
12
13const auth = vi.hoisted( () => ( { value: {} as Record< string, unknown > } ) );
14vi.mock( '../lib/auth/AuthProvider', () => ( {
15 AuthProvider: ( { children }: { children: unknown } ) => children,
16} ) );
17vi.mock( '../lib/auth/useAuth', () => ( { useAuth: () => auth.value } ) );
18
19const draft = vi.hoisted( () => ( {
20 store: {
21 load: vi.fn( async () => null ),
22 save: vi.fn( async () => {} ),
23 clear: vi.fn( async () => {} ),
24 setPublishIntent: vi.fn(),
25 consumePublishIntent: vi.fn( () => false ),
26 },
27} ) );
28vi.mock( '../lib/write/draft-store', () => ( {
29 createDraftStore: () => draft.store,
30} ) );
31vi.mock( '../lib/publish/publications', () => ( {
32 listPublications: vi.fn( async () => [] ),
33} ) );
34
35import WriteStudio from './WriteStudio';
36
37function render() {
38 const container = document.createElement( 'div' );
39 document.body.appendChild( container );
40 const root = createRoot( container );
41 return { container, root };
42}
43
44beforeEach( () => {
45 draft.store.consumePublishIntent.mockReturnValue( false );
46 draft.store.load.mockResolvedValue( null );
47} );
48
49describe( 'WriteStudio', () => {
50 it( 'signed out: no gate — renders the editor and a Publish action, with no sign-in shown until Publish is hit', async () => {
51 auth.value = { status: 'signed-out', agent: null, did: null, handle: null, signIn: vi.fn(), signOut: vi.fn() };
52 const { container, root } = render();
53 await act( async () => {
54 root.render( createElement( WriteStudio ) );
55 } );
56 // The editor is always present (no login gate)…
57 expect( container.querySelector( '[data-testid="sky-editor"]' ) ).not.toBe( null );
58 // …and the only auth-adjacent affordance is Publish — no persistent "Sign in" button,
59 // and no sign-in panel until the writer hits Publish.
60 expect( container.querySelector( '.write-publish' ) ).not.toBe( null );
61 expect( container.querySelector( '.signin-panel' ) ).toBe( null );
62 expect( container.textContent?.toLowerCase() ).not.toContain( 'sign in' );
63 } );
64
65 it( 'signed in: renders the editor and the Publish action, and does NOT duplicate the identity pill (the app bar carries it)', async () => {
66 auth.value = {
67 status: 'signed-in', agent: {}, did: 'did:plc:me', handle: 'me.test',
68 displayName: 'Me', avatar: null, pdsUrl: 'https://pds', signIn: vi.fn(), signOut: vi.fn(),
69 };
70 const { container, root } = render();
71 await act( async () => {
72 root.render( createElement( WriteStudio ) );
73 } );
74 expect( container.querySelector( '[data-testid="sky-editor"]' ) ).not.toBe( null );
75 expect( container.querySelector( '.write-publish' ) ).not.toBe( null );
76 // The app bar (mocked out here) is the single source of the signed-in identity —
77 // WriteStudio must not render its own duplicate pill.
78 expect( container.querySelector( '.account-pill' ) ).toBe( null );
79 } );
80
81 it( 'resume: a publish intent on a signed-in load opens the publish flow', async () => {
82 draft.store.consumePublishIntent.mockReturnValue( true );
83 auth.value = {
84 status: 'signed-in', agent: {}, did: 'did:plc:me', handle: 'me.test',
85 displayName: 'Me', avatar: null, pdsUrl: 'https://pds', signIn: vi.fn(), signOut: vi.fn(),
86 };
87 const { container, root } = render();
88 await act( async () => {
89 root.render( createElement( WriteStudio ) );
90 } );
91 expect( container.querySelector( '.writeflow' ) ).not.toBe( null );
92 // The top Publish action gives way to the stepper — no redundant button.
93 expect( container.querySelector( '.write-actions' ) ).toBe( null );
94 } );
95} );