feat(selector): support , for multiple targets

Fixes #867
Closes #1019
This commit is contained in:
Marc Laval 2015-03-19 17:01:42 +01:00
parent 0fb9f3bd6c
commit 41b53e71e1
6 changed files with 172 additions and 87 deletions

View File

@ -248,7 +248,7 @@ export class Directive extends Injectable {
* - `[attribute]`: select by attribute name. * - `[attribute]`: select by attribute name.
* - `[attribute=value]`: select by attribute name and value. * - `[attribute=value]`: select by attribute name and value.
* - `:not(sub_selector)`: select only if the element does not match the `sub_selector`. * - `:not(sub_selector)`: select only if the element does not match the `sub_selector`.
* - `selector1, selector2`: select if either `selector1` or `selector2` matches. [TO BE IMPLMENTED] * - `selector1, selector2`: select if either `selector1` or `selector2` matches.
* *
* *
* ## Example * ## Example

View File

@ -36,8 +36,8 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher = new SelectorMatcher(); this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) { for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i]; var directiveMetadata = directives[i];
selector=CssSelector.parse(directiveMetadata.annotation.selector); selector = CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectable(selector, directiveMetadata); this._selectorMatcher.addSelectables(selector, directiveMetadata);
} }
} }

View File

@ -9,7 +9,9 @@ var _SELECTOR_REGEXP =
RegExpWrapper.create('(\\:not\\()|' + //":not(" RegExpWrapper.create('(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag" '([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class" '(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]" '(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(?:\\))|' + // ")"
'(\\s*,\\s*)'); // ","
/** /**
* A css selector contains an element name, * A css selector contains an element name,
@ -21,7 +23,15 @@ export class CssSelector {
classNames:List; classNames:List;
attrs:List; attrs:List;
notSelector: CssSelector; notSelector: CssSelector;
static parse(selector:string): CssSelector { static parse(selector:string): List<CssSelector> {
var results = ListWrapper.create();
var _addResult = (res, cssSel) => {
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element)
&& ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = "*";
}
ListWrapper.push(res, cssSel);
}
var cssSelector = new CssSelector(); var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector); var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match; var match;
@ -43,13 +53,13 @@ export class CssSelector {
if (isPresent(match[4])) { if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]); current.addAttribute(match[4], match[5]);
} }
if (isPresent(match[6])) {
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
} }
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element) _addResult(results, cssSelector);
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) { return results;
cssSelector.element = "*";
}
return cssSelector;
} }
constructor() { constructor() {
@ -119,6 +129,7 @@ export class SelectorMatcher {
_classPartialMap:Map; _classPartialMap:Map;
_attrValueMap:Map; _attrValueMap:Map;
_attrValuePartialMap:Map; _attrValuePartialMap:Map;
_listContexts:List;
constructor() { constructor() {
this._elementMap = MapWrapper.create(); this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create(); this._elementPartialMap = MapWrapper.create();
@ -128,6 +139,19 @@ export class SelectorMatcher {
this._attrValueMap = MapWrapper.create(); this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create(); this._attrValuePartialMap = MapWrapper.create();
this._listContexts = ListWrapper.create();
}
addSelectables(cssSelectors:List<CssSelector>, callbackCtxt) {
var listContext = null;
if (cssSelectors.length > 1) {
listContext= new SelectorListContext(cssSelectors);
ListWrapper.push(this._listContexts, listContext);
}
for (var i = 0; i < cssSelectors.length; i++) {
this.addSelectable(cssSelectors[i], callbackCtxt, listContext);
}
} }
/** /**
@ -135,12 +159,12 @@ export class SelectorMatcher {
* @param cssSelector A css selector * @param cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function * @param callbackCtxt An opaque object that will be given to the callback of the `match` function
*/ */
addSelectable(cssSelector:CssSelector, callbackCtxt) { addSelectable(cssSelector, callbackCtxt, listContext: SelectorListContext) {
var matcher = this; var matcher = this;
var element = cssSelector.element; var element = cssSelector.element;
var classNames = cssSelector.classNames; var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs; var attrs = cssSelector.attrs;
var selectable = new SelectorContext(cssSelector, callbackCtxt); var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (isPresent(element)) { if (isPresent(element)) {
@ -215,6 +239,10 @@ export class SelectorMatcher {
var classNames = cssSelector.classNames; var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs; var attrs = cssSelector.attrs;
for (var i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false;
}
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result; result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result; result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
@ -282,26 +310,41 @@ export class SelectorMatcher {
} }
class SelectorListContext {
selectors: List<CssSelector>;
alreadyMatched: boolean;
constructor(selectors:List<CssSelector>) {
this.selectors = selectors;
this.alreadyMatched = false;
}
}
// Store context to pass back selector and context when a selector is matched // Store context to pass back selector and context when a selector is matched
class SelectorContext { class SelectorContext {
selector:CssSelector; selector:CssSelector;
notSelector:CssSelector; notSelector:CssSelector;
cbContext; // callback context cbContext; // callback context
listContext: SelectorListContext;
constructor(selector:CssSelector, cbContext) { constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) {
this.selector = selector; this.selector = selector;
this.notSelector = selector.notSelector; this.notSelector = selector.notSelector;
this.cbContext = cbContext; this.cbContext = cbContext;
this.listContext = listContext;
} }
finalize(cssSelector: CssSelector, callback) { finalize(cssSelector: CssSelector, callback) {
var result = true; var result = true;
if (isPresent(this.notSelector)) { if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = new SelectorMatcher(); var notMatcher = new SelectorMatcher();
notMatcher.addSelectable(this.notSelector, null); notMatcher.addSelectable(this.notSelector, null, null);
result = !notMatcher.match(cssSelector, null); result = !notMatcher.match(cssSelector, null);
} }
if (result && isPresent(callback)) { if (result && isPresent(callback) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
if (isPresent(this.listContext)) {
this.listContext.alreadyMatched = true;
}
callback(this.selector, this.cbContext); callback(this.selector, this.cbContext);
} }
return result; return result;

View File

@ -52,7 +52,7 @@ export class Parse5DomAdapter extends DomAdapter {
} }
}; };
var matcher = new SelectorMatcher(); var matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector)); matcher.addSelectables(CssSelector.parse(selector));
_recursive(res, el, selector, matcher); _recursive(res, el, selector, matcher);
return res; return res;
} }
@ -64,7 +64,7 @@ export class Parse5DomAdapter extends DomAdapter {
var result = false; var result = false;
if (matcher == null) { if (matcher == null) {
matcher = new SelectorMatcher(); matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector)); matcher.addSelectables(CssSelector.parse(selector));
} }
var cssSelector = new CssSelector(); var cssSelector = new CssSelector();

