@pie-lib/text-select 1.12.7 → 1.12.8-next.1639
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/CHANGELOG.json +1 -632
- package/CHANGELOG.md +178 -28
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/legend.js +63 -36
- package/lib/legend.js.map +1 -1
- package/lib/token-select/index.js +3 -2
- package/lib/token-select/index.js.map +1 -1
- package/lib/token-select/token.js +113 -74
- package/lib/token-select/token.js.map +1 -1
- package/lib/tokenizer/controls.js +14 -0
- package/lib/tokenizer/controls.js.map +1 -1
- package/lib/tokenizer/token-text.js +11 -2
- package/lib/tokenizer/token-text.js.map +1 -1
- package/package.json +8 -6
- package/src/__tests__/__snapshots__/text-select.test.jsx.snap +21 -0
- package/src/__tests__/text-select.test.jsx +34 -0
- package/src/__tests__/utils.test.jsx +27 -0
- package/src/legend.js +54 -36
- package/src/token-select/__tests__/__snapshots__/index.test.jsx.snap +49 -0
- package/src/token-select/__tests__/__snapshots__/token.test.jsx.snap +27 -0
- package/src/token-select/__tests__/index.test.jsx +257 -0
- package/src/token-select/__tests__/token.test.jsx +33 -0
- package/src/token-select/index.jsx +3 -1
- package/src/token-select/token.jsx +98 -72
- package/src/tokenizer/__tests__/__snapshots__/controls.test.jsx.snap +59 -0
- package/src/tokenizer/__tests__/__snapshots__/index.test.jsx.snap +31 -0
- package/src/tokenizer/__tests__/__snapshots__/token-text.test.jsx.snap +17 -0
- package/src/tokenizer/__tests__/builder.test.js +256 -0
- package/src/tokenizer/__tests__/controls.test.jsx +25 -0
- package/src/tokenizer/__tests__/index.test.jsx +140 -0
- package/src/tokenizer/__tests__/selection-utils.test.js +26 -0
- package/src/tokenizer/__tests__/token-text.test.jsx +136 -0
- package/src/tokenizer/controls.jsx +20 -1
- package/src/tokenizer/token-text.jsx +9 -0
- package/README.md +0 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Tokenizer } from '../index';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { shallow } from 'enzyme';
|
|
4
|
+
import { words, sentences, paragraphs } from '../builder';
|
|
5
|
+
|
|
6
|
+
const tokens = () => [
|
|
7
|
+
{
|
|
8
|
+
start: 0,
|
|
9
|
+
end: 1,
|
|
10
|
+
text: 'f',
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const eff = () => tokens()[0];
|
|
15
|
+
|
|
16
|
+
jest.mock('../builder', () => ({
|
|
17
|
+
words: jest.fn().mockReturnValue([{ start: 0, end: 3, text: 'foo' }]),
|
|
18
|
+
sentences: jest.fn().mockReturnValue([{ start: 0, end: 3, text: 'foo' }]),
|
|
19
|
+
paragraphs: jest.fn().mockReturnValue([{ start: 0, end: 3, text: 'foo' }]),
|
|
20
|
+
}));
|
|
21
|
+
describe('tokenizer', () => {
|
|
22
|
+
describe('snapshot', () => {
|
|
23
|
+
it('renders', () => {
|
|
24
|
+
const w = shallow(<Tokenizer text="foo" classes={{}} onChange={jest.fn()} tokens={tokens()} />);
|
|
25
|
+
expect(w).toMatchSnapshot();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('logic', () => {
|
|
30
|
+
let w;
|
|
31
|
+
let onChange;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
onChange = jest.fn();
|
|
35
|
+
w = shallow(<Tokenizer text="foo" classes={{}} onChange={onChange} tokens={tokens()} />);
|
|
36
|
+
});
|
|
37
|
+
describe('tokenIndex', () => {
|
|
38
|
+
it('returns 0', () => {
|
|
39
|
+
const index = w.instance().tokenIndex(eff());
|
|
40
|
+
expect(index).toEqual(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns -1', () => {
|
|
44
|
+
const index = w.instance().tokenIndex({ start: 2, end: 3, text: 'f' });
|
|
45
|
+
expect(index).toEqual(-1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('tokenClick', () => {
|
|
50
|
+
let i;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
i = w.instance();
|
|
54
|
+
i.setCorrect = jest.fn();
|
|
55
|
+
i.removeToken = jest.fn();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('calls removeToken if setCorrectMode == false', () => {
|
|
59
|
+
i.tokenClick(eff());
|
|
60
|
+
expect(i.setCorrect).not.toBeCalled();
|
|
61
|
+
expect(i.removeToken).toBeCalled();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('calls setCorrect if setCorrectMode == true', () => {
|
|
65
|
+
i.setState({ setCorrectMode: true });
|
|
66
|
+
i.tokenClick(eff());
|
|
67
|
+
expect(i.setCorrect).toBeCalled();
|
|
68
|
+
expect(i.removeToken).not.toBeCalled();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('selectToken', () => {
|
|
73
|
+
it('calls onChange', () => {
|
|
74
|
+
w.instance().selectToken({ start: 1, end: 3, text: 'oo' }, [{ start: 0, end: 1, text: 'f' }]);
|
|
75
|
+
expect(onChange).toBeCalledWith([{ start: 1, end: 3, text: 'oo' }], '');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('buildParagraphsTokens', () => {
|
|
80
|
+
it('calls paragraphs', () => {
|
|
81
|
+
w.instance().buildTokens('paragraph', paragraphs);
|
|
82
|
+
expect(paragraphs).toBeCalledWith('foo');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('buildSentenceTokens', () => {
|
|
87
|
+
it('calls sentences', () => {
|
|
88
|
+
w.instance().buildTokens('sentence', sentences);
|
|
89
|
+
expect(sentences).toBeCalledWith('foo');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('buildWordTokens', () => {
|
|
94
|
+
it('calls words', () => {
|
|
95
|
+
w.instance().buildTokens('word', words);
|
|
96
|
+
expect(words).toBeCalledWith('foo');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('clear', () => {
|
|
101
|
+
it('calls onChange with an empty array', () => {
|
|
102
|
+
w.instance().clear();
|
|
103
|
+
expect(onChange).toBeCalledWith([], '');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('toggleCorrectMode', () => {
|
|
108
|
+
it('set state', () => {
|
|
109
|
+
w.setState({ setCorrectMode: true });
|
|
110
|
+
w.instance().toggleCorrectMode();
|
|
111
|
+
expect(w.state('setCorrectMode')).toEqual(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('setCorrect', () => {
|
|
116
|
+
it('calls onChange', () => {
|
|
117
|
+
w.instance().setCorrect({ start: 0, end: 1, text: 'f' });
|
|
118
|
+
expect(onChange).toBeCalledWith([{ start: 0, end: 1, text: 'f', correct: true }], '');
|
|
119
|
+
});
|
|
120
|
+
it('calls onChange w/ correct: false', () => {
|
|
121
|
+
w.setProps({
|
|
122
|
+
tokens: [{ start: 0, end: 1, text: 'f', correct: true }],
|
|
123
|
+
});
|
|
124
|
+
w.instance().setCorrect({ start: 0, end: 1, text: 'f' });
|
|
125
|
+
expect(onChange).toBeCalledWith([{ start: 0, end: 1, text: 'f', correct: false }], '');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('removeToken', () => {
|
|
130
|
+
it('calls onChange', () => {
|
|
131
|
+
w.instance().removeToken({ start: 0, end: 1, text: 'f' });
|
|
132
|
+
expect(onChange).toBeCalledWith([], '');
|
|
133
|
+
});
|
|
134
|
+
it('does not call onChange if it cant find the token', () => {
|
|
135
|
+
w.instance().removeToken({ start: 2, end: 3, text: 'a' });
|
|
136
|
+
expect(onChange).not.toBeCalled();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { clearSelection, getCaretCharacterOffsetWithin } from '../selection-utils';
|
|
2
|
+
|
|
3
|
+
describe('selection-utils', () => {
|
|
4
|
+
let selection;
|
|
5
|
+
let range;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
selection = {
|
|
8
|
+
removeAllRanges: jest.fn(),
|
|
9
|
+
addRange: jest.fn(),
|
|
10
|
+
getRangeAt: jest.fn().mockReturnValue(range),
|
|
11
|
+
};
|
|
12
|
+
global.document.getSelection = jest.fn().mockReturnValue(selection);
|
|
13
|
+
global.document.createRange = jest.fn();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('clearSelection', () => {
|
|
17
|
+
it('calls removeAllRanges', () => {
|
|
18
|
+
clearSelection();
|
|
19
|
+
expect(selection.removeAllRanges).toBeCalled();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
xdescribe('getCaretCharacterOffsetWithin', () => {
|
|
24
|
+
it('TODO', () => {});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { shallow } from 'enzyme';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import TokenText from '../token-text';
|
|
4
|
+
import { intersection } from '../builder';
|
|
5
|
+
import { clearSelection, getCaretCharacterOffsetWithin } from '../selection-utils';
|
|
6
|
+
|
|
7
|
+
jest.mock('../selection-utils', () => ({
|
|
8
|
+
clearSelection: jest.fn(),
|
|
9
|
+
getCaretCharacterOffsetWithin: jest.fn().mockReturnValue(10),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
jest.mock('../builder', () => ({
|
|
13
|
+
intersection: jest.fn().mockReturnValue({
|
|
14
|
+
hasOverlap: jest.fn().mockReturnValue(false),
|
|
15
|
+
surroundedTokens: jest.fn().mockReturnValue([]),
|
|
16
|
+
}),
|
|
17
|
+
normalize: jest.fn().mockReturnValue([
|
|
18
|
+
{
|
|
19
|
+
text: `lorem\nfoo bar`,
|
|
20
|
+
start: 0,
|
|
21
|
+
end: 13,
|
|
22
|
+
predefined: true,
|
|
23
|
+
},
|
|
24
|
+
]),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const tokens = () => [
|
|
28
|
+
{
|
|
29
|
+
start: 0,
|
|
30
|
+
end: 7,
|
|
31
|
+
text: `lorem\nfoo bar`,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const mkEvent = () => ({
|
|
36
|
+
preventDefault: jest.fn(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('token-text', () => {
|
|
40
|
+
describe('snapshot', () => {
|
|
41
|
+
it('renders', () => {
|
|
42
|
+
const w = shallow(
|
|
43
|
+
<TokenText onTokenClick={jest.fn()} onSelectToken={jest.fn()} text={`lorem\nfoo bar`} tokens={tokens()} />,
|
|
44
|
+
);
|
|
45
|
+
expect(w).toMatchSnapshot();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('logic', () => {
|
|
50
|
+
let w;
|
|
51
|
+
let onSelectToken;
|
|
52
|
+
let onTokenClick;
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
global.window.getSelection = jest.fn().mockReturnValue({
|
|
55
|
+
toString: () => 'bar',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
onSelectToken = jest.fn();
|
|
59
|
+
onTokenClick = jest.fn();
|
|
60
|
+
|
|
61
|
+
w = shallow(
|
|
62
|
+
<TokenText
|
|
63
|
+
onTokenClick={onTokenClick}
|
|
64
|
+
onSelectToken={onSelectToken}
|
|
65
|
+
text={`lorem\nfoo bar`}
|
|
66
|
+
tokens={tokens()}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('mouseup', () => {
|
|
72
|
+
it('calls event.preventDefault', () => {
|
|
73
|
+
const event = mkEvent();
|
|
74
|
+
w.instance().onClick(event);
|
|
75
|
+
expect(event.preventDefault).toBeCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('calls getCaretCharacterOffsetWithin', () => {
|
|
79
|
+
const event = mkEvent();
|
|
80
|
+
w.instance().root = {};
|
|
81
|
+
w.instance().onClick(event);
|
|
82
|
+
expect(getCaretCharacterOffsetWithin).toBeCalledWith({});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('calls clear selection if there is an overlap', () => {
|
|
86
|
+
intersection.mockReturnValue({
|
|
87
|
+
hasOverlap: true,
|
|
88
|
+
});
|
|
89
|
+
const event = mkEvent();
|
|
90
|
+
w.instance().root = {};
|
|
91
|
+
w.instance().onClick(event);
|
|
92
|
+
expect(clearSelection).toBeCalled();
|
|
93
|
+
expect(onSelectToken).not.toBeCalled();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('calls onSelectToken', () => {
|
|
97
|
+
const event = mkEvent();
|
|
98
|
+
intersection.mockReturnValue({
|
|
99
|
+
hasOverlap: false,
|
|
100
|
+
surroundedTokens: [],
|
|
101
|
+
});
|
|
102
|
+
w.instance().root = {};
|
|
103
|
+
w.instance().onClick(event);
|
|
104
|
+
expect(onSelectToken).toBeCalledWith({ text: 'bar', start: 10, end: 13 }, []);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('does not call onSelectToken for ["\n", " ", "\t"]', () => {
|
|
108
|
+
const event = mkEvent();
|
|
109
|
+
|
|
110
|
+
intersection.mockReturnValue({
|
|
111
|
+
hasOverlap: false,
|
|
112
|
+
surroundedTokens: [],
|
|
113
|
+
});
|
|
114
|
+
w.instance().root = {};
|
|
115
|
+
|
|
116
|
+
global.window.getSelection = jest.fn().mockReturnValue({
|
|
117
|
+
toString: () => '\n',
|
|
118
|
+
});
|
|
119
|
+
w.instance().onClick(event);
|
|
120
|
+
expect(onSelectToken).not.toBeCalled();
|
|
121
|
+
|
|
122
|
+
global.window.getSelection = jest.fn().mockReturnValue({
|
|
123
|
+
toString: () => ' ',
|
|
124
|
+
});
|
|
125
|
+
w.instance().onClick(event);
|
|
126
|
+
expect(onSelectToken).not.toBeCalled();
|
|
127
|
+
|
|
128
|
+
global.window.getSelection = jest.fn().mockReturnValue({
|
|
129
|
+
toString: () => '\t',
|
|
130
|
+
});
|
|
131
|
+
w.instance().onClick(event);
|
|
132
|
+
expect(onSelectToken).not.toBeCalled();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -4,6 +4,8 @@ import Button from '@material-ui/core/Button';
|
|
|
4
4
|
import { withStyles } from '@material-ui/core/styles';
|
|
5
5
|
import Switch from '@material-ui/core/Switch';
|
|
6
6
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
|
7
|
+
import { color } from '@pie-lib/render-ui';
|
|
8
|
+
import classNames from 'classnames';
|
|
7
9
|
|
|
8
10
|
export class Controls extends React.Component {
|
|
9
11
|
static propTypes = {
|
|
@@ -50,7 +52,18 @@ export class Controls extends React.Component {
|
|
|
50
52
|
</Button>
|
|
51
53
|
</div>
|
|
52
54
|
<FormControlLabel
|
|
53
|
-
control={
|
|
55
|
+
control={
|
|
56
|
+
<Switch
|
|
57
|
+
classes={{
|
|
58
|
+
checked: classes.checkedThumb,
|
|
59
|
+
bar: classNames({
|
|
60
|
+
[classes.checkedBar]: setCorrectMode,
|
|
61
|
+
}),
|
|
62
|
+
}}
|
|
63
|
+
checked={setCorrectMode}
|
|
64
|
+
onChange={onToggleCorrectMode}
|
|
65
|
+
/>
|
|
66
|
+
}
|
|
54
67
|
label="Set correct answers"
|
|
55
68
|
/>
|
|
56
69
|
</div>
|
|
@@ -66,4 +79,10 @@ export default withStyles((theme) => ({
|
|
|
66
79
|
alignItems: 'center',
|
|
67
80
|
justifyContent: 'space-between',
|
|
68
81
|
},
|
|
82
|
+
checkedThumb: {
|
|
83
|
+
color: `${color.tertiary()} !important`,
|
|
84
|
+
},
|
|
85
|
+
checkedBar: {
|
|
86
|
+
backgroundColor: `${color.tertiaryLight()} !important`,
|
|
87
|
+
},
|
|
69
88
|
}))(Controls);
|
|
@@ -16,9 +16,18 @@ export const Text = withStyles(() => ({
|
|
|
16
16
|
cursor: 'pointer',
|
|
17
17
|
backgroundColor: yellow[100],
|
|
18
18
|
border: `dashed 0px ${yellow[700]}`,
|
|
19
|
+
// we need this for nested tokenized elements like paragraphs, where p is inside span
|
|
20
|
+
'& *': {
|
|
21
|
+
cursor: 'pointer',
|
|
22
|
+
backgroundColor: yellow[100],
|
|
23
|
+
border: `dashed 0px ${yellow[700]}`,
|
|
24
|
+
},
|
|
19
25
|
},
|
|
20
26
|
correct: {
|
|
21
27
|
backgroundColor: green[500],
|
|
28
|
+
'& *': {
|
|
29
|
+
backgroundColor: green[500],
|
|
30
|
+
},
|
|
22
31
|
},
|
|
23
32
|
}))(({ text, predefined, classes, onClick, correct }) => {
|
|
24
33
|
const formattedText = (text || '').replace(/\n/g, '<br>');
|
package/README.md
DELETED