Revisions
-
johan revised this gist
Dec 17, 2012 . 3 changed files with 0 additions and 272 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,239 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,32 +0,0 @@ -
johan revised this gist
Nov 29, 2012 . 2 changed files with 25 additions and 21 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,6 @@ section = ((describe) -> (name, test) -> describe "#{name}\t", test)(describe) section 'on()', -> fn = `on` it 'should throw an error on no input', -> @@ -21,7 +23,7 @@ describe 'on()', -> section 'on.dom(dom spec – see below for the three types of dom spec)', -> it 'should be a function after the first on() call', -> try `on()` catch e then err = e expect(err).toNotBe undefined @@ -34,16 +36,16 @@ describe 'on.dom(dom spec – see below for the three types of dom spec)', -> section 'on.dom(dom spec type 1: a selector string)', -> root = document.documentElement assertion = it section 'on.dom("css… selection"): Array/Node, optional/not?', -> section 'Array of Node:s (0+ occurrences):', -> assertion 'on.dom("css* NotFound") => []', -> expect(`on`.dom('css* NotFound')).toEqual [] assertion 'on.dom("css* html") => [html]', -> expect(`on`.dom('css* html')).toEqual [root] assertion 'on.dom("css* *") => document.all (but as a proper Array)', -> @@ -52,23 +54,23 @@ describe 'on.dom(dom spec type 1: a selector string)', -> expect(what).toEqual dall section 'Array of Node:s (1+ occurrences):', -> assertion 'on.dom("css+ html") => [html]', -> expect(`on`.dom('css+ html')).toEqual [root] assertion 'on.dom("css+ NotFound") => undefined', -> expect(`on`.dom('css+ NotFound')).toBe undefined section 'single optional Node, or null if not found:', -> assertion 'on.dom("css? *") => root element (= first match)', -> expect(`on`.dom('css? *')).toBe root assertion 'on.dom("css? NotFound") => null (not found)', -> expect(`on`.dom('css? NotFound')).toBe null section 'single mandatory Node:', -> assertion 'on.dom("css *") => the root element', -> expect(`on`.dom('css *')).toBe root @@ -77,32 +79,32 @@ describe 'on.dom(dom spec type 1: a selector string)', -> section 'on.dom("xpath… selection"): Array/Node, optional/not?', -> section 'xpath* => Array of Node:s (0+ occurrences):', -> assertion 'on.dom("xpath* /*") => [root element]', -> expect(`on`.dom('xpath* /*')).toEqual [root] assertion 'on.dom("xpath* /NotFound") => []', -> expect(`on`.dom('xpath* /NotFound')).toEqual [] section 'xpath+ => Array of Node:s (1+ occurrences):', -> assertion 'on.dom("xpath+ /*") => [root element]', -> expect(`on`.dom('xpath+ /*')).toEqual [root] assertion 'on.dom("xpath+ /NotFound") => undefined', -> expect(`on`.dom('xpath+ /NotFound')).toBe undefined section 'xpath? => single optional Node, or null if missing:', -> assertion 'on.dom("xpath? /NotFound") => null', -> expect(`on`.dom('xpath? /NotFound')).toBe null assertion 'on.dom("xpath? /*") => the root element', -> expect(`on`.dom('xpath? /*')).toBe root section 'xpath => single mandatory Node:', -> assertion 'on.dom("xpath /*") => the root element', -> expect(`on`.dom('xpath /*')).toBe root @@ -113,7 +115,7 @@ describe 'on.dom(dom spec type 1: a selector string)', -> expect(`on`.dom('xpath .')).toBe document section '…or queries yielding Number/String/Boolean answers:', -> assertion 'on.dom("xpath count(/)") => 1', -> expect(`on`.dom('xpath count(/)')).toBe 1 @@ -133,7 +135,7 @@ describe 'on.dom(dom spec type 1: a selector string)', -> expect(`on`.dom('xpath name(/*) = \'nope\'')).toBe false section 'xpath! makes assertions, requiring truthy answers:', -> assertion 'on.dom("xpath! count(/)") => 1', -> expect(`on`.dom('xpath count(/)')).toBe 1 @@ -154,7 +156,7 @@ describe 'on.dom(dom spec type 1: a selector string)', -> section 'on.dom(dom spec type 2: an object showing the structure you want)', -> html = document.documentElement head = document.querySelector 'head' try `on()` # ensures there's an on.dom to call @@ -213,7 +215,7 @@ describe 'on.dom(dom spec type 2: an object showing the structure you want)', -> section 'on.dom(dom spec type 3: [context_spec, per_match_spec])', -> html = document.documentElement head = document.querySelector 'head' try `on()` # ensures there's an on.dom to call @@ -245,7 +247,7 @@ describe 'on.dom(dom spec type 3: [context_spec, per_match_spec])', -> expect(`on`.dom([[head, html], "xpath ."])).toEqual [head, html] section 'on.dom plugins:', -> html = document.documentElement assertion = it fn = `on` This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,6 @@ section = ((describe) -> (name, test) -> describe "#{name}\t", test)(describe) section 'on.query', -> q_was = location.search query = (q) -> if location.search isnt q -
johan revised this gist
Nov 25, 2012 . 2 changed files with 46 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -103,7 +103,7 @@ */ function on(opts, plugins) { var Object_toString = Object.prototype.toString , Array_slice = Array.prototype.slice , FAIL = 'dom' in on ? undefined : (function() { @@ -145,8 +145,7 @@ function on(opts) { , load = get('load') , pushState = get('pushstate') , pjax_event = get('pjaxevent') , name, rule, test, result, retry, plugin ; if (typeof ready !== 'function' && @@ -156,6 +155,15 @@ function on(opts) { throw new Error('on() needs at least a "ready" or "load" function!'); } if (plugins) for (name in plugins) if ((rule = plugins[name]) && (test = on[name])) for (plugin in rule) if (!(test[plugin])) { on._parse_dom_rule = null; test[plugin] = rule[plugin]; } if (pushState && history.pushState && (on.pushState = on.pushState || []).indexOf(opts) === -1) { on.pushState.push(opts); // make sure we don't re-register after navigation @@ -318,9 +326,9 @@ function on(opts) { // fall-through default: throw new Error('non-String dom match rule: '+ rule); } if (!on._parse_dom_rule) on._parse_dom_rule = new RegExp('^(' + Object.keys(on.dom).map(quoteRe).join('|') + ')\\s*(.*)'); var match = on._parse_dom_rule.exec(rule), type, func; if (match) { type = match[1]; rule = match[2]; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -243,3 +243,36 @@ describe 'on.dom(dom spec type 3: [context_spec, per_match_spec])', -> assertion 'on.dom([[head, html], "xpath ."]) => [head, html]', -> expect(`on`.dom([[head, html], "xpath ."])).toEqual [head, html] describe 'on.dom plugins:', -> html = document.documentElement assertion = it fn = `on` assertion 'on( { dom: "my_plugin", ready: ready = (x) -> }\n' + ' , { dom: "my_plugin": -> document.body }\n' + ' ) => ready(document.body)', -> fn( { dom: "my_plugin", ready: ready = jasmine.createSpy 'ready' } , { dom: "my_plugin": -> document.body } ) expect(ready).toHaveBeenCalledWith(document.body) assertion 'on.dom(["my_plugin", "xpath ."]) => body', -> expect(`on`.dom(["my_plugin", "xpath ."])).toBe document.body assertion 'on.dom(["my_plugin", "xpath .."]) => html', -> expect(`on`.dom(["my_plugin", "xpath .."])).toBe html assertion 'on.dom("xpath .") => document', -> expect(`on`.dom("xpath .")).toBe document ### assertion 'on( { dom: ["my_plugin", "xpath ."], ready: ready = (x) -> }\n' + ' , { dom: my_plugin: -> document.body })\n' + '=> ready(body)', -> ready = jasmine.createSpy 'ready' fn( { dom: ["my_plugin", "xpath ."], ready: ready } , { dom: my_plugin: -> document.body }) expect(ready).toHaveBeenCalledWith(document.body) ### -
johan revised this gist
Nov 25, 2012 . 2 changed files with 12 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -312,8 +312,12 @@ function on(opts) { // returns null if it didn't find optional matches at this level // returns Node or an Array of nodes, or a basic type from some XPath query function lookup(rule) { switch (typeof rule) { case 'string': break; // main case - rest of function case 'object': if ('nodeType' in rule || rule.length) return rule; // fall-through default: throw new Error('non-String dom match rule: '+ rule); } if (!parse_dom_rule) parse_dom_rule = new RegExp('^(' + Object.keys(on.dom).map(quoteRe).join('|') + ')\\s*(.*)'); var match = parse_dom_rule.exec(rule), type, func; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -237,3 +237,9 @@ describe 'on.dom(dom spec type 3: [context_spec, per_match_spec])', -> assertion 'on.dom(["xpath /svg", "css* *"]) => undefined (not an svg doc)', -> expect(`on`.dom(["xpath /svg", "css* *"])).toBe undefined assertion 'on.dom([html, "xpath ."]) => html', -> expect(`on`.dom([html, "xpath ."])).toBe html assertion 'on.dom([[head, html], "xpath ."]) => [head, html]', -> expect(`on`.dom([[head, html], "xpath ."])).toEqual [head, html] -
johan revised this gist
Nov 25, 2012 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -288,9 +288,9 @@ function on(opts) { var doc = this.evaluate ? this : this.ownerDocument, next; var got = doc.evaluate(xpath, this, null, 0, null), all = []; switch (got.resultType) { case 1/*XPathResult.NUMBER_TYPE*/: return got.numberValue; case 2/*XPathResult.STRING_TYPE*/: return got.stringValue; case 3/*XPathResult.BOOLEAN_TYPE*/: return got.booleanValue; default: while ((next = got.iterateNext())) all.push(next); return all; } } -
johan revised this gist
Nov 25, 2012 . 1 changed file with 8 additions and 8 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -48,7 +48,7 @@ describe 'on.dom(dom spec type 1: a selector string)', -> assertion 'on.dom("css* *") => document.all (but as a proper Array)', -> what = `on`.dom("css* *") dall = [].slice.call document.getElementsByTagName('*'), 0 expect(what).toEqual dall @@ -120,14 +120,14 @@ describe 'on.dom(dom spec type 1: a selector string)', -> assertion 'on.dom("xpath count(/NotFound)") => 0', -> expect(`on`.dom('xpath count(/NotFound)')).toBe 0 assertion 'on.dom("xpath name(/*)") => "html" or "HTML"', -> expect(`on`.dom('xpath name(/*)')).toMatch /^(html|HTML)$/ assertion 'on.dom("xpath name(/)") => ""', -> expect(`on`.dom('xpath name(/)')).toBe '' assertion 'on.dom("xpath count(/*) = 1") => true', -> expect(`on`.dom('xpath count(/*) = 1')).toBe true assertion 'on.dom("xpath name(/*) = \'nope\'") => false', -> expect(`on`.dom('xpath name(/*) = \'nope\'')).toBe false @@ -141,13 +141,13 @@ describe 'on.dom(dom spec type 1: a selector string)', -> expect(`on`.dom('xpath! count(/NotFound)')).toBe undefined assertion 'on.dom("xpath! name(/*)") => "html"', -> expect(`on`.dom('xpath! name(/*)')).toMatch /^(html|HTML)$/ assertion 'on.dom("xpath! name(/)") => undefined', -> expect(`on`.dom('xpath! name(/)')).toBe undefined assertion 'on.dom("xpath! count(/*) = 1") => true', -> expect(`on`.dom('xpath! count(/*) = 1')).toBe true assertion 'on.dom("xpath! name(/*) = \'nope\'") => undefined', -> expect(`on`.dom('xpath! name(/*) = \'nope\'')).toBe undefined -
johan revised this gist
Nov 25, 2012 . 1 changed file with 9 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -60,7 +60,7 @@ about what this buys you The DOM API:s are not only verbose, but also cripple results with bad types! From `on.js` array selectors, you get a real `Array` that you can operate on with `.forEach`, `.map`, `.some`, `.every`, `.filter`, `.reduce`, `.slice`, `.indexOf`, and all the other really awesome @@ -71,7 +71,8 @@ about what this buys you As a script's complexity grows, so does the burden of maintaining it. With `on.js`, this is still true – but it grows far slower! Seeing (and naming) the structure of the input your code operates on helps reading and writing the code @@ -81,7 +82,8 @@ about what this buys you Maybe you later want to whip up another script using the same data, or even make a shell web scraper for it? Copy, paste, done! You're in business – in seconds. @@ -90,7 +92,8 @@ about what this buys you Web sites change. This makes user scripts break, start doing the wrong thing, or filling your console with errors. When they change so your script no longer has the stuff it needs, @@ -99,7 +102,8 @@ about what this buys you If you don't miss it, great! – that site has improved, and your script will never bother it. If you do – just update its `on.js` preamble, and your code just -
johan revised this gist
Nov 25, 2012 . 1 changed file with 14 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -48,12 +48,24 @@ about what this buys you Optimize for happiness! This is of course personal, but I love how clean `on.js` makes my user scripts. I can see what they do, at a moment's glance, and what data they depend on. Maintenance gets easier. * **Sane native javascript types** The DOM API:s are not only verbose, but also cripple results with bad types! From `on.js` array selectors, you get a real `Array`s that you can operate on with `.forEach`, `.map`, `.some`, `.every`, `.filter`, `.reduce`, `.slice`, `.indexOf`, and all the other really awesome `Array.prototype` methods. * **More complex scripts become feasible** As a script's complexity grows, @@ -121,4 +133,4 @@ test'em will pick it up, too. They are currently written in [jasmine](http://pivotal.github.com/jasmine/), lightly tweaked for improved readability. -
johan revised this gist
Nov 24, 2012 . 1 changed file with 26 additions and 28 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,17 +1,17 @@ # [on.js](http://git.io/on.js) The fun part of user scripting is deciding what _happens_. The _boring_ part is scavenging the DOM for bits of templated data, or elements you want to mod. Have `on.js` do it for you! [MIT licensed](http://en.wikipedia.org/wiki/MIT_License), or [Copyheart](http://copyheart.org/): copying is an act of love. Please copy and share! ## Quick Example @@ -37,18 +37,18 @@ function nukeCouponless(offers) { ## What's the benefit? **Separate logic from page structure.** I could talk all night about what this buys you – here are some highlights: * **Beautiful code** Optimize for happiness! This is of course personal, but I love how clean `on.js` makes my user scripts.<br> I can see what they do, at a moment's glance, and what data they depend on. @@ -59,17 +59,17 @@ about what this buys you As a script's complexity grows, so does the burden of maintaining it. With `on.js`, this is still true – but it grows far slower!<br> Seeing (and naming) the structure of the input your code operates on helps reading and writing the code operating on it. * **Reuse one script's page structure awareness** Maybe you later want to whip up another script using the same data, or even make a shell web scraper for it?<br> Copy, paste, done! You're in business – in seconds. @@ -78,26 +78,24 @@ about what this buys you Web sites change. This makes user scripts break, start doing the wrong thing, or filling your console with errors.<br> When they change so your script no longer has the stuff it needs, your code will just never run instead of breaking the page. If you don't miss it, great! – that site has improved, and your script will never bother it.<br> If you do – just update its `on.js` preamble, and your code just magically works again. ## Tests `on.js` comes with a test suite. It uses [node.js](http://nodejs.org), [test'em](https://github.com/airportyh/testem) and [coffee-script](coffeescript.org). @@ -106,13 +104,13 @@ run this to install the latter two: npm install testem coffee-script -g Then you can run it like this, for instance: testem -l chrome Incidentally, it provides pretty good docs about all the ways you can slice a page, and and how they work. New tests are easy to add. Just stick them in the appropriate @@ -123,4 +121,4 @@ test'em will pick it up, too. They are currently written in [jasmine](http://pivotal.github.com/jasmine/), lightly tweaked for improved readability. -
johan revised this gist
Nov 24, 2012 . 4 changed files with 398 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,126 @@ # on.js The fun part of user scripting is deciding what happens. The boring part is scavenging the DOM for bits of templated data or nodes you want to mod. Have `on.js` do it for you! [MIT licensed](http://en.wikipedia.org/wiki/MIT_License), or [Copyheart](http://copyheart.org/): Copying is an act of love. Please copy and share. ## Quick Example Want to sift away noise at [retailmenot](http://www.retailmenot.com/view/vayama.com)? ```js // hide the noise at http://www.retailmenot.com/view/vayama.com on({ dom: [ 'css* #offers li.offer' , { deal: 'xpath .' , coupon: 'css? .crux .code' } ] , ready: nukeCouponless }); function nukeCouponless(offers) { offers.forEach(function sift(a) { if (!a.coupon) a.deal.style.display = 'none'; }); } ``` ## What's the benefit? **Separate logic from page structure** I could talk all night about what this buys you – here are a few: * **Beautiful code** I optimize for happiness. This is of course personal, but I love how clean `on.js` makes my user scripts. I can see what they do, at a moment's glance, and what data they depend on. Maintenance gets easier. * **More complex scripts become feasible** As a script's complexity grows, so does the burden of maintaining it. With `on.js`, this is still true – but it grows much more slowly! Seeing (and naming) the structure of the input your code operates on helps reading and writing the code that operates on it. * **Reuse one script's page structure awareness** Maybe you later want to whip up another script using the same data, or even make a shell web scraper for it? Copy, paste, done! You're in business – in seconds. * **Have your scripts magically self-deprecate** Web sites change. This makes user scripts break, start doing the wrong thing, or filling your console with errors. When they change to the point where your script no longer has the stuff it needs, your code will just never run, instead of breaking the page. If you don't miss it, great! – that site has improved, and your script will never bother it. If you do, just update its `on.js` preamble – and your code just magically works again. ## Tests `on.js` has a test suite! It uses [node.js](http://nodejs.org), [test'em](https://github.com/airportyh/testem) and [coffee-script](coffeescript.org). Once node is installed, run this to install the latter two: npm install testem coffee-script -g And then you can run it like this, for instance: testem -l chrome This, incidentally, gives pretty good docs about all the ways you can slice a page, and and how they all work. New tests are easy to add. Just stick them in the appropriate `.coffee` file in `tests/`, or make a new `.js` or `.coffee` file in the same directory, and test'em will pick it up, too. They are currently written in [jasmine](http://pivotal.github.com/jasmine/), lightly tweaked for improved readability. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ *.js This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,239 @@ describe 'on()', -> fn = `on` it 'should throw an error on no input', -> try fn() catch e then err = e expect(err).toNotBe undefined it 'should expose an on.dom function after the first call', -> expect(typeof `on`.dom).toBe 'function' it 'should expose an on.query function after the first call', -> expect(typeof `on`.query).toBe 'function' it 'should expose an on.path_re function after the first call', -> expect(typeof `on`.path_re).toBe 'function' it 'should accept an object with "path_re", "dom", and/or "query" specs', -> what = fn( ready: (->), path_re: '/', dom: 'css *', query: true) expect(what).toEqual jasmine.any Array expect(what.length).toBeGreaterThan 2 describe 'on.dom(dom spec – see below for the three types of dom spec)', -> it 'should be a function after the first on() call', -> try `on()` catch e then err = e expect(err).toNotBe undefined it 'should expose on.dom.* functions once on.dom() has run once', -> try `on`.dom() fns = Object.keys(`on`.dom).join(',') expect(fns).toBe 'css*,css+,css?,css,xpath*,xpath+,xpath?,xpath!,xpath' describe 'on.dom(dom spec type 1: a selector string)', -> root = document.documentElement assertion = it describe 'on.dom("css… selection"): Array/Node, optional/not?', -> describe 'Array of Node:s (0+ occurrences):', -> assertion 'on.dom("css* NotFound") => []', -> expect(`on`.dom('css* NotFound')).toEqual [] assertion 'on.dom("css* html") => [root element]', -> expect(`on`.dom('css* html')).toEqual [root] assertion 'on.dom("css* *") => document.all (but as a proper Array)', -> what = `on`.dom("css* *") dall = [].slice.call document.all, 0 expect(what).toEqual dall describe 'Array of Node:s (1+ occurrences):', -> assertion 'on.dom("css+ html") => [root element]', -> expect(`on`.dom('css+ html')).toEqual [root] assertion 'on.dom("css+ NotFound") => undefined', -> expect(`on`.dom('css+ NotFound')).toBe undefined describe 'single optional Node, or null if not found:', -> assertion 'on.dom("css? *") => root element (= first match)', -> expect(`on`.dom('css? *')).toBe root assertion 'on.dom("css? NotFound") => null (not found)', -> expect(`on`.dom('css? NotFound')).toBe null describe 'single mandatory Node:', -> assertion 'on.dom("css *") => the root element', -> expect(`on`.dom('css *')).toBe root assertion 'on.dom("css NotFound") => undefined (unsatisfied)', -> expect(`on`.dom('css NotFound')).toBe undefined describe 'on.dom("xpath… selection"): Array/Node, optional/not?', -> describe 'xpath* => Array of Node:s (0+ occurrences):', -> assertion 'on.dom("xpath* /*") => [root element]', -> expect(`on`.dom('xpath* /*')).toEqual [root] assertion 'on.dom("xpath* /NotFound") => []', -> expect(`on`.dom('xpath* /NotFound')).toEqual [] describe 'xpath+ => Array of Node:s (1+ occurrences):', -> assertion 'on.dom("xpath+ /*") => [root element]', -> expect(`on`.dom('xpath+ /*')).toEqual [root] assertion 'on.dom("xpath+ /NotFound") => undefined', -> expect(`on`.dom('xpath+ /NotFound')).toBe undefined describe 'xpath? => single optional Node, or null if missing:', -> assertion 'on.dom("xpath? /NotFound") => null', -> expect(`on`.dom('xpath? /NotFound')).toBe null assertion 'on.dom("xpath? /*") => the root element', -> expect(`on`.dom('xpath? /*')).toBe root describe 'xpath => single mandatory Node:', -> assertion 'on.dom("xpath /*") => the root element', -> expect(`on`.dom('xpath /*')).toBe root assertion 'on.dom("xpath /NotFound") => undefined', -> expect(`on`.dom('xpath /NotFound')).toBe undefined assertion 'on.dom("xpath .") => the current document', -> expect(`on`.dom('xpath .')).toBe document describe '…or queries yielding Number/String/Boolean answers:', -> assertion 'on.dom("xpath count(/)") => 1', -> expect(`on`.dom('xpath count(/)')).toBe 1 assertion 'on.dom("xpath count(/NotFound)") => 0', -> expect(`on`.dom('xpath count(/NotFound)')).toBe 0 assertion 'on.dom("xpath name(/*)") => "html"', -> expect(`on`.dom('xpath name(/*)')).toBe 'html' assertion 'on.dom("xpath name(/)") => ""', -> expect(`on`.dom('xpath name(/)')).toBe '' assertion 'on.dom("xpath name(/*) = \'html\'") => true', -> expect(`on`.dom('xpath name(/*) = \'html\'')).toBe true assertion 'on.dom("xpath name(/*) = \'nope\'") => false', -> expect(`on`.dom('xpath name(/*) = \'nope\'')).toBe false describe 'xpath! makes assertions, requiring truthy answers:', -> assertion 'on.dom("xpath! count(/)") => 1', -> expect(`on`.dom('xpath count(/)')).toBe 1 assertion 'on.dom("xpath! count(/NotFound)") => undefined', -> expect(`on`.dom('xpath! count(/NotFound)')).toBe undefined assertion 'on.dom("xpath! name(/*)") => "html"', -> expect(`on`.dom('xpath! name(/*)')).toBe 'html' assertion 'on.dom("xpath! name(/)") => undefined', -> expect(`on`.dom('xpath! name(/)')).toBe undefined assertion 'on.dom("xpath! name(/*) = \'html\'") => true', -> expect(`on`.dom('xpath! name(/*) = \'html\'')).toBe true assertion 'on.dom("xpath! name(/*) = \'nope\'") => undefined', -> expect(`on`.dom('xpath! name(/*) = \'nope\'')).toBe undefined describe 'on.dom(dom spec type 2: an object showing the structure you want)', -> html = document.documentElement head = document.querySelector 'head' try `on()` # ensures there's an on.dom to call assertion = it pluralize = (n, noun) -> "#{n} #{noun}#{if n is 1 then '' else 's'}" assertion 'on.dom({}) => {} (fairly useless, but minimal, test case)', -> expect(`on`.dom({})).toEqual {} assertion 'on.dom({ h:"css head", H:"css html" }) => { h:head, H:html }', -> expect(`on`.dom({ h:"css head", H:"css html" })).toEqual { h:head, H:html } assertion 'on.dom({ h:"css head", f:"css? foot" }) => { h:head, f:null }', -> expect(`on`.dom({ h:"css head", f:"css? foot" })).toEqual { h:head, f:null } assertion 'on.dom({ h:"css head", f:"css foot" }) => undefined (no foot!)', -> expect(`on`.dom({ h:"css head", f:"css foot" })).toEqual undefined assertion 'on.dom({ x:"css* frame" }) => { x:[] } (frames optional here)', -> expect(`on`.dom({ x:"css* frame" })).toEqual { x:[] } assertion 'on.dom({ x:"css+ frame" }) => undefined (but mandatory here!)', -> expect(`on`.dom({ x:"css+ frame" })).toBe undefined assertion 'on.dom({ x:"css* script" }) => { x:[…all (>=0) script tags…] }', -> what = `on`.dom({ x:"css* script" }) expect(what.x).toEqual jasmine.any Array expect(what.x.every (s) -> s.nodeName is 'script') assertion 'on.dom({ x:"css+ script" }) => { x:[…all (>0) script tags…] }', -> what = `on`.dom({ x:"css+ script" }) expect(what.x).toEqual jasmine.any Array expect(what.x.length).toBeGreaterThan 0 expect(what.x.every (s) -> s.nodeName.toLowerCase() is 'script').toBe true assertion 'on.dom({ c:"xpath count(//script)" }) => {c:N} (any N is okay)', -> what = `on`.dom({ c:"xpath count(//script)" }) expect(what).toEqual jasmine.any Object expect(N = what.c).toEqual jasmine.any Number console.log "on.dom({ c: count(…) }) found #{pluralize N, 'script'}" delete what.c expect(what).toEqual {} assertion 'on.dom({ c:"xpath! count(//script)" }) => {c:N} (only N!=0 ok)', -> what = `on`.dom({ c:"xpath! count(//script)" }) expect(what.c).toBeGreaterThan 0 delete what.c expect(what).toEqual {} assertion 'on.dom({ c:"xpath! count(//missing)" }) => undefined (as N==0)', -> expect(`on`.dom({ c:"xpath! count(//missing)" })).toBe undefined assertion 'on.dom({ c:"xpath! count(//*) and /html" }) => { c:true }', -> expect(`on`.dom({ c:"xpath! count(//*) > 5 and /html" })).toEqual c: true describe 'on.dom(dom spec type 3: [context_spec, per_match_spec])', -> html = document.documentElement head = document.querySelector 'head' try `on()` # ensures there's an on.dom to call assertion = it assertion 'on.dom(["css* script[src]", "xpath string(@src)"]) => ["url"…]', -> what = `on`.dom(["css* script[src]", "xpath string(@src)"]) expect(what).toEqual jasmine.any Array expect(what.every (s) -> typeof s is 'string').toBe true assertion 'on.dom(["css? script:not([src])", "xpath string(.)"]) => "js…"', -> what = `on`.dom(["css? script:not([src])", "xpath string(.)"]) expect(typeof what).toBe 'string' desc = 'Code of first inline script tag' console.log "#{desc}:\n#{what}\n(#{desc} ends.)" assertion 'on.dom(["css? script:not([src])", "xpath! string(@src)"])' + ' => undefined (empty string is not truthy => not a match)', -> what = `on`.dom(["css? script:not([src])", "xpath! string(@src)"]) expect(what).toBe undefined assertion 'on.dom(["xpath /svg", "css* *"]) => undefined (not an svg doc)', -> expect(`on`.dom(["xpath /svg", "css* *"])).toBe undefined This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,32 @@ describe 'on.query', -> q_was = location.search query = (q) -> if location.search isnt q url = location.href.replace /(\?[^#]*)?(#.*)?$/, "#{q}$2" history.replaceState history.state, document.title, url it 'should be a function after the first on() call', -> try `on()` expect(typeof `on`.query).toBe 'function' it 'on.query() => {} for a missing query string', -> query '' expect(`on`.query()).toEqual {} it 'on.query() => {} for an empty query string ("?")', -> query '?' expect(`on`.query()).toEqual {} it 'on.query() => { a:"", x:"0" } for a query string "?a=&x=0"', -> query '?a=&x=0' expect(`on`.query()).toEqual a: '' x: '0' it 'on.query() => { ugh:undefined } for a query string "?ugh"', -> query '?ugh' result = `on`.query() expect('ugh' of result).toBe true expect(result).toEqual {} # FIXME - better test framework? expect(result.ugh).toBe `undefined` query q_was # reset, for good measure -
johan revised this gist
Nov 24, 2012 . 1 changed file with 1 addition and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1 @@ function on(d){function k(a){h[a]=void 0;return d[a]}function n(a){return"[object Array]"===A.call(a)}function B(a,b){var c=document.createElement("script"),f=document.documentElement,b=JSON.stringify(b||[]).slice(1,-1);c.textContent="("+a+")("+b+");";f.appendChild(c);f.removeChild(c)}function J(a){var b={};((this===on||this===window?location.search:this)||"").replace(/\+/g,"%20").split("&").forEach(function(a){if(a=/^\??([^=&]*)(?:=(.*))?/.exec(a)){var f,e;e=a[1];a=a[2];try{f=decodeURIComponent(e)}catch(g){f=unescape(e)}if(null!=(e=a))try{e=decodeURIComponent(a)}catch(d){e=unescape(a)}b[f]=e}});if(!0===a||null==a)return b;throw Error("bad query type "+typeof a+": "+a);}function K(a){n(a)||(a=n(a)?a:[a]);var b=a.shift();"string"===typeof b&&(b=RegExp(b));if(!(b instanceof RegExp))throw Error(typeof b+" was not a regexp: "+b);b=b.exec(this===on||this===window?location.pathname:this);if(null===b)return g;if(!a.length)return b;var c={};for(b.shift();a.length;)c[a.shift()]=b.shift();return c}function C(a){return function(b){var c=a.apply(this,arguments);return null!==c?c:g}}function D(a){return function(b){var c=a.apply(this,arguments);return c.length?c:g}}function E(a){a=this.querySelectorAll(a);return L.call(a,0)}function F(a){return this.querySelector(a)}function p(a){var b=(this.evaluate?this:this.ownerDocument).evaluate(a,this,null,0,null),c=[];switch(b.resultType){case b.STRING_TYPE:return b.stringValue;case b.NUMBER_TYPE:return b.numberValue;case b.BOOLEAN_TYPE:return b.booleanValue;default:for(;a=b.iterateNext();)c.push(a);return c}}function G(a){a=p.call(this,a);return a instanceof Array?a[0]||null:a}function M(a){return(a+"").replace(/([-$(-+.?[-^{|}])/g,"\\$1")}function m(a,b){function c(a){if("string"!==typeof a)throw Error("non-String dom match rule: "+a);u||(u=RegExp("^("+Object.keys(on.dom).map(M).join("|")+")\\s*(.*)"));var b=u.exec(a),c,d;b&&(c=b[1],a=b[2],d=m[c]);if(!d)throw Error("unknown dom match rule "+c+": "+a);return d.call(this,a)}var f,e,d;void 0===b&&(b=this===on||this===window?document:this);if(null===b||b===g)return g;if(n(b)){f=[];for(d=0;d<b.length;d++)e=m.call(b[d],a),e!==g&&f.push(e);return f}if("object"!==typeof b||!("nodeType"in b))throw Error("illegal context: "+b);if("string"===typeof a)return c.call(b,a);if(n(a))return b=c.call(b,a[0]),null===b||b===g?b:m.call(b,a[1]);if("[object Object]"===A.call(a)){f={};for(d in a){e=m.call(b,a[d]);if(e===g)return g;f[d]=e}return f}throw Error("dom spec was neither a String, Object nor Array: "+a);}var A=Object.prototype.toString,L=Array.prototype.slice;if(!("dom"in on)){var q=m,r=D(E),i=C(F),N=D(p),O=p,q={path_re:{fn:K},query:{fn:J},dom:{fn:q,my:{"css*":E,"css+":r,"css?":F,css:i,"xpath*":p,"xpath+":N,"xpath?":G,"xpath!":function(a){return O.apply(this,arguments)||g},xpath:C(G)}},inject:{fn:B}},j,l;for(j in q){i=q[j];r=i.fn;if(i=i.my)for(l in i)r[l]=i[l];on[j]=r}}var g=void 0,s=[],h=Object.create(d),H=k("debug");k("name");j=k("ready");var v=k("load"),w=k("pushstate");l=k("pjaxevent");var u,t,x,y,z,I;if("function"!==typeof j&&"function"!==typeof v&&"function"!==typeof w)throw alert("no on function"),Error('on() needs at least a "ready" or "load" function!');if(w&&history.pushState&&-1===(on.pushState=on.pushState||[]).indexOf(d))on.pushState.push(d),history.pushState.armed||(B(function(a){function b(){var a=document.createEvent("Events");a.initEvent("history.pushState",!1,!1);document.dispatchEvent(a)}var c=history.pushState;history.pushState=function(){if(a&&window.$&&$.pjax)$(document).one(a,b);else setTimeout(b,0);return c.apply(this,arguments)}},[l]),history.pushState.armed=l),I=function(){h=Object.create(d);h.load=h.pushstate=void 0;h.ready=w;on(h)},document.addEventListener("history.pushState",function(){H&&console.log("on.pushstate",location.pathname);I()},!1);try{for(t in h)if(x=h[t],void 0!==x){y=on[t];if(!y)throw Error('did not grok rule "'+t+'"!');z=y(x);if(z===g)return!1;s.push(z)}}catch(P){return H&&console.warn("on(debug): we didn't run because "+P.message),!1}j&&j.apply(d,s.concat());v&&window.addEventListener("load",function(){v.apply(d,s.concat())});return s.concat(d)} -
johan revised this gist
Nov 24, 2012 . 1 changed file with 9 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +1,9 @@ function on(d){function k(a){h[a]=void 0;return d[a]}function n(a){return"[object Array]"===A.call(a)}function B(a,b){var c=document.createElement("script"),f=document.documentElement,b=JSON.stringify(b||[]).slice(1,-1);c.textContent="("+a+")("+b+");";f.appendChild(c);f.removeChild(c)}function J(a){var b={};((this===on||this===window?location.search:this)||"").replace(/\+/g,"%20").split("&").forEach(function(a){if(a=/^\??([^=&]*)(?:=(.*))?/.exec(a)){var f,e;e=a[1];a=a[2];try{f=decodeURIComponent(e)}catch(g){f= unescape(e)}if(null!=(e=a))try{e=decodeURIComponent(a)}catch(d){e=unescape(a)}b[f]=e}});if(!0===a||null==a)return b;throw Error("bad query type "+typeof a+": "+a);}function K(a){n(a)||(a=n(a)?a:[a]);var b=a.shift();"string"===typeof b&&(b=RegExp(b));if(!(b instanceof RegExp))throw Error(typeof b+" was not a regexp: "+b);b=b.exec(this===on||this===window?location.pathname:this);if(null===b)return g;if(!a.length)return b;var c={};for(b.shift();a.length;)c[a.shift()]=b.shift();return c}function C(a){return function(b){var c= a.apply(this,arguments);return null!==c?c:g}}function D(a){return function(b){var c=a.apply(this,arguments);return c.length?c:g}}function E(a){a=this.querySelectorAll(a);return L.call(a,0)}function F(a){return this.querySelector(a)}function p(a){var b=(this.evaluate?this:this.ownerDocument).evaluate(a,this,null,0,null),c=[];switch(b.resultType){case b.STRING_TYPE:return b.stringValue;case b.NUMBER_TYPE:return b.numberValue;case b.BOOLEAN_TYPE:return b.booleanValue;default:for(;a=b.iterateNext();)c.push(a); return c}}function G(a){a=p.call(this,a);return a instanceof Array?a[0]||null:a}function M(a){return(a+"").replace(/([-$(-+.?[-^{|}])/g,"\\$1")}function m(a,b){function c(a){if("string"!==typeof a)throw Error("non-String dom match rule: "+a);u||(u=RegExp("^("+Object.keys(on.dom).map(M).join("|")+")\\s*(.*)"));var b=u.exec(a),c,d;b&&(c=b[1],a=b[2],d=m[c]);if(!d)throw Error("unknown dom match rule "+c+": "+a);return d.call(this,a)}var f,e,d;void 0===b&&(b=this===on||this===window?document:this);if(null=== b||b===g)return g;if(n(b)){f=[];for(d=0;d<b.length;d++)e=m.call(b[d],a),e!==g&&f.push(e);return f}if("object"!==typeof b||!("nodeType"in b))throw Error("illegal context: "+b);if("string"===typeof a)return c.call(b,a);if(n(a))return b=c.call(b,a[0]),null===b||b===g?b:m.call(b,a[1]);if("[object Object]"===A.call(a)){f={};for(d in a){e=m.call(b,a[d]);if(e===g)return g;f[d]=e}return f}throw Error("dom spec was neither a String, Object nor Array: "+a);}var A=Object.prototype.toString,L=Array.prototype.slice; if(!("dom"in on)){var q=m,r=D(E),i=C(F),N=D(p),O=p,q={path_re:{fn:K},query:{fn:J},dom:{fn:q,my:{"css*":E,"css+":r,"css?":F,css:i,"xpath*":p,"xpath+":N,"xpath?":G,"xpath!":function(a){return O.apply(this,arguments)||g},xpath:C(G)}},inject:{fn:B}},j,l;for(j in q){i=q[j];r=i.fn;if(i=i.my)for(l in i)r[l]=i[l];on[j]=r}}var g=void 0,s=[],h=Object.create(d),H=k("debug");k("name");j=k("ready");var v=k("load"),w=k("pushstate");l=k("pjaxevent");var u,t,x,y,z,I;if("function"!==typeof j&&"function"!==typeof v&& "function"!==typeof w)throw alert("no on function"),Error('on() needs at least a "ready" or "load" function!');if(w&&history.pushState&&-1===(on.pushState=on.pushState||[]).indexOf(d))on.pushState.push(d),history.pushState.armed||(B(function(a){function b(){var a=document.createEvent("Events");a.initEvent("history.pushState",!1,!1);document.dispatchEvent(a)}var c=history.pushState;history.pushState=function(){if(a&&window.$&&$.pjax)$(document).one(a,b);else setTimeout(b,0);return c.apply(this,arguments)}}, [l]),history.pushState.armed=l),I=function(){h=Object.create(d);h.load=h.pushstate=void 0;h.ready=w;on(h)},document.addEventListener("history.pushState",function(){H&&console.log("on.pushstate",location.pathname);I()},!1);try{for(t in h)if(x=h[t],void 0!==x){y=on[t];if(!y)throw Error('did not grok rule "'+t+'"!');z=y(x);if(z===g)return!1;s.push(z)}}catch(P){return H&&console.warn("on(debug): we didn't run because "+P.message),!1}j&&j.apply(d,s.concat());v&&window.addEventListener("load",function(){v.apply(d, s.concat())});return s.concat(d)} -
johan revised this gist
Nov 24, 2012 . 1 changed file with 33 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -46,6 +46,11 @@ describe 'on.dom(dom spec type 1: a selector string)', -> assertion 'on.dom("css* html") => [root element]', -> expect(`on`.dom('css* html')).toEqual [root] assertion 'on.dom("css* *") => document.all (but as a proper Array)', -> what = `on`.dom("css* *") dall = [].slice.call document.all, 0 expect(what).toEqual dall describe 'Array of Node:s (1+ occurrences):', -> assertion 'on.dom("css+ html") => [root element]', -> @@ -184,7 +189,7 @@ describe 'on.dom(dom spec type 2: an object showing the structure you want)', -> what = `on`.dom({ x:"css+ script" }) expect(what.x).toEqual jasmine.any Array expect(what.x.length).toBeGreaterThan 0 expect(what.x.every (s) -> s.nodeName.toLowerCase() is 'script').toBe true assertion 'on.dom({ c:"xpath count(//script)" }) => {c:N} (any N is okay)', -> what = `on`.dom({ c:"xpath count(//script)" }) @@ -205,3 +210,30 @@ describe 'on.dom(dom spec type 2: an object showing the structure you want)', -> assertion 'on.dom({ c:"xpath! count(//*) and /html" }) => { c:true }', -> expect(`on`.dom({ c:"xpath! count(//*) > 5 and /html" })).toEqual c: true describe 'on.dom(dom spec type 3: [context_spec, per_match_spec])', -> html = document.documentElement head = document.querySelector 'head' try `on()` # ensures there's an on.dom to call assertion = it assertion 'on.dom(["css* script[src]", "xpath string(@src)"]) => ["url"…]', -> what = `on`.dom(["css* script[src]", "xpath string(@src)"]) expect(what).toEqual jasmine.any Array expect(what.every (s) -> typeof s is 'string').toBe true assertion 'on.dom(["css? script:not([src])", "xpath string(.)"]) => "js…"', -> what = `on`.dom(["css? script:not([src])", "xpath string(.)"]) expect(typeof what).toBe 'string' desc = 'Code of first inline script tag' console.log "#{desc}:\n#{what}\n(#{desc} ends.)" assertion 'on.dom(["css? script:not([src])", "xpath! string(@src)"])' + ' => undefined (empty string is not truthy => not a match)', -> what = `on`.dom(["css? script:not([src])", "xpath! string(@src)"]) expect(what).toBe undefined assertion 'on.dom(["xpath /svg", "css* *"]) => undefined (not an svg doc)', -> expect(`on`.dom(["xpath /svg", "css* *"])).toBe undefined -
johan revised this gist
Nov 24, 2012 . 1 changed file with 13 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,8 @@ describe 'on()', -> fn = `on` it 'should throw an error on no input', -> try fn() catch e then err = e expect(err).toNotBe undefined it 'should expose an on.dom function after the first call', -> @@ -12,6 +11,15 @@ describe 'on()', -> it 'should expose an on.query function after the first call', -> expect(typeof `on`.query).toBe 'function' it 'should expose an on.path_re function after the first call', -> expect(typeof `on`.path_re).toBe 'function' it 'should accept an object with "path_re", "dom", and/or "query" specs', -> what = fn( ready: (->), path_re: '/', dom: 'css *', query: true) expect(what).toEqual jasmine.any Array expect(what.length).toBeGreaterThan 2 describe 'on.dom(dom spec – see below for the three types of dom spec)', -> it 'should be a function after the first on() call', -> @@ -25,6 +33,7 @@ describe 'on.dom(dom spec – see below for the three types of dom spec)', -> expect(fns).toBe 'css*,css+,css?,css,xpath*,xpath+,xpath?,xpath!,xpath' describe 'on.dom(dom spec type 1: a selector string)', -> root = document.documentElement assertion = it @@ -139,6 +148,7 @@ describe 'on.dom(dom spec type 1: a selector string)', -> expect(`on`.dom('xpath! name(/*) = \'nope\'')).toBe undefined describe 'on.dom(dom spec type 2: an object showing the structure you want)', -> html = document.documentElement head = document.querySelector 'head' -
johan revised this gist
Nov 24, 2012 . 1 changed file with 63 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -9,8 +9,11 @@ describe 'on()', -> it 'should expose an on.dom function after the first call', -> expect(typeof `on`.dom).toBe 'function' it 'should expose an on.query function after the first call', -> expect(typeof `on`.query).toBe 'function' describe 'on.dom(dom spec – see below for the three types of dom spec)', -> it 'should be a function after the first on() call', -> try `on()` catch e then err = e expect(err).toNotBe undefined @@ -22,7 +25,7 @@ describe 'on.dom', -> expect(fns).toBe 'css*,css+,css?,css,xpath*,xpath+,xpath?,xpath!,xpath' describe 'on.dom(dom spec type 1: a selector string)', -> root = document.documentElement assertion = it @@ -134,3 +137,61 @@ describe 'on.dom(a selector string)', -> assertion 'on.dom("xpath! name(/*) = \'nope\'") => undefined', -> expect(`on`.dom('xpath! name(/*) = \'nope\'')).toBe undefined describe 'on.dom(dom spec type 2: an object showing the structure you want)', -> html = document.documentElement head = document.querySelector 'head' try `on()` # ensures there's an on.dom to call assertion = it pluralize = (n, noun) -> "#{n} #{noun}#{if n is 1 then '' else 's'}" assertion 'on.dom({}) => {} (fairly useless, but minimal, test case)', -> expect(`on`.dom({})).toEqual {} assertion 'on.dom({ h:"css head", H:"css html" }) => { h:head, H:html }', -> expect(`on`.dom({ h:"css head", H:"css html" })).toEqual { h:head, H:html } assertion 'on.dom({ h:"css head", f:"css? foot" }) => { h:head, f:null }', -> expect(`on`.dom({ h:"css head", f:"css? foot" })).toEqual { h:head, f:null } assertion 'on.dom({ h:"css head", f:"css foot" }) => undefined (no foot!)', -> expect(`on`.dom({ h:"css head", f:"css foot" })).toEqual undefined assertion 'on.dom({ x:"css* frame" }) => { x:[] } (frames optional here)', -> expect(`on`.dom({ x:"css* frame" })).toEqual { x:[] } assertion 'on.dom({ x:"css+ frame" }) => undefined (but mandatory here!)', -> expect(`on`.dom({ x:"css+ frame" })).toBe undefined assertion 'on.dom({ x:"css* script" }) => { x:[…all (>=0) script tags…] }', -> what = `on`.dom({ x:"css* script" }) expect(what.x).toEqual jasmine.any Array expect(what.x.every (s) -> s.nodeName is 'script') assertion 'on.dom({ x:"css+ script" }) => { x:[…all (>0) script tags…] }', -> what = `on`.dom({ x:"css+ script" }) expect(what.x).toEqual jasmine.any Array expect(what.x.length).toBeGreaterThan 0 expect(what.x.every (s) -> s.nodeName is 'script') assertion 'on.dom({ c:"xpath count(//script)" }) => {c:N} (any N is okay)', -> what = `on`.dom({ c:"xpath count(//script)" }) expect(what).toEqual jasmine.any Object expect(N = what.c).toEqual jasmine.any Number console.log "on.dom({ c: count(…) }) found #{pluralize N, 'script'}" delete what.c expect(what).toEqual {} assertion 'on.dom({ c:"xpath! count(//script)" }) => {c:N} (only N!=0 ok)', -> what = `on`.dom({ c:"xpath! count(//script)" }) expect(what.c).toBeGreaterThan 0 delete what.c expect(what).toEqual {} assertion 'on.dom({ c:"xpath! count(//missing)" }) => undefined (as N==0)', -> expect(`on`.dom({ c:"xpath! count(//missing)" })).toBe undefined assertion 'on.dom({ c:"xpath! count(//*) and /html" }) => { c:true }', -> expect(`on`.dom({ c:"xpath! count(//*) > 5 and /html" })).toEqual c: true -
johan revised this gist
Nov 24, 2012 . 6 changed files with 175 additions and 199 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,11 @@ { "framework": "jasmine" , "src_files": [ "on.js" , "tests/*.coffee" ] , "serve_files": [ "on.js" , "tests/*.js" ] , "before_tests": "coffee -c tests/*.coffee" } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ *.js This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,136 @@ describe 'on()', -> it 'on should be a function', -> expect(typeof `on`).toBe 'function' it 'should throw an error on no input', -> try `on()` catch e then err = e expect(err).toNotBe undefined it 'should expose an on.dom function after the first call', -> expect(typeof `on`.dom).toBe 'function' describe 'on.dom', -> it 'should be a function after the first on() call', -> try `on()` catch e then err = e expect(err).toNotBe undefined it 'should expose on.dom.* functions once on.dom() has run once', -> try `on`.dom() fns = Object.keys(`on`.dom).join(',') expect(fns).toBe 'css*,css+,css?,css,xpath*,xpath+,xpath?,xpath!,xpath' describe 'on.dom(a selector string)', -> root = document.documentElement assertion = it describe 'on.dom("css… selection"): Array/Node, optional/not?', -> describe 'Array of Node:s (0+ occurrences):', -> assertion 'on.dom("css* NotFound") => []', -> expect(`on`.dom('css* NotFound')).toEqual [] assertion 'on.dom("css* html") => [root element]', -> expect(`on`.dom('css* html')).toEqual [root] describe 'Array of Node:s (1+ occurrences):', -> assertion 'on.dom("css+ html") => [root element]', -> expect(`on`.dom('css+ html')).toEqual [root] assertion 'on.dom("css+ NotFound") => undefined', -> expect(`on`.dom('css+ NotFound')).toBe undefined describe 'single optional Node, or null if not found:', -> assertion 'on.dom("css? *") => root element (= first match)', -> expect(`on`.dom('css? *')).toBe root assertion 'on.dom("css? NotFound") => null (not found)', -> expect(`on`.dom('css? NotFound')).toBe null describe 'single mandatory Node:', -> assertion 'on.dom("css *") => the root element', -> expect(`on`.dom('css *')).toBe root assertion 'on.dom("css NotFound") => undefined (unsatisfied)', -> expect(`on`.dom('css NotFound')).toBe undefined describe 'on.dom("xpath… selection"): Array/Node, optional/not?', -> describe 'xpath* => Array of Node:s (0+ occurrences):', -> assertion 'on.dom("xpath* /*") => [root element]', -> expect(`on`.dom('xpath* /*')).toEqual [root] assertion 'on.dom("xpath* /NotFound") => []', -> expect(`on`.dom('xpath* /NotFound')).toEqual [] describe 'xpath+ => Array of Node:s (1+ occurrences):', -> assertion 'on.dom("xpath+ /*") => [root element]', -> expect(`on`.dom('xpath+ /*')).toEqual [root] assertion 'on.dom("xpath+ /NotFound") => undefined', -> expect(`on`.dom('xpath+ /NotFound')).toBe undefined describe 'xpath? => single optional Node, or null if missing:', -> assertion 'on.dom("xpath? /NotFound") => null', -> expect(`on`.dom('xpath? /NotFound')).toBe null assertion 'on.dom("xpath? /*") => the root element', -> expect(`on`.dom('xpath? /*')).toBe root describe 'xpath => single mandatory Node:', -> assertion 'on.dom("xpath /*") => the root element', -> expect(`on`.dom('xpath /*')).toBe root assertion 'on.dom("xpath /NotFound") => undefined', -> expect(`on`.dom('xpath /NotFound')).toBe undefined assertion 'on.dom("xpath .") => the current document', -> expect(`on`.dom('xpath .')).toBe document describe '…or queries yielding Number/String/Boolean answers:', -> assertion 'on.dom("xpath count(/)") => 1', -> expect(`on`.dom('xpath count(/)')).toBe 1 assertion 'on.dom("xpath count(/NotFound)") => 0', -> expect(`on`.dom('xpath count(/NotFound)')).toBe 0 assertion 'on.dom("xpath name(/*)") => "html"', -> expect(`on`.dom('xpath name(/*)')).toBe 'html' assertion 'on.dom("xpath name(/)") => ""', -> expect(`on`.dom('xpath name(/)')).toBe '' assertion 'on.dom("xpath name(/*) = \'html\'") => true', -> expect(`on`.dom('xpath name(/*) = \'html\'')).toBe true assertion 'on.dom("xpath name(/*) = \'nope\'") => false', -> expect(`on`.dom('xpath name(/*) = \'nope\'')).toBe false describe 'xpath! makes assertions, requiring truthy answers:', -> assertion 'on.dom("xpath! count(/)") => 1', -> expect(`on`.dom('xpath count(/)')).toBe 1 assertion 'on.dom("xpath! count(/NotFound)") => undefined', -> expect(`on`.dom('xpath! count(/NotFound)')).toBe undefined assertion 'on.dom("xpath! name(/*)") => "html"', -> expect(`on`.dom('xpath! name(/*)')).toBe 'html' assertion 'on.dom("xpath! name(/)") => undefined', -> expect(`on`.dom('xpath! name(/)')).toBe undefined assertion 'on.dom("xpath! name(/*) = \'html\'") => true', -> expect(`on`.dom('xpath! name(/*) = \'html\'')).toBe true assertion 'on.dom("xpath! name(/*) = \'nope\'") => undefined', -> expect(`on`.dom('xpath! name(/*) = \'nope\'')).toBe undefined This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,162 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,32 @@ describe 'on.query', -> q_was = location.search query = (q) -> if location.search isnt q url = location.href.replace /(\?[^#]*)?(#.*)?$/, "#{q}$2" history.replaceState history.state, document.title, url it 'should be a function after the first on() call', -> try `on()` expect(typeof `on`.query).toBe 'function' it 'on.query() => {} for a missing query string', -> query '' expect(`on`.query()).toEqual {} it 'on.query() => {} for an empty query string ("?")', -> query '?' expect(`on`.query()).toEqual {} it 'on.query() => { a:"", x:"0" } for a query string "?a=&x=0"', -> query '?a=&x=0' expect(`on`.query()).toEqual a: '' x: '0' it 'on.query() => { ugh:undefined } for a query string "?ugh"', -> query '?ugh' result = `on`.query() expect('ugh' of result).toBe true expect(result).toEqual {} # FIXME - better test framework? expect(result.ugh).toBe `undefined` query q_was # reset, for good measure This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,36 +0,0 @@ -
johan revised this gist
Nov 24, 2012 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -336,8 +336,8 @@ function on(opts) { if (isArray(context)) { for (results = [], i = 0; i < context.length; i++) { result = test_dom.call(context[i], spec); if (result !== FAIL) results.push(result); } return results; } -
johan revised this gist
Nov 24, 2012 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -348,6 +348,7 @@ function on(opts) { if (typeof spec === 'string') return lookup.call(context, spec); if (isArray(spec)) { context = lookup.call(context, spec[0]); if (context === null || context === FAIL) return context; return test_dom.call(context, spec[1]); } if (isObject(spec)) { -
johan revised this gist
Nov 24, 2012 . 1 changed file with 9 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -145,6 +145,7 @@ function on(opts) { , load = get('load') , pushState = get('pushstate') , pjax_event = get('pjaxevent') , parse_dom_rule // regexp on.dom uses to recognize its various sub-commands , name, rule, test, result, retry ; @@ -298,17 +299,24 @@ function on(opts) { return got instanceof Array ? got[0] || null : got; } function quoteRe(s) { return (s+'').replace(/([-$(-+.?[-^{|}])/g, '\\$1'); } // DOM constraint tester / scraper facility: // "this" is the context Node(s) - initially the document // "spec" is either of: // * css / xpath Selector "selector_type selector" // * resolved for context [ context Selector, spec ] // * an Object of spec(s) { property_name: spec, ... } function test_dom(spec, context) { // returns FAIL if it turned out it wasn't a mandated match at this level // returns null if it didn't find optional matches at this level // returns Node or an Array of nodes, or a basic type from some XPath query function lookup(rule) { if (typeof rule !== 'string') throw new Error('non-String dom match rule: '+ rule); if (!parse_dom_rule) parse_dom_rule = new RegExp('^(' + Object.keys(on.dom).map(quoteRe).join('|') + ')\\s*(.*)'); var match = parse_dom_rule.exec(rule), type, func; if (match) { type = match[1]; rule = match[2]; -
johan revised this gist
Nov 24, 2012 . 2 changed files with 11 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -241,11 +241,12 @@ function on(opts) { function unparam(query) { var data = {}; (query || '').replace(/\+/g, '%20').split('&').forEach(function(kv) { kv = /^\??([^=&]*)(?:=(.*))?/.exec(kv); if (!kv) return; var prop, val, k = kv[1], v = kv[2], e, m; try { prop = decodeURIComponent(k); } catch (e) { prop = unescape(k); } if ((val = v) != null) try { val = decodeURIComponent(v); } catch (e) { val = unescape(v); } data[prop] = val; }); return data; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -25,4 +25,12 @@ describe('on.query', function() { query('?a=&x=0'); expect(on.query()).toEqual({ a:"", x:"0" }); }); it('on.query() => { ugh:undefined } for a query string "?ugh"', function() { query('?ugh'); var result = on.query(); expect('ugh' in result).toBe(true); expect(result).toEqual({}); // FIXME - better test framework? expect(result.ugh).toBe(undefined); }); }); -
johan revised this gist
Nov 24, 2012 . 1 changed file with 28 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,28 @@ describe('on.query', function() { function query(q) { if (location.search !== q) { var url = location.href.replace(/(\?[^#]*)?(#.*)?$/, q + '$2'); history.replaceState(history.state, document.title, url); } } it('should be a function after the first on() call', function() { try { on(); } catch(e) {} expect(typeof on.query).toBe('function'); }); it('on.query() => {} for a missing query string', function() { query(''); expect(on.query()).toEqual({}); }); it('on.query() => {} for an empty query string ("?")', function() { query('?'); expect(on.query()).toEqual({}); }); it('on.query() => { a:"", x:"0" } for a query string "?a=&x=0"', function() { query('?a=&x=0'); expect(on.query()).toEqual({ a:"", x:"0" }); }); }); -
johan revised this gist
Nov 24, 2012 . 2 changed files with 21 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -108,18 +108,18 @@ function on(opts) { , Array_slice = Array.prototype.slice , FAIL = 'dom' in on ? undefined : (function() { var tests = { path_re: { fn: test_regexp } , query: { fn: test_query } , dom: { fn: test_dom , my: { 'css*': $c , 'css+': one_or_more($c) , 'css?': $C , 'css': not_null($C) , 'xpath*': $x , 'xpath+': one_or_more($x) , 'xpath?': $X , 'xpath!': truthy($x) , 'xpath': not_null($X) } } , inject: { fn: inject } @@ -133,7 +133,7 @@ function on(opts) { if ((my = test.my)) for (mine in my) me[mine] = my[mine]; on[name] = me; } })() @@ -233,7 +233,7 @@ function on(opts) { } function test_query(spec) { var q = unparam(this === on || this === window ? location.search : this); if (spec === true || spec == null) return q; // decode the query for me! throw new Error('bad query type '+ (typeof spec) +': '+ spec); } @@ -258,7 +258,7 @@ function on(opts) { if (!(re instanceof RegExp)) throw new Error((typeof re) +' was not a regexp: '+ re); var ok = re.exec(this === on || this === window ? location.pathname : this); if (ok === null) return FAIL; if (!spec.length) return ok; var named = {}; @@ -318,7 +318,9 @@ function on(opts) { } var results, result, i, property_name; if (context === undefined) { context = this === on || this === window ? document : this; } // validate context: if (context === null || context === FAIL) return FAIL; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -23,6 +23,12 @@ describe('on.dom', function() { try { on(); } catch(e) {} expect(typeof on.dom).toBe('function'); }); it('should expose on.dom.* functions once on.dom() has run once', function() { try { on.dom(); } catch(e) {} var fns = Object.keys(on.dom).join(','); expect(fns).toBe('css*,css+,css?,css,xpath*,xpath+,xpath?,xpath!,xpath'); }); }); -
johan revised this gist
Nov 24, 2012 . 2 changed files with 162 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,6 @@ { "framework": "jasmine" , "src_files": [ "on.js" , "tests/*" ] } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,156 @@ // some tests for test'em: // http://net.tutsplus.com/tutorials/javascript-ajax/make-javascript-testing-fun-with-testem/ describe('on()', function() { it('on should be a function', function() { expect(typeof on).toBe('function'); }); it('should throw an error on no input', function() { var res, err; try { res = on(); } catch(e) { err = e; } expect(err).toNotBe(undefined); }); it('should expose an on.dom function after the first call', function() { expect(typeof on.dom).toBe('function'); }); }); describe('on.dom', function() { it('should be a function after the first on() call', function() { try { on(); } catch(e) {} expect(typeof on.dom).toBe('function'); }); }); describe('on.dom(a selector string)', function() { var root = document.documentElement, asserts = it; describe('on.dom("css… selection"): Array/Node, optional/not?', function() { describe('Array of Node:s (0+ occurrences):', function() { asserts('on.dom("css* NotFound") => []', function() { expect(on.dom("css* NotFound")).toEqual([]); }); asserts('on.dom("css* html") => [root element]', function() { expect(on.dom("css* html")).toEqual([root]); }); }); describe('Array of Node:s (1+ occurrences):', function() { asserts('on.dom("css+ html") => [root element]', function() { expect(on.dom("css+ html")).toEqual([root]); }); asserts('on.dom("css+ NotFound") => undefined', function() { expect(on.dom("css+ NotFound")).toBe(undefined); }); }); describe('single optional Node, or null if not found:', function() { asserts('on.dom("css? *") => root element (= first match)', function() { expect(on.dom("css? *")).toBe(root); }); asserts('on.dom("css? NotFound") => null (not found)', function() { expect(on.dom("css? NotFound")).toBe(null); }); }); describe('single mandatory Node:', function() { asserts('on.dom("css *") => the root element', function() { expect(on.dom("css *")).toBe(root); }); asserts('on.dom("css NotFound") => undefined (unsatisfied)', function() { expect(on.dom("css NotFound")).toBe(undefined); }); }); }); describe('on.dom("xpath… selection"): Array/Node, optional/not?', function() { describe('xpath* => Array of Node:s (0+ occurrences):', function() { asserts('on.dom("xpath* /*") => [root element]', function () { expect(on.dom("xpath* /*")).toEqual([root]); }); asserts('on.dom("xpath* /NotFound") => []', function () { expect(on.dom("xpath* /NotFound")).toEqual([]); }); }); describe('xpath+ => Array of Node:s (1+ occurrences):', function() { asserts('on.dom("xpath+ /*") => [root element]', function () { expect(on.dom("xpath+ /*")).toEqual([root]); }); asserts('on.dom("xpath+ /NotFound") => undefined', function () { expect(on.dom("xpath+ /NotFound")).toBe(undefined); }); }); describe('xpath? => single optional Node, or null if missing:', function() { asserts('on.dom("xpath? /NotFound") => null', function() { expect(on.dom("xpath? /NotFound")).toBe(null); }); asserts('on.dom("xpath? /*") => the root element', function() { expect(on.dom("xpath? /*")).toBe(root); }); }); describe('xpath => single mandatory Node:', function() { asserts('on.dom("xpath /*") => the root element', function() { expect(on.dom("xpath /*")).toBe(root); }); asserts('on.dom("xpath /NotFound") => undefined', function() { expect(on.dom("xpath /NotFound")).toBe(undefined); }); asserts('on.dom("xpath .") => the current document', function() { expect(on.dom("xpath .")).toBe(document); }); }); describe('…or queries yielding Number/String/Boolean answers:', function() { asserts('on.dom("xpath count(/)") => 1', function() { expect(on.dom("xpath count(/)")).toBe(1); }); asserts('on.dom("xpath count(/NotFound)") => 0', function() { expect(on.dom("xpath count(/NotFound)")).toBe(0); }); asserts('on.dom("xpath name(/*)") => "html"', function() { expect(on.dom("xpath name(/*)")).toBe('html'); }); asserts('on.dom("xpath name(/)") => ""', function() { expect(on.dom("xpath name(/)")).toBe(''); }); asserts('on.dom("xpath name(/*) = \'html\'") => true', function() { expect(on.dom("xpath name(/*) = \'html\'")).toBe(true); }); asserts('on.dom("xpath name(/*) = \'nope\'") => false', function() { expect(on.dom("xpath name(/*) = \'nope\'")).toBe(false); }); }); describe('xpath! makes assertions, requiring truthy answers:', function() { asserts('on.dom("xpath! count(/)") => 1', function() { expect(on.dom("xpath count(/)")).toBe(1); }); asserts('on.dom("xpath! count(/NotFound)") => undefined', function() { expect(on.dom("xpath! count(/NotFound)")).toBe(undefined); }); asserts('on.dom("xpath! name(/*)") => "html"', function() { expect(on.dom("xpath! name(/*)")).toBe('html'); }); asserts('on.dom("xpath! name(/)") => undefined', function() { expect(on.dom("xpath! name(/)")).toBe(undefined); }); asserts('on.dom("xpath! name(/*) = \'html\'") => true', function() { expect(on.dom("xpath! name(/*) = \'html\'")).toBe(true); }); asserts('on.dom("xpath! name(/*) = \'nope\'") => undefined', function() { expect(on.dom("xpath! name(/*) = \'nope\'")).toBe(undefined); }); }); }); }); -
johan revised this gist
Nov 7, 2012 . 2 changed files with 73 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -43,7 +43,7 @@ (see http://goo.gl/ejtMD for a more thorough discussion of something similar) The dom property is recursively defined so you can make nested structures. If you want a property that itself is an object full of matched things, pass an object of sub-dom-spec:s, instead of a string selector: @@ -79,6 +79,12 @@ that is not found will instead result in that part of your DOM being null, or an empty array, in the case of a * selector. Finally, there is the xpath! keyword, which is similar to xpath, but it also mandates that whatever is returned is truthy. This is useful when you use the xpath functions returning strings, numbers and of course booleans, to assert things about the pages you want to run on, like 'xpath! count(//img) = 0', if you never want the script to run on pages with inline images, say. After you have called on(), you may call on.dom to do page scraping later on, returning whatever matched your selector(s) passed. Mandatory selectors which failed to match at this point will return undefined, optional selectors null: @@ -90,6 +96,11 @@ A readable way to detect a failed mandatory match is on.dom(...) === on.FAIL; Github pjax hook: for re-running a script's on() block for every pjax request to a site - add a pushstate hook as per http://goo.gl/LNSv1 -- and be sure to make your script reentrant, so that it won't try to process the same elements again, if they are still sitting around in the page (see ':not([augmented])') */ function on(opts) { @@ -111,6 +122,7 @@ function on(opts) { , 'xpath!': truthy($x) } } , inject: { fn: inject } } , name, test, me, my, mine ; @@ -131,11 +143,23 @@ function on(opts) { , script = get('name') , ready = get('ready') , load = get('load') , pushState = get('pushstate') , pjax_event = get('pjaxevent') , name, rule, test, result, retry ; if (typeof ready !== 'function' && typeof load !== 'function' && typeof pushState !== 'function') { alert('no on function'); throw new Error('on() needs at least a "ready" or "load" function!'); } if (pushState && history.pushState && (on.pushState = on.pushState || []).indexOf(opts) === -1) { on.pushState.push(opts); // make sure we don't re-register after navigation initPushState(pushState, pjax_event); } try { for (name in rules) { @@ -153,9 +177,11 @@ function on(opts) { return false; } if (ready) { ready.apply(opts, input.concat()); } if (load) window.addEventListener('load', function() { load.apply(opts, input.concat()); }); return input.concat(opts); @@ -164,6 +190,47 @@ function on(opts) { function isObject(x) { return Object_toString.call(x) === '[object Object]'; } function array(a) { return Array_slice.call(a, 0); } // array:ish => Array function arrayify(x) { return isArray(x) ? x : [x]; } // non-array? => Array function inject(fn, args) { var script = document.createElement('script') , parent = document.documentElement; args = JSON.stringify(args || []).slice(1, -1); script.textContent = '('+ fn +')('+ args +');'; parent.appendChild(script); parent.removeChild(script); } function initPushState(callback, pjax_event) { if (!history.pushState.armed) { inject(function(pjax_event) { function reportBack() { var e = document.createEvent('Events'); e.initEvent('history.pushState', !'bubbles', !'cancelable'); document.dispatchEvent(e); } var pushState = history.pushState; history.pushState = function on_pushState() { if (pjax_event && window.$ && $.pjax) $(document).one(pjax_event, reportBack); else setTimeout(reportBack, 0); return pushState.apply(this, arguments); }; }, [pjax_event]); history.pushState.armed = pjax_event; } retry = function after_pushState() { rules = Object.create(opts); rules.load = rules.pushstate = undefined; rules.ready = callback; on(rules); }; document.addEventListener('history.pushState', function() { if (debug) console.log('on.pushstate', location.pathname); retry(); }, false); } function test_query(spec) { var q = unparam(this); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1 @@ function on(d){function j(a){h[a]=void 0;return d[a]}function n(a){return"[object Array]"===A.call(a)}function B(a,b){var c=document.createElement("script"),f=document.documentElement,b=JSON.stringify(b||[]).slice(1,-1);c.textContent="("+a+")("+b+");";f.appendChild(c);f.removeChild(c)}function J(a){var b={};(this||"").replace(/\+/g,"%20").split("&").forEach(function(a){if(a=/^\??([^=]*)=(.*)/.exec(a)){var f,e,g=a[1],a=a[2];try{f=decodeURIComponent(g)}catch(d){f=unescape(g)}try{e=decodeURIComponent(a)}catch(h){e=unescape(a)}b[f]=e}});if(!0===a||null==a)return b;throw Error("bad query type "+typeof a+": "+a);}function K(a){n(a)||(a=n(a)?a:[a]);var b=a.shift();"string"===typeof b&&(b=RegExp(b));if(!(b instanceof RegExp))throw Error(typeof b+" was not a regexp: "+b);b=b.exec(this);if(null===b)return e;if(!a.length)return b;var c={};for(b.shift();a.length;)c[a.shift()]=b.shift();return c}function C(a){return function(b){var c=a.apply(this,arguments);return null!==c?c:e}}function D(a){return function(b){var c=a.apply(this,arguments);return c.length?c:e}}function E(a){a=this.querySelectorAll(a);return L.call(a,0)}function F(a){return this.querySelector(a)}function p(a){var b=(this.evaluate?this:this.ownerDocument).evaluate(a,this,null,0,null),c=[];switch(b.resultType){case b.STRING_TYPE:return b.stringValue;case b.NUMBER_TYPE:return b.numberValue;case b.BOOLEAN_TYPE:return b.booleanValue;default:for(;a=b.iterateNext();)c.push(a);return c}}function G(a){a=p.call(this,a);return a instanceof Array?a[0]||null:a}function l(a,b){function c(a){if("string"!==typeof a)throw Error("non-String dom match rule: "+a);var b=/^((?:css|xpath)[?+*!]?)\s+(.*)/.exec(a),c,d;b&&(c=b[1],a=b[2],d=l[c]);if(!d)throw Error("unknown dom match rule "+c+": "+a);return d.call(this,a)}var f,d,g;void 0===b&&(b=this);if(null===b||b===e)return e;if(n(b)){f=[];for(g=0;g<b.length;g++){d=l.call(b[g],a);if(d===e)return e;f.push(d)}return f}if("object"!==typeof b||!("nodeType"in b))throw Error("illegal context: "+b);if("string"===typeof a)return c.call(b,a);if(n(a))return b=c.call(b,a[0]),l.call(b,a[1]);if("[object Object]"===A.call(a)){f={};for(g in a){d=l.call(b,a[g]);if(d===e)return e;f[g]=d}return f}throw Error("dom spec was neither a String, Object nor Array: "+a);}var A=Object.prototype.toString,L=Array.prototype.slice;if(!("dom"in on)){var q={fn:K,self:location.pathname},m={fn:J,self:location.search},r=l,s=C(F),M=D(E),N=C(G),O=D(p),P=p,q={path_re:q,query:m,dom:{fn:r,self:document,my:{css:s,"css?":F,"css+":M,"css*":E,xpath:N,"xpath?":G,"xpath+":O,"xpath*":p,"xpath!":function(a){return P.apply(this,arguments)||e}}},inject:{fn:B}},i,k;for(i in q){m=q[i];r=m.fn;if(s=m.my)for(k in s)r[k]=s[k];on[i]=r.bind(m.self)}}var e=void 0,t=[],h=Object.create(d),H=j("debug");j("name");i=j("ready");var v=j("load"),w=j("pushstate");k=j("pjaxevent");var u,x,y,z,I;if("function"!==typeof i&&"function"!==typeof v&&"function"!==typeof w)throw alert("no on function"),Error('on() needs at least a "ready" or "load" function!');if(w&&history.pushState&&-1===(on.pushState=on.pushState||[]).indexOf(d))on.pushState.push(d),history.pushState.armed||(B(function(a){function b(){var a=document.createEvent("Events");a.initEvent("history.pushState",!1,!1);document.dispatchEvent(a)}var c=history.pushState;history.pushState=function(){if(a&&window.$&&$.pjax)$(document).one(a,b);else setTimeout(b,0);return c.apply(this,arguments)}},[k]),history.pushState.armed=k),I=function(){h=Object.create(d);h.load=h.pushstate=void 0;h.ready=w;on(h)},document.addEventListener("history.pushState",function(){H&&console.log("on.pushstate",location.pathname);I()},!1);try{for(u in h)if(x=h[u],void 0!==x){y=on[u];if(!y)throw Error('did not grok rule "'+u+'"!');z=y(x);if(z===e)return!1;t.push(z)}}catch(Q){return H&&console.warn("on(debug): we didn't run because "+Q.message),!1}i&&i.apply(d,t.concat());v&&window.addEventListener("load",function(){v.apply(d,t.concat())});return t.concat(d)}; -
johan created this gist
Oct 26, 2012 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,287 @@ /* coffee-script example usage - at https://github.com/johan/dotjs/commits/johan on path_re: ['^/([^/]+)/([^/]+)(/?.*)', 'user', 'repo', 'rest'] query: true dom: keyboard: 'css .keyboard-shortcuts' branches: 'css+ .js-filter-branches h4 a' dates: 'css* .commit-group-heading' tracker: 'css? #gauges-tracker[defer]' johan_ci: 'xpath* //li[contains(@class,"commit")][.//a[.="johan"]]' ready: (path, query, dom) -> ...would make something like this call, as the path regexp matched, and there were DOM matches for the two mandatory "keyboard" and "branches" selectors: ready( { user: 'johan', repo: 'dotjs', rest: '/commits/johan' } , {} // would contain all query args (if any were present) , { keyboard: Node<a href="#keyboard_shortcuts_pane"> , branches: [ Node<a href="/johan/dotjs/commits/coffee"> , Node<a href="/johan/dotjs/commits/dirs"> , Node<a href="/johan/dotjs/commits/gh-pages"> , Node<a href="/johan/dotjs/commits/johan"> , Node<a href="/johan/dotjs/commits/jquery-1.8.2"> , Node<a href="/johan/dotjs/commits/master"> ] , dates: [ Node<h3 class="commit-group-heading">Oct 07, 2012</h3> , Node<h3 class="commit-group-heading">Aug 29, 2012</h3> , ... ] , tracker: null , johan_ci: [ Node<li class="commit">, ... ] } ) A selector returns an array of matches prefixed for "css*" and "css+" (ditto xpath), and a single result if it is prefixed "css" or "css?": If your script should only run on pages with a particular DOM node (or set of nodes), use the 'css' or 'css+' (ditto xpath) forms - and your callback won't get fired on pages that lack them. The 'css?' and 'css*' forms would run your callback but pass null or [] respectively, on not finding such nodes. You may recognize the semantics of x, x?, x* and x+ from regular expressions. (see http://goo.gl/ejtMD for a more thorough discussion of something similar) The dom prtoperty is recursively defined so you can make nested structures. If you want a property that itself is an object full of matched things, pass an object of sub-dom-spec:s, instead of a string selector: on dom: meta: base: 'xpath? /head/base title: 'xpath string(/head/title)' commits: 'css* li.commit' ready: (dom) -> You can also deconstruct repeated templated sections of a page into subarrays scraped as per your specs, by picking a context node for a dom spec. This is done by passing a two-element array: a selector resolving what node/nodes you look at and a dom spec describing how you want it/them deconstructed for you: on dom: meta: [ 'xpath /head', base: 'xpath? base title: 'xpath string(title)' ] commits: [ 'css* li.commit', avatar_url: ['css img.gravatar', 'xpath string(@src)'] author_name: 'xpath string(.//*[@class="author-name"])' ] ready: (dom) -> The mandatory/optional selector rules defined above behave as you'd expect as used for context selectors too: a mandatory node or array of nodes will limit what pages your script gets called on to those that match it, so your code is free to assume it will always be there when it runs. An optional context node that is not found will instead result in that part of your DOM being null, or an empty array, in the case of a * selector. After you have called on(), you may call on.dom to do page scraping later on, returning whatever matched your selector(s) passed. Mandatory selectors which failed to match at this point will return undefined, optional selectors null: on.dom('xpath //a[@id]') => undefined or <a id="..."> on.dom('xpath? //a[@id]') => null or <a id="..."> on.dom('xpath+ //a[@id]') => undefined or [<a id="...">, <a id="...">, ...] on.dom('xpath* //a[@id]') => [] or [<a id="...">, <a id="...">, ...] A readable way to detect a failed mandatory match is on.dom(...) === on.FAIL; */ function on(opts) { var Object_toString = Object.prototype.toString , Array_slice = Array.prototype.slice , FAIL = 'dom' in on ? undefined : (function() { var tests = { path_re: { fn: test_regexp, self: location.pathname } , query: { fn: test_query, self: location.search } , dom: { fn: test_dom, self: document , my: { 'css': not_null($C) , 'css?': $C , 'css+': one_or_more($c) , 'css*': $c , 'xpath': not_null($X) , 'xpath?': $X , 'xpath+': one_or_more($x) , 'xpath*': $x , 'xpath!': truthy($x) } } } , name, test, me, my, mine ; for (name in tests) { test = tests[name]; me = test.fn; if ((my = test.my)) for (mine in my) me[mine] = my[mine]; on[name] = me.bind(test.self); } })() , input = [] // args for the callback(s?) the script wants to run , rules = Object.create(opts) // wraps opts in a pokeable inherit layer , debug = get('debug') , script = get('name') , ready = get('ready') , load = get('load') , name, rule, test, result ; if (typeof ready !== 'function' && typeof load !== 'function') throw new Error('on() needs at least a "ready" or "load" function!'); try { for (name in rules) { rule = rules[name]; if (rule === undefined) continue; // was some callback or other non-rule test = on[name]; if (!test) throw new Error('did not grok rule "'+ name +'"!'); result = test(rule); if (result === FAIL) return false; // the page doesn't satisfy all rules input.push(result); } } catch(e) { if (debug) console.warn("on(debug): we didn't run because " + e.message); return false; } if (ready) ready.apply(opts, input.concat()); if (load) window.addEventListener('load', function() { ready.apply(opts, input.concat()); }); return input.concat(opts); function get(x) { rules[x] = undefined; return opts[x]; } function isArray(x) { return Object_toString.call(x) === '[object Array]'; } function isObject(x) { return Object_toString.call(x) === '[object Object]'; } function array(a) { return Array_slice.call(a, 0); } // array:ish => Array function arrayify(x) { return isArray(x) ? x : [x]; } // non-array? => Array function test_query(spec) { var q = unparam(this); if (spec === true || spec == null) return q; // decode the query for me! throw new Error('bad query type '+ (typeof spec) +': '+ spec); } function unparam(query) { var data = {}; (query || '').replace(/\+/g, '%20').split('&').forEach(function(kv) { kv = /^\??([^=]*)=(.*)/.exec(kv); if (!kv) return; var prop, val, k = kv[1], v = kv[2], e, m; try { prop = decodeURIComponent(k); } catch (e) { prop = unescape(k); } try { val = decodeURIComponent(v); } catch (e) { val = unescape(v); } data[prop] = val; }); return data; } function test_regexp(spec) { if (!isArray(spec)) spec = arrayify(spec); var re = spec.shift(); if (typeof re === 'string') re = new RegExp(re); if (!(re instanceof RegExp)) throw new Error((typeof re) +' was not a regexp: '+ re); var ok = re.exec(this); if (ok === null) return FAIL; if (!spec.length) return ok; var named = {}; ok.shift(); // drop matching-whole-regexp part while (spec.length) named[spec.shift()] = ok.shift(); return named; } function truthy(fn) { return function(s) { var x = fn.apply(this, arguments); return x || FAIL; }; } function not_null(fn) { return function(s) { var x = fn.apply(this, arguments); return x !== null ? x : FAIL; }; } function one_or_more(fn) { return function(s) { var x = fn.apply(this, arguments); return x.length ? x : FAIL; }; } function $c(css) { return array(this.querySelectorAll(css)); } function $C(css) { return this.querySelector(css); } function $x(xpath) { var doc = this.evaluate ? this : this.ownerDocument, next; var got = doc.evaluate(xpath, this, null, 0, null), all = []; switch (got.resultType) { case got.STRING_TYPE: return got.stringValue; case got.NUMBER_TYPE: return got.numberValue; case got.BOOLEAN_TYPE: return got.booleanValue; default: while ((next = got.iterateNext())) all.push(next); return all; } } function $X(xpath) { var got = $x.call(this, xpath); return got instanceof Array ? got[0] || null : got; } // DOM constraint tester / scraper facility: // "this" is the context Node(s) - initially the document // "spec" is either of: // * css / xpath Selector "selector_type selector" // * resolved for context [ context Selector, spec ] // * an Object of spec(s) { property_name: spec, ... } function test_dom(spec, context) { function lookup(rule) { if (typeof rule !== 'string') throw new Error('non-String dom match rule: '+ rule); var match = /^((?:css|xpath)[?+*!]?)\s+(.*)/.exec(rule), type, func; if (match) { type = match[1]; rule = match[2]; func = test_dom[type]; } if (!func) throw new Error('unknown dom match rule '+ type +': '+ rule); return func.call(this, rule); } var results, result, i, property_name; if (context === undefined) context = this; // validate context: if (context === null || context === FAIL) return FAIL; if (isArray(context)) { for (results = [], i = 0; i < context.length; i++) { result = test_dom.call(context[i], spec); if (result === FAIL) return FAIL; results.push(result); } return results; } if (typeof context !== 'object' || !('nodeType' in context)) throw new Error('illegal context: '+ context); // handle input spec format: if (typeof spec === 'string') return lookup.call(context, spec); if (isArray(spec)) { context = lookup.call(context, spec[0]); return test_dom.call(context, spec[1]); } if (isObject(spec)) { results = {}; for (property_name in spec) { result = test_dom.call(context, spec[property_name]); if (result === FAIL) return FAIL; results[property_name] = result; } return results; } throw new Error("dom spec was neither a String, Object nor Array: "+ spec); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,6 @@ function on(h){function l(a){v[a]=void 0;return h[a]}function m(a){return"[object Array]"===z.call(a)}function F(a){var b={};(this||"").replace(/\+/g,"%20").split("&").forEach(function(a){if(a=/^\??([^=]*)=(.*)/.exec(a)){var c,f,g=a[1],a=a[2];try{c=decodeURIComponent(g)}catch(N){c=unescape(g)}try{f=decodeURIComponent(a)}catch(d){f=unescape(a)}b[c]=f}});if(!0===a||null==a)return b;throw Error("bad query type "+typeof a+": "+a);}function G(a){m(a)||(a=m(a)?a:[a]);var b=a.shift();"string"===typeof b&& (b=RegExp(b));if(!(b instanceof RegExp))throw Error(typeof b+" was not a regexp: "+b);b=b.exec(this);if(null===b)return c;if(!a.length)return b;var e={};for(b.shift();a.length;)e[a.shift()]=b.shift();return e}function A(a){return function(b){var e=a.apply(this,arguments);return null!==e?e:c}}function B(a){return function(b){var e=a.apply(this,arguments);return e.length?e:c}}function C(a){a=this.querySelectorAll(a);return H.call(a,0)}function D(a){return this.querySelector(a)}function n(a){var b=(this.evaluate? this:this.ownerDocument).evaluate(a,this,null,0,null),c=[];switch(b.resultType){case b.STRING_TYPE:return b.stringValue;case b.NUMBER_TYPE:return b.numberValue;case b.BOOLEAN_TYPE:return b.booleanValue;default:for(;a=b.iterateNext();)c.push(a);return c}}function E(a){a=n.call(this,a);return a instanceof Array?a[0]||null:a}function i(a,b){function e(a){if("string"!==typeof a)throw Error("non-String dom match rule: "+a);var b=/^((?:css|xpath)[?+*!]?)\s+(.*)/.exec(a),c,d;b&&(c=b[1],a=b[2],d=i[c]);if(!d)throw Error("unknown dom match rule "+ c+": "+a);return d.call(this,a)}var d,f,g;void 0===b&&(b=this);if(null===b||b===c)return c;if(m(b)){d=[];for(g=0;g<b.length;g++){f=i.call(b[g],a);if(f===c)return c;d.push(f)}return d}if("object"!==typeof b||!("nodeType"in b))throw Error("illegal context: "+b);if("string"===typeof a)return e.call(b,a);if(m(a))return b=e.call(b,a[0]),i.call(b,a[1]);if("[object Object]"===z.call(a)){d={};for(g in a){f=i.call(b,a[g]);if(f===c)return c;d[g]=f}return d}throw Error("dom spec was neither a String, Object nor Array: "+ a);}var z=Object.prototype.toString,H=Array.prototype.slice;if(!("dom"in on)){var p={fn:G,self:location.pathname},j={fn:F,self:location.search},q=i,r=A(D),I=B(C),J=A(E),K=B(n),L=n,p={path_re:p,query:j,dom:{fn:q,self:document,my:{css:r,"css?":D,"css+":I,"css*":C,xpath:J,"xpath?":E,"xpath+":K,"xpath*":n,"xpath!":function(a){return L.apply(this,arguments)||c}}}},k,d;for(k in p){j=p[k];q=j.fn;if(r=j.my)for(d in r)q[d]=r[d];on[k]=q.bind(j.self)}}var c=void 0,s=[],v=Object.create(h);k=l("debug");l("name"); var t=l("ready");d=l("load");var u,w,x,y;if("function"!==typeof t&&"function"!==typeof d)throw Error('on() needs at least a "ready" or "load" function!');try{for(u in v)if(w=v[u],void 0!==w){x=on[u];if(!x)throw Error('did not grok rule "'+u+'"!');y=x(w);if(y===c)return!1;s.push(y)}}catch(M){return k&&console.warn("on(debug): we didn't run because "+M.message),!1}t&&t.apply(h,s.concat());d&&window.addEventListener("load",function(){t.apply(h,s.concat())});return s.concat(h)};