Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 6186

Show
Ignore:
Timestamp:
02/21/07 12:04:56 (1 year ago)
Author:
christophe
Message:

- Merged unit tests from Andrew and I
- Merged in my inline, togglable benchmarking in the unit tests
- Fixed assertEnumEqual, just in case its arguments are NOT arrays: the zip

call wouldn't work in the prior version.

- Renamed 'sibling' to 'laterSibling': was a misnomer.
- Optimized XPath for ..^=..
- Fixed XPath for :first-child, :last-child, :only-child, :checked,

:disabled, :enabled

- Made a few loops more consistent in style (inline declaration of results).
- Fixed handlers.laterSibling to actually use nextSiblings instead of

siblings

- Fixed handlers.tagName to deal properly with contextual use of '*'
- Fixed handlers.id to return [] instead of [null] on doc-context

nonexistent ID

- Removed incorrect optimization of x ~ #y, defaults to regular handler

processing

- Slight optimisation of handlers.byClassName by using strings instead of

Arrays and joins.

- Fixed handlers.(next|previous)ElementSibling, which assumed there would be

at least one sibling...

- Removed obsolete combinator argument on pseudo handler invocation.
- Slight optimisation using else's in nth-child handler.
- Fixed findChildElements which, using concat instead of handlers.concat,

was (a) suboptimal and (b) incorrectly processing raw NodeLists when
returned by the matcher (since we removed $A overhead from tagName, for
instance).

