summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom/nodes/Element-classlist.html
blob: 22b499e931f14fee784016612da46615f75fd815 (plain)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
<!doctype html>
<html>
  <head class="test test">
    <title class=" ">Element.classList in case-sensitive documents</title>
    <link rel="help" href="https://dom.spec.whatwg.org/#concept-class">
    <script type="text/javascript" src="/resources/testharness.js"></script>
    <script type="text/javascript" src="/resources/testharnessreport.js"></script>
    <style type="text/css">
.foo { font-style: italic; }
    </style>
    <script type="text/javascript">
var elem = document.getElementsByTagName('title')[0], secondelem = document.getElementsByTagName('head')[0];
test(function () {
  assert_equals( typeof elem.classList, 'object', 'critical test; ignore any results after this' );
}, 'Element.classList must exist as an object');
test(function () {
  assert_equals( typeof document.documentElement.classList, 'object' );
}, 'Element.classList must exist as an object even if the element has no class attribute');
test(function () {
  assert_true( !!window.DOMTokenList );
}, 'DOMTokenList should be exposed for prototyping');
test(function () {
  DOMTokenList.prototype.customProperty = true;
  assert_true( elem.classList.customProperty );
}, 'prototyping DOMTokenList should work');
test(function () {
  assert_true( elem.classList instanceof window.DOMTokenList );
  assert_equals( elem.classList.constructor, window.DOMTokenList );
}, 'Element.classList must implement DOMTokenList');
test(function () {
  assert_not_equals( getComputedStyle(elem,null).fontStyle, 'italic', 'critical test; required by the testsuite' );
}, 'CSS .foo selectors must not match elements without any class');
test(function () {
  assert_equals( secondelem.classList.length, 1, 'duplicates in initial string should be removed per https://dom.spec.whatwg.org/#concept-class' );
  assert_equals( secondelem.classList.item(0), 'test' );
  assert_true( secondelem.classList.contains('test') );
}, 'classList must be correct for an element that has classes');
test(function () {
  assert_equals( elem.classList.length, 0 );
}, 'classList.length must be 0 for an element that has no classes');
test(function () {
  assert_false( elem.classList.contains('foo') );
}, 'classList must not contain an undefined class');
test(function () {
  assert_equals( elem.classList.item(0), null );
}, 'classList.item() must return null for out-of-range index');
test(function () {
  assert_equals( elem.classList.item(-1), null );
}, 'classList.item() must return null for negative index');
test(function () {
  /* the normative part of the spec states that:
  "unless tokens is empty, in which case there are no supported property indices"
  ...
  "The term[...] supported property indices [is] used as defined in the WebIDL specification."
  WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
  assert_equals( elem.classList[0], undefined );
}, 'classList[index] must be undefined for out-of-range index');
test(function () {
  assert_equals( elem.classList[-1], undefined );
}, 'classList[index] must be undefined for negative index');
test(function () {
  assert_equals( elem.className, ' ' );
}, 'className should contain initial markup whitespace');
test(function () {
  assert_equals( elem.classList + '', ' ', 'implicit' );
  assert_equals( elem.classList.toString(), ' ', 'explicit' );
}, 'classList should contain initial markup whitespace');
test(function () {
  assert_false( elem.classList.contains('') );
}, '.contains(empty_string) must return false');
test(function () {
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.add(''); } );
}, '.add(empty_string) must throw a SYNTAX_ERR');
test(function () {
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.remove(''); } );
}, '.remove(empty_string) must throw a SYNTAX_ERR');
test(function () {
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.toggle(''); } );
}, '.toggle(empty_string) must throw a SYNTAX_ERR');
test(function () {
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.replace('', 'foo'); } );
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.replace('foo', ''); } );
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.replace('', 'foo bar'); } );
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.replace('foo bar', ''); } );
  assert_throws( 'SYNTAX_ERR', function () { elem.classList.replace('', ''); } );
}, '.replace with empty_string must throw a SYNTAX_ERR');
test(function () {
  assert_false( elem.classList.contains('a b') );
}, '.contains(string_with_spaces) must return false');
test(function () {
  assert_throws( 'INVALID_CHARACTER_ERR', function () { elem.classList.add('a b'); } );
}, '.add(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
  assert_throws( 'INVALID_CHARACTER_ERR', function () { elem.classList.remove('a b'); } );
}, '.remove(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
  assert_throws( 'INVALID_CHARACTER_ERR', function () { elem.classList.toggle('a b'); } );
}, '.toggle(string_with_spaces) must throw an INVALID_CHARACTER_ERR');
test(function () {
  assert_throws( 'INVALID_CHARACTER_ERR', function () { elem.classList.replace('z', 'a b'); } );
  assert_throws( 'INVALID_CHARACTER_ERR', function () { elem.classList.replace('a b', 'z'); } );
  assert_throws( 'INVALID_CHARACTER_ERR', function () { elem.classList.replace('a b', 'b c'); } );
}, '.replace with string_with_spaces must throw a INVALID_CHARACTER_ERR');
test(function () {
  var foo = document.createElement('div');
  foo.className = 'token1 token2 token3'
  foo.classList.replace('token1', 'token3');
  assert_equals( foo.classList.length, 2 );
  assert_false( foo.classList.contains('token1') );
  assert_true( foo.classList.contains('token2') );
  assert_true( foo.classList.contains('token3') );
}, '.replace with an already existing token')
elem.className = 'foo';
test(function () {
  assert_equals( getComputedStyle(elem,null).fontStyle, 'italic', 'critical test; required by the testsuite' );
}, 'computed style must update when setting .className');
test(function () {
  assert_true( elem.classList.contains('foo') );
}, 'classList.contains must update when .className is changed');
test(function () {
  assert_false( elem.classList.contains('FOO') );
}, 'classList.contains must be case sensitive');
test(function () {
  assert_false( elem.classList.contains('foo.') );
  assert_false( elem.classList.contains('foo)') );
  assert_false( elem.classList.contains('foo\'') );
  assert_false( elem.classList.contains('foo$') );
  assert_false( elem.classList.contains('foo~') );
  assert_false( elem.classList.contains('foo?') );
  assert_false( elem.classList.contains('foo\\') );
}, 'classList.contains must not match when punctuation characters are added');
test(function () {
  elem.classList.add('FOO');
  assert_equals( getComputedStyle(elem,null).fontStyle, 'italic' );
}, 'classList.add must not cause the CSS selector to stop matching');
test(function () {
  assert_true( elem.classList.contains('foo') );
}, 'classList.add must not remove existing classes');
test(function () {
  assert_true( elem.classList.contains('FOO') );
}, 'classList.contains case sensitivity must match a case-specific string');
test(function () {
  assert_equals( elem.classList.length, 2 );
}, 'classList.length must correctly reflect the number of tokens');
test(function () {
  assert_equals( elem.classList.item(0), 'foo' );
}, 'classList.item(0) must return the first token');
test(function () {
  assert_equals( elem.classList.item(1), 'FOO' );
}, 'classList.item must return case-sensitive strings and preserve token order');
test(function () {
  assert_equals( elem.classList[0], 'foo' );
}, 'classList[0] must return the first token');
test(function () {
  assert_equals( elem.classList[1], 'FOO' );
}, 'classList[index] must return case-sensitive strings and preserve token order');
test(function () {
  /* the normative part of the spec states that:
  "The object's supported property indices are the numbers in the range zero to the number of tokens in tokens minus one"
  ...
  "The term[...] supported property indices [is] used as defined in the WebIDL specification."
  WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
  assert_equals( elem.classList[2], undefined );
}, 'classList[index] must still be undefined for out-of-range index when earlier indexes exist');
test(function () {
  assert_equals( elem.className, 'foo FOO' );
}, 'className must update correctly when items have been added through classList');
test(function () {
  assert_equals( elem.classList + '', 'foo FOO', 'implicit' );
  assert_equals( elem.classList.toString(), 'foo FOO', 'explicit' );
}, 'classList must stringify correctly when items have been added');
test(function () {
  elem.classList.add('foo');
  assert_equals( elem.classList.length, 2 );
  assert_equals( elem.classList + '', 'foo FOO', 'implicit' );
  assert_equals( elem.classList.toString(), 'foo FOO', 'explicit' );
}, 'classList.add should not add a token if it already exists');
test(function () {
  elem.classList.remove('bar');
  assert_equals( elem.classList.length, 2 );
  assert_equals( elem.classList + '', 'foo FOO', 'implicit' );
  assert_equals( elem.classList.toString(), 'foo FOO', 'explicit' );
}, 'classList.remove removes arguments passed, if they are present.');
test(function () {
  elem.classList.remove('foo');
  assert_equals( elem.classList.length, 1 );
  assert_equals( elem.classList + '', 'FOO', 'implicit' );
  assert_equals( elem.classList.toString(), 'FOO', 'explicit' );
  assert_false( elem.classList.contains('foo') );
  assert_true( elem.classList.contains('FOO') );
}, 'classList.remove must remove existing tokens');
test(function () {
  assert_not_equals( getComputedStyle(elem,null).fontStyle, 'italic' );
}, 'classList.remove must not break case-sensitive CSS selector matching');
test(function () {
  secondelem.classList.remove('test');
  assert_equals( secondelem.classList.length, 0 );
  assert_false( secondelem.classList.contains('test') );
}, 'classList.remove must remove duplicated tokens');
test(function () {
  secondelem.className = 'token1 token2 token3';
  secondelem.classList.remove('token2');
  assert_equals( secondelem.classList + '', 'token1 token3', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1 token3', 'explicit' );
}, 'classList.remove must collapse whitespace around removed tokens');
test(function () {
  secondelem.className = ' token1 token2  ';
  secondelem.classList.remove('token2');
  assert_equals( secondelem.classList + '', 'token1', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1', 'explicit' );
}, 'classList.remove must collapse whitespaces around each token');
test(function () {
  secondelem.className = '  token1  token2  token1  ';
  secondelem.classList.remove('token2');
  assert_equals( secondelem.classList + '', 'token1', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1', 'explicit' );
}, 'classList.remove must collapse whitespaces around each token and remove duplicates');
test(function () {
  secondelem.className = '  token1  token2  token1  ';
  secondelem.classList.remove('token1');
  assert_equals( secondelem.classList + '', 'token2', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token2', 'explicit' );
}, 'classList.remove must collapse whitespace when removing duplicate tokens');
test(function () {
  secondelem.className = '  token1  token1  ';
  secondelem.classList.add('token1');
  assert_equals( secondelem.classList + '', 'token1', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1', 'explicit' );
}, 'classList.add must collapse whitespaces and remove duplicates when adding a token that already exists');
test(function () {
  assert_true(elem.classList.toggle('foo'));
  assert_equals( elem.classList.length, 2 );
  assert_true( elem.classList.contains('foo') );
  assert_true( elem.classList.contains('FOO') );
}, 'classList.toggle must toggle tokens case-sensitively when adding');
test(function () {
  assert_equals( getComputedStyle(elem,null).fontStyle, 'italic' );
}, 'classList.toggle must not break case-sensitive CSS selector matching');
test(function () {
  assert_false(elem.classList.toggle('foo'));
}, 'classList.toggle must be able to remove tokens');
test(function () {
  //will return true if the last test incorrectly removed both
  assert_false(elem.classList.toggle('FOO'));
  assert_false( elem.classList.contains('foo') );
  assert_false( elem.classList.contains('FOO') );
}, 'classList.toggle must be case-sensitive when removing tokens');
test(function () {
  secondelem.className = 'foo FOO'
  secondelem.classList.replace('bar', 'baz');
  assert_equals( secondelem.classList.length, 2 );
  assert_equals( secondelem.classList + '', 'foo FOO', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'foo FOO', 'explicit' );
}, 'classList.replace replaces arguments passed, if they are present.');
test(function () {
  secondelem.classList.replace('foo', 'bar');
  assert_equals( secondelem.classList.length, 2 );
  assert_equals( secondelem.classList + '', 'bar FOO', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'bar FOO', 'explicit' );
  assert_false( secondelem.classList.contains('foo') );
  assert_true( secondelem.classList.contains('bar') );
  assert_true( secondelem.classList.contains('FOO') );
}, 'classList.replace must replace existing tokens');
test(function () {
  assert_not_equals( getComputedStyle(secondelem,null).fontStyle, 'italic' );
}, 'classList.replace must not break case-sensitive CSS selector matching');
test(function () {
  secondelem.className = 'token1 token2 token1'
  secondelem.classList.replace('token1', 'token3');
  assert_equals( secondelem.classList.length, 2 );
  assert_false( secondelem.classList.contains('token1') );
  assert_true( secondelem.classList.contains('token2') );
  assert_true( secondelem.classList.contains('token3') );
}, 'classList.replace must replace duplicated tokens');
test(function () {
  secondelem.className = 'token1  token2  token3';
  secondelem.classList.replace('token2', 'token4');
  assert_equals( secondelem.classList + '', 'token1 token4 token3', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1 token4 token3', 'explicit' );
}, 'classList.replace must collapse whitespace around replaced tokens');
test(function () {
  secondelem.className = ' token1 token2  ';
  secondelem.classList.replace('token2', 'token3');
  assert_equals( secondelem.classList.length, 2 );
  assert_equals( secondelem.classList + '', 'token1 token3', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1 token3', 'explicit' );
}, 'classList.replace must collapse whitespaces around each token');
test(function () {
  secondelem.className = '  token1  token2  token1  ';
  secondelem.classList.replace('token2', 'token3');
  assert_equals( secondelem.classList + '', 'token1 token3', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token1 token3', 'explicit' );
}, 'classList.replace must collapse whitespaces around each token and remove duplicates');
test(function () {
  secondelem.className = '  token1  token2  token1  ';
  secondelem.classList.replace('token1', 'token3');
  assert_equals( secondelem.classList + '', 'token3 token2', 'implicit' );
  assert_equals( secondelem.classList.toString(), 'token3 token2', 'explicit' );
}, 'classList.replace must collapse whitespace when replacing duplicate tokens');
test(function () {
  assert_not_equals( getComputedStyle(elem,null).fontStyle, 'italic' );
}, 'CSS class selectors must stop matching when all classes have been removed');
test(function () {
  assert_equals( elem.className, '' );
}, 'className must be empty when all classes have been removed');
test(function () {
  assert_equals( elem.classList + '', '', 'implicit' );
  assert_equals( elem.classList.toString(), '', 'explicit' );
}, 'classList must stringify to an empty string when all classes have been removed');
test(function () {
  assert_equals( elem.classList.item(0), null );
}, 'classList.item(0) must return null when all classes have been removed');
test(function () {
  /* the normative part of the spec states that:
  "unless the length is zero, in which case there are no supported property indices"
  ...
  "The term[...] supported property indices [is] used as defined in the WebIDL specification."
  WebIDL creates actual OwnProperties and then [] just acts as a normal property lookup */
  assert_equals( elem.classList[0], undefined );
}, 'classList[0] must be undefined when all classes have been removed');
test(function () {
  var foo = document.createElement('div');
  foo.classList.add();
  assert_true( foo.hasAttribute('class') );
  assert_equals( foo.classList + '', '', 'implicit' );
  assert_equals( foo.classList.toString(), '', 'explicit' );
}, 'Invoking add or remove should set the class attribute');
// The ordered set parser must skip ASCII whitespace (U+0009, U+000A, U+000C, U+000D, and U+0020.)
test(function () {
  var foo = document.createElement('div');
  foo.className = 'a ';
  foo.classList.add('b');
  assert_equals(foo.className,'a b');
}, 'classList.add should treat " " as a space');
test(function () {
  var foo = document.createElement('div');
  foo.className = 'a\t';
  foo.classList.add('b');
  assert_equals(foo.className,'a b');
}, 'classList.add should treat \\t as a space');
test(function () {
  var foo = document.createElement('div');
  foo.className = 'a\r';
  foo.classList.add('b');
  assert_equals(foo.className,'a b');
}, 'classList.add should treat \\r as a space');
test(function () {
  var foo = document.createElement('div');
  foo.className = 'a\n';
  foo.classList.add('b');
  assert_equals(foo.className,'a b');
}, 'classList.add should treat \\n as a space');
test(function () {
  var foo = document.createElement('div');
  foo.className = 'a\f';
  foo.classList.add('b');
  assert_equals(foo.className,'a b');
}, 'classList.add should treat \\f as a space');
test(function () {
  //WebIDL and ECMAScript 5 - a readonly property has a getter but not a setter
  //ES5 makes [[Put]] fail but not throw
  var failed = false;
  secondelem.className = 'token1';
  try {
    secondelem.classList.length = 0;
  } catch(e) {
    failed = e;
  }
  assert_equals(secondelem.classList.length,1);
  assert_false(failed,'an error was thrown');
}, 'classList.length must be read-only');
test(function () {
  var realList = secondelem.classList;
  secondelem.classList = 'foo bar';
  assert_equals(secondelem.classList,realList);
  assert_equals(secondelem.classList.length,2);
  assert_equals(secondelem.classList[0],'foo');
  assert_equals(secondelem.classList[1],'bar');
}, 'classList must have [PutForwards=value]');
test(function () {
  var foo = document.createElement('div');
  foo.className = 'a';
  foo.classList.replace('token1', 'token2');

  assert_equals(foo.className, 'a');

  foo.classList.replace('a', 'b');
  assert_equals(foo.className, 'b');

  assert_throws('SYNTAX_ERR', function () { foo.classList.replace('t with space', '') });
  assert_throws('INVALID_CHARACTER_ERR', function () { foo.classList.replace('t with space', 'foo') });
  assert_throws('SYNTAX_ERR', function () { foo.classList.replace('', 'foo') });
}, 'classList.replace should work');

test(function() {
  var foo = document.createElement('div');
  assert_throws(new TypeError(),
                function() { foo.classList.supports('hello') });
}, 'classList.supports should throw');
    </script>
  </head>
  <body>

    <div id="log"></div>

  </body>
</html>