| OLD | NEW |
| (Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <!-- |
| 12 |
| 13 The `<test-fixture>` element can simplify the exercise of consistently |
| 14 resetting a test suite's DOM. To use it, wrap the test suite's DOM as a template
: |
| 15 |
| 16 ```html |
| 17 <test-fixture id="SomeElementFixture"> |
| 18 <template> |
| 19 <some-element id="SomeElementForTesting"></some-element> |
| 20 </template> |
| 21 </test-fixture> |
| 22 ``` |
| 23 |
| 24 Now, the `<test-fixture>` element can be used to generate a copy of its |
| 25 template: |
| 26 |
| 27 ```html |
| 28 <script> |
| 29 describe('<some-element>', function () { |
| 30 var someElement; |
| 31 |
| 32 beforeEach(function () { |
| 33 document.getElementById('SomeElementFixture').create(); |
| 34 someElement = document.getElementById('SomeElementForTesting'); |
| 35 }); |
| 36 }); |
| 37 </script> |
| 38 ``` |
| 39 |
| 40 Fixtured elements can be cleaned up by calling `restore` on the `<test-fixture>`
: |
| 41 |
| 42 ```javascript |
| 43 afterEach(function () { |
| 44 document.getElementById('SomeElementFixture').restore(); |
| 45 // <some-element id='SomeElementForTesting'> has been removed |
| 46 }); |
| 47 ``` |
| 48 |
| 49 `<test-fixture>` will create fixtures from all of its immediate `<template>` |
| 50 children. The DOM structure of fixture templates can be as simple or as complex |
| 51 as the situation calls for. |
| 52 |
| 53 ## Even simpler usage in Mocha |
| 54 |
| 55 In Mocha, usage can be simplified even further. Include `test-fixture-mocha.js` |
| 56 after Mocha in the `<head>` of your document and then fixture elements like so: |
| 57 |
| 58 ```html |
| 59 <script> |
| 60 describe('<some-element>', function () { |
| 61 var someElement; |
| 62 |
| 63 beforeEach(function () { |
| 64 someElement = fixture('SomeElementFixture'); |
| 65 }); |
| 66 }); |
| 67 </script> |
| 68 ``` |
| 69 |
| 70 Fixtured elements will be automatically restored in the `afterEach` phase of the |
| 71 current Mocha `Suite`. |
| 72 |
| 73 ## Data-bound templates |
| 74 |
| 75 Data-binding systems are also supported, as long as your (custom) template |
| 76 elements define a `stamp(model)` method that returns a document fragment. This |
| 77 allows you to stamp out templates w/ custom models for your fixtures. |
| 78 |
| 79 For example, using Polymer 0.8's `dom-template`: |
| 80 |
| 81 ```html |
| 82 <test-fixture id="bound"> |
| 83 <template is="dom-template"> |
| 84 <span>{{greeting}}</span> |
| 85 </template> |
| 86 </test-fixture> |
| 87 ``` |
| 88 |
| 89 You can pass an optional context argument to `create()` or `fixture()` to pass |
| 90 the model: |
| 91 |
| 92 ```js |
| 93 var bound = fixture('bound', {greeting: 'ohai thurr'}); |
| 94 ``` |
| 95 |
| 96 ## The problem being addressed |
| 97 |
| 98 Consider the following `web-component-tester` test suite: |
| 99 |
| 100 ```html |
| 101 <!doctype html> |
| 102 <html> |
| 103 <head> |
| 104 <title>some-element test suite</title> |
| 105 |
| 106 <link rel="import" href="../some-element.html"> |
| 107 </head> |
| 108 <body> |
| 109 <some-element id="SomeElementForTesting"></some-element> |
| 110 <script> |
| 111 describe('<some-element>', function () { |
| 112 var someElement; |
| 113 |
| 114 beforeEach(function () { |
| 115 someElement = document.getElementById('SomeElementForTesting'); |
| 116 }); |
| 117 |
| 118 it('can receive property `foo`', function () { |
| 119 someElement.foo = 'bar'; |
| 120 expect(someElement.foo).to.be.equal('bar'); |
| 121 }); |
| 122 |
| 123 it('has a default `foo` value of `undefined`', function () { |
| 124 expect(someElement.foo).to.be.equal(undefined); |
| 125 }); |
| 126 }); |
| 127 </script> |
| 128 </body> |
| 129 </html> |
| 130 ``` |
| 131 |
| 132 In this contrived example, the suite will pass or fail depending on which order |
| 133 the tests are run in. It is non-deterministic because `someElement` has |
| 134 internal state that is not properly reset at the end of each test. |
| 135 |
| 136 It would be trivial in the above example to simply reset `someElement.foo` to |
| 137 the expected default value of `undefined` in an `afterEach` hook. However, for |
| 138 non-contrived test suites that target complex elements, this can result in a |
| 139 large quantity of ever-growing set-up and tear-down boilerplate. |
| 140 |
| 141 @pseudoElement test-fixture |
| 142 --> |
| 143 |
| 144 <script> |
| 145 |
| 146 (function () { |
| 147 var TestFixturePrototype = Object.create(HTMLElement.prototype); |
| 148 var TestFixtureExtension = { |
| 149 _fixtureTemplates: null, |
| 150 |
| 151 _elementsFixtured: false, |
| 152 |
| 153 get elementsFixtured () { |
| 154 return this._elementsFixtured; |
| 155 }, |
| 156 |
| 157 get fixtureTemplates () { |
| 158 if (!this._fixtureTemplates) { |
| 159 // Copy fixtures to a true Array for Safari 7. This prevents their |
| 160 // `content` property from being improperly garbage collected. |
| 161 this._fixtureTemplates = Array.prototype.slice.apply(this.querySelectorA
ll('template')); |
| 162 } |
| 163 |
| 164 return this._fixtureTemplates; |
| 165 }, |
| 166 |
| 167 create: function (model) { |
| 168 var generatedDoms = []; |
| 169 |
| 170 this.restore(); |
| 171 |
| 172 this.removeElements(this.fixtureTemplates); |
| 173 |
| 174 this.forElements(this.fixtureTemplates, function (fixtureTemplate) { |
| 175 generatedDoms.push( |
| 176 this.createFrom(fixtureTemplate, model) |
| 177 ); |
| 178 }, this); |
| 179 |
| 180 this.forcePolyfillAttachedStateSynchrony(); |
| 181 |
| 182 if (generatedDoms.length < 2) { |
| 183 return generatedDoms[0]; |
| 184 } |
| 185 |
| 186 return generatedDoms; |
| 187 }, |
| 188 |
| 189 createFrom: function (fixtureTemplate, model) { |
| 190 var fixturedFragment; |
| 191 var fixturedElements; |
| 192 var fixturedElement; |
| 193 |
| 194 if (!(fixtureTemplate && |
| 195 fixtureTemplate.tagName === 'TEMPLATE')) { |
| 196 return; |
| 197 } |
| 198 |
| 199 try { |
| 200 fixturedFragment = this.stamp(fixtureTemplate, model); |
| 201 } catch (error) { |
| 202 console.error('Error stamping', fixtureTemplate, error); |
| 203 throw error; |
| 204 } |
| 205 |
| 206 fixturedElements = this.collectElementChildren(fixturedFragment); |
| 207 |
| 208 this.appendChild(fixturedFragment); |
| 209 this._elementsFixtured = true; |
| 210 |
| 211 if (fixturedElements.length < 2) { |
| 212 return fixturedElements[0]; |
| 213 } |
| 214 |
| 215 return fixturedElements; |
| 216 }, |
| 217 |
| 218 restore: function () { |
| 219 if (!this._elementsFixtured) { |
| 220 return; |
| 221 } |
| 222 |
| 223 this.removeElements(this.children); |
| 224 |
| 225 this.forElements(this.fixtureTemplates, function (fixtureTemplate) { |
| 226 this.appendChild(fixtureTemplate); |
| 227 }, this); |
| 228 |
| 229 this.generatedDomStack = []; |
| 230 |
| 231 this._elementsFixtured = false; |
| 232 |
| 233 this.forcePolyfillAttachedStateSynchrony(); |
| 234 }, |
| 235 |
| 236 forcePolyfillAttachedStateSynchrony: function () { |
| 237 // Force synchrony in attachedCallback and detachedCallback where |
| 238 // implemented, in the event that we are dealing with one of these async |
| 239 // polyfills: |
| 240 // 1. Web Components CustomElements polyfill (v1 or v0). |
| 241 if (window.customElements && window.customElements.flush) { |
| 242 window.customElements.flush(); |
| 243 } else if (window.CustomElements && window.CustomElements.takeRecords) { |
| 244 window.CustomElements.takeRecords(); |
| 245 } |
| 246 // 2. ShadyDOM polyfill. |
| 247 if (window.ShadyDOM && window.ShadyDOM.flush) { |
| 248 window.ShadyDOM.flush(); |
| 249 } |
| 250 }, |
| 251 |
| 252 collectElementChildren: function (parent) { |
| 253 // Note: Safari 7.1 does not support `firstElementChild` or |
| 254 // `nextElementSibling`, so we do things the old-fashioned way: |
| 255 var elements = []; |
| 256 var child = parent.firstChild; |
| 257 |
| 258 while (child) { |
| 259 if (child.nodeType === Node.ELEMENT_NODE) { |
| 260 elements.push(child); |
| 261 } |
| 262 |
| 263 child = child.nextSibling; |
| 264 } |
| 265 |
| 266 return elements; |
| 267 }, |
| 268 |
| 269 removeElements: function (elements) { |
| 270 this.forElements(elements, function (element) { |
| 271 this.removeChild(element); |
| 272 }, this); |
| 273 }, |
| 274 |
| 275 forElements: function (elements, iterator, context) { |
| 276 Array.prototype.slice.call(elements) |
| 277 .forEach(iterator, context); |
| 278 }, |
| 279 |
| 280 stamp: function (fixtureTemplate, model) { |
| 281 var stamped; |
| 282 // Check if we are dealing with a "stampable" `<template>`. This is a |
| 283 // vaguely defined special case of a `<template>` that is a custom |
| 284 // element with a public `stamp` method that implements some manner of |
| 285 // data binding. |
| 286 if (fixtureTemplate.stamp) { |
| 287 stamped = fixtureTemplate.stamp(model); |
| 288 // We leak Polymer specifics a little; if there is an element `root`, we |
| 289 // want that to be returned. |
| 290 stamped = stamped.root || stamped; |
| 291 |
| 292 // Otherwise, we fall back to standard HTML templates, which do not have |
| 293 // any sort of binding support. |
| 294 } else { |
| 295 if (model) { |
| 296 console.warn(this, 'was given a model to stamp, but the template is no
t of a bindable type'); |
| 297 } |
| 298 |
| 299 stamped = document.importNode(fixtureTemplate.content, true); |
| 300 |
| 301 // Immediately upgrade the subtree if we are dealing with async |
| 302 // Web Components polyfill. |
| 303 // https://github.com/Polymer/polymer/blob/0.8-preview/src/features/mini
/template.html#L52 |
| 304 if (window.CustomElements && window.CustomElements.upgradeSubtree) { |
| 305 window.CustomElements.upgradeSubtree(stamped); |
| 306 } |
| 307 } |
| 308 |
| 309 return stamped; |
| 310 } |
| 311 }; |
| 312 |
| 313 Object.getOwnPropertyNames(TestFixtureExtension) |
| 314 .forEach(function (property) { |
| 315 Object.defineProperty( |
| 316 TestFixturePrototype, |
| 317 property, |
| 318 Object.getOwnPropertyDescriptor(TestFixtureExtension, property) |
| 319 ); |
| 320 }); |
| 321 |
| 322 try { |
| 323 if (window.customElements) { |
| 324 function TestFixture() { |
| 325 return ((window.Reflect && Reflect.construct) ? |
| 326 Reflect.construct(HTMLElement, [], TestFixture) |
| 327 : HTMLElement.call(this)) || this; |
| 328 } |
| 329 TestFixture.prototype = TestFixturePrototype; |
| 330 // `constructor` is not writable on Safari 9, but is configurable. |
| 331 Object.defineProperty(TestFixture.prototype, 'constructor', { |
| 332 configurable: true, |
| 333 enumerable: true, |
| 334 writable: true, |
| 335 value: TestFixture, |
| 336 }); |
| 337 window.customElements.define('test-fixture', TestFixture); |
| 338 } else { |
| 339 document.registerElement('test-fixture', { |
| 340 prototype: TestFixturePrototype |
| 341 }); |
| 342 } |
| 343 } catch (e) { |
| 344 if (window.WCT) { |
| 345 console.warn('if you are using WCT, you do not need to manually import tes
t-fixture.html'); |
| 346 } else { |
| 347 console.warn('test-fixture has already been registered!'); |
| 348 } |
| 349 } |
| 350 })(); |
| 351 </script> |
| OLD | NEW |