- Added full TO-DO at the end of the script, so everybody sees what's ahead.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • spinoffs/prototype/branches/selector/src/selector.js

    r6183 r6186  
    8484   
    8585  match: function(element) { 
     86    // XXX TDD->Andrew: this prevents reusing the Selector on a changed DOM 
    8687    this.results = this.results || this.findElements(document); 
    8788    return this.results.include(element); 
    88     //return this.findElements(document).include(element); 
    8989  } 
    9090}; 
     
    9797    child:        "/", 
    9898    adjacent:     "/following-sibling::[1]", 
    99     sibling:      '/following-sibling::', 
     99    laterSibling: '/following-sibling::', 
    100100    tagName:      "#{1}", 
    101101    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]", 
     
    108108    pseudo: function(m) { 
    109109      var h = Selector.xpath.pseudos[m[1]]; 
     110      // XXX TDD->Andrew: shouldn't we return '' instead? 
    110111      if (!h) return; 
    111112      if (typeof h === 'function') return h(m); 
     
    116117      '=':  "[@#{1}='#{3}']", 
    117118      '!=': "[@#{1}!='#{3}']", 
    118       '^=': "[substring(@#{1}, 1, string-length('#{3}'))='#{3}']", 
     119      '^=': "[starts-with(@#{1}, '#{3}')]", 
    119120      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", 
    120121      '*=': "[contains(@#{1}, '#{3}')]", 
     
    123124    }, 
    124125    pseudos: { 
    125       'first-child': '[position()=1]', 
    126       'last-child':  '[position()=last()]', 
     126      'first-child': '[not(preceding-sibling::*)]', 
     127      'last-child':  '[not(following-sibling::*)]', 
     128      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]', 
    127129      'empty':       '[not(node())]', 
    128       'checked':     "[@checked='checked']", 
    129       'enabled':     "[not(@disabled='disabled')]", 
    130       'disabled':    "[@disabled='disabled']", 
    131       'not':         "[not(#{4})]", 
     130      'checked':     "[@checked='' or @checked='checked' or @checked='true' or @checked='yes']", 
     131      'enabled':     "[@disabled and not(@disabled='' or @disabled='disabled' or @disabled='true' or @disabled='yes')]", 
     132      'disabled':    "[@disabled='' or @disabled='disabled' or @disabled='true' or @disabled='yes']", 
    132133      'not':         function(m) { 
     134        // XXX TDD->Andrew: shouldn't we return '' instead? 
    133135        if (!m[4]) return; 
    134136        for (var i in Selector.patterns) { 
     
    150152          var a = m[1] !== undefined ? Number(m[1]) : 1; 
    151153          var b = m[3] !== undefined ? Number(m[2]) : 0; 
     154          // TODO: fix this, it won't work due to position's semantics. 
    152155          predicate = "position() mod " + mm[1] + " = " + mm[3]; 
    153156        } 
     
    170173    child:        'd = "child";', 
    171174    adjacent:     'd = "adjacent";', 
    172     sibling:      'd = "sibling";' 
     175    laterSibling: 'd = "laterSibling";' 
    173176  }, 
    174177 
     
    176179    // combinators must be listed first 
    177180    // (and descendant needs to be last combinator) 
    178     sibling:      /^\s*~\s*/, 
     181    laterSibling: /^\s*~\s*/, 
    179182    child:        /^\s*>\s*/, 
    180183    adjacent:     /^\s*\+\s*/, 
     
    199202     
    200203    child: function(nodes) { 
    201       var results = []; 
    202       for (var i = 0, node; node = nodes[i]; i++) 
     204      for (var i = 0, results = [], node; node = nodes[i]; i++) 
    203205        Selector.handlers.concat(results, Element.immediateDescendants(node)); 
    204206      return results; 
     
    206208     
    207209    adjacent: function(nodes) { 
    208       var results = []; 
    209       for (var i = 0, node; node = nodes[i]; i++) { 
     210      for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    210211        var next = this.nextElementSibling(node); 
    211212        if (next) results.push(next); 
     
    214215    }, 
    215216     
    216     sibling: function(nodes) { 
    217       var results = []; 
    218       for (var i = 0, node; node = nodes[i]; i++) 
    219         Selector.handlers.concat(results, Element.siblings(node)); 
     217    laterSibling: function(nodes) { 
     218      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     219        Selector.handlers.concat(results, Element.nextSiblings(node)); 
    220220      return results; 
    221221    }, 
     
    235235          } 
    236236          nodes = this[combinator](nodes); 
     237          if ('*' == tagName) return nodes; 
    237238        } 
    238239        for (var i = 0, node; node = nodes[i]; i++) { 
     
    246247     
    247248    className: function(nodes, root, className, combinator) { 
    248       var results = [], h = Selector.handlers; 
     249      var h = Selector.handlers; 
    249250      if (nodes) { 
    250251        if (combinator) nodes = this[combinator](nodes); 
     
    252253      } 
    253254      return h.byClassName(nodes, root, className); 
    254       //return document.getElementsByClassName(className, root); 
    255255    }, 
    256256     
    257257    id: function(nodes, root, id, combinator) { 
    258258      var targetNode = $(id); 
    259       if (!nodes && root == document) return [targetNode]; 
     259      if (!nodes && root == document) return targetNode ? [targetNode] : []; 
    260260      if (nodes) { 
    261261        if (combinator) { 
     
    268268          } else if (combinator == 'adjacent') { 
    269269            for (var i = 0, node; node = nodes[i]; i++) 
    270               if (targetNode.previousSibling == node) return [targetNode]; 
    271           } else if (combinator == 'sibling') { 
    272             if (nodes.include(targetNode)) return [targetNode]; 
     270              if (Selector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; 
    273271          } else nodes = Selector.handlers[combinator](nodes); 
    274272        }  
     
    313311        node._counted = true; 
    314312      return nodes; 
    315     }, 
     313    }, // RESUME 
    316314     
    317315    unmark: function(nodes) { 
     
    333331        } 
    334332      } 
     333      // TODO: check whether multiple calls won't require unmarking nodes 
    335334      return Selector.handlers.unmark(results); 
    336335    }, 
     
    338337    byClassName: function(nodes, root, className) { 
    339338      if (!nodes) nodes = Selector.handlers.descendant([root]); 
    340       var needle = [" ", className, " "].join('')
     339      var needle = ' ' + className + ' '
    341340      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { 
    342341        nodeClassName = node.className; 
    343342        if (nodeClassName.length == 0) continue; 
    344         if (nodeClassName == className ||  
    345           [" ", nodeClassName, " "].join('').include(needle)) 
     343        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) 
    346344          results.push(node); 
    347345      } 
     
    350348         
    351349    nextElementSibling: function(node) { 
    352       do { 
    353         var node = node.nextSibling; 
    354       } while (node.nodeType != 1 && node.nextSibling);  
    355       return (node.nodeType == 1) ? node : null; 
     350      while (node = node.nextSibling) 
     351        if (node.nodeType == 1) return node; 
     352      return null; 
    356353    }, 
    357354     
    358355    previousElementSibling: function(node) { 
    359       do { 
    360         var node = node.previousSibling; 
    361       } while (node.nodeType != 1 && node.previousSibling); 
    362       return (node.nodeType == 1) ? node : null; 
     356      while (node = node.previousSibling) 
     357        if (node.nodeType == 1) return node; 
     358      return null; 
    363359    }, 
    364360     
    365361    pseudo: function(nodes, name, value, root, combinator) { 
    366362      if (combinator) nodes = this[combinator](nodes); 
    367       return Selector.pseudos[name](nodes, value, root, combinator); 
     363      return Selector.pseudos[name](nodes, value, root); 
    368364    }, 
    369365     
     
    406402        for (var i = 0, node; node = nodes[i]; i++) 
    407403          if ((node.nodeIndex + 1) % 2 == mod) results.push(node); 
    408       } 
    409        
    410       if (formula.match(/^\d+$/)) { // just a number 
     404      } else if (formula.match(/^\d+$/)) { // just a number 
    411405        formula = Number(formula); 
    412406        for (var i = 0, node; node = nodes[i]; i++) 
    413407          if (node.nodeIndex == formula) results.push(node); 
    414       } 
    415  
    416       if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b 
     408      } else if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b 
    417409        var a = m[1] !== undefined ? Number(m[1]) : 1; 
    418410        var b = m[3] !== undefined ? Number(m[2]) : 0; 
     
    428420    'only-child': function(nodes, value, root) { 
    429421      var h = Selector.handlers; 
    430       for (var i = 0, results = [], node; node = nodes[i]; i++) { 
    431         if (!h.previousElementSibling(node) && 
    432           !h.nextElementSibling(node)) { 
     422      for (var i = 0, results = [], node; node = nodes[i]; i++) 
     423        if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) 
    433424          results.push(node);   
    434         } 
    435       } 
    436425      return results; 
    437426    }, 
     
    488477    for (var i = 0, l = expressions.length; i < l; i++) { 
    489478      selector = new Selector(expressions[i].strip()); 
    490       results  = results.concat(selector.findElements(element)); 
     479      Selector.handlers.concat(results, selector.findElements(element)); 
    491480    } 
    492481    return results; 
     
    498487} 
    499488 
    500  
    501 // TODO: 
    502 // (remaining pseudoclasses) 
    503 // nth-last-child 
    504 // nth-of-type 
    505 // nth-last-of-type 
    506 // 
     489/* 
     490TODO: 
     491 
     492- PC/JS:      TEST unique on multiple calls.  unmark(nodes) if needed. 
     493- PC/JS:      IMPROVE attr ~= and |= (strings).  Benchmark.  Revert if negligible. 
     494- PC/XPath:   FIX empty.  TEST. 
     495 
     496- PC/XPath:   ADD (first|last)-of-type 
     497- PC/Pattern: ADD (first|last)-of-type 
     498- PC/JS:      ADD (first|last)-of-type.  TEST. 
     499 
     500- PC/XPath:   CHECK/IMPROVE not. 
     501- PC/JS:      CHECK/IMPROVE not.  TEST. 
     502 
     503- PC/XPath:   ADD nth[-last]-(child|of-type) 
     504- PC/Pattern: ADD nth[-last]-(child|of-type) 
     505- PC/JS:      CHECK/IMPROVE nth-child 
     506- PC/JS:      ADD nth-[-last]-(child|of-type).  TEST. 
     507*/ 
  • spinoffs/prototype/branches/selector/test/lib/unittest.js

    r5649 r6186  
    306306  assertEnumEqual: function(expected, actual) { 
    307307    var message = arguments[2] || "assertEnumEqual"; 
    308     try { $A(expected).length == $A(actual).length &&  
     308    expected = $A(expected); 
     309    actual = $A(actual); 
     310    try { expected.length == actual.length &&  
    309311      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? 
    310312        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +  
  • spinoffs/prototype/branches/selector/test/unit/selector.html

    r6183 r6186  
    11<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    22        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
    3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
     3<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" 
     4 xmlns:html="http://www.w3.org/1999/xhtml"> 
    45<head> 
    56  <title>Prototype Unit test file</title> 
     
    3940  <form id="troubleForm"><input type="hidden" name="id" /></form> 
    4041   
     42  <html:p id="nsP"></html:p> 
    4143   
     44  <div id="level1"> 
     45    <span id="level2_1"> 
     46        <span id="level3_1"></span> 
     47        <!-- This comment should be ignored by the adjacent selector --> 
     48        <span id="level3_2"></span> 
     49    </span> 
     50    <span id="level2_2"><em id="level_only_child"></em></span> 
     51    <div id="level2_3"></div> 
     52  </div> 
     53 
     54  <div id="dupContainer"> 
     55    <span id="dupL1"> 
     56      <span id="dupL2"> 
     57        <span id="dupL3"> 
     58          <span id="dupL4"> 
     59            <span id="dupL5"></span> 
     60          </span> 
     61        </span> 
     62      </span> 
     63    </span> 
     64  </div> 
     65</div> 
     66 
    4267  <div id="grandfather"> grandfather     
    4368    <div id="father" class="brothers men"> father       
     
    5479<script type="text/javascript" language="javascript" charset="utf-8"> 
    5580// <![CDATA[ 
     81 
     82// Added by TDD - 2007.02.20 
     83$RunBenchmarks = false; 
     84 
    5685  new Test.Unit.Runner({ 
    5786     
     
    135164    }}, 
    136165     
    137     testSelectorWithSubstringAttributeValues: function() {with(this) { 
    138       assertEnumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string'); 
    139       assertEnumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string'); 
    140       assertEnumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring') 
    141     }}, 
    142      
    143166    test$$WithNestedAttributeSelectors: function() {with(this) { 
    144167      assertEnumEqual([$('strong')], $$('div[style] p[id] strong')); 
     
    167190    }}, 
    168191     
    169     testChildCombinator: function() {with(this) { 
     192    // AND NOW COME THOSE NEW TESTS AFTER ANDREW'S REWRITE! 
     193 
     194    testSelectorWithNamespacedAttributes: function() { with(this) { 
     195      if (Prototype.BrowserFeatures.XPath) { 
     196        assertUndefined(new Selector('html[xml:lang]').xpath); 
     197        assertUndefined(new Selector('body p[xml:lang]').xpath); 
     198        assertNotUndefined(new Selector('html:p#nsP').xpath); 
     199      } else 
     200        info("Could not test XPath bypass: no XPath to begin with!"); 
     201    }}, 
     202 
     203    testSelectorWithChild: function() { with(this) { 
    170204      assertEnumEqual($('link_1', 'link_2'), $$('p.first > a')); 
    171205      assertEnumEqual($('father', 'uncle'), $$('div#grandfather > div')); 
    172     }}, 
    173      
    174     testAdjacencyCombinator: function() {with(this) { 
     206      assertEnumEqual($('level2_1', 'level2_2'), $$('#level1>span')); 
     207      assertEnumEqual($('level2_1', 'level2_2'), $$('#level1 > span')); 
     208      assertEnumEqual($('level3_1', 'level3_2'), $$('#level2_1 > *')); 
     209      $RunBenchmarks && wait(500, function() { 
     210        benchmark(function() { $$('#level1 > span') }, 1000); 
     211      }); 
     212    }}, 
     213 
     214    testSelectorWithAdjacence: function() { with(this) { 
    175215      assertEnumEqual([$('uncle')], $$('div.brothers + div.brothers')); 
    176216      assertEnumEqual([$('uncle')], $$('div.brothers + div'));       
    177     }}, 
    178      
    179     testGeneralSiblingCombinator: function() {with(this) { 
     217      assertEqual($('level2_2'), $$('#level2_1+span').reduce()); 
     218      assertEqual($('level2_2'), $$('#level2_1 + span').reduce()); 
     219      assertEqual($('level2_2'), $$('#level2_1 + *').reduce()); 
     220      assertEnumEqual([], $$('#level2_2 + span')); 
     221      assertEqual($('level3_2'), $$('#level3_1 + span').reduce()); 
     222      assertEqual($('level3_2'), $$('#level3_1 + *').reduce()); 
     223      assertEnumEqual([], $$('#level3_2 + *')); 
     224      assertEnumEqual([], $$('#level3_1 + em')); 
     225      $RunBenchmarks && wait(500, function() { 
     226        benchmark(function() { $$('#level3_1 + span') }, 1000); 
     227      }); 
     228    }}, 
     229 
     230    testSelectorWithLaterSibling: function() { with(this) { 
    180231      assertEnumEqual([$('list')], $$('h1 ~ ul')); 
    181     }}, 
    182  
    183     testUniquenessOfResultSet: function() {with(this) { 
     232      assertEqual($('level2_2'), $$('#level2_1 ~ span').reduce()); 
     233      assertEnumEqual($('level2_2', 'level2_3'), $$('#level2_1 ~ *').reduce()); 
     234      assertEnumEqual([], $$('#level2_2 ~ span')); 
     235      assertEnumEqual([], $$('#level3_2 ~ *')); 
     236      assertEnumEqual([], $$('#level3_1 ~ em')); 
     237      assertEnumEqual([$('level3_2')], $$('#level3_1 ~ #level3_2')); 
     238      assertEnumEqual([$('level3_2')], $$('span ~ #level3_2')); 
     239      assertEnumEqual([], $$('div ~ #level3_2')); 
     240      assertEnumEqual([], $$('div ~ #level2_3')); 
     241      $RunBenchmarks && wait(500, function() { 
     242        benchmark(function() { $$('#level2_1 ~ span') }, 1000); 
     243      }); 
     244    }}, 
     245 
     246    testSelectorWithNewAttributeOperators: function() { with(this) { 
     247      assertEnumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string'); 
     248      assertEnumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string'); 
     249      assertEnumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring') 
     250      assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^="level2_"]')); 
     251      assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^=level2_]')); 
     252      assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$="_1"]')); 
     253      assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$=_1]')); 
     254      assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*="2"]')); 
     255      assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*=2]')); 
     256      $RunBenchmarks && wait(500, function() { 
     257        benchmark(function() { $$('#level1 *[id^=level2_]') }, 1000, '[^=]'); 
     258        benchmark(function() { $$('#level1 *[id$=_1]') }, 1000, '[$=]'); 
     259        benchmark(function() { $$('#level1 *[id*=_2]') }, 1000, '[*=]'); 
     260      }); 
     261    }}, 
     262 
     263    testSelectorWithDuplicates: function() { with(this) { 
    184264      assertEnumEqual($$('div div'), $$('div div').uniq()); 
    185     }} 
    186          
     265      assertEnumEqual($('dupL2', 'dupL3', 'dupL4', 'dupL5'), $$('#dupContainer span span')); 
     266      $RunBenchmarks && wait(500, function() { 
     267        benchmark(function() { $$('#dupContainer span span') }, 1000); 
     268      }); 
     269    }}, 
     270 
     271    testSelectorPCFirstLastOnlyChild: function() { with(this) { 
     272      assertEnumEqual([$('level2_1')], $$('#level1>*:first-child')); 
     273      assertEnumEqual($('level2_1', 'level3_1', 'level_only_child'), $$('#level1 *:first-child')); 
     274      assertEnumEqual([$('level2_3')], $$('#level1>*:last-child')); 
     275      assertEnumEqual($('level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:last-child')); 
     276      assertEnumEqual([$('level2_3')], $$('#level1>div:last-child')); 
     277      assertEnumEqual([$('level2_3')], $$('#level1 div:last-child')); 
     278      assertEnumEqual([], $$('#level1>div:first-child')); 
     279      assertEnumEqual([], $$('#level1>span:last-child')); 
     280      assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 span:first-child')); 
     281      assertEnumEqual([], $$('#level1:first-child')); 
     282      assertEnumEqual([], $$('#level1>*:only-child')); 
     283      assertEnumEqual([$('level_only_child')], $$('#level1 *:only-child')); 
     284      assertEnumEqual([], $$('#level1:only-child')); 
     285      $RunBenchmarks && wait(500, function() { 
     286        benchmark(function() { $$('#level1 *:first-child') }, 1000, ':first-child'); 
     287        benchmark(function() { $$('#level1 *:last-child') }, 1000, ':last-child'); 
     288        benchmark(function() { $$('#level1 *:only-child') }, 1000, ':only-child'); 
     289      }); 
     290    }}, 
     291     
    187292  }, 'testlog'); 
    188293// ]]>