Create css combnators tests page in apps

Setup direct siblings apps tests

Fix direct child test

Add direct sibling test on class and type element

Add sibling combinator handling for id selectors

Completely redo sibling selector and add attributes and pseudo class selectors tests

Handle sibling via a custom group instead of a selector

Fix Selector groups creation

Add unit test for sibling selection

Fix tslint errors
This commit is contained in:
SBats
2017-03-05 12:11:55 -05:00
committed by Panayot Cankov
parent 7585252821
commit af38cf5f3e
6 changed files with 191 additions and 16 deletions

View File

@@ -0,0 +1,36 @@
.direct-child--type > Button {
background-color: red;
color: white;
}
.direct-child--class > .test-child {
background-color: blue;
color: white;
}
.direct-sibling--type Button + Label {
background-color: green;
color: white;
}
.direct-sibling--id #test-child + #test-child-2 {
background-color: pink;
}
.direct-sibling--class .test-child + .test-child-2 {
background-color: yellow;
}
.direct-sibling--attribute Button[data="test-child"] + Button[data="test-child-2"] {
background-color: blueviolet;
color: white;
}
.direct-sibling--pseudo-selector Button + Button:disabled {
background-color: black;
color: white;
}
.sibling-test-label {
text-align: center;
}

View File

@@ -0,0 +1,66 @@
<Page>
<ScrollView>
<StackLayout>
<StackLayout class="direct-child--type">
<Label text="Direct child test by type"/>
<Button text="I'm a direct child!"/>
<StackLayout>
<Button text="I'm not!"/>
</StackLayout>
</StackLayout>
<StackLayout class="direct-child--class">
<Label text="Direct child test by class"/>
<Button class="test-child" text="I'm a direct child!"/>
<StackLayout>
<Button class="test-child" text="I'm not!"/>
</StackLayout>
</StackLayout>
<StackLayout class="direct-sibling--type">
<Label text="Direct sibling test by type"/>
<Button text="I'm the ref"/>
<Label class="sibling-test-label" text="I'm a direct sibling!"/>
<Label text="I'm not!" class="sibling-test-label"/>
<Button text="I'm not either!"/>
<Label class="sibling-test-label" text="But I am!"/>
</StackLayout>
<StackLayout class="direct-sibling--id">
<Label text="Direct sibling test by id"/>
<Button id="test-child" text="I'm the ref"/>
<Button id="test-child-2" text="I'm a direct sibling!"/>
<Button id="test-child-3" text="I'm not!"/>
</StackLayout>
<StackLayout class="direct-sibling--class">
<Label text="Direct sibling test by class"/>
<Button class="test-child" text="I'm the ref"/>
<Button class="test-child-2" text="I'm a direct sibling!"/>
<Button class="test-child-2" text="I'm not!"/>
<Button class="test-child" text="I'm not either!"/>
<Label class="test-child-2 sibling-test-label" text="But I am!"/>
</StackLayout>
<StackLayout class="direct-sibling--attribute">
<Label text="Direct sibling test by attribute"/>
<Button data="test-child" text="I'm the ref"/>
<Button data="test-child-2" text="I'm a direct sibling!"/>
<Button data="test-child-2" text="I'm not!"/>
<Button data="test-child" text="I'm not either!"/>
<Button data="test-child-2" text="But I am!"/>
</StackLayout>
<StackLayout class="direct-sibling--pseudo-selector">
<Label text="Direct sibling test by pseudo-selector"/>
<Button text="I'm the ref"/>
<Button isEnabled="false" text="I'm a direct sibling!"/>
<Button text="I'm not!"/>
<Button text="I'm not either!"/>
<Button isEnabled="false" text="But I am!"/>
</StackLayout>
</StackLayout>
</ScrollView>
</Page>

View File

