@wordpress/components 25.9.1 → 25.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/build/alignment-matrix-control/cell.js +8 -5
- package/build/alignment-matrix-control/cell.js.map +1 -1
- package/build/alignment-matrix-control/index.js +27 -43
- package/build/alignment-matrix-control/index.js.map +1 -1
- package/build/alignment-matrix-control/utils.js +29 -9
- package/build/alignment-matrix-control/utils.js.map +1 -1
- package/build/autocomplete/index.js +104 -52
- package/build/autocomplete/index.js.map +1 -1
- package/build/circular-option-picker/circular-option-picker-option.js +20 -39
- package/build/circular-option-picker/circular-option-picker-option.js.map +1 -1
- package/build/circular-option-picker/circular-option-picker.js +11 -32
- package/build/circular-option-picker/circular-option-picker.js.map +1 -1
- package/build/circular-option-picker/types.js.map +1 -1
- package/build/color-palette/index.js +7 -2
- package/build/color-palette/index.js.map +1 -1
- package/build/color-picker/component.js +12 -2
- package/build/color-picker/component.js.map +1 -1
- package/build/color-picker/picker.js +77 -1
- package/build/color-picker/picker.js.map +1 -1
- package/build/color-picker/styles.js +8 -8
- package/build/color-picker/styles.js.map +1 -1
- package/build/color-picker/types.js.map +1 -1
- package/build/composite/v2.js +43 -0
- package/build/composite/v2.js.map +1 -0
- package/build/confirm-dialog/component.js +74 -8
- package/build/confirm-dialog/component.js.map +1 -1
- package/build/confirm-dialog/types.js.map +1 -1
- package/build/custom-gradient-picker/gradient-bar/control-points.js +13 -4
- package/build/custom-gradient-picker/gradient-bar/control-points.js.map +1 -1
- package/build/dropdown-menu-v2-ariakit/index.js +217 -0
- package/build/dropdown-menu-v2-ariakit/index.js.map +1 -0
- package/build/dropdown-menu-v2-ariakit/styles.js +157 -0
- package/build/dropdown-menu-v2-ariakit/styles.js.map +1 -0
- package/build/dropdown-menu-v2-ariakit/types.js +6 -0
- package/build/dropdown-menu-v2-ariakit/types.js.map +1 -0
- package/build/font-size-picker/utils.js +1 -1
- package/build/font-size-picker/utils.js.map +1 -1
- package/build/input-control/styles/input-control-styles.js +23 -23
- package/build/input-control/styles/input-control-styles.js.map +1 -1
- package/build/mobile/global-styles-context/utils.native.js +1 -1
- package/build/mobile/global-styles-context/utils.native.js.map +1 -1
- package/build/modal/index.js +45 -16
- package/build/modal/index.js.map +1 -1
- package/build/palette-edit/index.js +4 -0
- package/build/palette-edit/index.js.map +1 -1
- package/build/popover/index.js +34 -6
- package/build/popover/index.js.map +1 -1
- package/build/private-apis.js +18 -2
- package/build/private-apis.js.map +1 -1
- package/build/progress-bar/styles.js +5 -5
- package/build/progress-bar/styles.js.map +1 -1
- package/build/sandbox/index.js +1 -1
- package/build/sandbox/index.js.map +1 -1
- package/build/sandbox/index.native.js +1 -1
- package/build/sandbox/index.native.js.map +1 -1
- package/build/select-control/styles/select-control-styles.js +8 -8
- package/build/select-control/styles/select-control-styles.js.map +1 -1
- package/build/slot-fill/bubbles-virtually/slot-fill-provider.js +1 -1
- package/build/slot-fill/bubbles-virtually/slot-fill-provider.js.map +1 -1
- package/build/tabs/context.js +16 -0
- package/build/tabs/context.js.map +1 -0
- package/build/tabs/index.js +147 -0
- package/build/tabs/index.js.map +1 -0
- package/build/tabs/styles.js +38 -0
- package/build/tabs/styles.js.map +1 -0
- package/build/tabs/tab.js +46 -0
- package/build/tabs/tab.js.map +1 -0
- package/build/tabs/tablist.js +47 -0
- package/build/tabs/tablist.js.map +1 -0
- package/build/tabs/tabpanel.js +48 -0
- package/build/tabs/tabpanel.js.map +1 -0
- package/build/tabs/types.js +6 -0
- package/build/tabs/types.js.map +1 -0
- package/build/text/component.js +7 -6
- package/build/text/component.js.map +1 -1
- package/build/text/hook.js +6 -15
- package/build/text/hook.js.map +1 -1
- package/build/text/index.js.map +1 -1
- package/build/text/styles.js +7 -7
- package/build/text/styles.js.map +1 -1
- package/build/text/types.js.map +1 -1
- package/build/text/utils.js +17 -33
- package/build/text/utils.js.map +1 -1
- package/build/toggle-group-control/toggle-group-control-option-base/component.js +1 -0
- package/build/toggle-group-control/toggle-group-control-option-base/component.js.map +1 -1
- package/build/toolbar/toolbar/index.js +17 -10
- package/build/toolbar/toolbar/index.js.map +1 -1
- package/build/toolbar/toolbar/types.js.map +1 -1
- package/build/tools-panel/tools-panel-item/hook.js +2 -2
- package/build/tools-panel/tools-panel-item/hook.js.map +1 -1
- package/build/tools-panel/types.js.map +1 -1
- package/build/tooltip/index.js +2 -2
- package/build/tooltip/index.js.map +1 -1
- package/build/unit-control/utils.js +108 -0
- package/build/unit-control/utils.js.map +1 -1
- package/build/utils/unit-values.js +1 -1
- package/build/utils/unit-values.js.map +1 -1
- package/build-module/alignment-matrix-control/cell.js +7 -4
- package/build-module/alignment-matrix-control/cell.js.map +1 -1
- package/build-module/alignment-matrix-control/index.js +27 -43
- package/build-module/alignment-matrix-control/index.js.map +1 -1
- package/build-module/alignment-matrix-control/utils.js +29 -8
- package/build-module/alignment-matrix-control/utils.js.map +1 -1
- package/build-module/autocomplete/index.js +104 -52
- package/build-module/autocomplete/index.js.map +1 -1
- package/build-module/circular-option-picker/circular-option-picker-option.js +20 -39
- package/build-module/circular-option-picker/circular-option-picker-option.js.map +1 -1
- package/build-module/circular-option-picker/circular-option-picker.js +10 -31
- package/build-module/circular-option-picker/circular-option-picker.js.map +1 -1
- package/build-module/circular-option-picker/types.js.map +1 -1
- package/build-module/color-palette/index.js +7 -2
- package/build-module/color-palette/index.js.map +1 -1
- package/build-module/color-picker/component.js +13 -3
- package/build-module/color-picker/component.js.map +1 -1
- package/build-module/color-picker/picker.js +78 -2
- package/build-module/color-picker/picker.js.map +1 -1
- package/build-module/color-picker/styles.js +8 -8
- package/build-module/color-picker/styles.js.map +1 -1
- package/build-module/color-picker/types.js.map +1 -1
- package/build-module/composite/v2.js +15 -0
- package/build-module/composite/v2.js.map +1 -0
- package/build-module/confirm-dialog/component.js +72 -7
- package/build-module/confirm-dialog/component.js.map +1 -1
- package/build-module/confirm-dialog/types.js.map +1 -1
- package/build-module/custom-gradient-picker/gradient-bar/control-points.js +13 -4
- package/build-module/custom-gradient-picker/gradient-bar/control-points.js.map +1 -1
- package/build-module/dropdown-menu-v2-ariakit/index.js +199 -0
- package/build-module/dropdown-menu-v2-ariakit/index.js.map +1 -0
- package/build-module/dropdown-menu-v2-ariakit/styles.js +136 -0
- package/build-module/dropdown-menu-v2-ariakit/styles.js.map +1 -0
- package/build-module/dropdown-menu-v2-ariakit/types.js +2 -0
- package/build-module/dropdown-menu-v2-ariakit/types.js.map +1 -0
- package/build-module/font-size-picker/utils.js +1 -1
- package/build-module/font-size-picker/utils.js.map +1 -1
- package/build-module/input-control/styles/input-control-styles.js +23 -23
- package/build-module/input-control/styles/input-control-styles.js.map +1 -1
- package/build-module/mobile/global-styles-context/utils.native.js +2 -2
- package/build-module/mobile/global-styles-context/utils.native.js.map +1 -1
- package/build-module/modal/index.js +47 -18
- package/build-module/modal/index.js.map +1 -1
- package/build-module/palette-edit/index.js +4 -0
- package/build-module/palette-edit/index.js.map +1 -1
- package/build-module/popover/index.js +34 -6
- package/build-module/popover/index.js.map +1 -1
- package/build-module/private-apis.js +18 -2
- package/build-module/private-apis.js.map +1 -1
- package/build-module/progress-bar/styles.js +5 -5
- package/build-module/progress-bar/styles.js.map +1 -1
- package/build-module/sandbox/index.js +1 -1
- package/build-module/sandbox/index.js.map +1 -1
- package/build-module/sandbox/index.native.js +1 -1
- package/build-module/sandbox/index.native.js.map +1 -1
- package/build-module/select-control/styles/select-control-styles.js +8 -8
- package/build-module/select-control/styles/select-control-styles.js.map +1 -1
- package/build-module/slot-fill/bubbles-virtually/slot-fill-provider.js +1 -1
- package/build-module/slot-fill/bubbles-virtually/slot-fill-provider.js.map +1 -1
- package/build-module/tabs/context.js +12 -0
- package/build-module/tabs/context.js.map +1 -0
- package/build-module/tabs/index.js +142 -0
- package/build-module/tabs/index.js.map +1 -0
- package/build-module/tabs/styles.js +36 -0
- package/build-module/tabs/styles.js.map +1 -0
- package/build-module/tabs/tab.js +43 -0
- package/build-module/tabs/tab.js.map +1 -0
- package/build-module/tabs/tablist.js +41 -0
- package/build-module/tabs/tablist.js.map +1 -0
- package/build-module/tabs/tabpanel.js +43 -0
- package/build-module/tabs/tabpanel.js.map +1 -0
- package/build-module/tabs/types.js +2 -0
- package/build-module/tabs/types.js.map +1 -0
- package/build-module/text/component.js +6 -6
- package/build-module/text/component.js.map +1 -1
- package/build-module/text/hook.js +11 -19
- package/build-module/text/hook.js.map +1 -1
- package/build-module/text/index.js.map +1 -1
- package/build-module/text/styles.js +7 -7
- package/build-module/text/styles.js.map +1 -1
- package/build-module/text/types.js.map +1 -1
- package/build-module/text/utils.js +17 -10
- package/build-module/text/utils.js.map +1 -1
- package/build-module/toggle-group-control/toggle-group-control-option-base/component.js +1 -0
- package/build-module/toggle-group-control/toggle-group-control-option-base/component.js.map +1 -1
- package/build-module/toolbar/toolbar/index.js +18 -11
- package/build-module/toolbar/toolbar/index.js.map +1 -1
- package/build-module/toolbar/toolbar/types.js.map +1 -1
- package/build-module/tools-panel/tools-panel-item/hook.js +2 -2
- package/build-module/tools-panel/tools-panel-item/hook.js.map +1 -1
- package/build-module/tools-panel/types.js.map +1 -1
- package/build-module/tooltip/index.js +2 -2
- package/build-module/tooltip/index.js.map +1 -1
- package/build-module/unit-control/utils.js +108 -0
- package/build-module/unit-control/utils.js.map +1 -1
- package/build-module/utils/unit-values.js +1 -1
- package/build-module/utils/unit-values.js.map +1 -1
- package/build-style/style-rtl.css +17 -5
- package/build-style/style.css +17 -5
- package/build-types/alignment-matrix-control/cell.d.ts +1 -1
- package/build-types/alignment-matrix-control/cell.d.ts.map +1 -1
- package/build-types/alignment-matrix-control/index.d.ts.map +1 -1
- package/build-types/alignment-matrix-control/stories/index.story.d.ts.map +1 -1
- package/build-types/alignment-matrix-control/utils.d.ts +9 -9
- package/build-types/alignment-matrix-control/utils.d.ts.map +1 -1
- package/build-types/autocomplete/index.d.ts.map +1 -1
- package/build-types/circular-option-picker/circular-option-picker-option.d.ts.map +1 -1
- package/build-types/circular-option-picker/circular-option-picker.d.ts.map +1 -1
- package/build-types/circular-option-picker/types.d.ts +4 -6
- package/build-types/circular-option-picker/types.d.ts.map +1 -1
- package/build-types/color-palette/index.d.ts.map +1 -1
- package/build-types/color-picker/component.d.ts.map +1 -1
- package/build-types/color-picker/picker.d.ts +1 -1
- package/build-types/color-picker/picker.d.ts.map +1 -1
- package/build-types/color-picker/styles.d.ts.map +1 -1
- package/build-types/color-picker/types.d.ts +3 -0
- package/build-types/color-picker/types.d.ts.map +1 -1
- package/build-types/composite/v2.d.ts +12 -0
- package/build-types/composite/v2.d.ts.map +1 -0
- package/build-types/confirm-dialog/component.d.ts +70 -29
- package/build-types/confirm-dialog/component.d.ts.map +1 -1
- package/build-types/confirm-dialog/stories/index.story.d.ts +11 -0
- package/build-types/confirm-dialog/stories/index.story.d.ts.map +1 -0
- package/build-types/confirm-dialog/test/index.d.ts +2 -0
- package/build-types/confirm-dialog/test/index.d.ts.map +1 -0
- package/build-types/confirm-dialog/types.d.ts +32 -10
- package/build-types/confirm-dialog/types.d.ts.map +1 -1
- package/build-types/custom-gradient-picker/gradient-bar/control-points.d.ts.map +1 -1
- package/build-types/dropdown-menu-v2-ariakit/index.d.ts +11 -0
- package/build-types/dropdown-menu-v2-ariakit/index.d.ts.map +1 -0
- package/build-types/dropdown-menu-v2-ariakit/stories/index.story.d.ts +16 -0
- package/build-types/dropdown-menu-v2-ariakit/stories/index.story.d.ts.map +1 -0
- package/build-types/dropdown-menu-v2-ariakit/styles.d.ts +88 -0
- package/build-types/dropdown-menu-v2-ariakit/styles.d.ts.map +1 -0
- package/build-types/dropdown-menu-v2-ariakit/test/index.d.ts +2 -0
- package/build-types/dropdown-menu-v2-ariakit/test/index.d.ts.map +1 -0
- package/build-types/dropdown-menu-v2-ariakit/types.d.ts +174 -0
- package/build-types/dropdown-menu-v2-ariakit/types.d.ts.map +1 -0
- package/build-types/font-size-picker/utils.d.ts.map +1 -1
- package/build-types/heading/stories/index.story.d.ts.map +1 -1
- package/build-types/modal/index.d.ts.map +1 -1
- package/build-types/palette-edit/index.d.ts.map +1 -1
- package/build-types/popover/index.d.ts +1 -1
- package/build-types/popover/index.d.ts.map +1 -1
- package/build-types/popover/stories/e2e/index.story.d.ts +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/progress-bar/styles.d.ts.map +1 -1
- package/build-types/sandbox/index.d.ts.map +1 -1
- package/build-types/tabs/context.d.ts +8 -0
- package/build-types/tabs/context.d.ts.map +1 -0
- package/build-types/tabs/index.d.ts +13 -0
- package/build-types/tabs/index.d.ts.map +1 -0
- package/build-types/tabs/stories/index.story.d.ts +20 -0
- package/build-types/tabs/stories/index.story.d.ts.map +1 -0
- package/build-types/tabs/styles.d.ts +17 -0
- package/build-types/tabs/styles.d.ts.map +1 -0
- package/build-types/tabs/tab.d.ts +10 -0
- package/build-types/tabs/tab.d.ts.map +1 -0
- package/build-types/tabs/tablist.d.ts +7 -0
- package/build-types/tabs/tablist.d.ts.map +1 -0
- package/build-types/tabs/tabpanel.d.ts +7 -0
- package/build-types/tabs/tabpanel.d.ts.map +1 -0
- package/build-types/tabs/test/index.d.ts +2 -0
- package/build-types/tabs/test/index.d.ts.map +1 -0
- package/build-types/tabs/types.d.ts +134 -0
- package/build-types/tabs/types.d.ts.map +1 -0
- package/build-types/text/component.d.ts +4 -2
- package/build-types/text/component.d.ts.map +1 -1
- package/build-types/text/hook.d.ts +171 -165
- package/build-types/text/hook.d.ts.map +1 -1
- package/build-types/text/index.d.ts +2 -2
- package/build-types/text/index.d.ts.map +1 -1
- package/build-types/text/stories/index.story.d.ts +21 -0
- package/build-types/text/stories/index.story.d.ts.map +1 -0
- package/build-types/text/styles.d.ts +7 -7
- package/build-types/text/styles.d.ts.map +1 -1
- package/build-types/text/types.d.ts +1 -1
- package/build-types/text/types.d.ts.map +1 -1
- package/build-types/text/utils.d.ts +56 -61
- package/build-types/text/utils.d.ts.map +1 -1
- package/build-types/toggle-group-control/toggle-group-control-option-base/component.d.ts.map +1 -1
- package/build-types/toolbar/stories/index.story.d.ts +5 -0
- package/build-types/toolbar/stories/index.story.d.ts.map +1 -1
- package/build-types/toolbar/toolbar/index.d.ts.map +1 -1
- package/build-types/toolbar/toolbar/types.d.ts +10 -0
- package/build-types/toolbar/toolbar/types.d.ts.map +1 -1
- package/build-types/tools-panel/tools-panel-item/hook.d.ts.map +1 -1
- package/build-types/tools-panel/types.d.ts +2 -0
- package/build-types/tools-panel/types.d.ts.map +1 -1
- package/build-types/tooltip/index.d.ts.map +1 -1
- package/build-types/unit-control/utils.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/alignment-matrix-control/cell.tsx +6 -2
- package/src/alignment-matrix-control/index.tsx +31 -54
- package/src/alignment-matrix-control/stories/index.story.tsx +3 -7
- package/src/alignment-matrix-control/test/index.tsx +117 -18
- package/src/alignment-matrix-control/utils.tsx +33 -9
- package/src/autocomplete/index.tsx +136 -77
- package/src/button/style.scss +1 -2
- package/src/circular-option-picker/circular-option-picker-option.tsx +24 -38
- package/src/circular-option-picker/circular-option-picker.tsx +11 -28
- package/src/circular-option-picker/types.ts +6 -5
- package/src/color-palette/index.tsx +6 -1
- package/src/color-picker/component.tsx +25 -3
- package/src/color-picker/picker.tsx +96 -2
- package/src/color-picker/styles.ts +0 -1
- package/src/color-picker/types.ts +3 -0
- package/src/composite/v2.ts +22 -0
- package/src/confirm-dialog/README.md +1 -1
- package/src/confirm-dialog/component.tsx +79 -13
- package/src/confirm-dialog/stories/{index.story.js → index.story.tsx} +26 -24
- package/src/confirm-dialog/test/{index.js → index.tsx} +3 -3
- package/src/confirm-dialog/types.ts +32 -12
- package/src/custom-gradient-picker/gradient-bar/control-points.tsx +32 -25
- package/src/dimension-control/test/__snapshots__/index.test.js.snap +8 -8
- package/src/dropdown-menu-v2-ariakit/README.md +324 -0
- package/src/dropdown-menu-v2-ariakit/index.tsx +318 -0
- package/src/dropdown-menu-v2-ariakit/stories/index.story.tsx +506 -0
- package/src/dropdown-menu-v2-ariakit/styles.ts +297 -0
- package/src/dropdown-menu-v2-ariakit/test/index.tsx +1139 -0
- package/src/dropdown-menu-v2-ariakit/types.ts +186 -0
- package/src/font-size-picker/utils.ts +2 -1
- package/src/heading/stories/index.story.tsx +2 -4
- package/src/input-control/styles/input-control-styles.tsx +2 -2
- package/src/mobile/global-styles-context/utils.native.js +2 -2
- package/src/modal/index.tsx +58 -22
- package/src/modal/test/index.tsx +29 -0
- package/src/notice/style.scss +0 -1
- package/src/palette-edit/index.tsx +4 -0
- package/src/popover/index.tsx +99 -57
- package/src/popover/style.scss +9 -0
- package/src/private-apis.ts +31 -1
- package/src/progress-bar/styles.ts +19 -4
- package/src/sandbox/index.native.js +1 -1
- package/src/sandbox/index.tsx +3 -1
- package/src/select-control/styles/select-control-styles.ts +2 -2
- package/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx +1 -1
- package/src/tabs/README.md +242 -0
- package/src/tabs/context.ts +13 -0
- package/src/tabs/index.tsx +167 -0
- package/src/tabs/stories/index.story.tsx +352 -0
- package/src/tabs/styles.ts +103 -0
- package/src/tabs/tab.tsx +39 -0
- package/src/tabs/tablist.tsx +40 -0
- package/src/tabs/tabpanel.tsx +42 -0
- package/src/tabs/test/index.tsx +1133 -0
- package/src/tabs/types.ts +142 -0
- package/src/text/README.md +2 -2
- package/src/text/{component.js → component.tsx} +10 -6
- package/src/text/{hook.js → hook.ts} +12 -15
- package/src/text/stories/index.story.tsx +80 -0
- package/src/text/types.ts +1 -6
- package/src/text/{utils.js → utils.ts} +40 -14
- package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +16 -0
- package/src/toggle-group-control/toggle-group-control-option-base/component.tsx +1 -0
- package/src/toolbar/stories/index.story.tsx +15 -0
- package/src/toolbar/test/index.tsx +8 -0
- package/src/toolbar/toolbar/README.md +9 -0
- package/src/toolbar/toolbar/index.tsx +21 -12
- package/src/toolbar/toolbar/style.scss +9 -0
- package/src/toolbar/toolbar/types.ts +10 -0
- package/src/tools-panel/tools-panel/README.md +3 -0
- package/src/tools-panel/tools-panel-item/hook.ts +4 -6
- package/src/tools-panel/types.ts +2 -0
- package/src/tooltip/index.tsx +2 -3
- package/src/unit-control/utils.ts +124 -0
- package/src/utils/unit-values.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/text/stories/index.story.js +0 -53
- /package/src/text/{index.js → index.ts} +0 -0
- /package/src/text/{styles.js → styles.ts} +0 -0
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { wordpress, category, media } from '@wordpress/icons';
|
|
11
|
+
import { useState } from '@wordpress/element';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal dependencies
|
|
15
|
+
*/
|
|
16
|
+
import Tabs from '..';
|
|
17
|
+
import type { TabsProps } from '../types';
|
|
18
|
+
import type { IconType } from '../../icon';
|
|
19
|
+
|
|
20
|
+
type Tab = {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
content: React.ReactNode;
|
|
24
|
+
tab: {
|
|
25
|
+
className?: string;
|
|
26
|
+
icon?: IconType;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const TABS: Tab[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'alpha',
|
|
34
|
+
title: 'Alpha',
|
|
35
|
+
content: 'Selected tab: Alpha',
|
|
36
|
+
tab: { className: 'alpha-class', icon: wordpress },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'beta',
|
|
40
|
+
title: 'Beta',
|
|
41
|
+
content: 'Selected tab: Beta',
|
|
42
|
+
tab: { className: 'beta-class', icon: category },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'gamma',
|
|
46
|
+
title: 'Gamma',
|
|
47
|
+
content: 'Selected tab: Gamma',
|
|
48
|
+
tab: { className: 'gamma-class', icon: media },
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const TABS_WITH_DELTA: Tab[] = [
|
|
53
|
+
...TABS,
|
|
54
|
+
{
|
|
55
|
+
id: 'delta',
|
|
56
|
+
title: 'Delta',
|
|
57
|
+
content: 'Selected tab: Delta',
|
|
58
|
+
tab: { className: 'delta-class', icon: media },
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const UncontrolledTabs = ( {
|
|
63
|
+
tabs,
|
|
64
|
+
showTabIcons = false,
|
|
65
|
+
...props
|
|
66
|
+
}: Omit< TabsProps, 'children' > & {
|
|
67
|
+
tabs: Tab[];
|
|
68
|
+
showTabIcons?: boolean;
|
|
69
|
+
} ) => {
|
|
70
|
+
return (
|
|
71
|
+
<Tabs { ...props }>
|
|
72
|
+
<Tabs.TabList>
|
|
73
|
+
{ tabs.map( ( tabObj ) => (
|
|
74
|
+
<Tabs.Tab
|
|
75
|
+
key={ tabObj.id }
|
|
76
|
+
id={ tabObj.id }
|
|
77
|
+
className={ tabObj.tab.className }
|
|
78
|
+
disabled={ tabObj.tab.disabled }
|
|
79
|
+
icon={ showTabIcons ? tabObj.tab.icon : undefined }
|
|
80
|
+
>
|
|
81
|
+
{ showTabIcons ? null : tabObj.title }
|
|
82
|
+
</Tabs.Tab>
|
|
83
|
+
) ) }
|
|
84
|
+
</Tabs.TabList>
|
|
85
|
+
{ tabs.map( ( tabObj ) => (
|
|
86
|
+
<Tabs.TabPanel key={ tabObj.id } id={ tabObj.id }>
|
|
87
|
+
{ tabObj.content }
|
|
88
|
+
</Tabs.TabPanel>
|
|
89
|
+
) ) }
|
|
90
|
+
</Tabs>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const ControlledTabs = ( {
|
|
95
|
+
tabs,
|
|
96
|
+
showTabIcons = false,
|
|
97
|
+
...props
|
|
98
|
+
}: Omit< TabsProps, 'children' > & {
|
|
99
|
+
tabs: Tab[];
|
|
100
|
+
showTabIcons?: boolean;
|
|
101
|
+
} ) => {
|
|
102
|
+
const [ selectedTabId, setSelectedTabId ] = useState<
|
|
103
|
+
string | undefined | null
|
|
104
|
+
>( props.selectedTabId );
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Tabs
|
|
108
|
+
{ ...props }
|
|
109
|
+
selectedTabId={ selectedTabId }
|
|
110
|
+
onSelect={ ( selectedId ) => {
|
|
111
|
+
setSelectedTabId( selectedId );
|
|
112
|
+
props.onSelect?.( selectedId );
|
|
113
|
+
} }
|
|
114
|
+
>
|
|
115
|
+
<Tabs.TabList>
|
|
116
|
+
{ tabs.map( ( tabObj ) => (
|
|
117
|
+
<Tabs.Tab
|
|
118
|
+
key={ tabObj.id }
|
|
119
|
+
id={ tabObj.id }
|
|
120
|
+
className={ tabObj.tab.className }
|
|
121
|
+
disabled={ tabObj.tab.disabled }
|
|
122
|
+
icon={ showTabIcons ? tabObj.tab.icon : undefined }
|
|
123
|
+
>
|
|
124
|
+
{ showTabIcons ? null : tabObj.title }
|
|
125
|
+
</Tabs.Tab>
|
|
126
|
+
) ) }
|
|
127
|
+
</Tabs.TabList>
|
|
128
|
+
{ tabs.map( ( tabObj ) => (
|
|
129
|
+
<Tabs.TabPanel key={ tabObj.id } id={ tabObj.id }>
|
|
130
|
+
{ tabObj.content }
|
|
131
|
+
</Tabs.TabPanel>
|
|
132
|
+
) ) }
|
|
133
|
+
</Tabs>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const getSelectedTab = async () =>
|
|
138
|
+
await screen.findByRole( 'tab', { selected: true } );
|
|
139
|
+
|
|
140
|
+
let originalGetClientRects: () => DOMRectList;
|
|
141
|
+
|
|
142
|
+
describe( 'Tabs', () => {
|
|
143
|
+
beforeAll( () => {
|
|
144
|
+
originalGetClientRects = window.HTMLElement.prototype.getClientRects;
|
|
145
|
+
// Mocking `getClientRects()` is necessary to pass a check performed by
|
|
146
|
+
// the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
|
|
147
|
+
// from the `@wordpress/dom` package.
|
|
148
|
+
// @ts-expect-error We're not trying to comply to the DOM spec, only mocking
|
|
149
|
+
window.HTMLElement.prototype.getClientRects = function () {
|
|
150
|
+
return [ 'trick-jsdom-into-having-size-for-element-rect' ];
|
|
151
|
+
};
|
|
152
|
+
} );
|
|
153
|
+
|
|
154
|
+
afterAll( () => {
|
|
155
|
+
window.HTMLElement.prototype.getClientRects = originalGetClientRects;
|
|
156
|
+
} );
|
|
157
|
+
|
|
158
|
+
describe( 'Accessibility and semantics', () => {
|
|
159
|
+
it( 'should use the correct aria attributes', async () => {
|
|
160
|
+
render( <UncontrolledTabs tabs={ TABS } /> );
|
|
161
|
+
|
|
162
|
+
const tabList = screen.getByRole( 'tablist' );
|
|
163
|
+
const allTabs = screen.getAllByRole( 'tab' );
|
|
164
|
+
const selectedTabPanel = await screen.findByRole( 'tabpanel' );
|
|
165
|
+
|
|
166
|
+
expect( tabList ).toBeVisible();
|
|
167
|
+
expect( tabList ).toHaveAttribute(
|
|
168
|
+
'aria-orientation',
|
|
169
|
+
'horizontal'
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
expect( allTabs ).toHaveLength( TABS.length );
|
|
173
|
+
|
|
174
|
+
// The selected `tab` aria-controls the active `tabpanel`,
|
|
175
|
+
// which is `aria-labelledby` the selected `tab`.
|
|
176
|
+
expect( selectedTabPanel ).toBeVisible();
|
|
177
|
+
expect( allTabs[ 0 ] ).toHaveAttribute(
|
|
178
|
+
'aria-controls',
|
|
179
|
+
selectedTabPanel.getAttribute( 'id' )
|
|
180
|
+
);
|
|
181
|
+
expect( selectedTabPanel ).toHaveAttribute(
|
|
182
|
+
'aria-labelledby',
|
|
183
|
+
allTabs[ 0 ].getAttribute( 'id' )
|
|
184
|
+
);
|
|
185
|
+
} );
|
|
186
|
+
} );
|
|
187
|
+
|
|
188
|
+
describe( 'Tab Attributes', () => {
|
|
189
|
+
it( "should apply the tab's `className` to the tab button", async () => {
|
|
190
|
+
render( <UncontrolledTabs tabs={ TABS } /> );
|
|
191
|
+
|
|
192
|
+
expect(
|
|
193
|
+
await screen.findByRole( 'tab', { name: 'Alpha' } )
|
|
194
|
+
).toHaveClass( 'alpha-class' );
|
|
195
|
+
expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass(
|
|
196
|
+
'beta-class'
|
|
197
|
+
);
|
|
198
|
+
expect( screen.getByRole( 'tab', { name: 'Gamma' } ) ).toHaveClass(
|
|
199
|
+
'gamma-class'
|
|
200
|
+
);
|
|
201
|
+
} );
|
|
202
|
+
} );
|
|
203
|
+
|
|
204
|
+
describe( 'Tab Activation', () => {
|
|
205
|
+
it( 'defaults to automatic tab activation (pointer clicks)', async () => {
|
|
206
|
+
const user = userEvent.setup();
|
|
207
|
+
const mockOnSelect = jest.fn();
|
|
208
|
+
|
|
209
|
+
render(
|
|
210
|
+
<UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } />
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Alpha is the initially selected tab
|
|
214
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
215
|
+
expect(
|
|
216
|
+
await screen.findByRole( 'tabpanel', { name: 'Alpha' } )
|
|
217
|
+
).toBeInTheDocument();
|
|
218
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
219
|
+
|
|
220
|
+
// Click on Beta, make sure beta is the selected tab
|
|
221
|
+
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
|
|
222
|
+
|
|
223
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
224
|
+
expect(
|
|
225
|
+
screen.getByRole( 'tabpanel', { name: 'Beta' } )
|
|
226
|
+
).toBeInTheDocument();
|
|
227
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
228
|
+
|
|
229
|
+
// Click on Alpha, make sure beta is the selected tab
|
|
230
|
+
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
|
|
231
|
+
|
|
232
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
233
|
+
expect(
|
|
234
|
+
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
|
|
235
|
+
).toBeInTheDocument();
|
|
236
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
237
|
+
} );
|
|
238
|
+
|
|
239
|
+
it( 'defaults to automatic tab activation (arrow keys)', async () => {
|
|
240
|
+
const user = userEvent.setup();
|
|
241
|
+
const mockOnSelect = jest.fn();
|
|
242
|
+
|
|
243
|
+
render(
|
|
244
|
+
<UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } />
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// onSelect gets called on the initial render. It should be called
|
|
248
|
+
// with the first enabled tab, which is alpha.
|
|
249
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
250
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
251
|
+
|
|
252
|
+
// Tab to focus the tablist. Make sure alpha is focused.
|
|
253
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
254
|
+
expect( await getSelectedTab() ).not.toHaveFocus();
|
|
255
|
+
await user.keyboard( '[Tab]' );
|
|
256
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
257
|
+
|
|
258
|
+
// Navigate forward with arrow keys and make sure the Beta tab is
|
|
259
|
+
// selected automatically.
|
|
260
|
+
await user.keyboard( '[ArrowRight]' );
|
|
261
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
262
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
263
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
264
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
265
|
+
|
|
266
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
267
|
+
// selected automatically.
|
|
268
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
269
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
270
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
271
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
272
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
273
|
+
} );
|
|
274
|
+
|
|
275
|
+
it( 'wraps around the last/first tab when using arrow keys', async () => {
|
|
276
|
+
const user = userEvent.setup();
|
|
277
|
+
const mockOnSelect = jest.fn();
|
|
278
|
+
|
|
279
|
+
render(
|
|
280
|
+
<UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } />
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// onSelect gets called on the initial render.
|
|
284
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
285
|
+
|
|
286
|
+
// Tab to focus the tablist. Make sure Alpha is focused.
|
|
287
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
288
|
+
expect( await getSelectedTab() ).not.toHaveFocus();
|
|
289
|
+
await user.keyboard( '[Tab]' );
|
|
290
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
291
|
+
|
|
292
|
+
// Navigate backwards with arrow keys and make sure that the Gamma tab
|
|
293
|
+
// (the last tab) is selected automatically.
|
|
294
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
295
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
296
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
297
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
298
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
299
|
+
|
|
300
|
+
// Navigate forward with arrow keys. Make sure alpha (the first tab) is
|
|
301
|
+
// selected automatically.
|
|
302
|
+
await user.keyboard( '[ArrowRight]' );
|
|
303
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
304
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
305
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
306
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
307
|
+
} );
|
|
308
|
+
|
|
309
|
+
it( 'should not move tab selection when pressing the up/down arrow keys, unless the orientation is changed to `vertical`', async () => {
|
|
310
|
+
const user = userEvent.setup();
|
|
311
|
+
const mockOnSelect = jest.fn();
|
|
312
|
+
|
|
313
|
+
const { rerender } = render(
|
|
314
|
+
<UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } />
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// onSelect gets called on the initial render. It should be called
|
|
318
|
+
// with the first enabled tab, which is alpha.
|
|
319
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
320
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
321
|
+
|
|
322
|
+
// Tab to focus the tablist. Make sure alpha is focused.
|
|
323
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
324
|
+
expect( await getSelectedTab() ).not.toHaveFocus();
|
|
325
|
+
await user.keyboard( '[Tab]' );
|
|
326
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
327
|
+
|
|
328
|
+
// Press the arrow up key, nothing happens.
|
|
329
|
+
await user.keyboard( '[ArrowUp]' );
|
|
330
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
331
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
332
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
333
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
334
|
+
|
|
335
|
+
// Press the arrow down key, nothing happens
|
|
336
|
+
await user.keyboard( '[ArrowDown]' );
|
|
337
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
338
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
339
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
340
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
341
|
+
|
|
342
|
+
// Change orientation to `vertical`. When the orientation is vertical,
|
|
343
|
+
// left/right arrow keys are replaced by up/down arrow keys.
|
|
344
|
+
rerender(
|
|
345
|
+
<UncontrolledTabs
|
|
346
|
+
tabs={ TABS }
|
|
347
|
+
onSelect={ mockOnSelect }
|
|
348
|
+
orientation="vertical"
|
|
349
|
+
/>
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
expect( screen.getByRole( 'tablist' ) ).toHaveAttribute(
|
|
353
|
+
'aria-orientation',
|
|
354
|
+
'vertical'
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// Make sure alpha is still focused.
|
|
358
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
359
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
360
|
+
|
|
361
|
+
// Navigate forward with arrow keys and make sure the Beta tab is
|
|
362
|
+
// selected automatically.
|
|
363
|
+
await user.keyboard( '[ArrowDown]' );
|
|
364
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
365
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
366
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
367
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
368
|
+
|
|
369
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
370
|
+
// selected automatically.
|
|
371
|
+
await user.keyboard( '[ArrowUp]' );
|
|
372
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
373
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
374
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
375
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
376
|
+
|
|
377
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
378
|
+
// selected automatically.
|
|
379
|
+
await user.keyboard( '[ArrowUp]' );
|
|
380
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
381
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
382
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
|
|
383
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
384
|
+
|
|
385
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
386
|
+
// selected automatically.
|
|
387
|
+
await user.keyboard( '[ArrowDown]' );
|
|
388
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
389
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
390
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 5 );
|
|
391
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
392
|
+
} );
|
|
393
|
+
|
|
394
|
+
it( 'should move focus on a tab even if disabled with arrow key, but not with pointer clicks', async () => {
|
|
395
|
+
const user = userEvent.setup();
|
|
396
|
+
const mockOnSelect = jest.fn();
|
|
397
|
+
|
|
398
|
+
const TABS_WITH_DELTA_DISABLED = TABS_WITH_DELTA.map( ( tabObj ) =>
|
|
399
|
+
tabObj.id === 'delta'
|
|
400
|
+
? {
|
|
401
|
+
...tabObj,
|
|
402
|
+
tab: {
|
|
403
|
+
...tabObj.tab,
|
|
404
|
+
disabled: true,
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
: tabObj
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
render(
|
|
411
|
+
<UncontrolledTabs
|
|
412
|
+
tabs={ TABS_WITH_DELTA_DISABLED }
|
|
413
|
+
onSelect={ mockOnSelect }
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// onSelect gets called on the initial render. It should be called
|
|
418
|
+
// with the first enabled tab, which is alpha.
|
|
419
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
420
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
421
|
+
|
|
422
|
+
// Tab to focus the tablist. Make sure Alpha is focused.
|
|
423
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
424
|
+
expect( await getSelectedTab() ).not.toHaveFocus();
|
|
425
|
+
await user.keyboard( '[Tab]' );
|
|
426
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
427
|
+
// Confirm onSelect has not been re-called
|
|
428
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
429
|
+
|
|
430
|
+
// Press the right arrow key three times. Since the delta tab is disabled:
|
|
431
|
+
// - it won't be selected. The gamma tab will be selected instead, since
|
|
432
|
+
// it was the tab that was last selected before delta. Therefore, the
|
|
433
|
+
// `mockOnSelect` function gets called only twice (and not three times)
|
|
434
|
+
// - it will receive focus, when using arrow keys
|
|
435
|
+
await user.keyboard( '[ArrowRight][ArrowRight][ArrowRight]' );
|
|
436
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
437
|
+
expect(
|
|
438
|
+
screen.getByRole( 'tab', { name: 'Delta' } )
|
|
439
|
+
).toHaveFocus();
|
|
440
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
441
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
442
|
+
|
|
443
|
+
// Navigate backwards with arrow keys. The gamma tab receives focus.
|
|
444
|
+
// The `mockOnSelect` callback doesn't fire, since the gamma tab was
|
|
445
|
+
// already selected.
|
|
446
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
447
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
448
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
449
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
450
|
+
|
|
451
|
+
// Click on the disabled tab. Compared to using arrow keys to move the
|
|
452
|
+
// focus, disabled tabs ignore pointer clicks — and therefore, they don't
|
|
453
|
+
// receive focus, nor they cause the `mockOnSelect` function to fire.
|
|
454
|
+
await user.click( screen.getByRole( 'tab', { name: 'Delta' } ) );
|
|
455
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
456
|
+
expect( await getSelectedTab() ).toHaveFocus();
|
|
457
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
458
|
+
} );
|
|
459
|
+
|
|
460
|
+
it( 'should not focus the next tab when the Tab key is pressed', async () => {
|
|
461
|
+
const user = userEvent.setup();
|
|
462
|
+
|
|
463
|
+
render( <UncontrolledTabs tabs={ TABS } /> );
|
|
464
|
+
|
|
465
|
+
// Tab should initially focus the first tab in the tablist, which
|
|
466
|
+
// is Alpha.
|
|
467
|
+
await user.keyboard( '[Tab]' );
|
|
468
|
+
expect(
|
|
469
|
+
await screen.findByRole( 'tab', { name: 'Alpha' } )
|
|
470
|
+
).toHaveFocus();
|
|
471
|
+
|
|
472
|
+
// Because all other tabs should have `tabindex=-1`, pressing Tab
|
|
473
|
+
// should NOT move the focus to the next tab, which is Beta.
|
|
474
|
+
// Instead, focus should go to the currently selected tabpanel (alpha).
|
|
475
|
+
await user.keyboard( '[Tab]' );
|
|
476
|
+
expect(
|
|
477
|
+
await screen.findByRole( 'tabpanel', {
|
|
478
|
+
name: 'Alpha',
|
|
479
|
+
} )
|
|
480
|
+
).toHaveFocus();
|
|
481
|
+
} );
|
|
482
|
+
|
|
483
|
+
it( 'switches to manual tab activation when the `selectOnMove` prop is set to `false`', async () => {
|
|
484
|
+
const user = userEvent.setup();
|
|
485
|
+
const mockOnSelect = jest.fn();
|
|
486
|
+
|
|
487
|
+
render(
|
|
488
|
+
<UncontrolledTabs
|
|
489
|
+
tabs={ TABS }
|
|
490
|
+
onSelect={ mockOnSelect }
|
|
491
|
+
selectOnMove={ false }
|
|
492
|
+
/>
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
// onSelect gets called on the initial render. It should be called
|
|
496
|
+
// with the first enabled tab, which is alpha.
|
|
497
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
498
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
499
|
+
|
|
500
|
+
// Click on Alpha and make sure it is selected.
|
|
501
|
+
// onSelect shouldn't fire since the selected tab didn't change.
|
|
502
|
+
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
|
|
503
|
+
expect(
|
|
504
|
+
await screen.findByRole( 'tab', { name: 'Alpha' } )
|
|
505
|
+
).toHaveFocus();
|
|
506
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
507
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
508
|
+
|
|
509
|
+
// Navigate forward with arrow keys. Make sure Beta is focused, but
|
|
510
|
+
// that the tab selection happens only when pressing the spacebar
|
|
511
|
+
// or enter key. onSelect shouldn't fire since the selected tab
|
|
512
|
+
// didn't change.
|
|
513
|
+
await user.keyboard( '[ArrowRight]' );
|
|
514
|
+
expect(
|
|
515
|
+
await screen.findByRole( 'tab', { name: 'Beta' } )
|
|
516
|
+
).toHaveFocus();
|
|
517
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
518
|
+
|
|
519
|
+
await user.keyboard( '[Enter]' );
|
|
520
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
521
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
522
|
+
|
|
523
|
+
// Navigate forward with arrow keys. Make sure Gamma (last tab) is
|
|
524
|
+
// focused, but that tab selection happens only when pressing the
|
|
525
|
+
// spacebar or enter key. onSelect shouldn't fire since the selected
|
|
526
|
+
// tab didn't change.
|
|
527
|
+
await user.keyboard( '[ArrowRight]' );
|
|
528
|
+
expect(
|
|
529
|
+
await screen.findByRole( 'tab', { name: 'Gamma' } )
|
|
530
|
+
).toHaveFocus();
|
|
531
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
532
|
+
expect(
|
|
533
|
+
screen.getByRole( 'tab', { name: 'Gamma' } )
|
|
534
|
+
).toHaveFocus();
|
|
535
|
+
|
|
536
|
+
await user.keyboard( '[Space]' );
|
|
537
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
538
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
539
|
+
} );
|
|
540
|
+
} );
|
|
541
|
+
describe( 'Uncontrolled mode', () => {
|
|
542
|
+
describe( 'Without `initialTabId` prop', () => {
|
|
543
|
+
it( 'should render first tab', async () => {
|
|
544
|
+
render( <UncontrolledTabs tabs={ TABS } /> );
|
|
545
|
+
|
|
546
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
547
|
+
expect(
|
|
548
|
+
await screen.findByRole( 'tabpanel', { name: 'Alpha' } )
|
|
549
|
+
).toBeInTheDocument();
|
|
550
|
+
} );
|
|
551
|
+
it( 'should fall back to first enabled tab if the active tab is removed', async () => {
|
|
552
|
+
const { rerender } = render(
|
|
553
|
+
<UncontrolledTabs tabs={ TABS } />
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
// Remove first item from `TABS` array
|
|
557
|
+
rerender( <UncontrolledTabs tabs={ TABS.slice( 1 ) } /> );
|
|
558
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
559
|
+
} );
|
|
560
|
+
it( 'should not load any tab if the active tab is removed and there are no enabled tabs', async () => {
|
|
561
|
+
const TABS_WITH_BETA_GAMMA_DISABLED = TABS.map( ( tabObj ) =>
|
|
562
|
+
tabObj.id !== 'alpha'
|
|
563
|
+
? {
|
|
564
|
+
...tabObj,
|
|
565
|
+
tab: {
|
|
566
|
+
...tabObj.tab,
|
|
567
|
+
disabled: true,
|
|
568
|
+
},
|
|
569
|
+
}
|
|
570
|
+
: tabObj
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
const { rerender } = render(
|
|
574
|
+
<UncontrolledTabs tabs={ TABS_WITH_BETA_GAMMA_DISABLED } />
|
|
575
|
+
);
|
|
576
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
577
|
+
|
|
578
|
+
// Remove alpha
|
|
579
|
+
rerender(
|
|
580
|
+
<UncontrolledTabs
|
|
581
|
+
tabs={ TABS_WITH_BETA_GAMMA_DISABLED.slice( 1 ) }
|
|
582
|
+
/>
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
586
|
+
await waitFor( () =>
|
|
587
|
+
expect(
|
|
588
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
589
|
+
).not.toBeInTheDocument()
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// No tabpanel should be rendered either
|
|
593
|
+
expect(
|
|
594
|
+
screen.queryByRole( 'tabpanel' )
|
|
595
|
+
).not.toBeInTheDocument();
|
|
596
|
+
} );
|
|
597
|
+
} );
|
|
598
|
+
|
|
599
|
+
describe( 'With `initialTabId`', () => {
|
|
600
|
+
it( 'should render the tab set by `initialTabId` prop', async () => {
|
|
601
|
+
render(
|
|
602
|
+
<UncontrolledTabs tabs={ TABS } initialTabId="beta" />
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
606
|
+
} );
|
|
607
|
+
|
|
608
|
+
it( 'should not select a tab when `initialTabId` does not match any known tab', () => {
|
|
609
|
+
render(
|
|
610
|
+
<UncontrolledTabs
|
|
611
|
+
tabs={ TABS }
|
|
612
|
+
initialTabId="does-not-exist"
|
|
613
|
+
/>
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
617
|
+
expect(
|
|
618
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
619
|
+
).not.toBeInTheDocument();
|
|
620
|
+
|
|
621
|
+
// No tabpanel should be rendered either
|
|
622
|
+
expect(
|
|
623
|
+
screen.queryByRole( 'tabpanel' )
|
|
624
|
+
).not.toBeInTheDocument();
|
|
625
|
+
} );
|
|
626
|
+
it( 'should not change tabs when initialTabId is changed', async () => {
|
|
627
|
+
const { rerender } = render(
|
|
628
|
+
<UncontrolledTabs tabs={ TABS } initialTabId="beta" />
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
rerender(
|
|
632
|
+
<UncontrolledTabs tabs={ TABS } initialTabId="alpha" />
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
636
|
+
} );
|
|
637
|
+
|
|
638
|
+
it( 'should fall back to the tab associated to `initialTabId` if the currently active tab is removed', async () => {
|
|
639
|
+
const user = userEvent.setup();
|
|
640
|
+
const mockOnSelect = jest.fn();
|
|
641
|
+
|
|
642
|
+
const { rerender } = render(
|
|
643
|
+
<UncontrolledTabs
|
|
644
|
+
tabs={ TABS }
|
|
645
|
+
initialTabId="gamma"
|
|
646
|
+
onSelect={ mockOnSelect }
|
|
647
|
+
/>
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
651
|
+
|
|
652
|
+
await user.click(
|
|
653
|
+
screen.getByRole( 'tab', { name: 'Alpha' } )
|
|
654
|
+
);
|
|
655
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
656
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
657
|
+
|
|
658
|
+
rerender(
|
|
659
|
+
<UncontrolledTabs
|
|
660
|
+
tabs={ TABS.slice( 1 ) }
|
|
661
|
+
initialTabId="gamma"
|
|
662
|
+
onSelect={ mockOnSelect }
|
|
663
|
+
/>
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
667
|
+
} );
|
|
668
|
+
|
|
669
|
+
it( 'should fall back to the tab associated to `initialTabId` if the currently active tab becomes disabled', async () => {
|
|
670
|
+
const user = userEvent.setup();
|
|
671
|
+
const mockOnSelect = jest.fn();
|
|
672
|
+
|
|
673
|
+
const { rerender } = render(
|
|
674
|
+
<UncontrolledTabs
|
|
675
|
+
tabs={ TABS }
|
|
676
|
+
initialTabId="gamma"
|
|
677
|
+
onSelect={ mockOnSelect }
|
|
678
|
+
/>
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
682
|
+
|
|
683
|
+
await user.click(
|
|
684
|
+
screen.getByRole( 'tab', { name: 'Alpha' } )
|
|
685
|
+
);
|
|
686
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
687
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
688
|
+
|
|
689
|
+
const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) =>
|
|
690
|
+
tabObj.id === 'alpha'
|
|
691
|
+
? {
|
|
692
|
+
...tabObj,
|
|
693
|
+
tab: {
|
|
694
|
+
...tabObj.tab,
|
|
695
|
+
disabled: true,
|
|
696
|
+
},
|
|
697
|
+
}
|
|
698
|
+
: tabObj
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
rerender(
|
|
702
|
+
<UncontrolledTabs
|
|
703
|
+
tabs={ TABS_WITH_ALPHA_DISABLED }
|
|
704
|
+
initialTabId="gamma"
|
|
705
|
+
onSelect={ mockOnSelect }
|
|
706
|
+
/>
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
710
|
+
} );
|
|
711
|
+
|
|
712
|
+
it( 'should have no active tabs when the tab associated to `initialTabId` is removed while being the active tab', async () => {
|
|
713
|
+
const { rerender } = render(
|
|
714
|
+
<UncontrolledTabs tabs={ TABS } initialTabId="gamma" />
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
718
|
+
|
|
719
|
+
// Remove gamma
|
|
720
|
+
rerender(
|
|
721
|
+
<UncontrolledTabs
|
|
722
|
+
tabs={ TABS.slice( 0, 2 ) }
|
|
723
|
+
initialTabId="gamma"
|
|
724
|
+
/>
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 );
|
|
728
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
729
|
+
expect(
|
|
730
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
731
|
+
).not.toBeInTheDocument();
|
|
732
|
+
// No tabpanel should be rendered either
|
|
733
|
+
expect(
|
|
734
|
+
screen.queryByRole( 'tabpanel' )
|
|
735
|
+
).not.toBeInTheDocument();
|
|
736
|
+
} );
|
|
737
|
+
|
|
738
|
+
it( 'waits for the tab with the `initialTabId` to be present in the `tabs` array before selecting it', async () => {
|
|
739
|
+
const { rerender } = render(
|
|
740
|
+
<UncontrolledTabs tabs={ TABS } initialTabId="delta" />
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
// There should be no selected tab yet.
|
|
744
|
+
expect(
|
|
745
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
746
|
+
).not.toBeInTheDocument();
|
|
747
|
+
|
|
748
|
+
rerender(
|
|
749
|
+
<UncontrolledTabs
|
|
750
|
+
tabs={ TABS_WITH_DELTA }
|
|
751
|
+
initialTabId="delta"
|
|
752
|
+
/>
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Delta' );
|
|
756
|
+
} );
|
|
757
|
+
} );
|
|
758
|
+
|
|
759
|
+
describe( 'Disabled tab', () => {
|
|
760
|
+
it( 'should disable the tab when `disabled` is `true`', async () => {
|
|
761
|
+
const user = userEvent.setup();
|
|
762
|
+
const mockOnSelect = jest.fn();
|
|
763
|
+
|
|
764
|
+
const TABS_WITH_DELTA_DISABLED = TABS_WITH_DELTA.map(
|
|
765
|
+
( tabObj ) =>
|
|
766
|
+
tabObj.id === 'delta'
|
|
767
|
+
? {
|
|
768
|
+
...tabObj,
|
|
769
|
+
tab: {
|
|
770
|
+
...tabObj.tab,
|
|
771
|
+
disabled: true,
|
|
772
|
+
},
|
|
773
|
+
}
|
|
774
|
+
: tabObj
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
render(
|
|
778
|
+
<UncontrolledTabs
|
|
779
|
+
tabs={ TABS_WITH_DELTA_DISABLED }
|
|
780
|
+
onSelect={ mockOnSelect }
|
|
781
|
+
/>
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
expect(
|
|
785
|
+
screen.getByRole( 'tab', { name: 'Delta' } )
|
|
786
|
+
).toHaveAttribute( 'aria-disabled', 'true' );
|
|
787
|
+
|
|
788
|
+
// onSelect gets called on the initial render.
|
|
789
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
790
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
791
|
+
|
|
792
|
+
// onSelect should not be called since the disabled tab is
|
|
793
|
+
// highlighted, but not selected.
|
|
794
|
+
await user.keyboard( '[Tab]' );
|
|
795
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
796
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
797
|
+
|
|
798
|
+
// Delta (which is disabled) has focus
|
|
799
|
+
expect(
|
|
800
|
+
screen.getByRole( 'tab', { name: 'Delta' } )
|
|
801
|
+
).toHaveFocus();
|
|
802
|
+
|
|
803
|
+
// Alpha retains the selection, even if it's not focused.
|
|
804
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
805
|
+
} );
|
|
806
|
+
|
|
807
|
+
it( 'should select first enabled tab when the initial tab is disabled', async () => {
|
|
808
|
+
const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) =>
|
|
809
|
+
tabObj.id === 'alpha'
|
|
810
|
+
? {
|
|
811
|
+
...tabObj,
|
|
812
|
+
tab: {
|
|
813
|
+
...tabObj.tab,
|
|
814
|
+
disabled: true,
|
|
815
|
+
},
|
|
816
|
+
}
|
|
817
|
+
: tabObj
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
const { rerender } = render(
|
|
821
|
+
<UncontrolledTabs tabs={ TABS_WITH_ALPHA_DISABLED } />
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
// As alpha (first tab) is disabled,
|
|
825
|
+
// the first enabled tab should be beta.
|
|
826
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
827
|
+
|
|
828
|
+
// Re-enable all tabs
|
|
829
|
+
rerender( <UncontrolledTabs tabs={ TABS } /> );
|
|
830
|
+
|
|
831
|
+
// Even if the initial tab becomes enabled again, the selected
|
|
832
|
+
// tab doesn't change.
|
|
833
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
834
|
+
} );
|
|
835
|
+
|
|
836
|
+
it( 'should select first enabled tab when the tab associated to `initialTabId` is disabled', async () => {
|
|
837
|
+
const TABS_ONLY_GAMMA_ENABLED = TABS.map( ( tabObj ) =>
|
|
838
|
+
tabObj.id !== 'gamma'
|
|
839
|
+
? {
|
|
840
|
+
...tabObj,
|
|
841
|
+
tab: {
|
|
842
|
+
...tabObj.tab,
|
|
843
|
+
disabled: true,
|
|
844
|
+
},
|
|
845
|
+
}
|
|
846
|
+
: tabObj
|
|
847
|
+
);
|
|
848
|
+
const { rerender } = render(
|
|
849
|
+
<UncontrolledTabs
|
|
850
|
+
tabs={ TABS_ONLY_GAMMA_ENABLED }
|
|
851
|
+
initialTabId="beta"
|
|
852
|
+
/>
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
// As alpha (first tab), and beta (the initial tab), are both
|
|
856
|
+
// disabled the first enabled tab should be gamma.
|
|
857
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
858
|
+
|
|
859
|
+
// Re-enable all tabs
|
|
860
|
+
rerender(
|
|
861
|
+
<UncontrolledTabs tabs={ TABS } initialTabId="beta" />
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
// Even if the initial tab becomes enabled again, the selected tab doesn't
|
|
865
|
+
// change.
|
|
866
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
867
|
+
} );
|
|
868
|
+
|
|
869
|
+
it( 'should select the first enabled tab when the selected tab becomes disabled', async () => {
|
|
870
|
+
const mockOnSelect = jest.fn();
|
|
871
|
+
const { rerender } = render(
|
|
872
|
+
<UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } />
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
876
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
877
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
878
|
+
|
|
879
|
+
const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) =>
|
|
880
|
+
tabObj.id === 'alpha'
|
|
881
|
+
? {
|
|
882
|
+
...tabObj,
|
|
883
|
+
tab: {
|
|
884
|
+
...tabObj.tab,
|
|
885
|
+
disabled: true,
|
|
886
|
+
},
|
|
887
|
+
}
|
|
888
|
+
: tabObj
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
// Disable alpha
|
|
892
|
+
rerender(
|
|
893
|
+
<UncontrolledTabs
|
|
894
|
+
tabs={ TABS_WITH_ALPHA_DISABLED }
|
|
895
|
+
onSelect={ mockOnSelect }
|
|
896
|
+
/>
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
900
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
901
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
902
|
+
|
|
903
|
+
// Re-enable all tabs
|
|
904
|
+
rerender(
|
|
905
|
+
<UncontrolledTabs tabs={ TABS } onSelect={ mockOnSelect } />
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
909
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
910
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
911
|
+
} );
|
|
912
|
+
|
|
913
|
+
it( 'should select the first enabled tab when the tab associated to `initialTabId` becomes disabled while being the active tab', async () => {
|
|
914
|
+
const mockOnSelect = jest.fn();
|
|
915
|
+
|
|
916
|
+
const { rerender } = render(
|
|
917
|
+
<UncontrolledTabs
|
|
918
|
+
tabs={ TABS }
|
|
919
|
+
onSelect={ mockOnSelect }
|
|
920
|
+
initialTabId="gamma"
|
|
921
|
+
/>
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
925
|
+
|
|
926
|
+
const TABS_WITH_GAMMA_DISABLED = TABS.map( ( tabObj ) =>
|
|
927
|
+
tabObj.id === 'gamma'
|
|
928
|
+
? {
|
|
929
|
+
...tabObj,
|
|
930
|
+
tab: {
|
|
931
|
+
...tabObj.tab,
|
|
932
|
+
disabled: true,
|
|
933
|
+
},
|
|
934
|
+
}
|
|
935
|
+
: tabObj
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
// Disable gamma
|
|
939
|
+
rerender(
|
|
940
|
+
<UncontrolledTabs
|
|
941
|
+
tabs={ TABS_WITH_GAMMA_DISABLED }
|
|
942
|
+
onSelect={ mockOnSelect }
|
|
943
|
+
initialTabId="gamma"
|
|
944
|
+
/>
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
948
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
949
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
950
|
+
|
|
951
|
+
// Re-enable all tabs
|
|
952
|
+
rerender(
|
|
953
|
+
<UncontrolledTabs
|
|
954
|
+
tabs={ TABS }
|
|
955
|
+
onSelect={ mockOnSelect }
|
|
956
|
+
initialTabId="gamma"
|
|
957
|
+
/>
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
// Confirm that alpha is still selected, and that onSelect has
|
|
961
|
+
// not been called again.
|
|
962
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
963
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
964
|
+
} );
|
|
965
|
+
} );
|
|
966
|
+
} );
|
|
967
|
+
|
|
968
|
+
describe( 'Controlled mode', () => {
|
|
969
|
+
it( 'should render the tab specified by the `selectedTabId` prop', async () => {
|
|
970
|
+
render( <ControlledTabs tabs={ TABS } selectedTabId="beta" /> );
|
|
971
|
+
|
|
972
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
973
|
+
expect(
|
|
974
|
+
await screen.findByRole( 'tabpanel', { name: 'Beta' } )
|
|
975
|
+
).toBeInTheDocument();
|
|
976
|
+
} );
|
|
977
|
+
it( 'should render the specified `selectedTabId`, and ignore the `initialTabId` prop', async () => {
|
|
978
|
+
render(
|
|
979
|
+
<ControlledTabs
|
|
980
|
+
tabs={ TABS }
|
|
981
|
+
selectedTabId="gamma"
|
|
982
|
+
initialTabId="beta"
|
|
983
|
+
/>
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
987
|
+
} );
|
|
988
|
+
it( 'should not render any tab if `selectedTabId` does not match any known tab', async () => {
|
|
989
|
+
render(
|
|
990
|
+
<ControlledTabs
|
|
991
|
+
tabs={ TABS_WITH_DELTA }
|
|
992
|
+
selectedTabId="does-not-exist"
|
|
993
|
+
/>
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
997
|
+
// `waitFor` is needed here to prevent testing library from
|
|
998
|
+
// throwing a 'not wrapped in `act()`' error.
|
|
999
|
+
await waitFor( () =>
|
|
1000
|
+
expect(
|
|
1001
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
1002
|
+
).not.toBeInTheDocument()
|
|
1003
|
+
);
|
|
1004
|
+
// No tabpanel should be rendered either
|
|
1005
|
+
expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument();
|
|
1006
|
+
} );
|
|
1007
|
+
it( 'should not render any tab if the active tab is removed', async () => {
|
|
1008
|
+
const { rerender } = render(
|
|
1009
|
+
<ControlledTabs tabs={ TABS } selectedTabId="beta" />
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
// Remove beta
|
|
1013
|
+
rerender(
|
|
1014
|
+
<ControlledTabs
|
|
1015
|
+
tabs={ TABS.filter( ( tab ) => tab.id !== 'beta' ) }
|
|
1016
|
+
selectedTabId="beta"
|
|
1017
|
+
/>
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 );
|
|
1021
|
+
|
|
1022
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
1023
|
+
// `waitFor` is needed here to prevent testing library from
|
|
1024
|
+
// throwing a 'not wrapped in `act()`' error.
|
|
1025
|
+
await waitFor( () =>
|
|
1026
|
+
expect(
|
|
1027
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
1028
|
+
).not.toBeInTheDocument()
|
|
1029
|
+
);
|
|
1030
|
+
// No tabpanel should be rendered either
|
|
1031
|
+
expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument();
|
|
1032
|
+
|
|
1033
|
+
// Restore beta
|
|
1034
|
+
rerender( <ControlledTabs tabs={ TABS } selectedTabId="beta" /> );
|
|
1035
|
+
|
|
1036
|
+
// No tab should be selected i.e. it doesn't reselect the previously
|
|
1037
|
+
// removed tab.
|
|
1038
|
+
expect(
|
|
1039
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
1040
|
+
).not.toBeInTheDocument();
|
|
1041
|
+
// No tabpanel should be rendered either
|
|
1042
|
+
expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument();
|
|
1043
|
+
} );
|
|
1044
|
+
|
|
1045
|
+
describe( 'Disabled tab', () => {
|
|
1046
|
+
it( 'should not render any tab if `selectedTabId` refers to a disabled tab', async () => {
|
|
1047
|
+
const TABS_WITH_DELTA_WITH_BETA_DISABLED = TABS_WITH_DELTA.map(
|
|
1048
|
+
( tabObj ) =>
|
|
1049
|
+
tabObj.id === 'beta'
|
|
1050
|
+
? {
|
|
1051
|
+
...tabObj,
|
|
1052
|
+
tab: {
|
|
1053
|
+
...tabObj.tab,
|
|
1054
|
+
disabled: true,
|
|
1055
|
+
},
|
|
1056
|
+
}
|
|
1057
|
+
: tabObj
|
|
1058
|
+
);
|
|
1059
|
+
|
|
1060
|
+
render(
|
|
1061
|
+
<ControlledTabs
|
|
1062
|
+
tabs={ TABS_WITH_DELTA_WITH_BETA_DISABLED }
|
|
1063
|
+
selectedTabId="beta"
|
|
1064
|
+
/>
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
1068
|
+
await waitFor( () => {
|
|
1069
|
+
expect(
|
|
1070
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
1071
|
+
).not.toBeInTheDocument();
|
|
1072
|
+
} );
|
|
1073
|
+
// No tabpanel should be rendered either
|
|
1074
|
+
expect(
|
|
1075
|
+
screen.queryByRole( 'tabpanel' )
|
|
1076
|
+
).not.toBeInTheDocument();
|
|
1077
|
+
} );
|
|
1078
|
+
it( 'should not render any tab when the selected tab becomes disabled', async () => {
|
|
1079
|
+
const { rerender } = render(
|
|
1080
|
+
<ControlledTabs tabs={ TABS } selectedTabId="beta" />
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
1084
|
+
|
|
1085
|
+
const TABS_WITH_BETA_DISABLED = TABS.map( ( tabObj ) =>
|
|
1086
|
+
tabObj.id === 'beta'
|
|
1087
|
+
? {
|
|
1088
|
+
...tabObj,
|
|
1089
|
+
tab: {
|
|
1090
|
+
...tabObj.tab,
|
|
1091
|
+
disabled: true,
|
|
1092
|
+
},
|
|
1093
|
+
}
|
|
1094
|
+
: tabObj
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
rerender(
|
|
1098
|
+
<ControlledTabs
|
|
1099
|
+
tabs={ TABS_WITH_BETA_DISABLED }
|
|
1100
|
+
selectedTabId="beta"
|
|
1101
|
+
/>
|
|
1102
|
+
);
|
|
1103
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
1104
|
+
// `waitFor` is needed here to prevent testing library from
|
|
1105
|
+
// throwing a 'not wrapped in `act()`' error.
|
|
1106
|
+
await waitFor( () => {
|
|
1107
|
+
expect(
|
|
1108
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
1109
|
+
).not.toBeInTheDocument();
|
|
1110
|
+
} );
|
|
1111
|
+
// No tabpanel should be rendered either
|
|
1112
|
+
expect(
|
|
1113
|
+
screen.queryByRole( 'tabpanel' )
|
|
1114
|
+
).not.toBeInTheDocument();
|
|
1115
|
+
|
|
1116
|
+
// re-enable all tabs
|
|
1117
|
+
rerender(
|
|
1118
|
+
<ControlledTabs tabs={ TABS } selectedTabId="beta" />
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
// If the previously selected tab is reenabled, it should not
|
|
1122
|
+
// be reselected.
|
|
1123
|
+
expect(
|
|
1124
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
1125
|
+
).not.toBeInTheDocument();
|
|
1126
|
+
// No tabpanel should be rendered either
|
|
1127
|
+
expect(
|
|
1128
|
+
screen.queryByRole( 'tabpanel' )
|
|
1129
|
+
).not.toBeInTheDocument();
|
|
1130
|
+
} );
|
|
1131
|
+
} );
|
|
1132
|
+
} );
|
|
1133
|
+
} );
|