@@ -107,6 +107,23 @@ function setup( {
107107 return { queryClient, onClose, onCreated, user } ;
108108}
109109
110+ type User = ReturnType < typeof userEvent . setup > ;
111+
112+ // Walk from the opening Identity step to the Feeds step. The name is required to
113+ // leave the first step, so it is entered here.
114+ async function reachFeedsStep ( user : User , name = 'Reading' ) {
115+ await user . type ( screen . getByLabelText ( 'Name' ) , name ) ;
116+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ; // Identity → Layout
117+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ; // Layout → Feeds
118+ }
119+
120+ // Walk all the way to the final Topics step, where Create lives.
121+ async function reachTopicsStep ( user : User , name = 'Reading' ) {
122+ await reachFeedsStep ( user , name ) ;
123+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ; // Feeds → Topics
124+ await screen . findByRole ( 'button' , { name : 'Create' } ) ;
125+ }
126+
110127describe ( 'CreateSpaceModal' , ( ) => {
111128 beforeEach ( ( ) => {
112129 mockSubscriptions = [ ] ;
@@ -121,46 +138,65 @@ describe( 'CreateSpaceModal', () => {
121138 expect ( screen . queryByRole ( 'dialog' ) ) . not . toBeInTheDocument ( ) ;
122139 } ) ;
123140
124- it ( 'keeps Create disabled until a valid name is entered' , async ( ) => {
141+ it ( 'keeps Next disabled until a valid name is entered' , async ( ) => {
125142 const { user } = setup ( ) ;
126143
127- expect ( screen . getByRole ( 'button' , { name : 'Create ' } ) ) . toBeDisabled ( ) ;
144+ expect ( screen . getByRole ( 'button' , { name : 'Next ' } ) ) . toBeDisabled ( ) ;
128145
129146 await user . type ( screen . getByLabelText ( 'Name' ) , 'Reading' ) ;
130147
131- expect ( screen . getByRole ( 'button' , { name : 'Create ' } ) ) . toBeEnabled ( ) ;
148+ expect ( screen . getByRole ( 'button' , { name : 'Next ' } ) ) . toBeEnabled ( ) ;
132149 } ) ;
133150
134- it ( 'uses the shared tabbed upsert modal while creating ' , async ( ) => {
151+ it ( 'steps through the identity, layout, feeds and topics sections ' , async ( ) => {
135152 mockSubscriptions = [ mockSubscription ] ;
136153 const { user } = setup ( ) ;
137154
138155 const dialog = screen . getByRole ( 'dialog' , { name : 'Create a new space' } ) ;
139156
140- expect ( within ( dialog ) . getByRole ( ' tab' , { name : 'Identity' } ) ) . toBeVisible ( ) ;
141- expect ( within ( dialog ) . getByRole ( 'tab' , { name : 'Layout' } ) ) . toBeVisible ( ) ;
142- expect ( within ( dialog ) . getByRole ( 'tab' , { name : 'Sources' } ) ) . toBeVisible ( ) ;
143- expect ( within ( dialog ) . queryByRole ( 'tab' , { name : 'Delete' } ) ) . not . toBeInTheDocument ( ) ;
157+ // The wizard replaces the tab strip with one step at a time.
158+ expect ( within ( dialog ) . queryByRole ( 'tab' ) ) . not . toBeInTheDocument ( ) ;
159+ expect ( within ( dialog ) . getByLabelText ( 'Name' ) ) . toBeVisible ( ) ;
160+ expect ( within ( dialog ) . getByLabelText ( 'Step 1 of 4' ) ) . toBeInTheDocument ( ) ;
144161
145- await user . click ( within ( dialog ) . getByRole ( 'tab' , { name : 'Layout' } ) ) ;
162+ await user . type ( within ( dialog ) . getByLabelText ( 'Name' ) , 'Reading' ) ;
163+ await user . click ( within ( dialog ) . getByRole ( 'button' , { name : 'Next' } ) ) ;
164+
165+ // Layout step.
146166 expect ( within ( dialog ) . getByRole ( 'radio' , { name : / C l a s s i c / } ) ) . toBeChecked ( ) ;
167+ await user . click ( within ( dialog ) . getByRole ( 'button' , { name : 'Next' } ) ) ;
147168
148- await user . click ( within ( dialog ) . getByRole ( 'tab' , { name : 'Sources' } ) ) ;
169+ // Feeds step: only the subscription picker.
149170 expect (
150- within ( dialog ) . getByText ( 'Choose which of your subscriptions appear in this space.' )
171+ within ( dialog ) . getByText (
172+ 'Pick the subscriptions whose posts make up this space’s main feed.'
173+ )
151174 ) . toBeVisible ( ) ;
175+ expect ( within ( dialog ) . getByRole ( 'button' , { name : 'All subscriptions' } ) ) . toBeVisible ( ) ;
152176 expect ( within ( dialog ) . getByRole ( 'listitem' , { name : 'Example Blog' } ) ) . toBeVisible ( ) ;
177+ expect ( within ( dialog ) . queryByRole ( 'combobox' , { name : 'Tags' } ) ) . not . toBeInTheDocument ( ) ;
178+ await user . click ( within ( dialog ) . getByRole ( 'button' , { name : 'Next' } ) ) ;
179+
180+ // Topics step is last, so it carries the Create button.
181+ expect ( within ( dialog ) . getByRole ( 'combobox' , { name : 'Tags' } ) ) . toBeVisible ( ) ;
182+ expect ( within ( dialog ) . getByRole ( 'combobox' , { name : 'Languages' } ) ) . toBeVisible ( ) ;
183+ expect ( await within ( dialog ) . findByRole ( 'button' , { name : 'Create' } ) ) . toBeVisible ( ) ;
184+
185+ // Back returns to the previous step.
186+ await user . click ( within ( dialog ) . getByRole ( 'button' , { name : 'Back' } ) ) ;
187+ expect ( within ( dialog ) . getByRole ( 'button' , { name : 'All subscriptions' } ) ) . toBeVisible ( ) ;
153188 } ) ;
154189
155- it ( 'shows a required error once the name is cleared ' , async ( ) => {
190+ it ( 'cannot advance past the identity step without a name ' , async ( ) => {
156191 const { user } = setup ( ) ;
157192
158- const input = screen . getByLabelText ( 'Name' ) ;
159- await user . type ( input , 'Reading' ) ;
160- await user . clear ( input ) ;
193+ expect ( screen . getByRole ( 'button' , { name : 'Next' } ) ) . toBeDisabled ( ) ;
194+
195+ await user . type ( screen . getByLabelText ( 'Name' ) , 'Reading' ) ;
196+ await user . clear ( screen . getByLabelText ( 'Name' ) ) ;
161197
162198 expect ( await screen . findByText ( 'Name is required' ) ) . toBeVisible ( ) ;
163- expect ( screen . getByRole ( 'button' , { name : 'Create ' } ) ) . toBeDisabled ( ) ;
199+ expect ( screen . getByRole ( 'button' , { name : 'Next ' } ) ) . toBeDisabled ( ) ;
164200 } ) ;
165201
166202 it ( 'rejects a name longer than the maximum length' , async ( ) => {
@@ -169,7 +205,7 @@ describe( 'CreateSpaceModal', () => {
169205 await user . type ( screen . getByLabelText ( 'Name' ) , 'a' . repeat ( 51 ) ) ;
170206
171207 expect ( await screen . findByText ( / 5 0 c h a r a c t e r s o r f e w e r / ) ) . toBeVisible ( ) ;
172- expect ( screen . getByRole ( 'button' , { name : 'Create ' } ) ) . toBeDisabled ( ) ;
208+ expect ( screen . getByRole ( 'button' , { name : 'Next ' } ) ) . toBeDisabled ( ) ;
173209 } ) ;
174210
175211 it ( 'rejects a duplicate name regardless of case' , async ( ) => {
@@ -178,7 +214,7 @@ describe( 'CreateSpaceModal', () => {
178214 await user . type ( screen . getByLabelText ( 'Name' ) , 'work' ) ;
179215
180216 expect ( await screen . findByText ( 'A space with this name already exists' ) ) . toBeVisible ( ) ;
181- expect ( screen . getByRole ( 'button' , { name : 'Create ' } ) ) . toBeDisabled ( ) ;
217+ expect ( screen . getByRole ( 'button' , { name : 'Next ' } ) ) . toBeDisabled ( ) ;
182218 } ) ;
183219
184220 it ( 'creates a space with identity and layout settings, updates the caches, and closes' , async ( ) => {
@@ -187,17 +223,25 @@ describe( 'CreateSpaceModal', () => {
187223 const onBody = jest . fn ( ) ;
188224 mockCreateEndpoint ( 'Reading' , onBody ) ;
189225
226+ // Identity step.
190227 await user . type ( screen . getByLabelText ( 'Name' ) , 'Reading' ) ;
191228 await user . click (
192229 within ( screen . getByRole ( 'radiogroup' , { name : 'Accent color' } ) ) . getByRole ( 'radio' , {
193230 name : 'Green' ,
194231 } )
195232 ) ;
196233 await user . click ( screen . getByRole ( 'radio' , { name : 'Star' } ) ) ;
197- await user . click ( screen . getByRole ( 'tab' , { name : 'Layout' } ) ) ;
234+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ;
235+
236+ // Layout step.
198237 await user . click ( screen . getByRole ( 'radio' , { name : / C l a s s i c / } ) ) ;
199- await user . click ( screen . getByRole ( 'tab' , { name : 'Sources' } ) ) ;
238+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ;
239+
240+ // Feeds step.
200241 await user . click ( screen . getByRole ( 'button' , { name : 'Add Example Blog' } ) ) ;
242+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ;
243+
244+ // Topics step (last) carries the Create button.
201245 await user . click ( screen . getByRole ( 'button' , { name : 'Create' } ) ) ;
202246
203247 await waitFor ( ( ) => expect ( onClose ) . toHaveBeenCalled ( ) ) ;
@@ -243,10 +287,11 @@ describe( 'CreateSpaceModal', () => {
243287 const onBody = jest . fn ( ) ;
244288 mockCreateEndpoint ( 'Leitura' , onBody ) ;
245289
290+ // The languages field lives on the final Topics step.
291+ await reachTopicsStep ( user , 'Leitura' ) ;
246292 const dialog = screen . getByRole ( 'dialog' , { name : 'Create a new space' } ) ;
247293 expect ( within ( dialog ) . getByText ( 'Português' ) ) . toBeVisible ( ) ;
248294
249- await user . type ( screen . getByLabelText ( 'Name' ) , 'Leitura' ) ;
250295 await user . click ( screen . getByRole ( 'button' , { name : 'Create' } ) ) ;
251296
252297 await waitFor ( ( ) => expect ( onClose ) . toHaveBeenCalled ( ) ) ;
@@ -255,12 +300,31 @@ describe( 'CreateSpaceModal', () => {
255300 ) ;
256301 } ) ;
257302
303+ it ( 'sends topics entered in the wizard when creating' , async ( ) => {
304+ const { user, onClose } = setup ( ) ;
305+ const onBody = jest . fn ( ) ;
306+ mockCreateEndpoint ( 'Reading' , onBody ) ;
307+
308+ await reachTopicsStep ( user ) ;
309+ await user . type ( screen . getByRole ( 'combobox' , { name : 'Tags' } ) , 'design[Enter]' ) ;
310+ await user . type ( screen . getByRole ( 'combobox' , { name : 'Languages' } ) , 'English[Enter]' ) ;
311+ await user . click ( screen . getByRole ( 'button' , { name : 'Create' } ) ) ;
312+
313+ await waitFor ( ( ) => expect ( onClose ) . toHaveBeenCalled ( ) ) ;
314+ expect ( onBody ) . toHaveBeenCalledWith (
315+ expect . objectContaining ( {
316+ tags : [ 'design' ] ,
317+ languages : [ 'en' ] ,
318+ } )
319+ ) ;
320+ } ) ;
321+
258322 it ( 'sends no languages when the account has no locale' , async ( ) => {
259323 const { user, onClose } = setup ( ) ;
260324 const onBody = jest . fn ( ) ;
261325 mockCreateEndpoint ( 'Reading' , onBody ) ;
262326
263- await user . type ( screen . getByLabelText ( 'Name' ) , 'Reading' ) ;
327+ await reachTopicsStep ( user ) ;
264328 await user . click ( screen . getByRole ( 'button' , { name : 'Create' } ) ) ;
265329
266330 await waitFor ( ( ) => expect ( onClose ) . toHaveBeenCalled ( ) ) ;
@@ -271,7 +335,7 @@ describe( 'CreateSpaceModal', () => {
271335 const { user, onCreated } = setup ( ) ;
272336 mockCreateEndpoint ( 'Reading' ) ;
273337
274- await user . type ( screen . getByLabelText ( 'Name' ) , 'Reading' ) ;
338+ await reachTopicsStep ( user ) ;
275339 await user . click ( screen . getByRole ( 'button' , { name : 'Create' } ) ) ;
276340
277341 await waitFor ( ( ) =>
0 commit comments