@@ -78,6 +78,43 @@ describe("RehearsalPlayer media lifecycle", () => {
7878 expect ( screen . queryByTestId ( "rehearsal-loop-audio-error" ) ) . toBeNull ( ) ;
7979 } ) ;
8080
81+ it ( "ignores a stale play rejection after playback has resumed" , async ( ) => {
82+ installAudioBoundary ( ) ;
83+ let rejectFirstPlay : ( ( reason ?: unknown ) => void ) | undefined ;
84+ const play = vi
85+ . spyOn ( HTMLMediaElement . prototype , "play" )
86+ . mockImplementationOnce (
87+ ( ) =>
88+ new Promise < void > ( ( _resolve , reject ) => {
89+ rejectFirstPlay = reject ;
90+ } ) ,
91+ )
92+ . mockResolvedValue ( undefined ) ;
93+ renderPlayableSong ( ) ;
94+
95+ fireEvent . click (
96+ screen . getByRole ( "button" , { name : / S t a r t t h e c o u n t - i n / i } ) ,
97+ ) ;
98+ fireEvent . click ( screen . getByRole ( "button" , { name : / P a u s e / i } ) ) ;
99+ fireEvent . click ( screen . getByRole ( "button" , { name : / R e s u m e / i } ) ) ;
100+ expect ( play ) . toHaveBeenCalledTimes ( 2 ) ;
101+ expect ( screen . getByTestId ( "rehearsal-loop-next-action" ) ) . toHaveTextContent (
102+ / C o u n t i n / i,
103+ ) ;
104+
105+ await act ( async ( ) => {
106+ const staleInterruption = new Error ( "The earlier play request was interrupted" ) ;
107+ staleInterruption . name = "AbortError" ;
108+ rejectFirstPlay ?.( staleInterruption ) ;
109+ await Promise . resolve ( ) ;
110+ } ) ;
111+
112+ expect ( screen . getByTestId ( "rehearsal-loop-next-action" ) ) . toHaveTextContent (
113+ / C o u n t i n / i,
114+ ) ;
115+ expect ( screen . queryByTestId ( "rehearsal-loop-audio-error" ) ) . toBeNull ( ) ;
116+ } ) ;
117+
81118 it ( "restarts a selected loop when the media ends at the section boundary" , ( ) => {
82119 vi . useFakeTimers ( ) ;
83120 installAudioBoundary ( ) ;
0 commit comments