@pie-lib/text-select 3.0.3-next.1 → 3.0.3-next.37

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.
Files changed (70) hide show
  1. package/dist/index.d.ts +15 -0
  2. package/dist/index.js +7 -0
  3. package/dist/legend.d.ts +13 -0
  4. package/dist/legend.js +64 -0
  5. package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +16 -0
  6. package/dist/text-select.d.ts +34 -0
  7. package/dist/text-select.js +53 -0
  8. package/dist/token-select/index.d.ts +44 -0
  9. package/dist/token-select/index.js +170 -0
  10. package/dist/token-select/token.d.ts +32 -0
  11. package/dist/token-select/token.js +134 -0
  12. package/dist/tokenizer/builder.d.ts +27 -0
  13. package/dist/tokenizer/builder.js +124 -0
  14. package/dist/tokenizer/controls.d.ts +23 -0
  15. package/dist/tokenizer/controls.js +68 -0
  16. package/dist/tokenizer/index.d.ts +35 -0
  17. package/dist/tokenizer/index.js +91 -0
  18. package/dist/tokenizer/selection-utils.d.ts +10 -0
  19. package/dist/tokenizer/selection-utils.js +18 -0
  20. package/dist/tokenizer/token-text.d.ts +27 -0
  21. package/dist/tokenizer/token-text.js +85 -0
  22. package/dist/utils.d.ts +12 -0
  23. package/dist/utils.js +21 -0
  24. package/package.json +33 -30
  25. package/CHANGELOG.json +0 -1
  26. package/CHANGELOG.md +0 -946
  27. package/LICENSE.md +0 -5
  28. package/lib/index.js +0 -57
  29. package/lib/index.js.map +0 -1
  30. package/lib/legend.js +0 -119
  31. package/lib/legend.js.map +0 -1
  32. package/lib/text-select.js +0 -105
  33. package/lib/text-select.js.map +0 -1
  34. package/lib/token-select/index.js +0 -267
  35. package/lib/token-select/index.js.map +0 -1
  36. package/lib/token-select/token.js +0 -236
  37. package/lib/token-select/token.js.map +0 -1
  38. package/lib/tokenizer/builder.js +0 -265
  39. package/lib/tokenizer/builder.js.map +0 -1
  40. package/lib/tokenizer/controls.js +0 -106
  41. package/lib/tokenizer/controls.js.map +0 -1
  42. package/lib/tokenizer/index.js +0 -147
  43. package/lib/tokenizer/index.js.map +0 -1
  44. package/lib/tokenizer/selection-utils.js +0 -55
  45. package/lib/tokenizer/selection-utils.js.map +0 -1
  46. package/lib/tokenizer/token-text.js +0 -176
  47. package/lib/tokenizer/token-text.js.map +0 -1
  48. package/lib/utils.js +0 -51
  49. package/lib/utils.js.map +0 -1
  50. package/src/__tests__/legend.test.jsx +0 -211
  51. package/src/__tests__/text-select.test.jsx +0 -44
  52. package/src/__tests__/utils.test.jsx +0 -27
  53. package/src/index.js +0 -8
  54. package/src/legend.js +0 -102
  55. package/src/text-select.jsx +0 -79
  56. package/src/token-select/__tests__/index.test.jsx +0 -623
  57. package/src/token-select/__tests__/token.test.jsx +0 -236
  58. package/src/token-select/index.jsx +0 -242
  59. package/src/token-select/token.jsx +0 -223
  60. package/src/tokenizer/__tests__/builder.test.js +0 -256
  61. package/src/tokenizer/__tests__/controls.test.jsx +0 -27
  62. package/src/tokenizer/__tests__/index.test.jsx +0 -329
  63. package/src/tokenizer/__tests__/selection-utils.test.js +0 -145
  64. package/src/tokenizer/__tests__/token-text.test.jsx +0 -318
  65. package/src/tokenizer/builder.js +0 -258
  66. package/src/tokenizer/controls.jsx +0 -71
  67. package/src/tokenizer/index.jsx +0 -144
  68. package/src/tokenizer/selection-utils.js +0 -49
  69. package/src/tokenizer/token-text.jsx +0 -135
  70. package/src/utils.js +0 -56
