@pie-lib/text-select 2.1.0-next.5 → 2.1.1-next.1
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.
- package/lib/token-select/index.js +3 -4
- package/lib/token-select/index.js.map +1 -1
- package/lib/token-select/token.js.map +1 -1
- package/lib/tokenizer/builder.js +3 -4
- package/lib/tokenizer/builder.js.map +1 -1
- package/lib/tokenizer/controls.js.map +1 -1
- package/lib/tokenizer/index.js +4 -6
- package/lib/tokenizer/index.js.map +1 -1
- package/lib/tokenizer/token-text.js.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/legend.test.jsx +211 -0
- package/src/token-select/__tests__/index.test.jsx +264 -2
- package/src/token-select/__tests__/token.test.jsx +207 -1
- package/src/token-select/index.jsx +1 -2
- package/src/token-select/token.jsx +1 -5
- package/src/tokenizer/__tests__/builder.test.js +1 -1
- package/src/tokenizer/__tests__/controls.test.jsx +1 -1
- package/src/tokenizer/__tests__/index.test.jsx +289 -7
- package/src/tokenizer/__tests__/selection-utils.test.js +122 -3
- package/src/tokenizer/__tests__/token-text.test.jsx +285 -9
- package/src/tokenizer/builder.js +2 -3
- package/src/tokenizer/controls.jsx +3 -18
- package/src/tokenizer/index.jsx +2 -4
- package/src/tokenizer/token-text.jsx +2 -2
|
@@ -3,6 +3,7 @@ import { clearSelection, getCaretCharacterOffsetWithin } from '../selection-util
|
|
|
3
3
|
describe('selection-utils', () => {
|
|
4
4
|
let selection;
|
|
5
5
|
let range;
|
|
6
|
+
|
|
6
7
|
beforeEach(() => {
|
|
7
8
|
selection = {
|
|
8
9
|
removeAllRanges: jest.fn(),
|
|
@@ -14,13 +15,131 @@ describe('selection-utils', () => {
|
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
describe('clearSelection', () => {
|
|
17
|
-
it('calls removeAllRanges', () => {
|
|
18
|
+
it('calls removeAllRanges and addRange with document.getSelection', () => {
|
|
19
|
+
const mockRange = {};
|
|
20
|
+
global.document.createRange = jest.fn().mockReturnValue(mockRange);
|
|
21
|
+
|
|
18
22
|
clearSelection();
|
|
23
|
+
|
|
19
24
|
expect(selection.removeAllRanges).toBeCalled();
|
|
25
|
+
expect(selection.addRange).toHaveBeenCalledWith(mockRange);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('uses window.getSelection if document.getSelection is not available', () => {
|
|
29
|
+
global.document.getSelection = undefined;
|
|
30
|
+
const windowSelection = {
|
|
31
|
+
removeAllRanges: jest.fn(),
|
|
32
|
+
addRange: jest.fn(),
|
|
33
|
+
};
|
|
34
|
+
global.window.getSelection = jest.fn().mockReturnValue(windowSelection);
|
|
35
|
+
const mockRange = {};
|
|
36
|
+
global.document.createRange = jest.fn().mockReturnValue(mockRange);
|
|
37
|
+
|
|
38
|
+
clearSelection();
|
|
39
|
+
|
|
40
|
+
expect(windowSelection.removeAllRanges).toBeCalled();
|
|
41
|
+
expect(windowSelection.addRange).toHaveBeenCalledWith(mockRange);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('uses window.getSelection().empty() if removeAllRanges is not available', () => {
|
|
45
|
+
global.document.getSelection = undefined;
|
|
46
|
+
const windowSelection = {
|
|
47
|
+
empty: jest.fn(),
|
|
48
|
+
};
|
|
49
|
+
global.window.getSelection = jest.fn().mockReturnValue(windowSelection);
|
|
50
|
+
|
|
51
|
+
clearSelection();
|
|
52
|
+
|
|
53
|
+
expect(windowSelection.empty).toBeCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('uses document.selection.empty() for IE8- compatibility', () => {
|
|
57
|
+
global.document.getSelection = undefined;
|
|
58
|
+
global.window.getSelection = undefined;
|
|
59
|
+
const docSelection = {
|
|
60
|
+
empty: jest.fn(),
|
|
61
|
+
};
|
|
62
|
+
global.document.selection = docSelection;
|
|
63
|
+
|
|
64
|
+
clearSelection();
|
|
65
|
+
|
|
66
|
+
expect(docSelection.empty).toBeCalled();
|
|
67
|
+
|
|
68
|
+
delete global.document.selection;
|
|
20
69
|
});
|
|
21
70
|
});
|
|
22
71
|
|
|
23
|
-
|
|
24
|
-
|
|
72
|
+
describe('getCaretCharacterOffsetWithin', () => {
|
|
73
|
+
let element;
|
|
74
|
+
let mockRange;
|
|
75
|
+
let mockSelection;
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
element = {
|
|
79
|
+
ownerDocument: global.document,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
mockRange = {
|
|
83
|
+
toString: jest.fn().mockReturnValue(''),
|
|
84
|
+
cloneRange: jest.fn(),
|
|
85
|
+
endContainer: {},
|
|
86
|
+
endOffset: 0,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const clonedRange = {
|
|
90
|
+
selectNodeContents: jest.fn(),
|
|
91
|
+
setEnd: jest.fn(),
|
|
92
|
+
toString: jest.fn().mockReturnValue('some text'),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
mockRange.cloneRange.mockReturnValue(clonedRange);
|
|
96
|
+
|
|
97
|
+
mockSelection = {
|
|
98
|
+
rangeCount: 1,
|
|
99
|
+
getRangeAt: jest.fn().mockReturnValue(mockRange),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
global.window.getSelection = jest.fn().mockReturnValue(mockSelection);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('returns 0 when rangeCount is 0', () => {
|
|
106
|
+
mockSelection.rangeCount = 0;
|
|
107
|
+
|
|
108
|
+
const offset = getCaretCharacterOffsetWithin(element);
|
|
109
|
+
|
|
110
|
+
expect(offset).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('returns caret offset for unselected text', () => {
|
|
114
|
+
mockRange.toString.mockReturnValue('');
|
|
115
|
+
const clonedRange = mockRange.cloneRange();
|
|
116
|
+
clonedRange.toString.mockReturnValue('hello');
|
|
117
|
+
|
|
118
|
+
const offset = getCaretCharacterOffsetWithin(element);
|
|
119
|
+
|
|
120
|
+
expect(offset).toBe(5);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns caret offset when text is selected', () => {
|
|
124
|
+
mockRange.toString.mockReturnValue('world');
|
|
125
|
+
const clonedRange = mockRange.cloneRange();
|
|
126
|
+
clonedRange.toString.mockReturnValue('hello world');
|
|
127
|
+
|
|
128
|
+
const offset = getCaretCharacterOffsetWithin(element);
|
|
129
|
+
|
|
130
|
+
expect(offset).toBe(6); // 11 - 5 = 6
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('returns 0 when selection type is Control in IE', () => {
|
|
134
|
+
global.window.getSelection = undefined;
|
|
135
|
+
const docSelection = {
|
|
136
|
+
type: 'Control',
|
|
137
|
+
};
|
|
138
|
+
element.ownerDocument.selection = docSelection;
|
|
139
|
+
|
|
140
|
+
const offset = getCaretCharacterOffsetWithin(element);
|
|
141
|
+
|
|
142
|
+
expect(offset).toBe(0);
|
|
143
|
+
});
|
|
25
144
|
});
|
|
26
145
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
import TokenText from '../token-text';
|
|
2
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import TokenText, { Text } from '../token-text';
|
|
4
4
|
|
|
5
5
|
const tokens = () => [
|
|
6
6
|
{
|
|
7
7
|
start: 0,
|
|
8
|
-
end:
|
|
9
|
-
text:
|
|
8
|
+
end: 5,
|
|
9
|
+
text: 'lorem',
|
|
10
10
|
},
|
|
11
11
|
];
|
|
12
12
|
|
|
@@ -14,10 +14,19 @@ describe('token-text', () => {
|
|
|
14
14
|
const defaultProps = {
|
|
15
15
|
onTokenClick: jest.fn(),
|
|
16
16
|
onSelectToken: jest.fn(),
|
|
17
|
-
text: `lorem
|
|
17
|
+
text: `lorem ipsum dolor`,
|
|
18
18
|
tokens: tokens(),
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
if (typeof global.window !== 'undefined') {
|
|
24
|
+
global.window.getSelection = jest.fn().mockReturnValue({
|
|
25
|
+
toString: () => '',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
21
30
|
describe('rendering', () => {
|
|
22
31
|
it('renders with text and tokens', () => {
|
|
23
32
|
const { container } = render(<TokenText {...defaultProps} />);
|
|
@@ -33,10 +42,277 @@ describe('token-text', () => {
|
|
|
33
42
|
const { container } = render(<TokenText {...defaultProps} text="Line 1\nLine 2\nLine 3" />);
|
|
34
43
|
expect(container.firstChild).toBeInTheDocument();
|
|
35
44
|
});
|
|
45
|
+
|
|
46
|
+
it('renders with custom className', () => {
|
|
47
|
+
const { container } = render(<TokenText {...defaultProps} className="custom-class" />);
|
|
48
|
+
expect(container.firstChild).toHaveClass('custom-class');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders normalized tokens', () => {
|
|
52
|
+
const { container } = render(<TokenText {...defaultProps} />);
|
|
53
|
+
const spans = container.querySelectorAll('span');
|
|
54
|
+
expect(spans.length).toBeGreaterThan(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('Text component', () => {
|
|
59
|
+
it('renders plain text when not predefined', () => {
|
|
60
|
+
const { container } = render(<Text text="hello" predefined={false} />);
|
|
61
|
+
expect(container.querySelector('span')).toBeInTheDocument();
|
|
62
|
+
expect(container.querySelector('span')).not.toHaveClass('predefined');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('renders predefined text with correct class', () => {
|
|
66
|
+
const { container } = render(<Text text="hello" predefined={true} onClick={jest.fn()} />);
|
|
67
|
+
expect(container.querySelector('span')).toHaveClass('predefined');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders correct text with both classes', () => {
|
|
71
|
+
const { container } = render(<Text text="hello" predefined={true} correct={true} onClick={jest.fn()} />);
|
|
72
|
+
const span = container.querySelector('span');
|
|
73
|
+
expect(span).toHaveClass('predefined');
|
|
74
|
+
expect(span).toHaveClass('correct');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('calls onClick when predefined text is clicked', () => {
|
|
78
|
+
const onClick = jest.fn();
|
|
79
|
+
const { container } = render(<Text text="hello" predefined={true} onClick={onClick} />);
|
|
80
|
+
const span = container.querySelector('span');
|
|
81
|
+
fireEvent.click(span);
|
|
82
|
+
expect(onClick).toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('token interaction', () => {
|
|
87
|
+
it('calls onTokenClick when clicking on a predefined token', () => {
|
|
88
|
+
const onTokenClick = jest.fn();
|
|
89
|
+
const { container } = render(<TokenText {...defaultProps} onTokenClick={onTokenClick} />);
|
|
90
|
+
const predefinedSpan = container.querySelector('.predefined');
|
|
91
|
+
if (predefinedSpan) {
|
|
92
|
+
fireEvent.click(predefinedSpan);
|
|
93
|
+
expect(onTokenClick).toHaveBeenCalled();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('handles click without window object (SSR)', () => {
|
|
98
|
+
const originalWindow = global.window;
|
|
99
|
+
global.window = undefined;
|
|
100
|
+
|
|
101
|
+
const { container } = render(<TokenText {...defaultProps} />);
|
|
102
|
+
expect(() => fireEvent.click(container.firstChild)).not.toThrow();
|
|
103
|
+
|
|
104
|
+
global.window = originalWindow;
|
|
105
|
+
});
|
|
36
106
|
});
|
|
37
107
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
108
|
+
describe('text selection', () => {
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
global.window.getSelection = jest.fn();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does not call onSelectToken when no text is selected', () => {
|
|
114
|
+
const onSelectToken = jest.fn();
|
|
115
|
+
global.window.getSelection.mockReturnValue({
|
|
116
|
+
toString: () => '',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const { container } = render(<TokenText {...defaultProps} onSelectToken={onSelectToken} />);
|
|
120
|
+
fireEvent.click(container.firstChild);
|
|
121
|
+
|
|
122
|
+
expect(onSelectToken).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('does not call onSelectToken for newline character selection', () => {
|
|
126
|
+
const onSelectToken = jest.fn();
|
|
127
|
+
global.window.getSelection.mockReturnValue({
|
|
128
|
+
toString: () => '\n',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const { container } = render(<TokenText {...defaultProps} onSelectToken={onSelectToken} />);
|
|
132
|
+
fireEvent.click(container.firstChild);
|
|
133
|
+
|
|
134
|
+
expect(onSelectToken).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('does not call onSelectToken for space character selection', () => {
|
|
138
|
+
const onSelectToken = jest.fn();
|
|
139
|
+
global.window.getSelection.mockReturnValue({
|
|
140
|
+
toString: () => ' ',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const { container } = render(<TokenText {...defaultProps} onSelectToken={onSelectToken} />);
|
|
144
|
+
fireEvent.click(container.firstChild);
|
|
145
|
+
|
|
146
|
+
expect(onSelectToken).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('does not call onSelectToken for tab character selection', () => {
|
|
150
|
+
const onSelectToken = jest.fn();
|
|
151
|
+
global.window.getSelection.mockReturnValue({
|
|
152
|
+
toString: () => '\t',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const { container } = render(<TokenText {...defaultProps} onSelectToken={onSelectToken} />);
|
|
156
|
+
fireEvent.click(container.firstChild);
|
|
157
|
+
|
|
158
|
+
expect(onSelectToken).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('props handling', () => {
|
|
163
|
+
it('accepts and uses all required props', () => {
|
|
164
|
+
const props = {
|
|
165
|
+
text: 'test text',
|
|
166
|
+
tokens: [],
|
|
167
|
+
onTokenClick: jest.fn(),
|
|
168
|
+
onSelectToken: jest.fn(),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const { container } = render(<TokenText {...props} />);
|
|
172
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('handles multiple tokens', () => {
|
|
176
|
+
const multipleTokens = [
|
|
177
|
+
{ start: 0, end: 5, text: 'lorem' },
|
|
178
|
+
{ start: 6, end: 11, text: 'ipsum' },
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const { container } = render(<TokenText {...defaultProps} tokens={multipleTokens} />);
|
|
182
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('text selection with valid text', () => {
|
|
187
|
+
let mockGetCaretCharacterOffsetWithin;
|
|
188
|
+
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
mockGetCaretCharacterOffsetWithin = jest.fn().mockReturnValue(6);
|
|
191
|
+
jest.mock('../selection-utils', () => ({
|
|
192
|
+
clearSelection: jest.fn(),
|
|
193
|
+
getCaretCharacterOffsetWithin: mockGetCaretCharacterOffsetWithin,
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('calls onSelectToken when valid text is selected without overlap', () => {
|
|
198
|
+
const onSelectToken = jest.fn();
|
|
199
|
+
const clearSelectionMock = jest.fn();
|
|
200
|
+
|
|
201
|
+
const selectionUtils = require('../selection-utils');
|
|
202
|
+
selectionUtils.clearSelection = clearSelectionMock;
|
|
203
|
+
selectionUtils.getCaretCharacterOffsetWithin = jest.fn().mockReturnValue(6);
|
|
204
|
+
|
|
205
|
+
global.window.getSelection.mockReturnValue({
|
|
206
|
+
toString: () => 'ipsum',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const { container } = render(
|
|
210
|
+
<TokenText {...defaultProps} text="lorem ipsum dolor" tokens={[]} onSelectToken={onSelectToken} />,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
fireEvent.click(container.firstChild);
|
|
214
|
+
|
|
215
|
+
expect(onSelectToken).toHaveBeenCalledWith(
|
|
216
|
+
expect.objectContaining({
|
|
217
|
+
text: 'ipsum',
|
|
218
|
+
start: expect.any(Number),
|
|
219
|
+
end: expect.any(Number),
|
|
220
|
+
}),
|
|
221
|
+
expect.any(Array),
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('handles text selection at the end of text', () => {
|
|
226
|
+
const onSelectToken = jest.fn();
|
|
227
|
+
const selectionUtils = require('../selection-utils');
|
|
228
|
+
selectionUtils.getCaretCharacterOffsetWithin = jest.fn().mockReturnValue(12);
|
|
229
|
+
|
|
230
|
+
global.window.getSelection.mockReturnValue({
|
|
231
|
+
toString: () => 'dolor',
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const { container } = render(
|
|
235
|
+
<TokenText {...defaultProps} text="lorem ipsum dolor" tokens={[]} onSelectToken={onSelectToken} />,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
fireEvent.click(container.firstChild);
|
|
239
|
+
|
|
240
|
+
expect(onSelectToken).toHaveBeenCalled();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('handles newline offset calculation', () => {
|
|
244
|
+
const onSelectToken = jest.fn();
|
|
245
|
+
const selectionUtils = require('../selection-utils');
|
|
246
|
+
selectionUtils.getCaretCharacterOffsetWithin = jest.fn().mockReturnValue(0);
|
|
247
|
+
|
|
248
|
+
global.window.getSelection.mockReturnValue({
|
|
249
|
+
toString: () => 'ipsum',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const { container } = render(
|
|
253
|
+
<TokenText {...defaultProps} text="lorem\nipsum dolor" tokens={[]} onSelectToken={onSelectToken} />,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
fireEvent.click(container.firstChild);
|
|
257
|
+
|
|
258
|
+
if (onSelectToken.mock.calls.length > 0) {
|
|
259
|
+
const token = onSelectToken.mock.calls[0][0];
|
|
260
|
+
expect(token.text).toBe('ipsum');
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('calls onSelectToken with tokensToRemove for surrounded tokens', () => {
|
|
265
|
+
const onSelectToken = jest.fn();
|
|
266
|
+
const selectionUtils = require('../selection-utils');
|
|
267
|
+
selectionUtils.getCaretCharacterOffsetWithin = jest.fn().mockReturnValue(0);
|
|
268
|
+
|
|
269
|
+
global.window.getSelection.mockReturnValue({
|
|
270
|
+
toString: () => 'lorem ipsum',
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const existingTokens = [{ start: 6, end: 11, text: 'ipsum' }];
|
|
274
|
+
|
|
275
|
+
const { container } = render(
|
|
276
|
+
<TokenText {...defaultProps} text="lorem ipsum dolor" tokens={existingTokens} onSelectToken={onSelectToken} />,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
fireEvent.click(container.firstChild);
|
|
280
|
+
|
|
281
|
+
expect(onSelectToken).toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('does not call onSelectToken when root is not available', () => {
|
|
285
|
+
const onSelectToken = jest.fn();
|
|
286
|
+
|
|
287
|
+
global.window.getSelection.mockReturnValue({
|
|
288
|
+
toString: () => 'ipsum',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const { container } = render(
|
|
292
|
+
<TokenText {...defaultProps} text="lorem ipsum" tokens={[]} onSelectToken={onSelectToken} />,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const instance = container.querySelector('div');
|
|
296
|
+
if (instance) {
|
|
297
|
+
fireEvent.click(instance);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Text component edge cases', () => {
|
|
303
|
+
it('handles null text gracefully', () => {
|
|
304
|
+
const { container } = render(<Text text={null} predefined={false} />);
|
|
305
|
+
expect(container.querySelector('span')).toBeInTheDocument();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('handles undefined text gracefully', () => {
|
|
309
|
+
const { container } = render(<Text text={undefined} predefined={false} />);
|
|
310
|
+
expect(container.querySelector('span')).toBeInTheDocument();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('renders empty string text', () => {
|
|
314
|
+
const { container } = render(<Text text="" predefined={false} />);
|
|
315
|
+
expect(container.querySelector('span')).toBeInTheDocument();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
42
318
|
});
|
package/src/tokenizer/builder.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import compact from 'lodash
|
|
1
|
+
import { clone, compact } from 'lodash-es';
|
|
2
2
|
import English from '@pie-framework/parse-english';
|
|
3
|
-
import clone from 'lodash/clone';
|
|
4
3
|
|
|
5
4
|
const g = (str, node) => {
|
|
6
5
|
if (node.children) {
|
|
@@ -41,7 +40,7 @@ export const paragraphs = (text) => {
|
|
|
41
40
|
export const handleSentence = (child, acc) => {
|
|
42
41
|
const sentenceChilds = [];
|
|
43
42
|
// we parse the children of the sentence
|
|
44
|
-
let newAcc = child.children.reduce(function(acc, child) {
|
|
43
|
+
let newAcc = child.children.reduce(function (acc, child) {
|
|
45
44
|
// if we find a whitespace node that's \n, we end the sentence
|
|
46
45
|
if (child.type === 'WhiteSpaceNode' && child.value === '\n') {
|
|
47
46
|
if (sentenceChilds.length) {
|
|
@@ -50,20 +50,10 @@ export class Controls extends React.Component {
|
|
|
50
50
|
<StyledButton onClick={onWords} size="small" color="primary" disabled={setCorrectMode}>
|
|
51
51
|
Words
|
|
52
52
|
</StyledButton>
|
|
53
|
-
<StyledButton
|
|
54
|
-
onClick={onSentences}
|
|
55
|
-
size="small"
|
|
56
|
-
color="primary"
|
|
57
|
-
disabled={setCorrectMode}
|
|
58
|
-
>
|
|
53
|
+
<StyledButton onClick={onSentences} size="small" color="primary" disabled={setCorrectMode}>
|
|
59
54
|
Sentences
|
|
60
55
|
</StyledButton>
|
|
61
|
-
<StyledButton
|
|
62
|
-
onClick={onParagraphs}
|
|
63
|
-
size="small"
|
|
64
|
-
color="primary"
|
|
65
|
-
disabled={setCorrectMode}
|
|
66
|
-
>
|
|
56
|
+
<StyledButton onClick={onParagraphs} size="small" color="primary" disabled={setCorrectMode}>
|
|
67
57
|
Paragraphs
|
|
68
58
|
</StyledButton>
|
|
69
59
|
<StyledButton size="small" color="secondary" onClick={onClear} disabled={setCorrectMode}>
|
|
@@ -71,12 +61,7 @@ export class Controls extends React.Component {
|
|
|
71
61
|
</StyledButton>
|
|
72
62
|
</div>
|
|
73
63
|
<FormControlLabel
|
|
74
|
-
control={
|
|
75
|
-
<StyledSwitch
|
|
76
|
-
checked={setCorrectMode}
|
|
77
|
-
onChange={onToggleCorrectMode}
|
|
78
|
-
/>
|
|
79
|
-
}
|
|
64
|
+
control={<StyledSwitch checked={setCorrectMode} onChange={onToggleCorrectMode} />}
|
|
80
65
|
label="Set correct answers"
|
|
81
66
|
/>
|
|
82
67
|
</StyledControls>
|
package/src/tokenizer/index.jsx
CHANGED
|
@@ -2,10 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import Controls from './controls';
|
|
4
4
|
import { styled } from '@mui/material/styles';
|
|
5
|
-
import {
|
|
6
|
-
import clone from 'lodash
|
|
7
|
-
import isEqual from 'lodash/isEqual';
|
|
8
|
-
import differenceWith from 'lodash/differenceWith';
|
|
5
|
+
import { paragraphs, sentences, words } from './builder';
|
|
6
|
+
import { clone, differenceWith, isEqual } from 'lodash-es';
|
|
9
7
|
import { noSelect } from '@pie-lib/style-utils';
|
|
10
8
|
import TokenText from './token-text';
|
|
11
9
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { styled } from '@mui/material/styles';
|
|
4
|
-
import {
|
|
4
|
+
import { intersection, normalize } from './builder';
|
|
5
5
|
import debug from 'debug';
|
|
6
6
|
import classNames from 'classnames';
|
|
7
7
|
|
|
8
8
|
import { clearSelection, getCaretCharacterOffsetWithin } from './selection-utils';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { green, yellow } from '@mui/material/colors';
|
|
11
11
|
|
|
12
12
|
const log = debug('@pie-lib:text-select:token-text');
|
|
13
13
|
|