Skip to content

Commit 6021811

Browse files
committed
Inital by-type split
Split type.class from CssTypeSelector to CssCompositeSelector, probably support type#id.class selectors Apply review comments, refactor css-selectors internally Applied refactoring, all tests pass, button does not notify changes Add tests for the css selectors parser. Added tests for css-selectors Added basic implementation of mayMatch and changeMap for css match state Implemented TKUnit.assertDeepEqual to check key and key/values in Map and Set Watch for property and pseudoClass changes Add one child group test Add typings for animations Added mechanism to enable/disable listeners for pseudo classes Count listeners instead of checking handlers, reverse subscription and unsubscription
1 parent 0477c81 commit 6021811

33 files changed

+1504
-941
lines changed

tests/app/TKUnit.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,82 @@ export function assertEqual(actual: any, expected: any, message?: string) {
227227
}
228228
};
229229

230+
/**
231+
* Assert two json like objects are deep equal.
232+
*/
233+
export function assertDeepEqual(actual, expected, path: any[] = []): void {
234+
let typeofActual = typeof actual;
235+
let typeofExpected = typeof expected;
236+
if (typeofActual !== typeofExpected) {
237+
throw new Error("At /" + path.join("/") + " types of actual " + typeofActual + " and expected " + typeofExpected + " differ.");
238+
} else if (typeofActual === "object" || typeofActual === "array") {
239+
if (expected instanceof Map) {
240+
if (actual instanceof Map) {
241+
expected.forEach((value, key) => {
242+
if (actual.has(key)) {
243+
assertDeepEqual(actual.get(key), value, path.concat([key]));
244+
} else {
245+
throw new Error("At /" + path.join("/") + " expected Map has key '" + key + "' but actual does not.");
246+
}
247+
});
248+
actual.forEach((value, key) => {
249+
if (!expected.has(key)) {
250+
throw new Error("At /" + path.join("/") + " actual Map has key '" + key + "' but expected does not.");
251+
}
252+
});
253+
} else {
254+
throw new Error("At /" + path.join("/") + " expected is Map but actual is not.");
255+
}
256+
}
257+
if (expected instanceof Set) {
258+
if (actual instanceof Set) {
259+
expected.forEach(i => {
260+
if (!actual.has(i)) {
261+
throw new Error("At /" + path.join("/") + " expected Set has item '" + i + "' but actual does not.");
262+
}
263+
});
264+
actual.forEach(i => {
265+
if (!expected.has(i)) {
266+
throw new Error("At /" + path.join("/") + " actual Set has item '" + i + "' but expected does not.");
267+
}
268+
})
269+
} else {
270+
throw new Error("At /" + path.join("/") + " expected is Set but actual is not.");
271+
}
272+
}
273+
for (let key in actual) {
274+
if (!(key in expected)) {
275+
throw new Error("At /" + path.join("/") + " found unexpected key " + key + ".");
276+
}
277+
assertDeepEqual(actual[key], expected[key], path.concat([key]));
278+
}
279+
for (let key in expected) {
280+
if (!(key in actual)) {
281+
throw new Error("At /" + path.join("/") + " expected a key " + key + ".");
282+
}
283+
}
284+
} else if (actual !== expected) {
285+
throw new Error("At /" + path.join("/") + " actual: '" + actual + "' and expected: '" + expected + "' differ.");
286+
}
287+
}
288+
289+
export function assertDeepSuperset(actual, expected, path: any[] = []): void {
290+
let typeofActual = typeof actual;
291+
let typeofExpected = typeof expected;
292+
if (typeofActual !== typeofExpected) {
293+
throw new Error("At /" + path.join("/") + " types of actual " + typeofActual + " and expected " + typeofExpected + " differ.");
294+
} else if (typeofActual === "object" || typeofActual === "array") {
295+
for (let key in expected) {
296+
if (!(key in actual)) {
297+
throw new Error("At /" + path.join("/") + " expected a key " + key + ".");
298+
}
299+
assertDeepSuperset(actual[key], expected[key], path.concat([key]));
300+
}
301+
} else if (actual !== expected) {
302+
throw new Error("At /" + path.join("/") + " actual: '" + actual + "' and expected: '" + expected + "' differ.");
303+
}
304+
}
305+
230306
export function assertNull(actual: any, message?: string) {
231307
if (actual !== null && actual !== undefined) {
232308
throw new Error(message + " Actual: " + actual + " is not null/undefined");

tests/app/testRunner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ allTests["VIEW"] = require("./ui/view/view-tests");
6868
allTests["STYLE"] = require("./ui/styling/style-tests");
6969
allTests["VISUAL-STATE"] = require("./ui/styling/visual-state-tests");
7070
allTests["VALUE-SOURCE"] = require("./ui/styling/value-source-tests");
71+
allTests["CSS-SELECTOR-PARSER"] = require("./ui/styling/css-selector-parser");
72+
allTests["CSS-SELECTOR"] = require("./ui/styling/css-selector");
7173
allTests["BUTTON"] = require("./ui/button/button-tests");
7274
allTests["BORDER"] = require("./ui/border/border-tests");
7375
allTests["LABEL"] = require("./ui/label/label-tests");

tests/app/ui/animation/css-animation-tests.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import helper = require("../../ui/helper");
66
import stackModule = require("ui/layouts/stack-layout");
77
import labelModule = require("ui/label");
88
import color = require("color");
9-
import selectorModule = require("ui/styling/css-selector");
9+
10+
import {SelectorCore} from "ui/styling/css-selector";
11+
1012
//import styling = require("ui/styling");
1113

1214
function createAnimationFromCSS(css: string, name: string): keyframeAnimation.KeyframeAnimationInfo {
@@ -15,21 +17,15 @@ function createAnimationFromCSS(css: string, name: string): keyframeAnimation.Ke
1517
scope.ensureSelectors();
1618
let selector = findSelectorInScope(scope, name);
1719
if (selector !== undefined) {
18-
let animation = selector.animations[0];
20+
let animation = scope.getAnimations(selector.ruleset)[0];
1921
return animation;
2022
}
2123
return undefined;
2224
}
2325

24-
function findSelectorInScope(scope: styleScope.StyleScope, name: string): selectorModule.CssSelector {
25-
let selector = undefined;
26-
for (let sel of (<any>scope)._mergedCssSelectors) {
27-
if (sel.expression === name) {
28-
selector = sel;
29-
break;
30-
}
31-
}
32-
return selector;
26+
function findSelectorInScope(scope: styleScope.StyleScope, cssClass: string): SelectorCore {
27+
let selectors = scope.query({cssClasses: new Set([cssClass])});
28+
return selectors[0];
3329
}
3430

3531
export function test_ReadAnimationProperties() {
@@ -108,7 +104,7 @@ export function test_ReadKeyframe() {
108104
scope.ensureSelectors();
109105
let selector = findSelectorInScope(scope, "test");
110106
TKUnit.assert(selector !== undefined, "CSS selector was not created!");
111-
let animation = selector.animations[0];
107+
let animation = scope.getAnimations(selector.ruleset)[0];
112108
TKUnit.assertEqual(animation.name, "test", "Wrong animation name!");
113109
TKUnit.assertEqual(animation.keyframes.length, 2, "Keyframes not parsed correctly!");
114110
TKUnit.assertEqual(animation.keyframes[0].duration, 0, "First keyframe duration should be 0");
@@ -221,15 +217,15 @@ export function test_LoadTwoAnimationsWithTheSameName() {
221217
scope.css = "@keyframes a1 { from { opacity: 0; } to { opacity: 1; } } @keyframes a1 { from { opacity: 0; } to { opacity: 0.5; } } .a { animation-name: a1; }";
222218
scope.ensureSelectors();
223219
let selector = findSelectorInScope(scope, "a");
224-
let animation = selector.animations[0];
220+
let animation = scope.getAnimations(selector.ruleset)[0];
225221
TKUnit.assertEqual(animation.keyframes.length, 2);
226222
TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 0.5);
227223
scope = new styleScope.StyleScope();
228224
scope.css = "@keyframes k { from { opacity: 0; } to { opacity: 1; } } .a { animation-name: k; animation-duration: 2; } .a { animation-name: k; animation-duration: 3; }";
229225
scope.ensureSelectors();
230226
selector = findSelectorInScope(scope, "a");
231-
TKUnit.assertEqual(selector.animations[0].keyframes.length, 2);
232-
TKUnit.assertEqual(selector.animations[0].keyframes.length, 2);
227+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset)[0].keyframes.length, 2);
228+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset)[0].keyframes.length, 2);
233229
}
234230

