| OLD | NEW | 
|    1 <!doctype html> |    1 <!doctype html> | 
|    2 <!-- |    2 <!-- | 
|    3 @license |    3 @license | 
|    4 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |    4 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. | 
|    5 This code may only be used under the BSD style license found at http://polymer.g
     ithub.io/LICENSE.txt |    5 This code may only be used under the BSD style license found at http://polymer.g
     ithub.io/LICENSE.txt | 
|    6 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |    6 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | 
|    7 The complete set of contributors may be found at http://polymer.github.io/CONTRI
     BUTORS.txt |    7 The complete set of contributors may be found at http://polymer.github.io/CONTRI
     BUTORS.txt | 
|    8 Code distributed by Google as part of the polymer project is also |    8 Code distributed by Google as part of the polymer project is also | 
|    9 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
     TS.txt |    9 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
     TS.txt | 
|   10 --> |   10 --> | 
|   11 <html> |   11 <html> | 
|   12 <head> |   12 <head> | 
|   13   <title>firebase-collection</title> |   13   <title>firebase-collection</title> | 
|   14  |   14  | 
|   15   <script src="../../webcomponentsjs/webcomponents.js"></script> |   15   <script src="../../webcomponentsjs/webcomponents.js"></script> | 
|   16   <script src="../../web-component-tester/browser.js"></script> |   16   <script src="../../web-component-tester/browser.js"></script> | 
|   17   <script src="../../test-fixture/test-fixture-mocha.js"></script> |   17   <script src="../../test-fixture/test-fixture-mocha.js"></script> | 
|   18  |   18  | 
|   19   <link rel="import" href="../../polymer/polymer.html"> |   19   <link rel="import" href="../../polymer/polymer.html"> | 
|   20   <link rel="import" href="../../promise-polyfill/promise-polyfill.html"> |  | 
|   21   <link rel="import" href="../../test-fixture/test-fixture.html"> |   20   <link rel="import" href="../../test-fixture/test-fixture.html"> | 
|   22   <link rel="import" href="test-helpers.html"> |   21   <link rel="import" href="test-helpers.html"> | 
|   23   <link rel="import" href="../firebase-collection.html"> |   22   <link rel="import" href="../firebase-collection.html"> | 
|   24 </head> |   23 </head> | 
|   25 <body> |   24 <body> | 
|   26   <test-fixture id="TrivialCollection"> |   25   <test-fixture id="TrivialCollection"> | 
|   27     <template> |   26     <template> | 
|   28       <firebase-collection |   27       <firebase-collection log></firebase-collection> | 
|   29         location="https://fb-element-demo.firebaseio.com/test/trivial" |  | 
|   30         log> |  | 
|   31       </firebase-collection> |  | 
|   32     </template> |  | 
|   33   </test-fixture> |  | 
|   34   <test-fixture id="PrimitiveCollection"> |  | 
|   35     <template> |  | 
|   36       <firebase-collection |  | 
|   37         location="https://fb-element-demo.firebaseio.com/test/primitives" |  | 
|   38         order-by-value |  | 
|   39         log> |  | 
|   40       </firebase-collection> |  | 
|   41     </template> |  | 
|   42   </test-fixture> |  | 
|   43   <test-fixture id="ChangingChildren"> |  | 
|   44     <template> |  | 
|   45       <firebase-collection |  | 
|   46         location="https://fb-element-demo.firebaseio.com/test/changing_children" |  | 
|   47         order-by-child="foo" |  | 
|   48         log> |  | 
|   49       </firebase-collection> |  | 
|   50     </template> |   28     </template> | 
|   51   </test-fixture> |   29   </test-fixture> | 
|   52  |   30  | 
|   53   <test-fixture id="SyncingCollections"> |  | 
|   54     <template> |  | 
|   55       <firebase-collection |  | 
|   56         location="https://fb-element-demo.firebaseio.com/test/syncing" |  | 
|   57         log> |  | 
|   58       </firebase-collection> |  | 
|   59       <firebase-collection |  | 
|   60         location="https://fb-element-demo.firebaseio.com/test/syncing" |  | 
|   61         order-by-child="foo" |  | 
|   62         log> |  | 
|   63       </firebase-collection> |  | 
|   64     </template> |  | 
|   65   </test-fixture> |  | 
|   66  |  | 
|   67   <test-fixture id="BoundCollection"> |   31   <test-fixture id="BoundCollection"> | 
|   68     <template> |   32     <template> | 
|   69       <section> |   33       <section> | 
|   70         <!-- TODO(cdata): Add support for elements like `dom-bind` at the root |   34         <!-- TODO(cdata): Add support for elements like `dom-bind` at the root | 
|   71              of the template to `test-fixture`, so that we can remove this |   35              of the template to `test-fixture`, so that we can remove this | 
|   72              wrapping `section`. --> |   36              wrapping `section`. --> | 
|   73         <template is="dom-bind"> |   37         <template is="dom-bind"> | 
|   74           <template is="dom-repeat" items="{{data}}"> |   38           <template is="dom-repeat" items="{{data}}"> | 
|   75             <div>[[item.value]]</div> |   39             <div>[[item.value]]</div> | 
|   76           </template> |   40           </template> | 
|   77           <firebase-collection |   41           <firebase-collection | 
|   78             location="https://fb-element-demo.firebaseio.com/test/empty" |  | 
|   79             data="{{data}}" |   42             data="{{data}}" | 
|   80             log> |   43             log> | 
|   81           </firebase-collection> |   44           </firebase-collection> | 
|   82         </template> |   45         </template> | 
|   83       </section> |   46       </section> | 
|   84     </template> |   47     </template> | 
|   85   </test-fixture> |   48   </test-fixture> | 
|   86  |   49  | 
|   87   <script> |   50   <script> | 
|   88     suite('<firebase-collection>', function() { |   51  | 
 |   52     suite('firebase-collection', function() { | 
|   89       var firebase; |   53       var firebase; | 
|   90  |   54  | 
|   91       suite('collection manipulation', function() { |   55       teardown(function() { | 
 |   56         if (firebase) { | 
 |   57           removeFirebase(firebase); | 
 |   58         } | 
 |   59       }); | 
 |   60  | 
 |   61       suite('basic usage', function() { | 
 |   62         var numberOfItems; | 
 |   63  | 
 |   64         setup(function() { | 
 |   65           numberOfItems = 3; | 
 |   66           firebase = fixtureFirebase('TrivialCollection', arrayOfObjects(numberO
     fItems)); | 
 |   67         }); | 
 |   68  | 
 |   69         test('exposes data as an array', function() { | 
 |   70           expect(firebase.data).to.be.an('array'); | 
 |   71           expect(firebase.data.length).to.be.equal(numberOfItems); | 
 |   72         }); | 
 |   73  | 
 |   74         test('receives data from Firebase location', function() { | 
 |   75           expect(firebase.data[0].value).to.be.a('number'); | 
 |   76         }); | 
 |   77       }); | 
 |   78  | 
 |   79       suite('ordered primitives', function() { | 
 |   80         var numberOfItems; | 
 |   81  | 
 |   82         setup(function() { | 
 |   83           numberOfItems = 5; | 
 |   84           firebase = fixtureFirebase('TrivialCollection', arrayOfPrimitives(numb
     erOfItems)); | 
 |   85           firebase.orderByValue = true; | 
 |   86         }); | 
 |   87  | 
 |   88         test('converts primitives into objects with a value key', function() { | 
 |   89           expect(firebase.data[0]).to.be.an('object'); | 
 |   90         }); | 
 |   91  | 
 |   92         test('orders primitives by value', function() { | 
 |   93           var lastValue = -Infinity; | 
 |   94  | 
 |   95           expect(firebase.data.length).to.be.equal(numberOfItems); | 
 |   96  | 
 |   97           firebase.data.forEach(function(datum) { | 
 |   98             expect(datum.value).to.not.be.lessThan(lastValue); | 
 |   99             lastValue = datum.value; | 
 |  100           }); | 
 |  101         }); | 
 |  102       }); | 
 |  103  | 
 |  104       suite('removing a value locally', function() { | 
 |  105         var numberOfItems; | 
 |  106         setup(function() { | 
 |  107           numberOfItems = 3; | 
 |  108           firebase = fixtureFirebase('TrivialCollection', arrayOfObjects(numberO
     fItems)); | 
 |  109         }); | 
 |  110  | 
 |  111         test('works for data-bound changes', function() { | 
 |  112           firebase.splice('data', 0, 1); | 
 |  113           expect(firebase.data.length).to.be.equal(numberOfItems - 1); | 
 |  114         }); | 
 |  115  | 
 |  116         test('can be done with `remove`', function() { | 
 |  117           var objectToBeRemoved = firebase.data[0]; | 
 |  118           firebase.remove(objectToBeRemoved); | 
 |  119  | 
 |  120           expect(firebase.data.length).to.be.equal(numberOfItems - 1); | 
 |  121           expect(firebase.data.indexOf(objectToBeRemoved)).to.be.equal(-1); | 
 |  122         }); | 
 |  123       }); | 
 |  124  | 
 |  125       suite('adding a value locally', function() { | 
 |  126         setup(function() { | 
 |  127           firebase = fixtureFirebase('TrivialCollection'); | 
 |  128         }); | 
 |  129  | 
 |  130         test('works for data-bound changes', function(done) { | 
 |  131           var intendedValue = randomInt(); | 
 |  132           var index = firebase.push('data', intendedValue) - 1; | 
 |  133  | 
 |  134           // NOTE(cdata): See polymer/polymer#2491. | 
 |  135           Polymer.Base.async(function() { | 
 |  136             expect(firebase.data[index]).to.have.property('value'); | 
 |  137             expect(firebase.data[index].value).to.be.equal(intendedValue); | 
 |  138             done(); | 
 |  139           }, 1); | 
 |  140         }); | 
 |  141  | 
 |  142         test('can be done with `add`', function(done) { | 
 |  143           var object = randomObject(); | 
 |  144           var length = firebase.data.length; | 
 |  145           var foundObject; | 
 |  146  | 
 |  147           firebase.add(object); | 
 |  148  | 
 |  149           // NOTE(cdata): See polymer/polymer#2491. | 
 |  150           Polymer.Base.async(function() { | 
 |  151             expect(firebase.data.length).to.be.equal(length + 1); | 
 |  152  | 
 |  153             firebase.data.forEach(function(datum) { | 
 |  154               if (datum.value === object.value) { | 
 |  155                 foundObject = datum; | 
 |  156               } | 
 |  157             }); | 
 |  158  | 
 |  159             expect(foundObject).to.be.okay; | 
 |  160             expect(foundObject.value).to.be.equal(object.value); | 
 |  161             done(); | 
 |  162           }, 1); | 
 |  163         }); | 
 |  164       }); | 
 |  165  | 
 |  166       suite('a changing child', function() { | 
 |  167         var numberOfItems; | 
 |  168         var remoteFirebase; | 
 |  169  | 
 |  170         setup(function() { | 
 |  171           numberOfItems = 3; | 
 |  172           firebase = fixtureFirebase('TrivialCollection', arrayOfObjects(numberO
     fItems)); | 
 |  173           remoteFirebase = new Firebase(firebase.location); | 
 |  174         }); | 
 |  175  | 
 |  176         test('updates the child key in place with the new value', function() { | 
 |  177           var datum = firebase.data[0]; | 
 |  178           var newValue = 99999; | 
 |  179           var key = Polymer.Collection.get(firebase.data).getKey(datum); | 
 |  180  | 
 |  181           firebase.set('data.' + key + '.value', newValue); | 
 |  182  | 
 |  183           expect(firebase.data[0].value).to.be.equal(newValue); | 
 |  184         }); | 
 |  185       }); | 
 |  186  | 
 |  187       suite('syncing collections', function() { | 
 |  188         var numberOfItems; | 
 |  189         var remoteFirebase; | 
 |  190  | 
 |  191         setup(function() { | 
 |  192           numberOfItems = 3; | 
 |  193  | 
 |  194           firebase = fixtureFirebase('TrivialCollection', arrayOfObjects(3)); | 
 |  195           firebase.orderValueType = 'number'; | 
 |  196           firebase.orderByValue = true; | 
 |  197  | 
 |  198           remoteFirebase = new Firebase(firebase.location); | 
 |  199         }); | 
 |  200  | 
 |  201         test('sync a new item at the correct index', function() { | 
 |  202           var firstValue = firebase.data[0]; | 
 |  203           var secondValue = firebase.data[1]; | 
 |  204           var datum = firebase.data[0]; | 
 |  205           var key = Polymer.Collection.get(firebase.data).getKey(datum); | 
 |  206           var remoteValue; | 
 |  207  | 
 |  208           remoteFirebase.on('value', function(snapshot) { | 
 |  209             remoteValue = snapshot.val(); | 
 |  210           }); | 
 |  211  | 
 |  212           expect(remoteValue[0].value).to.be.equal(firebase.data[0].value); | 
 |  213         }); | 
 |  214       }); | 
 |  215  | 
 |  216       suite('data-bound collection manipulation', function() { | 
 |  217         var numberOfItems; | 
 |  218         var elements; | 
|   92         var domBind; |  219         var domBind; | 
|   93         var dom; |  220  | 
|   94  |  221         setup(function() { | 
|   95         setup(function() { |  222           elements = fixture('BoundCollection'); | 
|   96           dom = fixture('BoundCollection'); |  223           domBind = elements.querySelector('[is=dom-bind]'); | 
|   97           domBind = dom.querySelector('[is=dom-bind]'); |  224           firebase = elements.querySelector('firebase-collection'); | 
|   98           firebase = dom.querySelector('firebase-collection'); |  225           firebase.location = fixtureLocation(arrayOfObjects(3)); | 
|   99         }); |  226           numberOfItems = 3; | 
|  100  |  227         }); | 
|  101         test('added values reflect 1-to-1 in the DOM', function(done) { |  228  | 
|  102           waitForEvent(firebase, 'firebase-value').then(function() { |  229         test('splices reflect in Firebase data', function(done) { | 
|  103             waitForEvent(firebase, 'firebase-child-added').then(function() { |  230           domBind.splice('data', 0, 1, randomObject()); | 
|  104               expect(dom.querySelectorAll('div').length).to.be.equal(firebase.da
     ta.length); |  231           domBind.shift('data'); | 
 |  232           domBind.push.apply(domBind, ['data'].concat(arrayOfObjects(2))); | 
 |  233  | 
 |  234           // NOTE(cdata): See polymer/polymer#2491. | 
 |  235           Polymer.Base.async(function() { | 
 |  236             expect(firebase.data.length).to.be.equal(domBind.data.length); | 
 |  237  | 
 |  238             firebase.data.forEach(function(datum, index) { | 
 |  239               expect(domBind.data[index].value).to.be.equal(datum.value); | 
 |  240             }); | 
 |  241  | 
 |  242             done(); | 
 |  243           }, 1); | 
 |  244         }); | 
 |  245  | 
 |  246         test('splices reflect in the DOM', function(done) { | 
 |  247           var divs; | 
 |  248  | 
 |  249           firebase.push.apply(firebase, ['data'].concat(arrayOfObjects(3))); | 
 |  250  | 
 |  251           Polymer.Base.async(function() { | 
 |  252             divs = elements.querySelectorAll('div'); | 
 |  253             expect(divs.length).to.be.equal(firebase.data.length); | 
 |  254  | 
 |  255             domBind.splice('data', 2, 1, randomObject()); | 
 |  256  | 
 |  257             Polymer.Base.async(function() { | 
 |  258               divs = elements.querySelectorAll('div'); | 
 |  259               expect(divs.length).to.be.equal(firebase.data.length); | 
 |  260  | 
 |  261               firebase.data.forEach(function(datum, index) { | 
 |  262                 var divValue = parseInt(divs[index].textContent, 10); | 
 |  263                 expect(datum.value).to.be.equal(divValue); | 
 |  264               }); | 
 |  265  | 
|  105               done(); |  266               done(); | 
|  106             }); |  267             }, 1); | 
|  107             domBind.unshift('data', { value: 'blah' }); |  268           }, 1); | 
|  108           }); |  | 
|  109         }); |  | 
|  110       }); |  | 
|  111  |  | 
|  112       suite('basic usage', function() { |  | 
|  113         setup(function() { |  | 
|  114           firebase = fixture('TrivialCollection'); |  | 
|  115         }); |  | 
|  116  |  | 
|  117         teardown(function() { |  | 
|  118           firebase.disconnect(); |  | 
|  119         }); |  | 
|  120  |  | 
|  121         test('exposes data as an array', function(done) { |  | 
|  122           waitForEvent(firebase, 'firebase-child-added').then(function() { |  | 
|  123             expect(firebase.data).to.be.an('array'); |  | 
|  124             done(); |  | 
|  125           }).catch(function(e) { |  | 
|  126             done(e); |  | 
|  127           }); |  | 
|  128         }); |  | 
|  129  |  | 
|  130         test('receives data from Firebase location', function(done) { |  | 
|  131           waitForEvent(firebase, 'data-changed').then(function() { |  | 
|  132             expect(firebase.data[0].value).to.be.equal(true); |  | 
|  133             done(); |  | 
|  134           }).catch(function(e) { |  | 
|  135             done(e); |  | 
|  136           }); |  | 
|  137         }); |  | 
|  138       }); |  | 
|  139  |  | 
|  140       suite('ordered primitives', function() { |  | 
|  141         setup(function() { |  | 
|  142           firebase = fixture('PrimitiveCollection'); |  | 
|  143         }); |  | 
|  144  |  | 
|  145         teardown(function() { |  | 
|  146           firebase.disconnect(); |  | 
|  147         }); |  | 
|  148  |  | 
|  149         test('converts primitives into objects with a value key', function(done)
      { |  | 
|  150           waitForEvent(firebase, 'firebase-child-added').then(function() { |  | 
|  151             expect(firebase.data[0]).to.be.an('object'); |  | 
|  152             done(); |  | 
|  153           }).catch(function(e) { |  | 
|  154             done(e); |  | 
|  155           }); |  | 
|  156         }); |  | 
|  157  |  | 
|  158         test('orders primitives by value', function(done) { |  | 
|  159           waitForEvent(firebase, 'firebase-value').then(function() { |  | 
|  160             var lastValue = -Infinity; |  | 
|  161             expect(firebase.data.length).to.be.greaterThan(0); |  | 
|  162             firebase.data.forEach(function(item) { |  | 
|  163               expect(item.value).to.not.be.lessThan(lastValue); |  | 
|  164               lastValue = item.value; |  | 
|  165             }); |  | 
|  166             done(); |  | 
|  167           }).catch(function(e) { |  | 
|  168             done(e); |  | 
|  169           }); |  | 
|  170         }); |  | 
|  171  |  | 
|  172         suite('adding a value locally', function() { |  | 
|  173           setup(function(done) { |  | 
|  174             waitForEvent(firebase, 'firebase-value').then(function() { |  | 
|  175               done(); |  | 
|  176             }).catch(function(e) { |  | 
|  177               done(e); |  | 
|  178             }); |  | 
|  179           }); |  | 
|  180  |  | 
|  181           test('can be done with `add`', function(done) { |  | 
|  182             var length = firebase.data.length; |  | 
|  183             var newValue = firebase.data[firebase.data.length - 1].value + 1; |  | 
|  184             var key; |  | 
|  185  |  | 
|  186             waitForEvent(firebase, 'firebase-child-added').then(function() { |  | 
|  187               expect(firebase.data.length).to.be.equal(length + 1); |  | 
|  188               expect(firebase.data[firebase.data.length - 1].value).to.be.equal(
     newValue); |  | 
|  189               done(); |  | 
|  190             }).catch(function(e) { |  | 
|  191               done(e); |  | 
|  192             }).then(function() { |  | 
|  193               firebase.removeByKey(key); |  | 
|  194             }); |  | 
|  195  |  | 
|  196             key = firebase.add(newValue).key(); |  | 
|  197           }); |  | 
|  198         }); |  | 
|  199       }); |  | 
|  200  |  | 
|  201       suite('a child changes', function() { |  | 
|  202         setup(function(done) { |  | 
|  203           firebase = fixture('ChangingChildren'); |  | 
|  204           waitForEvent(firebase, 'firebase-value').then(function() { |  | 
|  205             done(); |  | 
|  206           }).catch(function(e) { |  | 
|  207             done(e) |  | 
|  208           }); |  | 
|  209         }); |  | 
|  210  |  | 
|  211         test('adds a new child based on local changes'); |  | 
|  212  |  | 
|  213         test('updates the child key in place with the new value', function(done)
      { |  | 
|  214           var childrenKeys = []; |  | 
|  215  |  | 
|  216           waitForEvent(firebase, 'firebase-value').then(function() { |  | 
|  217             var middleValue = firebase.getByKey(childrenKeys[1]); |  | 
|  218             var changes; |  | 
|  219  |  | 
|  220             expect(middleValue.foo).to.be.equal(1); |  | 
|  221             expect(middleValue.bar).to.be.equal(1); |  | 
|  222  |  | 
|  223             changes = waitForEvent(firebase, 'firebase-child-changed'); |  | 
|  224  |  | 
|  225             firebase.set('data.' + firebase.data.indexOf(middleValue) + '.bar', 
     2); |  | 
|  226  |  | 
|  227             return changes; |  | 
|  228           }).then(function() { |  | 
|  229             var middleValue = firebase.getByKey(childrenKeys[1]); |  | 
|  230  |  | 
|  231             expect(middleValue.foo).to.be.equal(1); |  | 
|  232             expect(middleValue.bar).to.be.equal(2); |  | 
|  233  |  | 
|  234             done(); |  | 
|  235           }).catch(function(e) { |  | 
|  236             done(e); |  | 
|  237           }).then(function() { |  | 
|  238             childrenKeys.forEach(function(key) { |  | 
|  239               firebase.removeByKey(key); |  | 
|  240             }); |  | 
|  241           }); |  | 
|  242  |  | 
|  243           childrenKeys = [0, 1, 2].map(function(value, index) { |  | 
|  244             return firebase.add({ |  | 
|  245               foo: value, |  | 
|  246               bar: index |  | 
|  247             }).key(); |  | 
|  248           }); |  | 
|  249         }); |  | 
|  250       }); |  | 
|  251  |  | 
|  252       suite('syncing collections', function() { |  | 
|  253         var localFirebase; |  | 
|  254         var remoteFirebase; |  | 
|  255  |  | 
|  256         setup(function(done) { |  | 
|  257           firebase = fixture('SyncingCollections'); |  | 
|  258           localFirebase = firebase[0]; |  | 
|  259           remoteFirebase = firebase[1]; |  | 
|  260           Promise.all([ |  | 
|  261             waitForEvent(localFirebase, 'firebase-value'), |  | 
|  262             waitForEvent(remoteFirebase, 'firebase-value') |  | 
|  263           ]).then(function() { |  | 
|  264             done(); |  | 
|  265           }).catch(function(e) { |  | 
|  266             done(e); |  | 
|  267           }); |  | 
|  268         }); |  | 
|  269  |  | 
|  270         test('syncs a new item at the correct index', function(done) { |  | 
|  271           var data = { |  | 
|  272             foo: 100 |  | 
|  273           }; |  | 
|  274           var key; |  | 
|  275  |  | 
|  276           waitForEvent(remoteFirebase, 'firebase-value').then(function() { |  | 
|  277             var value = remoteFirebase.getByKey(key); |  | 
|  278             var lowValue = remoteFirebase.getByKey('lowValue'); |  | 
|  279             var highValue = remoteFirebase.getByKey('highValue'); |  | 
|  280  |  | 
|  281             var index = remoteFirebase.data.indexOf(value); |  | 
|  282             var lowIndex = remoteFirebase.data.indexOf(lowValue); |  | 
|  283             var highIndex = remoteFirebase.data.indexOf(highValue); |  | 
|  284  |  | 
|  285             expect(value).to.be.okay; |  | 
|  286             expect(index).to.be.lessThan(highIndex); |  | 
|  287             expect(index).to.be.greaterThan(lowIndex); |  | 
|  288             done(); |  | 
|  289           }).catch(function(e) { |  | 
|  290             done(e); |  | 
|  291           }).then(function() { |  | 
|  292             localFirebase.removeByKey(key); |  | 
|  293           }); |  | 
|  294  |  | 
|  295           key = localFirebase.add(data).key(); |  | 
|  296         }); |  269         }); | 
|  297       }); |  270       }); | 
|  298     }); |  271     }); | 
 |  272  | 
|  299   </script> |  273   </script> | 
|  300  |  274  | 
|  301 </body> |  275 </body> | 
|  302 </html> |  276 </html> | 
| OLD | NEW |