diff --git a/apps/tests/ui/style/style-tests.ts b/apps/tests/ui/style/style-tests.ts index 3446ec5d0..f32865dc9 100644 --- a/apps/tests/ui/style/style-tests.ts +++ b/apps/tests/ui/style/style-tests.ts @@ -3,6 +3,7 @@ import buttonModule = require("ui/button"); import labelModule = require("ui/label"); import pageModule = require("ui/page"); import stackModule = require("ui/layouts/stack-layout"); +import wrapModule = require("ui/layouts/wrap-layout"); import tabViewModule = require("ui/tab-view"); import helper = require("../../ui/helper"); import styling = require("ui/styling"); @@ -384,87 +385,66 @@ export function test_restore_original_values_when_state_is_changed() { helper.goBack(); } -// TODO: Complex composite selectors are not supported yet -//export var test_composite_selector_type_and_class = function () { -// // Arrange -// var testPage = new page.Page(); -// var testStack = new stack.StackLayout(); -// testPage.content = testStack; +export var test_composite_selector_type_and_class = function () { + // Arrange + var testStack = new stackModule.StackLayout(); -// var btnWithClass = new button.Button(); -// btnWithClass.className = "test"; -// testStack.addChild(btnWithClass); + var btnWithClass = new buttonModule.Button(); + btnWithClass.className = "test"; + testStack.addChild(btnWithClass); -// var btnWithNoClass = new button.Button(); -// testStack.addChild(btnWithNoClass); + var btnWithNoClass = new buttonModule.Button(); + testStack.addChild(btnWithNoClass); -// var lblWithClass = new label.Label(); -// lblWithClass.className = "test"; -// testStack.addChild(lblWithClass); + var lblWithClass = new labelModule.Label(); + lblWithClass.className = "test"; + testStack.addChild(lblWithClass); -// testPage.css = "button.test { color: red; }"; + let testCss = "button.test { color: red; }"; + + let testFunc = function(views: Array) { + TKUnit.assert(btnWithClass.style.color, "Color property no applied correctly."); + TKUnit.assert(btnWithClass.style.color.hex === "#FF0000", "Color property no applied correctly."); -// // Act & Assert -// var finished = false; -// testPage.onNavigatedTo = function (context) { -// TKUnit.assert(btnWithClass.style.color, "Color property no applied correctly."); -// TKUnit.assert(btnWithClass.style.color.hex === "#FF0000", "Color property no applied correctly."); + TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value"); -// TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value"); + TKUnit.assert(lblWithClass.style.color === undefined, "Color should not have a value"); + } + + helper.buildUIAndRunTest(testStack, testFunc, testCss); +} -// TKUnit.assert(lblWithClass.style.color === undefined, "Color should not have a value"); +export var test_composite_selector_type_class_state = function () { + // Arrange + var testStack = new stackModule.StackLayout(); + + var btnWithClass = new buttonModule.Button(); + btnWithClass.className = "test"; + testStack.addChild(btnWithClass); -// finished = true; -// }; -// frame.topmost().navigate(testPage); + var btnWithNoClass = new buttonModule.Button(); + testStack.addChild(btnWithNoClass); -// TKUnit.waitUntilReady(function () { -// return finished; -// }, 3); -//} + var lblWithClass = new labelModule.Label(); + lblWithClass.className = "test"; + testStack.addChild(lblWithClass); -// TODO: Complex composite selectors are not supported yet -//export var test_composite_selector_type_class_state = function () { -// // Arrange -// var testPage = new page.Page(); -// var testStack = new stack.StackLayout(); -// testPage.content = testStack; + let testCss = "button.test:pressed { color: red; }"; + + let testFunc = function(views: Array) { + testButtonPressedStateIsRed(btnWithClass); -// var btnWithClass = new button.Button(); -// btnWithClass.className = "test"; -// testStack.addChild(btnWithClass); + // The button with no class should not react to state changes. + TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value."); + btnWithNoClass._goToVisualState("pressed"); + TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value."); + btnWithNoClass._goToVisualState("normal"); + TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value."); -// var btnWithNoClass = new button.Button(); -// testStack.addChild(btnWithNoClass); - -// var lblWithClass = new label.Label(); -// lblWithClass.className = "test"; -// testStack.addChild(lblWithClass); - -// testPage.css = "button.test:pressed { color: red; }"; - -// // Act & Assert -// var finished = false; -// testPage.onNavigatedTo = function (context) { -// testButtonPressedStateIsRed(btnWithClass); - -// // The button with no class should not react to state changes. -// TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value."); -// btnWithNoClass._goToVisualState("pressed"); -// TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value."); -// btnWithNoClass._goToVisualState("normal"); -// TKUnit.assert(btnWithNoClass.style.color === undefined, "Color should not have a value."); - -// TKUnit.assert(lblWithClass.style.color === undefined, "Color should not have a value"); - -// finished = true; -// }; -// frame.topmost().navigate(testPage); - -// TKUnit.waitUntilReady(function () { -// return finished; -// }, 3); -//} + TKUnit.assert(lblWithClass.style.color === undefined, "Color should not have a value"); + } + helper.buildUIAndRunTest(testStack, testFunc, testCss); +} export var test_style_is_applied_when_control_is_added_after_load = function () { var page: pageModule.Page; @@ -843,6 +823,637 @@ export function test_set_mixed_CSS_cases_works() { }, casedCSS); } +export function test_basic_hierarchical_selectors() { + let stack = new stackModule.StackLayout(); + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "stacklayout button { background-color: #FF0000; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#FF0000"); + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#FF0000"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_basic_hierarchical_direct_child_selectors() { + let stack = new stackModule.StackLayout(); + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "stacklayout > button { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#FF0000"); + // only buttons that are direct children of StackLayout should have red background color + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#00FF00"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_basic_hierarchical_direct_child_more_levels_selectors() { + let stack = new stackModule.StackLayout(); + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "stacklayout > wraplayout > button { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#00FF00"); + // only buttons that are direct children of StackLayout and WrapLayout should have red background color + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#FF0000"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_hierarchical_direct_child_more_levels_diff_selector_types() { + let stack = new stackModule.StackLayout(); + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + wrap.className = "wraplayoutClass"; + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + testButton2.className = "buttonClass"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "stacklayout>.wraplayoutClass > .buttonClass { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#00FF00"); + // only buttons that are direct children of StackLayout and WrapLayout should have red background color + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#FF0000"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_hierarchical_direct_child_more_levels_diff_selector_types2() { + let stack = new stackModule.StackLayout(); + stack.id = "stack"; + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + wrap.className = "wraplayoutClass"; + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + testButton2.className = "buttonClass"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "#stack>.wraplayoutClass>.buttonClass { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#00FF00"); + // only buttons that are direct children of Layout with id stack and Layout with cssClass wraplayoutClass should have red background color + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#FF0000"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_hierarchical_direct_child_more_levels_diff_selector_types_invalid() { + let stack = new stackModule.StackLayout(); + stack.id = "stack"; + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + wrap.className = "wraplayoutClass"; + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + testButton2.className = "buttonClass"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "#stackErr > .wraplayoutClass > .buttonClass { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#00FF00"); + // this is an invalid css so red style should not be applied + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#00FF00"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_hierarchical_direct_child_more_levels_diff_selector_types_invalid_middle() { + let stack = new stackModule.StackLayout(); + stack.id = "stack"; + let testButton1 = new buttonModule.Button(); + testButton1.text = "Test 1"; + testButton1.id = "testButton1"; + + let wrap = new wrapModule.WrapLayout(); + wrap.className = "wraplayoutClass"; + let testButton2 = new buttonModule.Button(); + testButton2.text = "Test 2"; + testButton2.id = "testButton2"; + testButton2.className = "buttonClass"; + + wrap.addChild(testButton2); + stack.addChild(testButton1); + stack.addChild(wrap); + + let testCss = "#stack > .wraplayoutClassErr > .buttonClass { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(stack.getViewById("testButton1"), "#00FF00"); + // this is an invalid css so red style should not be applied + helper.assertViewBackgroundColor(stack.getViewById("testButton2"), "#00FF00"); + } + + helper.buildUIAndRunTest(stack, testFunc, testCss); +} + +export function test_type_attr_selector() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "some value"; + + let testCss = "button[testAttr] { background-color: #FF0000; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_class_attr_selector() { + let testButton = new buttonModule.Button(); + testButton.className = "button"; + testButton["testAttr"] = "some value"; + + let testCss = ".button[testAttr] { background-color: #FF0000; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_id_attr_selector() { + let testButton = new buttonModule.Button(); + testButton.id = "myButton"; + testButton["testAttr"] = "some value"; + + let testCss = "#myButton[testAttr] { background-color: #FF0000; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_type_attr_value_selector() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "somevalue"; + + let testCss = "button[testAttr='somevalue'] { background-color: #FF0000; }"; + + let testFunc = function (views: Array) { + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_type_attr_invalid_value_selector() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "somevalue"; + + let testCss = "button[testAttr='value'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_tilde_attr_selector_correct_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower"; + + let testCss = "button[testAttr~='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_tilde_attr_selector_correct_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "some flower"; + + let testCss = "button[testAttr~='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_tilde_attr_selector_correct_syntax2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower new"; + + let testCss = "button[testAttr~='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_tilde_attr_selector_incorrect_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "my-flower"; + + let testCss = "button[testAttr~='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_tilde_attr_selector_incorrect_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowers"; + + let testCss = "button[testAttr~='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_tilde_attr_selector_incorrect_syntax2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower-house"; + + let testCss = "button[testAttr~='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_pipe_attr_selector_correct_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_pipe_attr_selector_correct_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower-house"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_pipe_attr_selector_incorrect_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowers"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_pipe_attr_selector_incorrect_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "myflower"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_pipe_attr_selector_incorrect_syntax2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "my-flower"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_power_attr_selector_correct_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower"; + + let testCss = "button[testAttr^='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_power_attr_selector_correct_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower-house"; + + let testCss = "button[testAttr^='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_power_attr_selector_correct_synta2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowers"; + + let testCss = "button[testAttr^='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_power_attr_selector_incorrect_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "myflower"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_power_attr_selector_incorrect_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "my-flower"; + + let testCss = "button[testAttr|='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_dollar_attr_selector_correct_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower"; + + let testCss = "button[testAttr$='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_dollar_attr_selector_correct_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "myflower"; + + let testCss = "button[testAttr$='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_dollar_attr_selector_correct_syntax2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "my-flower"; + + let testCss = "button[testAttr$='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_dollar_attr_selector_incorrect_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowers"; + + let testCss = "button[testAttr$='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_dollar_attr_selector_incorrect_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowermy"; + + let testCss = "button[testAttr$='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_dollar_attr_selector_incorrect_syntax2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower-my"; + + let testCss = "button[testAttr$='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_correct_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_correct_syntax1() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "myflower"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_correct_syntax2() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "my-flower"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_correct_syntax3() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowers"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_correct_syntax4() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flowermy"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_correct_syntax5() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flower-my"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } "; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#FF0000"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} + +export function test_star_attr_selector_incorrect_syntax() { + let testButton = new buttonModule.Button(); + testButton["testAttr"] = "flow"; + + let testCss = "button[testAttr*='flower'] { background-color: #FF0000; } button { background-color: #00FF00; }"; + + let testFunc = function (views: Array) { + // style from correct type css should be applied + helper.assertViewBackgroundColor(testButton, "#00FF00"); + } + helper.buildUIAndRunTest(testButton, testFunc, testCss); +} // // For information and example how to use style properties please refer to special [**Styling**](../../../styling.md) topic. // diff --git a/ui/core/bindable.ts b/ui/core/bindable.ts index 3eb0975f5..eaefe8e7a 100644 --- a/ui/core/bindable.ts +++ b/ui/core/bindable.ts @@ -9,6 +9,7 @@ import viewModule = require("ui/core/view"); import * as applicationModule from "application"; import * as polymerExpressionsModule from "js-libs/polymer-expressions"; import * as specialPropertiesModule from "ui/builder/special-properties"; +import * as utils from "utils/utils"; //late import var application: typeof applicationModule; @@ -332,8 +333,7 @@ export class Binding { // text="{{ sourceProperty = $parents['ListView'].test, expression = $parents['ListView'].test + 2}}" // update expression will be '$newPropertyValue + 2' // then on expression execution the new value will be taken and target property will be updated with the value of the expression. - var escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g; - var escapedSourceProperty = this.options.sourceProperty.replace(escapeRegex, '\\$&'); + var escapedSourceProperty = utils.escapeRegexSymbols(this.options.sourceProperty); var expRegex = new RegExp(escapedSourceProperty, 'g'); var resultExp = this.options.expression.replace(expRegex, bc.newPropertyValueKey); return resultExp; diff --git a/ui/styling/css-selector.d.ts b/ui/styling/css-selector.d.ts index 77fe993ce..88747193e 100644 --- a/ui/styling/css-selector.d.ts +++ b/ui/styling/css-selector.d.ts @@ -7,6 +7,7 @@ constructor(expression: string, declarations: cssParser.Declaration[]); expression: string; + attrExpression: string; declarations(): Array<{ property: string; value: any }>; diff --git a/ui/styling/css-selector.ts b/ui/styling/css-selector.ts index cb917cca4..be109e776 100644 --- a/ui/styling/css-selector.ts +++ b/ui/styling/css-selector.ts @@ -3,23 +3,45 @@ import observable = require("ui/core/dependency-observable"); import cssParser = require("css"); import * as trace from "trace"; import * as styleProperty from "ui/styling/style-property"; +import * as types from "utils/types"; +import * as utils from "utils/utils"; -var ID_SPECIFICITY = 10000; +var ID_SPECIFICITY = 1000000; +var ATTR_SPECIFITY = 10000; var CLASS_SPECIFICITY = 100; var TYPE_SPECIFICITY = 1; export class CssSelector { private _expression: string; private _declarations: cssParser.Declaration[]; + private _attrExpression: string; constructor(expression: string, declarations: cssParser.Declaration[]) { - this._expression = expression; + if (expression) { + let leftSquareBracketIndex = expression.indexOf(LSBRACKET); + if (leftSquareBracketIndex > 0) { + // extracts what is inside square brackets ([target = 'test'] will extract "target = 'test'") + var paramsRegex = /\[\s*(.*)\s*\]/; + let attrParams = paramsRegex.exec(expression); + if (attrParams && attrParams.length > 1) { + this._attrExpression = attrParams[1].trim(); + } + this._expression = expression.substr(0, leftSquareBracketIndex); + } + else { + this._expression = expression; + } + } this._declarations = declarations; } get expression(): string { return this._expression; } + + get attrExpression(): string { + return this._attrExpression; + } get declarations(): Array<{ property: string; value: any }> { return this._declarations; @@ -74,13 +96,33 @@ class CssTypeSelector extends CssSelector { return TYPE_SPECIFICITY; } public matches(view: view.View): boolean { - return matchesType(this.expression, view); + let result = matchesType(this.expression, view); + if (result && this.attrExpression) { + return matchesAttr(this.attrExpression, view); + } + return result; } } function matchesType(expression: string, view: view.View): boolean { - return expression.toLowerCase() === view.typeName.toLowerCase() || - expression.toLowerCase() === view.typeName.split(/(?=[A-Z])/).join("-").toLowerCase(); + let exprArr = expression.split("."); + let exprTypeName = exprArr[0]; + let exprClassName = exprArr[1]; + + let typeCheck = exprTypeName.toLowerCase() === view.typeName.toLowerCase() || + exprTypeName.toLowerCase() === view.typeName.split(/(?=[A-Z])/).join("-").toLowerCase(); + + if (typeCheck) { + if (exprClassName) { + return view._cssClasses.some((cssClass, i, arr) => { return cssClass === exprClassName }); + } + else { + return typeCheck; + } + } + else { + return false; + } } class CssIdSelector extends CssSelector { @@ -88,7 +130,11 @@ class CssIdSelector extends CssSelector { return ID_SPECIFICITY; } public matches(view: view.View): boolean { - return this.expression === view.id; + let result = this.expression === view.id; + if (result && this.attrExpression) { + return matchesAttr(this.attrExpression, view); + } + return result; } } @@ -98,10 +144,151 @@ class CssClassSelector extends CssSelector { } public matches(view: view.View): boolean { var expectedClass = this.expression; - return view._cssClasses.some((cssClass, i, arr) => { return cssClass === expectedClass }); + let result = view._cssClasses.some((cssClass, i, arr) => { return cssClass === expectedClass }); + if (result && this.attrExpression) { + return matchesAttr(this.attrExpression, view); + } + return result; } } +class CssCompositeSelector extends CssSelector { + get specificity(): number { + let result = 0; + for(let i = 0; i < this.parentCssSelectors.length; i++) { + result += this.parentCssSelectors[i].selector.specificity; + } + return result; + } + + private parentCssSelectors: [{ selector: CssSelector, onlyDirectParent: boolean}]; + + private splitExpression(expression) { + let result = []; + let tempArr = []; + let validSpace = true; + for (let i = 0; i < expression.length; i++) { + if (expression[i] === LSBRACKET) { + validSpace = false; + } + if (expression[i] === RSBRACKET) { + validSpace = true; + } + if ((expression[i] === SPACE && validSpace) || (expression[i] === GTHAN)) { + if (tempArr.length > 0) { + result.push(tempArr.join("")); + tempArr = []; + } + if (expression[i] === GTHAN) { + result.push(GTHAN); + } + continue; + } + tempArr.push(expression[i]); + } + if (tempArr.length > 0) { + result.push(tempArr.join("")); + } + return result; + } + + constructor(expr: string, declarations: cssParser.Declaration[]) { + super(expr, declarations); + let expressions = this.splitExpression(expr); + let onlyParent = false; + this.parentCssSelectors = []; + for(let i = expressions.length - 1; i >= 0; i--) { + if (expressions[i].trim() === GTHAN) { + onlyParent = true; + continue; + } + this.parentCssSelectors.push({selector: createSelector(expressions[i].trim(), null), onlyDirectParent: onlyParent}); + onlyParent = false; + } + } + + public matches(view: view.View): boolean { + let result = this.parentCssSelectors[0].selector.matches(view); + if (!result) { + return result; + } + let tempView = view.parent; + for(let i = 1; i < this.parentCssSelectors.length; i++) { + let parentCounter = 0; + while (tempView && parentCounter === 0) { + result = this.parentCssSelectors[i].selector.matches(tempView); + if (result) { + tempView = tempView.parent; + break; + } + if (this.parentCssSelectors[i].onlyDirectParent) { + parentCounter++; + } + tempView = tempView.parent; + } + if (!result) { + break; + return result; + } + } + return result; + } +} + +class CssAttrSelector extends CssSelector { + get specificity(): number { + return ATTR_SPECIFITY; + } + + public matches(view: view.View): boolean { + return matchesAttr(this.attrExpression, view); + } +} + +function matchesAttr(attrExpression: string, view: view.View): boolean { + let equalSignIndex = attrExpression.indexOf(EQUAL); + if (equalSignIndex > 0) { + let nameValueRegex = /(.*[^~|\^\$\*])[~|\^\$\*]?=(.*)/; + let nameValueRegexRes = nameValueRegex.exec(attrExpression); + let attrName; + let attrValue; + if (nameValueRegexRes && nameValueRegexRes.length > 2) { + attrName = nameValueRegexRes[1].trim(); + attrValue = nameValueRegexRes[2].trim().replace(/^(["'])*(.*)\1$/, '$2'); + } + // extract entire sign (=, ~=, |=, ^=, $=, *=) + let escapedAttrValue = utils.escapeRegexSymbols(attrValue); + let attrCheckRegex; + switch (attrExpression.charAt(equalSignIndex - 1)) { + case "~": + attrCheckRegex = new RegExp("(^|[^a-zA-Z-])" + escapedAttrValue + "([^a-zA-Z-]|$)"); + break; + case "|": + attrCheckRegex = new RegExp("^" + escapedAttrValue + "\\b"); + break; + case "^": + attrCheckRegex = new RegExp("^" + escapedAttrValue); + break; + case "$": + attrCheckRegex = new RegExp(escapedAttrValue + "$"); + break; + case "*": + attrCheckRegex = new RegExp(escapedAttrValue); + break; + + // only = (EQUAL) + default: + attrCheckRegex = new RegExp("^"+escapedAttrValue+"$"); + break; + } + return !types.isNullOrUndefined(view[attrName]) && attrCheckRegex.test(view[attrName]+""); + } + else { + return !types.isNullOrUndefined(view[attrExpression]); + } + return false; +} + export class CssVisualStateSelector extends CssSelector { private _key: string; private _match: string; @@ -109,9 +296,11 @@ export class CssVisualStateSelector extends CssSelector { private _isById: boolean; private _isByClass: boolean; private _isByType: boolean; + private _isByAttr: boolean; get specificity(): number { return (this._isById ? ID_SPECIFICITY : 0) + + (this._isByAttr ? ATTR_SPECIFITY : 0) + (this._isByClass ? CLASS_SPECIFICITY : 0) + (this._isByType ? TYPE_SPECIFICITY : 0); } @@ -131,12 +320,15 @@ export class CssVisualStateSelector extends CssSelector { this._key = args[0]; this._state = args[1]; - if (this._key.charAt(0) === AMP) { + if (this._key.charAt(0) === HASH) { this._match = this._key.substring(1); this._isById = true; } else if (this._key.charAt(0) === DOT) { this._match = this._key.substring(1); this._isByClass = true; + } else if (this._key.charAt(0) === LSBRACKET) { + this._match = this._key; + this._isByAttr = true; } else if (this._key.length > 0) { // handle the case when there is no key. E.x. ":pressed" selector this._match = this._key; @@ -158,31 +350,51 @@ export class CssVisualStateSelector extends CssSelector { if (this._isByType) { matches = matchesType(this._match, view); } + + if (this._isByAttr) { + matches = matchesAttr(this._key, view); + } return matches; } } -var AMP = "#", - DOT = ".", - COLON = ":"; +var HASH = "#"; +var DOT = "."; +var COLON = ":"; +var SPACE = " "; +var GTHAN = ">"; +var LSBRACKET = "["; +var RSBRACKET = "]"; +var EQUAL = "="; export function createSelector(expression: string, declarations: cssParser.Declaration[]): CssSelector { - var colonIndex = expression.indexOf(COLON); + let goodExpr = expression.replace(/>/g, " > ").replace(/\s\s+/g, " "); + var spaceIndex = goodExpr.indexOf(SPACE); + if (spaceIndex >= 0) { + return new CssCompositeSelector(goodExpr, declarations); + } + + let leftSquareBracketIndex = goodExpr.indexOf(LSBRACKET); + if (leftSquareBracketIndex === 0) { + return new CssAttrSelector(goodExpr, declarations); + } + + var colonIndex = goodExpr.indexOf(COLON); if (colonIndex >= 0) { - return new CssVisualStateSelector(expression, declarations); + return new CssVisualStateSelector(goodExpr, declarations); } - if (expression.charAt(0) === AMP) { - return new CssIdSelector(expression.substring(1), declarations); + if (goodExpr.charAt(0) === HASH) { + return new CssIdSelector(goodExpr.substring(1), declarations); } - if (expression.charAt(0) === DOT) { + if (goodExpr.charAt(0) === DOT) { // TODO: Combinations like label.center - return new CssClassSelector(expression.substring(1), declarations); + return new CssClassSelector(goodExpr.substring(1), declarations); } - return new CssTypeSelector(expression, declarations); + return new CssTypeSelector(goodExpr, declarations); } class InlineStyleSelector extends CssSelector { diff --git a/utils/utils-common.ts b/utils/utils-common.ts index 4616d2cc6..94cfa5a3f 100644 --- a/utils/utils-common.ts +++ b/utils/utils-common.ts @@ -29,6 +29,11 @@ export function parseJSON(source: string): any { return JSON.parse(src); } +export function escapeRegexSymbols(source: string): string { + let escapeRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g; + return source.replace(escapeRegex, "\\$&"); +} + export module layout { var MODE_SHIFT = 30; diff --git a/utils/utils.d.ts b/utils/utils.d.ts index adbbfa2aa..17cb6e9b0 100644 --- a/utils/utils.d.ts +++ b/utils/utils.d.ts @@ -229,4 +229,10 @@ * @param url The url. */ export function openUrl(url: string): boolean + + /** + * Escapes special regex symbols (., *, ^, $ and so on) in string in order to create a valid regex from it. + * @param source The original value. + */ + export function escapeRegexSymbols(source: string): string }