@@ -137,6 +137,98 @@ describe('initCrowdIn', () => {
137137 expect ( container . style . position ) . toBe ( 'static' ) ;
138138 expect ( sidebar . contains ( container ) ) . toBe ( true ) ;
139139 } ) ;
140+
141+ it ( 'should wait for sphinx language picker before applying styling' , ( ) => {
142+ globalThis . document . body . innerHTML = `
143+ <div class="sidebar-sticky"></div>
144+ ` ;
145+
146+ initCrowdIn ( 'LizardByte' , 'sphinx' ) ;
147+
148+ expect ( ( ) => {
149+ jest . advanceTimersByTime ( 0 ) ;
150+ } ) . not . toThrow ( ) ;
151+
152+ globalThis . document . body . insertAdjacentHTML ( 'beforeend' , `
153+ <div id="crowdin-language-picker" class="cr-position-bottom-left">
154+ <div class="cr-picker-button"></div>
155+ <div class="cr-picker-submenu"></div>
156+ </div>
157+ ` ) ;
158+
159+ jest . advanceTimersByTime ( 50 ) ;
160+
161+ const container = document . getElementById ( 'crowdin-language-picker' ) ;
162+ const sidebar = document . getElementsByClassName ( 'sidebar-sticky' ) [ 0 ] ;
163+
164+ expect ( container . classList . contains ( 'cr-position-bottom-left' ) ) . toBe ( false ) ;
165+ expect ( container . style . position ) . toBe ( 'relative' ) ;
166+ expect ( sidebar . contains ( container ) ) . toBe ( true ) ;
167+ } ) ;
168+
169+ it ( 'should wait for rustdoc language picker before applying styling' , ( ) => {
170+ globalThis . document . body . innerHTML = `
171+ <nav class="sidebar">
172+ <div class="sidebar-elems"></div>
173+ </nav>
174+ ` ;
175+
176+ initCrowdIn ( 'LizardByte' , 'rustdoc' ) ;
177+
178+ expect ( ( ) => {
179+ jest . advanceTimersByTime ( 0 ) ;
180+ } ) . not . toThrow ( ) ;
181+
182+ globalThis . document . body . insertAdjacentHTML ( 'beforeend' , `
183+ <div id="crowdin-language-picker" class="cr-position-bottom-left">
184+ <div class="cr-picker-button"></div>
185+ <div class="cr-picker-submenu"></div>
186+ </div>
187+ ` ) ;
188+
189+ jest . advanceTimersByTime ( 50 ) ;
190+
191+ const container = document . getElementById ( 'crowdin-language-picker' ) ;
192+ const sidebar = document . getElementsByClassName ( 'sidebar-elems' ) [ 0 ] ;
193+
194+ expect ( container . classList . contains ( 'cr-position-bottom-left' ) ) . toBe ( false ) ;
195+ expect ( container . classList . contains ( 'rustdoc-crowdin-picker' ) ) . toBe ( true ) ;
196+ expect ( sidebar . contains ( container ) ) . toBe ( true ) ;
197+ } ) ;
198+
199+ it ( 'should move rustdoc language picker to sidebar when sidebar-elems is unavailable' , ( ) => {
200+ globalThis . document . body . innerHTML = `
201+ <nav class="sidebar"></nav>
202+ <div id="crowdin-language-picker" class="cr-position-bottom-left">
203+ <div class="cr-picker-button"></div>
204+ <div class="cr-picker-submenu"></div>
205+ </div>
206+ ` ;
207+
208+ initCrowdIn ( 'LizardByte' , 'rustdoc' ) ;
209+ jest . runAllTimers ( ) ;
210+
211+ const container = document . getElementById ( 'crowdin-language-picker' ) ;
212+ const sidebar = document . getElementsByClassName ( 'sidebar' ) [ 0 ] ;
213+
214+ expect ( container . classList . contains ( 'rustdoc-crowdin-picker' ) ) . toBe ( true ) ;
215+ expect ( sidebar . contains ( container ) ) . toBe ( true ) ;
216+ } ) ;
217+
218+ it ( 'should stop retrying platform styling after the retry limit' , ( ) => {
219+ globalThis . document . body . innerHTML = `
220+ <nav class="sidebar">
221+ <div class="sidebar-elems"></div>
222+ </nav>
223+ ` ;
224+
225+ initCrowdIn ( 'LizardByte' , 'rustdoc' ) ;
226+
227+ jest . advanceTimersByTime ( 0 ) ;
228+ jest . advanceTimersByTime ( 5000 ) ;
229+
230+ expect ( jest . getTimerCount ( ) ) . toBe ( 0 ) ;
231+ } ) ;
140232} ) ;
141233
142234describe ( 'Crowdin fetch interceptor' , ( ) => {
@@ -234,4 +326,39 @@ describe('Crowdin fetch interceptor', () => {
234326
235327 expect ( globalThis . fetch ) . toBe ( fetchAfterFirst ) ;
236328 } ) ;
329+
330+ it ( 'should continue when fetch is unavailable' , ( ) => {
331+ delete globalThis . fetch ;
332+
333+ initCrowdIn ( ) ;
334+ jest . runAllTimers ( ) ;
335+
336+ expect ( globalThis . proxyTranslator . init ) . toHaveBeenCalled ( ) ;
337+ } ) ;
338+
339+ it ( 'should pass non-string fetch inputs through unchanged' , async ( ) => {
340+ const mockFetch = globalThis . fetch ;
341+ const requestLike = new URL ( 'https://example.com/data.json' ) ;
342+
343+ initCrowdIn ( ) ;
344+ jest . runAllTimers ( ) ;
345+
346+ await globalThis . fetch ( requestLike ) ;
347+
348+ const calledUrl = mockFetch . mock . calls [ 0 ] [ 0 ] ;
349+ expect ( calledUrl ) . toBe ( requestLike ) ;
350+ } ) ;
351+
352+ it ( 'should pass invalid URL strings through unchanged' , async ( ) => {
353+ const mockFetch = globalThis . fetch ;
354+ const invalidUrl = 'not a valid absolute URL' ;
355+
356+ initCrowdIn ( ) ;
357+ jest . runAllTimers ( ) ;
358+
359+ await globalThis . fetch ( invalidUrl ) ;
360+
361+ const calledUrl = mockFetch . mock . calls [ 0 ] [ 0 ] ;
362+ expect ( calledUrl ) . toBe ( invalidUrl ) ;
363+ } ) ;
237364} ) ;
0 commit comments