@@ -1,623 +0,0 @@
1
- import React from 'react';
2
- import { fireEvent, render } from '@testing-library/react';
3
- import { TokenSelect } from '../index';
4
-
5
- describe('token-select', () => {
6
- const defaultProps = {
7
- tokens: [
8
- {
9
- text: 'foo bar',
10
- start: 0,
11
- end: 7,
12
- predefined: true,
13
- selectable: true,
14
- selected: false,
15
- },
16
- ],
17
- onChange: jest.fn(),
18
- };
19
-
20
- describe('rendering', () => {
21
- it('renders with default props', () => {
22
- const { container } = render(<TokenSelect {...defaultProps} />);
23
- expect(container.firstChild).toBeInTheDocument();
24
- });
25
-
26
- it('renders sentences with newlines', () => {
27
- const tokens = [
28
- {
29
- text: 'foo,',
30
- start: 0,
31
- end: 4,
32
- predefined: true,
33
- selectable: true,
34
- selected: false,
35
- },
36
- {
37
- text: '\n',
38
- start: 4,
39
- end: 5,
40
- selected: false,
41
- },
42
- {
43
- text: 'bar',
44
- start: 5,
45
- end: 8,
46
- predefined: true,
47
- selectable: true,
48
- selected: false,
49
- },
50
- ];
51
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
52
- expect(container.firstChild).toBeInTheDocument();
53
- });
54
-
55
- it('renders paragraphs with double newlines', () => {
56
- const tokens = [
57
- {
58
- text: 'foo,',
59
- start: 0,
60
- end: 4,
61
- predefined: true,
62
- selectable: true,
63
- selected: false,
64
- },
65
- {
66
- text: '\n\n',
67
- start: 4,
68
- end: 5,
69
- selected: false,
70
- },
71
- {
72
- text: 'bar',
73
- start: 5,
74
- end: 8,
75
- predefined: true,
76
- selectable: true,
77
- selected: false,
78
- },
79
- ];
80
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
81
- expect(container.firstChild).toBeInTheDocument();
82
- });
83
-
84
- it('renders in disabled mode with selected tokens', () => {
85
- const tokens = [
86
- {
87
- text: 'foo,',
88
- start: 0,
89
- end: 4,
90
- predefined: true,
91
- selectable: true,
92
- selected: true,
93
- },
94
- {
95
- text: '\n',
96
- start: 4,
97
- end: 5,
98
- selected: false,
99
- },
100
- {
101
- text: 'bar',
102
- start: 5,
103
- end: 8,
104
- predefined: true,
105
- selectable: true,
106
- selected: true,
107
- },
108
- ];
109
- const { container } = render(<TokenSelect {...defaultProps} disabled tokens={tokens} />);
110
- expect(container.firstChild).toBeInTheDocument();
111
- });
112
-
113
- it('renders with maxNoOfSelections', () => {
114
- const { container } = render(<TokenSelect {...defaultProps} maxNoOfSelections={5} />);
115
- expect(container.firstChild).toBeInTheDocument();
116
- });
117
-
118
- it('renders with highlightChoices enabled', () => {
119
- const { container } = render(<TokenSelect {...defaultProps} highlightChoices={true} />);
120
- expect(container.firstChild).toBeInTheDocument();
121
- });
122
-
123
- it('strips HTML tags from token text', () => {
124
- const tokens = [
125
- {
126
- text: '<b>bold text</b>',
127
- start: 0,
128
- end: 16,
129
- predefined: true,
130
- selectable: true,
131
- selected: false,
132
- },
133
- ];
134
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
135
- expect(container.textContent).toContain('bold text');
136
- // The selectable token should have HTML tags stripped before rendering
137
- // Look specifically in the main content div (not the hidden primer)
138
- const mainDiv = container.querySelectorAll('div[class*="css-"]')[1];
139
- expect(mainDiv.textContent).toContain('bold text');
140
- });
141
-
142
- it('renders with custom className', () => {
143
- const { container } = render(<TokenSelect {...defaultProps} className="custom-token-select" />);
144
- // The second div with Emotion class should have the custom className
145
- const styledDivs = container.querySelectorAll('div[class*="css-"]');
146
- const mainDiv = styledDivs[styledDivs.length - 1];
147
- expect(mainDiv).toHaveClass('custom-token-select');
148
- });
149
-
150
- it('renders tokens with correct and incorrect states', () => {
151
- const tokens = [
152
- {
153
- text: 'correct',
154
- start: 0,
155
- end: 7,
156
- predefined: true,
157
- selectable: true,
158
- selected: true,
159
- correct: true,
160
- },
161
- {
162
- text: 'incorrect',
163
- start: 8,
164
- end: 17,
165
- predefined: true,
166
- selectable: true,
167
- selected: true,
168
- correct: false,
169
- },
170
- ];
171
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
172
- expect(container.firstChild).toBeInTheDocument();
173
- const checkIcon = container.querySelector('svg[data-testid="CheckIcon"]');
174
- const closeIcon = container.querySelector('svg[data-testid="CloseIcon"]');
175
- expect(checkIcon).toBeInTheDocument();
176
- expect(closeIcon).toBeInTheDocument();
177
- });
178
-
179
- it('renders tokens with isMissing state', () => {
180
- const tokens = [
181
- {
182
- text: 'missing answer',
183
- start: 0,
184
- end: 14,
185
- predefined: true,
186
- selectable: true,
187
- selected: false,
188
- isMissing: true,
189
- },
190
- ];
191
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
192
- expect(container.firstChild).toBeInTheDocument();
193
- const closeIcon = container.querySelector('svg[data-testid="CloseIcon"]');
194
- expect(closeIcon).toBeInTheDocument();
195
- });
196
- });
197
-
198
- describe('token interaction', () => {
199
- it('calls onChange when clicking a selectable token', () => {
200
- const onChange = jest.fn();
201
- const tokens = [
202
- {
203
- text: 'foo bar',
204
- start: 0,
205
- end: 7,
206
- predefined: true,
207
- selectable: true,
208
- selected: false,
209
- },
210
- ];
211
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} />);
212
-
213
- const tokenElements = container.querySelectorAll('.tokenRootClass');
214
- // Find the first token with data-indexkey >= 0 (skip primer tokens at -1)
215
- let tokenElement = null;
216
- for (const el of tokenElements) {
217
- if (parseInt(el.getAttribute('data-indexkey'), 10) >= 0) {
218
- tokenElement = el;
219
- break;
220
- }
221
- }
222
- if (tokenElement) {
223
- fireEvent.click(tokenElement);
224
- expect(onChange).toHaveBeenCalled();
225
- const updatedTokens = onChange.mock.calls[0][0];
226
- expect(updatedTokens[0].selected).toBe(true);
227
- }
228
- });
229
-
230
- it('handles maxNoOfSelections of 1 by deselecting previous token', () => {
231
- const onChange = jest.fn();
232
- const tokens = [
233
- {
234
- text: 'first',
235
- start: 0,
236
- end: 5,
237
- predefined: true,
238
- selectable: true,
239
- selected: true,
240
- },
241
- {
242
- text: 'second',
243
- start: 6,
244
- end: 12,
245
- predefined: true,
246
- selectable: true,
247
- selected: false,
248
- },
249
- ];
250
- const { container } = render(
251
- <TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} maxNoOfSelections={1} />,
252
- );
253
-
254
- const tokenElements = container.querySelectorAll('.tokenRootClass');
255
- // Find tokens with data-indexkey >= 0 (skip primer tokens)
256
- const realTokens = Array.from(tokenElements).filter(
257
- (el) => parseInt(el.getAttribute('data-indexkey'), 10) >= 0,
258
- );
259
- if (realTokens.length > 1) {
260
- fireEvent.click(realTokens[1]);
261
- expect(onChange).toHaveBeenCalled();
262
- const updatedTokens = onChange.mock.calls[0][0];
263
- expect(updatedTokens[0].selected).toBe(false);
264
- expect(updatedTokens[1].selected).toBe(true);
265
- }
266
- });
267
-
268
- it('prevents selecting more tokens when maxNoOfSelections is reached', () => {
269
- const onChange = jest.fn();
270
- const tokens = [
271
- {
272
- text: 'first',
273
- start: 0,
274
- end: 5,
275
- predefined: true,
276
- selectable: true,
277
- selected: true,
278
- },
279
- {
280
- text: 'second',
281
- start: 6,
282
- end: 12,
283
- predefined: true,
284
- selectable: true,
285
- selected: true,
286
- },
287
- {
288
- text: 'third',
289
- start: 13,
290
- end: 18,
291
- predefined: true,
292
- selectable: true,
293
- selected: false,
294
- },
295
- ];
296
- const { container } = render(
297
- <TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} maxNoOfSelections={2} />,
298
- );
299
-
300
- const tokenElements = container.querySelectorAll('.tokenRootClass');
301
- const realTokens = Array.from(tokenElements).filter(
302
- (el) => parseInt(el.getAttribute('data-indexkey'), 10) >= 0,
303
- );
304
- if (realTokens.length > 2) {
305
- fireEvent.click(realTokens[2]);
306
- // onChange should not be called because max is reached
307
- expect(onChange).not.toHaveBeenCalled();
308
- }
309
- });
310
-
311
- it('does not toggle token when in animationsDisabled mode', () => {
312
- const onChange = jest.fn();
313
- const tokens = [
314
- {
315
- text: 'foo bar',
316
- start: 0,
317
- end: 7,
318
- predefined: true,
319
- selectable: true,
320
- selected: false,
321
- },
322
- ];
323
- const { container } = render(
324
- <TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} animationsDisabled={true} />,
325
- );
326
-
327
- const tokenElement = container.querySelector('.tokenRootClass');
328
- if (tokenElement) {
329
- fireEvent.click(tokenElement);
330
- expect(onChange).not.toHaveBeenCalled();
331
- }
332
- });
333
-
334
- it('does not toggle token when correct is defined', () => {
335
- const onChange = jest.fn();
336
- const tokens = [
337
- {
338
- text: 'foo bar',
339
- start: 0,
340
- end: 7,
341
- predefined: true,
342
- selectable: true,
343
- selected: false,
344
- correct: true,
345
- },
346
- ];
347
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} />);
348
-
349
- const tokenElement = container.querySelector('.tokenRootClass');
350
- if (tokenElement) {
351
- fireEvent.click(tokenElement);
352
- expect(onChange).not.toHaveBeenCalled();
353
- }
354
- });
355
-
356
- it('does not toggle token when isMissing is true', () => {
357
- const onChange = jest.fn();
358
- const tokens = [
359
- {
360
- text: 'foo bar',
361
- start: 0,
362
- end: 7,
363
- predefined: true,
364
- selectable: true,
365
- selected: false,
366
- isMissing: true,
367
- },
368
- ];
369
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} />);
370
-
371
- const tokenElement = container.querySelector('.tokenRootClass');
372
- if (tokenElement) {
373
- fireEvent.click(tokenElement);
374
- expect(onChange).not.toHaveBeenCalled();
375
- }
376
- });
377
-
378
- it('allows toggling token off when selected', () => {
379
- const onChange = jest.fn();
380
- const tokens = [
381
- {
382
- text: 'foo bar',
383
- start: 0,
384
- end: 7,
385
- predefined: true,
386
- selectable: true,
387
- selected: true,
388
- },
389
- ];
390
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} onChange={onChange} />);
391
-
392
- const tokenElements = container.querySelectorAll('.tokenRootClass');
393
- let tokenElement = null;
394
- for (const el of tokenElements) {
395
- if (parseInt(el.getAttribute('data-indexkey'), 10) >= 0) {
396
- tokenElement = el;
397
- break;
398
- }
399
- }
400
- if (tokenElement) {
401
- fireEvent.click(tokenElement);
402
- expect(onChange).toHaveBeenCalled();
403
- const updatedTokens = onChange.mock.calls[0][0];
404
- expect(updatedTokens[0].selected).toBe(false);
405
- }
406
- });
407
- });
408
-
409
- describe('CSS injection and styling', () => {
410
- it('renders HiddenCssPrimer to inject CSS for all Token variants', () => {
411
- const { container } = render(<TokenSelect {...defaultProps} />);
412
- // Check that the component renders without errors; the HiddenCssPrimer
413
- // ensures CSS is injected into the document for all token states.
414
- expect(container.firstChild).toBeInTheDocument();
415
- // The HiddenCssPrimer should have aria-hidden to mark it as invisible
416
- const primer = container.querySelector('[aria-hidden="true"]');
417
- expect(primer).toBeInTheDocument();
418
- });
419
-
420
- it('renders tokens with Emotion CSS class names', () => {
421
- const { container } = render(<TokenSelect {...defaultProps} />);
422
- const tokenElement = container.querySelector('.tokenRootClass');
423
- expect(tokenElement).toBeInTheDocument();
424
- // Should have Emotion-generated class name like css-xxxxx
425
- const hasEmotionClass = Array.from(tokenElement.classList).some((cls) => cls.startsWith('css-'));
426
- expect(hasEmotionClass).toBe(true);
427
- });
428
- });
429
-
430
- describe('HTML preservation', () => {
431
- it('preserves non-selectable HTML content', () => {
432
- const tokens = [
433
- {
434
- text: '<table><tr><td>table content</td></tr></table>',
435
- start: 0,
436
- end: 46,
437
- predefined: false,
438
- selectable: false,
439
- selected: false,
440
- },
441
- ];
442
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
443
- // Non-selectable HTML should be rendered as-is
444
- const html = container.innerHTML;
445
- expect(html).toContain('table');
446
- expect(html).toContain('table content');
447
- });
448
-
449
- it('preserves non-breaking spaces in HTML', () => {
450
- const tokens = [
451
- {
452
- text: 'word&nbsp;space',
453
- start: 0,
454
- end: 14,
455
- predefined: false,
456
- selectable: false,
457
- selected: false,
458
- },
459
- ];
460
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
461
- // &nbsp; should be converted to non-breaking space character
462
- expect(container.textContent).toContain('word');
463
- expect(container.textContent).toContain('space');
464
- });
465
-
466
- it('handles mixed selectable and non-selectable tokens', () => {
467
- const tokens = [
468
- {
469
- text: 'prefix ',
470
- start: 0,
471
- end: 7,
472
- predefined: false,
473
- selectable: false,
474
- selected: false,
475
- },
476
- {
477
- text: 'selectable token',
478
- start: 7,
479
- end: 22,
480
- predefined: true,
481
- selectable: true,
482
- selected: false,
483
- },
484
- {
485
- text: ' suffix',
486
- start: 22,
487
- end: 29,
488
- predefined: false,
489
- selectable: false,
490
- selected: false,
491
- },
492
- ];
493
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
494
- expect(container.textContent).toContain('prefix');
495
- expect(container.textContent).toContain('selectable token');
496
- expect(container.textContent).toContain('suffix');
497
- });
498
- });
499
-
500
- describe('newline handling', () => {
501
- it('renders single newlines as <br> tags', () => {
502
- const tokens = [
503
- {
504
- text: 'line1',
505
- start: 0,
506
- end: 5,
507
- predefined: true,
508
- selectable: true,
509
- selected: false,
510
- },
511
- {
512
- text: '\n',
513
- start: 5,
514
- end: 6,
515
- selected: false,
516
- },
517
- {
518
- text: 'line2',
519
- start: 6,
520
- end: 11,
521
- predefined: true,
522
- selectable: true,
523
- selected: false,
524
- },
525
- ];
526
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
527
- const html = container.innerHTML;
528
- expect(html).toContain('<br>');
529
- });
530
-
531
- it('renders double newlines as paragraph breaks', () => {
532
- const tokens = [
533
- {
534
- text: 'paragraph1',
535
- start: 0,
536
- end: 10,
537
- predefined: true,
538
- selectable: true,
539
- selected: false,
540
- },
541
- {
542
- text: '\n\n',
543
- start: 10,
544
- end: 12,
545
- selected: false,
546
- },
547
- {
548
- text: 'paragraph2',
549
- start: 12,
550
- end: 22,
551
- predefined: true,
552
- selectable: true,
553
- selected: false,
554
- },
555
- ];
556
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
557
- const html = container.innerHTML;
558
- expect(html).toContain('</p>');
559
- expect(html).toContain('<p>');
560
- });
561
- });
562
-
563
- describe('edge cases', () => {
564
- it('handles empty token list', () => {
565
- const { container } = render(<TokenSelect {...defaultProps} tokens={[]} />);
566
- expect(container.firstChild).toBeInTheDocument();
567
- });
568
-
569
- it('handles tokens with special characters', () => {
570
- const tokens = [
571
- {
572
- text: 'special & < > " chars',
573
- start: 0,
574
- end: 21,
575
- predefined: true,
576
- selectable: true,
577
- selected: false,
578
- },
579
- ];
580
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
581
- expect(container.textContent).toContain('special');
582
- });
583
-
584
- it('renders with disabled and highlightChoices together', () => {
585
- const tokens = [
586
- {
587
- text: 'token',
588
- start: 0,
589
- end: 5,
590
- predefined: true,
591
- selectable: true,
592
- selected: false,
593
- },
594
- ];
595
- const { container } = render(
596
- <TokenSelect {...defaultProps} tokens={tokens} disabled highlightChoices />,
597
- );
598
- expect(container.firstChild).toBeInTheDocument();
599
- });
600
-
601
- it('handles very long token text', () => {
602
- const longText = 'Lorem ipsum '.repeat(100);
603
- const tokens = [
604
- {
605
- text: longText,
606
- start: 0,
607
- end: longText.length,
608
- predefined: true,
609
- selectable: true,
610
- selected: false,
611
- },
612
- ];
613
- const { container } = render(<TokenSelect {...defaultProps} tokens={tokens} />);
614
- expect(container.textContent).toContain('Lorem ipsum');
615
- });
616
- });
617
-
618
- // Note: Tests for internal methods (selectedCount, canSelectMore, toggleToken) are
619
- // implementation details and cannot be directly tested with RTL. The original tests
620
- // used wrapper.instance() to test these methods, which tests implementation rather
621
- // than user-facing behavior. Token selection logic and user interactions should be
622
- // tested through integration/e2e tests.
623
- });