⎇ 9e53f03adea2b770d68954e404d2fa407dfc7d40
scripts/gentest/test_helper.js
History
Who touched what
raw
Implement "gap" property for flexbox algorithm (#248)
1
2class TrackSizingParser {
3 static INITIAL_CHAR_REGEX = /[a-z-A-Z0-9]/;
4 static TOKEN_CHAR_REGEX = /[-\.a-z-A-Z0-9%]/;
5
6 constructor(input, options = { allowFrUnits: true }) {
7 this.input = input;
8 this.index = 0;
9 this.options = options;
10 }
11
12 parseList() {
13 return this._parseItemList(' ', null);
14 }
15
16 parseSingleItem() {
17 return this.parseItem();
18 }
19
20 _parseItemList(separator, terminator = null) {
21 if (!separator) throw new Error('No terminator passed');
22 let tokenList = [];
23 // console.debug('Parse List', this.index, this.input.slice(this.index));
24
25 while (this.index < this.input.length) {
26 const char = this.input[this.index];
27 // console.debug(this.index, char);
28
29 // Skip whitespace
30 if (char === ' ') { this.index++; continue;}
31
32 if (TrackSizingParser.INITIAL_CHAR_REGEX.test(char)) {
33 const token = this._parseItem();
34 tokenList.push(token);
35
36 const nextChar = this.input[this.index];
37 if ((terminator && nextChar === terminator) || !terminator && !nextChar) {
38 return tokenList;
39 } else {
40 this.index++;
41 continue;
42 }
43 }
44
45 throw new Error (`Invalid start of token ${char}`);
46 }
47 }
48
49 _parseItem() {
50 let token = '';
51 // console.debug('Parse Item', this.index, this.input.slice(this.index));
52
53 while (this.index < this.input.length) {
54 const char = this.input[this.index];
55 // console.debug(this.index, char);
56
57 if (TrackSizingParser.TOKEN_CHAR_REGEX.test(char)) {
58 token += char;
59 this.index++;
60 continue;
61 }
62
63 if (char === '(') {
64 this.index++;
65 const args = this._parseItemList(',', ')');
66 this.index++;
67 return { kind: 'function', name: token, arguments: args };
68 }
69
70 return { kind: 'scalar', ...this._parseScalarItem(token) };
71 }
72 return { kind: 'scalar', ...this._parseScalarItem(token) };
73
74 }
75
76 _parseScalarItem(item) {
77 const res = parseDimension(item, { allowFrUnits: this.options.allowFrUnits });
78 if (!res) throw new Error(`Invalid scalar grid track sizing function ${item}`);
79 return res;
80 }
81
82}
83
84function parseDimension(input, options = { allowFrUnits: false }) {
85 if (options.allowFrUnits && input.endsWith('fr')) return { unit: 'fraction', value: parseFloat(input.replace('fr','')) };
86 if (input.endsWith('px')) return { unit: 'points', value: parseFloat(input.replace('px','')) };
87 if (input.endsWith('%')) return { unit: 'percent', value: parseFloat(input.replace('%','')) / 100 };
88 if (input === 'auto') return { unit: 'auto' };
89 if (input === 'min-content') return { unit: 'min-content' };
90 if (input === 'max-content') return { unit: 'max-content' };
91 return undefined;
92}
93
94function parseNumber(input) {
95 if (input === '' || isNaN(input)) return undefined;
96 return Number(input);
97}
98
99function parseEnum(input) {
100 if (input) return input;
101 return undefined;
102}
103
104function parseEdges(edges) {
105 const start = parseDimension(edges.start);
106 const end = parseDimension(edges.end);
107 const top = parseDimension(edges.top);
108 const bottom = parseDimension(edges.bottom);
109
110 if (!start && !end && !top && !bottom) return undefined;
111 return { start, end, top, bottom };
112}
113
114function parseSize(size) {
115 const width = parseDimension(size.width);
116 const height = parseDimension(size.height);
117
118 if (!width && !height) return undefined;
119 return { width, height };
120}
121
122function parseGaps(style) {
123 if (style.gap) {
124 const gaps = style.gap.trim().split(/\s+/).map(part => parseDimension(part));
125 return { row: gaps[0], column: gaps[1] ?? gaps[0] };
126 }
127 if (style.rowGap || style.columnGap) {
128 return { row: parseDimension(style.rowGap), column: parseDimension(style.columnGap) }
129 }
130 return undefined;
131}
132
133
134function parseGridTrackDefinitions(input) {
135 if (input === '') return undefined;
136 return new TrackSizingParser(input).parseList();
137}
138
139function parseGridAutoFlow(input) {
140 if (!/column/.test(input) && !/row/.test(input) && !/dense/.test(input)) return undefined;
141 const direction = /column/.test(input) ? 'column' : 'row';
142 const algorithm = /dense/.test(input) ? 'dense' : 'sparse';
143 return { direction, algorithm };
144}
145
146function parseGridPosition(input) {
147 if (input === 'auto') return { kind: 'auto' }
148 if (/^span +\d+$/.test(input)) return { kind: 'span', value: parseInt(input.replace(/[^\d]/g, ''), 10)}
149 if (/^-?\d+$/.test(input)) return { kind: 'track', value: parseInt(input, 10)}
150 return undefined;
151}
152
153function describeElement(e) {
154 return {
155 style: {
156 display: parseEnum(e.style.display),
157
158 positionType: parseEnum(e.style.position),
159 direction: parseEnum(e.style.direction),
160 flexDirection: parseEnum(e.style.flexDirection),
161
162 flexWrap: parseEnum(e.style.flexWrap),
163 overflow: parseEnum(e.style.overflow),
164
165 alignItems: parseEnum(e.style.alignItems),
166 alignSelf: parseEnum(e.style.alignSelf),
167 alignContent: parseEnum(e.style.alignContent),
168
169 justifyContent: parseEnum(e.style.justifyContent),
170
171 flexGrow: parseNumber(e.style.flexGrow),
172 flexShrink: parseNumber(e.style.flexShrink),
173 flexBasis: parseDimension(e.style.flexBasis),
174
175 gridTemplateRows: parseGridTrackDefinitions(e.style.gridTemplateRows),
176 gridTemplateColumns: parseGridTrackDefinitions(e.style.gridTemplateColumns),
177 gridAutoRows: parseGridTrackDefinitions(e.style.gridAutoRows),
178 gridAutoColumns: parseGridTrackDefinitions(e.style.gridAutoColumns),
179 gridAutoFlow: parseGridAutoFlow(e.style.gridAutoFlow),
180
181 gridRowStart: parseGridPosition(e.style.gridRowStart),
182 gridRowEnd: parseGridPosition(e.style.gridRowEnd),
183 gridColumnStart: parseGridPosition(e.style.gridColumnStart),
184 gridColumnEnd: parseGridPosition(e.style.gridColumnEnd),
185
186 gap: parseGaps(e.style),
187
188 size: parseSize({width: e.style.width, height: e.style.height}),
189 minSize: parseSize({width: e.style.minWidth, height: e.style.minHeight}),
190 maxSize: parseSize({width: e.style.maxWidth, height: e.style.maxHeight}),
191
192 margin: parseEdges({
193 start: e.style.marginLeft,
194 end: e.style.marginRight,
195 top: e.style.marginTop,
196 bottom: e.style.marginBottom,
197 }),
198
199 padding: parseEdges({
200 start: e.style.paddingLeft,
201 end: e.style.paddingRight,
202 top: e.style.paddingTop,
203 bottom: e.style.paddingBottom,
204 }),
205
206 border: parseEdges({
207 start: e.style.borderLeftWidth,
208 end: e.style.borderRightWidth,
209 top: e.style.borderTopWidth,
210 bottom: e.style.borderBottomWidth,
211 }),
212
213 position: parseEdges({
214 start: e.style.left,
215 end: e.style.right,
216 top: e.style.top,
217 bottom: e.style.bottom,
218 }),
219 },
220
221 layout: {
222 width: e.offsetWidth,
223 height: e.offsetHeight,
224 x: e.offsetLeft + e.parentNode.clientLeft,
225 y: e.offsetTop + e.parentNode.clientTop,
226 },
227
228 children: Array.from(e.children).map(c => describeElement(c)),
229 }
230}
231
232// Useful when developing this script. Logs the parsed style to the console when any test fixture is opened in a browser.
233window.onload = function () {
234 try {
235 console.log(describeElement(document.getElementById('test-root')));
236 } catch (e) {
237 console.error(e);
238 }
239}