11// Generated by Cursor
22// AI-assisted implementation with human review and modifications
3- import * as React from 'react' ;
4-
5- import { render , screen } from '@testing-library/react' ;
3+ import { renderHook } from '@testing-library/react' ;
64import useAccordionShowdownExtension from '../accordion-extension' ;
75import { ACCORDION_MARKDOWN_BUTTON_ID , ACCORDION_MARKDOWN_CONTENT_ID } from '../const' ;
86import { marked } from 'marked' ;
9- // eslint-disable-next-line @typescript-eslint/no-require-imports
10- const DOMPurify = require ( 'dompurify' ) ;
117
128// Mock marked
139jest . mock ( 'marked' , ( ) => ( {
@@ -21,298 +17,87 @@ jest.mock('dompurify', () => ({
2117 sanitize : jest . fn ( ( html ) => html ) ,
2218} ) ) ;
2319
24- // Test component that uses the hook
25- interface TestComponentProps {
26- onExtensionReady ?: ( extension : any ) => void ;
27- }
28-
29- const TestAccordionComponent : React . FunctionComponent < TestComponentProps > = ( { onExtensionReady } ) => {
30- const extension = useAccordionShowdownExtension ( ) ;
31-
32- React . useEffect ( ( ) => {
33- if ( onExtensionReady ) {
34- onExtensionReady ( extension ) ;
35- }
36- } , [ extension , onExtensionReady ] ) ;
37-
38- return (
39- < div data-testid = "test-component" >
40- < div data-testid = "extension-type" > { extension . type } </ div >
41- < div data-testid = "extension-regex-source" > { extension . regex . source } </ div >
42- < div data-testid = "extension-regex-flags" > { extension . regex . flags } </ div >
43- < button
44- data-testid = "test-replace-button"
45- onClick = { ( ) => {
46- const result = extension . replace (
47- '[Test content]{{accordion "Test Title"}}' ,
48- 'Test content' ,
49- 'accordion' ,
50- '"Test Title"' ,
51- 'Test Title'
52- ) ;
53- const resultDiv = document . createElement ( 'div' ) ;
54- resultDiv . innerHTML = result ;
55- resultDiv . setAttribute ( 'data-testid' , 'replace-result' ) ;
56- document . body . appendChild ( resultDiv ) ;
57- } }
58- >
59- Test Replace
60- </ button >
61- </ div >
62- ) ;
63- } ;
64-
6520describe ( 'useAccordionShowdownExtension' , ( ) => {
6621 beforeEach ( ( ) => {
6722 jest . clearAllMocks ( ) ;
68- // Clean up any previous test results
69- const existingResults = document . querySelectorAll ( '[data-testid="replace-result"]' ) ;
70- existingResults . forEach ( el => el . remove ( ) ) ;
7123 } ) ;
7224
73- it ( 'should return extension with correct type' , ( ) => {
74- render ( < TestAccordionComponent /> ) ;
75- expect ( screen . getByTestId ( 'extension-type' ) . textContent ) . toBe ( 'lang' ) ;
76- } ) ;
25+ it ( 'should return a showdown extension with correct properties' , ( ) => {
26+ const { result } = renderHook ( ( ) => useAccordionShowdownExtension ( ) ) ;
27+ const extension = result . current ;
7728
78- it ( 'should return extension with global regex' , ( ) => {
79- render ( < TestAccordionComponent /> ) ;
80- expect ( screen . getByTestId ( ' extension-regex-flags' ) . textContent ) . toBe ( 'g ' ) ;
29+ expect ( extension . type ) . toBe ( 'lang' ) ;
30+ expect ( extension . regex ) . toEqual ( / \[ ( . + ) ] { { ( a c c o r d i o n ) ( & q u o t ; ( . * ? ) & q u o t ; ) } } / g ) ;
31+ expect ( typeof extension . replace ) . toBe ( 'function ' ) ;
8132 } ) ;
8233
83- it ( 'should return extension with correct regex pattern for HTML-encoded quotes' , ( ) => {
84- render ( < TestAccordionComponent /> ) ;
85- const regexSource = screen . getByTestId ( 'extension-regex-source' ) . textContent ;
86- expect ( regexSource ) . toBe ( '\\[(.+)]{{(accordion) ("(.*?)")}}' ) ;
34+ it ( 'should match accordion syntax with HTML-encoded quotes' , ( ) => {
35+ const { result } = renderHook ( ( ) => useAccordionShowdownExtension ( ) ) ;
36+ const { regex } = result . current ;
37+
38+ const testText = '[Some content]{{accordion "My Title"}}' ;
39+ const matches = regex . exec ( testText ) ;
40+
41+ expect ( matches ) . not . toBeNull ( ) ;
42+ expect ( matches ! [ 1 ] ) . toBe ( 'Some content' ) ;
43+ expect ( matches ! [ 2 ] ) . toBe ( 'accordion' ) ;
44+ expect ( matches ! [ 4 ] ) . toBe ( 'My Title' ) ;
8745 } ) ;
8846
89- describe ( 'regex pattern matching' , ( ) => {
90- let extension : any ;
91-
92- beforeEach ( ( ) => {
93- const handleExtensionReady = ( ext : any ) => {
94- extension = ext ;
95- } ;
96- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
97- } ) ;
98-
99- it ( 'should match accordion syntax with HTML-encoded quotes' , ( ) => {
100- const testText = '[Some content here]{{accordion "My Title"}}' ;
101- const regex = new RegExp ( extension . regex ) ;
102- const matches = regex . exec ( testText ) ;
103-
104- expect ( matches ) . not . toBeNull ( ) ;
105- expect ( matches ! [ 1 ] ) . toBe ( 'Some content here' ) ;
106- expect ( matches ! [ 2 ] ) . toBe ( 'accordion' ) ;
107- expect ( matches ! [ 3 ] ) . toBe ( '"My Title"' ) ;
108- expect ( matches ! [ 4 ] ) . toBe ( 'My Title' ) ;
109- } ) ;
110-
111- it ( 'should match multiple accordions in the same text' , ( ) => {
112- const testText = `
113- [First content]{{accordion "First Title"}}
114- Some other text
115- [Second content]{{accordion "Second Title"}}
116- ` ;
117-
118- const matches = Array . from ( testText . matchAll ( extension . regex ) ) ;
119- expect ( matches ) . toHaveLength ( 2 ) ;
120- expect ( matches [ 0 ] [ 4 ] ) . toBe ( 'First Title' ) ;
121- expect ( matches [ 1 ] [ 4 ] ) . toBe ( 'Second Title' ) ;
122- } ) ;
123-
124- it ( 'should not match accordion syntax with regular quotes' , ( ) => {
125- const testText = '[Some content]{{accordion "My Title"}}' ;
126- const matches = testText . match ( extension . regex ) ;
127- expect ( matches ) . toBeNull ( ) ;
128- } ) ;
129-
130- it ( 'should not match malformed accordion syntax' , ( ) => {
131- const malformedCases = [
132- 'Some content]{{accordion "My Title"}}' ,
133- '[Some content{{accordion "My Title"}}' ,
134- '[Some content]{{accordion "My Title"}' ,
135- '[Some content]{{accordion My Title}}' ,
136- '[Some content]{{notaccordion "My Title"}}' ,
137- ] ;
138-
139- malformedCases . forEach ( testCase => {
140- const matches = testCase . match ( extension . regex ) ;
141- expect ( matches ) . toBeNull ( ) ;
142- } ) ;
143- } ) ;
47+ it ( 'should not match accordion syntax with regular quotes' , ( ) => {
48+ const { result } = renderHook ( ( ) => useAccordionShowdownExtension ( ) ) ;
49+ const { regex } = result . current ;
50+
51+ const testText = '[Some content]{{accordion "My Title"}}' ;
52+ expect ( testText . match ( regex ) ) . toBeNull ( ) ;
14453 } ) ;
14554
146- describe ( 'HTML generation' , ( ) => {
147- it ( 'should generate correct accordion HTML structure when replace function is called' , ( ) => {
148- render ( < TestAccordionComponent /> ) ;
149-
150- const testButton = screen . getByTestId ( 'test-replace-button' ) ;
151- testButton . click ( ) ;
152-
153- const result = document . querySelector ( '[data-testid="replace-result"]' ) ;
154- expect ( result ) . not . toBeNull ( ) ;
155-
156- // Check for PatternFly accordion classes in HTML
157- expect ( result ! . innerHTML ) . toContain ( 'pf-v6-c-accordion' ) ;
158- expect ( result ! . innerHTML ) . toContain ( 'pf-v6-c-accordion__item' ) ;
159- expect ( result ! . innerHTML ) . toContain ( 'pf-v6-c-accordion__toggle' ) ;
160- expect ( result ! . innerHTML ) . toContain ( 'pf-v6-c-accordion__expandable-content' ) ;
161-
162- // Check for correct IDs
163- expect ( result ! . innerHTML ) . toContain ( `${ ACCORDION_MARKDOWN_BUTTON_ID } -Test-Title` ) ;
164- expect ( result ! . innerHTML ) . toContain ( `${ ACCORDION_MARKDOWN_CONTENT_ID } -Test-Title` ) ;
165-
166- // Check that title is rendered
167- expect ( result ! . textContent ) . toContain ( 'Test Title' ) ;
168- } ) ;
169-
170- it ( 'should call marked.parseInline and DOMPurify.sanitize during rendering' , ( ) => {
171- let extension : any ;
172- const handleExtensionReady = ( ext : any ) => {
173- extension = ext ;
174- } ;
175-
176- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
177-
178- // Call replace function directly
179- extension . replace (
180- '[**Bold text**]{{accordion "Title"}}' ,
181- '**Bold text**' ,
182- 'accordion' ,
183- '"Title"' ,
184- 'Title'
185- ) ;
186-
187- expect ( marked . parseInline ) . toHaveBeenCalledWith ( '**Bold text**' ) ;
188- expect ( DOMPurify . sanitize ) . toHaveBeenCalled ( ) ;
189- } ) ;
190-
191- it ( 'should handle titles with spaces by replacing them with dashes in IDs' , ( ) => {
192- let extension : any ;
193- const handleExtensionReady = ( ext : any ) => {
194- extension = ext ;
195- } ;
196-
197- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
198-
199- const result = extension . replace (
200- '[Content]{{accordion "My Test Title"}}' ,
201- 'Content' ,
202- 'accordion' ,
203- '"My Test Title"' ,
204- 'My Test Title'
205- ) ;
206-
207- expect ( result ) . toContain ( `id="${ ACCORDION_MARKDOWN_BUTTON_ID } -My-Test-Title"` ) ;
208- expect ( result ) . toContain ( `id="${ ACCORDION_MARKDOWN_CONTENT_ID } -My-Test-Title"` ) ;
209- } ) ;
210-
211- it ( 'should handle special characters in titles' , ( ) => {
212- let extension : any ;
213- const handleExtensionReady = ( ext : any ) => {
214- extension = ext ;
215- } ;
216-
217- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
218-
219- const result = extension . replace (
220- '[Content]{{accordion "Title with 123 & symbols!"}}' ,
221- 'Content' ,
222- 'accordion' ,
223- '"Title with 123 & symbols!"' ,
224- 'Title with 123 & symbols!'
225- ) ;
226-
227- expect ( result ) . toContain ( `id="${ ACCORDION_MARKDOWN_BUTTON_ID } -Title-with-123-&-symbols!"` ) ;
228- expect ( result ) . toContain ( 'Title with 123 & symbols!' ) ;
229- } ) ;
55+ it ( 'should generate correct accordion HTML structure' , ( ) => {
56+ const { result } = renderHook ( ( ) => useAccordionShowdownExtension ( ) ) ;
57+ const { replace } = result . current ;
58+
59+ const html = replace (
60+ '[Test content]{{accordion "Test Title"}}' ,
61+ 'Test content' ,
62+ 'accordion' ,
63+ '"Test Title"' ,
64+ 'Test Title'
65+ ) ;
66+
67+ expect ( html ) . toContain ( 'pf-v6-c-accordion' ) ;
68+ expect ( html ) . toContain ( 'pf-v6-c-accordion__toggle' ) ;
69+ expect ( html ) . toContain ( `${ ACCORDION_MARKDOWN_BUTTON_ID } -Test-Title` ) ;
70+ expect ( html ) . toContain ( `${ ACCORDION_MARKDOWN_CONTENT_ID } -Test-Title` ) ;
71+ expect ( html ) . toContain ( 'Test Title' ) ;
23072 } ) ;
23173
232- describe ( 'edge cases' , ( ) => {
233- it ( 'should handle empty content' , ( ) => {
234- let extension : any ;
235- const handleExtensionReady = ( ext : any ) => {
236- extension = ext ;
237- } ;
238-
239- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
240-
241- const result = extension . replace (
242- '[]{{accordion "Empty"}}' ,
243- '' ,
244- 'accordion' ,
245- '"Empty"' ,
246- 'Empty'
247- ) ;
248-
249- expect ( result ) . toContain ( 'class="pf-v6-c-accordion"' ) ;
250- expect ( result ) . toContain ( 'Empty' ) ;
251- } ) ;
252-
253- it ( 'should handle content with HTML entities' , ( ) => {
254- let extension : any ;
255- const handleExtensionReady = ( ext : any ) => {
256- extension = ext ;
257- } ;
258-
259- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
260-
261- const result = extension . replace (
262- '[Content with <tags> & entities]{{accordion "Title"}}' ,
263- 'Content with <tags> & entities' ,
264- 'accordion' ,
265- '"Title"' ,
266- 'Title'
267- ) ;
268-
269- expect ( result ) . toContain ( 'Content with <tags> & entities' ) ;
270- } ) ;
271-
272- it ( 'should handle very long titles' , ( ) => {
273- let extension : any ;
274- const handleExtensionReady = ( ext : any ) => {
275- extension = ext ;
276- } ;
277-
278- render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
279-
280- const longTitle = 'This is a very long title that might cause issues with ID generation and display' ;
281- const result = extension . replace (
282- '[Content]{{accordion "' + longTitle + '"}}' ,
283- 'Content' ,
284- 'accordion' ,
285- '"' + longTitle + '"' ,
286- longTitle
287- ) ;
288-
289- expect ( result ) . toContain ( longTitle ) ;
290- expect ( result ) . toContain ( `id="${ ACCORDION_MARKDOWN_BUTTON_ID } -${ longTitle . replace ( / \s / g, '-' ) } "` ) ;
291- } ) ;
74+ it ( 'should process content through marked and sanitize HTML' , ( ) => {
75+ const { result } = renderHook ( ( ) => useAccordionShowdownExtension ( ) ) ;
76+ const { replace } = result . current ;
77+
78+ replace (
79+ '[**Bold text**]{{accordion "Title"}}' ,
80+ '**Bold text**' ,
81+ 'accordion' ,
82+ '"Title"' ,
83+ 'Title'
84+ ) ;
85+
86+ expect ( marked . parseInline ) . toHaveBeenCalledWith ( '**Bold text**' ) ;
29287 } ) ;
29388
294- describe ( 'memoization' , ( ) => {
295- it ( 'should return the same extension object on subsequent renders' , ( ) => {
296- let callCount = 0 ;
297- let firstExtension : any ;
298- let secondExtension : any ;
299-
300- const handleExtensionReady = ( ext : any ) => {
301- callCount ++ ;
302- if ( callCount === 1 ) {
303- firstExtension = ext ;
304- } else if ( callCount === 2 ) {
305- secondExtension = ext ;
306- }
307- } ;
308-
309- const { rerender } = render ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
310- rerender ( < TestAccordionComponent onExtensionReady = { handleExtensionReady } /> ) ;
311-
312- expect ( callCount ) . toBeGreaterThanOrEqual ( 1 ) ;
313- expect ( firstExtension ) . toBeDefined ( ) ;
314- expect ( firstExtension . type ) . toBe ( 'lang' ) ;
315- expect ( typeof firstExtension . replace ) . toBe ( 'function' ) ;
316- } ) ;
89+ it ( 'should handle titles with spaces in IDs' , ( ) => {
90+ const { result } = renderHook ( ( ) => useAccordionShowdownExtension ( ) ) ;
91+ const { replace } = result . current ;
92+
93+ const html = replace (
94+ '[Content]{{accordion "My Test Title"}}' ,
95+ 'Content' ,
96+ 'accordion' ,
97+ '"My Test Title"' ,
98+ 'My Test Title'
99+ ) ;
100+
101+ expect ( html ) . toContain ( `${ ACCORDION_MARKDOWN_BUTTON_ID } -My-Test-Title` ) ;
317102 } ) ;
318103} ) ;
0 commit comments