diff --git a/www/assert/main.js b/www/assert/main.js index 0913674ae..0b759c99b 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -7,7 +7,8 @@ define([ '/drive/tests.js', '/common/test.js', '/common/common-thumbnail.js', -], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb) { + '/common/flat-dom.js', +], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb, Flat) { window.Hyperjson = Hyperjson; window.TextPatcher = TextPatcher; window.Sortify = Sortify; @@ -241,6 +242,7 @@ define([ return cb(true); }, "version 2 hash failed to parse correctly"); +/* assert(function (cb) { var getBlob = function (url, cb) { var xhr = new XMLHttpRequest(); @@ -266,9 +268,21 @@ define([ }); }); }); +*/ Drive.test(assert); + assert(function (cb) { + // extract dom elements into a flattened JSON representation + var flat = Flat.fromDOM(document.body); + // recreate a _mostly_ equivalent DOM + var dom = Flat.toDOM(flat); + // assume we don't care about comments + var bodyText = document.body.outerHTML.replace(//g, ''); + // check for equality + cb(dom.outerHTML === bodyText); + }); + var swap = function (str, dict) { return str.replace(/\{\{(.*?)\}\}/g, function (all, key) { return typeof dict[key] !== 'undefined'? dict[key] : all; diff --git a/www/common/flat-dom.js b/www/common/flat-dom.js new file mode 100644 index 000000000..51c3d07b3 --- /dev/null +++ b/www/common/flat-dom.js @@ -0,0 +1,83 @@ +define([], function () { + var Flat = {}; + + var slice = function (coll) { + return Array.prototype.slice.call(coll); + }; + + var getAttrs = function (el) { + var i = 0; + var l = el.attributes.length; + var attr; + var data = {}; + for (;i < l;i++) { + attr = el.attributes[i]; + if (attr.name && attr.value) { data[attr.name] = attr.value; } + } + return data; + }; + + var identity = function (x) { return x; }; + Flat.fromDOM = function (dom) { + var data = { + map: {}, + }; + + var i = 1; // start from 1 so we're always truthey + var uid = function () { return i++; }; + + var process = function (el) { + var id; + if (!el.tagName && el.nodeType === Node.TEXT_NODE) { + id = uid(); + data.map[id] = el.textContent; + return id; + } + if (!el || !el.attributes) { return void console.error(el); } + id = uid(); + data.map[id] = [ + el.tagName, + getAttrs(el), + slice(el.childNodes).map(function (e) { + return process(e); + }).filter(identity) + ]; + return id; + }; + + data.root = process(dom); + return data; + }; + + Flat.toDOM = function (data) { + var visited = {}; + var process = function (key) { + if (!key) { return; } // ignore falsey keys + if (visited[key]) { + // TODO handle this more gracefully. + throw new Error('duplicate id or loop detected'); + } + visited[key] = true; // mark paths as visited. + + var hj = data.map[key]; + if (typeof(hj) === 'string') { return document.createTextNode(hj); } + if (typeof(hj) === 'undefined') { return; } + if (!Array.isArray(hj)) { console.error(hj); throw new Error('expected array'); } + + var e = document.createElement(hj[0]); + for (var x in hj[1]) { e.setAttribute(x, hj[1][x]); } + var child; + for (var i = 0; i < hj[2].length; i++) { + child = process(hj[2][i]); + if (child) { + e.appendChild(child); + } + } + return e; + }; + + return process(data.root); + }; + + return Flat; +}); diff --git a/www/common/sframe-boot2.js b/www/common/sframe-boot2.js index dd0370ca8..9b0f055b3 100644 --- a/www/common/sframe-boot2.js +++ b/www/common/sframe-boot2.js @@ -13,7 +13,8 @@ define(['/common/requireconfig.js'], function (RequireConfig) { var mkFakeStore = function () { var fakeStorage = { getItem: function (k) { return fakeStorage[k]; }, - setItem: function (k, v) { fakeStorage[k] = v; return v; } + setItem: function (k, v) { fakeStorage[k] = v; return v; }, + removeItem: function (k) { delete fakeStorage[k]; } }; return fakeStorage; };