@@ -59,10 +59,10 @@ import { waitUntil } from "./time";
5959 *
6060 * @typedef {{
6161 * contains?: string;
62+ * count?: number;
6263 * displayed?: boolean;
6364 * empty?: boolean;
6465 * eq?: number;
65- * exact?: number;
6666 * first?: boolean;
6767 * focusable?: boolean;
6868 * has?: boolean;
@@ -374,6 +374,17 @@ function getWaitForNoneMessage() {
374374 return message ;
375375}
376376
377+ /**
378+ *
379+ * @param {number } count
380+ * @param {Parameters<NodeFilter>[0] } _node
381+ * @param {Parameters<NodeFilter>[1] } _i
382+ * @param {Parameters<NodeFilter>[2] } nodes
383+ */
384+ function hasNodeCount ( count , _node , _i , nodes ) {
385+ return count === nodes . length ;
386+ }
387+
377388/**
378389 * @param {string } [char]
379390 */
@@ -906,7 +917,10 @@ function _guardedQueryAll(target, options) {
906917function _queryAll ( target , options ) {
907918 queryAllLevel ++ ;
908919
909- const { exact, root, ...modifiers } = options || { } ;
920+ const { count, root, ...modifiers } = options || { } ;
921+ if ( count !== null && count !== undefined && ( ! $isInteger ( count ) || count <= 0 ) ) {
922+ throw new HootDomError ( `invalid 'count' option: should be a positive integer` ) ;
923+ }
910924
911925 /** @type {Node[] } */
912926 let nodes = [ ] ;
@@ -954,7 +968,7 @@ function _queryAll(target, options) {
954968 const filteredNodes = applyFilters ( modifierFilters , nodes ) ;
955969
956970 // Register query message (if needed), and/or throw an error accordingly
957- const message = registerQueryMessage ( filteredNodes , exact ) ;
971+ const message = registerQueryMessage ( filteredNodes , count ) ;
958972 if ( message ) {
959973 throw new HootDomError ( message ) ;
960974 }
@@ -969,7 +983,7 @@ function _queryAll(target, options) {
969983 * @param {QueryOptions } options
970984 */
971985function _queryOne ( target , options ) {
972- return _guardedQueryAll ( target , { ...options , exact : 1 } ) [ 0 ] ;
986+ return _guardedQueryAll ( target , { ...options , count : 1 } ) [ 0 ] ;
973987}
974988
975989/**
@@ -1082,6 +1096,16 @@ const customPseudoClasses = new Map();
10821096
10831097customPseudoClasses
10841098 . set ( "contains" , makePatternBasedPseudoClass ( "contains" , getNodeText ) )
1099+ . set ( "count" , ( strCount ) => {
1100+ const count = $parseInt ( strCount ) ;
1101+ if ( ! $isInteger ( count ) || count <= 0 ) {
1102+ throw selectorError (
1103+ "count" ,
1104+ `expected count to be a positive integer (got ${ strCount } )`
1105+ ) ;
1106+ }
1107+ return hasNodeCount . bind ( null , count ) ;
1108+ } )
10851109 . set ( "displayed" , ( ) => isNodeDisplayed )
10861110 . set ( "empty" , ( ) => isEmpty )
10871111 . set ( "eq" , ( strIndex ) => {
@@ -1865,6 +1889,8 @@ export function observe(target, callback) {
18651889 * * given *text* will be matched against:
18661890 * - an `<input>`, `<textarea>` or `<select>` element's **value**;
18671891 * - or any other element's **inner text**.
1892+ * - `:count`: return nodes if their count match the given *count*.
1893+ * If not matching, an error is thrown;
18681894 * - `:displayed`: matches nodes that are "displayed" (see {@link isDisplayed});
18691895 * - `:empty`: matches nodes that have an empty *content* (**value** or **inner text**);
18701896 * - `:eq(n)`: matches the *nth* node (0-based index);
@@ -1885,17 +1911,17 @@ export function observe(target, callback) {
18851911 * - `:visible`: matches nodes that are "visible" (see {@link isVisible});
18861912 *
18871913 * An `options` object can be specified to filter[1] the results:
1888- * - `displayed`: whether the nodes must be "displayed" (see {@link isDisplayed});
1889- * - `exact`: the exact number of nodes to match (throws an error if the number of
1914+ * - `count`: the exact number of nodes to match (throws an error if the number of
18901915 * nodes doesn't match);
1916+ * - `displayed`: whether the nodes must be "displayed" (see {@link isDisplayed});
18911917 * - `focusable`: whether the nodes must be "focusable" (see {@link isFocusable});
18921918 * - `root`: the root node to query the selector in (defaults to the current fixture);
18931919 * - `viewPort`: whether the nodes must be partially visible in the current viewport
18941920 * (see {@link isInViewPort});
18951921 * - `visible`: whether the nodes must be "visible" (see {@link isVisible}).
18961922 * * This option implies `displayed`
18971923 *
1898- * [1] these filters (except for `exact ` and `root`) achieve the same result as
1924+ * [1] these filters (except for `count ` and `root`) achieve the same result as
18991925 * using their homonym pseudo-classes on the final group of the given selector
19001926 * string (e.g. ```queryAll`ul > li:visible`;``` = ```queryAll("ul > li", { visible: true })```).
19011927 *
@@ -1917,7 +1943,7 @@ export function observe(target, callback) {
19171943 * queryAll`#editor:shadow div`; // -> [div, div, ...] (inside shadow DOM)
19181944 * @example
19191945 * // with options
1920- * queryAll(`div:first`, { exact : 1 }); // -> [div]
1946+ * queryAll(`div:first`, { count : 1 }); // -> [div]
19211947 * queryAll(`div`, { root: queryOne`iframe` }); // -> [div, div, ...]
19221948 * // redundant, but possible
19231949 * queryAll(`button:visible`, { visible: true }); // -> [button, button, ...]
@@ -2039,20 +2065,20 @@ export function queryFirst(target, options) {
20392065}
20402066
20412067/**
2042- * Performs a {@link queryAll} with the given arguments, along with a forced `exact : 1`
2068+ * Performs a {@link queryAll} with the given arguments, along with a forced `count : 1`
20432069 * option to ensure only one node matches the given {@link Target}.
20442070 *
20452071 * The returned value is a single node instead of a list of nodes.
20462072 *
20472073 * @param {Target } target
2048- * @param {Omit<QueryOptions, "exact "> } [options]
2074+ * @param {Omit<QueryOptions, "count "> } [options]
20492075 * @returns {Element }
20502076 */
20512077export function queryOne ( target , options ) {
20522078 [ target , options ] = parseRawArgs ( arguments ) ;
2053- if ( $isInteger ( options ?. exact ) ) {
2079+ if ( $isInteger ( options ?. count ) ) {
20542080 throw new HootDomError (
2055- `cannot call \`queryOne\` with 'exact '=${ options . exact } : did you mean to use \`queryAll\`?`
2081+ `cannot call \`queryOne\` with 'count '=${ options . count } : did you mean to use \`queryAll\`?`
20562082 ) ;
20572083 }
20582084 return _queryOne ( target , options ) ;
0 commit comments