|
1 <!DOCTYPE HTML> |
|
2 <html> |
|
3 <head> |
|
4 <meta charset=utf-8> |
|
5 <title>@font-feature-values rule parsing tests</title> |
|
6 <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"> |
|
7 <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-feature-values" /> |
|
8 <meta name="assert" content="tests that valid @font-feature-values rules parse and invalid ones don't" /> |
|
9 <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=549861 --> |
|
10 <script type="text/javascript" src="/resources/testharness.js"></script> |
|
11 <script type="text/javascript" src="/resources/testharnessreport.js"></script> |
|
12 <style type="text/css"> |
|
13 </style> |
|
14 </head> |
|
15 <body> |
|
16 <div id="log"></div> |
|
17 <pre id="display"></pre> |
|
18 <style type="text/css" id="testbox"></style> |
|
19 |
|
20 <script type="text/javascript"> |
|
21 var gPrefix = ""; |
|
22 var kFontFeatureValuesRuleType = 14; |
|
23 |
|
24 function ruleName() { return "@" + gPrefix + "font-feature-values"; } |
|
25 function makeRule(f, v) { |
|
26 return ruleName() + " " + f + " { " + v + " }"; |
|
27 } |
|
28 |
|
29 function _() |
|
30 { |
|
31 var i, decl = []; |
|
32 for (i = 0; i < arguments.length; i++) { |
|
33 decl.push(arguments[i]); |
|
34 } |
|
35 return makeRule("bongo", decl.join(" ")); |
|
36 } |
|
37 |
|
38 // note: because of bugs in the way family names are serialized, |
|
39 // 'serializationSame' only implies that the value definition block |
|
40 // is the same (i.e. not including the family name list) |
|
41 |
|
42 var testrules = [ |
|
43 |
|
44 /* basic syntax */ |
|
45 { rule: ruleName() + ";", invalid: true }, |
|
46 { rule: ruleName() + " bongo;", invalid: true }, |
|
47 { rule: ruleName().replace("values", "value") + " {;}", invalid: true }, |
|
48 { rule: ruleName().replace("feature", "features") + " {;}", invalid: true }, |
|
49 { rule: makeRule("bongo", ""), serializationNoValueDefn: true }, |
|
50 { rule: makeRule("bongo", ";"), serializationNoValueDefn: true }, |
|
51 { rule: makeRule("bongo", ",;"), serializationNoValueDefn: true }, |
|
52 { rule: makeRule("bongo", ";,"), serializationNoValueDefn: true }, |
|
53 { rule: makeRule("bongo", ",;,"), serializationNoValueDefn: true }, |
|
54 { rule: makeRule("bongo", "@styleset;"), serializationNoValueDefn: true }, |
|
55 { rule: makeRule("bongo", "@styleset,;"), serializationNoValueDefn: true }, |
|
56 { rule: makeRule("bongo", "@styleset abc;"), serializationNoValueDefn: true }, |
|
57 { rule: makeRule("bongo", "@styleset { abc }"), serializationNoValueDefn: true }, |
|
58 { rule: makeRule("bongo", "@styleset { ;;abc }"), serializationNoValueDefn: true }, |
|
59 { rule: makeRule("bongo", "@styleset { abc;; }"), serializationNoValueDefn: true }, |
|
60 { rule: makeRule("bongo", "@styleset { abc: }"), serializationNoValueDefn: true }, |
|
61 { rule: makeRule("bongo", "@styleset { abc,: }"), serializationNoValueDefn: true }, |
|
62 { rule: makeRule("bongo", "@styleset { abc:, }"), serializationNoValueDefn: true }, |
|
63 { rule: makeRule("bongo", "@styleset { abc:,; }"), serializationNoValueDefn: true }, |
|
64 { rule: makeRule("bongo", "@styleset { a,b }"), serializationNoValueDefn: true }, |
|
65 { rule: makeRule("bongo", "@styleset { a;b }"), serializationNoValueDefn: true }, |
|
66 { rule: makeRule("bongo", "@styleset { a:;b: }"), serializationNoValueDefn: true }, |
|
67 { rule: makeRule("bongo", "@styleset { a:,;b: }"), serializationNoValueDefn: true }, |
|
68 { rule: makeRule("bongo", "@styleset { a:1,;b: }"), serializationNoValueDefn: true }, |
|
69 { rule: makeRule("bongo", "@styleset { abc 1 2 3 }"), serializationNoValueDefn: true }, |
|
70 { rule: makeRule("bongo", "@styleset { abc:, 1 2 3 }"), serializationNoValueDefn: true }, |
|
71 { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true }, |
|
72 { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true }, |
|
73 { rule: makeRule("bongo", "@styleset { abc: 1 2 3a }"), serializationNoValueDefn: true }, |
|
74 { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true }, |
|
75 { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, |
|
76 { rule: makeRule("bongo", "@blah } @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, |
|
77 { rule: makeRule("bongo", "@blah , @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, |
|
78 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") }, |
|
79 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3 }", serialization: _("@styleset { abc: 1 2 3; }") }, |
|
80 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3;", serialization: _("@styleset { abc: 1 2 3; }") }, |
|
81 { rule: ruleName() + " bongo { @styleset { abc: 1 2 3", serialization: _("@styleset { abc: 1 2 3; }") }, |
|
82 { rule: _("@styleset { ok-1: 1; }"), serializationSame: true }, |
|
83 { rule: _("@annotation { ok-1: 3; }"), serializationSame: true }, |
|
84 { rule: _("@stylistic { blah: 3; }"), serializationSame: true }, |
|
85 { rule: makeRule("bongo", "\n@styleset\n { blah: 3; super-blah: 4 5;\n more-blah: 5 6 7;\n }"), serializationSame: true }, |
|
86 { rule: makeRule("bongo", "\n@styleset\n {\n blah:\n 3\n;\n super-blah:\n 4\n 5\n;\n more-blah:\n 5 6\n 7;\n }"), serializationSame: true }, |
|
87 |
|
88 /* limits on number of values */ |
|
89 { rule: _("@stylistic { blah: 1; }"), serializationSame: true }, |
|
90 { rule: _("@styleset { blah: 1 2 3 4; }"), serializationSame: true }, |
|
91 { rule: _("@character-variant { blah: 1 2; }"), serializationSame: true }, |
|
92 { rule: _("@swash { blah: 1; }"), serializationSame: true }, |
|
93 { rule: _("@ornaments { blah: 1; }"), serializationSame: true }, |
|
94 { rule: _("@annotation { blah: 1; }"), serializationSame: true }, |
|
95 |
|
96 /* values ignored when used */ |
|
97 { rule: _("@styleset { blah: 0; }"), serializationSame: true }, |
|
98 { rule: _("@styleset { blah: 120 124; }"), serializationSame: true }, |
|
99 { rule: _("@character-variant { blah: 0; }"), serializationSame: true }, |
|
100 { rule: _("@character-variant { blah: 111; }"), serializationSame: true }, |
|
101 { rule: _("@character-variant { blah: 111 13; }"), serializationSame: true }, |
|
102 |
|
103 /* invalid value name */ |
|
104 { rulesrc: ["styleset { blah: 1 }"], serializationNoValueDefn: true }, |
|
105 { rulesrc: ["stylistic { blah: 1 }"], serializationNoValueDefn: true }, |
|
106 { rulesrc: ["character-variant { blah: 1 }"], serializationNoValueDefn: true }, |
|
107 { rulesrc: ["swash { blah: 1 }"], serializationNoValueDefn: true }, |
|
108 { rulesrc: ["ornaments { blah: 1 }"], serializationNoValueDefn: true }, |
|
109 { rulesrc: ["annotation { blah: 1 }"], serializationNoValueDefn: true }, |
|
110 { rulesrc: ["@bongo { blah: 1 }"], serializationNoValueDefn: true }, |
|
111 { rulesrc: ["@bongo { blah: 1 2 3 }"], serializationNoValueDefn: true }, |
|
112 { rulesrc: ["@bongo { blah: 1 2 3; burp: 1;;; }"], serializationNoValueDefn: true }, |
|
113 |
|
114 /* values */ |
|
115 { rulesrc: ["@styleset { blah: -1 }"], serializationNoValueDefn: true }, |
|
116 { rulesrc: ["@styleset { blah: 1 -1 }"], serializationNoValueDefn: true }, |
|
117 { rulesrc: ["@styleset { blah: 1.5 }"], serializationNoValueDefn: true }, |
|
118 { rulesrc: ["@styleset { blah: 15px }"], serializationNoValueDefn: true }, |
|
119 { rulesrc: ["@styleset { blah: red }"], serializationNoValueDefn: true }, |
|
120 { rulesrc: ["@styleset { blah: (1) }"], serializationNoValueDefn: true }, |
|
121 { rulesrc: ["@styleset { blah:(1) }"], serializationNoValueDefn: true }, |
|
122 { rulesrc: ["@styleset { blah:, 1 }"], serializationNoValueDefn: true }, |
|
123 { rulesrc: ["@styleset { blah: <1> }"], serializationNoValueDefn: true }, |
|
124 { rulesrc: ["@styleset { blah: 1! }"], serializationNoValueDefn: true }, |
|
125 { rulesrc: ["@styleset { blah: 1,, }"], serializationNoValueDefn: true }, |
|
126 { rulesrc: ["@styleset { blah: 1 1 1 1; }"], serializationSame: true }, |
|
127 |
|
128 /* limits on number of values */ |
|
129 { rulesrc: ["@stylistic { blah: 1 2 }"], serializationNoValueDefn: true }, |
|
130 { rulesrc: ["@character-variant { blah: 1 2 3 }"], serializationNoValueDefn: true }, |
|
131 { rulesrc: ["@swash { blah: 1 2 }"], serializationNoValueDefn: true }, |
|
132 { rulesrc: ["@ornaments { blah: 1 2 }"], serializationNoValueDefn: true }, |
|
133 { rulesrc: ["@annotation { blah: 1 2 }"], serializationNoValueDefn: true }, |
|
134 { rulesrc: ["@styleset { blah: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; }"], serializationSame: true }, |
|
135 |
|
136 /* family names */ |
|
137 { rule: makeRule("bongo", "@styleset { blah: 1; }"), serializationSame: true }, |
|
138 { rule: makeRule("\"bongo\"", "@styleset { blah: 1; }"), serializationSame: true }, |
|
139 { rule: makeRule("'bongo'", "@styleset { blah: 1; }"), serializationSame: true }, |
|
140 { rule: makeRule("\\62 ongo", "@styleset { blah: 1; }"), serializationSame: true }, |
|
141 { rule: makeRule("bongo, super bongo, bongo the supreme", "@styleset { blah: 1; }"), serializationSame: true }, |
|
142 { rule: makeRule("bongo,, super bongo", "@styleset { blah: 1; }"), invalid: true }, |
|
143 { rule: makeRule("bongo,*", "@styleset { blah: 1; }"), invalid: true }, |
|
144 { rule: makeRule("bongo, sans-serif", "@styleset { blah: 1; }"), invalid: true }, |
|
145 { rule: makeRule("serif, sans-serif", "@styleset { blah: 1; }"), invalid: true }, |
|
146 { rule: makeRule("'serif', 'sans-serif'", "@styleset { blah: 1; }"), serializationSame: true }, |
|
147 { rule: makeRule("bongo, \"super bongo\", 'bongo the supreme'", "@styleset { blah: 1; }"), serializationSame: true }, |
|
148 { rule: makeRule("毎日カレーを食べたい!", "@styleset { blah: 1; }"), serializationSame: true }, |
|
149 { rule: makeRule("毎日カレーを食べたい!, 納豆嫌い", "@styleset { blah: 1; }"), serializationSame: true }, |
|
150 |
|
151 { rule: makeRule("bongo, \"super\" bongo, bongo the supreme", "@styleset { blah: 1; }"), invalid: true }, |
|
152 { rule: makeRule("--bongo", "@styleset { blah: 1; }"), invalid: true }, |
|
153 |
|
154 /* ident tests */ |
|
155 { rule: _("@styleset { blah: 1; blah: 1; }"), serializationSame: true }, |
|
156 { rule: _("@styleset { blah: 1; de-blah: 1; blah: 2; }"), serializationSame: true }, |
|
157 { rule: _("@styleset { \\tra-la: 1; }"), serialization: _("@styleset { tra-la: 1; }") }, |
|
158 { rule: _("@styleset { b\\lah: 1; }"), serialization: _("@styleset { blah: 1; }") }, |
|
159 { rule: _("@styleset { \\62 lah: 1; }"), serialization: _("@styleset { blah: 1; }") }, |
|
160 { rule: _("@styleset { \\:blah: 1; }"), serialization: _("@styleset { \\:blah: 1; }") }, |
|
161 { rule: _("@styleset { \\;blah: 1; }"), serialization: _("@styleset { \\;blah: 1; }") }, |
|
162 { rule: _("@styleset { complex\\20 blah: 1; }"), serialization: _("@styleset { complex\\ blah: 1; }") }, |
|
163 { rule: _("@styleset { complex\\ blah: 1; }"), serializationSame: true }, |
|
164 { rule: _("@styleset { Håkon: 1; }"), serializationSame: true }, |
|
165 { rule: _("@styleset { Åквариум: 1; }"), serializationSame: true }, |
|
166 { rule: _("@styleset { \\1f449\\1f4a9\\1f448: 1; }"), serialization: _("@styleset { 👉💩👈: 1; }") }, |
|
167 { rule: _("@styleset { 魅力: 1; }"), serializationSame: true }, |
|
168 { rule: _("@styleset { 毎日カレーを食べたい!: 1; }"), serializationSame: true }, |
|
169 /* from http://en.wikipedia.org/wiki/Metal_umlaut */ |
|
170 { rule: _("@styleset { TECHNICIÄNS\\ ÖF\\ SPÅCE\\ SHIP\\ EÅRTH\\ THIS\\ IS\\ YÖÜR\\ CÄPTÅIN\\ SPEÄKING\\ YÖÜR\\ ØÅPTÅIN\\ IS\\ DEA̋D: 1; }"), serializationSame: true }, |
|
171 |
|
172 { rulesrc: ["@styleset { 123blah: 1; }"], serializationNoValueDefn: true }, |
|
173 { rulesrc: ["@styleset { :123blah 1; }"], serializationNoValueDefn: true }, |
|
174 { rulesrc: ["@styleset { :123blah: 1; }"], serializationNoValueDefn: true }, |
|
175 { rulesrc: ["@styleset { ?123blah: 1; }"], serializationNoValueDefn: true }, |
|
176 { rulesrc: ["@styleset { \"blah\": 1; }"], serializationNoValueDefn: true }, |
|
177 { rulesrc: ["@styleset { complex blah: 1; }"], serializationNoValueDefn: true }, |
|
178 { rulesrc: ["@styleset { complex\\ blah: 1; }"], serializationNoValueDefn: true } |
|
179 |
|
180 ]; |
|
181 |
|
182 // test that invalid value declarations don't affect the parsing of surrounding |
|
183 // declarations. So before + invalid + after should match the serialization |
|
184 // given in s. |
|
185 |
|
186 var gSurroundingTests = [ |
|
187 // -- invalid, valid ==> valid |
|
188 { before: "", after: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }") }, |
|
189 |
|
190 // -- valid, invalid ==> valid |
|
191 { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }", after: "", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }") }, |
|
192 |
|
193 // -- valid, invalid, valid ==> valid, valid |
|
194 { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", after: "@character-variant { whatchamacallit-2: 23 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; } @character-variant { whatchamacallit-2: 23 4; }") }, |
|
195 |
|
196 // -- invalid, valid, invalid ==> valid |
|
197 { between: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }") } |
|
198 ]; |
|
199 |
|
200 /* strip out just values, along with empty value blocks (e.g. @swash { })*/ |
|
201 function valuesText(ruletext) |
|
202 { |
|
203 var t = ruletext.replace(/@[a-zA-Z0-9\-]+[ \n]*{[ \n]*}/g, ""); |
|
204 t = t.replace(/[ \n]+/g, " "); |
|
205 t = t.replace(/^[^{]+{[ \n]*/, ""); |
|
206 t = t.replace(/[ \n]*}[^}]*$/, ""); |
|
207 t = t.replace(/[ \n]*;/g, ";"); |
|
208 return t; |
|
209 } |
|
210 |
|
211 function testParse(rulesrc) |
|
212 { |
|
213 var sheet = document.styleSheets[1]; |
|
214 var rule = _.apply(this, rulesrc); |
|
215 |
|
216 while(sheet.cssRules.length > 0) |
|
217 sheet.deleteRule(0); |
|
218 try { |
|
219 sheet.insertRule(rule, 0); |
|
220 } catch (e) { |
|
221 return e.toString(); |
|
222 } |
|
223 |
|
224 if (sheet.cssRules.length == 1 && sheet.cssRules[0].type == kFontFeatureValuesRuleType) { |
|
225 return sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "); |
|
226 } |
|
227 |
|
228 return ""; |
|
229 } |
|
230 |
|
231 function testOneRule(testrule) { |
|
232 var sheet = document.styleSheets[1]; |
|
233 var rule; |
|
234 |
|
235 if ("rulesrc" in testrule) { |
|
236 rule = _.apply(this, testrule.rulesrc); |
|
237 } else { |
|
238 rule = testrule.rule; |
|
239 } |
|
240 |
|
241 var parseErr = false; |
|
242 var expectedErr = false; |
|
243 var invalid = false; |
|
244 if ("invalid" in testrule && testrule.invalid) invalid = true; |
|
245 |
|
246 while(sheet.cssRules.length > 0) |
|
247 sheet.deleteRule(0); |
|
248 try { |
|
249 sheet.insertRule(rule, 0); |
|
250 } catch (e) { |
|
251 expectedErr = (e.name == "SyntaxError" |
|
252 && e instanceof DOMException |
|
253 && e.code == DOMException.SYNTAX_ERR |
|
254 && invalid); |
|
255 parseErr = true; |
|
256 } |
|
257 |
|
258 test(function() { |
|
259 assert_true(!parseErr || expectedErr, "unexpected syntax error"); |
|
260 if (!parseErr) { |
|
261 assert_equals(sheet.cssRules.length, 1, "bad rule count"); |
|
262 assert_equals(sheet.cssRules[0].type, kFontFeatureValuesRuleType, "bad rule type"); |
|
263 } |
|
264 }, "basic parse tests - " + rule); |
|
265 |
|
266 var sanitizedRule = rule.replace(/[ \n]+/g, " "); |
|
267 if (parseErr) { |
|
268 return; |
|
269 } |
|
270 |
|
271 // should result in one @font-feature-values rule constructed |
|
272 |
|
273 // serialization matches expectation |
|
274 // -- note: due to inconsistent font family serialization problems, |
|
275 // only the serialization of the values is tested currently |
|
276 |
|
277 var ruleValues = valuesText(rule); |
|
278 var serialized = sheet.cssRules[0].cssText; |
|
279 var serializedValues = valuesText(serialized); |
|
280 var haveSerialization = true; |
|
281 |
|
282 if (testrule.serializationSame) { |
|
283 test(function() { |
|
284 assert_equals(serializedValues, ruleValues, "canonical cssText serialization doesn't match"); |
|
285 }, "serialization check - " + rule); |
|
286 } else if ("serialization" in testrule) { |
|
287 var s = valuesText(testrule.serialization); |
|
288 test(function() { |
|
289 assert_equals(serializedValues, s, "non-canonical cssText serialization doesn't match - "); |
|
290 }, "serialization check - " + rule); |
|
291 } else if (testrule.serializationNoValueDefn) { |
|
292 test(function() { |
|
293 assert_equals(serializedValues, "", "cssText serialization should have no value defintions - "); |
|
294 }, "no value definitions in serialization - " + rule); |
|
295 |
|
296 haveSerialization = false; |
|
297 |
|
298 if ("rulesrc" in testrule) { |
|
299 test(function() { |
|
300 var j, rulesrc = testrule.rulesrc; |
|
301 |
|
302 // invalid value definitions shouldn't affect the parsing of valid |
|
303 // definitions before or after an invalid one |
|
304 for (var j = 0; j < gSurroundingTests.length; j++) { |
|
305 var t = gSurroundingTests[j]; |
|
306 var srulesrc = []; |
|
307 |
|
308 if ("between" in t) { |
|
309 srulesrc = srulesrc.concat(rulesrc); |
|
310 srulesrc = srulesrc.concat(t.between); |
|
311 srulesrc = srulesrc.concat(rulesrc); |
|
312 } else { |
|
313 if (t.before != "") |
|
314 srulesrc = srulesrc.concat(t.before); |
|
315 srulesrc = srulesrc.concat(rulesrc); |
|
316 if (t.after != "") |
|
317 srulesrc = srulesrc.concat(t.after); |
|
318 } |
|
319 |
|
320 var result = testParse(srulesrc); |
|
321 assert_equals(valuesText(result), valuesText(t.s), "invalid declarations should not affect valid ones - "); |
|
322 } |
|
323 }, "invalid declarations don't affect valid ones - " + rule); |
|
324 } |
|
325 } |
|
326 |
|
327 // if serialization non-empty, serialization should round-trip to itself |
|
328 if (haveSerialization) { |
|
329 var roundTripText = testParse([serializedValues]); |
|
330 test(function() { |
|
331 assert_equals(valuesText(roundTripText), serializedValues, |
|
332 "serialization should round-trip to itself - "); |
|
333 }, "serialization round-trip - " + rule); |
|
334 } |
|
335 } |
|
336 |
|
337 function testFontFeatureValuesRuleParsing() { |
|
338 // Gecko-specific check - if pref not set, skip these tests |
|
339 if (window.SpecialPowers && !window.SpecialPowers.getBoolPref("layout.css.font-features.enabled")) { |
|
340 return; |
|
341 } |
|
342 var i; |
|
343 for (i = 0; i < testrules.length; i++) { |
|
344 var testrule = testrules[i]; |
|
345 var rule; |
|
346 |
|
347 if ("rulesrc" in testrule) { |
|
348 rule = _.apply(this, testrule.rulesrc); |
|
349 } else { |
|
350 rule = testrule.rule; |
|
351 } |
|
352 |
|
353 testOneRule(testrule); |
|
354 //test(function() { testOneRule(testrule); }, "parsing " + rule); |
|
355 } |
|
356 } |
|
357 |
|
358 testFontFeatureValuesRuleParsing(); |
|
359 </script> |
|
360 </body></html> |