@react-aria/test-utils 1.0.0-nightly.5042 → 1.0.0-rc.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.
Files changed (124) hide show
  1. package/README.md +70 -0
  2. package/dist/import.mjs +6 -4
  3. package/dist/main.js +15 -23
  4. package/dist/main.js.map +1 -1
  5. package/dist/module.js +6 -4
  6. package/dist/module.js.map +1 -1
  7. package/dist/private/act.cjs +33 -0
  8. package/dist/private/act.cjs.map +1 -0
  9. package/dist/private/act.js +28 -0
  10. package/dist/private/act.js.map +1 -0
  11. package/dist/private/checkboxgroup.cjs +107 -0
  12. package/dist/private/checkboxgroup.cjs.map +1 -0
  13. package/dist/private/checkboxgroup.js +102 -0
  14. package/dist/private/checkboxgroup.js.map +1 -0
  15. package/dist/private/combobox.cjs +199 -0
  16. package/dist/private/combobox.cjs.map +1 -0
  17. package/dist/private/combobox.js +194 -0
  18. package/dist/private/combobox.js.map +1 -0
  19. package/dist/private/dialog.cjs +110 -0
  20. package/dist/private/dialog.cjs.map +1 -0
  21. package/dist/private/dialog.js +105 -0
  22. package/dist/private/dialog.js.map +1 -0
  23. package/dist/private/gridlist.cjs +173 -0
  24. package/dist/private/gridlist.cjs.map +1 -0
  25. package/dist/private/gridlist.js +168 -0
  26. package/dist/private/gridlist.js.map +1 -0
  27. package/dist/private/listbox.cjs +163 -0
  28. package/dist/private/listbox.cjs.map +1 -0
  29. package/dist/private/listbox.js +158 -0
  30. package/dist/private/listbox.js.map +1 -0
  31. package/dist/private/menu.cjs +265 -0
  32. package/dist/private/menu.cjs.map +1 -0
  33. package/dist/private/menu.js +260 -0
  34. package/dist/private/menu.js.map +1 -0
  35. package/dist/private/radiogroup.cjs +122 -0
  36. package/dist/private/radiogroup.cjs.map +1 -0
  37. package/dist/private/radiogroup.js +117 -0
  38. package/dist/private/radiogroup.js.map +1 -0
  39. package/dist/private/select.cjs +169 -0
  40. package/dist/private/select.cjs.map +1 -0
  41. package/dist/private/select.js +164 -0
  42. package/dist/private/select.js.map +1 -0
  43. package/dist/private/table.cjs +346 -0
  44. package/dist/private/table.cjs.map +1 -0
  45. package/dist/private/table.js +341 -0
  46. package/dist/private/table.js.map +1 -0
  47. package/dist/private/tabs.cjs +131 -0
  48. package/dist/private/tabs.cjs.map +1 -0
  49. package/dist/private/tabs.js +126 -0
  50. package/dist/private/tabs.js.map +1 -0
  51. package/dist/private/testSetup.cjs +87 -0
  52. package/dist/private/testSetup.cjs.map +1 -0
  53. package/dist/private/testSetup.js +81 -0
  54. package/dist/private/testSetup.js.map +1 -0
  55. package/dist/private/tree.cjs +181 -0
  56. package/dist/private/tree.cjs.map +1 -0
  57. package/dist/private/tree.js +176 -0
  58. package/dist/private/tree.js.map +1 -0
  59. package/dist/private/user.cjs +85 -0
  60. package/dist/private/user.cjs.map +1 -0
  61. package/dist/private/user.js +76 -0
  62. package/dist/private/user.js.map +1 -0
  63. package/dist/{userEventMaps.main.js → private/userEventMaps.cjs} +3 -3
  64. package/dist/private/userEventMaps.cjs.map +1 -0
  65. package/dist/{userEventMaps.mjs → private/userEventMaps.js} +3 -3
  66. package/dist/private/userEventMaps.js.map +1 -0
  67. package/dist/private/utils.cjs +136 -0
  68. package/dist/private/utils.cjs.map +1 -0
  69. package/dist/private/utils.js +127 -0
  70. package/dist/private/utils.js.map +1 -0
  71. package/dist/types/src/act.d.ts +4 -0
  72. package/dist/types/src/checkboxgroup.d.ts +47 -0
  73. package/dist/types/src/combobox.d.ts +87 -0
  74. package/dist/types/src/dialog.d.ts +37 -0
  75. package/dist/types/src/events.d.ts +25 -0
  76. package/dist/types/src/gridlist.d.ts +56 -0
  77. package/dist/types/src/index.d.ts +16 -0
  78. package/dist/types/src/listbox.d.ts +91 -0
  79. package/dist/types/src/menu.d.ts +112 -0
  80. package/dist/types/src/radiogroup.d.ts +47 -0
  81. package/dist/types/src/select.d.ts +74 -0
  82. package/dist/types/src/table.d.ts +120 -0
  83. package/dist/types/src/tabs.d.ts +59 -0
  84. package/dist/types/src/testSetup.d.ts +6 -0
  85. package/dist/types/src/tree.d.ts +62 -0
  86. package/dist/types/src/types.d.ts +143 -0
  87. package/dist/types/src/user.d.ts +49 -0
  88. package/dist/types/src/userEventMaps.d.ts +2 -0
  89. package/dist/types/src/utils.d.ts +29 -0
  90. package/package.json +26 -18
  91. package/src/act.ts +35 -0
  92. package/src/checkboxgroup.ts +165 -0
  93. package/src/combobox.ts +307 -0
  94. package/src/dialog.ts +155 -0
  95. package/src/gridlist.ts +278 -0
  96. package/src/index.ts +17 -3
  97. package/src/listbox.ts +300 -0
  98. package/src/menu.ts +479 -0
  99. package/src/radiogroup.ts +179 -0
  100. package/src/select.ts +273 -0
  101. package/src/table.ts +589 -0
  102. package/src/tabs.ts +204 -0
  103. package/src/testSetup.ts +41 -36
  104. package/src/tree.ts +290 -0
  105. package/src/types.ts +171 -0
  106. package/src/user.ts +153 -0
  107. package/src/userEventMaps.ts +1 -1
  108. package/src/utils.ts +155 -0
  109. package/dist/events.main.js +0 -37
  110. package/dist/events.main.js.map +0 -1
  111. package/dist/events.mjs +0 -31
  112. package/dist/events.module.js +0 -31
  113. package/dist/events.module.js.map +0 -1
  114. package/dist/testSetup.main.js +0 -82
  115. package/dist/testSetup.main.js.map +0 -1
  116. package/dist/testSetup.mjs +0 -76
  117. package/dist/testSetup.module.js +0 -76
  118. package/dist/testSetup.module.js.map +0 -1
  119. package/dist/types.d.ts +0 -16
  120. package/dist/types.d.ts.map +0 -1
  121. package/dist/userEventMaps.main.js.map +0 -1
  122. package/dist/userEventMaps.module.js +0 -38
  123. package/dist/userEventMaps.module.js.map +0 -1
  124. package/src/events.ts +0 -28
