@@ -2,26 +2,30 @@ import {describe, expect, test} from 'vitest'
22import { hasInteractiveNodes } from '../hasInteractiveNodes'
33
44describe ( 'hasInteractiveNodes' , ( ) => {
5- test ( 'if there are no interactive nodes' , ( ) => {
5+ test ( 'returns false when node is null' , ( ) => {
6+ expect ( hasInteractiveNodes ( null ) ) . toBe ( false )
7+ } )
8+
9+ test ( 'returns false if there are no interactive nodes' , ( ) => {
610 const node = document . createElement ( 'div' )
711 expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
812 } )
913
10- test ( 'if there are interactive nodes' , ( ) => {
14+ test ( 'returns true if there are interactive nodes' , ( ) => {
1115 const node = document . createElement ( 'div' )
1216 const button = document . createElement ( 'button' )
1317 node . appendChild ( button )
1418
1519 expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
1620 } )
1721
18- test ( 'if the node itself is interactive' , ( ) => {
22+ test ( 'returns false if the node itself is interactive' , ( ) => {
1923 const node = document . createElement ( 'button' )
2024
2125 expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
2226 } )
2327
24- test ( 'if there are nested interactive nodes' , ( ) => {
28+ test ( 'returns true if there are nested interactive nodes' , ( ) => {
2529 const node = document . createElement ( 'div' )
2630 const wrapper = document . createElement ( 'div' )
2731 const button = document . createElement ( 'button' )
@@ -33,28 +37,329 @@ describe('hasInteractiveNodes', () => {
3337 expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
3438 } )
3539
36- test ( 'if the node is disabled' , ( ) => {
37- const node = document . createElement ( 'button' )
38- node . disabled = true
40+ describe ( 'disabled elements' , ( ) => {
41+ test ( 'returns false if the node is disabled' , ( ) => {
42+ const node = document . createElement ( 'button' )
43+ node . disabled = true
3944
40- expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
45+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
46+ } )
47+
48+ test ( 'returns false if the child node is disabled' , ( ) => {
49+ const node = document . createElement ( 'div' )
50+ const button = document . createElement ( 'button' )
51+ button . disabled = true
52+ node . appendChild ( button )
53+
54+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
55+ } )
4156 } )
4257
43- test ( 'if the child node is disabled' , ( ) => {
44- const node = document . createElement ( 'div' )
45- const button = document . createElement ( 'button' )
46- button . disabled = true
47- node . appendChild ( button )
58+ describe ( 'tabindex handling' , ( ) => {
59+ test ( 'returns true if child node has tabindex="0"' , ( ) => {
60+ const node = document . createElement ( 'div' )
61+ const span = document . createElement ( 'span' )
62+ span . setAttribute ( 'tabindex' , '0' )
63+ node . appendChild ( span )
4864
49- expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
65+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
66+ } )
67+
68+ test ( 'returns false if child node has tabindex="-1"' , ( ) => {
69+ const node = document . createElement ( 'div' )
70+ const span = document . createElement ( 'span' )
71+ span . setAttribute ( 'tabindex' , '-1' )
72+ node . appendChild ( span )
73+
74+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
75+ } )
5076 } )
5177
52- test ( 'if child node has tabindex' , ( ) => {
53- const node = document . createElement ( 'div' )
54- const span = document . createElement ( 'span' )
55- span . setAttribute ( 'tabindex' , '0' )
56- node . appendChild ( span )
78+ describe ( 'interactive element types' , ( ) => {
79+ test ( 'returns true for anchor with href' , ( ) => {
80+ const node = document . createElement ( 'div' )
81+ const anchor = document . createElement ( 'a' )
82+ anchor . href = 'https://example.com'
83+ node . appendChild ( anchor )
5784
58- expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
85+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
86+ } )
87+
88+ test ( 'returns false for anchor without href' , ( ) => {
89+ const node = document . createElement ( 'div' )
90+ const anchor = document . createElement ( 'a' )
91+ node . appendChild ( anchor )
92+
93+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
94+ } )
95+
96+ test ( 'returns true for button' , ( ) => {
97+ const node = document . createElement ( 'div' )
98+ const button = document . createElement ( 'button' )
99+ node . appendChild ( button )
100+
101+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
102+ } )
103+
104+ test ( 'returns true for summary' , ( ) => {
105+ const node = document . createElement ( 'div' )
106+ const summary = document . createElement ( 'summary' )
107+ node . appendChild ( summary )
108+
109+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
110+ } )
111+
112+ test ( 'returns true for select' , ( ) => {
113+ const node = document . createElement ( 'div' )
114+ const select = document . createElement ( 'select' )
115+ node . appendChild ( select )
116+
117+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
118+ } )
119+
120+ test ( 'returns true for input (not hidden)' , ( ) => {
121+ const node = document . createElement ( 'div' )
122+ const input = document . createElement ( 'input' )
123+ input . type = 'text'
124+ node . appendChild ( input )
125+
126+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
127+ } )
128+
129+ test ( 'returns false for input with type=hidden' , ( ) => {
130+ const node = document . createElement ( 'div' )
131+ const input = document . createElement ( 'input' )
132+ input . type = 'hidden'
133+ node . appendChild ( input )
134+
135+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
136+ } )
137+
138+ test ( 'returns true for textarea' , ( ) => {
139+ const node = document . createElement ( 'div' )
140+ const textarea = document . createElement ( 'textarea' )
141+ node . appendChild ( textarea )
142+
143+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
144+ } )
145+
146+ test ( 'returns true for audio with controls' , ( ) => {
147+ const node = document . createElement ( 'div' )
148+ const audio = document . createElement ( 'audio' )
149+ audio . controls = true
150+ node . appendChild ( audio )
151+
152+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
153+ } )
154+
155+ test ( 'returns false for audio without controls' , ( ) => {
156+ const node = document . createElement ( 'div' )
157+ const audio = document . createElement ( 'audio' )
158+ node . appendChild ( audio )
159+
160+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
161+ } )
162+
163+ test ( 'returns true for video with controls' , ( ) => {
164+ const node = document . createElement ( 'div' )
165+ const video = document . createElement ( 'video' )
166+ video . controls = true
167+ node . appendChild ( video )
168+
169+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
170+ } )
171+
172+ test ( 'returns false for video without controls' , ( ) => {
173+ const node = document . createElement ( 'div' )
174+ const video = document . createElement ( 'video' )
175+ node . appendChild ( video )
176+
177+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
178+ } )
179+
180+ test ( 'returns true for contenteditable element' , ( ) => {
181+ const node = document . createElement ( 'div' )
182+ const editable = document . createElement ( 'div' )
183+ editable . contentEditable = 'true'
184+ node . appendChild ( editable )
185+
186+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
187+ } )
188+ } )
189+
190+ describe ( 'hidden and inert elements' , ( ) => {
191+ test ( 'returns false for element with hidden attribute' , ( ) => {
192+ const node = document . createElement ( 'div' )
193+ const button = document . createElement ( 'button' )
194+ button . hidden = true
195+ node . appendChild ( button )
196+
197+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
198+ } )
199+
200+ test ( 'returns false for element with inert attribute' , ( ) => {
201+ const node = document . createElement ( 'div' )
202+ const button = document . createElement ( 'button' )
203+ button . setAttribute ( 'inert' , '' )
204+ node . appendChild ( button )
205+
206+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
207+ } )
208+ } )
209+
210+ describe ( 'CSS visibility' , ( ) => {
211+ test ( 'returns false for element with display:none' , ( ) => {
212+ const node = document . createElement ( 'div' )
213+ const button = document . createElement ( 'button' )
214+ button . style . display = 'none'
215+ node . appendChild ( button )
216+ document . body . appendChild ( node )
217+
218+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
219+
220+ document . body . removeChild ( node )
221+ } )
222+
223+ test ( 'returns false for element with visibility:hidden' , ( ) => {
224+ const node = document . createElement ( 'div' )
225+ const button = document . createElement ( 'button' )
226+ button . style . visibility = 'hidden'
227+ node . appendChild ( button )
228+ document . body . appendChild ( node )
229+
230+ expect ( hasInteractiveNodes ( node ) ) . toBe ( false )
231+
232+ document . body . removeChild ( node )
233+ } )
234+
235+ test ( 'returns true for element with visibility:visible' , ( ) => {
236+ const node = document . createElement ( 'div' )
237+ const button = document . createElement ( 'button' )
238+ button . style . visibility = 'visible'
239+ node . appendChild ( button )
240+ document . body . appendChild ( node )
241+
242+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
243+
244+ document . body . removeChild ( node )
245+ } )
246+ } )
247+
248+ describe ( 'ignoreNodes parameter' , ( ) => {
249+ test ( 'ignores nodes in ignoreNodes array' , ( ) => {
250+ const node = document . createElement ( 'div' )
251+ const button1 = document . createElement ( 'button' )
252+ const button2 = document . createElement ( 'button' )
253+ node . appendChild ( button1 )
254+ node . appendChild ( button2 )
255+
256+ expect ( hasInteractiveNodes ( node , [ button1 , button2 ] ) ) . toBe ( false )
257+ } )
258+
259+ test ( 'returns true if there are interactive nodes not in ignoreNodes' , ( ) => {
260+ const node = document . createElement ( 'div' )
261+ const button1 = document . createElement ( 'button' )
262+ const button2 = document . createElement ( 'button' )
263+ node . appendChild ( button1 )
264+ node . appendChild ( button2 )
265+
266+ expect ( hasInteractiveNodes ( node , [ button1 ] ) ) . toBe ( true )
267+ } )
268+
269+ test ( 'always ignores the node itself' , ( ) => {
270+ const node = document . createElement ( 'button' )
271+ const childButton = document . createElement ( 'button' )
272+ node . appendChild ( childButton )
273+
274+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
275+ } )
276+ } )
277+
278+ describe ( 'performance optimizations' , ( ) => {
279+ test ( 'handles large DOM trees efficiently' , ( ) => {
280+ const node = document . createElement ( 'div' )
281+
282+ // Create a large tree with multiple levels
283+ for ( let i = 0 ; i < 100 ; i ++ ) {
284+ const wrapper = document . createElement ( 'div' )
285+ const span = document . createElement ( 'span' )
286+ wrapper . appendChild ( span )
287+ node . appendChild ( wrapper )
288+ }
289+
290+ // Add one interactive node at the end
291+ const button = document . createElement ( 'button' )
292+ node . appendChild ( button )
293+
294+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
295+ } )
296+
297+ test ( 'stops early when first interactive node is found' , ( ) => {
298+ const node = document . createElement ( 'div' )
299+
300+ // Add interactive node at the beginning
301+ const button = document . createElement ( 'button' )
302+ node . appendChild ( button )
303+
304+ // Add many more elements after
305+ for ( let i = 0 ; i < 100 ; i ++ ) {
306+ const div = document . createElement ( 'div' )
307+ node . appendChild ( div )
308+ }
309+
310+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
311+ } )
312+ } )
313+
314+ describe ( 'complex scenarios' , ( ) => {
315+ test ( 'handles multiple types of interactive elements' , ( ) => {
316+ const node = document . createElement ( 'div' )
317+ const anchor = document . createElement ( 'a' )
318+ anchor . href = '#'
319+ const button = document . createElement ( 'button' )
320+ const input = document . createElement ( 'input' )
321+
322+ node . appendChild ( anchor )
323+ node . appendChild ( button )
324+ node . appendChild ( input )
325+
326+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
327+ } )
328+
329+ test ( 'handles deeply nested structure' , ( ) => {
330+ const node = document . createElement ( 'div' )
331+ let current = node
332+
333+ // Create deep nesting
334+ for ( let i = 0 ; i < 10 ; i ++ ) {
335+ const wrapper = document . createElement ( 'div' )
336+ current . appendChild ( wrapper )
337+ current = wrapper
338+ }
339+
340+ // Add button at the deepest level
341+ const button = document . createElement ( 'button' )
342+ current . appendChild ( button )
343+
344+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
345+ } )
346+
347+ test ( 'correctly handles mix of valid and invalid interactive elements' , ( ) => {
348+ const node = document . createElement ( 'div' )
349+
350+ const disabledButton = document . createElement ( 'button' )
351+ disabledButton . disabled = true
352+
353+ const hiddenButton = document . createElement ( 'button' )
354+ hiddenButton . hidden = true
355+
356+ const validButton = document . createElement ( 'button' )
357+
358+ node . appendChild ( disabledButton )
359+ node . appendChild ( hiddenButton )
360+ node . appendChild ( validButton )
361+
362+ expect ( hasInteractiveNodes ( node ) ) . toBe ( true )
363+ } )
59364 } )
60365} )
0 commit comments