autopair 1.2.1 → 1.2.2
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/cypress/e2e/autopair.cy.js +192 -0
- package/cypress/fixtures/example.json +5 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +17 -0
- package/cypress.config.js +9 -0
- package/package.json +8 -2
- package/src/autopair.js +3 -19
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
describe('autopair', () => {
|
|
2
|
+
it('wraps selected text in parentheses when "(" is typed', () => {
|
|
3
|
+
cy.visit('/index.html');
|
|
4
|
+
|
|
5
|
+
// Type some text
|
|
6
|
+
cy.get('textarea').type('hello');
|
|
7
|
+
|
|
8
|
+
// Select all text
|
|
9
|
+
cy.get('textarea').then($el => {
|
|
10
|
+
$el[0].selectionStart = 0;
|
|
11
|
+
$el[0].selectionEnd = $el.val().length;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Type "(" to wrap selection
|
|
15
|
+
cy.get('textarea').type('(');
|
|
16
|
+
|
|
17
|
+
// Check result
|
|
18
|
+
cy.get('textarea').should('have.value', '(hello)');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('types through closing character', () => {
|
|
22
|
+
cy.visit('/index.html');
|
|
23
|
+
|
|
24
|
+
// Type a pair
|
|
25
|
+
cy.get('textarea').type('()');
|
|
26
|
+
|
|
27
|
+
// Move cursor between the parentheses
|
|
28
|
+
cy.get('textarea').then($el => {
|
|
29
|
+
$el[0].selectionStart = 1;
|
|
30
|
+
$el[0].selectionEnd = 1;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Type ")" to test typethrough
|
|
34
|
+
cy.get('textarea').type(')');
|
|
35
|
+
|
|
36
|
+
// Value should be unchanged, cursor moves past
|
|
37
|
+
cy.get('textarea').then($el => {
|
|
38
|
+
expect($el.val()).to.eq('()');
|
|
39
|
+
expect($el[0].selectionStart).to.eq(2); // cursor after )
|
|
40
|
+
expect($el[0].selectionEnd).to.eq(2);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('atomically deletes a pair', () => {
|
|
45
|
+
cy.visit('/index.html');
|
|
46
|
+
|
|
47
|
+
// Type a pair
|
|
48
|
+
cy.get('textarea').type('()');
|
|
49
|
+
|
|
50
|
+
// Move cursor between the parentheses
|
|
51
|
+
cy.get('textarea').then($el => {
|
|
52
|
+
$el[0].selectionStart = 1;
|
|
53
|
+
$el[0].selectionEnd = 1;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Press Backspace
|
|
57
|
+
cy.get('textarea').type('{backspace}');
|
|
58
|
+
|
|
59
|
+
// Both characters should be deleted
|
|
60
|
+
cy.get('textarea').should('have.value', '');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('automatically inserts closing parenthesis when "(" is typed', () => {
|
|
64
|
+
cy.visit('/index.html');
|
|
65
|
+
|
|
66
|
+
// Type opening parenthesis
|
|
67
|
+
cy.get('textarea').type('(');
|
|
68
|
+
|
|
69
|
+
// Should insert closing parenthesis and place cursor between
|
|
70
|
+
cy.get('textarea').then($el => {
|
|
71
|
+
expect($el.val()).to.eq('()');
|
|
72
|
+
expect($el[0].selectionStart).to.eq(1);
|
|
73
|
+
expect($el[0].selectionEnd).to.eq(1);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('autopairs before a whitelisted character', () => {
|
|
78
|
+
cy.visit('/index.html');
|
|
79
|
+
|
|
80
|
+
// Type a semicolon
|
|
81
|
+
cy.get('textarea').type(';');
|
|
82
|
+
|
|
83
|
+
// Move cursor before the semicolon
|
|
84
|
+
cy.get('textarea').then($el => {
|
|
85
|
+
$el[0].selectionStart = 0;
|
|
86
|
+
$el[0].selectionEnd = 0;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Type "("
|
|
90
|
+
cy.get('textarea').type('(');
|
|
91
|
+
|
|
92
|
+
// Should autopair
|
|
93
|
+
cy.get('textarea').then($el => {
|
|
94
|
+
expect($el.val()).to.eq('();');
|
|
95
|
+
expect($el[0].selectionStart).to.eq(1);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('does not autopair before a non-whitelisted character', () => {
|
|
100
|
+
cy.visit('/index.html');
|
|
101
|
+
|
|
102
|
+
// Type a period
|
|
103
|
+
cy.get('textarea').type('.');
|
|
104
|
+
|
|
105
|
+
// Move cursor before the period
|
|
106
|
+
cy.get('textarea').then($el => {
|
|
107
|
+
$el[0].selectionStart = 0;
|
|
108
|
+
$el[0].selectionEnd = 0;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Type "("
|
|
112
|
+
cy.get('textarea').type('(');
|
|
113
|
+
|
|
114
|
+
// Should NOT autopair
|
|
115
|
+
cy.get('textarea').then($el => {
|
|
116
|
+
expect($el.val()).to.eq('(.');
|
|
117
|
+
expect($el[0].selectionStart).to.eq(1);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('autopairs multiple opening parentheses correctly', () => {
|
|
122
|
+
cy.visit('/index.html');
|
|
123
|
+
|
|
124
|
+
// Type three opening parentheses
|
|
125
|
+
cy.get('textarea').type('(((');
|
|
126
|
+
|
|
127
|
+
// Should result in three closing parentheses as well
|
|
128
|
+
cy.get('textarea').then($el => {
|
|
129
|
+
expect($el.val()).to.eq('((()))');
|
|
130
|
+
expect($el[0].selectionStart).to.eq(3); // cursor inside the innermost pair
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('does not autopair symmetric characters after an existing pair', () => {
|
|
135
|
+
cy.visit('/index.html');
|
|
136
|
+
|
|
137
|
+
// Type a single quote
|
|
138
|
+
cy.get('textarea').type("'");
|
|
139
|
+
|
|
140
|
+
// Should autopair to ''
|
|
141
|
+
cy.get('textarea').then($el => {
|
|
142
|
+
expect($el.val()).to.eq("''");
|
|
143
|
+
expect($el[0].selectionStart).to.eq(1); // cursor inside
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Move cursor to the end
|
|
147
|
+
cy.get('textarea').then($el => {
|
|
148
|
+
$el[0].selectionStart = $el.val().length;
|
|
149
|
+
$el[0].selectionEnd = $el.val().length;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Type another single quote
|
|
153
|
+
cy.get('textarea').type("'");
|
|
154
|
+
|
|
155
|
+
// Should NOT autopair again
|
|
156
|
+
cy.get('textarea').then($el => {
|
|
157
|
+
expect($el.val()).to.eq("'''");
|
|
158
|
+
expect($el[0].selectionStart).to.eq(3); // cursor after last '
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('handles custom pairing correctly', () => {
|
|
163
|
+
cy.visit('/index.html');
|
|
164
|
+
|
|
165
|
+
// Inject autopair immediately
|
|
166
|
+
cy.document().then(doc => {
|
|
167
|
+
const script = doc.createElement('script');
|
|
168
|
+
script.type = 'module';
|
|
169
|
+
script.textContent = `
|
|
170
|
+
import autopair from './autopair.js';
|
|
171
|
+
autopair(document.querySelector('textarea'), {
|
|
172
|
+
'(': ')',
|
|
173
|
+
'[': ']',
|
|
174
|
+
'{': '}',
|
|
175
|
+
"'": "'",
|
|
176
|
+
'"': '"',
|
|
177
|
+
'<': '>'
|
|
178
|
+
});
|
|
179
|
+
`;
|
|
180
|
+
doc.body.appendChild(script);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Type <
|
|
184
|
+
cy.get('textarea').type('<');
|
|
185
|
+
|
|
186
|
+
// Should autopair to <>
|
|
187
|
+
cy.get('textarea').then($el => {
|
|
188
|
+
expect($el.val()).to.eq('<>');
|
|
189
|
+
expect($el[0].selectionStart).to.eq(1);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ***********************************************
|
|
2
|
+
// This example commands.js shows you how to
|
|
3
|
+
// create various custom commands and overwrite
|
|
4
|
+
// existing commands.
|
|
5
|
+
//
|
|
6
|
+
// For more comprehensive examples of custom
|
|
7
|
+
// commands please read more here:
|
|
8
|
+
// https://on.cypress.io/custom-commands
|
|
9
|
+
// ***********************************************
|
|
10
|
+
//
|
|
11
|
+
//
|
|
12
|
+
// -- This is a parent command --
|
|
13
|
+
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
14
|
+
//
|
|
15
|
+
//
|
|
16
|
+
// -- This is a child command --
|
|
17
|
+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
|
18
|
+
//
|
|
19
|
+
//
|
|
20
|
+
// -- This is a dual command --
|
|
21
|
+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
22
|
+
//
|
|
23
|
+
//
|
|
24
|
+
// -- This will overwrite an existing command --
|
|
25
|
+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ***********************************************************
|
|
2
|
+
// This example support/e2e.js is processed and
|
|
3
|
+
// loaded automatically before your test files.
|
|
4
|
+
//
|
|
5
|
+
// This is a great place to put global configuration and
|
|
6
|
+
// behavior that modifies Cypress.
|
|
7
|
+
//
|
|
8
|
+
// You can change the location of this file or turn off
|
|
9
|
+
// automatically serving support files with the
|
|
10
|
+
// 'supportFile' configuration option.
|
|
11
|
+
//
|
|
12
|
+
// You can read more here:
|
|
13
|
+
// https://on.cypress.io/configuration
|
|
14
|
+
// ***********************************************************
|
|
15
|
+
|
|
16
|
+
// Import commands.js using ES2015 syntax:
|
|
17
|
+
import './commands'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autopair",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Automatically closes parentheses and similar characters.",
|
|
5
5
|
"main": "src/autopair.js",
|
|
6
6
|
"author": "Dennis Hackethal <engineering@dennishackethal.com>",
|
|
@@ -34,5 +34,11 @@
|
|
|
34
34
|
"input",
|
|
35
35
|
"javascript",
|
|
36
36
|
"web"
|
|
37
|
-
]
|
|
37
|
+
],
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"cypress": "^15.9.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "npx cypress run"
|
|
43
|
+
}
|
|
38
44
|
}
|
package/src/autopair.js
CHANGED
|
@@ -41,9 +41,6 @@ export default function autopair(textarea, pairs = {
|
|
|
41
41
|
const closing = pairs[evt.key];
|
|
42
42
|
if (!closing) return;
|
|
43
43
|
|
|
44
|
-
const isWordChar = /[\w]/;
|
|
45
|
-
const punctuation = /[;,.})\]]/;
|
|
46
|
-
|
|
47
44
|
// Wrap selection if present
|
|
48
45
|
if (start !== end) {
|
|
49
46
|
evt.preventDefault();
|
|
@@ -55,28 +52,15 @@ export default function autopair(textarea, pairs = {
|
|
|
55
52
|
return;
|
|
56
53
|
}
|
|
57
54
|
|
|
55
|
+
const nextCharWhitelist = /[\s;})\]]/;
|
|
58
56
|
const nextChar = value[end] || '';
|
|
59
57
|
const prevChar = value[start - 1] || '';
|
|
60
|
-
|
|
61
58
|
const insidePair = closing === nextChar;
|
|
62
|
-
const safeNext =
|
|
59
|
+
const safeNext = nextChar === '' || nextCharWhitelist.test(nextChar);
|
|
63
60
|
const isSymmetric = evt.key === closing;
|
|
64
61
|
|
|
65
62
|
// Autoclose only when allowed
|
|
66
|
-
if (
|
|
67
|
-
!insidePair &&
|
|
68
|
-
(
|
|
69
|
-
!safeNext ||
|
|
70
|
-
(
|
|
71
|
-
isSymmetric &&
|
|
72
|
-
start === end &&
|
|
73
|
-
(
|
|
74
|
-
isWordChar.test(prevChar) || // end of word
|
|
75
|
-
prevChar === evt.key // repeated quote
|
|
76
|
-
)
|
|
77
|
-
)
|
|
78
|
-
)
|
|
79
|
-
) {
|
|
63
|
+
if (!insidePair && (!safeNext || (isSymmetric && start === end && prevChar === evt.key))) {
|
|
80
64
|
return;
|
|
81
65
|
}
|
|
82
66
|
|