UNPKG

10.4 kBJavaScriptView Raw
1"use strict";
2
3var Grammar = require("../lib/grammar");
4var assert = require("assert");
5var Testutils = require("eyeglass-dev-testutils");
6var testutils = new Testutils({
7 engines: {
8 sass: require("node-sass"),
9 eyeglass: require("eyeglass")
10 }
11});
12
13var defaultKnownTypes = ["button", "close-button", "dialog", "container", "window"];
14
15var nestedContextStack = [
16 [
17 new Map([
18 ["description", ["small"]],
19 ["type", "window"]
20 ]),
21 new Map([
22 ["description", ["large"]],
23 ["type", "window"]
24 ])
25 ],
26 [
27 new Map([
28 ["type", "dialog"]
29 ])
30 ]
31];
32
33var defaultAliases = new Map();
34defaultAliases.set("alias1", "button");
35defaultAliases.set("alias2", ["small", "button"]);
36
37var defaultGrammarEngines = [
38 function splitOnDots(Grammar) {
39 if (this.description) {
40 this.description = this.description.join(Grammar.WORD_DELIM).replace(/\.+/g, Grammar.WORD_DELIM).split(Grammar.WORD_DELIM);
41 }
42 },
43
44 function btnsAreButtons(Grammars, allowedTypes) {
45 if (!this.type && this.description && allowedTypes.indexOf("button") !== -1) {
46 this.description = this.description.filter(function(word) {
47 if (word === "btn") {
48 this.type = "button";
49 return false;
50 }
51 return true;
52 }.bind(this));
53 }
54 }
55];
56
57var ERRORS = {
58 noType: /A type could not be found in the description .*\. Please specify one of the registered types: .*/,
59 ambiguous: /The description .* is incomplete and cannot be understood\. Ambiguous word .* found but no type was found\./,
60 unused: /The description .* could not be understood\. The following words were found without being bound to a type: .*/
61};
62
63var testData = [
64 {
65 name: "single type",
66 data: {
67 description: "button"
68 },
69 expectedGrammar: {
70 description: null,
71 type: "button"
72 }
73 },
74 {
75 name: "description only (Array)",
76 data: {
77 description: ["a", "large", "primary", "button"]
78 },
79 expectedGrammar: {
80 description: ["large", "primary"],
81 type: "button"
82 }
83 },
84 {
85 name: "description only (String)",
86 data: {
87 description: "a large primary button"
88 },
89 expectedGrammar: {
90 description: ["large", "primary"],
91 type: "button"
92 }
93 },
94 {
95 name: "description only (String)",
96 data: {
97 description: "a large primary button"
98 },
99 expectedGrammar: {
100 description: ["large", "primary"],
101 type: "button"
102 }
103 },
104 {
105 name: "description + type",
106 data: {
107 description: "something else",
108 type: "my-type"
109 },
110 expectedGrammar: {
111 description: ["something", "else"],
112 type: "my-type"
113 }
114 },
115 {
116 name: "empty description",
117 data: {
118 description: [],
119 type: "my-type"
120 },
121 expectedGrammar: {
122 description: null,
123 type: "my-type"
124 }
125 },
126 {
127 name: "description only with a context",
128 data: {
129 description: "a large primary button in a dialog"
130 },
131 expectedGrammar: {
132 description: ["large", "primary", "in-dialog"],
133 type: "button"
134 }
135 },
136 {
137 name: "description with a context (in)",
138 data: {
139 description: "a large primary button in a dialog"
140 },
141 expectedGrammar: {
142 description: ["large", "primary", "in-dialog"],
143 type: "button"
144 }
145 },
146 {
147 name: "description with a context (within)",
148 data: {
149 description: "a large primary button within a dialog"
150 },
151 expectedGrammar: {
152 description: ["large", "primary", "within-dialog"],
153 type: "button"
154 }
155 },
156 {
157 name: "description with multiple context",
158 data: {
159 description: "a large primary button in a container within a dialog"
160 },
161 expectedGrammar: {
162 description: ["large", "primary", "in-container", "within-dialog"],
163 type: "button"
164 }
165 },
166 {
167 name: "description with direct descendant",
168 data: {
169 description: "dialog > a large primary button"
170 },
171 expectedGrammar: {
172 description: ["large", "primary", "in-dialog"],
173 type: "button"
174 }
175 },
176 {
177 name: "description with direct descendant",
178 data: {
179 description: "dialog > a large primary button"
180 },
181 expectedGrammar: {
182 description: ["large", "primary", "in-dialog"],
183 type: "button"
184 }
185 },
186 {
187 name: "simple `on` in description",
188 data: {
189 description: "leaf button on a dialog"
190 },
191 expectedGrammar: {
192 description: ["leaf", "on-dialog"],
193 type: "button"
194 }
195 },
196 {
197 name: "multiple contexts (in with)",
198 data: {
199 description: "close-button with a shadow in a modeless dialog with a header"
200 },
201 expectedGrammar: {
202 description: ["with-shadow", "in-dialog-modeless", "in-dialog", "in-dialog-with-header"],
203 type: "close-button"
204 }
205 },
206 {
207 name: "multiple contexts (in in)",
208 data: {
209 description: "close-button in a dialog in a window"
210 },
211 expectedGrammar: {
212 description: ["in-dialog", "in-dialog-in-window"],
213 type: "close-button"
214 }
215 },
216 {
217 name: "multiple contexts (in with within with)",
218 data: {
219 description: "button in a dialog with a shadow within a window with a box"
220 },
221 expectedGrammar: {
222 description: ["in-dialog", "in-dialog-with-shadow", "within-window", "within-window-with-box"],
223 type: "button"
224 }
225 },
226 {
227 name: "multiple contexts (in with and in)",
228 data: {
229 description: "button in a dialog with a shadow and a box in a window"
230 },
231 expectedGrammar: {
232 description: ["in-dialog", "in-dialog-with-shadow", "in-dialog-with-box", "in-dialog-in-window"],
233 type: "button"
234 }
235 },
236 {
237 name: "remove duplicate words",
238 data: {
239 description: "small small small primary button"
240 },
241 expectedGrammar: {
242 description: ["small", "primary"],
243 type: "button"
244 }
245 },
246 {
247 name: "invalid description without a recognized type",
248 data: {
249 description: "something else"
250 },
251 expectedError: ERRORS.noType
252 },
253 {
254 name: "invalid description with ambiguous words",
255 data: {
256 description: "something else in a window"
257 },
258 expectedError: ERRORS.ambiguous
259 },
260 {
261 name: "invalid description with unused words",
262 data: {
263 description: "button in a car in a window"
264 },
265 expectedError: ERRORS.unused
266 },
267 {
268 name: "simple alias (as description)",
269 data: {
270 description: "alias1"
271 },
272 expectedGrammar: {
273 description: null,
274 type: "button"
275 }
276 },
277 {
278 name: "alias without aliases",
279 data: {
280 description: "alias1",
281 aliases: []
282 },
283 expectedError: ERRORS.noType
284 },
285 {
286 name: "simple alias (as type)",
287 data: {
288 type: "alias1"
289 },
290 expectedGrammar: {
291 description: null,
292 type: "button"
293 }
294 },
295 {
296 name: "alias with modifier",
297 data: {
298 description: "small alias1"
299 },
300 expectedGrammar: {
301 description: ["small"],
302 type: "button"
303 }
304 },
305 {
306 name: "complex alias with modifier",
307 data: {
308 description: "super alias2"
309 },
310 expectedGrammar: {
311 description: ["super", "small"],
312 type: "button"
313 }
314 },
315 {
316 name: "custom grammar engine",
317 data: {
318 description: "this will test.a.custom.engine button"
319 },
320 expectedGrammar: {
321 description: ["will", "test", "custom", "engine"],
322 type: "button"
323 }
324 },
325 {
326 name: "custom grammar engine 2",
327 data: {
328 description: "custom btn"
329 },
330 expectedGrammar: {
331 description: ["custom"],
332 type: "button"
333 }
334 },
335 {
336 name: "no custom grammar engine",
337 data: {
338 description: "custom btn",
339 grammarEngines: []
340 },
341 expectedError: ERRORS.noType
342 },
343 {
344 name: "without knownTypes arg",
345 data: {
346 description: "a button",
347 knownTypes: null
348 },
349 expectedError: ERRORS.noType
350 },
351 {
352 name: "nested context",
353 data: {
354 type: "button",
355 contextStack: nestedContextStack
356 },
357 expectedGrammar: {
358 description: ["within-window-small", "within-window", "within-window-large", "within-dialog"],
359 type: "button"
360 }
361 },
362 {
363 name: "nested context",
364 data: {
365 type: "button",
366 description: ["small"],
367 contextStack: nestedContextStack
368 },
369 expectedGrammar: {
370 description: ["small", "within-window-small", "within-window", "within-window-large", "within-dialog"],
371 type: "button"
372 }
373 }
374];
375
376
377describe("grammar", function() {
378 function testGrammar(test) {
379 return new Grammar(
380 test.data.description,
381 test.data.type,
382 test.data.knownTypes === undefined ? defaultKnownTypes : test.data.knownTypes,
383 test.data.aliases === undefined ? defaultAliases : test.data.aliases,
384 test.data.contextStack || [],
385 test.data.grammarEngines === undefined ? defaultGrammarEngines : test.data.grammarEngines
386 );
387 }
388
389 testData.forEach(function(test) {
390 var testGrammarFn = testGrammar.bind(testGrammar, test);
391
392 it(test.name, function() {
393 if (test.expectedError) {
394 assert.throws(testGrammarFn, test.expectedError, "should throw an error");
395 }
396 else {
397 assert.deepEqual(testGrammarFn(), test.expectedGrammar, "the grammar should match");
398 }
399 });
400 });
401});
402
403describe("adding custom grammar engine", function() {
404 var data = "@import 'restyle'; @include restyle-define(test); /* #{inspect(-restyle--grammar(simple test))} */";
405 var expectedCSS = "/* (description: (custom,), type: test) */";
406
407 function customGrammarEngine() {
408 this.description = ["custom"];
409 }
410
411 it("should allow a custom engine via options", function(done) {
412 var options = {
413 data: data,
414 restyle: {
415 grammarEngines: [customGrammarEngine]
416 }
417 };
418 testutils.assertCompiles(options, expectedCSS, done);
419 });
420
421 it("should allow a custom engine via #addGrammarEngine", function(done) {
422 // create a new instance of eyeglass
423 var Eyeglass = testutils.engines.eyeglass;
424 var eyeglass = new Eyeglass({
425 data: data
426 });
427 // find the restyle module
428 var restyle = eyeglass.modules.find("restyle");
429 // invoke the addGrammarEngine
430 restyle.addGrammarEngine(customGrammarEngine);
431
432 testutils.assertCompiles(eyeglass.options, expectedCSS, done);
433 });
434});