@@ -45,6 +45,7 @@ export function pageLoaded(args: EventData) {
examples.set("border-playground", "css/border-playground");
examples.set("textview-hint-color", "css/textview-hint-color");
examples.set("hint-text-color", "css/hint-text-color");
examples.set("combinators", "css/combinators");
let viewModel = new SubMainPageViewModel(wrapLayout, examples);
page.bindingContext = viewModel;

View File

@@ -232,3 +232,38 @@ export function test_query_match_one_child_group() {
let expected = new Map<selector.Node, selector.Changes>().set(prod, { attributes: new Set(["special"])} );
TKUnit.assertDeepEqual(match.changeMap, expected);
}
export function test_query_match_one_sibling_group() {
let {map} = create(`list button:highlighted+button:disabled { color: red; }`);
let list, button, disabledButton;
list = {
cssType: "list",
toString,
getChildIndex: () => 1,
getChildAt: () => button
};
button = {
cssType: "button",
cssPseudoClasses: new Set<string>(["highlighted"]),
toString,
parent: list
};
disabledButton = {
cssType: "button",
cssPseudoClasses: new Set<string>(["disabled"]),
toString,
parent: list
};
let match = map.query(disabledButton);
TKUnit.assertEqual(match.selectors.length, 1, "Expected match to have one selector.");
let expected = new Map<selector.Node, selector.Changes>()
.set(button, { pseudoClasses: new Set(["highlighted"]) })
.set(disabledButton, { pseudoClasses: new Set(["disabled"]) });
TKUnit.assertDeepEqual(match.changeMap, expected);
}

View File

@@ -15,6 +15,8 @@ export interface Node {
cssType?: string;
cssClasses?: Set<string>;
cssPseudoClasses?: Set<string>;
getChildIndex?(node: Node): number
getChildAt?(index: number): Node
}
export interface Declaration {

View File

@@ -45,6 +45,17 @@ namespace Match {
export var Static = false;
}
function getNodeDirectSibling(node): null | Node {
if (!node.parent || !node.parent.getChildIndex || !node.parent.getChildAt) {
return null;
}
const nodeIndex = node.parent.getChildIndex(node);
if (nodeIndex === 0) {
return null;
}
return node.parent.getChildAt(nodeIndex - 1);
}
function SelectorProperties(specificity: Specificity, rarity: Rarity, dynamic: boolean = false): ClassDecorator {
return cls => {
cls.prototype.specificity = specificity;
@@ -229,21 +240,27 @@ export class Selector extends SelectorCore {
constructor(public selectors: SimpleSelector[]) {
super();
let lastGroup: SimpleSelector[];
let groups: SimpleSelector[][] = [];
const supportedCombinator = [undefined, " ", ">", "+"];
let siblingGroup: SimpleSelector[];
let lastGroup: SimpleSelector[][];
let groups: SimpleSelector[][][] = [];
selectors.reverse().forEach(sel => {
switch(sel.combinator) {
case undefined:
case " ":
groups.push(lastGroup = []);
case ">":
lastGroup.push(sel);
break;
default:
throw new Error(`Unsupported combinator "${sel.combinator}".`);
if (supportedCombinator.indexOf(sel.combinator) === -1) {
throw new Error(`Unsupported combinator "${sel.combinator}".`);
}
if (sel.combinator === undefined || sel.combinator === " ") {
groups.push(lastGroup = [siblingGroup = []]);
}
if (sel.combinator === ">") {
lastGroup.push(siblingGroup = []);
}
siblingGroup.push(sel);
});
this.groups = groups.map(g => new Selector.ChildGroup(g));
this.groups = groups.map(g =>
new Selector.ChildGroup(g.map(sg =>
new Selector.SiblingGroup(sg)
))
);
this.last = selectors[0];
this.specificity = selectors.reduce((sum, sel) => sel.specificity + sum, 0);
this.dynamic = selectors.some(sel => sel.dynamic);
@@ -329,20 +346,39 @@ export namespace Selector {
export class ChildGroup {
public dynamic: boolean;
constructor(private selectors: SiblingGroup[]) {
this.dynamic = selectors.some(sel => sel.dynamic);
}
public match(node: Node): Node {
return this.selectors.every((sel, i) => (i === 0 ? node : node = node.parent) && !!sel.match(node)) ? node : null;
}
public mayMatch(node: Node): Node {
return this.selectors.every((sel, i) => (i === 0 ? node : node = node.parent) && !!sel.mayMatch(node)) ? node : null;
}
public trackChanges(node: Node, map: ChangeAccumulator) {
this.selectors.forEach((sel, i) => (i === 0 ? node : node = node.parent) && sel.trackChanges(node, map));
}
}
export class SiblingGroup {
public dynamic: boolean;
constructor(private selectors: SimpleSelector[]) {
this.dynamic = selectors.some(sel => sel.dynamic);
}
public match(node: Node): Node {
return this.selectors.every((sel, i) => (i === 0 ? node : node = node.parent) && sel.match(node)) ? node : null;
return this.selectors.every((sel, i) => (i === 0 ? node : node = getNodeDirectSibling(node)) && sel.match(node)) ? node : null;
}
public mayMatch(node: Node): Node {
return this.selectors.every((sel, i) => (i === 0 ? node : node = node.parent) && sel.mayMatch(node)) ? node : null;
return this.selectors.every((sel, i) => (i === 0 ? node : node = getNodeDirectSibling(node)) && sel.mayMatch(node)) ? node : null;
}
public trackChanges(node: Node, map: ChangeAccumulator) {
this.selectors.forEach((sel, i) => (i === 0 ? node : node = node.parent) && sel.trackChanges(node, map));
this.selectors.forEach((sel, i) => (i === 0 ? node : node = getNodeDirectSibling(node)) && sel.trackChanges(node, map));
}
}
export interface Bound {
@@ -381,7 +417,6 @@ function createSelector(sel: string): SimpleSelector | SimpleSelectorSequence |
let selectors = ast.map(createSimpleSelector);
let sequences: (SimpleSelector | SimpleSelectorSequence)[] = [];
// Join simple selectors into sequences, set combinators
for (let seqStart = 0, seqEnd = 0, last = selectors.length - 1; seqEnd <= last; seqEnd++) {
let sel = selectors[seqEnd];