mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
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:
36
apps/app/ui-tests-app/css/combinators.css
Normal file
36
apps/app/ui-tests-app/css/combinators.css
Normal 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;
|
||||
}
|
||||
66
apps/app/ui-tests-app/css/combinators.xml
Normal file
66
apps/app/ui-tests-app/css/combinators.xml
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user