235231
export function test_LoadAnimationProgrammatically() {
@@ -295,19 +291,19 @@ export function test_ReadTwoAnimations() {
295291
scope.css = ".test { animation: one 0.2s ease-out 1 2, two 2s ease-in; }";
296292
scope.ensureSelectors();
297293
let selector = findSelectorInScope(scope, "test");
298-
TKUnit.assertEqual(selector.animations.length, 2);
299-
TKUnit.assertEqual(selector.animations[0].curve, enums.AnimationCurve.easeOut);
300-
TKUnit.assertEqual(selector.animations[1].curve, enums.AnimationCurve.easeIn);
301-
TKUnit.assertEqual(selector.animations[1].name, "two");
302-
TKUnit.assertEqual(selector.animations[1].duration, 2000);
294+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset).length, 2);
295+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset)[0].curve, enums.AnimationCurve.easeOut);
296+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset)[1].curve, enums.AnimationCurve.easeIn);
297+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset)[1].name, "two");
298+
TKUnit.assertEqual(scope.getAnimations(selector.ruleset)[1].duration, 2000);
303299
}
304300

305301
export function test_AnimationCurveInKeyframes() {
306302
let scope = new styleScope.StyleScope();
307303
scope.css = "@keyframes an { from { animation-timing-function: linear; background-color: red; } 50% { background-color: green; } to { background-color: black; } } .test { animation-name: an; animation-timing-function: ease-in; }";
308304
scope.ensureSelectors();
309305
let selector = findSelectorInScope(scope, "test");
310-
let animation = selector.animations[0];
306+
let animation = scope.getAnimations(selector.ruleset)[0];
311307
TKUnit.assertEqual(animation.keyframes[0].curve, enums.AnimationCurve.linear);
312308
TKUnit.assertEqual(animation.keyframes[1].curve, undefined);
313309
TKUnit.assertEqual(animation.keyframes[1].curve, undefined);
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import * as parser from "ui/styling/css-selector-parser";
2+
import * as TKUnit from "../../TKUnit";
3+
4+
function test(css: string, expected: {}): void {
5+
let result = parser.parse(css);
6+
TKUnit.assertDeepEqual(result, expected);
7+
}
8+
9+
export function test_fairly_complex_selector(): void {
10+
test(` listview#products.mark gridlayout:selected[row="2"] a> b > c >d>e *[src] `, [
11+
{ pos: 2, type: "", ident: "listview" },
12+
{ pos: 10, type: "#", ident: "products" },
13+
{ pos: 19, type: ".", ident: "mark", comb: " " },
14+
{ pos: 25, type: "", ident: "gridlayout" },
15+
{ pos: 35, type: ":", ident: "selected" },
16+
{ pos: 44, type: "[]", prop: "row", test: "=", value: "2", comb: " " },
17+
{ pos: 54, type: "", ident: "a", comb: ">" },
18+
{ pos: 57, type: "", ident: "b", comb: ">" },
19+
{ pos: 63, type: "", ident: "c", comb: ">" },
20+
{ pos: 66, type: "", ident: "d", comb: ">" },
21+
{ pos: 68, type: "", ident: "e", comb: " " },
22+
{ pos: 70, type: "*" },
23+
{ pos: 71, type: "[]", prop: "src" }
24+
]);
25+
}
26+
27+
export function test_typeguard_isUniversal(): void {
28+
let selector = parser.parse("*")[0];
29+
TKUnit.assertTrue(parser.isUniversal(selector));
30+
TKUnit.assertFalse(parser.isType(selector));
31+
TKUnit.assertFalse(parser.isClass(selector));
32+
TKUnit.assertFalse(parser.isId(selector));
33+
TKUnit.assertFalse(parser.isPseudo(selector));
34+
TKUnit.assertFalse(parser.isAttribute(selector));
35+
}
36+
export function test_typeguard_isType(): void {
37+
let selector = parser.parse("button")[0];
38+
TKUnit.assertFalse(parser.isUniversal(selector));
39+
TKUnit.assertTrue(parser.isType(selector));
40+
TKUnit.assertFalse(parser.isClass(selector));
41+
TKUnit.assertFalse(parser.isId(selector));
42+
TKUnit.assertFalse(parser.isPseudo(selector));
43+
TKUnit.assertFalse(parser.isAttribute(selector));
44+
}
45+
export function test_typeguard_isClass(): void {
46+
let selector = parser.parse(".login")[0];
47+
TKUnit.assertFalse(parser.isUniversal(selector));
48+
TKUnit.assertFalse(parser.isType(selector));
49+
TKUnit.assertTrue(parser.isClass(selector));
50+
TKUnit.assertFalse(parser.isId(selector));
51+
TKUnit.assertFalse(parser.isPseudo(selector));
52+
TKUnit.assertFalse(parser.isAttribute(selector));
53+
}
54+
export function test_typeguard_isId(): void {
55+
let selector = parser.parse("#login")[0];
56+
TKUnit.assertFalse(parser.isUniversal(selector));
57+
TKUnit.assertFalse(parser.isType(selector));
58+
TKUnit.assertFalse(parser.isClass(selector));
59+
TKUnit.assertTrue(parser.isId(selector));
60+
TKUnit.assertFalse(parser.isPseudo(selector));
61+
TKUnit.assertFalse(parser.isAttribute(selector));
62+
}
63+
export function test_typeguard_isPseudo(): void {
64+
let selector = parser.parse(":hover")[0];
65+
TKUnit.assertFalse(parser.isUniversal(selector));
66+
TKUnit.assertFalse(parser.isType(selector));
67+
TKUnit.assertFalse(parser.isClass(selector));
68+
TKUnit.assertFalse(parser.isId(selector));
69+
TKUnit.assertTrue(parser.isPseudo(selector));
70+
TKUnit.assertFalse(parser.isAttribute(selector));
71+
}
72+
export function test_typeguard_isAttribute(): void {
73+
let selector = parser.parse("[src]")[0];
74+
TKUnit.assertFalse(parser.isUniversal(selector));
75+
TKUnit.assertFalse(parser.isType(selector));
76+
TKUnit.assertFalse(parser.isClass(selector));
77+
TKUnit.assertFalse(parser.isId(selector));
78+
TKUnit.assertFalse(parser.isPseudo(selector));
79+
TKUnit.assertTrue(parser.isAttribute(selector));
80+
}
81+
82+
export function test_universal_selector(): void {
83+
test(`*`, [{ pos: 0, type: "*" }]);
84+
}
85+
export function test_type_selector(): void {
86+
test(`button`, [{ pos: 0, type: "", ident: "button" }]);
87+
}
88+
export function test_class_selector(): void {
89+
test(`.red`, [{ pos: 0, type: ".", ident: "red" }]);
90+
}
91+
export function test_id_selector(): void {
92+
test(`#login`, [{ pos: 0, type: "#", ident: "login" }]);
93+
}
94+
export function test_pseudoClass(): void {
95+
test(`:hover`, [{ pos: 0, type: ":", ident: "hover" }]);
96+
}
97+
export function test_attribute_no_value(): void {
98+
test(`[src]`, [{ pos: 0, type: "[]", prop: "src" }]);
99+
}
100+
export function test_attribute_equal(): void {
101+
test(`[src = "res://"]`, [{ pos: 0, type: "[]", prop: "src", test: "=", value: `res://` }]);
102+
}
103+
export function test_attribute_all_tests(): void {
104+
["=", "^=", "$=", "*=", "=", "~=", "|="].forEach(t => test(`[src ${t} "val"]`, [{ pos: 0, type: "[]", prop: "src", test: t, value: "val"}]));
105+
}
106+
export function test_direct_parent_comb(): void {
107+
test(`listview > .image`, [
108+
{ pos: 0, type: "", ident: "listview", comb: ">" },
109+
{ pos: 11, type: ".", ident: "image" }
110+
]);
111+
}
112+
export function test_ancestor_comb(): void {
113+
test(`listview .image`, [
114+
{ pos: 0, type: "", ident: "listview", comb: " " },
115+
{ pos: 10, type: ".", ident: "image" }
116+
]);
117+
}
118+
export function test_single_sequence(): void {
119+
test(`button:hover`, [
120+
{ pos: 0, type: "", ident: "button" },
121+
{ pos: 6, type: ":", ident: "hover" }
122+
]);
123+
}
124+
export function test_multiple_sequences(): void {
125+
test(`listview>:selected image.product`, [
126+
{ pos: 0, type: "", ident: "listview", comb: ">" },
127+
{ pos: 9, type: ":", ident: "selected", comb: " " },
128+
{ pos: 19, type: "", ident: "image" },
129+
{ pos: 24, type: ".", ident: "product" }
130+
]);
131+
}
132+
export function test_multiple_attribute_and_pseudo_classes(): void {
133+
test(`button#login[user][pass]:focused:hovered`, [
134+
{ pos: 0, type: "", ident: "button" },
135+
{ pos: 6, type: "#", ident: "login" },
136+
{ pos: 12, type: "[]", prop: "user" },
137+
{ pos: 18, type: "[]", prop: "pass" },
138+
{ pos: 24, type: ":", ident: "focused" },
139+
{ pos: 32, type: ":", ident: "hovered" }
140+
]);
141+
}

0 commit comments

Comments
 (0)