View File

@ -23,162 +23,181 @@ export function main() {
}); });
it('should select by element name case insensitive', () => { it('should select by element name case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('someTag'), 1); matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('SOMETAG'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select by class name case insensitive', () => { it('should select by class name case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('.someClass'), 1); matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass.class2'), 2); matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
}); });
it('should select by attr name case insensitive independent of the value', () => { it('should select by attr name case insensitive independent of the value', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectable(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2); matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
}); });
it('should select by attr name only once if the value is from the DOM', () => { it('should select by attr name only once if the value is from the DOM', () => {
matcher.addSelectable(s1 = CssSelector.parse('[some-decor]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[some-decor]'), 1);
var elementSelector = new CssSelector(); var elementSelector = new CssSelector();
var element = el('<div attr></div>'); var element = el('<div attr></div>');
var empty = DOM.getAttribute(element, 'attr'); var empty = DOM.getAttribute(element, 'attr');
elementSelector.addAttribute('some-decor', empty); elementSelector.addAttribute('some-decor', empty);
matcher.match(elementSelector, selectableCollector); matcher.match(elementSelector, selectableCollector);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select by attr name and value case insensitive', () => { it('should select by attr name and value case insensitive', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr=someValue]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select by element name, class name and attribute name with value', () => { it('should select by element name, class name and attribute name with value', () => {
matcher.addSelectable(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1); matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1]); expect(matched).toEqual([s1[0],1]);
}); });
it('should select independent of the order in the css selector', () => { it('should select independent of the order in the css selector', () => {
matcher.addSelectable(s1 = CssSelector.parse('[someAttr].someClass'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr].someClass'), 1);
matcher.addSelectable(s2 = CssSelector.parse('.someClass[someAttr]'), 2); matcher.addSelectables(s2 = CssSelector.parse('.someClass[someAttr]'), 2);
matcher.addSelectable(s3 = CssSelector.parse('.class1.class2'), 3); matcher.addSelectables(s3 = CssSelector.parse('.class1.class2'), 3);
matcher.addSelectable(s4 = CssSelector.parse('.class2.class1'), 4); matcher.addSelectables(s4 = CssSelector.parse('.class2.class1'), 4);
expect(matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('[someAttr].someClass')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.someClass[someAttr]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2]); expect(matched).toEqual([s1[0],1,s2[0],2]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.class1.class2'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.class1.class2')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s3,3,s4,4]); expect(matched).toEqual([s3[0],3,s4[0],4]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.class2.class1'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('.class2.class1')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s4,4,s3,3]); expect(matched).toEqual([s4[0],4,s3[0],3]);
}); });
it('should not select with a matching :not selector', () => { it('should not select with a matching :not selector', () => {
matcher.addSelectable(CssSelector.parse('p:not(.someClass)'), 1); matcher.addSelectables(CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectable(CssSelector.parse('p:not([someAttr])'), 2); matcher.addSelectables(CssSelector.parse('p:not([someAttr])'), 2);
matcher.addSelectable(CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectable(CssSelector.parse(':not(p)'), 4); matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
matcher.addSelectable(CssSelector.parse(':not(p[someAttr])'), 5); matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]'), selectableCollector)).toEqual(false); expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
}); });
it('should select with a non matching :not selector', () => { it('should select with a non matching :not selector', () => {
matcher.addSelectable(s1 = CssSelector.parse('p:not(.someClass)'), 1); matcher.addSelectables(s1 = CssSelector.parse('p:not(.someClass)'), 1);
matcher.addSelectable(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2); matcher.addSelectables(s2 = CssSelector.parse('p:not(.someOtherClass[someAttr])'), 2);
matcher.addSelectable(s3 = CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectable(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4); matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass'), selectableCollector)).toEqual(true); expect(matcher.match(CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1,1,s2,2,s3,3,s4,4]); expect(matched).toEqual([s1[0],1,s2[0],2,s3[0],3,s4[0],4]);
});
it('should select with one match in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[1],1]);
reset();
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0],1]);
});
it('should not select twice with two matches in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector)).toEqual(true);
expect(matched.length).toEqual(2);
expect(matched).toEqual([s1[0],1]);
}); });
}); });
describe('CssSelector.parse', () => { describe('CssSelector.parse', () => {
it('should detect element names', () => { it('should detect element names', () => {
var cssSelector = CssSelector.parse('sometag'); var cssSelector = CssSelector.parse('sometag')[0];
expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.toString()).toEqual('sometag'); expect(cssSelector.toString()).toEqual('sometag');
}); });
it('should detect class names', () => { it('should detect class names', () => {
var cssSelector = CssSelector.parse('.someClass'); var cssSelector = CssSelector.parse('.someClass')[0];
expect(cssSelector.classNames).toEqual(['someclass']); expect(cssSelector.classNames).toEqual(['someclass']);
expect(cssSelector.toString()).toEqual('.someclass'); expect(cssSelector.toString()).toEqual('.someclass');
}); });
it('should detect attr names', () => { it('should detect attr names', () => {
var cssSelector = CssSelector.parse('[attrname]'); var cssSelector = CssSelector.parse('[attrname]')[0];
expect(cssSelector.attrs).toEqual(['attrname', '']); expect(cssSelector.attrs).toEqual(['attrname', '']);
expect(cssSelector.toString()).toEqual('[attrname]'); expect(cssSelector.toString()).toEqual('[attrname]');
}); });
it('should detect attr values', () => { it('should detect attr values', () => {
var cssSelector = CssSelector.parse('[attrname=attrvalue]'); var cssSelector = CssSelector.parse('[attrname=attrvalue]')[0];
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]'); expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
}); });
it('should detect multiple parts', () => { it('should detect multiple parts', () => {
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass'); var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass')[0];
expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']); expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelector.classNames).toEqual(['someclass']); expect(cssSelector.classNames).toEqual(['someclass']);
@ -187,7 +206,7 @@ export function main() {
}); });
it('should detect :not', () => { it('should detect :not', () => {
var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)'); var cssSelector = CssSelector.parse('sometag:not([attrname=attrvalue].someclass)')[0];
expect(cssSelector.element).toEqual('sometag'); expect(cssSelector.element).toEqual('sometag');
expect(cssSelector.attrs.length).toEqual(0); expect(cssSelector.attrs.length).toEqual(0);
expect(cssSelector.classNames.length).toEqual(0); expect(cssSelector.classNames.length).toEqual(0);
@ -201,7 +220,7 @@ export function main() {
}); });
it('should detect :not without truthy', () => { it('should detect :not without truthy', () => {
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)'); var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0];
expect(cssSelector.element).toEqual("*"); expect(cssSelector.element).toEqual("*");
var notSelector = cssSelector.notSelector; var notSelector = cssSelector.notSelector;
@ -213,8 +232,31 @@ export function main() {
it('should throw when nested :not', () => { it('should throw when nested :not', () => {
expect(() => { expect(() => {
CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))') CssSelector.parse('sometag:not(:not([attrname=attrvalue].someclass))')[0];
}).toThrowError('Nesting :not is not allowed in a selector'); }).toThrowError('Nesting :not is not allowed in a selector');
}); });
it('should detect lists of selectors', () => {
var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag');
expect(cssSelectors.length).toEqual(3);
expect(cssSelectors[0].classNames).toEqual(['someclass']);
expect(cssSelectors[1].attrs).toEqual(['attrname', 'attrvalue']);
expect(cssSelectors[2].element).toEqual('sometag');
});
it('should detect lists of selectors with :not', () => {
var cssSelectors = CssSelector.parse('input[type=text], :not(textarea), textbox:not(.special)');
expect(cssSelectors.length).toEqual(3);
expect(cssSelectors[0].element).toEqual('input');
expect(cssSelectors[0].attrs).toEqual(['type', 'text']);
expect(cssSelectors[1].element).toEqual('*');
expect(cssSelectors[1].notSelector.element).toEqual('textarea');
expect(cssSelectors[2].element).toEqual('textbox');
expect(cssSelectors[2].notSelector.classNames).toEqual(['special']);
});
}); });
} }

View File

@ -20,7 +20,7 @@ export function main() {
} }
fixedMatcher = new SelectorMatcher(); fixedMatcher = new SelectorMatcher();
for (var i=0; i<count; i++) { for (var i=0; i<count; i++) {
fixedMatcher.addSelectable(fixedSelectors[i], i); fixedMatcher.addSelectables(fixedSelectors[i], i);
} }
function parse() { function parse() {
@ -34,7 +34,7 @@ export function main() {
function addSelectable() { function addSelectable() {
var matcher = new SelectorMatcher(); var matcher = new SelectorMatcher();
for (var i=0; i<count; i++) { for (var i=0; i<count; i++) {
matcher.addSelectable(fixedSelectors[i], i); matcher.addSelectables(fixedSelectors[i], i);
} }
return matcher; return matcher;
} }
@ -42,7 +42,7 @@ export function main() {
function match() { function match() {
var matchCount = 0; var matchCount = 0;
for (var i=0; i<count; i++) { for (var i=0; i<count; i++) {
fixedMatcher.match(fixedSelectors[i], (selector, selected) => { fixedMatcher.match(fixedSelectors[i][0], (selector, selected) => {
matchCount += selected; matchCount += selected;
}); });
} }