Skip to content

Commit 9b84bd7

Browse files
committed
feat: add support for user-events
1 parent cbeb145 commit 9b84bd7

File tree

15 files changed

+336
-146
lines changed

15 files changed

+336
-146
lines changed

devtools/src/content-script/highlighter/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export default function setupHighlighter({
125125
function onPointerDown(event) {
126126
stopPropagation(event);
127127

128-
selectNode(event.target, { trigger: 'click' });
128+
selectNode(event.target, { origin: event.isTrusted ? 'click' : 'script' });
129129
}
130130

131131
function onPointerOver(event) {
@@ -145,7 +145,7 @@ export default function setupHighlighter({
145145
}
146146

147147
showOverlay([target], false);
148-
selectNode(target, { trigger: 'hover' });
148+
selectNode(target, { origin: event.isTrusted ? 'hover' : 'script' });
149149
}
150150

151151
function onPointerUp(event) {

package-lock.json

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
},
4040
"dependencies": {
4141
"@testing-library/dom": "^7.15.0",
42+
"@testing-library/user-event": "^12.0.2",
4243
"codemirror": "5.54.0",
4344
"crx-bridge": "^2.1.0",
4445
"deep-diff": "^1.0.2",
@@ -65,7 +66,6 @@
6566
"@babel/preset-react": "^7.10.1",
6667
"@testing-library/jest-dom": "^5.9.0",
6768
"@testing-library/react": "^10.2.0",
68-
"@testing-library/user-event": "^11.4.2",
6969
"@types/fs-extra": "^9.0.1",
7070
"babel-eslint": "^10.1.0",
7171
"chrome-launch": "^1.1.4",

src/components/Editor.js

Lines changed: 125 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import 'codemirror/addon/fold/xml-fold';
88
import 'codemirror/addon/scroll/simplescrollbars';
99
import 'codemirror/addon/hint/show-hint';
1010
import { queries } from '@testing-library/dom';
11+
import userEvent from '@testing-library/user-event';
1112

1213
import CodeMirror from 'codemirror';
13-
import debounce from 'lodash/debounce';
1414
import beautify from '../lib/beautify';
1515

1616
const baseOptions = {
@@ -26,17 +26,14 @@ const baseOptions = {
2626

2727
const options = {
2828
html: {
29-
...baseOptions,
3029
mode: { name: 'text/html', multilineTagIndentPastTag: false },
3130
},
3231

3332
htmlmixed: {
34-
...baseOptions,
3533
mode: { name: 'htmlmixed', multilineTagIndentPastTag: false },
3634
},
3735

3836
javascript: {
39-
...baseOptions,
4037
mode: 'javascript',
4138
extraKeys: { 'Ctrl-Space': 'autocomplete' },
4239
hintOptions: { hint: getQueryHints },
@@ -48,6 +45,7 @@ const suggestions = {
4845
.filter((x) => x.startsWith('getBy'))
4946
.sort(),
5047
container: ['querySelector', 'querySelectorAll'],
48+
userEvent: Object.keys(userEvent).sort(),
5149
};
5250

5351
function getQueryHints(cm) {
@@ -66,7 +64,7 @@ function getQueryHints(cm) {
6664
++end;
6765
}
6866

69-
const word = line.slice(start, end).toLowerCase();
67+
const word = line.slice(start, end); //.toLowerCase();
7068
const offset = word.lastIndexOf('.') + 1;
7169
const list = [];
7270

@@ -83,8 +81,8 @@ function getQueryHints(cm) {
8381
} else if (word.includes('.')) {
8482
// user is already one level deeper, entered `screen.get...`
8583
const [obj, method] = word.split('.');
86-
const values = (suggestions[obj] || []).filter((x) =>
87-
x.toLowerCase().includes(method),
84+
const values = (suggestions[obj] || []).filter(
85+
(x) => x.includes(method), //x.toLowerCase().includes(method.toLowerCase()),
8886
);
8987
list.push(...values);
9088
} else {
@@ -154,71 +152,143 @@ const NON_TRIGGER_KEYS = {
154152
'222': 'quote',
155153
};
156154

157-
function Editor({ onLoad, onChange, mode, initialValue }) {
158-
const elem = useRef();
159-
const editor = useRef();
155+
function formatValue(cm) {
156+
const mode = cm.options.mode.name;
157+
const value = cm.getValue();
158+
const formatted = beautify.format(mode, value);
159+
cm.setValue(formatted);
160+
}
160161

161-
useEffect(() => {
162-
editor.current = CodeMirror.fromTextArea(
163-
elem.current,
164-
options[mode] || baseOptions,
165-
);
166-
editor.current.setValue(initialValue || '');
162+
function autoComplete(cm, event) {
163+
const cursor = cm.getDoc().getCursor();
164+
const token = cm.getTokenAt(cursor);
167165

168-
// in some cases, CM loads with a scrollbar visible
169-
// while it shouldn't be required. Requesting a refresh
170-
// fixes this
171-
requestAnimationFrame(() => {
172-
editor.current.refresh();
166+
const shouldComplete =
167+
!cm.state.completionActive &&
168+
!NON_TRIGGER_KEYS[(event.keyCode || event.which).toString()] &&
169+
!(token.string === '(' || token.string === ')');
170+
171+
if (shouldComplete) {
172+
CodeMirror.commands.autocomplete(cm, null, {
173+
completeSingle: false,
173174
});
174-
}, [mode]);
175+
}
176+
}
175177

176-
useEffect(() => {
177-
if (!editor.current || typeof onChange !== 'function') {
178+
function handleChange(cm, change) {
179+
switch (change.origin) {
180+
case 'setValue':
178181
return;
182+
183+
case 'paste': {
184+
formatValue(cm);
185+
break;
179186
}
180187

181-
editor.current.on(
182-
'changes',
183-
debounce(() => {
184-
onChange(editor.current.getValue());
185-
}, 25),
186-
);
188+
default: {
189+
cm.onChange(cm.getValue(), { origin: change.origin });
190+
break;
191+
}
192+
}
193+
}
187194

188-
editor.current.on('keyup', (editor, event) => {
189-
const cursor = editor.getDoc().getCursor();
190-
const token = editor.getTokenAt(cursor);
195+
// with devtools open, the roundtrip between blur and result is about 200 ms.
196+
// Close devtools, and it drops down to ~ 5 ms. If you're still getting weird
197+
// user-event / fireEvent issues, it might be that either 25 or 500 ms is to low for
198+
// your machine. Pumping up production timeouts can cause more side-effects, but
199+
// I think we can quite safely increase to ~ 100 ms when neccasary. For the dev
200+
// environment, I hope you can life with it. The worst thing that happens, is
201+
// that the query editor loses focus while you're typing your userEvents.
202+
const threshold = process.env.NODE_ENV === 'production' ? 25 : 500;
191203

192-
const shouldComplete =
193-
!editor.state.completionActive &&
194-
!NON_TRIGGER_KEYS[(event.keyCode || event.which).toString()] &&
195-
!(token.string === '(' || token.string === ')');
204+
// There are two ways that the query editor can use blur. One is if the user
205+
// decides to leave the editor. The other is caused by evaluating the users
206+
// script. For example to focus one of the inputs in the sandbox, to enter
207+
// some text, or click an element. If this happens, we need to restore focus
208+
// as soon as possible. This is, when the SANDBOX_READY event occurs before
209+
// the `threshold`ms timeout has passed.
210+
function handleBlur(cm) {
211+
const cursor = cm.getCursor();
212+
let timeout;
196213

197-
if (shouldComplete) {
198-
CodeMirror.commands.autocomplete(editor, null, {
199-
completeSingle: false,
200-
});
201-
}
202-
});
214+
function listener(event) {
215+
const {
216+
data: { source, type },
217+
} = event;
203218

204-
const format = () => {
205-
const value = editor.current.getValue();
206-
const formatted = beautify.format(mode, value);
207-
editor.current.setValue(formatted);
208-
};
219+
if (source !== 'testing-playground-sandbox' || type !== 'SANDBOX_READY') {
220+
return;
221+
}
222+
223+
// if we came to here, it means that the time between the `blur` event and
224+
// SANDBOX_READY msg was within `threshold` ms. Otherwise, the timeout would
225+
// already have removed this listener.
226+
clearTimeout(timeout);
227+
window.removeEventListener('message', listener);
228+
cm.focus();
229+
cm.setCursor(cursor);
230+
}
231+
232+
// note, { once: true } doesn't work here! there can be multiple messages, while
233+
// we need one with a specific event attribute
234+
window.addEventListener('message', listener);
235+
236+
// we wait a couple of ms for the SANDBOX_READY event, if that doesn't come
237+
// before given treshold, we assume the user left the editor, and allow it
238+
// lose focus.
239+
timeout = setTimeout(() => {
240+
window.removeEventListener('message', listener);
241+
formatValue(cm);
242+
}, threshold);
243+
}
209244

210-
editor.current.on('change', (_, change) => {
211-
if (change.origin !== 'paste') {
212-
return;
213-
}
245+
function Editor({ onLoad, onChange, mode, initialValue }) {
246+
const elem = useRef();
247+
const editor = useRef();
214248

215-
format();
249+
useEffect(() => {
250+
editor.current = CodeMirror.fromTextArea(elem.current, {
251+
...baseOptions,
252+
...options[mode],
253+
extraKeys:
254+
typeof onChange === 'function'
255+
? {
256+
'Ctrl-Enter': () => {
257+
onChange(editor.current.getValue(), { origin: 'user' });
258+
},
259+
'Cmd-Enter': () => {
260+
onChange(editor.current.getValue(), { origin: 'user' });
261+
},
262+
...(options[mode].extraKeys || {}),
263+
}
264+
: options[mode].extraKeys,
216265
});
217266

218-
editor.current.on('blur', format);
267+
editor.current.setValue(initialValue || '');
268+
269+
editor.current.on('change', handleChange);
270+
editor.current.on('keyup', autoComplete);
271+
editor.current.on('blur', handleBlur);
219272

220273
onLoad(editor.current);
221-
}, [editor.current, onChange]);
274+
275+
// in some cases, CM loads with a scrollbar visible
276+
// while it shouldn't be required. Requesting a refresh
277+
// fixes this
278+
requestAnimationFrame(() => {
279+
editor.current.refresh();
280+
});
281+
282+
return () => {
283+
editor.current.off('change', handleChange);
284+
editor.current.off('keyup', autoComplete);
285+
editor.current.off('blur', handleBlur);
286+
};
287+
}, [mode, onChange, onLoad, initialValue]);
288+
289+
useEffect(() => {
290+
editor.current.onChange = onChange;
291+
}, [onChange]);
222292

223293
return (
224294
<div className="w-full h-full">

src/components/Playground.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ function Playground() {
3939

4040
<div className="flex-none h-8" />
4141

42-
<div className="editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2 overflow-hidden">
42+
<div className="editor gap-4 md:gap-8 md:h-56 flex-auto grid-cols-1 md:grid-cols-2">
4343
<div className="flex-auto relative h-56 md:h-full">
4444
<Query query={query} result={result} dispatch={dispatch} />
4545
</div>
4646

47-
<div className="flex-auto h-56 md:h-full overflow-hidden">
47+
<div className="flex-auto h-56 md:h-full">
4848
<Result result={result} dispatch={dispatch} />
4949
</div>
5050
</div>

0 commit comments

Comments
 (0)