mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Add parsers for the background css shorthand property, make ViewBase unit testable in node environment
Add background parser and linear-gradient parser
Use sticky regexes
Simplify some types, introduce generic Parsed<T> instead of & TokenRange
Apply each parser to return a { start, end, value } object
Move the css selector parser to the css/parser and unify types
Add the first steps toward building homegrown css parser
Add somewhat standards compliant tokenizer, add baseline, rework and shady css parsers
Enable all tests again, skip flaky perf test
Improve css parser tokenizer by converting some char token types to simple string
Implement 'parse a stylesheet'
Add gonzales css-parser
Add parseLib and css-tree perf
Add a thin parser layer that will convert CSS3 tokens to values, for now output is compatible with rework
Make root tsc green
Return the requires of tns-core-modules to use relative paths for webpack to work
Implement support for '@import 'url-string';
Fix function parser, function-token is no-longer neglected
Make the style-scope be able to load from "css" and "css-ast" modules
Add a loadAppCss event so theme can be added to snapshot separately from loaded
289 lines
13 KiB
TypeScript
289 lines
13 KiB
TypeScript
import { assert } from "chai";
|
|
import * as parser from "tns-core-modules/css";
|
|
import * as selector from "tns-core-modules/ui/styling/css-selector";
|
|
|
|
describe("ui", () => {
|
|
describe("styling", () => {
|
|
describe("css-selectors", () => {
|
|
describe("match", () => {
|
|
it("button[attr]", () => {
|
|
const sel = selector.createSelector("button[testAttr]");
|
|
assert.isTrue(sel.match(<any>{
|
|
cssType: "button",
|
|
testAttr: true
|
|
}));
|
|
assert.isFalse(sel.match(<any>{
|
|
cssType: "button"
|
|
}));
|
|
});
|
|
|
|
function create(css: string, source: string = "css-selectors.ts@test"): { rules: selector.RuleSet[], map: selector.SelectorsMap } {
|
|
let parse = parser.parse(css, { source });
|
|
let rulesAst = parse.stylesheet.rules.filter(n => n.type === "rule");
|
|
let rules = selector.fromAstNodes(rulesAst);
|
|
let map = new selector.SelectorsMap(rules);
|
|
return { rules, map };
|
|
}
|
|
|
|
function createOne(css: string, source: string = "css-selectors.ts@test"): selector.RuleSet {
|
|
let {rules} = create(css, source);
|
|
assert.equal(rules.length, 1);
|
|
return rules[0];
|
|
}
|
|
|
|
it("single selector", () => {
|
|
let rule = createOne(`* { color: red; }`);
|
|
assert.isTrue(rule.selectors[0].match({ cssType: "button" }));
|
|
assert.isTrue(rule.selectors[0].match({ cssType: "image" }));
|
|
});
|
|
|
|
it("two selectors", () => {
|
|
let rule = createOne(`button, image { color: red; }`);
|
|
assert.isTrue(rule.selectors[0].match({ cssType: "button" }));
|
|
assert.isTrue(rule.selectors[1].match({ cssType: "image" }));
|
|
assert.isFalse(rule.selectors[0].match({ cssType: "stacklayout" }));
|
|
assert.isFalse(rule.selectors[1].match({ cssType: "stacklayout" }));
|
|
});
|
|
|
|
it("narrow selection", () => {
|
|
let {map} = create(`
|
|
.login { color: blue; }
|
|
button { color: red; }
|
|
image { color: green; }
|
|
`);
|
|
|
|
let buttonQuerry = map.query({ cssType: "button" }).selectors;
|
|
assert.equal(buttonQuerry.length, 1);
|
|
assert.includeDeepMembers(buttonQuerry[0].ruleset.declarations, [
|
|
{ property: "color", value: "red" }
|
|
]);
|
|
|
|
let imageQuerry = map.query({ cssType: "image", cssClasses: new Set(["login"]) }).selectors;
|
|
assert.equal(imageQuerry.length, 2);
|
|
// Note class before type
|
|
assert.includeDeepMembers(imageQuerry[0].ruleset.declarations, [
|
|
{ property: "color", value: "green" }
|
|
]);
|
|
assert.includeDeepMembers(imageQuerry[1].ruleset.declarations, [
|
|
{ property: "color", value: "blue" }
|
|
]);
|
|
});
|
|
|
|
let positiveMatches = {
|
|
"*": (view) => true,
|
|
"type": (view) => view.cssType === "type",
|
|
"#id": (view) => view.id === "id",
|
|
".class": (view) => view.cssClasses.has("class"),
|
|
":pseudo": (view) => view.cssPseudoClasses.has("pseudo"),
|
|
"[src1]": (view) => "src1" in view,
|
|
"[src2='src-value']": (view) => view['src2'] === 'src-value'
|
|
}
|
|
|
|
let positivelyMatchingView = {
|
|
cssType: "type",
|
|
id: "id",
|
|
cssClasses: new Set(["class"]),
|
|
cssPseudoClasses: new Set(["pseudo"]),
|
|
"src1": "src",
|
|
"src2": "src-value"
|
|
}
|
|
|
|
let negativelyMatchingView = {
|
|
cssType: "nottype",
|
|
id: "notid",
|
|
cssClasses: new Set(["notclass"]),
|
|
cssPseudoClasses: new Set(["notpseudo"]),
|
|
// Has no "src1"
|
|
"src2": "not-src-value"
|
|
}
|
|
|
|
it("simple selectors match", () => {
|
|
for (let sel in positiveMatches) {
|
|
let css = sel + " { color: red; }";
|
|
let rule = createOne(css);
|
|
assert.isTrue(rule.selectors[0].match(positivelyMatchingView), "Expected successful match for: " + css);
|
|
if (sel !== "*") {
|
|
assert.isFalse(rule.selectors[0].match(negativelyMatchingView), "Expected match failure for: " + css);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("two selector sequence positive match", () => {
|
|
for (let firstStr in positiveMatches) {
|
|
for (let secondStr in positiveMatches) {
|
|
if (secondStr !== firstStr && secondStr !== "*" && secondStr !== "type") {
|
|
let css = firstStr + secondStr + " { color: red; }";
|
|
let rule = createOne(css);
|
|
assert.isTrue(rule.selectors[0].match(positivelyMatchingView), "Expected successful match for: " + css);
|
|
if (firstStr !== "*") {
|
|
assert.isFalse(rule.selectors[0].match(negativelyMatchingView), "Expected match failure for: " + css);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it("direct parent combinator", () => {
|
|
let rule = createOne(`listview > item:selected { color: red; }`);
|
|
assert.isTrue(rule.selectors[0].match({
|
|
cssType: "item",
|
|
cssPseudoClasses: new Set(["selected"]),
|
|
parent: {
|
|
cssType: "listview"
|
|
}
|
|
}), "Item in list view expected to match");
|
|
assert.isFalse(rule.selectors[0].match({
|
|
cssType: "item",
|
|
cssPseudoClasses: new Set(["selected"]),
|
|
parent: {
|
|
cssType: "stacklayout",
|
|
parent: {
|
|
cssType: "listview"
|
|
}
|
|
}
|
|
}), "Item in stack in list view NOT expected to match.");
|
|
});
|
|
|
|
it("ancestor combinator", () => {
|
|
let rule = createOne(`listview item:selected { color: red; }`);
|
|
assert.isTrue(rule.selectors[0].match({
|
|
cssType: "item",
|
|
cssPseudoClasses: new Set(["selected"]),
|
|
parent: {
|
|
cssType: "listview"
|
|
}
|
|
}), "Item in list view expected to match");
|
|
assert.isTrue(rule.selectors[0].match({
|
|
cssType: "item",
|
|
cssPseudoClasses: new Set(["selected"]),
|
|
parent: {
|
|
cssType: "stacklayout",
|
|
parent: {
|
|
cssType: "listview"
|
|
}
|
|
}
|
|
}), "Item in stack in list view expected to match.");
|
|
assert.isFalse(rule.selectors[0].match({
|
|
cssType: "item",
|
|
cssPseudoClasses: new Set(["selected"]),
|
|
parent: {
|
|
cssType: "stacklayout",
|
|
parent: {
|
|
cssType: "page"
|
|
}
|
|
}
|
|
}), "Item in stack in page NOT expected to match.");
|
|
});
|
|
|
|
it("backtracking css selector", () => {
|
|
let sel = createOne(`a>b c { color: red; }`).selectors[0];
|
|
let child = {
|
|
cssType: "c",
|
|
parent: {
|
|
cssType: "b",
|
|
parent: {
|
|
cssType: "fail",
|
|
parent: {
|
|
cssType: "b",
|
|
parent: {
|
|
cssType: "a"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert.isTrue(sel.match(child));
|
|
});
|
|
|
|
function toString() { return this.cssType; }
|
|
|
|
it("simple query match", () => {
|
|
let {map} = create(`list grid[promotion] button:highlighted { color: red; }`);
|
|
|
|
let list, grid, button;
|
|
|
|
button = {
|
|
cssType: "button",
|
|
cssPseudoClasses: new Set<string>(["highlighted"]),
|
|
toString,
|
|
parent: grid = {
|
|
cssType: "grid",
|
|
promotion: true,
|
|
toString,
|
|
parent: list = {
|
|
cssType: "list",
|
|
toString
|
|
}
|
|
}
|
|
}
|
|
|
|
let match = map.query(button);
|
|
assert.equal(match.selectors.length, 1, "Expected match to have one selector.");
|
|
|
|
let expected = new Map<selector.Node, selector.Changes>()
|
|
.set(grid, { attributes: new Set(["promotion"]) })
|
|
.set(button, { pseudoClasses: new Set(["highlighted"]) });
|
|
|
|
assert.deepEqual(match.changeMap, expected);
|
|
});
|
|
|
|
it("query match one child group", () => {
|
|
let {map} = create(`#prod[special] > gridlayout { color: red; }`);
|
|
let gridlayout, prod;
|
|
|
|
gridlayout = {
|
|
cssType: "gridlayout",
|
|
toString,
|
|
parent: prod = {
|
|
id: "prod",
|
|
cssType: "listview",
|
|
toString
|
|
}
|
|
};
|
|
|
|
let match = map.query(gridlayout);
|
|
assert.equal(match.selectors.length, 1, "Expected match to have one selector.");
|
|
|
|
let expected = new Map<selector.Node, selector.Changes>().set(prod, { attributes: new Set(["special"])} );
|
|
assert.deepEqual(match.changeMap, expected);
|
|
});
|
|
|
|
it("query match one sibling group (deepEqual does not compare Map?)", () => {
|
|
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);
|
|
assert.equal(match.selectors.length, 1, "Expected match to have one selector.");
|
|
|
|
let expected = new Map<selector.Node, selector.Changes>()
|
|
.set(disabledButton, { pseudoClasses: new Set(["disabled"]) })
|
|
.set(button, { pseudoClasses: new Set(["highlighted"]) });
|
|
|
|
assert.deepEqual(match.changeMap, expected);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|