11/// <reference types="@testing-library/jest-dom/vitest" />
2- import { render , screen } from '@testing-library/react'
2+ import { fireEvent , render , screen } from '@testing-library/react'
33import { describe , expect , it , vi } from 'vitest'
44import { EmptyContent } from '../empty-content'
55
@@ -19,25 +19,148 @@ describe('emptyContent', () => {
1919 expect ( screen . getByText ( 'Try adjusting your filters' ) ) . toBeInTheDocument ( )
2020 } )
2121
22- it ( 'renders action buttons' , ( ) => {
23- const handleClick = vi . fn ( )
22+ it ( 'merges custom className' , ( ) => {
23+ const { container } = render (
24+ < EmptyContent title = "No data found" className = "custom-class" /> ,
25+ )
26+ expect ( container . firstChild ) . toHaveClass ( 'custom-class' )
27+ } )
28+
29+ it ( 'renders button and link actions' , ( ) => {
30+ const onClick = vi . fn ( )
2431 render (
2532 < EmptyContent
2633 title = "No data found"
2734 actions = { [
28- { type : 'button' , label : 'Add Item' , onClick : handleClick } ,
29- { type : 'link' , label : 'Learn More' , to : '/docs' } ,
35+ { as : 'button' , label : 'Add Item' , onClick } ,
36+ { as : 'link' , label : 'Learn More' , to : '/docs' } ,
3037 ] }
3138 /> ,
3239 )
33- expect ( screen . getByText ( ' Add Item') ) . toBeInTheDocument ( )
34- expect ( screen . getByText ( ' Learn More') ) . toBeInTheDocument ( )
40+ expect ( screen . getByRole ( 'button' , { name : ' Add Item' } ) ) . toBeInTheDocument ( )
41+ expect ( screen . getByRole ( 'link' , { name : ' Learn More' } ) ) . toBeInTheDocument ( )
3542 } )
3643
37- it ( 'merges custom className' , ( ) => {
38- const { container } = render (
39- < EmptyContent title = "No data found" className = "custom-class" /> ,
44+ it ( 'fires onClick for button actions' , ( ) => {
45+ const onClick = vi . fn ( )
46+ render (
47+ < EmptyContent title = "x" actions = { [ { as : 'button' , label : 'Add' , onClick } ] } /> ,
4048 )
41- expect ( container . firstChild ) . toHaveClass ( 'custom-class' )
49+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Add' } ) )
50+ expect ( onClick ) . toHaveBeenCalledOnce ( )
51+ } )
52+
53+ it ( 'renders external-link with target and rel' , ( ) => {
54+ render (
55+ < EmptyContent
56+ title = "x"
57+ actions = { [ { as : 'external-link' , label : 'Ext' , to : 'https://example.com' } ] }
58+ /> ,
59+ )
60+ const link = screen . getByRole ( 'link' , { name : 'Ext' } )
61+ expect ( link ) . toHaveAttribute ( 'href' , 'https://example.com' )
62+ expect ( link ) . toHaveAttribute ( 'target' , '_blank' )
63+ expect ( link ) . toHaveAttribute ( 'rel' , 'noopener noreferrer' )
64+ } )
65+
66+ it ( 'renders internal link with _self target' , ( ) => {
67+ render (
68+ < EmptyContent title = "x" actions = { [ { as : 'link' , label : 'In' , to : '/in' } ] } /> ,
69+ )
70+ const link = screen . getByRole ( 'link' , { name : 'In' } )
71+ expect ( link ) . toHaveAttribute ( 'href' , '/in' )
72+ expect ( link ) . toHaveAttribute ( 'target' , '_self' )
73+ } )
74+
75+ it ( 'does not fire onClick when a button action is disabled' , ( ) => {
76+ const onClick = vi . fn ( )
77+ render (
78+ < EmptyContent
79+ title = "x"
80+ actions = { [ { as : 'button' , label : 'Add' , onClick, disabled : true } ] }
81+ /> ,
82+ )
83+ const btn = screen . getByRole ( 'button' , { name : 'Add' } )
84+ expect ( btn ) . toBeDisabled ( )
85+ fireEvent . click ( btn )
86+ expect ( onClick ) . not . toHaveBeenCalled ( )
87+ } )
88+
89+ it ( 'renders a disabled link as a plain disabled button without an anchor' , ( ) => {
90+ render (
91+ < EmptyContent
92+ title = "x"
93+ actions = { [ { as : 'link' , label : 'Go' , to : '/x' , disabled : true } ] }
94+ /> ,
95+ )
96+ expect ( screen . getByRole ( 'button' , { name : 'Go' } ) ) . toBeDisabled ( )
97+ expect ( screen . queryByRole ( 'link' ) ) . not . toBeInTheDocument ( )
98+ } )
99+
100+ it ( 'does not leak anchor attributes onto a disabled link button' , ( ) => {
101+ render (
102+ < EmptyContent
103+ title = "x"
104+ actions = { [ { as : 'link' , label : 'Go' , to : '/x' , disabled : true } ] }
105+ /> ,
106+ )
107+ const btn = screen . getByRole ( 'button' , { name : 'Go' } )
108+ expect ( btn ) . not . toHaveAttribute ( 'to' )
109+ expect ( btn ) . not . toHaveAttribute ( 'href' )
110+ expect ( btn ) . not . toHaveAttribute ( 'target' )
111+ expect ( btn ) . not . toHaveAttribute ( 'rel' )
112+ } )
113+
114+ it ( 'does not render hidden actions' , ( ) => {
115+ render (
116+ < EmptyContent
117+ title = "x"
118+ actions = { [
119+ { as : 'button' , label : 'Visible' } ,
120+ { as : 'button' , label : 'Secret' , hidden : true } ,
121+ ] }
122+ /> ,
123+ )
124+ expect ( screen . getByText ( 'Visible' ) ) . toBeInTheDocument ( )
125+ expect ( screen . queryByText ( 'Secret' ) ) . not . toBeInTheDocument ( )
126+ } )
127+
128+ it ( 'does not render the actions container when every action is hidden' , ( ) => {
129+ render (
130+ < EmptyContent
131+ title = "x"
132+ actions = { [ { as : 'button' , label : 'Secret' , hidden : true } ] }
133+ /> ,
134+ )
135+ expect ( screen . queryByText ( 'Secret' ) ) . not . toBeInTheDocument ( )
136+ } )
137+
138+ it ( 'wraps the control in a hover target span so a disabled action can still show its tooltip' , ( ) => {
139+ render (
140+ < EmptyContent
141+ title = "x"
142+ actions = { [
143+ { as : 'button' , label : 'Locked' , disabled : true , tooltip : 'No permission' } ,
144+ ] }
145+ /> ,
146+ )
147+ const btn = screen . getByRole ( 'button' , { name : 'Locked' } )
148+ // renderAction wraps the control in <span class="inline-flex"> before handing it
149+ // to <Tooltip>, so the disabled button still has an element that receives hover.
150+ expect ( btn . closest ( 'span.inline-flex' ) ) . not . toBeNull ( )
151+ } )
152+
153+ it ( 'forwards Button props (icon and semantic type) to the underlying button' , ( ) => {
154+ render (
155+ < EmptyContent
156+ title = "x"
157+ actions = { [
158+ { as : 'button' , label : 'Delete' , type : 'danger' , icon : < svg data-testid = "trash-icon" /> } ,
159+ ] }
160+ /> ,
161+ )
162+ expect ( screen . getByTestId ( 'trash-icon' ) ) . toBeInTheDocument ( )
163+ // type='danger' + default theme='solid' compiles to the danger solid class.
164+ expect ( screen . getByRole ( 'button' , { name : 'Delete' } ) . className ) . toContain ( 'bg-btn-danger' )
42165 } )
43166} )
0 commit comments