-
-
Notifications
You must be signed in to change notification settings - Fork 62
/
parser.js
143 lines (117 loc) · 3.81 KB
/
parser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import prettyFormat from 'pretty-format';
import { ensureArray } from './lib';
import { queries as supportedQueries } from './constants';
// this is not the way I want it to be, but I can't get '@testing-library/dom'
// to build with Parcel. Something with "Incompatible receiver, Map required".
// It works when running parcel in dev mode, but not in build mode. Seems to
// have something to do with a core-js Map polyfill being used?
// It's now loaded from unpkg.com via ./index.html
//import { getQueriesForElement, queries, logDOM } from '@testing-library/dom';
const {
getQueriesForElement,
queries,
getRoles,
logDOM,
} = window.TestingLibraryDom;
const debug = (element, maxLength, options) =>
Array.isArray(element)
? element.map((el) => logDOM(el, maxLength, options)).join('\n')
: logDOM(element, maxLength, options);
function getScreen(root) {
return getQueriesForElement(root, queries, { debug });
}
function scopedEval(context, expr) {
const evaluator = Function.apply(null, [
...Object.keys(context),
'expr',
'return eval(expr)',
]);
return evaluator.apply(null, [...Object.values(context), expr.trim()]);
}
function unQuote(string = '') {
// first and last char of a quoted string should be the same
if (string[0] !== string[string.length - 1]) {
return string;
}
// I only know of 3 valid quote chars, ` ' and "
if (string[0] === `'` || string[0] === `"` || string[0] === '`') {
return string.substr(1, string.length - 2);
}
// return as is, if string wasn't (properly) quoted
return string;
}
function getLastExpression(code) {
const minified = (code || '')
// remove comments
.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')
// remove all white space outside quotes
.replace(/[ ]+(?=[^"'`]*(?:["'`][^"'`]*["'`][^"'`]*)*$)/g, '');
const start = supportedQueries.reduce(
(idx, qry) => Math.max(idx, minified.lastIndexOf(qry.method)),
-1,
);
if (start === -1) {
return '';
}
const end = minified.indexOf(')', start);
const call = minified
.substr(start, end - start + 1)
// add back some spaces that were removed previously
.replace(/{/g, '{ ')
.replace(/}/g, ' }')
.replace(/:/g, ': ')
.replace(/,/g, ', ');
// call now holds something like getByRole('button'), but we also want to know
// if the user was calling the method on screen, view, or container
const leading = minified.substr(0, start);
let scope = leading.match(/([a-zA-Z_$][0-9a-zA-Z_$]+)\.$/)?.[1] || '';
// and let's break up the various parts (['getByRole', 'button', '{ name: `input` }'])
const [method, ...args] = call
.split(/[(),]/)
.filter(Boolean)
.map((x) => unQuote(x.trim()));
const expression = [scope, call].filter(Boolean).join('.');
const level = supportedQueries.find((x) => x.method === method)?.level ?? 3;
return {
expression,
scope,
method,
level,
args,
call,
};
}
let id = 0;
function parse({ htmlRoot, js }) {
let result = {
// increment the id every time we call parse, so we can use
// it for react keys, when iterating over targets
id: ++id,
};
try {
const context = Object.assign({}, queries, {
screen: getScreen(htmlRoot),
container: htmlRoot,
});
result.code = scopedEval(context, js);
} catch (e) {
result.error = e.message.split('\n')[0];
result.errorBody = e.message.split('\n').slice(1).join('\n').trim();
}
result.targets = ensureArray(result.code).filter(
(x) => x?.nodeType === Node.ELEMENT_NODE,
);
result.target = result.targets[0];
result.expression = getLastExpression(js);
result.text = prettyFormat(result.code, {
plugins: [
prettyFormat.plugins.DOMElement,
prettyFormat.plugins.DOMCollection,
],
});
result.roles = getRoles(htmlRoot);
return result;
}
export default {
parse,
};