feat(selector): initial version of the selector
This commit is contained in:
parent
d0c870fb32
commit
08d4a37c06
|
@ -0,0 +1,17 @@
|
||||||
|
System.import('benchmarks/compiler/selector_benchmark').then(function (bm) {
|
||||||
|
bm.setup();
|
||||||
|
|
||||||
|
window.benchmarkSteps.push({name: 'CssSelector.parse', fn: bm.runParse});
|
||||||
|
}, console.log.bind(console));
|
||||||
|
|
||||||
|
System.import('benchmarks/compiler/selector_benchmark').then(function (bm) {
|
||||||
|
bm.setup();
|
||||||
|
|
||||||
|
window.benchmarkSteps.push({name: 'SelectorMatcher.addSelectable', fn: bm.runAdd});
|
||||||
|
}, console.log.bind(console));
|
||||||
|
|
||||||
|
System.import('benchmarks/compiler/selector_benchmark').then(function (bm) {
|
||||||
|
bm.setup();
|
||||||
|
|
||||||
|
window.benchmarkSteps.push({name: 'SelectorMatcher.match', fn: bm.runMatch});
|
||||||
|
}, console.log.bind(console));
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = function(config) {
|
||||||
|
config.set({
|
||||||
|
scripts: [
|
||||||
|
{src: '/js/traceur-runtime.js'},
|
||||||
|
{src: '/js/es6-module-loader-sans-promises.src.js'},
|
||||||
|
{src: '/js/extension-register.js'},
|
||||||
|
{src: 'register_system.js'},
|
||||||
|
{src: 'benchmark.js'}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
System.paths = {
|
||||||
|
'core/*': '/js/core/lib/*.js',
|
||||||
|
'change_detection/*': '/js/change_detection/lib/*.js',
|
||||||
|
'facade/*': '/js/facade/lib/*.js',
|
||||||
|
'di/*': '/js/di/lib/*.js',
|
||||||
|
'rtts_assert/*': '/js/rtts_assert/lib/*.js',
|
||||||
|
'test_lib/*': '/js/test_lib/lib/*.js',
|
||||||
|
'benchmarks/*': '/js/benchmarks/lib/*.js'
|
||||||
|
};
|
||||||
|
register(System);
|
|
@ -0,0 +1,81 @@
|
||||||
|
import {SelectorMatcher, CssSelector} from "core/compiler/selector";
|
||||||
|
import {StringWrapper, Math} from 'facade/lang';
|
||||||
|
import {ListWrapper} from 'facade/collection';
|
||||||
|
|
||||||
|
var fixedMatcher;
|
||||||
|
var fixedSelectorStrings = [];
|
||||||
|
var fixedSelectors = [];
|
||||||
|
|
||||||
|
var COUNT = 1000;
|
||||||
|
|
||||||
|
export function setup() {
|
||||||
|
for (var i=0; i<COUNT; i++) {
|
||||||
|
ListWrapper.push(fixedSelectorStrings, randomSelector());
|
||||||
|
}
|
||||||
|
for (var i=0; i<COUNT; i++) {
|
||||||
|
ListWrapper.push(fixedSelectors, CssSelector.parse(fixedSelectorStrings[i]));
|
||||||
|
}
|
||||||
|
fixedMatcher = new SelectorMatcher();
|
||||||
|
for (var i=0; i<COUNT; i++) {
|
||||||
|
fixedMatcher.addSelectable(fixedSelectors[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runParse() {
|
||||||
|
var result = [];
|
||||||
|
for (var i=0; i<COUNT; i++) {
|
||||||
|
ListWrapper.push(result, CssSelector.parse(fixedSelectorStrings[i]));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runAdd() {
|
||||||
|
// The sum is used to prevent Chrome from optimizing the loop away...
|
||||||
|
var matcher = new SelectorMatcher();
|
||||||
|
var count = 0;
|
||||||
|
for (var i=0; i<COUNT; i++) {
|
||||||
|
count += matcher.addSelectable(fixedSelectors[i], i);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runMatch() {
|
||||||
|
// The sum is used to prevent Chrome from optimizing the loop away...
|
||||||
|
var count = 0;
|
||||||
|
for (var i=0; i<COUNT; i++) {
|
||||||
|
fixedMatcher.match(fixedSelectors[i], (selected) => {
|
||||||
|
count += selected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomSelector() {
|
||||||
|
var res = randomStr(5);
|
||||||
|
for (var i=0; i<3; i++) {
|
||||||
|
res += '.'+randomStr(5);
|
||||||
|
}
|
||||||
|
for (var i=0; i<3; i++) {
|
||||||
|
res += '['+randomStr(3)+'='+randomStr(6)+']';
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomStr(len){
|
||||||
|
var s = '';
|
||||||
|
while (s.length < len) {
|
||||||
|
s += randomChar();
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomChar(){
|
||||||
|
var n = randomNum(62);
|
||||||
|
if(n<10) return n; //1-10
|
||||||
|
if(n<36) return StringWrapper.fromCharCode(n+55); //A-Z
|
||||||
|
return StringWrapper.fromCharCode(n+61); //a-z
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomNum(max) {
|
||||||
|
return Math.floor(Math.random() * max);
|
||||||
|
}
|
|
@ -1,19 +1,232 @@
|
||||||
import {Set} from 'facade/lang';
|
import {List, ListWrapper, StringMapWrapper} from 'facade/collection';
|
||||||
//import {AnnotatedType} from './annotated_type';
|
import {RegExpWrapper, RegExpMatcherWrapper, CONST, isPresent, isBlank} from 'facade/lang';
|
||||||
|
|
||||||
export class Selector {
|
const _EMPTY_ATTR_VALUE = '';
|
||||||
constructor(directives:Set<AnnotatedType>) {
|
|
||||||
this.directives = directives;
|
export class SelectorMatcher {
|
||||||
|
/* TODO: Add these fields when the transpiler supports fields
|
||||||
|
_elementMap:Map<String, List>;
|
||||||
|
_elementPartialMap:Map<String, Selector>;
|
||||||
|
|
||||||
|
_classMap:Map<String, List>;
|
||||||
|
_classPartialMap:Map<String, Selector>;
|
||||||
|
|
||||||
|
_attrValueMap:Map<String, Map<String, List>>;
|
||||||
|
_attrValuePartialMap:Map<String, Map<String, Selector>>;
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this._selectables = ListWrapper.create();
|
||||||
|
|
||||||
|
this._elementMap = StringMapWrapper.create();
|
||||||
|
this._elementPartialMap = StringMapWrapper.create();
|
||||||
|
|
||||||
|
this._classMap = StringMapWrapper.create();
|
||||||
|
this._classPartialMap = StringMapWrapper.create();
|
||||||
|
|
||||||
|
this._attrValueMap = StringMapWrapper.create();
|
||||||
|
this._attrValuePartialMap = StringMapWrapper.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When presented with an element description it will return the current set of
|
* Add an object that can be found later on by calling `match`.
|
||||||
* directives which are present on the element.
|
* @param cssSelector A css selector
|
||||||
*
|
* @param selectable An opaque object that will be given to the callback of the `match` function
|
||||||
* @param elementName Name of the element
|
|
||||||
* @param attributes Attributes on the Element.
|
|
||||||
*/
|
*/
|
||||||
visitElement(elementName:string, attributes:Map<string, string>):List<AnnotatedType> {
|
addSelectable(cssSelector:CssSelector, selectable) {
|
||||||
return null;
|
var matcher = this;
|
||||||
|
var element = cssSelector.element;
|
||||||
|
var classNames = cssSelector.classNames;
|
||||||
|
var attrs = cssSelector.attrs;
|
||||||
|
|
||||||
|
if (isPresent(element)) {
|
||||||
|
var isTerminal = attrs.length === 0 && classNames.length === 0;
|
||||||
|
if (isTerminal) {
|
||||||
|
this._addTerminal(matcher._elementMap, element, selectable);
|
||||||
|
} else {
|
||||||
|
matcher = this._addPartial(matcher._elementPartialMap, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPresent(classNames)) {
|
||||||
|
for (var index = 0; index<classNames.length; index++) {
|
||||||
|
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
|
||||||
|
var className = classNames[index];
|
||||||
|
if (isTerminal) {
|
||||||
|
this._addTerminal(matcher._classMap, className, selectable);
|
||||||
|
} else {
|
||||||
|
matcher = this._addPartial(matcher._classPartialMap, className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPresent(attrs)) {
|
||||||
|
for (var index = 0; index<attrs.length; index++) {
|
||||||
|
var isTerminal = index === attrs.length - 1;
|
||||||
|
var attr = attrs[index];
|
||||||
|
var attrName = attr.name;
|
||||||
|
var attrValue = isPresent(attr.value) ? attr.value : _EMPTY_ATTR_VALUE;
|
||||||
|
var map = isTerminal ? matcher._attrValueMap : matcher._attrValuePartialMap;
|
||||||
|
var valuesMap = StringMapWrapper.get(map, attrName)
|
||||||
|
if (isBlank(valuesMap)) {
|
||||||
|
valuesMap = StringMapWrapper.create();
|
||||||
|
StringMapWrapper.set(map, attrName, valuesMap);
|
||||||
|
}
|
||||||
|
if (isTerminal) {
|
||||||
|
this._addTerminal(valuesMap, attrValue, selectable);
|
||||||
|
} else {
|
||||||
|
matcher = this._addPartial(valuesMap, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: map:StringMap when we have a StringMap type...
|
||||||
|
_addTerminal(map, name:string, selectable) {
|
||||||
|
var terminalList = StringMapWrapper.get(map, name)
|
||||||
|
if (isBlank(terminalList)) {
|
||||||
|
terminalList = ListWrapper.create();
|
||||||
|
StringMapWrapper.set(map, name, terminalList);
|
||||||
|
}
|
||||||
|
ListWrapper.push(terminalList, selectable);
|
||||||
|
}
|
||||||
|
// TODO: map:StringMap when we have a StringMap type...
|
||||||
|
_addPartial(map, name:string) {
|
||||||
|
var matcher = StringMapWrapper.get(map, name)
|
||||||
|
if (isBlank(matcher)) {
|
||||||
|
matcher = new SelectorMatcher();
|
||||||
|
StringMapWrapper.set(map, name, matcher);
|
||||||
|
}
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the objects that have been added via `addSelectable`
|
||||||
|
* whose css selector is contained in the given css selector.
|
||||||
|
* @param cssSelector A css selector
|
||||||
|
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
|
||||||
|
*/
|
||||||
|
match(cssSelector:CssSelector, matchedCallback:Function) {
|
||||||
|
var element = cssSelector.element;
|
||||||
|
var classNames = cssSelector.classNames;
|
||||||
|
var attrs = cssSelector.attrs;
|
||||||
|
|
||||||
|
this._matchTerminal(this._elementMap, element, matchedCallback);
|
||||||
|
this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback);
|
||||||
|
|
||||||
|
if (isPresent(classNames)) {
|
||||||
|
for (var index = 0; index<classNames.length; index++) {
|
||||||
|
var className = classNames[index];
|
||||||
|
this._matchTerminal(this._classMap, className, matchedCallback);
|
||||||
|
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPresent(attrs)) {
|
||||||
|
for (var index = 0; index<attrs.length; index++) {
|
||||||
|
var attr = attrs[index];
|
||||||
|
var attrName = attr.name;
|
||||||
|
var attrValue = isPresent(attr.value) ? attr.value : _EMPTY_ATTR_VALUE;
|
||||||
|
|
||||||
|
var valuesMap = StringMapWrapper.get(this._attrValueMap, attrName)
|
||||||
|
this._matchTerminal(valuesMap, attrValue, matchedCallback);
|
||||||
|
|
||||||
|
valuesMap = StringMapWrapper.get(this._attrValuePartialMap, attrName)
|
||||||
|
this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: map:StringMap when we have a StringMap type...
|
||||||
|
_matchTerminal(map, name, matchedCallback) {
|
||||||
|
if (isBlank(map) || isBlank(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var selectables = StringMapWrapper.get(map, name)
|
||||||
|
if (isBlank(selectables)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var index=0; index<selectables.length; index++) {
|
||||||
|
matchedCallback(selectables[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: map:StringMap when we have a StringMap type...
|
||||||
|
_matchPartial(map, name, cssSelector, matchedCallback) {
|
||||||
|
if (isBlank(map) || isBlank(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var nestedSelector = StringMapWrapper.get(map, name)
|
||||||
|
if (isBlank(nestedSelector)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO(perf): get rid of recursion and measure again
|
||||||
|
// TODO(perf): don't pass the whole selector into the recursion,
|
||||||
|
// but only the not processed parts
|
||||||
|
nestedSelector.match(cssSelector, matchedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Attr {
|
||||||
|
@CONST()
|
||||||
|
constructor(name:string, value:string = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Can't use `const` here as
|
||||||
|
// in Dart this is not transpiled into `final` yet...
|
||||||
|
var _SELECTOR_REGEXP =
|
||||||
|
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
|
||||||
|
'(?:\\.([-\\w]+))|' + // ".class"
|
||||||
|
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
|
||||||
|
|
||||||
|
export class CssSelector {
|
||||||
|
static parse(selector:string):CssSelector {
|
||||||
|
var element = null;
|
||||||
|
var classNames = ListWrapper.create();
|
||||||
|
var attrs = ListWrapper.create();
|
||||||
|
selector = selector.toLowerCase();
|
||||||
|
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||||
|
var match;
|
||||||
|
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
||||||
|
if (isPresent(match[1])) {
|
||||||
|
element = match[1];
|
||||||
|
}
|
||||||
|
if (isPresent(match[2])) {
|
||||||
|
ListWrapper.push(classNames, match[2]);
|
||||||
|
}
|
||||||
|
if (isPresent(match[3])) {
|
||||||
|
ListWrapper.push(attrs, new Attr(match[3], match[4]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CssSelector(element, classNames, attrs);
|
||||||
|
}
|
||||||
|
// TODO: do a toLowerCase() for all arguments
|
||||||
|
@CONST()
|
||||||
|
constructor(element:string, classNames:List<string>, attrs:List<Attr>) {
|
||||||
|
this.element = element;
|
||||||
|
this.classNames = classNames;
|
||||||
|
this.attrs = attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString():string {
|
||||||
|
var res = '';
|
||||||
|
if (isPresent(this.element)) {
|
||||||
|
res += this.element;
|
||||||
|
}
|
||||||
|
if (isPresent(this.classNames)) {
|
||||||
|
for (var i=0; i<this.classNames.length; i++) {
|
||||||
|
res += '.' + this.classNames[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isPresent(this.attrs)) {
|
||||||
|
for (var i=0; i<this.attrs.length; i++) {
|
||||||
|
var attr = this.attrs[i];
|
||||||
|
res += '[' + attr.name;
|
||||||
|
if (isPresent(attr.value)) {
|
||||||
|
res += '=' + attr.value;
|
||||||
|
}
|
||||||
|
res += ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
import {describe, it, expect, beforeEach, ddescribe, iit} from 'test_lib/test_lib';
|
||||||
|
import {SelectorMatcher, CssSelector, Attr} from 'core/compiler/selector';
|
||||||
|
import {List, ListWrapper, MapWrapper} from 'facade/collection';
|
||||||
|
import {isPresent} from 'facade/lang';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('SelectorMatcher', () => {
|
||||||
|
var matcher, matched, selectableCollector;
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
matched = ListWrapper.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
reset();
|
||||||
|
selectableCollector = (selectable) => {
|
||||||
|
ListWrapper.push(matched, selectable);
|
||||||
|
}
|
||||||
|
matcher = new SelectorMatcher();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select by element name case insensitive', () => {
|
||||||
|
matcher.addSelectable(CssSelector.parse('someTag'), 1);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('SOMETAG'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select by class name case insensitive', () => {
|
||||||
|
matcher.addSelectable(CssSelector.parse('.someClass'), 1);
|
||||||
|
matcher.addSelectable(CssSelector.parse('.someClass.class2'), 2);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1]);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1,2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select by attr name case insensitive', () => {
|
||||||
|
matcher.addSelectable(CssSelector.parse('[someAttr]'), 1);
|
||||||
|
matcher.addSelectable(CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1]);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1,2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select by attr name and value case insensitive', () => {
|
||||||
|
matcher.addSelectable(CssSelector.parse('[someAttr=someValue]'), 1);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select by element name, class name and attribute name with value', () => {
|
||||||
|
matcher.addSelectable(CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('someTag.someClass[someAttr]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select independent of the order in the css selector', () => {
|
||||||
|
matcher.addSelectable(CssSelector.parse('[someAttr].someClass'), 1);
|
||||||
|
matcher.addSelectable(CssSelector.parse('.someClass[someAttr]'), 2);
|
||||||
|
matcher.addSelectable(CssSelector.parse('.class1.class2'), 3);
|
||||||
|
matcher.addSelectable(CssSelector.parse('.class2.class1'), 4);
|
||||||
|
|
||||||
|
matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1,2]);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector);
|
||||||
|
expect(matched).toEqual([1,2]);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
matcher.match(CssSelector.parse('.class1.class2'), selectableCollector);
|
||||||
|
expect(matched).toEqual([3,4]);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
matcher.match(CssSelector.parse('.class2.class1'), selectableCollector);
|
||||||
|
expect(matched).toEqual([4,3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CssSelector.parse', () => {
|
||||||
|
it('should detect element names', () => {
|
||||||
|
var cssSelector = CssSelector.parse('sometag');
|
||||||
|
expect(cssSelector.element).toEqual('sometag');
|
||||||
|
expect(cssSelector.toString()).toEqual('sometag');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect class names', () => {
|
||||||
|
var cssSelector = CssSelector.parse('.someClass');
|
||||||
|
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||||
|
|
||||||
|
expect(cssSelector.toString()).toEqual('.someclass');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect attr names', () => {
|
||||||
|
var cssSelector = CssSelector.parse('[attrname]');
|
||||||
|
var attr = ListWrapper.get(cssSelector.attrs, 0);
|
||||||
|
expect(attr.name).toEqual('attrname');
|
||||||
|
expect(isPresent(attr.value)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect attr values', () => {
|
||||||
|
var cssSelector = CssSelector.parse('[attrname=attrvalue]');
|
||||||
|
var attr = ListWrapper.get(cssSelector.attrs, 0);
|
||||||
|
expect(attr.name).toEqual('attrname');
|
||||||
|
expect(attr.value).toEqual('attrvalue');
|
||||||
|
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect multiple parts', () => {
|
||||||
|
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass');
|
||||||
|
expect(cssSelector.element).toEqual('sometag');
|
||||||
|
var attr = ListWrapper.get(cssSelector.attrs, 0);
|
||||||
|
expect(attr.name).toEqual('attrname');
|
||||||
|
expect(attr.value).toEqual('attrvalue');
|
||||||
|
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||||
|
|
||||||
|
expect(cssSelector.toString()).toEqual('sometag.someclass[attrname=attrvalue]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue