diff --git a/background.html b/background.html index 6e33915..f4670d5 100644 --- a/background.html +++ b/background.html @@ -1,4 +1,6 @@ + + \ No newline at end of file diff --git a/background.js b/background.js index 6953320..c051889 100644 --- a/background.js +++ b/background.js @@ -1,23 +1,31 @@ -chrome.pageAction.onClicked.addListener(function(tab) { - chrome.tabs.executeScript(null, { file: "src/startContentProcessor.js" }); -}); +requirejs.config(requirejsConfig); -// When the extension is installed or upgraded ... -chrome.runtime.onInstalled.addListener(function() { - // Replace all rules ... - chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { - // With a new rule ... - chrome.declarativeContent.onPageChanged.addRules([ - { - // That fires when a page's URL contains a 'g' ... - conditions: [ - new chrome.declarativeContent.PageStateMatcher({ - pageUrl: { hostEquals: 'workflowy.com', schemes: ['https', 'http'] }, - }) - ], - // And shows the extension's page action. - actions: [ new chrome.declarativeContent.ShowPageAction() ] - } - ]); - }); -}); \ No newline at end of file +requirejs( + { baseUrl: chrome.extension.getURL("/") }, + ["src/startContentProcessor"], + function (Processor) { + chrome.pageAction.onClicked.addListener(function(tab) { + Processor(); + }); + + // When the extension is installed or upgraded ... + chrome.runtime.onInstalled.addListener(function() { + // Replace all rules ... + chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { + // With a new rule ... + chrome.declarativeContent.onPageChanged.addRules([ + { + // That fires when a page's URL contains a 'g' ... + conditions: [ + new chrome.declarativeContent.PageStateMatcher({ + pageUrl: { hostEquals: 'workflowy.com', schemes: ['https', 'http'] }, + }) + ], + // And shows the extension's page action. + actions: [ new chrome.declarativeContent.ShowPageAction() ] + } + ]); + }); + }); + } +); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 3f47982..40cecfa 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -10,14 +10,17 @@ module.exports = function(config) { // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine'], + frameworks: ['jasmine', 'requirejs'], // list of files / patterns to load in the browser files: [ - 'lib/*.js', - 'src/*.js', - 'test/*.test.js' + 'lib/jquery-2.1.4.min.js', + 'test/test-main.js', + {pattern: 'src/*.js', included: false}, + {pattern: 'lib/*.js', included: false}, + {pattern: 'test/*.test.js', included: false}, + {pattern: 'test/mock/*.mock.js', included: false} ], diff --git a/lib/require.min.js b/lib/require.min.js new file mode 100644 index 0000000..651902f --- /dev/null +++ b/lib/require.min.js @@ -0,0 +1,37 @@ +/* + RequireJS 2.1.22 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +var requirejs,require,define; +(function(ha){function L(b){return"[object Function]"===R.call(b)}function M(b){return"[object Array]"===R.call(b)}function x(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(L(l)){try{f=h.execCb(c,l,b,f)}catch(d){a=d}this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports: +this.usingExports&&(f=this.exports));if(a){if(this.events.error&&this.map.isDefine||k.onError!==ia)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",B(this.error=a);if("undefined"!==typeof console&&console.error)console.error(a);else k.onError(a)}}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,k.onResourceLoad)){var e=[];x(this.depMaps,function(a){e.push(a.normalizedMap||a)});k.onResourceLoad(h, +this.map,e)}D(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}},callPlugin:function(){var a=this.map,b=a.id,d=q(a.prefix);this.depMaps.push(d);v(d,"defined",y(this,function(f){var l,d,e=g(ga,this.map.id),N=this.map.name,p=this.map.parentMap?this.map.parentMap.name:null,r=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(N=f.normalize(N,function(a){return c(a, +p,!0)})||""),d=q(a.prefix+"!"+N,this.map.parentMap),v(d,"defined",y(this,function(a){this.map.normalizedMap=d;this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),f=g(t,d.id)){this.depMaps.push(d);if(this.events.error)f.on("error",y(this,function(a){this.emit("error",a)}));f.enable()}}else e?(this.map.url=h.nameToUrl(e),this.load()):(l=y(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=y(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b]; +E(t,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&D(a.map.id)});B(a)}),l.fromText=y(this,function(f,c){var d=a.name,e=q(d),N=T;c&&(f=c);N&&(T=!1);u(e);w(m.config,b)&&(m.config[d]=m.config[b]);try{k.exec(f)}catch(g){return B(G("fromtexteval","fromText eval for "+b+" failed: "+g,g,[b]))}N&&(T=!0);this.depMaps.push(e);h.completeLoad(d);r([d],l)}),f.load(a.name,r,l,m))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){aa[this.map.id]=this;this.enabling=this.enabled=!0;x(this.depMaps, +y(this,function(a,b){var c,f;if("string"===typeof a){a=q(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=g(S,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;v(a,"defined",y(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?v(a,"error",y(this,this.errback)):this.events.error&&v(a,"error",y(this,function(a){this.emit("error",a)}))}c=a.id;f=t[c];w(S,c)||!f||f.enabled||h.enable(a,this)}));E(this.pluginMaps,y(this,function(a){var b= +g(t,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:m,contextName:b,registry:t,defined:r,urlFetched:X,defQueue:H,defQueueMap:{},Module:ea,makeModuleMap:q,nextTick:k.nextTick,onError:B,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.shim,c={paths:!0, +bundles:!0,config:!0,map:!0};E(a,function(a,b){c[b]?(m[b]||(m[b]={}),Z(m[b],a,!0,!0)):m[b]=a});a.bundles&&E(a.bundles,function(a,b){x(a,function(a){a!==b&&(ga[a]=b)})});a.shim&&(E(a.shim,function(a,c){M(a)&&(a={deps:a});!a.exports&&!a.init||a.exportsFn||(a.exportsFn=h.makeShimExports(a));b[c]=a}),m.shim=b);a.packages&&x(a.packages,function(a){var b;a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(m.paths[b]=a.location);m.pkgs[b]=a.name+"/"+(a.main||"main").replace(na,"").replace(V,"")});E(t, +function(a,b){a.inited||a.map.unnormalized||(a.map=q(b,null,!0))});(a.deps||a.callback)&&h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ha,arguments));return b||a.exports&&ja(a.exports)}},makeRequire:function(a,n){function e(c,d,g){var m,p;n.enableBuildCallback&&d&&L(d)&&(d.__requireJsBuild=!0);if("string"===typeof c){if(L(d))return B(G("requireargs","Invalid require call"),g);if(a&&w(S,c))return S[c](t[a.id]);if(k.get)return k.get(h, +c,a,e);m=q(c,a,!1,!0);m=m.id;return w(r,m)?r[m]:B(G("notloaded",'Module name "'+m+'" has not been loaded yet for context: '+b+(a?"":". Use require([])")))}Q();h.nextTick(function(){Q();p=u(q(null,a));p.skipMap=n.skipMap;p.init(c,d,g,{enabled:!0});I()});return e}n=n||{};Z(e,{isBrowser:F,toUrl:function(b){var d,e=b.lastIndexOf("."),n=b.split("/")[0];-1!==e&&("."!==n&&".."!==n||1e.attachEvent.toString().indexOf("[native code")||da?(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)):(T=!0,e.attachEvent("onreadystatechange",b.onScriptLoad));e.src=d;Q=e;I?D.insertBefore(e,I):D.appendChild(e);Q=null;return e}if(ka)try{importScripts(d),b.completeLoad(c)}catch(q){b.onError(G("importscripts","importScripts failed for "+ +c+" at "+d,q,[c]))}};F&&!v.skipDataMain&&Y(document.getElementsByTagName("script"),function(b){D||(D=b.parentNode);if(P=b.getAttribute("data-main"))return u=P,v.baseUrl||(J=u.split("/"),u=J.pop(),U=J.length?J.join("/")+"/":"./",v.baseUrl=U),u=u.replace(V,""),k.jsExtRegExp.test(u)&&(u=P),v.deps=v.deps?v.deps.concat(u):[u],!0});define=function(b,c,d){var g,e;"string"!==typeof b&&(d=c,c=b,b=null);M(c)||(d=c,c=null);!c&&L(d)&&(c=[],d.length&&(d.toString().replace(qa,"").replace(ra,function(b,d){c.push(d)}), +c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));T&&(g=Q||pa())&&(b||(b=g.getAttribute("data-requiremodule")),e=K[g.getAttribute("data-requirecontext")]);e?(e.defQueue.push([b,c,d]),e.defQueueMap[b]=!0):W.push([b,c,d])};define.amd={jQuery:!0};k.exec=function(b){return eval(b)};k(v)}})(this); diff --git a/lib/requireConfig.js b/lib/requireConfig.js new file mode 100644 index 0000000..6f816c7 --- /dev/null +++ b/lib/requireConfig.js @@ -0,0 +1,6 @@ +var requirejsConfig = { + baseUrl: '/src', + paths: { + lib: '/lib' + } +}; diff --git a/lib/requireContent.js b/lib/requireContent.js new file mode 100644 index 0000000..f3923d4 --- /dev/null +++ b/lib/requireContent.js @@ -0,0 +1,15 @@ +(function() { + var global = this; + require.load = function (context, moduleName, url) { + var xhr; + xhr = new XMLHttpRequest(); + xhr.open("GET", chrome.extension.getURL(url) + '?r=' + new Date().getTime(), true); + xhr.onreadystatechange = function (e) { + if (xhr.readyState === 4 && xhr.status === 200) { + eval.call(global, xhr.responseText + '\n//@ sourceURL=' + url); + context.completeLoad(moduleName) + } + }; + xhr.send(null); + }; +})(); diff --git a/manifest.json b/manifest.json index a767104..e222bc1 100644 --- a/manifest.json +++ b/manifest.json @@ -22,16 +22,28 @@ "default_title": "Workflowy2Web" }, "icons": { - "48": "icons/48.png", - "128": "icons/128.png" + "48": "icons/48.png", + "128": "icons/128.png" }, "content_scripts": [ { - "matches": [ "https://workflowy.com/*" ], - "js": [ "lib/jquery-2.1.4.min.js", "lib/jszip.min.js", "lib/jszip-utils.min.js", "lib/FileSaver.min.js", "lib/async.min.js", "src/htmlGenerator.js", "src/converter.js", "src/outline.js", "src/utilities.js" ] + "matches": [ "https://workflowy.com/*" ], + "js": [ + "lib/require.min.js", + "lib/jquery-2.1.4.min.js", + "lib/requireContent.js", + "lib/requireConfig.js", + "lib/jszip.min.js", + "lib/jszip-utils.min.js", + "lib/FileSaver.min.js", + "lib/async.min.js", + "background.js" + ] } ], "web_accessible_resources": [ - "resources/*" + "resources/*", + "lib/*", + "src/*" ] } \ No newline at end of file diff --git a/package.json b/package.json index a926520..9fa347f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "karma-jasmine": "^0.3.6", "karma-nyan-reporter": "^0.2.2", "karma-phantomjs-launcher": "^0.2.1", - "phantomjs": "^1.9.19" + "karma-requirejs": "^0.2.2", + "phantomjs": "^1.9.19", + "requirejs": "^2.1.22" } } diff --git a/src/converter.js b/src/converter.js index bde1079..b539fc2 100644 --- a/src/converter.js +++ b/src/converter.js @@ -1,45 +1,57 @@ -var Converter = function (xml, siteTitle) { - this.GetZippedHtmlFiles = function (callback) { - //TODO: Note this creates two nested folders called html which isn't the ideal file structure... - var topNode = xml.find('body'); - // Ignore the top node if there is only one at that level and it matches the site title and it doesn't have a content node - if (topNode.children().length == 1 && - stripText(siteTitle) == stripText(topNode.children().first().attr('text')) && - topNode.children().first().children('[text=Content]').length == 0) { - topNode = topNode.children().first(); - } - var bodyNode = new Outline(topNode, siteTitle, new HtmlGenerator(), '', '', 'html', []); - var htmlPages = bodyNode.process(); +define([ + 'src/htmlGenerator', + 'src/outline', + 'src/utilities', + 'lib/jszip.min', + 'lib/async.min', + 'lib/jszip-utils.min' +], function(HtmlGenerator, Outline, Util, JSZip, async, JSZipUtils) { + return function (xml, siteTitle) { + this.GetZippedHtmlFiles = function (callback) { + //TODO: Note this creates two nested folders called html which isn't the ideal file structure... + var topNode = xml.find('body'); + // Ignore the top node if there is only one at that level and it matches the site title and it doesn't have a content node + if (topNode.children().length == 1 && + Util.stripText(siteTitle) == Util.stripText(topNode.children().first().attr('text')) && + topNode.children().first().children('[text=Content]').length == 0) { + topNode = topNode.children().first(); + } + var bodyNode = new Outline(topNode, siteTitle, new HtmlGenerator(), '', '', 'html', []); + var htmlPages = bodyNode.process(); - var zip = new JSZip(); - htmlPages.forEach(function (page) { - zip.file(page.filePath, page.content); - }); + var zip = new JSZip(); + htmlPages.forEach(function (page) { + zip.file(page.filePath, page.content); + }); - var imageFileNames = ['bar-graph', 'image' ,'line-graph', 'pie-chart', 'quote', 'text', 'video']; - - //Import javascript and stylesheets - async.series([ + var imageFileNames = ['bar-graph', 'image' ,'line-graph', 'pie-chart', 'quote', 'text', 'video']; + + //Import javascript and stylesheets + async.series([ + function (callback) { + addExistingFile(zip, chrome.extension.getURL('resources/stylesheets/style.txt'), 'stylesheets/style.css', callback); + }, function (callback) { - addExistingFile(zip, chrome.extension.getURL('resources/stylesheets/style.css'), 'stylesheets/style.css', callback); - addExistingFile(zip, chrome.extension.getURL('resources/stylesheets/empty.css'), 'stylesheets/custom.css', callback); + addExistingFile(zip, chrome.extension.getURL('resources/stylesheets/empty.txt'), 'stylesheets/custom.css', callback); } ].concat(imageFileNames.map(function(imageFileName) { - return function (callback) { - addExistingFile(zip, chrome.extension.getURL('resources/images/' + imageFileName + '.png'), 'images/' + imageFileName + '.png', callback); - }; - })), function () { - callback(zip.generate({ type: 'blob' })); - }); - }; + return function (callback) { + addExistingFile(zip, chrome.extension.getURL('resources/images/' + imageFileName + '.png'), 'images/' + imageFileName + '.png', callback); + }; + })), + function () { + callback(zip.generate({ type: 'blob' })); + }); + }; - function addExistingFile(zip, filePath, zipPath, callback) { - JSZipUtils.getBinaryContent(filePath, function (err, data) { - if (err) { - console.log('File failed to load', filePath, err); - } - zip.file(zipPath, data); - callback(null, true); - }); - }; -} \ No newline at end of file + function addExistingFile(zip, filePath, zipPath, callback) { + JSZipUtils.getBinaryContent(filePath, function (err, data) { + if (err) { + console.log('File failed to load', filePath, err); + } + zip.file(zipPath, data); + callback(null, true); + }); + } + } +}); \ No newline at end of file diff --git a/src/htmlGenerator.js b/src/htmlGenerator.js index 40b028c..0995e48 100644 --- a/src/htmlGenerator.js +++ b/src/htmlGenerator.js @@ -1,133 +1,137 @@ -var HtmlGenerator = function() { - this.generate = function (node, siteTitle, title, navigationObject, navLevel) { - var doc = docType() + tag('html', head(title, navLevel) + body(node, siteTitle, navigationObject)); - // There must be a better way to convert the character set? - var dashRegex = new RegExp(String.fromCharCode(8211), 'g'); - var spaceRegex = new RegExp(String.fromCharCode(160), 'g'); - return doc.replace(dashRegex, '-').replace(spaceRegex, ' '); - }; +define([ + 'src/utilities' +], function(Util) { + return function() { + this.generate = function (node, siteTitle, title, navigationObject, navLevel) { + var doc = docType() + tag('html', head(title, navLevel) + body(node, siteTitle, navigationObject)); + // There must be a better way to convert the character set? + var dashRegex = new RegExp(String.fromCharCode(8211), 'g'); + var spaceRegex = new RegExp(String.fromCharCode(160), 'g'); + return doc.replace(dashRegex, '-').replace(spaceRegex, ' '); + }; - function docType() { - return ''; - }; + function docType() { + return ''; + } - function tag(tagName, content, attributes) { - attributes = attributes || {}; - var attrString = Object.keys(attributes).map(function (item) { - return (item == 'className' ? 'class' : item) + '="' + attributes[item] + '"'; - }).join(' '); - attrString = attrString ? ' ' + attrString : ''; - return '<' + tagName + attrString + '>' + content + ''; - }; + function tag(tagName, content, attributes) { + attributes = attributes || {}; + var attrString = Object.keys(attributes).map(function (item) { + return (item == 'className' ? 'class' : item) + '="' + attributes[item] + '"'; + }).join(' '); + attrString = attrString ? ' ' + attrString : ''; + return '<' + tagName + attrString + '>' + content + ''; + } - function head(title, navLevel) { - return tag('head', - tag('title', title) + - tag('link', '', { rel: 'stylesheet', href: '../'.repeat(navLevel) + 'stylesheets/style.css' }) + - notesToggleFunction() - ); - }; + function head(title, navLevel) { + return tag('head', + tag('title', title) + + tag('link', '', { rel: 'stylesheet', href: '../'.repeat(navLevel) + 'stylesheets/style.css' }) + + notesToggleFunction() + ); + } + + function body(node, siteTitle, navigationObject) { + return tag('body', + tag('h1', siteTitle) + + navigation(navigationObject) + + pageContent(node) + ); + } - function body(node, siteTitle, navigationObject) { - return tag('body', - tag('h1', siteTitle) + - navigation(navigationObject) + - pageContent(node) - ); - }; + function navigation(navigationObject) { + return navigationObject.map(function(navigationLinks, index) { + return navigationBar(navigationLinks, index == 0); + }).join(''); + } - function navigation(navigationObject) { - return navigationObject.map(function(navigationLinks, index) { - return navigationBar(navigationLinks, index == 0); - }).join(''); - }; + function navigationBar(navigationLinks, mainNav) { + return tag('div', navigationLinks.map(function(link) { + var attributes = { href: link.path }; + if (link.selected) { + attributes.className = 'selected'; + } + return tag('a', link.displayText, attributes); + }).join(''), { className: mainNav ? 'mainNav' : 'localNav' }); + } - function navigationBar(navigationLinks, mainNav) { - return tag('div', navigationLinks.map(function(link) { - var attributes = { href: link.path }; - if (link.selected) { - attributes.className = 'selected'; + function pageContent(node) { + var contentNode = $(node).children('[text=Content]'); + if (contentNode.length == 0) { + return tag('div', getNotes(node).join('\n'), { className: 'contentMissing' }); + } + if (contentNode.children().first().attr('text').match(/##.+##/)) { + var nodeToFind = contentNode.children().first().attr('text').replace(/#/g, ''); + contentNode = $(node).children('[text*="' + nodeToFind + '"]').children('[text=Content]'); } - return tag('a', link.displayText, attributes); - }).join(''), { className: mainNav ? 'mainNav' : 'localNav' }); - }; - function pageContent(node) { - var contentNode = $(node).children('[text=Content]'); - if (contentNode.length == 0) { - return tag('div', getNotes(node).join('\n'), { className: 'contentMissing' }); + return $.makeArray(contentNode.children()).map(function(outline) { + return placeholder(outline, 2); + }).join(''); } - if (contentNode.children().first().attr('text').match(/##.+##/)) { - var nodeToFind = contentNode.children().first().attr('text').replace(/#/g, ''); - contentNode = $(node).children('[text*="' + nodeToFind + '"]').children('[text=Content]'); - } - - return $.makeArray(contentNode.children()).map(function(outline) { - return placeholder(outline, 2); - }).join(''); - }; - function placeholder(node, headingLevel) { - var notesLines = getNotes(node); - var contentBlockLevelClass = headingLevel < 7 ? 'content-block-level-' + headingLevel : 'content-block-text' - var notesClasses = [ 'placeholder', contentBlockLevelClass ]; - $.each(notesLines, function (index, line) { - notesClasses = notesClasses.concat(line.split(' ').filter(function(word) { - return word.indexOf('#') == 0; - }).map(function(className) { - return className.substring(1); - })); - }); - var heading = stripText($(node).attr('text')); - var childContent = $.makeArray($(node).children()).map(function (outline) { - return placeholder(outline, headingLevel + 1); - }).join(''); - var headingTag = headingLevel < 7 ? 'h' + headingLevel : 'span'; - return tag('div', tag(headingTag, heading) + tag('span', notesLines.join('\n'), { className: 'note' }) + childContent, { className: notesClasses.join(' ').toLowerCase() }); - }; + function placeholder(node, headingLevel) { + var notesLines = getNotes(node); + var contentBlockLevelClass = headingLevel < 7 ? 'content-block-level-' + headingLevel : 'content-block-text' + var notesClasses = [ 'placeholder', contentBlockLevelClass ]; + $.each(notesLines, function (index, line) { + notesClasses = notesClasses.concat(line.split(' ').filter(function(word) { + return word.indexOf('#') == 0; + }).map(function(className) { + return className.substring(1); + })); + }); + var heading = Util.stripText($(node).attr('text')); + var childContent = $.makeArray($(node).children()).map(function (outline) { + return placeholder(outline, headingLevel + 1); + }).join(''); + var headingTag = headingLevel < 7 ? 'h' + headingLevel : 'span'; + return tag('div', tag(headingTag, heading) + tag('span', notesLines.join('\n'), { className: 'note' }) + childContent, { className: notesClasses.join(' ').toLowerCase() }); + } - function getNotes(node) { - return stripText($(node).attr('_note')).split('\n').filter(function(line) { - return line.indexOf('~') != 0; - }); - }; + function getNotes(node) { + return Util.stripText($(node).attr('_note')).split('\n').filter(function(line) { + return line.indexOf('~') != 0; + }); + } - function notesToggleFunction() { - return tag('script', - 'window.onload = function() {' + - 'var body = document.body;' + - 'var list = [].slice.call(document.querySelectorAll("a"));' + - 'function getParameterByName(name) {' + + function notesToggleFunction() { + return tag('script', + 'window.onload = function() {' + + 'var body = document.body;' + + 'var list = [].slice.call(document.querySelectorAll("a"));' + + 'function getParameterByName(name) {' + 'name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");' + 'var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),' + - 'results = regex.exec(location.search);' + + 'results = regex.exec(location.search);' + 'return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));' + - '}' + - 'function addNotes() {' + + '}' + + 'function addNotes() {' + 'body.classList.add("notes");' + 'list.forEach(function(link) { link.setAttribute("href", link.getAttribute("href") + "?showNotes=1"); });' + - '}' + - 'function removeNotes() {' + + '}' + + 'function removeNotes() {' + 'body.classList.remove("notes");' + 'list.forEach(function(link) { link.setAttribute("href", link.getAttribute("href").replace("?showNotes=1", "")); });' + - '}' + + '}' + - 'if (location.href.indexOf("?showNotes=1") > -1) {' + + 'if (location.href.indexOf("?showNotes=1") > -1) {' + 'addNotes();' + - '}' + + '}' + - 'document.onkeypress = function(e) {' + + 'document.onkeypress = function(e) {' + 'var spaceKeyCode = 32;' + 'if (e.keyCode == spaceKeyCode && e.shiftKey) {' + - 'if (body.classList.contains("notes")) {' + - 'removeNotes();' + - '} else {' + - 'addNotes();' + - '}' + + 'if (body.classList.contains("notes")) {' + + 'removeNotes();' + + '} else {' + + 'addNotes();' + + '}' + '}' + 'return false;' + - '};' + - '}' - ); - }; -}; \ No newline at end of file + '};' + + '}' + ); + } + } +}); diff --git a/src/outline.js b/src/outline.js index 18f4ea6..eaf1f14 100644 --- a/src/outline.js +++ b/src/outline.js @@ -1,59 +1,65 @@ -var Outline = function (node, siteTitle, generator, title, fileName, filePath, parentNavigationObject) { - var navigationObject = parentNavigationObject.map(function (navBar) { - return navBar.map(function (link) { - return { - displayText: link.displayText, - path: '../' + link.path, - selected: link.selected || link.displayText == title - }; +define([ + 'src/utilities' +], function(Util) { + var Outline = function (node, siteTitle, generator, title, fileName, filePath, parentNavigationObject) { + var navigationObject = parentNavigationObject.map(function (navBar) { + return navBar.map(function (link) { + return { + displayText: link.displayText, + path: '../' + link.path, + selected: link.selected || link.displayText == title + }; + }); }); - }); - var htmlPages = []; - var navLevel = parentNavigationObject.length; + var htmlPages = []; + var navLevel = parentNavigationObject.length; + + this.process = function () { + var childPages = getChildPages(); + updateNavigationObject(childPages); + if (Util.isPage(title)) { + htmlPages.push({ + filePath: filePath + '/' + fileName + '.html', + content: generator.generate(node, siteTitle, title, navigationObject, navLevel) + }); + } + processChildren(childPages); + return htmlPages; + }; - this.process = function () { - var childPages = getChildPages(); - updateNavigationObject(childPages); - if (isPage(title)) { - htmlPages.push({ - filePath: filePath + '/' + fileName + '.html', - content: generator.generate(node, siteTitle, title, navigationObject, navLevel) + function getChildPages() { + var pages = []; + $.each($(node).children(), function (index, child) { + var childTitle = Util.stripText($(child).attr('text')); + if (Util.isPage(childTitle)) { + if (childTitle[0] == '[') { + $.each(childTitle.substring(1, childTitle.length - 1).split(','), function(index, subTitle) { + pages.push({ title: subTitle, fileName: Util.stripText(subTitle, true).toLowerCase(), node: child }); + }); + } else { + pages.push({ title: childTitle, fileName: Util.stripText(childTitle, true).toLowerCase(), node: child }); + } + } }); + return pages; } - processChildren(childPages); - return htmlPages; - }; - function getChildPages() { - var pages = []; - $.each($(node).children(), function (index, child) { - var childTitle = stripText($(child).attr('text')); - if (isPage(childTitle)) { - if (childTitle[0] == '[') { - $.each(childTitle.substring(1, childTitle.length - 1).split(','), function(index, subTitle) { - pages.push({ title: subTitle, fileName: stripText(subTitle, true).toLowerCase(), node: child }); - }); - } else { - pages.push({ title: childTitle, fileName: stripText(childTitle, true).toLowerCase(), node: child }); - } + function updateNavigationObject(childPages) { + if (childPages.length > 0) { + navigationObject.push(childPages.map(function (childPage) { + return { displayText: childPage.title, path: (fileName || filePath) + '/' + childPage.fileName + '.html', selected: false }; + })); } - }); - return pages; - }; + } - function updateNavigationObject(childPages) { - if (childPages.length > 0) { - navigationObject.push(childPages.map(function (childPage) { - return { displayText: childPage.title, path: (fileName || filePath) + '/' + childPage.fileName + '.html', selected: false }; - })); + function processChildren(childPages) { + $.each(childPages, function (index, childPage) { + var path = fileName ? filePath + '/' + fileName : filePath; + var outline = new Outline(childPage.node, siteTitle, generator, childPage.title, childPage.fileName, path, navigationObject); + htmlPages = htmlPages.concat(outline.process()); + }); } }; - function processChildren(childPages) { - $.each(childPages, function (index, childPage) { - var path = fileName ? filePath + '/' + fileName : filePath; - var outline = new Outline(childPage.node, siteTitle, generator, childPage.title, childPage.fileName, path, navigationObject); - htmlPages = htmlPages.concat(outline.process()); - }); - }; -}; \ No newline at end of file + return Outline; +}); \ No newline at end of file diff --git a/src/startContentProcessor.js b/src/startContentProcessor.js index 73bf102..9833ff6 100644 --- a/src/startContentProcessor.js +++ b/src/startContentProcessor.js @@ -1,41 +1,46 @@ -// Dependent on DOM structure and javascript of workflowy -// exportIt is a custom workflowy function so can only be called in the page context - - -var opml = extractOpmlData(); -var parsedOpml = $.parseXML(opml); -var xml = $(parsedOpml); - -var siteTitle = $('.project.selected > .name').text(); - -var converter = new Converter(xml, siteTitle); -converter.GetZippedHtmlFiles(function (data) { - saveAs(data, "Prototype.zip"); +define([ + 'lib/jquery-2.1.4.min', + 'src/converter' +], function ($, Converter) { + return function () { + // Dependent on DOM structure and javascript of workflowy + // exportIt is a custom workflowy function so can only be called in the page context + var opml = extractOpmlData(); + var parsedOpml = $.parseXML(opml); + var xml = $(parsedOpml); + + var siteTitle = $('.project.selected > .name').text(); + + var converter = new Converter(xml, siteTitle); + converter.GetZippedHtmlFiles(function (data) { + saveAs(data, "Prototype.zip"); + }); + + function extractOpmlData() { + openExportPopup(); + switchToOpmlTab(); + var opml = getWorkflowyAsXml(); + closeExportPopup(); + return opml; + } + + function switchToOpmlTab() { + $('#id_opml').click() + } + + function getWorkflowyAsXml() { + return $('#exportPopup').find('.previewWindow.hasOpml').text(); + } + + function openExportPopup() { + var script = document.createElement('script'); + var code = document.createTextNode('(function() {$(".project.selected").exportIt();})();'); + script.appendChild(code); + document.head.appendChild(script); + } + + function closeExportPopup() { + $('#exportPopup').parent().find('.ui-icon-closethick').click(); + } + } }); - -function extractOpmlData() { - openExportPopup(); - switchToOpmlTab(); - var opml = getWorkflowyAsXml(); - closeExportPopup() - return opml; -} - -function switchToOpmlTab() { - $('#id_opml').click() -} - -function getWorkflowyAsXml() { - return $('#exportPopup').find('.previewWindow.hasOpml').text(); -} - -function openExportPopup() { - var script = document.createElement('script'); - var code = document.createTextNode('(function() {$(".project.selected").exportIt();})();'); - script.appendChild(code); - document.head.appendChild(script); -} - -function closeExportPopup() { - $('#exportPopup').parent().find('.ui-icon-closethick').click(); -} \ No newline at end of file diff --git a/src/utilities.js b/src/utilities.js index b21f18f..ab84f22 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -1,17 +1,24 @@ -function stripText(text, isFileName) { - if (!text) { - return ''; - } - var stringsToRemove = [//g, /<\/b>/g, //g, /<\/i>/g]; - stringsToRemove.forEach(function (stringToRemove) { - text = text.replace(stringToRemove, ''); - }); - if (isFileName) { - text = text.replace(/[^a-zA-Z0-9]+/g, ""); - } - return text.trim(); -}; - -function isPage(title) { - return title && title.toLowerCase() != "content" && title.indexOf("~") != 0; -}; \ No newline at end of file +define([], function() { + var stripText = function (text, isFileName) { + if (!text) { + return ''; + } + var stringsToRemove = [//g, /<\/b>/g, //g, /<\/i>/g]; + stringsToRemove.forEach(function (stringToRemove) { + text = text.replace(stringToRemove, ''); + }); + if (isFileName) { + text = text.replace(/[^a-zA-Z0-9]+/g, ""); + } + return text.trim(); + }; + + var isPage = function (title) { + return title && title.toLowerCase() != "content" && title.indexOf("~") != 0; + }; + + return { + isPage: isPage, + stripText: stripText + }; +}); \ No newline at end of file diff --git a/test/converter.test.js b/test/converter.test.js index 67cc9df..5a5514d 100644 --- a/test/converter.test.js +++ b/test/converter.test.js @@ -1,64 +1,47 @@ -var async = { - series: function(arr, cb) { - cb(); - } -}; -describe('converter', function() { - // Define mocks - var converter; - var mockZip = { - file: function() {}, - generate: function() { - return 'generatedZip'; - } - }; - var mockOutline = { - process: function() { - return [ - { - filePath: 'path1', - content: 'content1' - }, - { - filePath: 'path2', - content: 'content2' - } - ]; - } - }; - beforeEach(function() { - converter = new Converter($($.parseXML('')), 'The Title'); - spyOn(mockZip, 'file'); - spyOn(mockZip, 'generate').and.callThrough(); - spyOn(async, 'series').and.callThrough(); - spyOn(window, 'Outline').and.returnValue(mockOutline); - spyOn(window, 'JSZip').and.returnValue(mockZip); - }); +define([ + 'src/converter', + 'test/mock/jszip.mock', + 'test/mock/async.mock' +], function(Converter, mockZip, async) { - describe('GetZippedHtmlFiles', function() { - it('adds pages to the zip file for each page returned from the outline', function(done) { - converter.GetZippedHtmlFiles(function() { - expect(mockZip.file.calls.count()).toEqual(2); - expect(mockZip.file).toHaveBeenCalledWith('path1', 'content1'); - expect(mockZip.file).toHaveBeenCalledWith('path2', 'content2'); - done(); - }); + describe('converter', function() { + var converter; + var spies = new mockZip().spies; + + beforeEach(function() { + converter = new Converter($($.parseXML('')), 'The Title'); + spyOn(async, 'series').and.callThrough(); }); - - it('adds static files to the zip', function(done) { - converter.GetZippedHtmlFiles(function() { - expect(async.series.calls.count()).toEqual(1); - expect(async.series.calls.mostRecent().args[0].length).toEqual(8); - done(); - }); + + afterEach(function() { + spies.reset(); }); - - it('calls callback function with generated zip file', function(done) { - converter.GetZippedHtmlFiles(function(generatedZip) { - expect(mockZip.generate.calls.count()).toEqual(1); - expect(mockZip.generate.calls.mostRecent().args[0].type).toEqual('blob'); - expect(generatedZip).toEqual('generatedZip'); - done(); + + describe('GetZippedHtmlFiles', function() { + it('adds pages to the zip file for each page returned from the outline', function(done) { + converter.GetZippedHtmlFiles(function() { + expect(spies.file.count).toEqual(2); + expect(spies.file.args[0]).toEqual(['path1', 'content1']); + expect(spies.file.args[1]).toEqual(['path2', 'content2']); + done(); + }); + }); + + it('adds static files to the zip', function(done) { + converter.GetZippedHtmlFiles(function() { + expect(async.series.calls.count()).toEqual(1); + expect(async.series.calls.mostRecent().args[0].length).toEqual(9); + done(); + }); + }); + + it('calls callback function with generated zip file', function(done) { + converter.GetZippedHtmlFiles(function(generatedZip) { + expect(spies.generate.count).toEqual(1); + expect(spies.generate.lastArg().type).toEqual('blob'); + expect(generatedZip).toEqual('generatedZip'); + done(); + }); }); }); }); diff --git a/test/htmlGenerator.test.js b/test/htmlGenerator.test.js index 2f895cd..4b66f1f 100644 --- a/test/htmlGenerator.test.js +++ b/test/htmlGenerator.test.js @@ -1,52 +1,56 @@ -describe('outline', function() { - var generator = new HtmlGenerator(); - var opml = '' + -'' + - '' + - 'sat@softwire.com' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + +define([ + 'src/htmlGenerator' +], function(HtmlGenerator) { + describe('outline', function() { + var generator = new HtmlGenerator(); + var opml = '' + + '' + + '' + + 'sat@softwire.com' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' + - '' + - '' + - '' + - '' + -''; - var navObject = [[ - { - displayText: 'Home', - path: '../../home.html', - selected: true - }, - { - displayText: 'Section', - path: '../../section.html', - selected: false - } - ],[ - { - displayText: 'Top', - path: '../top.html', - selected: true - }, - { - displayText: 'Footer - tag', - path: '../footertag.html', - selected: false - } - ]]; - var expectedHtml = 'Footer - tag

Site Title

Something - or other

TV – Quarterly Reach / Share / Av Time

'; + '' + + '
' + + '' + + '' + + '
'; + var navObject = [[ + { + displayText: 'Home', + path: '../../home.html', + selected: true + }, + { + displayText: 'Section', + path: '../../section.html', + selected: false + } + ],[ + { + displayText: 'Top', + path: '../top.html', + selected: true + }, + { + displayText: 'Footer - tag', + path: '../footertag.html', + selected: false + } + ]]; + var expectedHtml = 'Footer - tag

Site Title

Something - or other

TV � Quarterly Reach / Share / Av Time

'; - describe('generate', function() { - it('returns the correct HTML', function() { - var result = generator.generate($($($.parseXML(opml)).find('body').children()[1]), - 'Site Title', 'Footer - tag', navObject, 2); - expect(result).toEqual(expectedHtml); + describe('generate', function() { + it('returns the correct HTML', function() { + var result = generator.generate($($($.parseXML(opml)).find('body').children()[1]), + 'Site Title', 'Footer - tag', navObject, 2); + expect(result).toEqual(expectedHtml); + }); }); }); }); \ No newline at end of file diff --git a/test/mock/async.mock.js b/test/mock/async.mock.js new file mode 100644 index 0000000..ce1c540 --- /dev/null +++ b/test/mock/async.mock.js @@ -0,0 +1,7 @@ +define([], function () { + return { + series: function (arr, cb) { + cb(); + } + }; +}); \ No newline at end of file diff --git a/test/mock/jszip.mock.js b/test/mock/jszip.mock.js new file mode 100644 index 0000000..732cd8c --- /dev/null +++ b/test/mock/jszip.mock.js @@ -0,0 +1,35 @@ +define([], function () { + var Spy = function () { + return { + count: 0, + args: [], + lastArg: function () { + if (this.args.length === 0) { + return null; + } else { + return this.args[this.args.length - 1]; + } + } + }; + }; + var spies = { + file: new Spy(), + generate: new Spy(), + reset: function () { + this.file = new Spy(); + this.generate = new Spy(); + } + }; + return function () { + this.file = function (arg1, arg2) { + spies.file.count++; + spies.file.args.push([arg1, arg2]); + }; + this.generate = function (arg) { + spies.generate.count++; + spies.generate.args.push(arg); + return 'generatedZip'; + }; + this.spies = spies; + }; +}); \ No newline at end of file diff --git a/test/mock/outline.mock.js b/test/mock/outline.mock.js new file mode 100644 index 0000000..afc4ec7 --- /dev/null +++ b/test/mock/outline.mock.js @@ -0,0 +1,16 @@ +define([], function () { + return function () { + this.process = function () { + return [ + { + filePath: 'path1', + content: 'content1' + }, + { + filePath: 'path2', + content: 'content2' + } + ]; + } + }; +}); \ No newline at end of file diff --git a/test/outline.test.js b/test/outline.test.js index e0e0a68..5cae3f0 100644 --- a/test/outline.test.js +++ b/test/outline.test.js @@ -1,87 +1,92 @@ -describe('outline', function() { - var outline; +define([ + 'src/outline' +], function(Outline) { - beforeEach(function() { - var opml = '' + -'' + - '' + - 'sat@softwire.com' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + -''; - var generator = { - generate: function(node, siteTitle, title, navigationObject, navLevel) { - return arguments; - } - }; - outline = new Outline( - $($.parseXML(opml)).find('body'), - 'Site Title', - generator, - 'Page Title', - 'pageTitle', - '/path/to', - [[ - { - displayText: 'Home', - path: 'path/to/home.html', - selected: true - }, - { - displayText: 'Section', - path: 'path/to/section.html', - selected: false - } - ]]); - }); + describe('outline', function () { + var outline; - describe('process', function() { - it('returns a list of html pages with structured file paths', function() { - var result = outline.process(); - expect(result.length).toBe(4); - expect(result[0].filePath).toEqual('/path/to/pageTitle.html'); - expect(result[1].filePath).toEqual('/path/to/pageTitle/top.html'); - expect(result[2].filePath).toEqual('/path/to/pageTitle/footertag.html'); - expect(result[3].filePath).toEqual('/path/to/pageTitle/footertag/print.html'); + beforeEach(function () { + var opml = '' + + '' + + '' + + 'sat@softwire.com' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + var generator = { + generate: function (node, siteTitle, title, navigationObject, navLevel) { + return arguments; + } + }; + outline = new Outline( + $($.parseXML(opml)).find('body'), + 'Site Title', + generator, + 'Page Title', + 'pageTitle', + '/path/to', + [[ + { + displayText: 'Home', + path: 'path/to/home.html', + selected: true + }, + { + displayText: 'Section', + path: 'path/to/section.html', + selected: false + } + ]]); }); - it('calls the generator with the correct title for each page', function() { - var result = outline.process(); - expect(result[0].content[2]).toEqual('Page Title'); - expect(result[1].content[2]).toEqual('Top'); - expect(result[2].content[2]).toEqual('Footer - tag'); - expect(result[3].content[2]).toEqual('Print'); - }); - - it('calls the generator with the navigation object for the page', function() { - var result = outline.process(); - expect(result[0].content[3].length).toEqual(2); - expect(result[3].content[3][2][0].path).toEqual('../footertag/print.html'); - }); - - it('sets the selected state of parent pages in the navigation object', function() { - var result = outline.process(); - expect(result[3].content[3][0][0].selected).toEqual(true); - expect(result[3].content[3][1][1].selected).toEqual(true); - expect(result[3].content[3][2][0].selected).toEqual(true); - }); - - it('calls the generator with the navigation level of the page', function() { - var result = outline.process(); - expect(result[0].content[4]).toEqual(1); - expect(result[1].content[4]).toEqual(2); - expect(result[2].content[4]).toEqual(2); - expect(result[3].content[4]).toEqual(3); + describe('process', function () { + it('returns a list of html pages with structured file paths', function () { + var result = outline.process(); + expect(result.length).toBe(4); + expect(result[0].filePath).toEqual('/path/to/pageTitle.html'); + expect(result[1].filePath).toEqual('/path/to/pageTitle/top.html'); + expect(result[2].filePath).toEqual('/path/to/pageTitle/footertag.html'); + expect(result[3].filePath).toEqual('/path/to/pageTitle/footertag/print.html'); + }); + + it('calls the generator with the correct title for each page', function () { + var result = outline.process(); + expect(result[0].content[2]).toEqual('Page Title'); + expect(result[1].content[2]).toEqual('Top'); + expect(result[2].content[2]).toEqual('Footer - tag'); + expect(result[3].content[2]).toEqual('Print'); + }); + + it('calls the generator with the navigation object for the page', function () { + var result = outline.process(); + expect(result[0].content[3].length).toEqual(2); + expect(result[3].content[3][2][0].path).toEqual('../footertag/print.html'); + }); + + it('sets the selected state of parent pages in the navigation object', function () { + var result = outline.process(); + expect(result[3].content[3][0][0].selected).toEqual(true); + expect(result[3].content[3][1][1].selected).toEqual(true); + expect(result[3].content[3][2][0].selected).toEqual(true); + }); + + it('calls the generator with the navigation level of the page', function () { + var result = outline.process(); + expect(result[0].content[4]).toEqual(1); + expect(result[1].content[4]).toEqual(2); + expect(result[2].content[4]).toEqual(2); + expect(result[3].content[4]).toEqual(3); + }); }); }); }); \ No newline at end of file diff --git a/test/test-main.js b/test/test-main.js new file mode 100644 index 0000000..0134f6a --- /dev/null +++ b/test/test-main.js @@ -0,0 +1,38 @@ +var tests = []; +for (var file in window.__karma__.files) { + if (window.__karma__.files.hasOwnProperty(file)) { + if (/\.test\.js$/.test(file)) { + tests.push(file); + } + } +} +window.chrome = { + extension: { + getURL: function (url, callback) { + } + } +}; + +requirejs.config({ + // Karma serves files from '/base' + baseUrl: '/base', + + paths: { + 'lib': '/base/lib' + }, + + map: { + 'src/converter': { + 'src/outline': 'test/mock/outline.mock', + 'lib/jszip.min': 'test/mock/jszip.mock', + 'lib/async.min': 'test/mock/async.mock' + } + }, + + // ask Require.js to load these files (all our tests) + deps: tests, + + // start test run, once Require.js is done + callback: window.__karma__.start +}); + diff --git a/test/utilities.test.js b/test/utilities.test.js index c6da8d3..bd31e54 100644 --- a/test/utilities.test.js +++ b/test/utilities.test.js @@ -1,38 +1,42 @@ -describe('utilities', function() { - describe('stripText', function() { - it('returns an empty string if no string is provided', function() { - expect(stripText(null)).toBe(''); - expect(stripText()).toBe(''); +define([ + 'src/utilities' +], function(Util) { + describe('utilities', function () { + describe('stripText', function () { + it('returns an empty string if no string is provided', function () { + expect(Util.stripText(null)).toBe(''); + expect(Util.stripText()).toBe(''); + }); + it('removes bold tags', function () { + expect(Util.stripText('Something important here')).toBe('Something important here'); + }); + it('removes italic tags', function () { + expect(Util.stripText('Something important here')).toBe('Something important here'); + }); + it('trims excess spaces', function () { + expect(Util.stripText('Something important ')).toBe('Something important'); + }); + it('removes removes all spaces from file names', function () { + expect(Util.stripText('File name', true)).toBe('Filename'); + }); + it('removes removes all non alpha-numeric characters from file names', function () { + expect(Util.stripText('[]#~:;File"�$%^&* name!()-+@{}', true)).toBe('Filename'); + }); }); - it('removes bold tags', function() { - expect(stripText('Something important here')).toBe('Something important here'); - }); - it('removes italic tags', function() { - expect(stripText('Something important here')).toBe('Something important here'); - }); - it('trims excess spaces', function() { - expect(stripText('Something important ')).toBe('Something important'); - }); - it('removes removes all spaces from file names', function() { - expect(stripText('File name', true)).toBe('Filename'); - }); - it('removes removes all non alpha-numeric characters from file names', function() { - expect(stripText('[]#~:;File"£$%^&* name!()-+@{}', true)).toBe('Filename'); - }); - }); - describe('isPage', function() { - it('returns false if title is empty', function() { - expect(isPage('')).toBe(''); - }); - it('returns false if title is Content', function() { - expect(isPage('Content')).toBe(false); - }); - it('returns false if title starts with ~', function() { - expect(isPage('~Something')).toBe(false); - }); - it('returns true otherwise', function() { - expect(isPage('Something~')).toBe(true); + describe('isPage', function () { + it('returns false if title is empty', function () { + expect(Util.isPage('')).toBe(''); + }); + it('returns false if title is Content', function () { + expect(Util.isPage('Content')).toBe(false); + }); + it('returns false if title starts with ~', function () { + expect(Util.isPage('~Something')).toBe(false); + }); + it('returns true otherwise', function () { + expect(Util.isPage('Something~')).toBe(true); + }); }); }); }); \ No newline at end of file