1- import { vi , expect } from "vitest" ;
1+ import { vi , expect , afterAll } from "vitest" ;
22
33// vitest-cucumber maps each Given/When/Then to a separate test(). The DOM must
44// persist across steps within a scenario, but @solidjs/testing-library registers
@@ -39,6 +39,13 @@ import { render, screen, waitFor, fireEvent, cleanup } from "@solidjs/testing-li
3939
4040const feature = await loadFeature ( "../org-order-stability.feature" ) ;
4141
42+ // STL_SKIP_AUTO_CLEANUP is file-scoped (Vitest isolates each file in its own
43+ // module context), but clean it up explicitly so the env doesn't leak if
44+ // Vitest's isolation model changes in future versions.
45+ afterAll ( ( ) => {
46+ delete process . env . STL_SKIP_AUTO_CLEANUP ;
47+ } ) ;
48+
4249// ── Org entry fixtures ────────────────────────────────────────────────────────
4350const aliceEntry = { login : "alice" , avatarUrl : "" , type : "user" as const } ;
4451const acmeEntry = { login : "acme-corp" , avatarUrl : "" , type : "org" as const } ;
@@ -58,8 +65,10 @@ function makeOrgRepos(org: string): RepoEntry[] {
5865}
5966
6067// ── Helper: flat (non-accordion) org header order ────────────────────────────
68+ // Org names follow GitHub's [A-Za-z0-9-] pattern — no regex escaping needed.
6169function getOrgHeaderOrder ( orgNames : string [ ] ) : string [ ] {
62- const pattern = new RegExp ( `^(${ orgNames . join ( "|" ) } )$` ) ;
70+ const escaped = orgNames . map ( ( n ) => n . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) ) ;
71+ const pattern = new RegExp ( `^(${ escaped . join ( "|" ) } )$` ) ;
6372 return screen . getAllByText ( pattern ) . map ( ( el ) => el . textContent ! ) ;
6473}
6574
@@ -285,6 +294,79 @@ describeFeature(feature, ({ Scenario, Background, BeforeEachScenario, AfterEachS
285294 ) ;
286295 } ) ;
287296
297+ // ── S5: Org order stable in accordion layout after retry ────────────────
298+ Scenario (
299+ "S5 - Org order stable in accordion layout after retry" ,
300+ ( { Given, When, Then, And } ) => {
301+ const sevenOrgs = [ "alice" , "acme-corp" , "beta-org" , "charlie-co" , "delta-inc" , "echo-labs" , "foxtrot-io" ] ;
302+
303+ Given (
304+ 'the RepoSelector displays 7 orgs in accordion layout with "alice" first and "echo-labs" showing a Retry button' ,
305+ async ( ) => {
306+ vi . mocked ( api . fetchRepos ) . mockImplementation ( ( _client , org ) => {
307+ if ( org === "echo-labs" ) return Promise . reject ( new Error ( "echo load failed" ) ) ;
308+ return Promise . resolve ( makeOrgRepos ( org as string ) ) ;
309+ } ) ;
310+
311+ const sevenEntries = sevenOrgs . map ( ( login ) => ( {
312+ login,
313+ avatarUrl : "" ,
314+ type : login === "alice" ? ( "user" as const ) : ( "org" as const ) ,
315+ } ) ) ;
316+
317+ render ( ( ) => (
318+ < RepoSelector
319+ selectedOrgs = { sevenOrgs }
320+ orgEntries = { sevenEntries }
321+ selected = { [ ] }
322+ onChange = { vi . fn ( ) }
323+ />
324+ ) ) ;
325+
326+ // Wait for accordion mode to render, expand echo-labs to show Retry
327+ await waitFor ( ( ) => {
328+ screen . getByRole ( "button" , { name : / a l i c e / } ) ;
329+ } ) ;
330+
331+ const echoBtn = screen . getByRole ( "button" , { name : / e c h o - l a b s / } ) ;
332+ fireEvent . click ( echoBtn ) ;
333+
334+ await waitFor ( ( ) => {
335+ screen . getByText ( "Retry" ) ;
336+ } ) ;
337+ }
338+ ) ;
339+
340+ When (
341+ 'the user clicks Retry on "echo-labs" and its repos load successfully' ,
342+ async ( ) => {
343+ vi . mocked ( api . fetchRepos ) . mockImplementation ( ( _client , org ) =>
344+ Promise . resolve ( makeOrgRepos ( org as string ) )
345+ ) ;
346+
347+ fireEvent . click ( screen . getByText ( "Retry" ) ) ;
348+
349+ await waitFor ( ( ) => {
350+ screen . getByText ( "echo-labs-repo" ) ;
351+ } ) ;
352+ }
353+ ) ;
354+
355+ Then (
356+ 'the org header order remains "alice", "acme-corp", "beta-org", "charlie-co", "delta-inc", "echo-labs", "foxtrot-io"' ,
357+ ( ) => {
358+ const order = getAccordionOrder ( sevenOrgs ) ;
359+ expect ( order ) . toEqual ( sevenOrgs ) ;
360+ }
361+ ) ;
362+
363+ And ( "the currently expanded accordion panel remains expanded" , ( ) => {
364+ const echoBtn = screen . getByRole ( "button" , { name : / e c h o - l a b s / } ) ;
365+ expect ( echoBtn . getAttribute ( "aria-expanded" ) ) . toBe ( "true" ) ;
366+ } ) ;
367+ }
368+ ) ;
369+
288370 // ── S6: New org appears in correct sorted position with 6+ orgs ──────────
289371 Scenario (
290372 "S6 - New org appears in correct sorted position with 6+ orgs" ,
0 commit comments