@@ -0,0 +1,163 @@
1
+ var $a84d573878cc1e5e$exports = require("./act.cjs");
2
+ var $022fcf8e360befed$exports = require("./utils.cjs");
3
+ var $ivkeh$testinglibrarydom = require("@testing-library/dom");
4
+
5
+
6
+ function $parcel$export(e, n, v, s) {
7
+ Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
8
+ }
9
+
10
+ $parcel$export(module.exports, "ListBoxTester", function () { return $c0f7ae61eac30828$export$54c24f7ee8577f4f; });
11
+ /*
12
+ * Copyright 2024 Adobe. All rights reserved.
13
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
14
+ * you may not use this file except in compliance with the License. You may obtain a copy
15
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software distributed under
18
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
19
+ * OF ANY KIND, either express or implied. See the License for the specific language
20
+ * governing permissions and limitations under the License.
21
+ */
22
+
23
+
24
+ class $c0f7ae61eac30828$export$54c24f7ee8577f4f {
25
+ constructor(opts){
26
+ let { root: root, user: user, interactionType: interactionType, advanceTimer: advanceTimer, layout: layout } = opts;
27
+ this.user = user;
28
+ this._interactionType = interactionType || 'mouse';
29
+ this._advanceTimer = advanceTimer;
30
+ this._layout = layout || 'stack';
31
+ this._listbox = root;
32
+ if (root.getAttribute('role') !== 'listbox') {
33
+ let listbox = (0, $ivkeh$testinglibrarydom.within)(root).queryByRole('listbox');
34
+ if (listbox) this._listbox = listbox;
35
+ }
36
+ }
37
+ /**
38
+ * Set the interaction type used by the listbox tester.
39
+ */ setInteractionType(type) {
40
+ this._interactionType = type;
41
+ }
42
+ /**
43
+ * Returns a option matching the specified index or text content.
44
+ */ findOption(opts) {
45
+ let { indexOrText: indexOrText } = opts;
46
+ let option;
47
+ let options = this.getOptions();
48
+ if (typeof indexOrText === 'number') option = options[indexOrText];
49
+ else if (typeof indexOrText === 'string') option = (0, $ivkeh$testinglibrarydom.within)(this.getListbox()).getByText(indexOrText).closest('[role=option]');
50
+ return option;
51
+ }
52
+ async keyboardNavigateToOption(opts) {
53
+ let { option: option, selectionOnNav: selectionOnNav = 'default' } = opts;
54
+ let altKey = (0, $022fcf8e360befed$exports.getAltKey)();
55
+ let options = this.getOptions();
56
+ let targetIndex = options.indexOf(option);
57
+ if (targetIndex === -1) throw new Error('Option provided is not in the listbox');
58
+ if (document.activeElement !== this._listbox && !this._listbox.contains(document.activeElement)) {
59
+ (0, $a84d573878cc1e5e$exports.act)(()=>this._listbox.focus());
60
+ await this.user.keyboard(`${selectionOnNav === 'none' ? `[${altKey}>]` : ''}[ArrowDown]${selectionOnNav === 'none' ? `[/${altKey}]` : ''}`);
61
+ }
62
+ let currIndex = options.indexOf(document.activeElement);
63
+ if (currIndex === -1) throw new Error('ActiveElement is not in the listbox');
64
+ if (selectionOnNav === 'none') await this.user.keyboard(`[${altKey}>]`);
65
+ if (this._layout === 'grid') while(document.activeElement !== option){
66
+ let curr = document.activeElement.getBoundingClientRect();
67
+ let target = option.getBoundingClientRect();
68
+ let key;
69
+ // compare current position with desired position to determine if we need to go up/down/left/right
70
+ // use 1 in the comparison here for subpixels since getBoundingClientRect returns subpixels precision
71
+ if (Math.abs(curr.top - target.top) > 1) key = curr.top < target.top ? 'ArrowDown' : 'ArrowUp';
72
+ else if (Math.abs(curr.left - target.left) > 1) key = curr.left < target.left ? 'ArrowRight' : 'ArrowLeft';
73
+ else // if the diff in current vs desired is < 1 but it is claiming we arent focused on the target
74
+ // then we might be in a case where getBoundingClientRect isnt mocked
75
+ throw new Error('Could not navigate to target option in grid layout. Did the test mock getBoundingClientRect?');
76
+ await this.user.keyboard(`[${key}]`);
77
+ }
78
+ else {
79
+ let direction = targetIndex > currIndex ? 'down' : 'up';
80
+ for(let i = 0; i < Math.abs(targetIndex - currIndex); i++)await this.user.keyboard(`[${direction === 'down' ? 'ArrowDown' : 'ArrowUp'}]`);
81
+ }
82
+ if (selectionOnNav === 'none') await this.user.keyboard(`[/${altKey}]`);
83
+ }
84
+ /**
85
+ * Toggles the selection for the specified listbox option. Defaults to using the interaction type
86
+ * set on the listbox tester.
87
+ */ async toggleOptionSelection(opts) {
88
+ let { option: option, needsLongPress: needsLongPress, keyboardActivation: keyboardActivation = 'Enter', interactionType: interactionType = this._interactionType, selectionBehavior: selectionBehavior = 'toggle' } = opts;
89
+ let altKey = (0, $022fcf8e360befed$exports.getAltKey)();
90
+ let metaKey = (0, $022fcf8e360befed$exports.getMetaKey)();
91
+ if (typeof option === 'string' || typeof option === 'number') option = this.findOption({
92
+ indexOrText: option
93
+ });
94
+ if (!option) throw new Error(`Target option "${(0, $022fcf8e360befed$exports.formatTargetNode)(opts.option)}" not found in the listbox.`);
95
+ if (interactionType === 'keyboard') {
96
+ if (option?.getAttribute('aria-disabled') === 'true') throw new Error(`Cannot toggle selection on disabled option "${(0, $022fcf8e360befed$exports.formatTargetNode)(opts.option)}".`);
97
+ await this.keyboardNavigateToOption({
98
+ option: option,
99
+ selectionOnNav: selectionBehavior === 'replace' ? 'none' : 'default'
100
+ });
101
+ if (selectionBehavior === 'replace') await this.user.keyboard(`[${altKey}>]`);
102
+ await this.user.keyboard(`[${keyboardActivation}]`);
103
+ if (selectionBehavior === 'replace') await this.user.keyboard(`[/${altKey}]`);
104
+ } else if (needsLongPress && interactionType === 'touch') await (0, $022fcf8e360befed$exports.triggerLongPress)({
105
+ element: option,
106
+ advanceTimer: this._advanceTimer,
107
+ pointerOpts: {
108
+ pointerType: 'touch'
109
+ }
110
+ });
111
+ else {
112
+ if (selectionBehavior === 'replace' && interactionType !== 'touch') await this.user.keyboard(`[${metaKey}>]`);
113
+ await (0, $022fcf8e360befed$exports.pressElement)(this.user, option, interactionType);
114
+ if (selectionBehavior === 'replace' && interactionType !== 'touch') await this.user.keyboard(`[/${metaKey}]`);
115
+ }
116
+ }
117
+ /**
118
+ * Triggers the action for the specified listbox option. Defaults to using the interaction type
119
+ * set on the listbox tester.
120
+ */ async triggerOptionAction(opts) {
121
+ let { option: option, needsDoubleClick: needsDoubleClick, interactionType: interactionType = this._interactionType } = opts;
122
+ if (typeof option === 'string' || typeof option === 'number') option = this.findOption({
123
+ indexOrText: option
124
+ });
125
+ if (!option) throw new Error(`Target option "${(0, $022fcf8e360befed$exports.formatTargetNode)(opts.option)}" not found in the listbox.`);
126
+ if (needsDoubleClick) await this.user.dblClick(option);
127
+ else if (interactionType === 'keyboard') {
128
+ if (option?.getAttribute('aria-disabled') === 'true') throw new Error(`Cannot trigger action on disabled option "${(0, $022fcf8e360befed$exports.formatTargetNode)(opts.option)}".`);
129
+ await this.keyboardNavigateToOption({
130
+ option: option,
131
+ selectionOnNav: 'none'
132
+ });
133
+ await this.user.keyboard('[Enter]');
134
+ } else await (0, $022fcf8e360befed$exports.pressElement)(this.user, option, interactionType);
135
+ }
136
+ /**
137
+ * Returns the listbox.
138
+ */ getListbox() {
139
+ return this._listbox;
140
+ }
141
+ /**
142
+ * Returns the listbox options. Can be filtered to a subsection of the listbox if provided via
143
+ * `element`.
144
+ */ getOptions(opts = {}) {
145
+ let { element: element = this._listbox } = opts;
146
+ let options = [];
147
+ if (element) options = (0, $ivkeh$testinglibrarydom.within)(element).queryAllByRole('option');
148
+ return options;
149
+ }
150
+ /**
151
+ * Returns the listbox's selected options if any.
152
+ */ getSelectedOptions() {
153
+ return this.getOptions().filter((row)=>row.getAttribute('aria-selected') === 'true');
154
+ }
155
+ /**
156
+ * Returns the listbox's sections if any.
157
+ */ getSections() {
158
+ return (0, $ivkeh$testinglibrarydom.within)(this._listbox).queryAllByRole('group');
159
+ }
160
+ }
161
+
162
+
163
+ //# sourceMappingURL=listbox.cjs.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","sources":["packages/@react-aria/test-utils/src/listbox.ts"],"sourcesContent":["/*\n * Copyright 2024 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {act} from './act';\nimport {formatTargetNode, getAltKey, getMetaKey, pressElement, triggerLongPress} from './utils';\nimport {ListBoxTesterOpts, UserOpts} from './types';\nimport {within} from '@testing-library/dom';\n\ninterface ListBoxToggleOptionOpts {\n /**\n * What interaction type to use when toggling selection for an option. Defaults to the interaction\n * type set on the tester.\n */\n interactionType?: UserOpts['interactionType'];\n /**\n * The index, text, or node of the option to toggle selection for.\n */\n option: number | string | HTMLElement;\n /**\n * Whether the option should be triggered by Space or Enter in keyboard modality.\n *\n * @default 'Enter'\n */\n keyboardActivation?: 'Space' | 'Enter';\n /**\n * Whether the option needs to be long pressed to be selected. Depends on the listbox's\n * implementation.\n */\n needsLongPress?: boolean;\n /**\n * Whether the listbox has a selectionBehavior of \"toggle\" or \"replace\" (aka highlight selection).\n * This affects the user operations required to toggle option selection by adding modifier keys\n * during user actions, useful when performing multi-option selection in a \"selectionBehavior:\n * 'replace'\" listbox. If you would like to still simulate user actions (aka press) without these\n * modifiers keys for a \"selectionBehavior: replace\" listbox, simply omit this option. See the\n * [RAC Listbox docs](https://react-spectrum.adobe.com/react-aria/ListBox.html#selection-behavior)\n * for more info on this behavior.\n *\n * @default 'toggle'\n */\n selectionBehavior?: 'toggle' | 'replace';\n}\n\ninterface ListBoxOptionActionOpts extends Omit<\n ListBoxToggleOptionOpts,\n 'keyboardActivation' | 'needsLongPress'\n> {\n /**\n * Whether or not the option needs a double click to trigger the option action. Depends on the\n * listbox's implementation.\n */\n needsDoubleClick?: boolean;\n}\n\nexport class ListBoxTester {\n private user;\n private _interactionType: UserOpts['interactionType'];\n private _advanceTimer: UserOpts['advanceTimer'];\n private _listbox: HTMLElement;\n private _layout: ListBoxTesterOpts['layout'];\n\n constructor(opts: ListBoxTesterOpts) {\n let {root, user, interactionType, advanceTimer, layout} = opts;\n this.user = user;\n this._interactionType = interactionType || 'mouse';\n this._advanceTimer = advanceTimer;\n this._layout = layout || 'stack';\n this._listbox = root;\n if (root.getAttribute('role') !== 'listbox') {\n let listbox = within(root).queryByRole('listbox');\n if (listbox) {\n this._listbox = listbox;\n }\n }\n }\n\n /**\n * Set the interaction type used by the listbox tester.\n */\n setInteractionType(type: UserOpts['interactionType']): void {\n this._interactionType = type;\n }\n\n /**\n * Returns a option matching the specified index or text content.\n */\n findOption(opts: {indexOrText: number | string}): HTMLElement {\n let {indexOrText} = opts;\n\n let option;\n let options = this.getOptions();\n\n if (typeof indexOrText === 'number') {\n option = options[indexOrText];\n } else if (typeof indexOrText === 'string') {\n option = within(this.getListbox()!)\n .getByText(indexOrText)\n .closest('[role=option]')! as HTMLElement;\n }\n\n return option;\n }\n\n private async keyboardNavigateToOption(opts: {\n option: HTMLElement;\n selectionOnNav?: 'default' | 'none';\n }) {\n let {option, selectionOnNav = 'default'} = opts;\n let altKey = getAltKey();\n let options = this.getOptions();\n let targetIndex = options.indexOf(option);\n if (targetIndex === -1) {\n throw new Error('Option provided is not in the listbox');\n }\n\n if (\n document.activeElement !== this._listbox &&\n !this._listbox.contains(document.activeElement)\n ) {\n act(() => this._listbox.focus());\n await this.user.keyboard(\n `${selectionOnNav === 'none' ? `[${altKey}>]` : ''}[ArrowDown]${selectionOnNav === 'none' ? `[/${altKey}]` : ''}`\n );\n }\n\n let currIndex = options.indexOf(document.activeElement as HTMLElement);\n if (currIndex === -1) {\n throw new Error('ActiveElement is not in the listbox');\n }\n\n if (selectionOnNav === 'none') {\n await this.user.keyboard(`[${altKey}>]`);\n }\n if (this._layout === 'grid') {\n while (document.activeElement !== option) {\n let curr = (document.activeElement as HTMLElement).getBoundingClientRect();\n let target = option.getBoundingClientRect();\n let key: string;\n // compare current position with desired position to determine if we need to go up/down/left/right\n // use 1 in the comparison here for subpixels since getBoundingClientRect returns subpixels precision\n if (Math.abs(curr.top - target.top) > 1) {\n key = curr.top < target.top ? 'ArrowDown' : 'ArrowUp';\n } else if (Math.abs(curr.left - target.left) > 1) {\n key = curr.left < target.left ? 'ArrowRight' : 'ArrowLeft';\n } else {\n // if the diff in current vs desired is < 1 but it is claiming we arent focused on the target\n // then we might be in a case where getBoundingClientRect isnt mocked\n throw new Error(\n 'Could not navigate to target option in grid layout. Did the test mock getBoundingClientRect?'\n );\n }\n await this.user.keyboard(`[${key}]`);\n }\n } else {\n let direction = targetIndex > currIndex ? 'down' : 'up';\n for (let i = 0; i < Math.abs(targetIndex - currIndex); i++) {\n await this.user.keyboard(`[${direction === 'down' ? 'ArrowDown' : 'ArrowUp'}]`);\n }\n }\n if (selectionOnNav === 'none') {\n await this.user.keyboard(`[/${altKey}]`);\n }\n }\n\n /**\n * Toggles the selection for the specified listbox option. Defaults to using the interaction type\n * set on the listbox tester.\n */\n async toggleOptionSelection(opts: ListBoxToggleOptionOpts): Promise<void> {\n let {\n option,\n needsLongPress,\n keyboardActivation = 'Enter',\n interactionType = this._interactionType,\n selectionBehavior = 'toggle'\n } = opts;\n\n let altKey = getAltKey();\n let metaKey = getMetaKey();\n\n if (typeof option === 'string' || typeof option === 'number') {\n option = this.findOption({indexOrText: option});\n }\n\n if (!option) {\n throw new Error(`Target option \"${formatTargetNode(opts.option)}\" not found in the listbox.`);\n }\n\n if (interactionType === 'keyboard') {\n if (option?.getAttribute('aria-disabled') === 'true') {\n throw new Error(\n `Cannot toggle selection on disabled option \"${formatTargetNode(opts.option)}\".`\n );\n }\n\n await this.keyboardNavigateToOption({\n option,\n selectionOnNav: selectionBehavior === 'replace' ? 'none' : 'default'\n });\n if (selectionBehavior === 'replace') {\n await this.user.keyboard(`[${altKey}>]`);\n }\n await this.user.keyboard(`[${keyboardActivation}]`);\n if (selectionBehavior === 'replace') {\n await this.user.keyboard(`[/${altKey}]`);\n }\n } else {\n if (needsLongPress && interactionType === 'touch') {\n await triggerLongPress({\n element: option,\n advanceTimer: this._advanceTimer!,\n pointerOpts: {pointerType: 'touch'}\n });\n } else {\n if (selectionBehavior === 'replace' && interactionType !== 'touch') {\n await this.user.keyboard(`[${metaKey}>]`);\n }\n await pressElement(this.user, option, interactionType);\n if (selectionBehavior === 'replace' && interactionType !== 'touch') {\n await this.user.keyboard(`[/${metaKey}]`);\n }\n }\n }\n }\n\n /**\n * Triggers the action for the specified listbox option. Defaults to using the interaction type\n * set on the listbox tester.\n */\n async triggerOptionAction(opts: ListBoxOptionActionOpts): Promise<void> {\n let {option, needsDoubleClick, interactionType = this._interactionType} = opts;\n\n if (typeof option === 'string' || typeof option === 'number') {\n option = this.findOption({indexOrText: option});\n }\n\n if (!option) {\n throw new Error(`Target option \"${formatTargetNode(opts.option)}\" not found in the listbox.`);\n }\n\n if (needsDoubleClick) {\n await this.user.dblClick(option);\n } else if (interactionType === 'keyboard') {\n if (option?.getAttribute('aria-disabled') === 'true') {\n throw new Error(\n `Cannot trigger action on disabled option \"${formatTargetNode(opts.option)}\".`\n );\n }\n\n await this.keyboardNavigateToOption({option, selectionOnNav: 'none'});\n await this.user.keyboard('[Enter]');\n } else {\n await pressElement(this.user, option, interactionType);\n }\n }\n\n /**\n * Returns the listbox.\n */\n getListbox(): HTMLElement {\n return this._listbox;\n }\n\n /**\n * Returns the listbox options. Can be filtered to a subsection of the listbox if provided via\n * `element`.\n */\n getOptions(opts: {element?: HTMLElement} = {}): HTMLElement[] {\n let {element = this._listbox} = opts;\n let options = [];\n if (element) {\n options = within(element).queryAllByRole('option');\n }\n\n return options;\n }\n\n /**\n * Returns the listbox's selected options if any.\n */\n getSelectedOptions(): HTMLElement[] {\n return this.getOptions().filter(row => row.getAttribute('aria-selected') === 'true');\n }\n\n /**\n * Returns the listbox's sections if any.\n */\n getSections(): HTMLElement[] {\n return within(this._listbox).queryAllByRole('group');\n }\n}\n"],"names":[],"version":3,"file":"listbox.cjs.map"}
@@ -0,0 +1,158 @@
1
+ import {act as $1350703ef3ac1acc$export$3ba232387fd5d6dd} from "./act.js";
2
+ import {formatTargetNode as $b4e037a2907521c6$export$bc3bc4a9206bf789, getAltKey as $b4e037a2907521c6$export$6fc0ccaebd758b9d, getMetaKey as $b4e037a2907521c6$export$7943c508934e27ce, pressElement as $b4e037a2907521c6$export$6ffa3eb717517feb, triggerLongPress as $b4e037a2907521c6$export$3a22a5a9bc0fd3b} from "./utils.js";
3
+ import {within as $6BrfQ$within} from "@testing-library/dom";
4
+
5
+ /*
6
+ * Copyright 2024 Adobe. All rights reserved.
7
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License. You may obtain a copy
9
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software distributed under
12
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
13
+ * OF ANY KIND, either express or implied. See the License for the specific language
14
+ * governing permissions and limitations under the License.
15
+ */
16
+
17
+
18
+ class $8d2aea0e455eb460$export$54c24f7ee8577f4f {
19
+ constructor(opts){
20
+ let { root: root, user: user, interactionType: interactionType, advanceTimer: advanceTimer, layout: layout } = opts;
21
+ this.user = user;
22
+ this._interactionType = interactionType || 'mouse';
23
+ this._advanceTimer = advanceTimer;
24
+ this._layout = layout || 'stack';
25
+ this._listbox = root;
26
+ if (root.getAttribute('role') !== 'listbox') {
27
+ let listbox = (0, $6BrfQ$within)(root).queryByRole('listbox');
28
+ if (listbox) this._listbox = listbox;
29
+ }
30
+ }
31
+ /**
32
+ * Set the interaction type used by the listbox tester.
33
+ */ setInteractionType(type) {
34
+ this._interactionType = type;
35
+ }
36
+ /**
37
+ * Returns a option matching the specified index or text content.
38
+ */ findOption(opts) {
39
+ let { indexOrText: indexOrText } = opts;
40
+ let option;
41
+ let options = this.getOptions();
42
+ if (typeof indexOrText === 'number') option = options[indexOrText];
43
+ else if (typeof indexOrText === 'string') option = (0, $6BrfQ$within)(this.getListbox()).getByText(indexOrText).closest('[role=option]');
44
+ return option;
45
+ }
46
+ async keyboardNavigateToOption(opts) {
47
+ let { option: option, selectionOnNav: selectionOnNav = 'default' } = opts;
48
+ let altKey = (0, $b4e037a2907521c6$export$6fc0ccaebd758b9d)();
49
+ let options = this.getOptions();
50
+ let targetIndex = options.indexOf(option);
51
+ if (targetIndex === -1) throw new Error('Option provided is not in the listbox');
52
+ if (document.activeElement !== this._listbox && !this._listbox.contains(document.activeElement)) {
53
+ (0, $1350703ef3ac1acc$export$3ba232387fd5d6dd)(()=>this._listbox.focus());
54
+ await this.user.keyboard(`${selectionOnNav === 'none' ? `[${altKey}>]` : ''}[ArrowDown]${selectionOnNav === 'none' ? `[/${altKey}]` : ''}`);
55
+ }
56
+ let currIndex = options.indexOf(document.activeElement);
57
+ if (currIndex === -1) throw new Error('ActiveElement is not in the listbox');
58
+ if (selectionOnNav === 'none') await this.user.keyboard(`[${altKey}>]`);
59
+ if (this._layout === 'grid') while(document.activeElement !== option){
60
+ let curr = document.activeElement.getBoundingClientRect();
61
+ let target = option.getBoundingClientRect();
62
+ let key;
63
+ // compare current position with desired position to determine if we need to go up/down/left/right
64
+ // use 1 in the comparison here for subpixels since getBoundingClientRect returns subpixels precision
65
+ if (Math.abs(curr.top - target.top) > 1) key = curr.top < target.top ? 'ArrowDown' : 'ArrowUp';
66
+ else if (Math.abs(curr.left - target.left) > 1) key = curr.left < target.left ? 'ArrowRight' : 'ArrowLeft';
67
+ else // if the diff in current vs desired is < 1 but it is claiming we arent focused on the target
68
+ // then we might be in a case where getBoundingClientRect isnt mocked
69
+ throw new Error('Could not navigate to target option in grid layout. Did the test mock getBoundingClientRect?');
70
+ await this.user.keyboard(`[${key}]`);
71
+ }
72
+ else {
73
+ let direction = targetIndex > currIndex ? 'down' : 'up';
74
+ for(let i = 0; i < Math.abs(targetIndex - currIndex); i++)await this.user.keyboard(`[${direction === 'down' ? 'ArrowDown' : 'ArrowUp'}]`);
75
+ }
76
+ if (selectionOnNav === 'none') await this.user.keyboard(`[/${altKey}]`);
77
+ }
78
+ /**
79
+ * Toggles the selection for the specified listbox option. Defaults to using the interaction type
80
+ * set on the listbox tester.
81
+ */ async toggleOptionSelection(opts) {
82
+ let { option: option, needsLongPress: needsLongPress, keyboardActivation: keyboardActivation = 'Enter', interactionType: interactionType = this._interactionType, selectionBehavior: selectionBehavior = 'toggle' } = opts;
83
+ let altKey = (0, $b4e037a2907521c6$export$6fc0ccaebd758b9d)();
84
+ let metaKey = (0, $b4e037a2907521c6$export$7943c508934e27ce)();
85
+ if (typeof option === 'string' || typeof option === 'number') option = this.findOption({
86
+ indexOrText: option
87
+ });
88
+ if (!option) throw new Error(`Target option "${(0, $b4e037a2907521c6$export$bc3bc4a9206bf789)(opts.option)}" not found in the listbox.`);
89
+ if (interactionType === 'keyboard') {
90
+ if (option?.getAttribute('aria-disabled') === 'true') throw new Error(`Cannot toggle selection on disabled option "${(0, $b4e037a2907521c6$export$bc3bc4a9206bf789)(opts.option)}".`);
91
+ await this.keyboardNavigateToOption({
92
+ option: option,
93
+ selectionOnNav: selectionBehavior === 'replace' ? 'none' : 'default'
94
+ });
95
+ if (selectionBehavior === 'replace') await this.user.keyboard(`[${altKey}>]`);
96
+ await this.user.keyboard(`[${keyboardActivation}]`);
97
+ if (selectionBehavior === 'replace') await this.user.keyboard(`[/${altKey}]`);
98
+ } else if (needsLongPress && interactionType === 'touch') await (0, $b4e037a2907521c6$export$3a22a5a9bc0fd3b)({
99
+ element: option,
100
+ advanceTimer: this._advanceTimer,
101
+ pointerOpts: {
102
+ pointerType: 'touch'
103
+ }
104
+ });
105
+ else {
106
+ if (selectionBehavior === 'replace' && interactionType !== 'touch') await this.user.keyboard(`[${metaKey}>]`);
107
+ await (0, $b4e037a2907521c6$export$6ffa3eb717517feb)(this.user, option, interactionType);
108
+ if (selectionBehavior === 'replace' && interactionType !== 'touch') await this.user.keyboard(`[/${metaKey}]`);
109
+ }
110
+ }
111
+ /**
112
+ * Triggers the action for the specified listbox option. Defaults to using the interaction type
113
+ * set on the listbox tester.
114
+ */ async triggerOptionAction(opts) {
115
+ let { option: option, needsDoubleClick: needsDoubleClick, interactionType: interactionType = this._interactionType } = opts;
116
+ if (typeof option === 'string' || typeof option === 'number') option = this.findOption({
117
+ indexOrText: option
118
+ });
119
+ if (!option) throw new Error(`Target option "${(0, $b4e037a2907521c6$export$bc3bc4a9206bf789)(opts.option)}" not found in the listbox.`);
120
+ if (needsDoubleClick) await this.user.dblClick(option);
121
+ else if (interactionType === 'keyboard') {
122
+ if (option?.getAttribute('aria-disabled') === 'true') throw new Error(`Cannot trigger action on disabled option "${(0, $b4e037a2907521c6$export$bc3bc4a9206bf789)(opts.option)}".`);
123
+ await this.keyboardNavigateToOption({
124
+ option: option,
125
+ selectionOnNav: 'none'
126
+ });
127
+ await this.user.keyboard('[Enter]');
128
+ } else await (0, $b4e037a2907521c6$export$6ffa3eb717517feb)(this.user, option, interactionType);
129
+ }
130
+ /**
131
+ * Returns the listbox.
132
+ */ getListbox() {
133
+ return this._listbox;
134
+ }
135
+ /**
136
+ * Returns the listbox options. Can be filtered to a subsection of the listbox if provided via
137
+ * `element`.
138
+ */ getOptions(opts = {}) {
139
+ let { element: element = this._listbox } = opts;
140
+ let options = [];
141
+ if (element) options = (0, $6BrfQ$within)(element).queryAllByRole('option');
142
+ return options;
143
+ }
144
+ /**
145
+ * Returns the listbox's selected options if any.
146
+ */ getSelectedOptions() {
147
+ return this.getOptions().filter((row)=>row.getAttribute('aria-selected') === 'true');
148
+ }
149
+ /**
150
+ * Returns the listbox's sections if any.
151
+ */ getSections() {
152
+ return (0, $6BrfQ$within)(this._listbox).queryAllByRole('group');
153
+ }
154
+ }
155
+
156
+
157
+ export {$8d2aea0e455eb460$export$54c24f7ee8577f4f as ListBoxTester};
158
+ //# sourceMappingURL=listbox.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","sources":["packages/@react-aria/test-utils/src/listbox.ts"],"sourcesContent":["/*\n * Copyright 2024 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {act} from './act';\nimport {formatTargetNode, getAltKey, getMetaKey, pressElement, triggerLongPress} from './utils';\nimport {ListBoxTesterOpts, UserOpts} from './types';\nimport {within} from '@testing-library/dom';\n\ninterface ListBoxToggleOptionOpts {\n /**\n * What interaction type to use when toggling selection for an option. Defaults to the interaction\n * type set on the tester.\n */\n interactionType?: UserOpts['interactionType'];\n /**\n * The index, text, or node of the option to toggle selection for.\n */\n option: number | string | HTMLElement;\n /**\n * Whether the option should be triggered by Space or Enter in keyboard modality.\n *\n * @default 'Enter'\n */\n keyboardActivation?: 'Space' | 'Enter';\n /**\n * Whether the option needs to be long pressed to be selected. Depends on the listbox's\n * implementation.\n */\n needsLongPress?: boolean;\n /**\n * Whether the listbox has a selectionBehavior of \"toggle\" or \"replace\" (aka highlight selection).\n * This affects the user operations required to toggle option selection by adding modifier keys\n * during user actions, useful when performing multi-option selection in a \"selectionBehavior:\n * 'replace'\" listbox. If you would like to still simulate user actions (aka press) without these\n * modifiers keys for a \"selectionBehavior: replace\" listbox, simply omit this option. See the\n * [RAC Listbox docs](https://react-spectrum.adobe.com/react-aria/ListBox.html#selection-behavior)\n * for more info on this behavior.\n *\n * @default 'toggle'\n */\n selectionBehavior?: 'toggle' | 'replace';\n}\n\ninterface ListBoxOptionActionOpts extends Omit<\n ListBoxToggleOptionOpts,\n 'keyboardActivation' | 'needsLongPress'\n> {\n /**\n * Whether or not the option needs a double click to trigger the option action. Depends on the\n * listbox's implementation.\n */\n needsDoubleClick?: boolean;\n}\n\nexport class ListBoxTester {\n private user;\n private _interactionType: UserOpts['interactionType'];\n private _advanceTimer: UserOpts['advanceTimer'];\n private _listbox: HTMLElement;\n private _layout: ListBoxTesterOpts['layout'];\n\n constructor(opts: ListBoxTesterOpts) {\n let {root, user, interactionType, advanceTimer, layout} = opts;\n this.user = user;\n this._interactionType = interactionType || 'mouse';\n this._advanceTimer = advanceTimer;\n this._layout = layout || 'stack';\n this._listbox = root;\n if (root.getAttribute('role') !== 'listbox') {\n let listbox = within(root).queryByRole('listbox');\n if (listbox) {\n this._listbox = listbox;\n }\n }\n }\n\n /**\n * Set the interaction type used by the listbox tester.\n */\n setInteractionType(type: UserOpts['interactionType']): void {\n this._interactionType = type;\n }\n\n /**\n * Returns a option matching the specified index or text content.\n */\n findOption(opts: {indexOrText: number | string}): HTMLElement {\n let {indexOrText} = opts;\n\n let option;\n let options = this.getOptions();\n\n if (typeof indexOrText === 'number') {\n option = options[indexOrText];\n } else if (typeof indexOrText === 'string') {\n option = within(this.getListbox()!)\n .getByText(indexOrText)\n .closest('[role=option]')! as HTMLElement;\n }\n\n return option;\n }\n\n private async keyboardNavigateToOption(opts: {\n option: HTMLElement;\n selectionOnNav?: 'default' | 'none';\n }) {\n let {option, selectionOnNav = 'default'} = opts;\n let altKey = getAltKey();\n let options = this.getOptions();\n let targetIndex = options.indexOf(option);\n if (targetIndex === -1) {\n throw new Error('Option provided is not in the listbox');\n }\n\n if (\n document.activeElement !== this._listbox &&\n !this._listbox.contains(document.activeElement)\n ) {\n act(() => this._listbox.focus());\n await this.user.keyboard(\n `${selectionOnNav === 'none' ? `[${altKey}>]` : ''}[ArrowDown]${selectionOnNav === 'none' ? `[/${altKey}]` : ''}`\n );\n }\n\n let currIndex = options.indexOf(document.activeElement as HTMLElement);\n if (currIndex === -1) {\n throw new Error('ActiveElement is not in the listbox');\n }\n\n if (selectionOnNav === 'none') {\n await this.user.keyboard(`[${altKey}>]`);\n }\n if (this._layout === 'grid') {\n while (document.activeElement !== option) {\n let curr = (document.activeElement as HTMLElement).getBoundingClientRect();\n let target = option.getBoundingClientRect();\n let key: string;\n // compare current position with desired position to determine if we need to go up/down/left/right\n // use 1 in the comparison here for subpixels since getBoundingClientRect returns subpixels precision\n if (Math.abs(curr.top - target.top) > 1) {\n key = curr.top < target.top ? 'ArrowDown' : 'ArrowUp';\n } else if (Math.abs(curr.left - target.left) > 1) {\n key = curr.left < target.left ? 'ArrowRight' : 'ArrowLeft';\n } else {\n // if the diff in current vs desired is < 1 but it is claiming we arent focused on the target\n // then we might be in a case where getBoundingClientRect isnt mocked\n throw new Error(\n 'Could not navigate to target option in grid layout. Did the test mock getBoundingClientRect?'\n );\n }\n await this.user.keyboard(`[${key}]`);\n }\n } else {\n let direction = targetIndex > currIndex ? 'down' : 'up';\n for (let i = 0; i < Math.abs(targetIndex - currIndex); i++) {\n await this.user.keyboard(`[${direction === 'down' ? 'ArrowDown' : 'ArrowUp'}]`);\n }\n }\n if (selectionOnNav === 'none') {\n await this.user.keyboard(`[/${altKey}]`);\n }\n }\n\n /**\n * Toggles the selection for the specified listbox option. Defaults to using the interaction type\n * set on the listbox tester.\n */\n async toggleOptionSelection(opts: ListBoxToggleOptionOpts): Promise<void> {\n let {\n option,\n needsLongPress,\n keyboardActivation = 'Enter',\n interactionType = this._interactionType,\n selectionBehavior = 'toggle'\n } = opts;\n\n let altKey = getAltKey();\n let metaKey = getMetaKey();\n\n if (typeof option === 'string' || typeof option === 'number') {\n option = this.findOption({indexOrText: option});\n }\n\n if (!option) {\n throw new Error(`Target option \"${formatTargetNode(opts.option)}\" not found in the listbox.`);\n }\n\n if (interactionType === 'keyboard') {\n if (option?.getAttribute('aria-disabled') === 'true') {\n throw new Error(\n `Cannot toggle selection on disabled option \"${formatTargetNode(opts.option)}\".`\n );\n }\n\n await this.keyboardNavigateToOption({\n option,\n selectionOnNav: selectionBehavior === 'replace' ? 'none' : 'default'\n });\n if (selectionBehavior === 'replace') {\n await this.user.keyboard(`[${altKey}>]`);\n }\n await this.user.keyboard(`[${keyboardActivation}]`);\n if (selectionBehavior === 'replace') {\n await this.user.keyboard(`[/${altKey}]`);\n }\n } else {\n if (needsLongPress && interactionType === 'touch') {\n await triggerLongPress({\n element: option,\n advanceTimer: this._advanceTimer!,\n pointerOpts: {pointerType: 'touch'}\n });\n } else {\n if (selectionBehavior === 'replace' && interactionType !== 'touch') {\n await this.user.keyboard(`[${metaKey}>]`);\n }\n await pressElement(this.user, option, interactionType);\n if (selectionBehavior === 'replace' && interactionType !== 'touch') {\n await this.user.keyboard(`[/${metaKey}]`);\n }\n }\n }\n }\n\n /**\n * Triggers the action for the specified listbox option. Defaults to using the interaction type\n * set on the listbox tester.\n */\n async triggerOptionAction(opts: ListBoxOptionActionOpts): Promise<void> {\n let {option, needsDoubleClick, interactionType = this._interactionType} = opts;\n\n if (typeof option === 'string' || typeof option === 'number') {\n option = this.findOption({indexOrText: option});\n }\n\n if (!option) {\n throw new Error(`Target option \"${formatTargetNode(opts.option)}\" not found in the listbox.`);\n }\n\n if (needsDoubleClick) {\n await this.user.dblClick(option);\n } else if (interactionType === 'keyboard') {\n if (option?.getAttribute('aria-disabled') === 'true') {\n throw new Error(\n `Cannot trigger action on disabled option \"${formatTargetNode(opts.option)}\".`\n );\n }\n\n await this.keyboardNavigateToOption({option, selectionOnNav: 'none'});\n await this.user.keyboard('[Enter]');\n } else {\n await pressElement(this.user, option, interactionType);\n }\n }\n\n /**\n * Returns the listbox.\n */\n getListbox(): HTMLElement {\n return this._listbox;\n }\n\n /**\n * Returns the listbox options. Can be filtered to a subsection of the listbox if provided via\n * `element`.\n */\n getOptions(opts: {element?: HTMLElement} = {}): HTMLElement[] {\n let {element = this._listbox} = opts;\n let options = [];\n if (element) {\n options = within(element).queryAllByRole('option');\n }\n\n return options;\n }\n\n /**\n * Returns the listbox's selected options if any.\n */\n getSelectedOptions(): HTMLElement[] {\n return this.getOptions().filter(row => row.getAttribute('aria-selected') === 'true');\n }\n\n /**\n * Returns the listbox's sections if any.\n */\n getSections(): HTMLElement[] {\n return within(this._listbox).queryAllByRole('group');\n }\n}\n"],"names":[],"version":3,"file":"listbox.js.map"}