react-markdown-table-ts 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,392 @@
1
+ /**
2
+ * @fileoverview Integration tests that verify the complete workflow from data
3
+ * input through markdown generation to component rendering.
4
+ */
5
+
6
+ import { render, screen } from '@testing-library/react';
7
+ import { MarkdownTable } from '../index';
8
+
9
+ describe('Integration Tests', () => {
10
+ describe('complete data flow', () => {
11
+ it('should transform input data into valid markdown table', () => {
12
+ const inputData = [
13
+ ['Product', 'Price', 'Quantity'],
14
+ ['Apple', '$1.50', '10'],
15
+ ['Banana', '$0.75', '15'],
16
+ ['Orange', '$2.00', '8'],
17
+ ];
18
+
19
+ const onGenerate = jest.fn();
20
+
21
+ render(
22
+ <MarkdownTable
23
+ inputData={inputData}
24
+ hasHeader={true}
25
+ columnAlignments={['left', 'right', 'center']}
26
+ onGenerate={onGenerate}
27
+ />
28
+ );
29
+
30
+ expect(onGenerate).toHaveBeenCalledTimes(1);
31
+
32
+ const generatedMarkdown = onGenerate.mock.calls[0][0];
33
+
34
+ // Verify structure
35
+ const lines = generatedMarkdown.split('\n');
36
+ expect(lines.length).toBe(5); // Header + alignment + 3 data rows
37
+
38
+ // Verify header
39
+ expect(lines[0]).toContain('Product');
40
+ expect(lines[0]).toContain('Price');
41
+ expect(lines[0]).toContain('Quantity');
42
+
43
+ // Verify alignment row
44
+ expect(lines[1]).toContain(':'); // Has alignment indicators
45
+
46
+ // Verify data rows
47
+ expect(lines[2]).toContain('Apple');
48
+ expect(lines[3]).toContain('Banana');
49
+ expect(lines[4]).toContain('Orange');
50
+ });
51
+
52
+ it('should handle real-world scenario with mixed content', () => {
53
+ const inputData = [
54
+ ['Feature', 'Status', 'Notes'],
55
+ ['Authentication', '✓', 'Complete with OAuth'],
56
+ ['API Integration', '⚠', 'In progress'],
57
+ ['Testing', '✗', 'Not started'],
58
+ ];
59
+
60
+ render(
61
+ <MarkdownTable
62
+ inputData={inputData}
63
+ columnAlignments={['left', 'center', 'left']}
64
+ hasPadding={true}
65
+ />
66
+ );
67
+
68
+ const codeElement = screen.getByRole('code');
69
+
70
+ expect(codeElement.textContent).toContain('Authentication');
71
+ expect(codeElement.textContent).toContain('✓');
72
+ expect(codeElement.textContent).toContain('OAuth');
73
+ expect(codeElement.textContent).toContain('⚠');
74
+ expect(codeElement.textContent).toContain('✗');
75
+ });
76
+ });
77
+
78
+ describe('error recovery', () => {
79
+ it('should display error message and continue to render for invalid data', () => {
80
+ const onGenerate = jest.fn();
81
+
82
+ const { rerender } = render(
83
+ <MarkdownTable inputData={[]} onGenerate={onGenerate} />
84
+ );
85
+
86
+ // Verify error is displayed
87
+ let codeElement = screen.getByRole('code');
88
+ expect(codeElement.textContent).toContain('Error:');
89
+ expect(onGenerate).toHaveBeenCalledWith(expect.stringContaining('Error:'));
90
+
91
+ // Now provide valid data
92
+ const validData = [['A'], ['1']];
93
+ rerender(<MarkdownTable inputData={validData} onGenerate={onGenerate} />);
94
+
95
+ // Should recover and display valid data
96
+ codeElement = screen.getByRole('code');
97
+ expect(codeElement.textContent).not.toContain('Error:');
98
+ expect(codeElement.textContent).toContain('A');
99
+ expect(onGenerate).toHaveBeenLastCalledWith(expect.not.stringContaining('Error:'));
100
+ });
101
+ });
102
+
103
+ describe('dynamic updates', () => {
104
+ it('should update rendered markdown when data changes', () => {
105
+ const initialData = [
106
+ ['Name', 'Age'],
107
+ ['Alice', '25'],
108
+ ];
109
+
110
+ const updatedData = [
111
+ ['Name', 'Age'],
112
+ ['Alice', '25'],
113
+ ['Bob', '30'],
114
+ ];
115
+
116
+ const onGenerate = jest.fn();
117
+
118
+ const { rerender } = render(
119
+ <MarkdownTable inputData={initialData} onGenerate={onGenerate} />
120
+ );
121
+
122
+ let codeElement = screen.getByRole('code');
123
+ expect(codeElement.textContent).toContain('Alice');
124
+ expect(codeElement.textContent).not.toContain('Bob');
125
+
126
+ rerender(<MarkdownTable inputData={updatedData} onGenerate={onGenerate} />);
127
+
128
+ codeElement = screen.getByRole('code');
129
+ expect(codeElement.textContent).toContain('Alice');
130
+ expect(codeElement.textContent).toContain('Bob');
131
+ expect(onGenerate).toHaveBeenCalledTimes(2);
132
+ });
133
+
134
+ it('should update when alignment changes', () => {
135
+ const data = [['A', 'B'], ['1', '2']];
136
+
137
+ const { rerender } = render(
138
+ <MarkdownTable inputData={data} columnAlignments={['left']} />
139
+ );
140
+
141
+ let codeElement = screen.getByRole('code');
142
+ let content = codeElement.textContent || '';
143
+ expect(content).toMatch(/:-+/);
144
+
145
+ rerender(<MarkdownTable inputData={data} columnAlignments={['right']} />);
146
+
147
+ codeElement = screen.getByRole('code');
148
+ content = codeElement.textContent || '';
149
+ expect(content).toMatch(/-+:/);
150
+ });
151
+
152
+ it('should update when formatting options change', () => {
153
+ const data = [['Header'], ['Value1\nValue2']];
154
+
155
+ const { rerender } = render(
156
+ <MarkdownTable inputData={data} convertLineBreaks={false} />
157
+ );
158
+
159
+ let codeElement = screen.getByRole('code');
160
+ expect(codeElement.textContent).not.toContain('<br>');
161
+
162
+ rerender(<MarkdownTable inputData={data} convertLineBreaks={true} />);
163
+
164
+ codeElement = screen.getByRole('code');
165
+ expect(codeElement.textContent).toContain('<br>');
166
+ });
167
+ });
168
+
169
+ describe('theme switching', () => {
170
+ it('should switch between light and dark themes', () => {
171
+ const data = [['A'], ['1']];
172
+
173
+ const { container, rerender } = render(
174
+ <MarkdownTable inputData={data} theme="light" />
175
+ );
176
+
177
+ let preElement = container.querySelector('pre');
178
+ expect(preElement?.className).not.toContain('dark-theme');
179
+
180
+ rerender(<MarkdownTable inputData={data} theme="dark" />);
181
+
182
+ preElement = container.querySelector('pre');
183
+ expect(preElement?.className).toContain('dark-theme');
184
+
185
+ rerender(<MarkdownTable inputData={data} theme="light" />);
186
+
187
+ preElement = container.querySelector('pre');
188
+ expect(preElement?.className).not.toContain('dark-theme');
189
+ });
190
+ });
191
+
192
+ describe('complex data scenarios', () => {
193
+ it('should handle table with uneven row lengths', () => {
194
+ const inputData = [
195
+ ['A', 'B', 'C', 'D'],
196
+ ['1'],
197
+ ['2', '3'],
198
+ ['4', '5', '6'],
199
+ ['7', '8', '9', '10'],
200
+ ];
201
+
202
+ render(<MarkdownTable inputData={inputData} />);
203
+
204
+ const codeElement = screen.getByRole('code');
205
+ const lines = (codeElement.textContent || '').split('\n');
206
+
207
+ // Each row should have the same number of column separators
208
+ const columnCounts = lines
209
+ .filter(line => line.trim())
210
+ .map(line => (line.match(/\|/g) || []).length);
211
+
212
+ // All rows should have the same number of pipes
213
+ expect(new Set(columnCounts).size).toBe(1);
214
+ });
215
+
216
+ it('should handle markdown syntax in cell content', () => {
217
+ const inputData = [
218
+ ['Type', 'Example'],
219
+ ['Bold', '**text**'],
220
+ ['Italic', '*text*'],
221
+ ['Code', '`code`'],
222
+ ['Link', '[link](url)'],
223
+ ];
224
+
225
+ render(<MarkdownTable inputData={inputData} />);
226
+
227
+ const codeElement = screen.getByRole('code');
228
+
229
+ expect(codeElement.textContent).toContain('**text**');
230
+ expect(codeElement.textContent).toContain('*text*');
231
+ expect(codeElement.textContent).toContain('`code`');
232
+ expect(codeElement.textContent).toContain('[link](url)');
233
+ });
234
+
235
+ it('should handle multiline content with line break conversion', () => {
236
+ const inputData = [
237
+ ['Name', 'Address'],
238
+ ['John Doe', '123 Main St\nApt 4B\nNew York, NY 10001'],
239
+ ['Jane Smith', 'Single line address'],
240
+ ];
241
+
242
+ render(<MarkdownTable inputData={inputData} convertLineBreaks={true} />);
243
+
244
+ const codeElement = screen.getByRole('code');
245
+
246
+ expect(codeElement.textContent).toContain('123 Main St<br>Apt 4B<br>New York, NY 10001');
247
+ expect(codeElement.textContent).toContain('Single line address');
248
+ });
249
+
250
+ it('should handle special characters and escaping', () => {
251
+ const inputData = [
252
+ ['Character', 'Example'],
253
+ ['Pipe', 'This | That'],
254
+ ['Backslash', 'Path\\To\\File'],
255
+ ['HTML', '<div>content</div>'],
256
+ ['Quotes', '"double" and \'single\''],
257
+ ];
258
+
259
+ render(<MarkdownTable inputData={inputData} />);
260
+
261
+ const codeElement = screen.getByRole('code');
262
+ const content = codeElement.textContent || '';
263
+
264
+ expect(content).toContain('This | That');
265
+ expect(content).toContain('Path\\To\\File');
266
+ expect(content).toContain('<div>content</div>');
267
+ expect(content).toContain('"double"');
268
+ expect(content).toContain('\'single\'');
269
+ });
270
+
271
+ it('should handle empty table with headers only', () => {
272
+ const inputData = [['Column1', 'Column2', 'Column3']];
273
+
274
+ render(<MarkdownTable inputData={inputData} hasHeader={true} />);
275
+
276
+ const codeElement = screen.getByRole('code');
277
+ const lines = (codeElement.textContent || '').split('\n').filter(l => l.trim());
278
+
279
+ // Should have header and alignment row only
280
+ expect(lines.length).toBe(2);
281
+ expect(lines[0]).toContain('Column1');
282
+ expect(lines[0]).toContain('Column2');
283
+ expect(lines[0]).toContain('Column3');
284
+ });
285
+ });
286
+
287
+ describe('performance scenarios', () => {
288
+ it('should handle large tables efficiently', () => {
289
+ const rowCount = 100;
290
+ const columnCount = 10;
291
+
292
+ const inputData = Array.from({ length: rowCount }, (_, rowIndex) =>
293
+ Array.from({ length: columnCount }, (_, colIndex) =>
294
+ `R${rowIndex}C${colIndex}`
295
+ )
296
+ );
297
+
298
+ const startTime = performance.now();
299
+
300
+ render(<MarkdownTable inputData={inputData} />);
301
+
302
+ const endTime = performance.now();
303
+ const renderTime = endTime - startTime;
304
+
305
+ const codeElement = screen.getByRole('code');
306
+ expect(codeElement).toBeInTheDocument();
307
+
308
+ // Should render within reasonable time (adjust threshold as needed)
309
+ expect(renderTime).toBeLessThan(1000); // 1 second
310
+ });
311
+
312
+ it('should handle rapid prop changes', () => {
313
+ const data = [['A'], ['1']];
314
+
315
+ const { rerender } = render(<MarkdownTable inputData={data} hasPadding={true} />);
316
+
317
+ // Rapidly change multiple props
318
+ for (let i = 0; i < 10; i++) {
319
+ rerender(<MarkdownTable inputData={data} hasPadding={i % 2 === 0} />);
320
+ }
321
+
322
+ const codeElement = screen.getByRole('code');
323
+ expect(codeElement).toBeInTheDocument();
324
+ });
325
+ });
326
+
327
+ describe('accessibility', () => {
328
+ it('should have accessible role attribute', () => {
329
+ const data = [['A'], ['1']];
330
+
331
+ render(<MarkdownTable inputData={data} />);
332
+
333
+ const codeElement = screen.getByRole('code');
334
+ expect(codeElement).toBeInTheDocument();
335
+ expect(codeElement.getAttribute('role')).toBe('code');
336
+ });
337
+
338
+ it('should maintain semantic HTML structure', () => {
339
+ const data = [['A'], ['1']];
340
+
341
+ const { container } = render(<MarkdownTable inputData={data} />);
342
+
343
+ const preElement = container.querySelector('pre');
344
+ const codeElement = container.querySelector('code');
345
+
346
+ expect(preElement).toBeInTheDocument();
347
+ expect(codeElement).toBeInTheDocument();
348
+ expect(preElement?.contains(codeElement)).toBe(true);
349
+ });
350
+ });
351
+
352
+ describe('style isolation', () => {
353
+ it('should isolate styles with isolation property', () => {
354
+ const data = [['A'], ['1']];
355
+
356
+ const { container } = render(<MarkdownTable inputData={data} />);
357
+
358
+ const wrapper = container.querySelector('div');
359
+ expect(wrapper?.style.isolation).toBe('isolate');
360
+ });
361
+
362
+ it('should inject theme-specific styles in style tag', () => {
363
+ const data = [['A'], ['1']];
364
+
365
+ const { container } = render(<MarkdownTable inputData={data} theme="light" />);
366
+
367
+ const styleTag = container.querySelector('style');
368
+ expect(styleTag).toBeInTheDocument();
369
+ expect(styleTag?.textContent).toContain('code[class*=language-]');
370
+ });
371
+
372
+ it('should update styles when theme changes', () => {
373
+ const data = [['A'], ['1']];
374
+
375
+ const { container, rerender } = render(
376
+ <MarkdownTable inputData={data} theme="light" />
377
+ );
378
+
379
+ let styleTag = container.querySelector('style');
380
+ const lightStyles = styleTag?.textContent;
381
+
382
+ rerender(<MarkdownTable inputData={data} theme="dark" />);
383
+
384
+ styleTag = container.querySelector('style');
385
+ const darkStyles = styleTag?.textContent;
386
+
387
+ // Styles should be different for different themes
388
+ expect(lightStyles).not.toBe(darkStyles);
389
+ });
390
+ });
391
+ });
392
+
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @fileoverview Jest setup configuration including mocks for Prism.js and
3
+ * testing library extensions.
4
+ */
5
+
6
+ import '@testing-library/jest-dom';
7
+
8
+ // Mock Prism.js
9
+ jest.mock('prismjs', () => ({
10
+ __esModule: true,
11
+ default: {
12
+ highlightElement: jest.fn(),
13
+ highlightAll: jest.fn(),
14
+ },
15
+ }));
16
+
17
+ // Mock Prism markdown component
18
+ jest.mock('prismjs/components/prism-markdown', () => ({}));
19
+
20
+ // Mock Prism line numbers plugin
21
+ jest.mock('prismjs/plugins/line-numbers/prism-line-numbers', () => ({}));
22
+
23
+ // Mock requestAnimationFrame
24
+ global.requestAnimationFrame = (callback: FrameRequestCallback): number => {
25
+ return setTimeout(callback, 0) as unknown as number;
26
+ };
27
+
28
+ global.cancelAnimationFrame = (id: number): void => {
29
+ clearTimeout(id);
30
+ };
31
+