test_font_feature_values_parsing.html (17262B)
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/css-fonts-4/#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 3a }"), serializationNoValueDefn: true }, 73 { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true }, 74 { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), 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: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") }, 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: _("@styleset { ok-1: 1; }"), serializationSame: true }, 82 { rule: _("@annotation { ok-1: 3; }"), serializationSame: true }, 83 { rule: _("@stylistic { blah: 3; }"), serializationSame: true }, 84 { rule: _("@stylistic { blah: sibling-index(); }"), serializationNoValueDefn: 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 var i; 339 for (i = 0; i < testrules.length; i++) { 340 var testrule = testrules[i]; 341 var rule; 342 343 if ("rulesrc" in testrule) { 344 rule = _.apply(this, testrule.rulesrc); 345 } else { 346 rule = testrule.rule; 347 } 348 349 testOneRule(testrule); 350 //test(function() { testOneRule(testrule); }, "parsing " + rule); 351 } 352 } 353 354 testFontFeatureValuesRuleParsing(); 355 </script> 356 </body></html>