@@ -327,3 +327,143 @@ test("clicking a transcript line seeks to that line and plays", async ({ page })
327327 expect ( t ) . toBeGreaterThanOrEqual ( start - 0.5 ) ;
328328 expect ( t ) . toBeLessThan ( start + 3 ) ;
329329} ) ;
330+
331+ /* ---------- Chapters (video3), sign language (video5), playlist (playlist2) ---------- */
332+
333+ async function openDemo ( page , name ) {
334+ await page . goto ( `/demos/${ name } .html` ) ;
335+ await page . waitForSelector ( ".able-controller" , { timeout : 20_000 } ) ;
336+ }
337+
338+ test ( "video3: selecting a chapter seeks to it and starts playback" , async ( { page } ) => {
339+ await openDemo ( page , "video3" ) ;
340+ const chapters = btn ( page , "chapters" ) ;
341+ const menu = page . locator ( '.able-popup[id$="-chapters-menu"]' ) ;
342+ const items = menu . locator ( "> li" ) ;
343+ await expect ( items ) . toHaveCount ( 2 ) ;
344+
345+ await chapters . focus ( ) ;
346+ await page . keyboard . press ( "Enter" ) ;
347+ await expect ( menu ) . toBeVisible ( ) ;
348+ await expect ( items . nth ( 0 ) ) . toBeFocused ( ) ;
349+
350+ await page . keyboard . press ( "ArrowDown" ) ;
351+ await expect ( items . nth ( 1 ) ) . toBeFocused ( ) ;
352+ const start = await page . evaluate ( ( ) => {
353+ const m = document . querySelector ( '.able-popup[id$="-chapters-menu"] > li:nth-child(2)' ) . textContent ;
354+ const [ mm , ss ] = m . split ( " - " ) [ 1 ] . split ( ":" ) . map ( Number ) ;
355+ return mm * 60 + ss ;
356+ } ) ;
357+ expect ( start ) . toBe ( 37 ) ;
358+
359+ await page . keyboard . press ( "Enter" ) ;
360+ await expect ( menu ) . toBeHidden ( ) ;
361+ await expect ( chapters ) . toBeFocused ( ) ;
362+ await expect . poll ( async ( ) => ( await mediaState ( page ) ) . t ) . toBeGreaterThanOrEqual ( start - 0.5 ) ;
363+ expect ( ( await mediaState ( page ) ) . t ) . toBeLessThan ( start + 3 ) ;
364+ await expect . poll ( async ( ) => ( await mediaState ( page ) ) . paused , {
365+ message : "selecting a chapter must start playback" ,
366+ } ) . toBe ( false ) ;
367+ } ) ;
368+
369+ test ( "video5: sign language window opens and both videos play in sync" , async ( { page } ) => {
370+ await openDemo ( page , "video5" ) ;
371+ const sign = page . locator ( ".able-sign-window" ) ;
372+ await expect ( sign ) . toBeHidden ( ) ;
373+
374+ await btn ( page , "sign" ) . focus ( ) ;
375+ await page . keyboard . press ( "Enter" ) ;
376+ await expect ( sign ) . toBeVisible ( ) ;
377+ const signVideo = sign . locator ( "video" ) ;
378+ await expect ( signVideo ) . toHaveCount ( 1 ) ;
379+
380+ const times = ( ) =>
381+ page . evaluate ( ( ) => {
382+ const [ main , sign ] = document . querySelectorAll ( "video" ) ;
383+ return { main : main . currentTime , sign : sign . currentTime , mp : main . paused , sp : sign . paused } ;
384+ } ) ;
385+
386+ await btn ( page , "play" ) . click ( ) ;
387+ await expect
388+ . poll ( async ( ) => {
389+ const s = await times ( ) ;
390+ return ! s . mp && ! s . sp && s . main > 1 ;
391+ } , { timeout : 15_000 , message : "both videos must be playing" } )
392+ . toBe ( true ) ;
393+ const s = await times ( ) ;
394+ expect ( Math . abs ( s . main - s . sign ) ) . toBeLessThan ( 1 ) ;
395+
396+ // Seeking the main video re-syncs the sign video.
397+ await btn ( page , "forward" ) . click ( ) ;
398+ await expect
399+ . poll ( async ( ) => {
400+ const t = await times ( ) ;
401+ return Math . abs ( t . main - t . sign ) ;
402+ } , { timeout : 10_000 } )
403+ . toBeLessThan ( 1 ) ;
404+
405+ await btn ( page , "play" ) . click ( ) ;
406+ await expect . poll ( async ( ) => {
407+ const t = await times ( ) ;
408+ return t . mp && t . sp ;
409+ } ) . toBe ( true ) ;
410+ } ) ;
411+
412+ test . describe ( "playlist2" , ( ) => {
413+ const current = ( page ) =>
414+ page . evaluate ( ( ) => {
415+ const lis = [ ...document . querySelectorAll ( ".able-playlist > li" ) ] ;
416+ return {
417+ index : lis . findIndex ( ( li ) => li . classList . contains ( "able-current" ) ) ,
418+ src : document . querySelector ( "video" ) . currentSrc ,
419+ } ;
420+ } ) ;
421+
422+ // A track change rebuilds the player; further presses are ignored until it settles.
423+ const settle = async ( page ) => {
424+ await page . waitForFunction ( ( ) => document . querySelector ( ".able-wrapper video" ) ?. readyState >= 3 ) ;
425+ await page . waitForTimeout ( 1000 ) ;
426+ } ;
427+
428+ test . beforeEach ( async ( { page } ) => {
429+ await openDemo ( page , "playlist2" ) ;
430+ await expect ( page . locator ( ".able-playlist > li" ) ) . toHaveCount ( 3 ) ;
431+ await expect . poll ( async ( ) => ( await current ( page ) ) . index , { timeout : 15_000 } ) . toBe ( 0 ) ;
432+ await settle ( page ) ;
433+ } ) ;
434+
435+ test ( "playlist items take focus and start playback when activated" , async ( { page } ) => {
436+ const itemButton = page . locator ( ".able-playlist > li button" ) . nth ( 2 ) ;
437+ await itemButton . focus ( ) ;
438+ await expect ( itemButton ) . toBeFocused ( ) ;
439+ await page . keyboard . press ( "Enter" ) ;
440+
441+ await expect . poll ( async ( ) => ( await current ( page ) ) . index , { timeout : 15_000 } ) . toBe ( 2 ) ;
442+ await expect ( itemButton ) . toHaveAttribute ( "aria-current" , "true" ) ;
443+ await expect . poll ( async ( ) => ( await mediaState ( page ) ) . paused , { timeout : 15_000 } ) . toBe ( false ) ;
444+ } ) ;
445+
446+ test ( "next and previous track buttons navigate the playlist" , async ( { page } ) => {
447+ await btn ( page , "next" ) . focus ( ) ;
448+ await page . keyboard . press ( "Enter" ) ;
449+ await expect . poll ( async ( ) => ( await current ( page ) ) . index , { timeout : 15_000 } ) . toBe ( 1 ) ;
450+ await settle ( page ) ;
451+
452+ await btn ( page , "previous" ) . focus ( ) ;
453+ await page . keyboard . press ( "Enter" ) ;
454+ await expect . poll ( async ( ) => ( await current ( page ) ) . index , { timeout : 15_000 } ) . toBe ( 0 ) ;
455+ await settle ( page ) ;
456+
457+ // Previous on the first track wraps to the last.
458+ await btn ( page , "previous" ) . focus ( ) ;
459+ await page . keyboard . press ( "Enter" ) ;
460+ await expect . poll ( async ( ) => ( await current ( page ) ) . index , { timeout : 15_000 } ) . toBe ( 2 ) ;
461+ await settle ( page ) ;
462+
463+ // Next on the last track wraps to the first.
464+ await btn ( page , "next" ) . focus ( ) ;
465+ await page . keyboard . press ( "Enter" ) ;
466+ await expect . poll ( async ( ) => ( await current ( page ) ) . index , { timeout : 15_000 } ) . toBe ( 0 ) ;
467+ await settle ( page ) ;
468+ } ) ;
469+ } ) ;
0 commit comments