1+ import { beforeEach , describe , expect , test } from 'vitest'
2+ import { experimental_AstroContainer as AstroContainer } from 'astro/container'
3+ import TooltipAstro from '@components/Tooltip/index.astro'
4+ import {
5+ enhanceTooltipElements ,
6+ type TooltipElement as TooltipElementInstance ,
7+ } from '@components/Tooltip/client'
8+ import type { WebComponentModule } from '@components/scripts/@types/webComponentModule'
9+ import { executeRender } from '@test/unit/helpers/litRuntime'
10+ import { getTooltipElements } from '../selectors'
11+
12+ type TooltipComponentModule = WebComponentModule < TooltipElementInstance >
13+
14+ describe ( 'TooltipElement' , ( ) => {
15+ let container : AstroContainer
16+
17+ beforeEach ( async ( ) => {
18+ container = await AstroContainer . create ( )
19+ } )
20+
21+ const runComponentRender = async (
22+ args : Parameters < AstroContainer [ 'renderToString' ] > [ 1 ] ,
23+ assertion : ( _context : {
24+ element : TooltipElementInstance
25+ window : Window
26+ } ) => Promise < void > | void
27+ ) : Promise < void > => {
28+ await executeRender < TooltipComponentModule > ( {
29+ container,
30+ component : TooltipAstro ,
31+ moduleSpecifier : '@components/Tooltip/client/index' ,
32+ args,
33+ waitForReady : async ( element : TooltipElementInstance ) => {
34+ await element . updateComplete
35+ } ,
36+ assert : async ( { element, window } ) => {
37+ await assertion ( { element, window : window as unknown as Window } )
38+ } ,
39+ } )
40+ }
41+
42+ test ( 'wires aria-describedby and keeps the tooltip hidden by default' , async ( ) => {
43+ await runComponentRender (
44+ {
45+ props : {
46+ text : 'HyperText Markup Language' ,
47+ } ,
48+ slots : {
49+ default : '<abbr>HTML</abbr>' ,
50+ } ,
51+ } ,
52+ async ( { element } ) => {
53+ const { trigger, tooltip } = getTooltipElements ( element )
54+
55+ expect ( trigger . getAttribute ( 'aria-describedby' ) ) . toBe ( tooltip . id )
56+ expect ( trigger . tabIndex ) . toBe ( 0 )
57+ expect ( tooltip . classList . contains ( 'hidden' ) ) . toBe ( true )
58+ expect ( tooltip . getAttribute ( 'aria-hidden' ) ) . toBe ( 'true' )
59+ }
60+ )
61+ } )
62+
63+ test ( 'shows on hover and hides on mouse leave' , async ( ) => {
64+ await runComponentRender (
65+ {
66+ props : {
67+ text : 'HyperText Markup Language' ,
68+ } ,
69+ slots : {
70+ default : '<abbr>HTML</abbr>' ,
71+ } ,
72+ } ,
73+ async ( { element } ) => {
74+ const { trigger, tooltip } = getTooltipElements ( element )
75+
76+ trigger . dispatchEvent ( new Event ( 'mouseenter' ) )
77+ await element . updateComplete
78+
79+ expect ( tooltip . classList . contains ( 'hidden' ) ) . toBe ( false )
80+ expect ( tooltip . getAttribute ( 'aria-hidden' ) ) . toBe ( 'false' )
81+
82+ trigger . dispatchEvent ( new Event ( 'mouseleave' ) )
83+ await element . updateComplete
84+
85+ expect ( tooltip . classList . contains ( 'hidden' ) ) . toBe ( true )
86+ expect ( tooltip . getAttribute ( 'aria-hidden' ) ) . toBe ( 'true' )
87+ }
88+ )
89+ } )
90+
91+ test ( 'closes when escape is pressed after opening on focus' , async ( ) => {
92+ await runComponentRender (
93+ {
94+ props : {
95+ text : 'Tooltip body' ,
96+ } ,
97+ slots : {
98+ default : 'Info' ,
99+ } ,
100+ } ,
101+ async ( { element } ) => {
102+ const { trigger, tooltip } = getTooltipElements ( element )
103+
104+ trigger . dispatchEvent ( new Event ( 'focusin' , { bubbles : true } ) )
105+ await element . updateComplete
106+ expect ( tooltip . classList . contains ( 'hidden' ) ) . toBe ( false )
107+
108+ const escapeEvent = new Event ( 'keyup' , { bubbles : true } )
109+ Object . defineProperty ( escapeEvent , 'key' , { value : 'Escape' } )
110+ trigger . dispatchEvent ( escapeEvent )
111+ await element . updateComplete
112+ expect ( tooltip . classList . contains ( 'hidden' ) ) . toBe ( true )
113+ }
114+ )
115+ } )
116+
117+ test ( 'does not add a wrapper tabindex when the slotted content is already focusable' , async ( ) => {
118+ await runComponentRender (
119+ {
120+ props : {
121+ text : 'Open documentation' ,
122+ } ,
123+ slots : {
124+ default : '<a href="/docs">Docs</a>' ,
125+ } ,
126+ } ,
127+ async ( { element } ) => {
128+ const { trigger } = getTooltipElements ( element )
129+
130+ expect ( trigger . hasAttribute ( 'tabindex' ) ) . toBe ( false )
131+ }
132+ )
133+ } )
134+
135+ test ( 'upgrades markdown-style tooltip candidates without throwing during connection' , async ( ) => {
136+ await runComponentRender (
137+ {
138+ props : {
139+ text : 'Tooltip body' ,
140+ } ,
141+ slots : {
142+ default : 'Info' ,
143+ } ,
144+ } ,
145+ async ( { window } ) => {
146+ const host = window . document . createElement ( 'div' )
147+ host . innerHTML = '<abbr data-tooltip="" title="Pod Security Policy">PSP</abbr>'
148+ window . document . body . append ( host )
149+
150+ expect ( ( ) => enhanceTooltipElements ( host ) ) . not . toThrow ( )
151+
152+ const upgradedTooltip = host . querySelector ( 'site-tooltip' )
153+ const trigger = upgradedTooltip ?. querySelector ( '[data-tooltip-trigger]' )
154+ const tooltip = upgradedTooltip ?. querySelector ( '[data-tooltip-popup]' )
155+
156+ expect ( upgradedTooltip ) . toBeTruthy ( )
157+ expect ( trigger ?. textContent ) . toBe ( 'PSP' )
158+ expect ( tooltip ?. textContent ) . toBe ( 'Pod Security Policy' )
159+ }
160+ )
161+ } )
162+ } )
0 commit comments