6
6
from playwright .sync_api import expect as playwright_expect
7
7
from typing_extensions import Literal
8
8
9
+ from shiny .types import ListOrTuple
10
+
9
11
from .._types import PatternOrStr , Timeout
12
+ from ..expect import expect_to_have_class , expect_to_have_style
13
+ from ..expect ._internal import expect_attribute_to_have_value
10
14
from ..expect ._internal import expect_class_to_have_value as _expect_class_to_have_value
11
- from ..expect ._internal import expect_style_to_have_value as _expect_style_to_have_value
12
15
from ._base import (
13
16
InitLocator ,
14
17
UiWithContainer ,
@@ -79,8 +82,8 @@ def expect_placement(
79
82
The maximum time to wait for the expectation to pass. Defaults to `None`.
80
83
"""
81
84
ex_class = "card-header" if location == "above" else "card-footer"
82
- playwright_expect ( self . loc_container . locator ( ".." )). to_have_class (
83
- ex_class , timeout = timeout
85
+ expect_to_have_class (
86
+ self . loc_container . locator ( ".." ), ex_class , timeout = timeout
84
87
)
85
88
86
89
@@ -424,23 +427,73 @@ def __init__(self, page: Page, id: str) -> None:
424
427
loc = "> li.nav-item" ,
425
428
)
426
429
427
- def expect_well (self , has_well : bool , * , timeout : Timeout = None ) -> None :
430
+ def expect_well (self , value : bool , * , timeout : Timeout = None ) -> None :
428
431
"""
429
432
Expects the navset pill list to have a well.
430
433
431
434
Parameters
432
435
----------
433
- has_well
434
- `True` if the navset pill list is expected to have a well, `False` otherwise.
436
+ value
437
+ `True` if the navset pill list is expected to be constructed with a well,
438
+ `False` otherwise.
435
439
timeout
436
440
The maximum time to wait for the expectation to pass. Defaults to `None`.
437
441
"""
438
- if has_well :
439
- playwright_expect (self .loc_container .locator (".." )).to_have_class ("well" )
440
- else :
441
- playwright_expect (self .loc_container .locator (".." )).not_to_have_class (
442
- "well"
442
+ _expect_class_to_have_value (
443
+ self .loc_container .locator (".." ), "well" , has_class = value , timeout = timeout
444
+ )
445
+
446
+ def expect_widths (
447
+ self , value : ListOrTuple [int ], * , timeout : Timeout = None
448
+ ) -> None :
449
+ """
450
+ Expects the navset pill list to have the specified widths.
451
+
452
+ Parameters
453
+ ----------
454
+ value
455
+ The expected widths of the navset pill list.
456
+ timeout
457
+ The maximum time to wait for the expectation to pass. Defaults to `None`.
458
+ """
459
+ widths = tuple (value )
460
+ assert len (widths ) == 2 , "`value=` must be a tuple of two integers"
461
+ assert all (
462
+ isinstance (width , int ) for width in widths
463
+ ), "`value=` must be integers"
464
+
465
+ loc_row_container = self .loc_container .locator (".." ).locator (".." )
466
+
467
+ # Make sure the two children are present
468
+ loc_complicated = loc_row_container .locator (
469
+ "xpath=." ,
470
+ has = self .page .locator (f"> div.col-sm-{ widths [0 ]} + div.col-sm-{ widths [1 ]} " ),
471
+ )
472
+
473
+ # Make sure there are only two children present
474
+ try :
475
+ playwright_expect (loc_complicated .locator ("> div" )).to_have_count (
476
+ 2 , timeout = timeout
443
477
)
478
+ except AssertionError as e :
479
+ # Make sure there are only two children
480
+ playwright_expect (loc_row_container .locator ("> div" )).to_have_count (
481
+ 2 , timeout = 1
482
+ )
483
+
484
+ expect_to_have_class (
485
+ loc_row_container .locator ("> div" ).first ,
486
+ f"col-sm-{ widths [0 ]} " ,
487
+ timeout = 1 ,
488
+ )
489
+ expect_to_have_class (
490
+ loc_row_container .locator ("> div" ).last ,
491
+ f"col-sm-{ widths [1 ]} " ,
492
+ timeout = 1 ,
493
+ )
494
+
495
+ # Re-raise the original exception if nothing could be debugged
496
+ raise e
444
497
445
498
446
499
class _NavsetCardBase (
@@ -563,12 +616,12 @@ def __init__(self, page: Page, id: str) -> None:
563
616
)
564
617
565
618
566
- class NavsetBar (
619
+ class _NavsetBarBase (
567
620
_ExpectNavsetSidebarM ,
568
621
_ExpectNavsetTitleM ,
569
622
_NavsetBase ,
570
623
):
571
- """Controller for :func:`shiny.ui.navset_bar` ."""
624
+ """Mixin class for common expectations of nav bars ."""
572
625
573
626
def __init__ (self , page : Page , id : str ) -> None :
574
627
"""
@@ -627,24 +680,32 @@ def expect_position(
627
680
timeout = timeout ,
628
681
)
629
682
else :
630
- playwright_expect (self ._loc_navbar ).to_have_class (
631
- re .compile (rf"{ position } " ), timeout = timeout
632
- )
683
+ expect_to_have_class (self ._loc_navbar , position , timeout = timeout )
633
684
634
- def expect_inverse (self , * , timeout : Timeout = None ) -> None :
685
+ def expect_inverse (
686
+ self ,
687
+ value : bool ,
688
+ * ,
689
+ timeout : Timeout = None ,
690
+ ) -> None :
635
691
"""
636
692
Expects the navset bar to be light text color if inverse is True
637
693
638
694
Parameters
639
695
----------
696
+ value
697
+ `True` if the navset bar is expected to have inverse text color, `False` otherwise.
640
698
timeout
641
699
The maximum time to wait for the expectation to pass. Defaults to `None`.
642
700
"""
643
- playwright_expect (self ._loc_navbar ).to_have_class (
644
- re .compile ("navbar-inverse" ), timeout = timeout
701
+ _expect_class_to_have_value (
702
+ self ._loc_navbar ,
703
+ "navbar-inverse" ,
704
+ has_class = value ,
705
+ timeout = timeout ,
645
706
)
646
707
647
- def expect_bg (self , bg : str , * , timeout : Timeout = None ) -> None :
708
+ def expect_bg (self , bg : PatternOrStr , * , timeout : Timeout = None ) -> None :
648
709
"""
649
710
Expects the navset bar to have the specified background color.
650
711
@@ -655,11 +716,14 @@ def expect_bg(self, bg: str, *, timeout: Timeout = None) -> None:
655
716
timeout
656
717
The maximum time to wait for the expectation to pass. Defaults to `None`.
657
718
"""
658
- _expect_style_to_have_value (
659
- self ._loc_navbar , "background-color" , f"{ bg } !important" , timeout = timeout
719
+ expect_to_have_style (
720
+ self ._loc_navbar ,
721
+ "background-color" ,
722
+ f"{ bg } !important" ,
723
+ timeout = timeout ,
660
724
)
661
725
662
- def expect_gap (self , gap : str , * , timeout : Timeout = None ) -> None :
726
+ def expect_gap (self , gap : PatternOrStr , * , timeout : Timeout = None ) -> None :
663
727
"""
664
728
Expects the navset bar to have the specified gap.
665
729
@@ -670,28 +734,128 @@ def expect_gap(self, gap: str, *, timeout: Timeout = None) -> None:
670
734
timeout
671
735
The maximum time to wait for the expectation to pass. Defaults to `None`.
672
736
"""
673
- _expect_style_to_have_value (
674
- self .get_loc_active_content (), "gap" , gap , timeout = timeout
737
+ expect_to_have_style (
738
+ self .get_loc_active_content (),
739
+ "gap" ,
740
+ gap ,
741
+ timeout = timeout ,
675
742
)
676
743
677
- def expect_layout (
678
- self , layout : Literal ["fluid" , "fixed" ] = "fluid" , * , timeout : Timeout = None
744
+ def expect_fluid (
745
+ self ,
746
+ value : bool ,
747
+ * ,
748
+ timeout : Timeout = None ,
679
749
) -> None :
680
750
"""
681
- Expects the navset bar to have the specified layout.
751
+ Expects the navset bar to have a fluid or fixed layout.
682
752
683
753
Parameters
684
754
----------
685
- layout
686
- The expected layout.
755
+ value
756
+ `True` if the layout is `fluid` or `False` if it is `fixed` .
687
757
timeout
688
758
The maximum time to wait for the expectation to pass. Defaults to `None`.
689
759
"""
690
- if layout == "fluid" :
691
- playwright_expect (
692
- self .loc_container .locator (".." ).locator (".." )
693
- ).to_have_class (re .compile ("container-fluid" ), timeout = timeout )
760
+ if value :
761
+ expect_to_have_class (
762
+ self ._loc_navbar .locator ("> div" ),
763
+ "container-fluid" ,
764
+ timeout = timeout ,
765
+ )
694
766
else :
695
- playwright_expect (self .loc_container .locator (".." )).to_have_class (
696
- re .compile ("container" ), timeout = timeout
767
+ expect_to_have_class (
768
+ self ._loc_navbar .locator ("> div" ),
769
+ "container" ,
770
+ timeout = timeout ,
697
771
)
772
+
773
+
774
+ class NavsetBar (_NavsetBarBase ):
775
+ """Controller for :func:`shiny.ui.navset_bar`."""
776
+
777
+
778
+ class PageNavbar (_NavsetBarBase ):
779
+ """Controller for :func:`shiny.ui.page_navbar`."""
780
+
781
+ def expect_fillable (self , value : bool , * , timeout : Timeout = None ) -> None :
782
+ """
783
+ Expects the main content area to be considered a fillable (i.e., flexbox) container
784
+
785
+ Parameters
786
+ ----------
787
+ value
788
+ `True` if the main content area is expected to be fillable, `False` otherwise.
789
+ timeout
790
+ The maximum time to wait for the expectation to pass. Defaults to `None`.
791
+ """
792
+ # confirm page is fillable
793
+ _expect_class_to_have_value (
794
+ self .page .locator ("body" ),
795
+ "bslib-page-fill" ,
796
+ has_class = value ,
797
+ timeout = timeout ,
798
+ )
799
+
800
+ # confirm content is fillable
801
+ _expect_class_to_have_value (
802
+ self .get_loc_active_content (),
803
+ "html-fill-container" ,
804
+ has_class = value ,
805
+ timeout = timeout ,
806
+ )
807
+
808
+ def expect_fillable_mobile (self , value : bool , * , timeout : Timeout = None ) -> None :
809
+ """
810
+ Expects the main content area to be considered a fillable (i.e., flexbox) container on mobile
811
+ This method will always call `.expect_fillable(True)` first to ensure the fillable property is set
812
+
813
+ Parameters
814
+ ----------
815
+ value
816
+ `True` if the main content area is expected to be fillable on mobile, `False` otherwise.
817
+ timeout
818
+ The maximum time to wait for the expectation to pass. Defaults to `None`.
819
+ """
820
+
821
+ # This is important since fillable_mobile needs fillable property to be True
822
+ self .expect_fillable (True , timeout = timeout )
823
+ _expect_class_to_have_value (
824
+ self .page .locator ("body" ),
825
+ "bslib-flow-mobile" ,
826
+ has_class = not value ,
827
+ timeout = timeout ,
828
+ )
829
+
830
+ def expect_window_title (
831
+ self , title : PatternOrStr , * , timeout : Timeout = None
832
+ ) -> None :
833
+ """
834
+ Expects the window title to have the specified text.
835
+
836
+ Parameters
837
+ ----------
838
+ title
839
+ The expected window title.
840
+ timeout
841
+ The maximum time to wait for the expectation to pass. Defaults to `None`.
842
+ """
843
+ playwright_expect (self .page ).to_have_title (title , timeout = timeout )
844
+
845
+ def expect_lang (self , lang : PatternOrStr , * , timeout : Timeout = None ) -> None :
846
+ """
847
+ Expects the HTML tag to have the specified language.
848
+
849
+ Parameters
850
+ ----------
851
+ lang
852
+ The expected language.
853
+ timeout
854
+ The maximum time to wait for the expectation to pass. Defaults to `None`.
855
+ """
856
+ expect_attribute_to_have_value (
857
+ self .page .locator ("html" ),
858
+ "lang" ,
859
+ lang ,
860
+ timeout = timeout ,
861
+ )
0 commit comments