diff options
Diffstat (limited to 'devtools/client/inspector/rules/test')
207 files changed, 14659 insertions, 0 deletions
diff --git a/devtools/client/inspector/rules/test/.eslintrc.js b/devtools/client/inspector/rules/test/.eslintrc.js new file mode 100644 index 000000000..698ae9181 --- /dev/null +++ b/devtools/client/inspector/rules/test/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../../.eslintrc.mochitests.js" +}; diff --git a/devtools/client/inspector/rules/test/browser.ini b/devtools/client/inspector/rules/test/browser.ini new file mode 100644 index 000000000..2c11219fb --- /dev/null +++ b/devtools/client/inspector/rules/test/browser.ini @@ -0,0 +1,221 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + doc_author-sheet.html + doc_blob_stylesheet.html + doc_content_stylesheet.html + doc_content_stylesheet_imported.css + doc_content_stylesheet_imported2.css + doc_content_stylesheet_linked.css + doc_content_stylesheet_script.css + doc_copystyles.css + doc_copystyles.html + doc_cssom.html + doc_custom.html + doc_filter.html + doc_frame_script.js + doc_inline_sourcemap.html + doc_invalid_sourcemap.css + doc_invalid_sourcemap.html + doc_keyframeanimation.css + doc_keyframeanimation.html + doc_keyframeLineNumbers.html + doc_media_queries.html + doc_pseudoelement.html + doc_ruleLineNumbers.html + doc_sourcemaps.css + doc_sourcemaps.css.map + doc_sourcemaps.html + doc_sourcemaps.scss + doc_style_editor_link.css + doc_test_image.png + doc_urls_clickable.css + doc_urls_clickable.html + head.js + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + !/devtools/client/inspector/test/head.js + !/devtools/client/inspector/test/shared-head.js + !/devtools/client/shared/test/test-actor.js + !/devtools/client/shared/test/test-actor-registry.js + +[browser_rules_add-property-and-reselect.js] +[browser_rules_add-property-cancel_01.js] +[browser_rules_add-property-cancel_02.js] +[browser_rules_add-property-cancel_03.js] +[browser_rules_add-property-commented.js] +[browser_rules_add-property_01.js] +[browser_rules_add-property_02.js] +[browser_rules_add-property-svg.js] +[browser_rules_add-rule-and-property.js] +[browser_rules_add-rule-button-state.js] +[browser_rules_add-rule-edit-selector.js] +[browser_rules_add-rule-iframes.js] +[browser_rules_add-rule-namespace-elements.js] +[browser_rules_add-rule-pseudo-class.js] +[browser_rules_add-rule-then-property-edit-selector.js] +[browser_rules_add-rule-with-menu.js] +[browser_rules_add-rule.js] +[browser_rules_authored.js] +[browser_rules_authored_color.js] +[browser_rules_authored_override.js] +[browser_rules_blob_stylesheet.js] +[browser_rules_colorpicker-and-image-tooltip_01.js] +[browser_rules_colorpicker-and-image-tooltip_02.js] +[browser_rules_colorpicker-appears-on-swatch-click.js] +[browser_rules_colorpicker-commit-on-ENTER.js] +[browser_rules_colorpicker-edit-gradient.js] +[browser_rules_colorpicker-hides-on-tooltip.js] +[browser_rules_colorpicker-multiple-changes.js] +[browser_rules_colorpicker-release-outside-frame.js] +[browser_rules_colorpicker-revert-on-ESC.js] +[browser_rules_colorpicker-swatch-displayed.js] +[browser_rules_colorUnit.js] +[browser_rules_completion-existing-property_01.js] +[browser_rules_completion-existing-property_02.js] +[browser_rules_completion-new-property_01.js] +[browser_rules_completion-new-property_02.js] +[browser_rules_completion-new-property_03.js] +[browser_rules_completion-new-property_04.js] +[browser_rules_completion-new-property_multiline.js] +[browser_rules_computed-lists_01.js] +[browser_rules_computed-lists_02.js] +[browser_rules_completion-popup-hidden-after-navigation.js] +[browser_rules_content_01.js] +[browser_rules_content_02.js] +skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s +[browser_rules_context-menu-show-mdn-docs-01.js] +[browser_rules_context-menu-show-mdn-docs-02.js] +[browser_rules_context-menu-show-mdn-docs-03.js] +[browser_rules_copy_styles.js] +subsuite = clipboard +[browser_rules_cssom.js] +[browser_rules_cubicbezier-appears-on-swatch-click.js] +[browser_rules_cubicbezier-commit-on-ENTER.js] +[browser_rules_cubicbezier-revert-on-ESC.js] +[browser_rules_custom.js] +[browser_rules_cycle-angle.js] +[browser_rules_cycle-color.js] +[browser_rules_edit-display-grid-property.js] +[browser_rules_edit-property-cancel.js] +[browser_rules_edit-property-click.js] +[browser_rules_edit-property-commit.js] +[browser_rules_edit-property-computed.js] +[browser_rules_edit-property-increments.js] +[browser_rules_edit-property-order.js] +[browser_rules_edit-property-remove_01.js] +[browser_rules_edit-property-remove_02.js] +[browser_rules_edit-property-remove_03.js] +[browser_rules_edit-property_01.js] +[browser_rules_edit-property_02.js] +[browser_rules_edit-property_03.js] +[browser_rules_edit-property_04.js] +[browser_rules_edit-property_05.js] +[browser_rules_edit-property_06.js] +[browser_rules_edit-property_07.js] +[browser_rules_edit-property_08.js] +[browser_rules_edit-property_09.js] +[browser_rules_edit-selector-click.js] +[browser_rules_edit-selector-click-on-scrollbar.js] +skip-if = os == "mac" # Bug 1245996 : click on scrollbar not working on OSX +[browser_rules_edit-selector-commit.js] +[browser_rules_edit-selector_01.js] +[browser_rules_edit-selector_02.js] +[browser_rules_edit-selector_03.js] +[browser_rules_edit-selector_04.js] +[browser_rules_edit-selector_05.js] +[browser_rules_edit-selector_06.js] +[browser_rules_edit-selector_07.js] +[browser_rules_edit-selector_08.js] +[browser_rules_edit-selector_09.js] +[browser_rules_edit-selector_10.js] +[browser_rules_edit-selector_11.js] +[browser_rules_edit-value-after-name_01.js] +[browser_rules_edit-value-after-name_02.js] +[browser_rules_edit-value-after-name_03.js] +[browser_rules_edit-value-after-name_04.js] +[browser_rules_editable-field-focus_01.js] +[browser_rules_editable-field-focus_02.js] +[browser_rules_eyedropper.js] +[browser_rules_filtereditor-appears-on-swatch-click.js] +[browser_rules_filtereditor-commit-on-ENTER.js] +[browser_rules_filtereditor-revert-on-ESC.js] +skip-if = (os == "win" && debug) # bug 963492: win. +[browser_rules_grid-highlighter-on-navigate.js] +[browser_rules_grid-highlighter-on-reload.js] +[browser_rules_grid-toggle_01.js] +[browser_rules_grid-toggle_02.js] +[browser_rules_grid-toggle_03.js] +[browser_rules_guessIndentation.js] +[browser_rules_inherited-properties_01.js] +[browser_rules_inherited-properties_02.js] +[browser_rules_inherited-properties_03.js] +[browser_rules_inline-source-map.js] +[browser_rules_invalid.js] +[browser_rules_invalid-source-map.js] +[browser_rules_keybindings.js] +[browser_rules_keyframes-rule_01.js] +[browser_rules_keyframes-rule_02.js] +[browser_rules_keyframeLineNumbers.js] +[browser_rules_lineNumbers.js] +[browser_rules_livepreview.js] +[browser_rules_mark_overridden_01.js] +[browser_rules_mark_overridden_02.js] +[browser_rules_mark_overridden_03.js] +[browser_rules_mark_overridden_04.js] +[browser_rules_mark_overridden_05.js] +[browser_rules_mark_overridden_06.js] +[browser_rules_mark_overridden_07.js] +[browser_rules_mathml-element.js] +[browser_rules_media-queries.js] +[browser_rules_multiple-properties-duplicates.js] +[browser_rules_multiple-properties-priority.js] +[browser_rules_multiple-properties-unfinished_01.js] +[browser_rules_multiple-properties-unfinished_02.js] +[browser_rules_multiple_properties_01.js] +[browser_rules_multiple_properties_02.js] +[browser_rules_original-source-link.js] +[browser_rules_pseudo-element_01.js] +[browser_rules_pseudo-element_02.js] +[browser_rules_pseudo_lock_options.js] +[browser_rules_refresh-no-flicker.js] +[browser_rules_refresh-on-attribute-change_01.js] +[browser_rules_refresh-on-attribute-change_02.js] +[browser_rules_refresh-on-style-change.js] +[browser_rules_search-filter-computed-list_01.js] +[browser_rules_search-filter-computed-list_02.js] +[browser_rules_search-filter-computed-list_03.js] +[browser_rules_search-filter-computed-list_04.js] +[browser_rules_search-filter-computed-list_expander.js] +[browser_rules_search-filter-overridden-property.js] +[browser_rules_search-filter_01.js] +[browser_rules_search-filter_02.js] +[browser_rules_search-filter_03.js] +[browser_rules_search-filter_04.js] +[browser_rules_search-filter_05.js] +[browser_rules_search-filter_06.js] +[browser_rules_search-filter_07.js] +[browser_rules_search-filter_08.js] +[browser_rules_search-filter_09.js] +[browser_rules_search-filter_10.js] +[browser_rules_search-filter_context-menu.js] +subsuite = clipboard +[browser_rules_search-filter_escape-keypress.js] +[browser_rules_select-and-copy-styles.js] +subsuite = clipboard +[browser_rules_selector-highlighter-on-navigate.js] +[browser_rules_selector-highlighter_01.js] +[browser_rules_selector-highlighter_02.js] +[browser_rules_selector-highlighter_03.js] +[browser_rules_selector-highlighter_04.js] +[browser_rules_selector_highlight.js] +[browser_rules_strict-search-filter-computed-list_01.js] +[browser_rules_strict-search-filter_01.js] +[browser_rules_strict-search-filter_02.js] +[browser_rules_strict-search-filter_03.js] +[browser_rules_style-editor-link.js] +[browser_rules_urls-clickable.js] +[browser_rules_user-agent-styles.js] +[browser_rules_user-agent-styles-uneditable.js] +[browser_rules_user-property-reset.js] diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js b/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js new file mode 100644 index 000000000..492739abe --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js @@ -0,0 +1,44 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that adding properties to rules work and reselecting the element still +// show them. + +const TEST_URI = URL_ROOT + "doc_content_stylesheet.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode("#target", inspector); + + info("Setting a font-weight property on all rules"); + yield setPropertyOnAllRules(view); + + info("Reselecting the element"); + yield selectNode("body", inspector); + yield selectNode("#target", inspector); + + checkPropertyOnAllRules(view); +}); + +function* setPropertyOnAllRules(view) { + // Wait for the properties to be properly created on the backend and for the + // view to be updated. + let onRefreshed = view.once("ruleview-refreshed"); + for (let rule of view._elementStyle.rules) { + rule.editor.addProperty("font-weight", "bold", "", true); + } + yield onRefreshed; +} + +function checkPropertyOnAllRules(view) { + for (let rule of view._elementStyle.rules) { + let lastRule = rule.textProps[rule.textProps.length - 1]; + + is(lastRule.name, "font-weight", "Last rule name is font-weight"); + is(lastRule.value, "bold", "Last rule value is bold"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js new file mode 100644 index 000000000..78b3a4c91 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js @@ -0,0 +1,44 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a new property and escapes the new empty property name editor. + +const TEST_URI = ` + <style type="text/css"> + #testid { + background-color: blue; + } + .testclass { + background-color: green; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let elementRuleEditor = getRuleViewRuleEditor(view, 0); + let editor = yield focusNewRuleViewProperty(elementRuleEditor); + is(inplaceEditor(elementRuleEditor.newPropSpan), editor, + "The new property editor got focused"); + + info("Escape the new property editor"); + let onBlur = once(editor.input, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onBlur; + + info("Checking the state of cancelling a new property name editor"); + is(elementRuleEditor.rule.textProps.length, 0, + "Should have cancelled creating a new text property."); + ok(!elementRuleEditor.propertyList.hasChildNodes(), + "Should not have any properties."); + + is(view.styleDocument.activeElement, view.styleDocument.body, + "Correct element has focus"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js new file mode 100644 index 000000000..7f4d1564c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js @@ -0,0 +1,34 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a new property and escapes the new empty property value editor. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Test creating a new property and escaping"); + yield addProperty(view, 1, "color", "red", "VK_ESCAPE", false); + + is(view.styleDocument.activeElement, view.styleDocument.body, + "Correct element has focus"); + + let elementRuleEditor = getRuleViewRuleEditor(view, 1); + is(elementRuleEditor.rule.textProps.length, 1, + "Removed the new text property."); + is(elementRuleEditor.propertyList.children.length, 1, + "Removed the property editor."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js new file mode 100644 index 000000000..4f8b42009 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js @@ -0,0 +1,43 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a new property and escapes the property name editor with a +// value. + +const TEST_URI = ` + <style type='text/css'> + div { + background-color: blue; + } + </style> + <div>Test node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + // Add a property to the element's style declaration, add some text, + // then press escape. + + let elementRuleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusNewRuleViewProperty(elementRuleEditor); + + is(inplaceEditor(elementRuleEditor.newPropSpan), editor, + "Next focused editor should be the new property editor."); + + EventUtils.sendString("background", view.styleWindow); + + let onBlur = once(editor.input, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield onBlur; + + is(elementRuleEditor.rule.textProps.length, 1, + "Should have canceled creating a new text property."); + is(view.styleDocument.activeElement, view.styleDocument.body, + "Correct element has focus"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-commented.js b/devtools/client/inspector/rules/test/browser_rules_add-property-commented.js new file mode 100644 index 000000000..eacf5db5a --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property-commented.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that commented properties can be added and are disabled. + +const TEST_URI = "<div id='testid'></div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testCreateNewSetOfCommentedAndUncommentedProperties(view); +}); + +function* testCreateNewSetOfCommentedAndUncommentedProperties(view) { + info("Test creating a new set of commented and uncommented properties"); + + info("Focusing a new property name in the rule-view"); + let ruleEditor = getRuleViewRuleEditor(view, 0); + let editor = yield focusEditableField(view, ruleEditor.closeBrace); + is(inplaceEditor(ruleEditor.newPropSpan), editor, + "The new property editor has focus"); + + info( + "Entering a commented property/value pair into the property name editor"); + let input = editor.input; + input.value = `color: blue; + /* background-color: yellow; */ + width: 200px; + height: 100px; + /* padding-bottom: 1px; */`; + + info("Pressing return to commit and focus the new value field"); + let onModifications = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onModifications; + + let textProps = ruleEditor.rule.textProps; + ok(textProps[0].enabled, "The 'color' property is enabled."); + ok(!textProps[1].enabled, "The 'background-color' property is disabled."); + ok(textProps[2].enabled, "The 'width' property is enabled."); + ok(textProps[3].enabled, "The 'height' property is enabled."); + ok(!textProps[4].enabled, "The 'padding-bottom' property is disabled."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js b/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js new file mode 100644 index 000000000..a53421db3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js @@ -0,0 +1,22 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests editing SVG styles using the rules view. + +var TEST_URL = "chrome://global/skin/icons/warning.svg"; +var TEST_SELECTOR = "path"; + +add_task(function* () { + yield addTab(TEST_URL); + let {inspector, view} = yield openRuleView(); + yield selectNode(TEST_SELECTOR, inspector); + + info("Test creating a new property"); + yield addProperty(view, 0, "fill", "red"); + + is((yield getComputedStyleProperty(TEST_SELECTOR, null, "fill")), + "rgb(255, 0, 0)", "The fill was changed to red"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property_01.js b/devtools/client/inspector/rules/test/browser_rules_add-property_01.js new file mode 100644 index 000000000..1d7068d54 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property_01.js @@ -0,0 +1,32 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test adding an invalid property. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + .testclass { + background-color: green; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Test creating a new property"); + let textProp = yield addProperty(view, 0, "background-color", "#XYZ"); + + is(textProp.value, "#XYZ", "Text prop should have been changed."); + is(textProp.overridden, true, "Property should be overridden"); + is(textProp.editor.isValid(), false, "#XYZ should not be a valid entry"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property_02.js b/devtools/client/inspector/rules/test/browser_rules_add-property_02.js new file mode 100644 index 000000000..6f6bef0f7 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-property_02.js @@ -0,0 +1,65 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test adding a valid property to a CSS rule, and navigating through the fields +// by pressing ENTER. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: blue; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Focus the new property name field"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusNewRuleViewProperty(ruleEditor); + let input = editor.input; + + is(inplaceEditor(ruleEditor.newPropSpan), editor, + "Next focused editor should be the new property editor."); + ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, + "Editor contents are selected."); + + // Try clicking on the editor's input again, shouldn't cause trouble + // (see bug 761665). + EventUtils.synthesizeMouse(input, 1, 1, {}, view.styleWindow); + input.select(); + + info("Entering the property name"); + editor.input.value = "background-color"; + + info("Pressing RETURN and waiting for the value field focus"); + let onNameAdded = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + + yield onNameAdded; + + editor = inplaceEditor(view.styleDocument.activeElement); + + is(ruleEditor.rule.textProps.length, 2, + "Should have created a new text property."); + is(ruleEditor.propertyList.children.length, 2, + "Should have created a property editor."); + let textProp = ruleEditor.rule.textProps[1]; + is(editor, inplaceEditor(textProp.editor.valueSpan), + "Should be editing the value span now."); + + info("Entering the property value"); + let onValueAdded = view.once("ruleview-changed"); + editor.input.value = "purple"; + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onValueAdded; + + is(textProp.value, "purple", "Text prop should have been changed."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js new file mode 100644 index 000000000..1cf04a275 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js @@ -0,0 +1,30 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a new rule and a new property in this rule. + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8,<div id='testid'>Styled Node</div>"); + let {inspector, view} = yield openRuleView(); + + info("Selecting the test node"); + yield selectNode("#testid", inspector); + + info("Adding a new rule for this node and blurring the new selector field"); + yield addNewRuleAndDismissEditor(inspector, view, "#testid", 1); + + info("Adding a new property for this rule"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + + let onRuleViewChanged = view.once("ruleview-changed"); + ruleEditor.addProperty("font-weight", "bold", "", true); + yield onRuleViewChanged; + + let textProps = ruleEditor.rule.textProps; + let prop = textProps[textProps.length - 1]; + is(prop.name, "font-weight", "The last property name is font-weight"); + is(prop.value, "bold", "The last property value is bold"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js new file mode 100644 index 000000000..1441213b3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js @@ -0,0 +1,51 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests if the `Add rule` button disables itself properly for non-element nodes +// and anonymous element. + +const TEST_URI = ` + <style type="text/css"> + #pseudo::before { + content: "before"; + } + </style> + <div id="pseudo"></div> + <div id="testid">Test Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield testDisabledButton(inspector, view); +}); + +function* testDisabledButton(inspector, view) { + let node = "#testid"; + + info("Selecting a real element"); + yield selectNode(node, inspector); + ok(!view.addRuleButton.disabled, "Add rule button should be enabled"); + + info("Select a null element"); + yield view.selectElement(null); + ok(view.addRuleButton.disabled, "Add rule button should be disabled"); + + info("Selecting a real element"); + yield selectNode(node, inspector); + ok(!view.addRuleButton.disabled, "Add rule button should be enabled"); + + info("Selecting a pseudo element"); + let pseudo = yield getNodeFront("#pseudo", inspector); + let children = yield inspector.walker.children(pseudo); + let before = children.nodes[0]; + yield selectNode(before, inspector); + ok(view.addRuleButton.disabled, "Add rule button should be disabled"); + + info("Selecting a real element"); + yield selectNode(node, inspector); + ok(!view.addRuleButton.disabled, "Add rule button should be enabled"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js new file mode 100644 index 000000000..b59f317a5 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js @@ -0,0 +1,55 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the behaviour of adding a new rule to the rule view and editing +// its selector. + +const TEST_URI = ` + <style type="text/css"> + #testid { + text-align: center; + } + </style> + <div id="testid">Styled Node</div> + <span>This is a span</span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + yield addNewRule(inspector, view); + yield testEditSelector(view, "span"); + + info("Selecting the modified element with the new rule"); + yield selectNode("span", inspector); + yield checkModifiedElement(view, "span"); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector field"); + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let editor = idRuleEditor.selectorText.ownerDocument.activeElement; + + info("Entering a new selector name and committing"); + editor.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); +} + +function* checkModifiedElement(view, name) { + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js new file mode 100644 index 000000000..7b0ba7812 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js @@ -0,0 +1,57 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a rule on elements nested in iframes. + +const TEST_URI = + `<div>outer</div> + <iframe id="frame1" src="data:text/html;charset=utf-8,<div>inner1</div>"> + </iframe> + <iframe id="frame2" src="data:text/html;charset=utf-8,<div>inner2</div>"> + </iframe>`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + yield addNewRuleAndDismissEditor(inspector, view, "div", 1); + yield addNewProperty(view, 1, "color", "red"); + + let innerFrameDiv1 = yield getNodeFrontInFrame("div", "#frame1", inspector); + yield selectNode(innerFrameDiv1, inspector); + yield addNewRuleAndDismissEditor(inspector, view, "div", 1); + yield addNewProperty(view, 1, "color", "blue"); + + let innerFrameDiv2 = yield getNodeFrontInFrame("div", "#frame2", inspector); + yield selectNode(innerFrameDiv2, inspector); + yield addNewRuleAndDismissEditor(inspector, view, "div", 1); + yield addNewProperty(view, 1, "color", "green"); +}); + +/** + * Add a new property in the rule at the provided index in the rule view. + * + * @param {RuleView} view + * @param {Number} index + * The index of the rule in which we should add a new property. + * @param {String} name + * The name of the new property. + * @param {String} value + * The value of the new property. + */ +function* addNewProperty(view, index, name, value) { + let idRuleEditor = getRuleViewRuleEditor(view, index); + info(`Adding new property "${name}: ${value};"`); + + let onRuleViewChanged = view.once("ruleview-changed"); + idRuleEditor.addProperty(name, value, "", true); + yield onRuleViewChanged; + + let textProps = idRuleEditor.rule.textProps; + let lastProperty = textProps[textProps.length - 1]; + is(lastProperty.name, name, "Last property has the expected name"); + is(lastProperty.value, value, "Last property has the expected value"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js new file mode 100644 index 000000000..98e34e69f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js @@ -0,0 +1,41 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the behaviour of adding a new rule using the add rule button +// on namespaced elements. + +const XHTML = ` + <!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" + xmlns:svg="http://www.w3.org/2000/svg"> + <body> + <svg:svg width="100" height="100"> + <svg:clipPath> + <svg:rect x="0" y="0" width="10" height="5"></svg:rect> + </svg:clipPath> + <svg:circle cx="0" cy="0" r="5"></svg:circle> + </svg:svg> + </body> + </html> +`; +const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML); + +const TEST_DATA = [ + { node: "clipPath", expected: "clipPath" }, + { node: "rect", expected: "rect" }, + { node: "circle", expected: "circle" } +]; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + + for (let data of TEST_DATA) { + let {node, expected} = data; + yield selectNode(node, inspector); + yield addNewRuleAndDismissEditor(inspector, view, expected, 1); + } +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js new file mode 100644 index 000000000..39f773c13 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js @@ -0,0 +1,82 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a rule with pseudo class locks on. + +const TEST_URI = "<p id='element'>Test element</p>"; + +const EXPECTED_SELECTOR = "#element"; +const TEST_DATA = [ + [], + [":hover"], + [":hover", ":active"], + [":hover", ":active", ":focus"], + [":active"], + [":active", ":focus"], + [":focus"] +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#element", inspector); + + for (let data of TEST_DATA) { + yield runTestData(inspector, view, data); + } +}); + +function* runTestData(inspector, view, pseudoClasses) { + yield setPseudoLocks(inspector, view, pseudoClasses); + + let expected = EXPECTED_SELECTOR + pseudoClasses.join(""); + yield addNewRuleAndDismissEditor(inspector, view, expected, 1); + + yield resetPseudoLocks(inspector, view); +} + +function* setPseudoLocks(inspector, view, pseudoClasses) { + if (pseudoClasses.length == 0) { + return; + } + + for (let pseudoClass of pseudoClasses) { + switch (pseudoClass) { + case ":hover": + view.hoverCheckbox.click(); + yield inspector.once("rule-view-refreshed"); + break; + case ":active": + view.activeCheckbox.click(); + yield inspector.once("rule-view-refreshed"); + break; + case ":focus": + view.focusCheckbox.click(); + yield inspector.once("rule-view-refreshed"); + break; + } + } +} + +function* resetPseudoLocks(inspector, view) { + if (!view.hoverCheckbox.checked && + !view.activeCheckbox.checked && + !view.focusCheckbox.checked) { + return; + } + if (view.hoverCheckbox.checked) { + view.hoverCheckbox.click(); + yield inspector.once("rule-view-refreshed"); + } + if (view.activeCheckbox.checked) { + view.activeCheckbox.click(); + yield inspector.once("rule-view-refreshed"); + } + if (view.focusCheckbox.checked) { + view.focusCheckbox.click(); + yield inspector.once("rule-view-refreshed"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js new file mode 100644 index 000000000..294eb67e4 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js @@ -0,0 +1,80 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the behaviour of adding a new rule to the rule view, adding a new +// property and editing the selector. + +const TEST_URI = ` + <style type="text/css"> + #testid { + text-align: center; + } + </style> + <div id="testid">Styled Node</div> + <span>This is a span</span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + yield addNewRuleAndDismissEditor(inspector, view, "#testid", 1); + + info("Adding a new property to the new rule"); + yield testAddingProperty(view, 1); + + info("Editing existing selector field"); + yield testEditSelector(view, "span"); + + info("Selecting the modified element"); + yield selectNode("span", inspector); + + info("Check new rule and property exist in the modified element"); + yield checkModifiedElement(view, "span", 1); +}); + +function* testAddingProperty(view, index) { + let ruleEditor = getRuleViewRuleEditor(view, index); + ruleEditor.addProperty("font-weight", "bold", "", true); + let textProps = ruleEditor.rule.textProps; + let lastRule = textProps[textProps.length - 1]; + is(lastRule.name, "font-weight", "Last rule name is font-weight"); + is(lastRule.value, "bold", "Last rule value is bold"); +} + +function* testEditSelector(view, name) { + let idRuleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, idRuleEditor.selectorText); + + is(inplaceEditor(idRuleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name: " + name); + editor.input.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); +} + +function* checkModifiedElement(view, name, index) { + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); + + let idRuleEditor = getRuleViewRuleEditor(view, index); + let textProps = idRuleEditor.rule.textProps; + let lastRule = textProps[textProps.length - 1]; + is(lastRule.name, "font-weight", "Last rule name is font-weight"); + is(lastRule.value, "bold", "Last rule value is bold"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js new file mode 100644 index 000000000..976fc9643 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js @@ -0,0 +1,42 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the a new CSS rule can be added using the context menu. + +const TEST_URI = '<div id="testid">Test Node</div>'; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield selectNode("#testid", inspector); + yield addNewRuleFromContextMenu(inspector, view); + yield testNewRule(view); +}); + +function* addNewRuleFromContextMenu(inspector, view) { + info("Waiting for context menu to be shown"); + + let allMenuItems = openStyleContextMenuAndGetAllItems(view, view.element); + let menuitemAddRule = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.addNewRule")); + + ok(menuitemAddRule.visible, "Add rule is visible"); + + info("Adding the new rule and expecting a ruleview-changed event"); + let onRuleViewChanged = view.once("ruleview-changed"); + menuitemAddRule.click(); + yield onRuleViewChanged; +} + +function* testNewRule(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = ruleEditor.selectorText.ownerDocument.activeElement; + is(editor.value, "#testid", "Selector editor value is as expected"); + + info("Escaping from the selector field the change"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule.js b/devtools/client/inspector/rules/test/browser_rules_add-rule.js new file mode 100644 index 000000000..296105c85 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_add-rule.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a new rule using the add rule button. + +const TEST_URI = ` + <style type="text/css"> + .testclass { + text-align: center; + } + </style> + <div id="testid" class="testclass">Styled Node</div> + <span class="testclass2">This is a span</span> + <span class="class1 class2">Multiple classes</span> + <span class="class3 class4">Multiple classes</span> + <p>Empty<p> + <h1 class="asd@@@@a!!!!:::@asd">Invalid characters in class</h1> + <h2 id="asd@@@a!!2a">Invalid characters in id</h2> + <svg viewBox="0 0 10 10"> + <circle cx="5" cy="5" r="5" fill="blue"></circle> + </svg> +`; + +const TEST_DATA = [ + { node: "#testid", expected: "#testid" }, + { node: ".testclass2", expected: ".testclass2" }, + { node: ".class1.class2", expected: ".class1.class2" }, + { node: ".class3.class4", expected: ".class3.class4" }, + { node: "p", expected: "p" }, + { node: "h1", expected: ".asd\\@\\@\\@\\@a\\!\\!\\!\\!\\:\\:\\:\\@asd" }, + { node: "h2", expected: "#asd\\@\\@\\@a\\!\\!2a" }, + { node: "circle", expected: "circle" } +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + for (let data of TEST_DATA) { + let {node, expected} = data; + yield selectNode(node, inspector); + yield addNewRuleAndDismissEditor(inspector, view, expected, 1); + } +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_authored.js b/devtools/client/inspector/rules/test/browser_rules_authored.js new file mode 100644 index 000000000..cb0dd1186 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_authored.js @@ -0,0 +1,49 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for as-authored styles. + +function* createTestContent(style) { + let html = `<style type="text/css"> + ${style} + </style> + <div id="testid" class="testclass">Styled Node</div>`; + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html)); + + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + return view; +} + +add_task(function* () { + let view = yield createTestContent("#testid {" + + // Invalid property. + " something: random;" + + // Invalid value. + " color: orang;" + + // Override. + " background-color: blue;" + + " background-color: #f0c;" + + "} "); + + let elementStyle = view._elementStyle; + + let expected = [ + {name: "something", overridden: true}, + {name: "color", overridden: true}, + {name: "background-color", overridden: true}, + {name: "background-color", overridden: false} + ]; + + let rule = elementStyle.rules[1]; + + for (let i = 0; i < expected.length; ++i) { + let prop = rule.textProps[i]; + is(prop.name, expected[i].name, "test name for prop " + i); + is(prop.overridden, expected[i].overridden, + "test overridden for prop " + i); + } +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_authored_color.js b/devtools/client/inspector/rules/test/browser_rules_authored_color.js new file mode 100644 index 000000000..4c5cab206 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_authored_color.js @@ -0,0 +1,67 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for as-authored color styles. + +/** + * Array of test color objects: + * {String} name: name of the used & expected color format. + * {String} id: id of the element that will be created to test this color. + * {String} color: initial value of the color property applied to the test element. + * {String} result: expected value of the color property after edition. + */ +const colors = [ + {name: "hex", id: "test1", color: "#f0c", result: "#0f0"}, + {name: "rgb", id: "test2", color: "rgb(0,128,250)", result: "rgb(0, 255, 0)"}, + // Test case preservation. + {name: "hex", id: "test3", color: "#F0C", result: "#0F0"}, +]; + +add_task(function* () { + Services.prefs.setCharPref("devtools.defaultColorUnit", "authored"); + + let html = ""; + for (let {color, id} of colors) { + html += `<div id="${id}" style="color: ${color}">Styled Node</div>`; + } + + let tab = yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html)); + + let {inspector, view} = yield openRuleView(); + + for (let color of colors) { + let cPicker = view.tooltips.colorPicker; + let selector = "#" + color.id; + yield selectNode(selector, inspector); + + let swatch = getRuleViewProperty(view, "element", "color").valueSpan + .querySelector(".ruleview-colorswatch"); + let onColorPickerReady = cPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], { + selector, + name: "color", + value: "rgb(0, 255, 0)" + }); + + let spectrum = cPicker.spectrum; + let onHidden = cPicker.tooltip.once("hidden"); + // Validating the color change ends up updating the rule view twice + let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2); + focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN"); + yield onHidden; + yield onRuleViewChanged; + + is(getRuleViewPropertyValue(view, "element", "color"), color.result, + "changing the color preserved the unit for " + color.name); + } + + let target = TargetFactory.forTab(tab); + yield gDevTools.closeToolbox(target); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_authored_override.js b/devtools/client/inspector/rules/test/browser_rules_authored_override.js new file mode 100644 index 000000000..7305e5712 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_authored_override.js @@ -0,0 +1,53 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for as-authored styles. + +function* createTestContent(style) { + let html = `<style type="text/css"> + ${style} + </style> + <div id="testid" class="testclass">Styled Node</div>`; + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html)); + + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + return view; +} + +add_task(function* () { + let gradientText1 = "(orange, blue);"; + let gradientText2 = "(pink, teal);"; + + let view = + yield createTestContent("#testid {" + + " background-image: linear-gradient" + + gradientText1 + + " background-image: -ms-linear-gradient" + + gradientText2 + + " background-image: linear-gradient" + + gradientText2 + + "} "); + + let elementStyle = view._elementStyle; + let rule = elementStyle.rules[1]; + + // Initially the last property should be active. + for (let i = 0; i < 3; ++i) { + let prop = rule.textProps[i]; + is(prop.name, "background-image", "check the property name"); + is(prop.overridden, i !== 2, "check overridden for " + i); + } + + yield togglePropStatus(view, rule.textProps[2]); + + // Now the first property should be active. + for (let i = 0; i < 3; ++i) { + let prop = rule.textProps[i]; + is(prop.overridden || !prop.enabled, i !== 0, + "post-change check overridden for " + i); + } +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js b/devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js new file mode 100644 index 000000000..adc8eb2ee --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js @@ -0,0 +1,20 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view content is correct for stylesheet generated +// with createObjectURL(cssBlob) +const TEST_URL = URL_ROOT + "doc_blob_stylesheet.html"; + +add_task(function* () { + yield addTab(TEST_URL); + let {inspector, view} = yield openRuleView(); + + yield selectNode("h1", inspector); + is(view.element.querySelectorAll("#noResults").length, 0, + "The no-results element is not displayed"); + + is(view.element.querySelectorAll(".ruleview-rule").length, 2, + "There are 2 displayed rules"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_colorUnit.js b/devtools/client/inspector/rules/test/browser_rules_colorUnit.js new file mode 100644 index 000000000..138f68365 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorUnit.js @@ -0,0 +1,65 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that color selection respects the user pref. + +const TEST_URI = ` + <style type='text/css'> + #testid { + color: blue; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + let TESTS = [ + {name: "hex", result: "#0f0"}, + {name: "rgb", result: "rgb(0, 255, 0)"} + ]; + + for (let {name, result} of TESTS) { + info("starting test for " + name); + Services.prefs.setCharPref("devtools.defaultColorUnit", name); + + let tab = yield addTab("data:text/html;charset=utf-8," + + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield selectNode("#testid", inspector); + yield basicTest(view, name, result); + + let target = TargetFactory.forTab(tab); + yield gDevTools.closeToolbox(target); + gBrowser.removeCurrentTab(); + } +}); + +function* basicTest(view, name, result) { + let cPicker = view.tooltips.colorPicker; + let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan + .querySelector(".ruleview-colorswatch"); + let onColorPickerReady = cPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], { + selector: "#testid", + name: "color", + value: "rgb(0, 255, 0)" + }); + + let spectrum = cPicker.spectrum; + let onHidden = cPicker.tooltip.once("hidden"); + // Validating the color change ends up updating the rule view twice + let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2); + focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN"); + yield onHidden; + yield onRuleViewChanged; + + is(getRuleViewPropertyValue(view, "#testid", "color"), result, + "changing the color used the " + name + " unit"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js new file mode 100644 index 000000000..a8d2fd5f1 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js @@ -0,0 +1,63 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that after a color change, the image preview tooltip in the same +// property is displayed and positioned correctly. +// See bug 979292 + +const TEST_URI = ` + <style type="text/css"> + body { + background: url("chrome://global/skin/icons/warning-64.png"), linear-gradient(white, #F06 400px); + } + </style> + Testing the color picker tooltip! +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + let value = getRuleViewProperty(view, "body", "background").valueSpan; + let swatch = value.querySelectorAll(".ruleview-colorswatch")[0]; + let url = value.querySelector(".theme-link"); + yield testImageTooltipAfterColorChange(swatch, url, view); +}); + +function* testImageTooltipAfterColorChange(swatch, url, ruleView) { + info("First, verify that the image preview tooltip works"); + let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, + url); + ok(anchor, "The image preview tooltip is shown on the url span"); + is(anchor, url, "The anchor returned by the showOnHover callback is correct"); + + info("Open the color picker tooltip and change the color"); + let picker = ruleView.tooltips.colorPicker; + let onColorPickerReady = picker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + yield simulateColorPickerChange(ruleView, picker, [0, 0, 0, 1], { + selector: "body", + name: "background-image", + value: 'url("chrome://global/skin/icons/warning-64.png"), linear-gradient(rgb(0, 0, 0), rgb(255, 0, 102) 400px)' + }); + + let spectrum = picker.spectrum; + let onHidden = picker.tooltip.once("hidden"); + let onModifications = ruleView.once("ruleview-changed"); + focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN"); + yield onHidden; + yield onModifications; + + info("Verify again that the image preview tooltip works"); + // After a color change, the property is re-populated, we need to get the new + // dom node + url = getRuleViewProperty(ruleView, "body", "background").valueSpan + .querySelector(".theme-link"); + anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url); + ok(anchor, "The image preview tooltip is shown on the url span"); + is(anchor, url, "The anchor returned by the showOnHover callback is correct"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js new file mode 100644 index 000000000..743ad5180 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js @@ -0,0 +1,66 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that after a color change, opening another tooltip, like the image +// preview doesn't revert the color change in the rule view. +// This used to happen when the activeSwatch wasn't reset when the colorpicker +// would hide. +// See bug 979292 + +const TEST_URI = ` + <style type="text/css"> + body { + background: red url("chrome://global/skin/icons/warning-64.png") + no-repeat center center; + } + </style> + Testing the color picker tooltip! +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + yield testColorChangeIsntRevertedWhenOtherTooltipIsShown(view); +}); + +function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) { + let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan + .querySelector(".ruleview-colorswatch"); + + info("Open the color picker tooltip and change the color"); + let picker = ruleView.tooltips.colorPicker; + let onColorPickerReady = picker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + yield simulateColorPickerChange(ruleView, picker, [0, 0, 0, 1], { + selector: "body", + name: "background-color", + value: "rgb(0, 0, 0)" + }); + + let spectrum = picker.spectrum; + + let onModifications = waitForNEvents(ruleView, "ruleview-changed", 2); + let onHidden = picker.tooltip.once("hidden"); + focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN"); + yield onHidden; + yield onModifications; + + info("Open the image preview tooltip"); + let value = getRuleViewProperty(ruleView, "body", "background").valueSpan; + let url = value.querySelector(".theme-link"); + let onShown = ruleView.tooltips.previewTooltip.once("shown"); + let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url); + ruleView.tooltips.previewTooltip.show(anchor); + yield onShown; + + info("Image tooltip is shown, verify that the swatch is still correct"); + swatch = value.querySelector(".ruleview-colorswatch"); + is(swatch.style.backgroundColor, "black", + "The swatch's color is correct"); + is(swatch.nextSibling.textContent, "black", "The color name is correct"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js new file mode 100644 index 000000000..383ffed6c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js @@ -0,0 +1,51 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that color pickers appear when clicking on color swatches. + +const TEST_URI = ` + <style type="text/css"> + body { + color: red; + background-color: #ededed; + background-image: url(chrome://global/skin/icons/warning-64.png); + border: 2em solid rgba(120, 120, 120, .5); + } + </style> + Testing the color picker tooltip! +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + let propertiesToTest = ["color", "background-color", "border"]; + + for (let property of propertiesToTest) { + info("Testing that the colorpicker appears on swatch click"); + let value = getRuleViewProperty(view, "body", property).valueSpan; + let swatch = value.querySelector(".ruleview-colorswatch"); + yield testColorPickerAppearsOnColorSwatchClick(view, swatch); + } +}); + +function* testColorPickerAppearsOnColorSwatchClick(view, swatch) { + let cPicker = view.tooltips.colorPicker; + ok(cPicker, "The rule-view has the expected colorPicker property"); + + let cPickerPanel = cPicker.tooltip.panel; + ok(cPickerPanel, "The XUL panel for the color picker exists"); + + let onColorPickerReady = cPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + ok(true, "The color picker was shown on click of the color swatch"); + ok(!inplaceEditor(swatch.parentNode), + "The inplace editor wasn't shown as a result of the color swatch click"); + + yield hideTooltipAndWaitForRuleViewChanged(cPicker, view); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js new file mode 100644 index 000000000..129e8f245 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js @@ -0,0 +1,61 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that a color change in the color picker is committed when ENTER is +// pressed. + +const TEST_URI = ` + <style type="text/css"> + body { + border: 2em solid rgba(120, 120, 120, .5); + } + </style> + Testing the color picker tooltip! +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + let swatch = getRuleViewProperty(view, "body", "border").valueSpan + .querySelector(".ruleview-colorswatch"); + yield testPressingEnterCommitsChanges(swatch, view); +}); + +function* testPressingEnterCommitsChanges(swatch, ruleView) { + let cPicker = ruleView.tooltips.colorPicker; + + let onColorPickerReady = cPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + yield simulateColorPickerChange(ruleView, cPicker, [0, 255, 0, .5], { + selector: "body", + name: "border-left-color", + value: "rgba(0, 255, 0, 0.5)" + }); + + is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)", + "The color swatch's background was updated"); + is(getRuleViewProperty(ruleView, "body", "border").valueSpan.textContent, + "2em solid rgba(0, 255, 0, 0.5)", + "The text of the border css property was updated"); + + let onModified = ruleView.once("ruleview-changed"); + let spectrum = cPicker.spectrum; + let onHidden = cPicker.tooltip.once("hidden"); + focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN"); + yield onHidden; + yield onModified; + + is((yield getComputedStyleProperty("body", null, "border-left-color")), + "rgba(0, 255, 0, 0.5)", "The element's border was kept after RETURN"); + is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)", + "The color swatch's background was kept after RETURN"); + is(getRuleViewProperty(ruleView, "body", "border").valueSpan.textContent, + "2em solid rgba(0, 255, 0, 0.5)", + "The text of the border css property was kept after RETURN"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js new file mode 100644 index 000000000..71ceb14c3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js @@ -0,0 +1,77 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that changing a color in a gradient css declaration using the tooltip +// color picker works. + +const TEST_URI = ` + <style type="text/css"> + body { + background-image: linear-gradient(to left, #f06 25%, #333 95%, #000 100%); + } + </style> + Updating a gradient declaration with the color picker tooltip +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + info("Testing that the colors in gradient properties are parsed correctly"); + testColorParsing(view); + + info("Testing that changing one of the colors of a gradient property works"); + yield testPickingNewColor(view); +}); + +function testColorParsing(view) { + let ruleEl = getRuleViewProperty(view, "body", "background-image"); + ok(ruleEl, "The background-image gradient declaration was found"); + + let swatchEls = ruleEl.valueSpan.querySelectorAll(".ruleview-colorswatch"); + ok(swatchEls, "The color swatch elements were found"); + is(swatchEls.length, 3, "There are 3 color swatches"); + + let colorEls = ruleEl.valueSpan.querySelectorAll(".ruleview-color"); + ok(colorEls, "The color elements were found"); + is(colorEls.length, 3, "There are 3 color values"); + + let colors = ["#f06", "#333", "#000"]; + for (let i = 0; i < colors.length; i++) { + is(colorEls[i].textContent, colors[i], "The right color value was found"); + } +} + +function* testPickingNewColor(view) { + // Grab the first color swatch and color in the gradient + let ruleEl = getRuleViewProperty(view, "body", "background-image"); + let swatchEl = ruleEl.valueSpan.querySelector(".ruleview-colorswatch"); + let colorEl = ruleEl.valueSpan.querySelector(".ruleview-color"); + + info("Get the color picker tooltip and clicking on the swatch to show it"); + let cPicker = view.tooltips.colorPicker; + let onColorPickerReady = cPicker.once("ready"); + swatchEl.click(); + yield onColorPickerReady; + + let change = { + selector: "body", + name: "background-image", + value: "linear-gradient(to left, rgb(1, 1, 1) 25%, " + + "rgb(51, 51, 51) 95%, rgb(0, 0, 0) 100%)" + }; + yield simulateColorPickerChange(view, cPicker, [1, 1, 1, 1], change); + + is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)", + "The color swatch's background was updated"); + is(colorEl.textContent, "#010101", "The color text was updated"); + is((yield getComputedStyleProperty("body", null, "background-image")), + "linear-gradient(to left, rgb(1, 1, 1) 25%, rgb(51, 51, 51) 95%, " + + "rgb(0, 0, 0) 100%)", + "The gradient has been updated correctly"); + + yield hideTooltipAndWaitForRuleViewChanged(cPicker, view); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js new file mode 100644 index 000000000..b50c63605 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js @@ -0,0 +1,46 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the color picker tooltip hides when an image tooltip appears. + +const TEST_URI = ` + <style type="text/css"> + body { + color: red; + background-color: #ededed; + background-image: url(chrome://global/skin/icons/warning-64.png); + border: 2em solid rgba(120, 120, 120, .5); + } + </style> + Testing the color picker tooltip! +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + let swatch = getRuleViewProperty(view, "body", "color").valueSpan + .querySelector(".ruleview-colorswatch"); + + let bgImageSpan = getRuleViewProperty(view, "body", "background-image").valueSpan; + let uriSpan = bgImageSpan.querySelector(".theme-link"); + + let colorPicker = view.tooltips.colorPicker; + info("Showing the color picker tooltip by clicking on the color swatch"); + let onColorPickerReady = colorPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + info("Now showing the image preview tooltip to hide the color picker"); + let onHidden = colorPicker.tooltip.once("hidden"); + // Hiding the color picker refreshes the value. + let onRuleViewChanged = view.once("ruleview-changed"); + yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan); + yield onHidden; + yield onRuleViewChanged; + + ok(true, "The color picker closed when the image preview tooltip appeared"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js new file mode 100644 index 000000000..06fab72d6 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js @@ -0,0 +1,124 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the color in the colorpicker tooltip can be changed several times. +// without causing error in various cases: +// - simple single-color property (color) +// - color and image property (background-image) +// - overridden property +// See bug 979292 and bug 980225 + +const TEST_URI = ` + <style type="text/css"> + body { + color: green; + background: red url("chrome://global/skin/icons/warning-64.png") + no-repeat center center; + } + p { + color: blue; + } + </style> + <p>Testing the color picker tooltip!</p> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield testSimpleMultipleColorChanges(inspector, view); + yield testComplexMultipleColorChanges(inspector, view); + yield testOverriddenMultipleColorChanges(inspector, view); +}); + +function* testSimpleMultipleColorChanges(inspector, ruleView) { + yield selectNode("p", inspector); + + info("Getting the <p> tag's color property"); + let swatch = getRuleViewProperty(ruleView, "p", "color").valueSpan + .querySelector(".ruleview-colorswatch"); + + info("Opening the color picker"); + let picker = ruleView.tooltips.colorPicker; + let onColorPickerReady = picker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + info("Changing the color several times"); + let colors = [ + {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"}, + {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"}, + {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"} + ]; + for (let {rgba, computed} of colors) { + yield simulateColorPickerChange(ruleView, picker, rgba, { + selector: "p", + name: "color", + value: computed + }); + } +} + +function* testComplexMultipleColorChanges(inspector, ruleView) { + yield selectNode("body", inspector); + + info("Getting the <body> tag's color property"); + let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan + .querySelector(".ruleview-colorswatch"); + + info("Opening the color picker"); + let picker = ruleView.tooltips.colorPicker; + let onColorPickerReady = picker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + info("Changing the color several times"); + let colors = [ + {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"}, + {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"}, + {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"} + ]; + for (let {rgba, computed} of colors) { + yield simulateColorPickerChange(ruleView, picker, rgba, { + selector: "body", + name: "background-color", + value: computed + }); + } + + info("Closing the color picker"); + yield hideTooltipAndWaitForRuleViewChanged(picker, ruleView); +} + +function* testOverriddenMultipleColorChanges(inspector, ruleView) { + yield selectNode("p", inspector); + + info("Getting the <body> tag's color property"); + let swatch = getRuleViewProperty(ruleView, "body", "color").valueSpan + .querySelector(".ruleview-colorswatch"); + + info("Opening the color picker"); + let picker = ruleView.tooltips.colorPicker; + let onColorPickerReady = picker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + info("Changing the color several times"); + let colors = [ + {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"}, + {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"}, + {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"} + ]; + for (let {rgba, computed} of colors) { + yield simulateColorPickerChange(ruleView, picker, rgba, { + selector: "body", + name: "color", + value: computed + }); + is((yield getComputedStyleProperty("p", null, "color")), + "rgb(200, 200, 200)", "The color of the P tag is still correct"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js new file mode 100644 index 000000000..ef6ca02b1 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js @@ -0,0 +1,67 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that color pickers stops following the pointer if the pointer is +// released outside the tooltip frame (bug 1160720). + +const TEST_URI = "<body style='color: red'>Test page for bug 1160720"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + let cSwatch = getRuleViewProperty(view, "element", "color").valueSpan + .querySelector(".ruleview-colorswatch"); + + let picker = yield openColorPickerForSwatch(cSwatch, view); + let spectrum = picker.spectrum; + let change = spectrum.once("changed"); + + info("Pressing mouse down over color picker."); + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeMouseAtCenter(spectrum.dragger, { + type: "mousedown", + }, spectrum.dragger.ownerDocument.defaultView); + yield onRuleViewChanged; + + let value = yield change; + info(`Color changed to ${value} on mousedown.`); + + // If the mousemove below fails to detect that the button is no longer pressed + // the spectrum will update and emit changed event synchronously after calling + // synthesizeMouse so this handler is executed before the test ends. + spectrum.once("changed", (event, newValue) => { + is(newValue, value, "Value changed on mousemove without a button pressed."); + }); + + // Releasing the button pressed by mousedown above on top of a different frame + // does not make sense in this test as EventUtils doesn't preserve the context + // i.e. the buttons that were pressed down between events. + + info("Moving mouse over color picker without any buttons pressed."); + + EventUtils.synthesizeMouse(spectrum.dragger, 10, 10, { + // -1 = no buttons are pressed down + button: -1, + type: "mousemove", + }, spectrum.dragger.ownerDocument.defaultView); +}); + +function* openColorPickerForSwatch(swatch, view) { + let cPicker = view.tooltips.colorPicker; + ok(cPicker, "The rule-view has the expected colorPicker property"); + + let cPickerPanel = cPicker.tooltip.panel; + ok(cPickerPanel, "The XUL panel for the color picker exists"); + + let onColorPickerReady = cPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + ok(true, "The color picker was shown on click of the color swatch"); + + return cPicker; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js new file mode 100644 index 000000000..e244d429c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js @@ -0,0 +1,109 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that a color change in the color picker is reverted when ESC is +// pressed. + +const TEST_URI = ` + <style type="text/css"> + body { + background-color: #EDEDED; + } + </style> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + yield testPressingEscapeRevertsChanges(view); + yield testPressingEscapeRevertsChangesAndDisables(view); +}); + +function* testPressingEscapeRevertsChanges(view) { + let {swatch, propEditor, cPicker} = yield openColorPickerAndSelectColor(view, + 1, 0, [0, 0, 0, 1], { + selector: "body", + name: "background-color", + value: "rgb(0, 0, 0)" + }); + + is(swatch.style.backgroundColor, "rgb(0, 0, 0)", + "The color swatch's background was updated"); + is(propEditor.valueSpan.textContent, "#000", + "The text of the background-color css property was updated"); + + let spectrum = cPicker.spectrum; + + info("Pressing ESCAPE to close the tooltip"); + let onHidden = cPicker.tooltip.once("hidden"); + let onModifications = view.once("ruleview-changed"); + EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView); + yield onHidden; + yield onModifications; + + yield waitForComputedStyleProperty("body", null, "background-color", + "rgb(237, 237, 237)"); + is(propEditor.valueSpan.textContent, "#EDEDED", + "Got expected property value."); +} + +function* testPressingEscapeRevertsChangesAndDisables(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Disabling background-color property"); + let textProp = ruleEditor.rule.textProps[0]; + yield togglePropStatus(view, textProp); + + ok(textProp.editor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(textProp.editor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!textProp.editor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + ok(!textProp.editor.prop.enabled, + "background-color property is disabled."); + let newValue = yield getRulePropertyValue("background-color"); + is(newValue, "", "background-color should have been unset."); + + let {cPicker} = yield openColorPickerAndSelectColor(view, + 1, 0, [0, 0, 0, 1]); + + ok(!textProp.editor.element.classList.contains("ruleview-overridden"), + "property overridden is not displayed."); + is(textProp.editor.enable.style.visibility, "hidden", + "property enable checkbox is hidden."); + + let spectrum = cPicker.spectrum; + + info("Pressing ESCAPE to close the tooltip"); + let onHidden = cPicker.tooltip.once("hidden"); + let onModifications = view.once("ruleview-changed"); + EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView); + yield onHidden; + yield onModifications; + + ok(textProp.editor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(textProp.editor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!textProp.editor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + ok(!textProp.editor.prop.enabled, + "background-color property is disabled."); + newValue = yield getRulePropertyValue("background-color"); + is(newValue, "", "background-color should have been unset."); + is(textProp.editor.valueSpan.textContent, "#EDEDED", + "Got expected property value."); +} + +function* getRulePropertyValue(name) { + let propValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: name + }); + return propValue; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js new file mode 100644 index 000000000..b06ff37df --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js @@ -0,0 +1,73 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that color swatches are displayed next to colors in the rule-view. + +const TEST_URI = ` + <style type="text/css"> + body { + color: red; + background-color: #ededed; + background-image: url(chrome://global/skin/icons/warning-64.png); + border: 2em solid rgba(120, 120, 120, .5); + } + * { + color: blue; + background: linear-gradient( + to right, + #f00, + #f008, + #00ff00, + #00ff0080, + rgb(31,170,217), + rgba(31,170,217,.5), + hsl(5, 5%, 5%), + hsla(5, 5%, 5%, 0.25), + #F00, + #F008, + #00FF00, + #00FF0080, + RGB(31,170,217), + RGBA(31,170,217,.5), + HSL(5, 5%, 5%), + HSLA(5, 5%, 5%, 0.25)); + box-shadow: inset 0 0 2px 20px red, inset 0 0 2px 40px blue; + } + </style> + Testing the color picker tooltip! +`; + +// Tests that properties in the rule-view contain color swatches. +// Each entry in the test array should contain: +// { +// selector: the rule-view selector to look for the property in +// propertyName: the property to test +// nb: the number of color swatches this property should have +// } +const TESTS = [ + {selector: "body", propertyName: "color", nb: 1}, + {selector: "body", propertyName: "background-color", nb: 1}, + {selector: "body", propertyName: "border", nb: 1}, + {selector: "*", propertyName: "color", nb: 1}, + {selector: "*", propertyName: "background", nb: 16}, + {selector: "*", propertyName: "box-shadow", nb: 2}, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + for (let {selector, propertyName, nb} of TESTS) { + info("Looking for color swatches in property " + propertyName + + " in selector " + selector); + + let prop = getRuleViewProperty(view, selector, propertyName).valueSpan; + let swatches = prop.querySelectorAll(".ruleview-colorswatch"); + + ok(swatches.length, "Swatches found in the property"); + is(swatches.length, nb, "Correct number of swatches found in the property"); + } +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js new file mode 100644 index 000000000..566bae259 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js @@ -0,0 +1,139 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that CSS property names are autocompleted and cycled correctly when +// editing an existing property in the rule view. + +// format : +// [ +// what key to press, +// expected input box value after keypress, +// is the popup open, +// is a suggestion selected in the popup, +// ] + +const OPEN = true, SELECTED = true; +var testData = [ + ["VK_RIGHT", "font", !OPEN, !SELECTED], + ["-", "font-size", OPEN, SELECTED], + ["f", "font-family", OPEN, SELECTED], + ["VK_BACK_SPACE", "font-f", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "font-", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "font", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "fon", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "fo", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "f", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "", !OPEN, !SELECTED], + ["d", "display", OPEN, SELECTED], + ["VK_DOWN", "dominant-baseline", OPEN, SELECTED], + ["VK_DOWN", "direction", OPEN, SELECTED], + ["VK_DOWN", "display", OPEN, SELECTED], + ["VK_UP", "direction", OPEN, SELECTED], + ["VK_UP", "dominant-baseline", OPEN, SELECTED], + ["VK_UP", "display", OPEN, SELECTED], + ["VK_BACK_SPACE", "d", !OPEN, !SELECTED], + ["i", "display", OPEN, SELECTED], + ["s", "display", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "dis", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "di", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "d", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "", !OPEN, !SELECTED], + ["VK_HOME", "", !OPEN, !SELECTED], + ["VK_END", "", !OPEN, !SELECTED], + ["VK_PAGE_UP", "", !OPEN, !SELECTED], + ["VK_PAGE_DOWN", "", !OPEN, !SELECTED], + ["d", "display", OPEN, SELECTED], + ["VK_HOME", "display", !OPEN, !SELECTED], + ["VK_END", "display", !OPEN, !SELECTED], + // Press right key to ensure caret move to end of the input on Mac OS since + // Mac OS doesn't move caret after pressing HOME / END. + ["VK_RIGHT", "display", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "displa", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "displ", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "disp", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "dis", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "di", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "d", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "", !OPEN, !SELECTED], + ["f", "font-size", OPEN, SELECTED], + ["i", "filter", OPEN, SELECTED], + ["VK_LEFT", "filter", !OPEN, !SELECTED], + ["VK_LEFT", "filter", !OPEN, !SELECTED], + ["i", "fiilter", !OPEN, !SELECTED], + ["VK_ESCAPE", null, !OPEN, !SELECTED], +]; + +const TEST_URI = "<h1 style='font: 24px serif'>Header</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view, testActor} = yield openRuleView(); + + info("Test autocompletion after 1st page load"); + yield runAutocompletionTest(toolbox, inspector, view); + + info("Test autocompletion after page navigation"); + yield reloadPage(inspector, testActor); + yield runAutocompletionTest(toolbox, inspector, view); +}); + +function* runAutocompletionTest(toolbox, inspector, view) { + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing the css property editable field"); + let propertyName = view.styleDocument.querySelectorAll(".ruleview-propertyname")[0]; + let editor = yield focusEditableField(view, propertyName); + + info("Starting to test for css property completion"); + for (let i = 0; i < testData.length; i++) { + yield testCompletion(testData[i], editor, view); + } +} + +function* testCompletion([key, completion, open, selected], + editor, view) { + info("Pressing key " + key); + info("Expecting " + completion); + info("Is popup opened: " + open); + info("Is item selected: " + selected); + + // Listening for the right event that will tell us when the key has been + // entered and processed. + let onSuggest; + if (/(left|right|back_space|escape|home|end|page_up|page_down)/ig.test(key)) { + info("Adding event listener for " + + "left|right|back_space|escape|home|end|page_up|page_down keys"); + onSuggest = once(editor.input, "keypress"); + } else { + info("Waiting for after-suggest event on the editor"); + onSuggest = editor.once("after-suggest"); + } + + // Also listening for popup opened/closed events if needed. + let popupEvent = open ? "popup-opened" : "popup-closed"; + let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null; + + info("Synthesizing key " + key); + EventUtils.synthesizeKey(key, {}, view.styleWindow); + + // Flush the throttle for the preview text. + view.throttle.flush(); + + yield onSuggest; + yield onPopupEvent; + + info("Checking the state"); + if (completion !== null) { + is(editor.input.value, completion, "Correct value is autocompleted"); + } + if (!open) { + ok(!(editor.popup && editor.popup.isOpen), "Popup is closed"); + } else { + ok(editor.popup.isOpen, "Popup is open"); + is(editor.popup.selectedIndex !== -1, selected, "An item is selected"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js new file mode 100644 index 000000000..fde8f5d12 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js @@ -0,0 +1,123 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that CSS property names and values are autocompleted and cycled +// correctly when editing existing properties in the rule view. + +// format : +// [ +// what key to press, +// modifers, +// expected input box value after keypress, +// is the popup open, +// is a suggestion selected in the popup, +// expect ruleview-changed, +// ] + +const OPEN = true, SELECTED = true, CHANGE = true; +var testData = [ + ["b", {}, "beige", OPEN, SELECTED, CHANGE], + ["l", {}, "black", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "blanchedalmond", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "blue", OPEN, SELECTED, CHANGE], + ["VK_RIGHT", {}, "blue", !OPEN, !SELECTED, !CHANGE], + [" ", {}, "blue aliceblue", OPEN, SELECTED, CHANGE], + ["!", {}, "blue !important", !OPEN, !SELECTED, CHANGE], + ["VK_BACK_SPACE", {}, "blue !", !OPEN, !SELECTED, CHANGE], + ["VK_BACK_SPACE", {}, "blue ", !OPEN, !SELECTED, CHANGE], + ["VK_BACK_SPACE", {}, "blue", !OPEN, !SELECTED, CHANGE], + ["VK_TAB", {shiftKey: true}, "color", !OPEN, !SELECTED, CHANGE], + ["VK_BACK_SPACE", {}, "", !OPEN, !SELECTED, !CHANGE], + ["d", {}, "display", OPEN, SELECTED, !CHANGE], + ["VK_TAB", {}, "blue", !OPEN, !SELECTED, CHANGE], + ["n", {}, "none", !OPEN, !SELECTED, CHANGE], + ["VK_RETURN", {}, null, !OPEN, !SELECTED, CHANGE] +]; + +const TEST_URI = "<h1 style='color: red'>Header</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view, testActor} = yield openRuleView(); + + info("Test autocompletion after 1st page load"); + yield runAutocompletionTest(toolbox, inspector, view); + + info("Test autocompletion after page navigation"); + yield reloadPage(inspector, testActor); + yield runAutocompletionTest(toolbox, inspector, view); +}); + +function* runAutocompletionTest(toolbox, inspector, view) { + info("Selecting the test node"); + yield selectNode("h1", inspector); + + let rule = getRuleViewRuleEditor(view, 0).rule; + let prop = rule.textProps[0]; + + info("Focusing the css property editable value"); + let editor = yield focusEditableField(view, prop.editor.valueSpan); + + info("Starting to test for css property completion"); + for (let i = 0; i < testData.length; i++) { + // Re-define the editor at each iteration, because the focus may have moved + // from property to value and back + editor = inplaceEditor(view.styleDocument.activeElement); + yield testCompletion(testData[i], editor, view); + } +} + +function* testCompletion([key, modifiers, completion, open, selected, change], + editor, view) { + info("Pressing key " + key); + info("Expecting " + completion); + info("Is popup opened: " + open); + info("Is item selected: " + selected); + + let onDone; + if (change) { + // If the key triggers a ruleview-changed, wait for that event, it will + // always be the last to be triggered and tells us when the preview has + // been done. + onDone = view.once("ruleview-changed"); + } else { + // Otherwise, expect an after-suggest event (except if the popup gets + // closed). + onDone = key !== "VK_RIGHT" && key !== "VK_BACK_SPACE" + ? editor.once("after-suggest") + : null; + } + + info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers)); + + // Also listening for popup opened/closed events if needed. + let popupEvent = open ? "popup-opened" : "popup-closed"; + let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null; + + EventUtils.synthesizeKey(key, modifiers, view.styleWindow); + + // Flush the throttle for the preview text. + view.throttle.flush(); + + yield onDone; + yield onPopupEvent; + + // The key might have been a TAB or shift-TAB, in which case the editor will + // be a new one + editor = inplaceEditor(view.styleDocument.activeElement); + + info("Checking the state"); + if (completion !== null) { + is(editor.input.value, completion, "Correct value is autocompleted"); + } + + if (!open) { + ok(!(editor.popup && editor.popup.isOpen), "Popup is closed"); + } else { + ok(editor.popup.isOpen, "Popup is open"); + is(editor.popup.selectedIndex !== -1, selected, "An item is selected"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js new file mode 100644 index 000000000..86ff9ca03 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js @@ -0,0 +1,102 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that CSS property names are autocompleted and cycled correctly when +// creating a new property in the rule view. + +// format : +// [ +// what key to press, +// expected input box value after keypress, +// is the popup open, +// is a suggestion selected in the popup, +// ] +const OPEN = true, SELECTED = true; +var testData = [ + ["d", "display", OPEN, SELECTED], + ["VK_DOWN", "dominant-baseline", OPEN, SELECTED], + ["VK_DOWN", "direction", OPEN, SELECTED], + ["VK_DOWN", "display", OPEN, SELECTED], + ["VK_UP", "direction", OPEN, SELECTED], + ["VK_UP", "dominant-baseline", OPEN, SELECTED], + ["VK_UP", "display", OPEN, SELECTED], + ["VK_BACK_SPACE", "d", !OPEN, !SELECTED], + ["i", "display", OPEN, SELECTED], + ["s", "display", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "dis", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "di", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "d", !OPEN, !SELECTED], + ["VK_BACK_SPACE", "", !OPEN, !SELECTED], + ["f", "font-size", OPEN, SELECTED], + ["i", "filter", OPEN, SELECTED], + ["VK_ESCAPE", null, !OPEN, !SELECTED], +]; + +const TEST_URI = "<h1 style='border: 1px solid red'>Header</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view, testActor} = yield openRuleView(); + + info("Test autocompletion after 1st page load"); + yield runAutocompletionTest(toolbox, inspector, view); + + info("Test autocompletion after page navigation"); + yield reloadPage(inspector, testActor); + yield runAutocompletionTest(toolbox, inspector, view); +}); + +function* runAutocompletionTest(toolbox, inspector, view) { + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing the css property editable field"); + let ruleEditor = getRuleViewRuleEditor(view, 0); + let editor = yield focusNewRuleViewProperty(ruleEditor); + + info("Starting to test for css property completion"); + for (let i = 0; i < testData.length; i++) { + yield testCompletion(testData[i], editor, view); + } +} + +function* testCompletion([key, completion, open, isSelected], editor, view) { + info("Pressing key " + key); + info("Expecting " + completion); + info("Is popup opened: " + open); + info("Is item selected: " + isSelected); + + let onSuggest; + + if (/(right|back_space|escape)/ig.test(key)) { + info("Adding event listener for right|back_space|escape keys"); + onSuggest = once(editor.input, "keypress"); + } else { + info("Waiting for after-suggest event on the editor"); + onSuggest = editor.once("after-suggest"); + } + + // Also listening for popup opened/closed events if needed. + let popupEvent = open ? "popup-opened" : "popup-closed"; + let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null; + + info("Synthesizing key " + key); + EventUtils.synthesizeKey(key, {}, view.styleWindow); + + yield onSuggest; + yield onPopupEvent; + + info("Checking the state"); + if (completion !== null) { + is(editor.input.value, completion, "Correct value is autocompleted"); + } + if (!open) { + ok(!(editor.popup && editor.popup.isOpen), "Popup is closed"); + } else { + ok(editor.popup.isOpen, "Popup is open"); + is(editor.popup.selectedIndex !== -1, isSelected, "An item is selected"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js new file mode 100644 index 000000000..d89e5129d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js @@ -0,0 +1,129 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that CSS property names and values are autocompleted and cycled +// correctly when editing new properties in the rule view. + +// format : +// [ +// what key to press, +// modifers, +// expected input box value after keypress, +// is the popup open, +// is a suggestion selected in the popup, +// expect ruleview-changed, +// ] + +const OPEN = true, SELECTED = true, CHANGE = true; +const testData = [ + ["d", {}, "display", OPEN, SELECTED, !CHANGE], + ["VK_TAB", {}, "", OPEN, !SELECTED, CHANGE], + ["VK_DOWN", {}, "block", OPEN, SELECTED, CHANGE], + ["n", {}, "none", !OPEN, !SELECTED, CHANGE], + ["VK_TAB", {shiftKey: true}, "display", !OPEN, !SELECTED, CHANGE], + ["VK_BACK_SPACE", {}, "", !OPEN, !SELECTED, !CHANGE], + ["o", {}, "overflow", OPEN, SELECTED, !CHANGE], + ["u", {}, "outline", OPEN, SELECTED, !CHANGE], + ["VK_DOWN", {}, "outline-color", OPEN, SELECTED, !CHANGE], + ["VK_TAB", {}, "none", !OPEN, !SELECTED, CHANGE], + ["r", {}, "rebeccapurple", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "red", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "rgb", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "rgba", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "rosybrown", OPEN, SELECTED, CHANGE], + ["VK_DOWN", {}, "royalblue", OPEN, SELECTED, CHANGE], + ["VK_RIGHT", {}, "royalblue", !OPEN, !SELECTED, !CHANGE], + [" ", {}, "royalblue aliceblue", OPEN, SELECTED, CHANGE], + ["!", {}, "royalblue !important", !OPEN, !SELECTED, CHANGE], + ["VK_ESCAPE", {}, null, !OPEN, !SELECTED, CHANGE] +]; + +const TEST_URI = ` + <style type="text/css"> + h1 { + border: 1px solid red; + } + </style> + <h1>Test element</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view, testActor} = yield openRuleView(); + + info("Test autocompletion after 1st page load"); + yield runAutocompletionTest(toolbox, inspector, view); + + info("Test autocompletion after page navigation"); + yield reloadPage(inspector, testActor); + yield runAutocompletionTest(toolbox, inspector, view); +}); + +function* runAutocompletionTest(toolbox, inspector, view) { + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing a new css property editable property"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusNewRuleViewProperty(ruleEditor); + + info("Starting to test for css property completion"); + for (let i = 0; i < testData.length; i++) { + // Re-define the editor at each iteration, because the focus may have moved + // from property to value and back + editor = inplaceEditor(view.styleDocument.activeElement); + yield testCompletion(testData[i], editor, view); + } +} + +function* testCompletion([key, modifiers, completion, open, selected, change], + editor, view) { + info("Pressing key " + key); + info("Expecting " + completion); + info("Is popup opened: " + open); + info("Is item selected: " + selected); + + let onDone; + if (change) { + // If the key triggers a ruleview-changed, wait for that event, it will + // always be the last to be triggered and tells us when the preview has + // been done. + onDone = view.once("ruleview-changed"); + } else { + // Otherwise, expect an after-suggest event (except if the popup gets + // closed). + onDone = key !== "VK_RIGHT" && key !== "VK_BACK_SPACE" + ? editor.once("after-suggest") + : null; + } + + // Also listening for popup opened/closed events if needed. + let popupEvent = open ? "popup-opened" : "popup-closed"; + let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null; + + info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers)); + EventUtils.synthesizeKey(key, modifiers, view.styleWindow); + + // Flush the throttle for the preview text. + view.throttle.flush(); + + yield onDone; + yield onPopupEvent; + + info("Checking the state"); + if (completion !== null) { + // The key might have been a TAB or shift-TAB, in which case the editor will + // be a new one + editor = inplaceEditor(view.styleDocument.activeElement); + is(editor.input.value, completion, "Correct value is autocompleted"); + } + if (!open) { + ok(!(editor.popup && editor.popup.isOpen), "Popup is closed"); + } else { + ok(editor.popup.isOpen, "Popup is open"); + is(editor.popup.selectedIndex !== -1, selected, "An item is selected"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js new file mode 100644 index 000000000..a5072429c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Regression test for a case where completing gave the wrong answer. +// See bug 1179318. + +const TEST_URI = "<h1 style='color: red'>Header</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view} = yield openRuleView(); + + info("Test autocompletion for background-color"); + yield runAutocompletionTest(toolbox, inspector, view); +}); + +function* runAutocompletionTest(toolbox, inspector, view) { + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing the new property editable field"); + let ruleEditor = getRuleViewRuleEditor(view, 0); + let editor = yield focusNewRuleViewProperty(ruleEditor); + + info("Sending \"background\" to the editable field"); + for (let key of "background") { + let onSuggest = editor.once("after-suggest"); + EventUtils.synthesizeKey(key, {}, view.styleWindow); + yield onSuggest; + } + + const itemIndex = 4; + + let bgcItem = editor.popup.getItemAtIndex(itemIndex); + is(bgcItem.label, "background-color", + "check the expected completion element"); + + editor.popup.selectedIndex = itemIndex; + + let node = editor.popup._list.childNodes[itemIndex]; + EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window); + + is(editor.input.value, "background-color", "Correct value is autocompleted"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js new file mode 100644 index 000000000..e19794e1b --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js @@ -0,0 +1,73 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that a new property editor supports the following flow: +// - type first character of property name +// - select an autocomplete suggestion !!with a mouse click!! +// - press RETURN to move to the property value +// - blur the input to commit + +const TEST_URI = "<style>.title {color: red;}</style>" + + "<h1 class=title>Header</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let { inspector, view} = yield openRuleView(); + + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing the new property editable field"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusNewRuleViewProperty(ruleEditor); + + info("Sending \"background\" to the editable field."); + for (let key of "background") { + let onSuggest = editor.once("after-suggest"); + EventUtils.synthesizeKey(key, {}, view.styleWindow); + yield onSuggest; + } + + const itemIndex = 4; + let bgcItem = editor.popup.getItemAtIndex(itemIndex); + is(bgcItem.label, "background-color", + "Check the expected completion element is background-color."); + editor.popup.selectedIndex = itemIndex; + + info("Select the background-color suggestion with a mouse click."); + let onSuggest = editor.once("after-suggest"); + let node = editor.popup.elements.get(bgcItem); + EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window); + + yield onSuggest; + is(editor.input.value, "background-color", "Correct value is autocompleted"); + + info("Press RETURN to move the focus to a property value editor."); + let onModifications = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + + yield onModifications; + + // Getting the new value editor after focus + editor = inplaceEditor(view.styleDocument.activeElement); + let textProp = ruleEditor.rule.textProps[1]; + + is(ruleEditor.rule.textProps.length, 2, + "Created a new text property."); + is(ruleEditor.propertyList.children.length, 2, + "Created a property editor."); + is(editor, inplaceEditor(textProp.editor.valueSpan), + "Editing the value span now."); + + info("Entering a value and blurring the field to expect a rule change"); + editor.input.value = "#F00"; + + onModifications = view.once("ruleview-changed"); + editor.input.blur(); + yield onModifications; + + is(textProp.value, "#F00", "Text prop should have been changed."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js new file mode 100644 index 000000000..ec939eafc --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js @@ -0,0 +1,131 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the behaviour of the CSS autocomplete for CSS value displayed on +// multiple lines. Expected behavior is: +// - UP/DOWN should navigate in the input and not increment/decrement numbers +// - typing a new value should still trigger the autocomplete +// - UP/DOWN when the autocomplete popup is displayed should cycle through +// suggestions + +const LONG_CSS_VALUE = + "transparent linear-gradient(0deg, blue 0%, white 5%, red 10%, blue 15%, " + + "white 20%, red 25%, blue 30%, white 35%, red 40%, blue 45%, white 50%, " + + "red 55%, blue 60%, white 65%, red 70%, blue 75%, white 80%, red 85%, " + + "blue 90%, white 95% ) repeat scroll 0% 0%"; + +const EXPECTED_CSS_VALUE = LONG_CSS_VALUE.replace("95%", "95%, red"); + +const TEST_URI = + `<style> + .title { + background: ${LONG_CSS_VALUE}; + } + </style> + <h1 class=title>Header</h1>`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let { inspector, view} = yield openRuleView(); + + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing the property editable field"); + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + // Calculate offsets to click in the middle of the first box quad. + let rect = prop.editor.valueSpan.getBoundingClientRect(); + let firstQuad = prop.editor.valueSpan.getBoxQuads()[0]; + // For a multiline value, the first quad left edge is not aligned with the + // bounding rect left edge. The offsets expected by focusEditableField are + // relative to the bouding rectangle, so we need to translate the x-offset. + let x = firstQuad.bounds.left - rect.left + firstQuad.bounds.width / 2; + // The first quad top edge is aligned with the bounding top edge, no + // translation needed here. + let y = firstQuad.bounds.height / 2; + + info("Focusing the css property editable value"); + let editor = yield focusEditableField(view, prop.editor.valueSpan, x, y); + + info("Moving the caret next to a number"); + let pos = editor.input.value.indexOf("0deg") + 1; + editor.input.setSelectionRange(pos, pos); + is(editor.input.value[editor.input.selectionStart - 1], "0", + "Input caret is after a 0"); + + info("Check that UP/DOWN navigates in the input, even when next to a number"); + EventUtils.synthesizeKey("VK_DOWN", {}, view.styleWindow); + ok(editor.input.selectionStart !== pos, "Input caret moved"); + is(editor.input.value, LONG_CSS_VALUE, "Input value was not decremented."); + + info("Move the caret to the end of the gradient definition."); + pos = editor.input.value.indexOf("95%") + 3; + editor.input.setSelectionRange(pos, pos); + + info("Sending \", re\" to the editable field."); + for (let key of ", re") { + yield synthesizeKeyForAutocomplete(key, editor, view.styleWindow); + } + + info("Check the autocomplete can still be displayed."); + ok(editor.popup && editor.popup.isOpen, "Autocomplete popup is displayed."); + is(editor.popup.selectedIndex, 0, + "Autocomplete has an item selected by default"); + + let item = editor.popup.getItemAtIndex(editor.popup.selectedIndex); + is(item.label, "rebeccapurple", + "Check autocomplete displays expected value."); + + info("Check autocomplete suggestions can be cycled using UP/DOWN arrows."); + + yield synthesizeKeyForAutocomplete("VK_DOWN", editor, view.styleWindow); + ok(editor.popup.selectedIndex, 1, "Using DOWN cycles autocomplete values."); + yield synthesizeKeyForAutocomplete("VK_DOWN", editor, view.styleWindow); + ok(editor.popup.selectedIndex, 2, "Using DOWN cycles autocomplete values."); + yield synthesizeKeyForAutocomplete("VK_UP", editor, view.styleWindow); + is(editor.popup.selectedIndex, 1, "Using UP cycles autocomplete values."); + item = editor.popup.getItemAtIndex(editor.popup.selectedIndex); + is(item.label, "red", "Check autocomplete displays expected value."); + + info("Select the background-color suggestion with a mouse click."); + let onRuleviewChanged = view.once("ruleview-changed"); + let onSuggest = editor.once("after-suggest"); + + let node = editor.popup._list.childNodes[editor.popup.selectedIndex]; + EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window); + + view.throttle.flush(); + yield onSuggest; + yield onRuleviewChanged; + + is(editor.input.value, EXPECTED_CSS_VALUE, + "Input value correctly autocompleted"); + + info("Press ESCAPE to leave the input."); + onRuleviewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onRuleviewChanged; +}); + +/** + * Send the provided key to the currently focused input of the provided window. + * Wait for the editor to emit "after-suggest" to make sure the autocompletion + * process is finished. + * + * @param {String} key + * The key to send to the input. + * @param {InplaceEditor} editor + * The inplace editor which owns the focused input. + * @param {Window} win + * Window in which the key event will be dispatched. + */ +function* synthesizeKeyForAutocomplete(key, editor, win) { + let onSuggest = editor.once("after-suggest"); + EventUtils.synthesizeKey(key, {}, win); + yield onSuggest; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js b/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js new file mode 100644 index 000000000..84f119606 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js @@ -0,0 +1,41 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the ruleview autocomplete popup is hidden after page navigation. + +const TEST_URI = "<h1 style='font: 24px serif'></h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view, testActor} = yield openRuleView(); + + info("Test autocompletion popup is hidden after page navigation"); + + info("Selecting the test node"); + yield selectNode("h1", inspector); + + info("Focusing the css property editable field"); + let propertyName = view.styleDocument + .querySelectorAll(".ruleview-propertyname")[0]; + let editor = yield focusEditableField(view, propertyName); + + info("Pressing key VK_DOWN"); + let onSuggest = once(editor.input, "keypress"); + let onPopupOpened = once(editor.popup, "popup-opened"); + + EventUtils.synthesizeKey("VK_DOWN", {}, view.styleWindow); + + info("Waiting for autocomplete popup to be displayed"); + yield onSuggest; + yield onPopupOpened; + + ok(view.popup && view.popup.isOpen, "Popup should be opened"); + + info("Reloading the page"); + yield reloadPage(inspector, testActor); + + ok(!(view.popup && view.popup.isOpen), "Popup should be closed"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js b/devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js new file mode 100644 index 000000000..5acebd562 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view shows expanders for properties with computed lists. + +var TEST_URI = ` + <style type="text/css"> + #testid { + margin: 4px; + top: 0px; + } + </style> + <h1 id="testid">Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testExpandersShown(inspector, view); +}); + +function* testExpandersShown(inspector, view) { + let rule = getRuleViewRuleEditor(view, 1).rule; + + info("Check that the correct rules are visible"); + is(rule.selectorText, "#testid", "Second rule is #testid."); + is(rule.textProps[0].name, "margin", "First property is margin."); + is(rule.textProps[1].name, "top", "Second property is top."); + + info("Check that the expanders are shown correctly"); + is(rule.textProps[0].editor.expander.style.visibility, "visible", + "margin expander is visible."); + is(rule.textProps[1].editor.expander.style.visibility, "hidden", + "top expander is hidden."); + ok(!rule.textProps[0].editor.expander.hasAttribute("open"), + "margin computed list is closed."); + ok(!rule.textProps[1].editor.expander.hasAttribute("open"), + "top computed list is closed."); + ok(!rule.textProps[0].editor.computed.hasChildNodes(), + "margin computed list is empty before opening."); + ok(!rule.textProps[1].editor.computed.hasChildNodes(), + "top computed list is empty."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js b/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js new file mode 100644 index 000000000..d6dc82d5f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js @@ -0,0 +1,74 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view computed lists can be expanded/collapsed, +// and contain the right subproperties. + +var TEST_URI = ` + <style type="text/css"> + #testid { + margin: 0px 1px 2px 3px; + top: 0px; + } + </style> + <h1 id="testid">Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testComputedList(inspector, view); +}); + +function* testComputedList(inspector, view) { + let rule = getRuleViewRuleEditor(view, 1).rule; + let propEditor = rule.textProps[0].editor; + let expander = propEditor.expander; + + ok(!expander.hasAttribute("open"), "margin computed list is closed"); + + info("Opening the computed list of margin property"); + expander.click(); + ok(expander.hasAttribute("open"), "margin computed list is open"); + + let computed = propEditor.prop.computed; + let computedDom = propEditor.computed; + let propNames = [ + "margin-top", + "margin-right", + "margin-bottom", + "margin-left" + ]; + + is(computed.length, propNames.length, "There should be 4 computed values"); + is(computedDom.children.length, propNames.length, + "There should be 4 nodes in the DOM"); + + propNames.forEach((propName, i) => { + let propValue = i + "px"; + is(computed[i].name, propName, + "Computed property #" + i + " has name " + propName); + is(computed[i].value, propValue, + "Computed property #" + i + " has value " + propValue); + is(computedDom.querySelectorAll(".ruleview-propertyname")[i].textContent, + propName, + "Computed property #" + i + " in DOM has correct name"); + is(computedDom.querySelectorAll(".ruleview-propertyvalue")[i].textContent, + propValue, + "Computed property #" + i + " in DOM has correct value"); + }); + + info("Closing the computed list of margin property"); + expander.click(); + ok(!expander.hasAttribute("open"), "margin computed list is closed"); + + info("Opening the computed list of margin property"); + expander.click(); + ok(expander.hasAttribute("open"), "margin computed list is open"); + is(computed.length, propNames.length, "Still 4 computed values"); + is(computedDom.children.length, propNames.length, "Still 4 nodes in the DOM"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_content_01.js b/devtools/client/inspector/rules/test/browser_rules_content_01.js new file mode 100644 index 000000000..8695d9b8d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_content_01.js @@ -0,0 +1,51 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view content is correct + +const TEST_URI = ` + <style type="text/css"> + @media screen and (min-width: 10px) { + #testid { + background-color: blue; + } + } + .testclass, .unmatched { + background-color: green; + } + </style> + <div id="testid" class="testclass">Styled Node</div> + <div id="testid2">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield selectNode("#testid", inspector); + is(view.element.querySelectorAll("#ruleview-no-results").length, 0, + "After a highlight, no longer has a no-results element."); + + yield clearCurrentNodeSelection(inspector); + is(view.element.querySelectorAll("#ruleview-no-results").length, 1, + "After highlighting null, has a no-results element again."); + + yield selectNode("#testid", inspector); + + let linkText = getRuleViewLinkTextByIndex(view, 1); + is(linkText, "inline:3 @screen and (min-width: 10px)", + "link text at index 1 contains media query text."); + + linkText = getRuleViewLinkTextByIndex(view, 2); + is(linkText, "inline:7", + "link text at index 2 contains no media query text."); + + let selector = getRuleViewRuleEditor(view, 2).selectorText; + is(selector.querySelector(".ruleview-selector-matched").textContent, + ".testclass", ".textclass should be matched."); + is(selector.querySelector(".ruleview-selector-unmatched").textContent, + ".unmatched", ".unmatched should not be matched."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_content_02.js b/devtools/client/inspector/rules/test/browser_rules_content_02.js new file mode 100644 index 000000000..253f374b4 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_content_02.js @@ -0,0 +1,60 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* globals getTestActorWithoutToolbox */ +"use strict"; + +// Test the rule-view content when the inspector gets opened via the page +// ctx-menu "inspect element" + +const CONTENT = ` + <body style="color:red;"> + <div style="color:blue;"> + <p style="color:green;"> + <span style="color:yellow;">test element</span> + </p> + </div> + </body> +`; + +add_task(function* () { + let tab = yield addTab("data:text/html;charset=utf-8," + CONTENT); + + let testActor = yield getTestActorWithoutToolbox(tab); + let inspector = yield clickOnInspectMenuItem(testActor, "span"); + + checkRuleViewContent(inspector.ruleview.view); +}); + +function checkRuleViewContent({styleDocument}) { + info("Making sure the rule-view contains the expected content"); + + let headers = [...styleDocument.querySelectorAll(".ruleview-header")]; + is(headers.length, 3, "There are 3 headers for inherited rules"); + + is(headers[0].textContent, + STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", "p"), + "The first header is correct"); + is(headers[1].textContent, + STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", "div"), + "The second header is correct"); + is(headers[2].textContent, + STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", "body"), + "The third header is correct"); + + let rules = styleDocument.querySelectorAll(".ruleview-rule"); + is(rules.length, 4, "There are 4 rules in the view"); + + for (let rule of rules) { + let selector = rule.querySelector(".ruleview-selectorcontainer"); + is(selector.textContent, STYLE_INSPECTOR_L10N.getStr("rule.sourceElement"), + "The rule's selector is correct"); + + let propertyNames = [...rule.querySelectorAll(".ruleview-propertyname")]; + is(propertyNames.length, 1, "There's only one property name, as expected"); + + let propertyValues = [...rule.querySelectorAll(".ruleview-propertyvalue")]; + is(propertyValues.length, 1, "There's only one property value, as expected"); + } +} + diff --git a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js new file mode 100644 index 000000000..b81bb8013 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js @@ -0,0 +1,96 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This file tests the code that integrates the Style Inspector's rule view + * with the MDN docs tooltip. + * + * If you display the context click on a property name in the rule view, you + * should see a menu item "Show MDN Docs". If you click that item, the MDN + * docs tooltip should be shown, containing docs from MDN for that property. + * + * This file tests that the context menu item is shown when it should be + * shown and hidden when it should be hidden. + */ + +"use strict"; + +/** + * The test document tries to confuse the context menu + * code by having a tag called "padding" and a property + * value called "margin". + */ +const TEST_URI = ` + <html> + <head> + <style> + padding {font-family: margin;} + </style> + </head> + + <body> + <padding>MDN tooltip testing</padding> + </body> + </html> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("padding", inspector); + yield testMdnContextMenuItemVisibility(view); +}); + +/** + * Tests that the MDN context menu item is shown when it should be, + * and hidden when it should be. + * - iterate through every node in the rule view + * - set that node as popupNode (the node that the context menu + * is shown for) + * - update the context menu's state + * - test that the MDN context menu item is hidden, or not, + * depending on popupNode + */ +function* testMdnContextMenuItemVisibility(view) { + info("Test that MDN context menu item is shown only when it should be."); + + let root = rootElement(view); + for (let node of iterateNodes(root)) { + info("Setting " + node + " as popupNode"); + info("Creating context menu with " + node + " as popupNode"); + let allMenuItems = openStyleContextMenuAndGetAllItems(view, node); + let menuitemShowMdnDocs = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs")); + + let isVisible = menuitemShowMdnDocs.visible; + let shouldBeVisible = isPropertyNameNode(node); + let message = shouldBeVisible ? "shown" : "hidden"; + is(isVisible, shouldBeVisible, + "The MDN context menu item is " + message + " ; content : " + + node.textContent + " ; type : " + node.nodeType); + } +} + +/** + * Check if a node is a property name. + */ +function isPropertyNameNode(node) { + return node.textContent === "font-family"; +} + +/** + * A generator that iterates recursively through all child nodes of baseNode. + */ +function* iterateNodes(baseNode) { + yield baseNode; + + for (let child of baseNode.childNodes) { + yield* iterateNodes(child); + } +} + +/** + * Returns the root element for the rule view. + */ +var rootElement = view => (view.element) ? view.element : view.styleDocument; diff --git a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js new file mode 100644 index 000000000..e0d08d28a --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js @@ -0,0 +1,61 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This file tests the code that integrates the Style Inspector's rule view + * with the MDN docs tooltip. + * + * If you display the context click on a property name in the rule view, you + * should see a menu item "Show MDN Docs". If you click that item, the MDN + * docs tooltip should be shown, containing docs from MDN for that property. + * + * This file tests that: + * - clicking the context menu item shows the tooltip + * - the tooltip content matches the property name for which the context menu was opened + */ + +"use strict"; + +const {setBaseCssDocsUrl} = + require("devtools/client/shared/widgets/MdnDocsWidget"); + +const PROPERTYNAME = "color"; + +const TEST_DOC = ` + <html> + <body> + <div style="color: red"> + Test "Show MDN Docs" context menu option + </div> + </body> + </html> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + setBaseCssDocsUrl(URL_ROOT); + + info("Setting the popupNode for the MDN docs tooltip"); + + let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME); + + let allMenuItems = openStyleContextMenuAndGetAllItems(view, nameSpan.firstChild); + let menuitemShowMdnDocs = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs")); + + let cssDocs = view.tooltips.cssDocs; + + info("Showing the MDN docs tooltip"); + let onShown = cssDocs.tooltip.once("shown"); + menuitemShowMdnDocs.click(); + yield onShown; + ok(true, "The MDN docs tooltip was shown"); + + info("Quick check that the tooltip contents are set"); + let h1 = cssDocs.tooltip.container.querySelector(".mdn-property-name"); + is(h1.textContent, PROPERTYNAME, "The MDN docs tooltip h1 is correct"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js new file mode 100644 index 000000000..d1089fcf6 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js @@ -0,0 +1,118 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This file tests the "devtools.inspector.mdnDocsTooltip.enabled" preference, + * that we use to enable/disable the MDN tooltip in the Inspector. + * + * The desired behavior is: + * - if the preference is true, show the "Show MDN Docs" context menu item + * - if the preference is false, don't show the item + * - listen for changes to the pref, so we can show/hide the item dynamically + */ + +"use strict"; + +const { PrefObserver } = require("devtools/client/styleeditor/utils"); +const PREF_ENABLE_MDN_DOCS_TOOLTIP = + "devtools.inspector.mdnDocsTooltip.enabled"; +const PROPERTY_NAME_CLASS = "ruleview-propertyname"; + +const TEST_DOC = ` + <html> + <body> + <div style="color: red"> + Test the pref to enable/disable the "Show MDN Docs" context menu option + </div> + </body> + </html> +`; + +add_task(function* () { + info("Ensure the pref is true to begin with"); + let initial = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP); + if (initial != true) { + setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true); + } + + yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC)); + + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + yield testMdnContextMenuItemVisibility(view, true); + + yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, false); + yield testMdnContextMenuItemVisibility(view, false); + + info("Close the Inspector"); + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.closeToolbox(target); + + ({inspector, view} = yield openRuleView()); + yield selectNode("div", inspector); + yield testMdnContextMenuItemVisibility(view, false); + + yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true); + yield testMdnContextMenuItemVisibility(view, true); + + info("Ensure the pref is reset to its initial value"); + let eventual = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP); + if (eventual != initial) { + setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, initial); + } +}); + +/** + * Set a boolean pref, and wait for the pref observer to + * trigger, so that code listening for the pref change + * has had a chance to update itself. + * + * @param pref {string} Name of the pref to change + * @param state {boolean} Desired value of the pref. + * + * Note that if the pref already has the value in `state`, + * then the prefObserver will not trigger. So you should only + * call this function if you know the pref's current value is + * not `state`. + */ +function* setBooleanPref(pref, state) { + let oncePrefChanged = defer(); + let prefObserver = new PrefObserver("devtools."); + prefObserver.on(pref, oncePrefChanged.resolve); + + info("Set the pref " + pref + " to: " + state); + Services.prefs.setBoolPref(pref, state); + + info("Wait for prefObserver to call back so the UI can update"); + yield oncePrefChanged.promise; + prefObserver.off(pref, oncePrefChanged.resolve); +} + +/** + * Test whether the MDN tooltip context menu item is visible when it should be. + * + * @param view The rule view + * @param shouldBeVisible {boolean} Whether we expect the context + * menu item to be visible or not. + */ +function* testMdnContextMenuItemVisibility(view, shouldBeVisible) { + let message = shouldBeVisible ? "shown" : "hidden"; + info("Test that MDN context menu item is " + message); + + info("Set a CSS property name as popupNode"); + let root = rootElement(view); + let node = root.querySelector("." + PROPERTY_NAME_CLASS).firstChild; + let allMenuItems = openStyleContextMenuAndGetAllItems(view, node); + let menuitemShowMdnDocs = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs")); + + let isVisible = menuitemShowMdnDocs.visible; + is(isVisible, shouldBeVisible, + "The MDN context menu item is " + message); +} + +/** + * Returns the root element for the rule view. + */ +var rootElement = view => (view.element) ? view.element : view.styleDocument; diff --git a/devtools/client/inspector/rules/test/browser_rules_copy_styles.js b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js new file mode 100644 index 000000000..a6f991a60 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js @@ -0,0 +1,307 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the behaviour of the copy styles context menu items in the rule + * view. + */ + +const osString = Services.appinfo.OS; + +const TEST_URI = URL_ROOT + "doc_copystyles.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let { inspector, view } = yield openRuleView(); + yield selectNode("#testid", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + let data = [ + { + desc: "Test Copy Property Name", + node: ruleEditor.rule.textProps[0].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyPropertyName", + expectedPattern: "color", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Property Value", + node: ruleEditor.rule.textProps[2].editor.valueSpan, + menuItemLabel: "styleinspector.contextmenu.copyPropertyValue", + expectedPattern: "12px", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: false, + copyPropertyValue: true, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Property Value with Priority", + node: ruleEditor.rule.textProps[3].editor.valueSpan, + menuItemLabel: "styleinspector.contextmenu.copyPropertyValue", + expectedPattern: "#00F !important", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: false, + copyPropertyValue: true, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Property Declaration", + node: ruleEditor.rule.textProps[2].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration", + expectedPattern: "font-size: 12px;", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Property Declaration with Priority", + node: ruleEditor.rule.textProps[3].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration", + expectedPattern: "border-color: #00F !important;", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Rule", + node: ruleEditor.rule.textProps[2].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyRule", + expectedPattern: "#testid {[\\r\\n]+" + + "\tcolor: #F00;[\\r\\n]+" + + "\tbackground-color: #00F;[\\r\\n]+" + + "\tfont-size: 12px;[\\r\\n]+" + + "\tborder-color: #00F !important;[\\r\\n]+" + + "\t--var: \"\\*/\";[\\r\\n]+" + + "}", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Selector", + node: ruleEditor.selectorText, + menuItemLabel: "styleinspector.contextmenu.copySelector", + expectedPattern: "html, body, #testid", + visible: { + copyLocation: false, + copyPropertyDeclaration: false, + copyPropertyName: false, + copyPropertyValue: false, + copySelector: true, + copyRule: true + } + }, + { + desc: "Test Copy Location", + node: ruleEditor.source, + menuItemLabel: "styleinspector.contextmenu.copyLocation", + expectedPattern: "http://example.com/browser/devtools/client/" + + "inspector/rules/test/doc_copystyles.css", + visible: { + copyLocation: true, + copyPropertyDeclaration: false, + copyPropertyName: false, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + setup: function* () { + yield disableProperty(view, 0); + }, + desc: "Test Copy Rule with Disabled Property", + node: ruleEditor.rule.textProps[2].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyRule", + expectedPattern: "#testid {[\\r\\n]+" + + "\t\/\\* color: #F00; \\*\/[\\r\\n]+" + + "\tbackground-color: #00F;[\\r\\n]+" + + "\tfont-size: 12px;[\\r\\n]+" + + "\tborder-color: #00F !important;[\\r\\n]+" + + "\t--var: \"\\*/\";[\\r\\n]+" + + "}", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + setup: function* () { + yield disableProperty(view, 4); + }, + desc: "Test Copy Rule with Disabled Property with Comment", + node: ruleEditor.rule.textProps[2].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyRule", + expectedPattern: "#testid {[\\r\\n]+" + + "\t\/\\* color: #F00; \\*\/[\\r\\n]+" + + "\tbackground-color: #00F;[\\r\\n]+" + + "\tfont-size: 12px;[\\r\\n]+" + + "\tborder-color: #00F !important;[\\r\\n]+" + + "\t/\\* --var: \"\\*\\\\\/\"; \\*\/[\\r\\n]+" + + "}", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + { + desc: "Test Copy Property Declaration with Disabled Property", + node: ruleEditor.rule.textProps[0].editor.nameSpan, + menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration", + expectedPattern: "\/\\* color: #F00; \\*\/", + visible: { + copyLocation: false, + copyPropertyDeclaration: true, + copyPropertyName: true, + copyPropertyValue: false, + copySelector: false, + copyRule: true + } + }, + ]; + + for (let { setup, desc, node, menuItemLabel, expectedPattern, visible } of data) { + if (setup) { + yield setup(); + } + + info(desc); + yield checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible); + } +}); + +function* checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible) { + let allMenuItems = openStyleContextMenuAndGetAllItems(view, node); + let menuItem = allMenuItems.find(item => + item.label === STYLE_INSPECTOR_L10N.getStr(menuItemLabel)); + let menuitemCopy = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy")); + let menuitemCopyLocation = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyLocation")); + let menuitemCopyPropertyDeclaration = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyDeclaration")); + let menuitemCopyPropertyName = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyName")); + let menuitemCopyPropertyValue = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyValue")); + let menuitemCopySelector = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copySelector")); + let menuitemCopyRule = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyRule")); + + ok(menuitemCopy.disabled, + "Copy disabled is as expected: true"); + ok(menuitemCopy.visible, + "Copy visible is as expected: true"); + + is(menuitemCopyLocation.visible, + visible.copyLocation, + "Copy Location visible attribute is as expected: " + + visible.copyLocation); + + is(menuitemCopyPropertyDeclaration.visible, + visible.copyPropertyDeclaration, + "Copy Property Declaration visible attribute is as expected: " + + visible.copyPropertyDeclaration); + + is(menuitemCopyPropertyName.visible, + visible.copyPropertyName, + "Copy Property Name visible attribute is as expected: " + + visible.copyPropertyName); + + is(menuitemCopyPropertyValue.visible, + visible.copyPropertyValue, + "Copy Property Value visible attribute is as expected: " + + visible.copyPropertyValue); + + is(menuitemCopySelector.visible, + visible.copySelector, + "Copy Selector visible attribute is as expected: " + + visible.copySelector); + + is(menuitemCopyRule.visible, + visible.copyRule, + "Copy Rule visible attribute is as expected: " + + visible.copyRule); + + try { + yield waitForClipboardPromise(() => menuItem.click(), + () => checkClipboardData(expectedPattern)); + } catch (e) { + failedClipboard(expectedPattern); + } +} + +function* disableProperty(view, index) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let textProp = ruleEditor.rule.textProps[index]; + yield togglePropStatus(view, textProp); +} + +function checkClipboardData(expectedPattern) { + let actual = SpecialPowers.getClipboardData("text/unicode"); + let expectedRegExp = new RegExp(expectedPattern, "g"); + return expectedRegExp.test(actual); +} + +function failedClipboard(expectedPattern) { + // Format expected text for comparison + let terminator = osString == "WINNT" ? "\r\n" : "\n"; + expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator); + expectedPattern = expectedPattern.replace(/\\\(/g, "("); + expectedPattern = expectedPattern.replace(/\\\)/g, ")"); + + let actual = SpecialPowers.getClipboardData("text/unicode"); + + // Trim the right hand side of our strings. This is because expectedPattern + // accounts for windows sometimes adding a newline to our copied data. + expectedPattern = expectedPattern.trimRight(); + actual = actual.trimRight(); + + ok(false, "Clipboard text does not match expected " + + "results (escaped for accurate comparison):\n"); + info("Actual: " + escape(actual)); + info("Expected: " + escape(expectedPattern)); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js b/devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js new file mode 100644 index 000000000..f386f45b4 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js @@ -0,0 +1,51 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the CssDocs tooltip of the ruleview can be closed when pressing the Escape + * key. + */ + +"use strict"; + +const {setBaseCssDocsUrl} = + require("devtools/client/shared/widgets/MdnDocsWidget"); + +const PROPERTYNAME = "color"; + +const TEST_URI = ` + <html> + <body> + <div style="color: red"> + Test "Show MDN Docs" closes on escape + </div> + </body> + </html> +`; + +/** + * Test that the tooltip is hidden when we press Escape + */ +add_task(function* () { + yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + setBaseCssDocsUrl(URL_ROOT); + + info("Retrieve a valid anchor for the CssDocs tooltip"); + let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME); + + info("Showing the MDN docs tooltip"); + let onShown = view.tooltips.cssDocs.tooltip.once("shown"); + view.tooltips.cssDocs.show(nameSpan, PROPERTYNAME); + yield onShown; + ok(true, "The MDN docs tooltip was shown"); + + info("Simulate pressing the 'Escape' key"); + let onHidden = view.tooltips.cssDocs.tooltip.once("hidden"); + EventUtils.sendKey("escape"); + yield onHidden; + ok(true, "The MDN docs tooltip was hidden on pressing 'escape'"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_cssom.js b/devtools/client/inspector/rules/test/browser_rules_cssom.js new file mode 100644 index 000000000..d20e85192 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_cssom.js @@ -0,0 +1,22 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test to ensure that CSSOM doesn't make the rule view blow up. +// https://bugzilla.mozilla.org/show_bug.cgi?id=1224121 + +const TEST_URI = URL_ROOT + "doc_cssom.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode("#target", inspector); + + let elementStyle = view._elementStyle; + let rule = elementStyle.rules[1]; + + is(rule.textProps.length, 1, "rule should have one property"); + is(rule.textProps[0].name, "color", "the property should be 'color'"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js new file mode 100644 index 000000000..18099894b --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js @@ -0,0 +1,70 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that cubic-bezier pickers appear when clicking on cubic-bezier +// swatches. + +const TEST_URI = ` + <style type="text/css"> + div { + animation: move 3s linear; + transition: top 4s cubic-bezier(.1, 1.45, 1, -1.2); + } + .test { + animation-timing-function: ease-in-out; + transition-timing-function: ease-out; + } + </style> + <div class="test">Testing the cubic-bezier tooltip!</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + let swatches = []; + swatches.push( + getRuleViewProperty(view, "div", "animation").valueSpan + .querySelector(".ruleview-bezierswatch") + ); + swatches.push( + getRuleViewProperty(view, "div", "transition").valueSpan + .querySelector(".ruleview-bezierswatch") + ); + swatches.push( + getRuleViewProperty(view, ".test", "animation-timing-function").valueSpan + .querySelector(".ruleview-bezierswatch") + ); + swatches.push( + getRuleViewProperty(view, ".test", "transition-timing-function").valueSpan + .querySelector(".ruleview-bezierswatch") + ); + + for (let swatch of swatches) { + info("Testing that the cubic-bezier appears on cubicswatch click"); + yield testAppears(view, swatch); + } +}); + +function* testAppears(view, swatch) { + ok(swatch, "The cubic-swatch exists"); + + let bezier = view.tooltips.cubicBezier; + ok(bezier, "The rule-view has the expected cubicBezier property"); + + let bezierPanel = bezier.tooltip.panel; + ok(bezierPanel, "The XUL panel for the cubic-bezier tooltip exists"); + + let onBezierWidgetReady = bezier.once("ready"); + swatch.click(); + yield onBezierWidgetReady; + + ok(true, "The cubic-bezier tooltip was shown on click of the cibuc swatch"); + ok(!inplaceEditor(swatch.parentNode), + "The inplace editor wasn't shown as a result of the cibuc swatch click"); + yield hideTooltipAndWaitForRuleViewChanged(bezier, view); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js new file mode 100644 index 000000000..5dc43d1c9 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js @@ -0,0 +1,66 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that a curve change in the cubic-bezier tooltip is committed when ENTER +// is pressed. + +const TEST_URI = ` + <style type="text/css"> + body { + transition: top 2s linear; + } + </style> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + info("Getting the bezier swatch element"); + let swatch = getRuleViewProperty(view, "body", "transition").valueSpan + .querySelector(".ruleview-bezierswatch"); + + yield testPressingEnterCommitsChanges(swatch, view); +}); + +function* testPressingEnterCommitsChanges(swatch, ruleView) { + let bezierTooltip = ruleView.tooltips.cubicBezier; + + info("Showing the tooltip"); + let onBezierWidgetReady = bezierTooltip.once("ready"); + swatch.click(); + yield onBezierWidgetReady; + + let widget = yield bezierTooltip.widget; + info("Simulating a change of curve in the widget"); + widget.coordinates = [0.1, 2, 0.9, -1]; + let expected = "cubic-bezier(0.1, 2, 0.9, -1)"; + + yield waitForSuccess(function* () { + let func = yield getComputedStyleProperty("body", null, + "transition-timing-function"); + return func === expected; + }, "Waiting for the change to be previewed on the element"); + + ok(getRuleViewProperty(ruleView, "body", "transition").valueSpan.textContent + .indexOf("cubic-bezier(") !== -1, + "The text of the timing-function was updated"); + + info("Sending RETURN key within the tooltip document"); + // Pressing RETURN ends up doing 2 rule-view updates, one for the preview and + // one for the commit when the tooltip closes. + let onRuleViewChanged = waitForNEvents(ruleView, "ruleview-changed", 2); + focusAndSendKey(widget.parent.ownerDocument.defaultView, "RETURN"); + yield onRuleViewChanged; + + let style = yield getComputedStyleProperty("body", null, + "transition-timing-function"); + is(style, expected, "The element's timing-function was kept after RETURN"); + + let ruleViewStyle = getRuleViewProperty(ruleView, "body", "transition") + .valueSpan.textContent.indexOf("cubic-bezier(") !== -1; + ok(ruleViewStyle, "The text of the timing-function was kept after RETURN"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js new file mode 100644 index 000000000..826d8a5aa --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js @@ -0,0 +1,100 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that changes made to the cubic-bezier timing-function in the +// cubic-bezier tooltip are reverted when ESC is pressed. + +const TEST_URI = ` + <style type='text/css'> + body { + animation-timing-function: linear; + } + </style> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + yield testPressingEscapeRevertsChanges(view); + yield testPressingEscapeRevertsChangesAndDisables(view); +}); + +function* testPressingEscapeRevertsChanges(view) { + let {propEditor} = yield openCubicBezierAndChangeCoords(view, 1, 0, + [0.1, 2, 0.9, -1], { + selector: "body", + name: "animation-timing-function", + value: "cubic-bezier(0.1, 2, 0.9, -1)" + }); + + is(propEditor.valueSpan.textContent, "cubic-bezier(.1,2,.9,-1)", + "Got expected property value."); + + yield escapeTooltip(view); + + yield waitForComputedStyleProperty("body", null, "animation-timing-function", + "linear"); + is(propEditor.valueSpan.textContent, "linear", + "Got expected property value."); +} + +function* testPressingEscapeRevertsChangesAndDisables(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let textProp = ruleEditor.rule.textProps[0]; + let propEditor = textProp.editor; + + info("Disabling animation-timing-function property"); + yield togglePropStatus(view, textProp); + + ok(propEditor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(propEditor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!propEditor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + ok(!propEditor.prop.enabled, + "animation-timing-function property is disabled."); + let newValue = yield getRulePropertyValue("animation-timing-function"); + is(newValue, "", "animation-timing-function should have been unset."); + + yield openCubicBezierAndChangeCoords(view, 1, 0, [0.1, 2, 0.9, -1]); + + yield escapeTooltip(view); + + ok(propEditor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(propEditor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!propEditor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + ok(!propEditor.prop.enabled, + "animation-timing-function property is disabled."); + newValue = yield getRulePropertyValue("animation-timing-function"); + is(newValue, "", "animation-timing-function should have been unset."); + is(propEditor.valueSpan.textContent, "linear", + "Got expected property value."); +} + +function* getRulePropertyValue(name) { + let propValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: name + }); + return propValue; +} + +function* escapeTooltip(view) { + info("Pressing ESCAPE to close the tooltip"); + + let bezierTooltip = view.tooltips.cubicBezier; + let widget = yield bezierTooltip.widget; + let onHidden = bezierTooltip.tooltip.once("hidden"); + let onModifications = view.once("ruleview-changed"); + focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE"); + yield onHidden; + yield onModifications; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_custom.js b/devtools/client/inspector/rules/test/browser_rules_custom.js new file mode 100644 index 000000000..7c941af6f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_custom.js @@ -0,0 +1,72 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URI = URL_ROOT + "doc_custom.html"; + +// Tests the display of custom declarations in the rule-view. + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + + yield simpleCustomOverride(inspector, view); + yield importantCustomOverride(inspector, view); + yield disableCustomOverride(inspector, view); +}); + +function* simpleCustomOverride(inspector, view) { + yield selectNode("#testidSimple", inspector); + + let idRule = getRuleViewRuleEditor(view, 1).rule; + let idRuleProp = idRule.textProps[0]; + + is(idRuleProp.name, "--background-color", + "First ID prop should be --background-color"); + ok(!idRuleProp.overridden, "ID prop should not be overridden."); + + let classRule = getRuleViewRuleEditor(view, 2).rule; + let classRuleProp = classRule.textProps[0]; + + is(classRuleProp.name, "--background-color", + "First class prop should be --background-color"); + ok(classRuleProp.overridden, "Class property should be overridden."); + + // Override --background-color by changing the element style. + let elementProp = yield addProperty(view, 0, "--background-color", "purple"); + + is(classRuleProp.name, "--background-color", + "First element prop should now be --background-color"); + ok(!elementProp.overridden, + "Element style property should not be overridden"); + ok(idRuleProp.overridden, "ID property should be overridden"); + ok(classRuleProp.overridden, "Class property should be overridden"); +} + +function* importantCustomOverride(inspector, view) { + yield selectNode("#testidImportant", inspector); + + let idRule = getRuleViewRuleEditor(view, 1).rule; + let idRuleProp = idRule.textProps[0]; + ok(idRuleProp.overridden, "Not-important rule should be overridden."); + + let classRule = getRuleViewRuleEditor(view, 2).rule; + let classRuleProp = classRule.textProps[0]; + ok(!classRuleProp.overridden, "Important rule should not be overridden."); +} + +function* disableCustomOverride(inspector, view) { + yield selectNode("#testidDisable", inspector); + + let idRule = getRuleViewRuleEditor(view, 1).rule; + let idRuleProp = idRule.textProps[0]; + + yield togglePropStatus(view, idRuleProp); + + let classRule = getRuleViewRuleEditor(view, 2).rule; + let classRuleProp = classRule.textProps[0]; + ok(!classRuleProp.overridden, + "Class prop should not be overridden after id prop was disabled."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_cycle-angle.js b/devtools/client/inspector/rules/test/browser_rules_cycle-angle.js new file mode 100644 index 000000000..fa135f937 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_cycle-angle.js @@ -0,0 +1,93 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test cycling angle units in the rule view. + +const TEST_URI = ` + <style type="text/css"> + body { + image-orientation: 1turn; + } + div { + image-orientation: 180deg; + } + </style> + <body><div>Test</div>cycling angle units in the rule view!</body> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let container = getRuleViewProperty( + view, "body", "image-orientation").valueSpan; + yield checkAngleCycling(container, view); + yield checkAngleCyclingPersist(inspector, view); +}); + +function* checkAngleCycling(container, view) { + let valueNode = container.querySelector(".ruleview-angle"); + let win = view.styleWindow; + + // turn + is(valueNode.textContent, "1turn", "Angle displayed as a turn value."); + + let tests = [{ + value: "360deg", + comment: "Angle displayed as a degree value." + }, { + value: `${Math.round(Math.PI * 2 * 10000) / 10000}rad`, + comment: "Angle displayed as a radian value." + }, { + value: "400grad", + comment: "Angle displayed as a gradian value." + }, { + value: "1turn", + comment: "Angle displayed as a turn value again." + }]; + + for (let test of tests) { + yield checkSwatchShiftClick(container, win, test.value, test.comment); + } +} + +function* checkAngleCyclingPersist(inspector, view) { + yield selectNode("div", inspector); + let container = getRuleViewProperty( + view, "div", "image-orientation").valueSpan; + let valueNode = container.querySelector(".ruleview-angle"); + let win = view.styleWindow; + + is(valueNode.textContent, "180deg", "Angle displayed as a degree value."); + + yield checkSwatchShiftClick(container, win, + `${Math.round(Math.PI * 10000) / 10000}rad`, + "Angle displayed as a radian value."); + + // Select the body and reselect the div to see + // if the new angle unit persisted + yield selectNode("body", inspector); + yield selectNode("div", inspector); + + // We have to query for the container and the swatch because + // they've been re-generated + container = getRuleViewProperty(view, "div", "image-orientation").valueSpan; + valueNode = container.querySelector(".ruleview-angle"); + is(valueNode.textContent, `${Math.round(Math.PI * 10000) / 10000}rad`, + "Angle still displayed as a radian value."); +} + +function* checkSwatchShiftClick(container, win, expectedValue, comment) { + let swatch = container.querySelector(".ruleview-angleswatch"); + let valueNode = container.querySelector(".ruleview-angle"); + + let onUnitChange = swatch.once("unit-change"); + EventUtils.synthesizeMouseAtCenter(swatch, { + type: "mousedown", + shiftKey: true + }, win); + yield onUnitChange; + is(valueNode.textContent, expectedValue, comment); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_cycle-color.js b/devtools/client/inspector/rules/test/browser_rules_cycle-color.js new file mode 100644 index 000000000..e31ffa133 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_cycle-color.js @@ -0,0 +1,120 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test cycling color types in the rule view. + +const TEST_URI = ` + <style type="text/css"> + body { + color: #f00; + } + span { + color: blue; + border-color: #ff000080; + } + </style> + <body><span>Test</span> cycling color types in the rule view!</body> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let container = getRuleViewProperty(view, "body", "color").valueSpan; + yield checkColorCycling(container, view); + yield checkAlphaColorCycling(inspector, view); + yield checkColorCyclingPersist(inspector, view); +}); + +function* checkColorCycling(container, view) { + let valueNode = container.querySelector(".ruleview-color"); + let win = view.styleWindow; + + // Hex + is(valueNode.textContent, "#f00", "Color displayed as a hex value."); + + let tests = [{ + value: "hsl(0, 100%, 50%)", + comment: "Color displayed as an HSL value." + }, { + value: "rgb(255, 0, 0)", + comment: "Color displayed as an RGB value." + }, { + value: "red", + comment: "Color displayed as a color name." + }, { + value: "#f00", + comment: "Color displayed as an authored value." + }, { + value: "hsl(0, 100%, 50%)", + comment: "Color displayed as an HSL value again." + }]; + + for (let test of tests) { + yield checkSwatchShiftClick(container, win, test.value, test.comment); + } +} + +function* checkAlphaColorCycling(inspector, view) { + yield selectNode("span", inspector); + let container = getRuleViewProperty(view, "span", "border-color").valueSpan; + let valueNode = container.querySelector(".ruleview-color"); + let win = view.styleWindow; + + is(valueNode.textContent, "#ff000080", + "Color displayed as an alpha hex value."); + + let tests = [{ + value: "hsla(0, 100%, 50%, 0.5)", + comment: "Color displayed as an HSLa value." + }, { + value: "rgba(255, 0, 0, 0.5)", + comment: "Color displayed as an RGBa value." + }, { + value: "#ff000080", + comment: "Color displayed as an alpha hex value again." + }]; + + for (let test of tests) { + yield checkSwatchShiftClick(container, win, test.value, test.comment); + } +} + +function* checkColorCyclingPersist(inspector, view) { + yield selectNode("span", inspector); + let container = getRuleViewProperty(view, "span", "color").valueSpan; + let valueNode = container.querySelector(".ruleview-color"); + let win = view.styleWindow; + + is(valueNode.textContent, "blue", "Color displayed as a color name."); + + yield checkSwatchShiftClick(container, win, "#00f", + "Color displayed as a hex value."); + + // Select the body and reselect the span to see + // if the new color unit persisted + yield selectNode("body", inspector); + yield selectNode("span", inspector); + + // We have to query for the container and the swatch because + // they've been re-generated + container = getRuleViewProperty(view, "span", "color").valueSpan; + valueNode = container.querySelector(".ruleview-color"); + is(valueNode.textContent, "#00f", + "Color is still displayed as a hex value."); +} + +function* checkSwatchShiftClick(container, win, expectedValue, comment) { + let swatch = container.querySelector(".ruleview-colorswatch"); + let valueNode = container.querySelector(".ruleview-color"); + + let onUnitChange = swatch.once("unit-change"); + EventUtils.synthesizeMouseAtCenter(swatch, { + type: "mousedown", + shiftKey: true + }, win); + yield onUnitChange; + is(valueNode.textContent, expectedValue, comment); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js b/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js new file mode 100644 index 000000000..18522b527 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js @@ -0,0 +1,49 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test toggling the grid highlighter in the rule view and modifying the 'display: grid' +// declaration. + +const TEST_URI = ` + <style type='text/css'> + #grid { + display: grid; + } + </style> + <div id="grid"> + <div id="cell1">cell1</div> + <div id="cell2">cell2</div> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let highlighters = view.highlighters; + + yield selectNode("#grid", inspector); + let container = getRuleViewProperty(view, "#grid", "display").valueSpan; + let gridToggle = container.querySelector(".ruleview-grid"); + + info("Toggling ON the CSS grid highlighter from the rule-view."); + let onHighlighterShown = highlighters.once("highlighter-shown"); + gridToggle.click(); + yield onHighlighterShown; + + info("Edit the 'grid' property value to 'block'."); + let editor = yield focusEditableField(view, container); + let onHighlighterHidden = highlighters.once("highlighter-hidden"); + let onDone = view.once("ruleview-changed"); + editor.input.value = "block;"; + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onHighlighterHidden; + yield onDone; + + info("Check the grid highlighter and grid toggle button are hidden."); + gridToggle = container.querySelector(".ruleview-grid"); + ok(!gridToggle, "Grid highlighter toggle is not visible."); + ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js new file mode 100644 index 000000000..af1a6fbc0 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js @@ -0,0 +1,46 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests editing a property name or value and escaping will revert the +// changes and restore the original value. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: #00F; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + + yield focusEditableField(view, propEditor.nameSpan); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, + ["DELETE", "ESCAPE"]); + + is(propEditor.nameSpan.textContent, "background-color", + "'background-color' property name is correctly set."); + is((yield getComputedStyleProperty("#testid", null, "background-color")), + "rgb(0, 0, 255)", "#00F background color is set."); + + yield focusEditableField(view, propEditor.valueSpan); + let onValueDeleted = view.once("ruleview-changed"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, + ["DELETE", "ESCAPE"]); + yield onValueDeleted; + + is(propEditor.valueSpan.textContent, "#00F", + "'#00F' property value is correctly set."); + is((yield getComputedStyleProperty("#testid", null, "background-color")), + "rgb(0, 0, 255)", "#00F background color is set."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-click.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-click.js new file mode 100644 index 000000000..08a5ee786 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-click.js @@ -0,0 +1,61 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the property name and value editors can be triggered when +// clicking on the property-name, the property-value, the colon or semicolon. + +const TEST_URI = ` + <style type='text/css'> + #testid { + margin: 0; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testEditPropertyAndCancel(inspector, view); +}); + +function* testEditPropertyAndCancel(inspector, view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + + info("Test editor is created when clicking on property name"); + yield focusEditableField(view, propEditor.nameSpan); + ok(propEditor.nameSpan.inplaceEditor, "Editor created for property name"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]); + + info("Test editor is created when clicking on ':' next to property name"); + let nameRect = propEditor.nameSpan.getBoundingClientRect(); + yield focusEditableField(view, propEditor.nameSpan, nameRect.width + 1); + ok(propEditor.nameSpan.inplaceEditor, "Editor created for property name"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]); + + info("Test editor is created when clicking on property value"); + yield focusEditableField(view, propEditor.valueSpan); + ok(propEditor.valueSpan.inplaceEditor, "Editor created for property value"); + // When cancelling a value edition, the text-property-editor will trigger + // a modification to make sure the property is back to its original value + // => need to wait on "ruleview-changed" to avoid unhandled promises + let onRuleviewChanged = view.once("ruleview-changed"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]); + yield onRuleviewChanged; + + info("Test editor is created when clicking on ';' next to property value"); + let valueRect = propEditor.valueSpan.getBoundingClientRect(); + yield focusEditableField(view, propEditor.valueSpan, valueRect.width + 1); + ok(propEditor.valueSpan.inplaceEditor, "Editor created for property value"); + // When cancelling a value edition, the text-property-editor will trigger + // a modification to make sure the property is back to its original value + // => need to wait on "ruleview-changed" to avoid unhandled promises + onRuleviewChanged = view.once("ruleview-changed"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]); + yield onRuleviewChanged; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js new file mode 100644 index 000000000..8e16601c7 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js @@ -0,0 +1,92 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test original value is correctly displayed when ESCaping out of the +// inplace editor in the style inspector. + +const TEST_URI = ` + <style type='text/css'> + #testid { + color: #00F; + } + </style> + <div id='testid'>Styled Node</div> +`; + +// Test data format +// { +// value: what char sequence to type, +// commitKey: what key to type to "commit" the change, +// modifiers: commitKey modifiers, +// expected: what value is expected as a result +// } +const testData = [ + { + value: "red", + commitKey: "VK_ESCAPE", + modifiers: {}, + expected: "#00F" + }, + { + value: "red", + commitKey: "VK_RETURN", + modifiers: {}, + expected: "red" + }, + { + value: "invalid", + commitKey: "VK_RETURN", + modifiers: {}, + expected: "invalid" + }, + { + value: "blue", + commitKey: "VK_TAB", modifiers: {shiftKey: true}, + expected: "blue" + } +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + for (let data of testData) { + yield runTestData(view, data); + } +}); + +function* runTestData(view, {value, commitKey, modifiers, expected}) { + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = idRuleEditor.rule.textProps[0].editor; + + info("Focusing the inplace editor field"); + + let editor = yield focusEditableField(view, propEditor.valueSpan); + is(inplaceEditor(propEditor.valueSpan), editor, + "Focused editor should be the value span."); + + info("Entering test data " + value); + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.sendString(value, view.styleWindow); + view.throttle.flush(); + yield onRuleViewChanged; + + info("Entering the commit key " + commitKey + " " + modifiers); + onRuleViewChanged = view.once("ruleview-changed"); + let onBlur = once(editor.input, "blur"); + EventUtils.synthesizeKey(commitKey, modifiers); + yield onBlur; + yield onRuleViewChanged; + + if (commitKey === "VK_ESCAPE") { + is(propEditor.valueSpan.textContent, expected, + "Value is as expected: " + expected); + } else { + is(propEditor.valueSpan.textContent, expected, + "Value is as expected: " + expected); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js new file mode 100644 index 000000000..ee0a1fa74 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js @@ -0,0 +1,89 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the computed values of a style (the shorthand expansion) are +// properly updated after the style is changed. + +const TEST_URI = ` + <style type="text/css"> + #testid { + padding: 10px; + } + </style> + <div id="testid">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield editAndCheck(view); +}); + +function* editAndCheck(view) { + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let prop = idRuleEditor.rule.textProps[0]; + let propEditor = prop.editor; + let newPaddingValue = "20px"; + + info("Focusing the inplace editor field"); + let editor = yield focusEditableField(view, propEditor.valueSpan); + is(inplaceEditor(propEditor.valueSpan), editor, + "Focused editor should be the value span."); + + let onPropertyChange = waitForComputedStyleProperty("#testid", null, + "padding-top", newPaddingValue); + let onRefreshAfterPreview = once(view, "ruleview-changed"); + + info("Entering a new value"); + EventUtils.sendString(newPaddingValue, view.styleWindow); + + info("Waiting for the throttled previewValue to apply the " + + "changes to document"); + + view.throttle.flush(); + yield onPropertyChange; + + info("Waiting for ruleview-refreshed after previewValue was applied."); + yield onRefreshAfterPreview; + + let onBlur = once(editor.input, "blur"); + + info("Entering the commit key and finishing edit"); + EventUtils.synthesizeKey("VK_RETURN", {}); + + info("Waiting for blur on the field"); + yield onBlur; + + info("Waiting for the style changes to be applied"); + yield once(view, "ruleview-changed"); + + let computed = prop.computed; + let propNames = [ + "padding-top", + "padding-right", + "padding-bottom", + "padding-left" + ]; + + is(computed.length, propNames.length, "There should be 4 computed values"); + propNames.forEach((propName, i) => { + is(computed[i].name, propName, + "Computed property #" + i + " has name " + propName); + is(computed[i].value, newPaddingValue, + "Computed value of " + propName + " is as expected"); + }); + + propEditor.expander.click(); + let computedDom = propEditor.computed; + is(computedDom.children.length, propNames.length, + "There should be 4 nodes in the DOM"); + propNames.forEach((propName, i) => { + is(computedDom.getElementsByClassName("ruleview-propertyvalue")[i] + .textContent, newPaddingValue, + "Computed value of " + propName + " in DOM is as expected"); + }); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js new file mode 100644 index 000000000..ca63cedcc --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js @@ -0,0 +1,280 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that increasing/decreasing values in rule view using +// arrow keys works correctly. + +// Bug 1275446 - This test happen to hit the default timeout on linux32 +requestLongerTimeout(2); + +const TEST_URI = ` + <style> + #test { + margin-top: 0px; + padding-top: 0px; + color: #000000; + background-color: #000000; + background: none; + transition: initial; + z-index: 0; + } + </style> + <div id="test"></div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + + let {inspector, view} = yield openRuleView(); + yield selectNode("#test", inspector); + + yield testMarginIncrements(view); + yield testVariousUnitIncrements(view); + yield testHexIncrements(view); + yield testAlphaHexIncrements(view); + yield testRgbIncrements(view); + yield testShorthandIncrements(view); + yield testOddCases(view); + yield testZeroValueIncrements(view); +}); + +function* testMarginIncrements(view) { + info("Testing keyboard increments on the margin property"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let marginPropEditor = idRuleEditor.rule.textProps[0].editor; + + yield runIncrementTest(marginPropEditor, view, { + 1: {alt: true, start: "0px", end: "0.1px", selectAll: true}, + 2: {start: "0px", end: "1px", selectAll: true}, + 3: {shift: true, start: "0px", end: "10px", selectAll: true}, + 4: {down: true, alt: true, start: "0.1px", end: "0px", selectAll: true}, + 5: {down: true, start: "0px", end: "-1px", selectAll: true}, + 6: {down: true, shift: true, start: "0px", end: "-10px", selectAll: true}, + 7: {pageUp: true, shift: true, start: "0px", end: "100px", selectAll: true}, + 8: {pageDown: true, shift: true, start: "0px", end: "-100px", + selectAll: true}, + 9: {start: "0", end: "1px", selectAll: true}, + 10: {down: true, start: "0", end: "-1px", selectAll: true}, + }); +} + +function* testVariousUnitIncrements(view) { + info("Testing keyboard increments on values with various units"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let paddingPropEditor = idRuleEditor.rule.textProps[1].editor; + + yield runIncrementTest(paddingPropEditor, view, { + 1: {start: "0px", end: "1px", selectAll: true}, + 2: {start: "0pt", end: "1pt", selectAll: true}, + 3: {start: "0pc", end: "1pc", selectAll: true}, + 4: {start: "0em", end: "1em", selectAll: true}, + 5: {start: "0%", end: "1%", selectAll: true}, + 6: {start: "0in", end: "1in", selectAll: true}, + 7: {start: "0cm", end: "1cm", selectAll: true}, + 8: {start: "0mm", end: "1mm", selectAll: true}, + 9: {start: "0ex", end: "1ex", selectAll: true}, + 10: {start: "0", end: "1px", selectAll: true}, + 11: {down: true, start: "0", end: "-1px", selectAll: true}, + }); +} + +function* testHexIncrements(view) { + info("Testing keyboard increments with hex colors"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor; + + yield runIncrementTest(hexColorPropEditor, view, { + 1: {start: "#CCCCCC", end: "#CDCDCD", selectAll: true}, + 2: {shift: true, start: "#CCCCCC", end: "#DCDCDC", selectAll: true}, + 3: {start: "#CCCCCC", end: "#CDCCCC", selection: [1, 3]}, + 4: {shift: true, start: "#CCCCCC", end: "#DCCCCC", selection: [1, 3]}, + 5: {start: "#FFFFFF", end: "#FFFFFF", selectAll: true}, + 6: {down: true, shift: true, start: "#000000", end: "#000000", + selectAll: true} + }); +} + +function* testAlphaHexIncrements(view) { + info("Testing keyboard increments with alpha hex colors"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor; + + yield runIncrementTest(hexColorPropEditor, view, { + 1: {start: "#CCCCCCAA", end: "#CDCDCDAB", selectAll: true}, + 2: {shift: true, start: "#CCCCCCAA", end: "#DCDCDCBA", selectAll: true}, + 3: {start: "#CCCCCCAA", end: "#CDCCCCAA", selection: [1, 3]}, + 4: {shift: true, start: "#CCCCCCAA", end: "#DCCCCCAA", selection: [1, 3]}, + 5: {start: "#FFFFFFFF", end: "#FFFFFFFF", selectAll: true}, + 6: {down: true, shift: true, start: "#00000000", end: "#00000000", + selectAll: true} + }); +} + +function* testRgbIncrements(view) { + info("Testing keyboard increments with rgb colors"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let rgbColorPropEditor = idRuleEditor.rule.textProps[3].editor; + + yield runIncrementTest(rgbColorPropEditor, view, { + 1: {start: "rgb(0,0,0)", end: "rgb(0,1,0)", selection: [6, 7]}, + 2: {shift: true, start: "rgb(0,0,0)", end: "rgb(0,10,0)", + selection: [6, 7]}, + 3: {start: "rgb(0,255,0)", end: "rgb(0,255,0)", selection: [6, 9]}, + 4: {shift: true, start: "rgb(0,250,0)", end: "rgb(0,255,0)", + selection: [6, 9]}, + 5: {down: true, start: "rgb(0,0,0)", end: "rgb(0,0,0)", selection: [6, 7]}, + 6: {down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)", + selection: [6, 7]} + }); +} + +function* testShorthandIncrements(view) { + info("Testing keyboard increments within shorthand values"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let paddingPropEditor = idRuleEditor.rule.textProps[1].editor; + + yield runIncrementTest(paddingPropEditor, view, { + 1: {start: "0px 0px 0px 0px", end: "0px 1px 0px 0px", selection: [4, 7]}, + 2: {shift: true, start: "0px 0px 0px 0px", end: "0px 10px 0px 0px", + selection: [4, 7]}, + 3: {start: "0px 0px 0px 0px", end: "1px 0px 0px 0px", selectAll: true}, + 4: {shift: true, start: "0px 0px 0px 0px", end: "10px 0px 0px 0px", + selectAll: true}, + 5: {down: true, start: "0px 0px 0px 0px", end: "0px 0px -1px 0px", + selection: [8, 11]}, + 6: {down: true, shift: true, start: "0px 0px 0px 0px", + end: "-10px 0px 0px 0px", selectAll: true}, + 7: {up: true, start: "0.1em .1em 0em 0em", end: "0.1em 1.1em 0em 0em", + selection: [6, 9]}, + 8: {up: true, alt: true, start: "0.1em .9em 0em 0em", + end: "0.1em 1em 0em 0em", selection: [6, 9]}, + 9: {up: true, shift: true, start: "0.2em .2em 0em 0em", + end: "0.2em 10.2em 0em 0em", selection: [6, 9]} + }); +} + +function* testOddCases(view) { + info("Testing some more odd cases"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let marginPropEditor = idRuleEditor.rule.textProps[0].editor; + + yield runIncrementTest(marginPropEditor, view, { + 1: {start: "98.7%", end: "99.7%", selection: [3, 3]}, + 2: {alt: true, start: "98.7%", end: "98.8%", selection: [3, 3]}, + 3: {start: "0", end: "1px"}, + 4: {down: true, start: "0", end: "-1px"}, + 5: {start: "'a=-1'", end: "'a=0'", selection: [4, 4]}, + 6: {start: "0 -1px", end: "0 0px", selection: [2, 2]}, + 7: {start: "url(-1)", end: "url(-1)", selection: [4, 4]}, + 8: {start: "url('test1.1.png')", end: "url('test1.2.png')", + selection: [11, 11]}, + 9: {start: "url('test1.png')", end: "url('test2.png')", selection: [9, 9]}, + 10: {shift: true, start: "url('test1.1.png')", end: "url('test11.1.png')", + selection: [9, 9]}, + 11: {down: true, start: "url('test-1.png')", end: "url('test-2.png')", + selection: [9, 11]}, + 12: {start: "url('test1.1.png')", end: "url('test1.2.png')", + selection: [11, 12]}, + 13: {down: true, alt: true, start: "url('test-0.png')", + end: "url('test--0.1.png')", selection: [10, 11]}, + 14: {alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')", + selection: [10, 14]} + }); +} + +function* testZeroValueIncrements(view) { + info("Testing a valid unit is added when incrementing from 0"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + let backgroundPropEditor = idRuleEditor.rule.textProps[4].editor; + yield runIncrementTest(backgroundPropEditor, view, { + 1: { start: "url(test-0.png) no-repeat 0 0", + end: "url(test-0.png) no-repeat 1px 0", selection: [26, 26] }, + 2: { start: "url(test-0.png) no-repeat 0 0", + end: "url(test-0.png) no-repeat 0 1px", selection: [28, 28] }, + 3: { start: "url(test-0.png) no-repeat center/0", + end: "url(test-0.png) no-repeat center/1px", selection: [34, 34] }, + 4: { start: "url(test-0.png) no-repeat 0 0", + end: "url(test-1.png) no-repeat 0 0", selection: [10, 10] }, + 5: { start: "linear-gradient(0, red 0, blue 0)", + end: "linear-gradient(1deg, red 0, blue 0)", selection: [17, 17] }, + 6: { start: "linear-gradient(1deg, red 0, blue 0)", + end: "linear-gradient(1deg, red 1px, blue 0)", selection: [27, 27] }, + 7: { start: "linear-gradient(1deg, red 0, blue 0)", + end: "linear-gradient(1deg, red 0, blue 1px)", selection: [35, 35] }, + }); + + let transitionPropEditor = idRuleEditor.rule.textProps[5].editor; + yield runIncrementTest(transitionPropEditor, view, { + 1: { start: "all 0 ease-out", end: "all 1s ease-out", selection: [5, 5] }, + 2: { start: "margin 4s, color 0", + end: "margin 4s, color 1s", selection: [18, 18] }, + }); + + let zIndexPropEditor = idRuleEditor.rule.textProps[6].editor; + yield runIncrementTest(zIndexPropEditor, view, { + 1: {start: "0", end: "1", selection: [1, 1]}, + }); +} + +function* runIncrementTest(propertyEditor, view, tests) { + let editor = yield focusEditableField(view, propertyEditor.valueSpan); + + for (let test in tests) { + yield testIncrement(editor, tests[test], view, propertyEditor); + } + + // Blur the field to put back the UI in its initial state (and avoid pending + // requests when the test ends). + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + view.throttle.flush(); + yield onRuleViewChanged; +} + +function* testIncrement(editor, options, view) { + editor.input.value = options.start; + let input = editor.input; + + if (options.selectAll) { + input.select(); + } else if (options.selection) { + input.setSelectionRange(options.selection[0], options.selection[1]); + } + + is(input.value, options.start, "Value initialized at " + options.start); + + let onRuleViewChanged = view.once("ruleview-changed"); + let onKeyUp = once(input, "keyup"); + + let key; + key = options.down ? "VK_DOWN" : "VK_UP"; + if (options.pageDown) { + key = "VK_PAGE_DOWN"; + } else if (options.pageUp) { + key = "VK_PAGE_UP"; + } + + EventUtils.synthesizeKey(key, {altKey: options.alt, shiftKey: options.shift}, + view.styleWindow); + + yield onKeyUp; + + // Only expect a change if the value actually changed! + if (options.start !== options.end) { + view.throttle.flush(); + yield onRuleViewChanged; + } + + is(input.value, options.end, "Value changed to " + options.end); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js new file mode 100644 index 000000000..b4a86c194 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js @@ -0,0 +1,89 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Checking properties orders and overrides in the rule-view. + +const TEST_URI = "<style>#testid {}</style><div id='testid'>Styled Node</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let elementStyle = view._elementStyle; + let elementRule = elementStyle.rules[1]; + + info("Checking rules insertion order and checking the applied style"); + let firstProp = yield addProperty(view, 1, "background-color", "green"); + let secondProp = yield addProperty(view, 1, "background-color", "blue"); + + is(elementRule.textProps[0], firstProp, + "Rules should be in addition order."); + is(elementRule.textProps[1], secondProp, + "Rules should be in addition order."); + + // rgb(0, 0, 255) = blue + is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)", + "Second property should have been used."); + + info("Removing the second property and checking the applied style again"); + yield removeProperty(view, secondProp); + // rgb(0, 128, 0) = green + is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)", + "After deleting second property, first should be used."); + + info("Creating a new second property and checking that the insertion order " + + "is still the same"); + + secondProp = yield addProperty(view, 1, "background-color", "blue"); + + is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)", + "New property should be used."); + is(elementRule.textProps[0], firstProp, + "Rules shouldn't have switched places."); + is(elementRule.textProps[1], secondProp, + "Rules shouldn't have switched places."); + + info("Disabling the second property and checking the applied style"); + yield togglePropStatus(view, secondProp); + + is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)", + "After disabling second property, first value should be used"); + + info("Disabling the first property too and checking the applied style"); + yield togglePropStatus(view, firstProp); + + is((yield getValue("#testid", "background-color")), "transparent", + "After disabling both properties, value should be empty."); + + info("Re-enabling the second propertyt and checking the applied style"); + yield togglePropStatus(view, secondProp); + + is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)", + "Value should be set correctly after re-enabling"); + + info("Re-enabling the first property and checking the insertion order " + + "is still respected"); + yield togglePropStatus(view, firstProp); + + is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)", + "Re-enabling an earlier property shouldn't make it override " + + "a later property."); + is(elementRule.textProps[0], firstProp, + "Rules shouldn't have switched places."); + is(elementRule.textProps[1], secondProp, + "Rules shouldn't have switched places."); + info("Modifying the first property and checking the applied style"); + yield setProperty(view, firstProp, "purple"); + + is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)", + "Modifying an earlier property shouldn't override a later property."); +}); + +function* getValue(selector, propName) { + let value = yield getComputedStyleProperty(selector, null, propName); + return value; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js new file mode 100644 index 000000000..0aed2f5c8 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js @@ -0,0 +1,67 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests removing a property by clearing the property name and pressing the +// return key, and checks if the focus is moved to the appropriate editable +// field. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: #00F; + color: #00F; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Getting the first property in the #testid rule"); + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + info("Deleting the name of that property to remove the property"); + yield removeProperty(view, prop, false); + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "background-color" + }); + is(newValue, "", "background-color should have been unset."); + + info("Getting the new first property in the rule"); + prop = rule.textProps[0]; + + let editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(prop.editor.nameSpan), editor, + "Focus should have moved to the next property name"); + + info("Deleting the name of that property to remove the property"); + view.styleDocument.activeElement.blur(); + yield removeProperty(view, prop, false); + + newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "color" + }); + is(newValue, "", "color should have been unset."); + + editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(rule.editor.newPropSpan), editor, + "Focus should have moved to the new property span"); + is(rule.textProps.length, 0, + "All properties should have been removed."); + is(rule.editor.propertyList.children.length, 1, + "Should have the new property span."); + + view.styleDocument.activeElement.blur(); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js new file mode 100644 index 000000000..5690e7c2d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js @@ -0,0 +1,67 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests removing a property by clearing the property value and pressing the +// return key, and checks if the focus is moved to the appropriate editable +// field. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: #00F; + color: #00F; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Getting the first property in the rule"); + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + info("Clearing the property value"); + yield setProperty(view, prop, null, false); + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "background-color" + }); + is(newValue, "", "background-color should have been unset."); + + info("Getting the new first property in the rule"); + prop = rule.textProps[0]; + + let editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(prop.editor.nameSpan), editor, + "Focus should have moved to the next property name"); + view.styleDocument.activeElement.blur(); + + info("Clearing the property value"); + yield setProperty(view, prop, null, false); + + newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "color" + }); + is(newValue, "", "color should have been unset."); + + editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(rule.editor.newPropSpan), editor, + "Focus should have moved to the new property span"); + is(rule.textProps.length, 0, + "All properties should have been removed."); + is(rule.editor.propertyList.children.length, 1, + "Should have the new property span."); + + view.styleDocument.activeElement.blur(); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js new file mode 100644 index 000000000..21a1063c2 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js @@ -0,0 +1,83 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests removing a property by clearing the property name and pressing shift +// and tab keys, and checks if the focus is moved to the appropriate editable +// field. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: #00F; + color: #00F; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Getting the second property in the rule"); + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[1]; + + info("Clearing the property value and pressing shift-tab"); + let editor = yield focusEditableField(view, prop.editor.valueSpan); + let onValueDone = view.once("ruleview-changed"); + editor.input.value = ""; + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow); + yield onValueDone; + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "color" + }); + is(newValue, "", "color should have been unset."); + is(prop.editor.valueSpan.textContent, "", + "'' property value is correctly set."); + + info("Pressing shift-tab again to focus the previous property value"); + let onValueFocused = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow); + yield onValueFocused; + + info("Getting the first property in the rule"); + prop = rule.textProps[0]; + + editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(prop.editor.valueSpan), editor, + "Focus should have moved to the previous property value"); + + info("Pressing shift-tab again to focus the property name"); + let onNameFocused = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow); + yield onNameFocused; + + info("Removing the name and pressing shift-tab to focus the selector"); + let onNameDeleted = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow); + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow); + yield onNameDeleted; + + newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "background-color" + }); + is(newValue, "", "background-color should have been unset."); + + editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(rule.editor.selectorText), editor, + "Focus should have moved to the selector text."); + is(rule.textProps.length, 0, + "All properties should have been removed."); + ok(!rule.editor.propertyList.hasChildNodes(), + "Should not have any properties."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js new file mode 100644 index 000000000..6f4c49e20 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js @@ -0,0 +1,93 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing adding new properties via the inplace-editors in the rule +// view. +// FIXME: some of the inplace-editor focus/blur/commit/revert stuff +// should be factored out in head.js + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: red; + background-color: blue; + } + .testclass, .unmatched { + background-color: green; + } + </style> + <div id="testid" class="testclass">Styled Node</div> + <div id="testid2">Styled Node</div> +`; + +var BACKGROUND_IMAGE_URL = 'url("' + URL_ROOT + 'doc_test_image.png")'; + +var TEST_DATA = [ + { name: "border-color", value: "red", isValid: true }, + { name: "background-image", value: BACKGROUND_IMAGE_URL, isValid: true }, + { name: "border", value: "solid 1px foo", isValid: false }, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let rule = getRuleViewRuleEditor(view, 1).rule; + for (let {name, value, isValid} of TEST_DATA) { + yield testEditProperty(view, rule, name, value, isValid); + } +}); + +function* testEditProperty(view, rule, name, value, isValid) { + info("Test editing existing property name/value fields"); + + let doc = rule.editor.doc; + let prop = rule.textProps[0]; + + info("Focusing an existing property name in the rule-view"); + let editor = yield focusEditableField(view, prop.editor.nameSpan, 32, 1); + + is(inplaceEditor(prop.editor.nameSpan), editor, + "The property name editor got focused"); + let input = editor.input; + + info("Entering a new property name, including : to commit and " + + "focus the value"); + let onValueFocus = once(rule.editor.element, "focus", true); + let onNameDone = view.once("ruleview-changed"); + EventUtils.sendString(name + ":", doc.defaultView); + yield onValueFocus; + yield onNameDone; + + // Getting the value editor after focus + editor = inplaceEditor(doc.activeElement); + input = editor.input; + is(inplaceEditor(prop.editor.valueSpan), editor, "Focus moved to the value."); + + info("Entering a new value, including ; to commit and blur the value"); + let onValueDone = view.once("ruleview-changed"); + let onBlur = once(input, "blur"); + EventUtils.sendString(value + ";", doc.defaultView); + yield onBlur; + yield onValueDone; + + is(prop.editor.isValid(), isValid, + value + " is " + isValid ? "valid" : "invalid"); + + info("Checking that the style property was changed on the content page"); + let propValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name + }); + + if (isValid) { + is(propValue, value, name + " should have been set."); + } else { + isnot(propValue, value, name + " shouldn't have been set."); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js new file mode 100644 index 000000000..7e6315236 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js @@ -0,0 +1,133 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test several types of rule-view property edition + +const TEST_URI = ` + <style type="text/css"> + #testid { + background-color: blue; + } + .testclass, .unmatched { + background-color: green; + } + </style> + <div id="testid" class="testclass">Styled Node</div> + <div id="testid2">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + yield testEditProperty(inspector, view); + yield testDisableProperty(inspector, view); + yield testPropertyStillMarkedDirty(inspector, view); +}); + +function* testEditProperty(inspector, ruleView) { + let idRule = getRuleViewRuleEditor(ruleView, 1).rule; + let prop = idRule.textProps[0]; + + let editor = yield focusEditableField(ruleView, prop.editor.nameSpan); + let input = editor.input; + is(inplaceEditor(prop.editor.nameSpan), editor, + "Next focused editor should be the name editor."); + + ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, + "Editor contents are selected."); + + // Try clicking on the editor's input again, shouldn't cause trouble + // (see bug 761665). + EventUtils.synthesizeMouse(input, 1, 1, {}, ruleView.styleWindow); + input.select(); + + info("Entering property name \"border-color\" followed by a colon to " + + "focus the value"); + let onNameDone = ruleView.once("ruleview-changed"); + let onFocus = once(idRule.editor.element, "focus", true); + EventUtils.sendString("border-color:", ruleView.styleWindow); + yield onFocus; + yield onNameDone; + + info("Verifying that the focused field is the valueSpan"); + editor = inplaceEditor(ruleView.styleDocument.activeElement); + input = editor.input; + is(inplaceEditor(prop.editor.valueSpan), editor, + "Focus should have moved to the value."); + ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, + "Editor contents are selected."); + + info("Entering a value following by a semi-colon to commit it"); + let onBlur = once(editor.input, "blur"); + // Use sendChar() to pass each character as a string so that we can test + // prop.editor.warning.hidden after each character. + for (let ch of "red;") { + let onPreviewDone = ruleView.once("ruleview-changed"); + EventUtils.sendChar(ch, ruleView.styleWindow); + ruleView.throttle.flush(); + yield onPreviewDone; + is(prop.editor.warning.hidden, true, + "warning triangle is hidden or shown as appropriate"); + } + yield onBlur; + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "border-color" + }); + is(newValue, "red", "border-color should have been set."); + + ruleView.styleDocument.activeElement.blur(); + yield addProperty(ruleView, 1, "color", "red", ";"); + + let props = ruleView.element.querySelectorAll(".ruleview-property"); + for (let i = 0; i < props.length; i++) { + is(props[i].hasAttribute("dirty"), i <= 1, + "props[" + i + "] marked dirty as appropriate"); + } +} + +function* testDisableProperty(inspector, ruleView) { + let idRule = getRuleViewRuleEditor(ruleView, 1).rule; + let prop = idRule.textProps[0]; + + info("Disabling a property"); + yield togglePropStatus(ruleView, prop); + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "border-color" + }); + is(newValue, "", "Border-color should have been unset."); + + info("Enabling the property again"); + yield togglePropStatus(ruleView, prop); + + newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "border-color" + }); + is(newValue, "red", "Border-color should have been reset."); +} + +function* testPropertyStillMarkedDirty(inspector, ruleView) { + // Select an unstyled node. + yield selectNode("#testid2", inspector); + + // Select the original node again. + yield selectNode("#testid", inspector); + + let props = ruleView.element.querySelectorAll(".ruleview-property"); + for (let i = 0; i < props.length; i++) { + is(props[i].hasAttribute("dirty"), i <= 1, + "props[" + i + "] marked dirty as appropriate"); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_03.js new file mode 100644 index 000000000..a5771b41e --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_03.js @@ -0,0 +1,50 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that emptying out an existing value removes the property and +// doesn't cause any other issues. See also Bug 1150780. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: red; + background-color: blue; + font-size: 12px; + } + .testclass, .unmatched { + background-color: green; + } + </style> + <div id="testid" class="testclass">Styled Node</div> + <div id="testid2">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[1].editor; + + yield focusEditableField(view, propEditor.valueSpan); + + info("Deleting all the text out of a value field"); + let onRuleViewChanged = view.once("ruleview-changed"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, + ["DELETE", "RETURN"]); + yield onRuleViewChanged; + + info("Pressing enter a couple times to cycle through editors"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["RETURN"]); + onRuleViewChanged = view.once("ruleview-changed"); + yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["RETURN"]); + yield onRuleViewChanged; + + isnot(ruleEditor.rule.textProps[1].editor.nameSpan.style.display, "none", + "The name span is visible"); + is(ruleEditor.rule.textProps.length, 2, "Correct number of props"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js new file mode 100644 index 000000000..7460db4cd --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js @@ -0,0 +1,85 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that a disabled property remains disabled when the escaping out of +// the property editor. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + info("Disabling a property"); + yield togglePropStatus(view, prop); + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "background-color" + }); + is(newValue, "", "background-color should have been unset."); + + yield testEditDisableProperty(view, rule, prop, "name", "VK_ESCAPE"); + yield testEditDisableProperty(view, rule, prop, "value", "VK_ESCAPE"); + yield testEditDisableProperty(view, rule, prop, "value", "VK_TAB"); + yield testEditDisableProperty(view, rule, prop, "value", "VK_RETURN"); +}); + +function* testEditDisableProperty(view, rule, prop, fieldType, commitKey) { + let field = fieldType === "name" ? prop.editor.nameSpan + : prop.editor.valueSpan; + + let editor = yield focusEditableField(view, field); + + ok(!prop.editor.element.classList.contains("ruleview-overridden"), + "property is not overridden."); + is(prop.editor.enable.style.visibility, "hidden", + "property enable checkbox is hidden."); + + let newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "background-color" + }); + is(newValue, "", "background-color should remain unset."); + + let onChangeDone; + if (fieldType === "value") { + onChangeDone = view.once("ruleview-changed"); + } + + let onBlur = once(editor.input, "blur"); + EventUtils.synthesizeKey(commitKey, {}, view.styleWindow); + yield onBlur; + yield onChangeDone; + + ok(!prop.enabled, "property is disabled."); + ok(prop.editor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(prop.editor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!prop.editor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + + newValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: "background-color" + }); + is(newValue, "", "background-color should remain unset."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js new file mode 100644 index 000000000..3d37c81d5 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js @@ -0,0 +1,77 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that a disabled property is re-enabled if the property name or value is +// modified + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + info("Disabling background-color property"); + yield togglePropStatus(view, prop); + + let newValue = yield getRulePropertyValue("background-color"); + is(newValue, "", "background-color should have been unset."); + + info("Entering a new property name, including : to commit and " + + "focus the value"); + + yield focusEditableField(view, prop.editor.nameSpan); + let onNameDone = view.once("ruleview-changed"); + EventUtils.sendString("border-color:", view.styleWindow); + yield onNameDone; + + info("Escape editing the property value"); + let onValueDone = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onValueDone; + + newValue = yield getRulePropertyValue("border-color"); + is(newValue, "blue", "border-color should have been set."); + + ok(prop.enabled, "border-color property is enabled."); + ok(!prop.editor.element.classList.contains("ruleview-overridden"), + "border-color is not overridden"); + + info("Disabling border-color property"); + yield togglePropStatus(view, prop); + + newValue = yield getRulePropertyValue("border-color"); + is(newValue, "", "border-color should have been unset."); + + info("Enter a new property value for the border-color property"); + yield setProperty(view, prop, "red"); + + newValue = yield getRulePropertyValue("border-color"); + is(newValue, "red", "new border-color should have been set."); + + ok(prop.enabled, "border-color property is enabled."); + ok(!prop.editor.element.classList.contains("ruleview-overridden"), + "border-color is not overridden"); +}); + +function* getRulePropertyValue(name) { + let propValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: name + }); + return propValue; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js new file mode 100644 index 000000000..95211f1d0 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js @@ -0,0 +1,52 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that editing a property's priority is behaving correctly, and disabling +// and editing the property will re-enable the property. + +const TEST_URI = ` + <style type='text/css'> + body { + background-color: green !important; + } + body { + background-color: red; + } + </style> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("body", inspector); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + is((yield getComputedStyleProperty("body", null, "background-color")), + "rgb(0, 128, 0)", "green background color is set."); + + yield setProperty(view, prop, "red !important"); + + is(prop.editor.valueSpan.textContent, "red !important", + "'red !important' property value is correctly set."); + is((yield getComputedStyleProperty("body", null, "background-color")), + "rgb(255, 0, 0)", "red background color is set."); + + info("Disabling red background color property"); + yield togglePropStatus(view, prop); + + is((yield getComputedStyleProperty("body", null, "background-color")), + "rgb(0, 128, 0)", "green background color is set."); + + yield setProperty(view, prop, "red"); + + is(prop.editor.valueSpan.textContent, "red", + "'red' property value is correctly set."); + ok(prop.enabled, "red background-color property is enabled."); + is((yield getComputedStyleProperty("body", null, "background-color")), + "rgb(0, 128, 0)", "green background color is set."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js new file mode 100644 index 000000000..40314819f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js @@ -0,0 +1,50 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that adding multiple values will enable the property even if the +// property does not change, and that the extra values are added correctly. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: #f00; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let prop = rule.textProps[0]; + + info("Disabling red background color property"); + yield togglePropStatus(view, prop); + ok(!prop.enabled, "red background-color property is disabled."); + + let editor = yield focusEditableField(view, prop.editor.valueSpan); + let onDone = view.once("ruleview-changed"); + editor.input.value = "red; color: red;"; + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onDone; + + is(prop.editor.valueSpan.textContent, "red", + "'red' property value is correctly set."); + ok(prop.enabled, "red background-color property is enabled."); + is((yield getComputedStyleProperty("#testid", null, "background-color")), + "rgb(255, 0, 0)", "red background color is set."); + + let propEditor = rule.textProps[1].editor; + is(propEditor.nameSpan.textContent, "color", + "new 'color' property name is correctly set."); + is(propEditor.valueSpan.textContent, "red", + "new 'red' property value is correctly set."); + is((yield getComputedStyleProperty("#testid", null, "color")), + "rgb(255, 0, 0)", "red color is set."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_08.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_08.js new file mode 100644 index 000000000..1becd40d9 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_08.js @@ -0,0 +1,57 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that renaming a property works. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: #FFF; + } + </style> + <div style='color: red' id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Get the color property editor"); + let ruleEditor = getRuleViewRuleEditor(view, 0); + let propEditor = ruleEditor.rule.textProps[0].editor; + is(ruleEditor.rule.textProps[0].name, "color"); + + info("Focus the property name field"); + yield focusEditableField(ruleEditor.ruleView, propEditor.nameSpan, 32, 1); + + info("Rename the property to background-color"); + // Expect 3 events: the value editor being focused, the ruleview-changed event + // which signals that the new value has been previewed (fires once when the + // value gets focused), and the markupmutation event since we're modifying an + // inline style. + let onValueFocus = once(ruleEditor.element, "focus", true); + let onRuleViewChanged = ruleEditor.ruleView.once("ruleview-changed"); + let onMutation = inspector.once("markupmutation"); + EventUtils.sendString("background-color:", ruleEditor.doc.defaultView); + yield onValueFocus; + yield onRuleViewChanged; + yield onMutation; + + is(ruleEditor.rule.textProps[0].name, "background-color"); + yield waitForComputedStyleProperty("#testid", null, "background-color", + "rgb(255, 0, 0)"); + + is((yield getComputedStyleProperty("#testid", null, "color")), + "rgb(255, 255, 255)", "color is white"); + + // The value field is still focused. Blur it now and wait for the + // ruleview-changed event to avoid pending requests. + onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield onRuleViewChanged; +}); + diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_09.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_09.js new file mode 100644 index 000000000..51f714021 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_09.js @@ -0,0 +1,69 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that a newProperty editor is only created if no other editor was +// previously displayed. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testClickOnEmptyAreaToCloseEditor(inspector, view); +}); + +function synthesizeMouseOnEmptyArea(ruleEditor, view) { + // any text property editor will do + let propEditor = ruleEditor.rule.textProps[0].editor; + let valueContainer = propEditor.valueContainer; + let valueRect = valueContainer.getBoundingClientRect(); + // click right next to the ";" at the end of valueContainer + EventUtils.synthesizeMouse(valueContainer, valueRect.width + 1, 1, {}, + view.styleWindow); +} + +function* testClickOnEmptyAreaToCloseEditor(inspector, view) { + // Start at the beginning: start to add a rule to the element's style + // declaration, add some text, then press escape. + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + + info("Create a property value editor"); + let editor = yield focusEditableField(view, propEditor.valueSpan); + ok(editor.input, "The inplace-editor field is ready"); + + info("Close the property value editor by clicking on an empty area " + + "in the rule editor"); + let onRuleViewChanged = view.once("ruleview-changed"); + let onBlur = once(editor.input, "blur"); + synthesizeMouseOnEmptyArea(ruleEditor, view); + yield onBlur; + yield onRuleViewChanged; + ok(!view.isEditing, "No inplace editor should be displayed in the ruleview"); + + info("Create new newProperty editor by clicking again on the empty area"); + let onFocus = once(ruleEditor.element, "focus", true); + synthesizeMouseOnEmptyArea(ruleEditor, view); + yield onFocus; + editor = inplaceEditor(ruleEditor.element.ownerDocument.activeElement); + is(inplaceEditor(ruleEditor.newPropSpan), editor, + "New property editor was created"); + + info("Close the newProperty editor by clicking again on the empty area"); + onBlur = once(editor.input, "blur"); + synthesizeMouseOnEmptyArea(ruleEditor, view); + yield onBlur; + + ok(!view.isEditing, "No inplace editor should be displayed in the ruleview"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js new file mode 100644 index 000000000..1846df60d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js @@ -0,0 +1,88 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing ruleview inplace-editor is not blurred when clicking on the ruleview +// container scrollbar. + +const TEST_URI = ` + <style type="text/css"> + div.testclass { + color: black; + } + .a { + color: #aaa; + } + .b { + color: #bbb; + } + .c { + color: #ccc; + } + .d { + color: #ddd; + } + .e { + color: #eee; + } + .f { + color: #fff; + } + </style> + <div class="testclass a b c d e f">Styled Node</div> +`; + +add_task(function* () { + info("Toolbox height should be small enough to force scrollbars to appear"); + yield new Promise(done => { + let options = {"set": [ + ["devtools.toolbox.footer.height", 200], + ]}; + SpecialPowers.pushPrefEnv(options, done); + }); + + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".testclass", inspector); + + info("Check we have an overflow on the ruleview container."); + let container = view.element; + let hasScrollbar = container.offsetHeight < container.scrollHeight; + ok(hasScrollbar, "The rule view container should have a vertical scrollbar."); + + info("Focusing an existing selector name in the rule-view."); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor is focused."); + + info("Click on the scrollbar element."); + yield clickOnRuleviewScrollbar(view); + + is(editor.input, view.styleDocument.activeElement, + "The editor input should still be focused."); + + info("Check a new value can still be committed in the editable field"); + let newValue = ".testclass.a.b.c.d.e.f"; + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Enter new value and commit."); + editor.input.value = newValue; + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + ok(getRuleViewRule(view, newValue), "Rule with '" + newValue + " 'exists."); +}); + +function* clickOnRuleviewScrollbar(view) { + let container = view.element.parentNode; + let onScroll = once(container, "scroll"); + let rect = container.getBoundingClientRect(); + // click 5 pixels before the bottom-right corner should hit the scrollbar + EventUtils.synthesizeMouse(container, rect.width - 5, rect.height - 5, + {}, view.styleWindow); + yield onScroll; + + ok(true, "The rule view container scrolled after clicking on the scrollbar."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js new file mode 100644 index 000000000..7a3b6d467 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js @@ -0,0 +1,63 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing selector inplace-editor remains available and focused after clicking +// in its input. + +const TEST_URI = ` + <style type="text/css"> + .testclass { + text-align: center; + } + </style> + <div class="testclass">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".testclass", inspector); + yield testClickOnSelectorEditorInput(view); +}); + +function* testClickOnSelectorEditorInput(view) { + info("Test clicking inside the selector editor input"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + let editorInput = editor.input; + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Click inside the editor input"); + let onClick = once(editorInput, "click"); + EventUtils.synthesizeMouse(editor.input, 2, 1, {}, view.styleWindow); + yield onClick; + is(editor.input, view.styleDocument.activeElement, + "The editor input should still be focused"); + ok(!ruleEditor.newPropSpan, "No newProperty editor was created"); + + info("Doubleclick inside the editor input"); + let onDoubleClick = once(editorInput, "dblclick"); + EventUtils.synthesizeMouse(editor.input, 2, 1, { clickCount: 2 }, + view.styleWindow); + yield onDoubleClick; + is(editor.input, view.styleDocument.activeElement, + "The editor input should still be focused"); + ok(!ruleEditor.newPropSpan, "No newProperty editor was created"); + + info("Click outside the editor input"); + let onBlur = once(editorInput, "blur"); + let rect = editorInput.getBoundingClientRect(); + EventUtils.synthesizeMouse(editorInput, rect.width + 5, rect.height / 2, {}, + view.styleWindow); + yield onBlur; + + isnot(editorInput, view.styleDocument.activeElement, + "The editor input should no longer be focused"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js new file mode 100644 index 000000000..f7058371f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js @@ -0,0 +1,117 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test selector value is correctly displayed when committing the inplace editor +// with ENTER, ESC, SHIFT+TAB and TAB + +const TEST_URI = ` + <style type='text/css'> + #testid1 { + text-align: center; + } + #testid2 { + text-align: center; + } + #testid3 { + } + </style> + <div id='testid1'>Styled Node</div> + <div id='testid2'>Styled Node</div> + <div id='testid3'>Styled Node</div> +`; + +const TEST_DATA = [ + { + node: "#testid1", + value: ".testclass", + commitKey: "VK_ESCAPE", + modifiers: {}, + expected: "#testid1", + + }, + { + node: "#testid1", + value: ".testclass1", + commitKey: "VK_RETURN", + modifiers: {}, + expected: ".testclass1" + }, + { + node: "#testid2", + value: ".testclass2", + commitKey: "VK_TAB", + modifiers: {}, + expected: ".testclass2" + }, + { + node: "#testid3", + value: ".testclass3", + commitKey: "VK_TAB", + modifiers: {shiftKey: true}, + expected: ".testclass3" + } +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let { inspector, view } = yield openRuleView(); + + for (let data of TEST_DATA) { + yield runTestData(inspector, view, data); + } +}); + +function* runTestData(inspector, view, data) { + let {node, value, commitKey, modifiers, expected} = data; + + info("Updating " + node + " to " + value + " and committing with " + + commitKey + ". Expecting: " + expected); + + info("Selecting the test element"); + yield selectNode(node, inspector); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, idRuleEditor.selectorText); + is(inplaceEditor(idRuleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Enter the new selector value: " + value); + editor.input.value = value; + + info("Entering the commit key " + commitKey + " " + modifiers); + EventUtils.synthesizeKey(commitKey, modifiers); + + let activeElement = view.styleDocument.activeElement; + + if (commitKey === "VK_ESCAPE") { + is(idRuleEditor.rule.selectorText, expected, + "Value is as expected: " + expected); + is(idRuleEditor.isEditing, false, "Selector is not being edited."); + is(idRuleEditor.selectorText, activeElement, + "Focus is on selector span."); + return; + } + + yield once(view, "ruleview-changed"); + + ok(getRuleViewRule(view, expected), + "Rule with " + expected + " selector exists."); + + if (modifiers.shiftKey) { + idRuleEditor = getRuleViewRuleEditor(view, 0); + } + + let rule = idRuleEditor.rule; + if (rule.textProps.length > 0) { + is(inplaceEditor(rule.textProps[0].editor.nameSpan).input, activeElement, + "Focus is on the first property name span."); + } else { + is(inplaceEditor(idRuleEditor.newPropSpan).input, activeElement, + "Focus is on the new property span."); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js new file mode 100644 index 000000000..af228094b --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js @@ -0,0 +1,62 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing selector inplace-editor behaviors in the rule-view + +const TEST_URI = ` + <style type="text/css"> + .testclass { + text-align: center; + } + </style> + <div id="testid" class="testclass">Styled Node</div> + <span>This is a span</span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Selecting the test element"); + yield selectNode("#testid", inspector); + yield testEditSelector(view, "span"); + + info("Selecting the modified element with the new rule"); + yield selectNode("span", inspector); + yield checkModifiedElement(view, "span"); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, idRuleEditor.selectorText); + + is(inplaceEditor(idRuleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); + ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"), + "Rule with " + name + " does not match the current element."); +} + +function* checkModifiedElement(view, name) { + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js new file mode 100644 index 000000000..503f91efa --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js @@ -0,0 +1,88 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing selector inplace-editor behaviors in the rule-view with pseudo +// classes. + +const TEST_URI = ` + <style type="text/css"> + .testclass { + text-align: center; + } + #testid3::first-letter { + text-decoration: "italic" + } + </style> + <div id="testid">Styled Node</div> + <span class="testclass">This is a span</span> + <div class="testclass2">A</div> + <div id="testid3">B</div> +`; + +const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements"; + +add_task(function* () { + // Expand the pseudo-elements section by default. + Services.prefs.setBoolPref(PSEUDO_PREF, true); + + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Selecting the test element"); + yield selectNode(".testclass", inspector); + yield testEditSelector(view, "div:nth-child(1)"); + + info("Selecting the modified element"); + yield selectNode("#testid", inspector); + yield checkModifiedElement(view, "div:nth-child(1)"); + + info("Selecting the test element"); + yield selectNode("#testid3", inspector); + yield testEditSelector(view, ".testclass2::first-letter"); + + info("Selecting the modified element"); + yield selectNode(".testclass2", inspector); + yield checkModifiedElement(view, ".testclass2::first-letter"); + + // Reset the pseudo-elements section pref to its default value. + Services.prefs.clearUserPref(PSEUDO_PREF); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let idRuleEditor = getRuleViewRuleEditor(view, 1) || + getRuleViewRuleEditor(view, 1, 0); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, idRuleEditor.selectorText); + + is(inplaceEditor(idRuleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name: " + name); + editor.input.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 2, "Should have 2 rule."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); + + let newRuleEditor = getRuleViewRuleEditor(view, 1) || + getRuleViewRuleEditor(view, 1, 0); + ok(newRuleEditor.element.getAttribute("unmatched"), + "Rule with " + name + " does not match the current element."); +} + +function* checkModifiedElement(view, name) { + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js new file mode 100644 index 000000000..c6834f6ee --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js @@ -0,0 +1,48 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing selector inplace-editor behaviors in the rule-view with invalid +// selectors + +const TEST_URI = ` + <style type="text/css"> + .testclass { + text-align: center; + } + </style> + <div class="testclass">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".testclass", inspector); + yield testEditSelector(view, "asd@:::!"); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + let onRuleViewChanged = once(view, "ruleview-invalid-selector"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + is(getRuleViewRule(view, name), undefined, + "Rule with " + name + " selector should not exist."); + ok(getRuleViewRule(view, ".testclass"), + "Rule with .testclass selector exists."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js new file mode 100644 index 000000000..09b6ad841 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js @@ -0,0 +1,69 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the selector highlighter is removed when modifying a selector and +// the selector highlighter works for the newly added unmatched rule. + +const TEST_URI = ` + <style type="text/css"> + p { + background: red; + } + </style> + <p>Test the selector highlighter</p> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("p", inspector); + + ok(!view.selectorHighlighter, + "No selectorhighlighter exist in the rule-view"); + + yield testSelectorHighlight(view, "p"); + yield testEditSelector(view, "body"); + yield testSelectorHighlight(view, "body"); +}); + +function* testSelectorHighlight(view, name) { + info("Test creating selector highlighter"); + + info("Clicking on a selector icon"); + let icon = getRuleViewSelectorHighlighterIcon(view, name); + + let onToggled = view.once("ruleview-selectorhighlighter-toggled"); + EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow); + let isVisible = yield onToggled; + + ok(view.selectorHighlighter, "The selectorhighlighter instance was created"); + ok(isVisible, "The toggle event says the highlighter is visible"); +} + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Waiting for rule view to update"); + let onToggled = view.once("ruleview-selectorhighlighter-toggled"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + EventUtils.synthesizeKey("VK_RETURN", {}); + + let isVisible = yield onToggled; + + ok(!view.highlighters.selectorHighlighterShown, + "The selectorHighlighterShown instance was removed"); + ok(!isVisible, "The toggle event says the highlighter is not visible"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js new file mode 100644 index 000000000..cd996b4b0 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js @@ -0,0 +1,78 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that adding a new property of an unmatched rule works properly. + +const TEST_URI = ` + <style type="text/css"> + #testid { + } + .testclass { + background-color: white; + } + </style> + <div id="testid">Styled Node</div> + <span class="testclass">This is a span</span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Selecting the test element"); + yield selectNode("#testid", inspector); + yield testEditSelector(view, "span"); + yield testAddProperty(view); + + info("Selecting the modified element with the new rule"); + yield selectNode("span", inspector); + yield checkModifiedElement(view, "span"); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); + ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"), + "Rule with " + name + " does not match the current element."); + + // Escape the new property editor after editing the selector + let onBlur = once(view.styleDocument.activeElement, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onBlur; +} + +function* checkModifiedElement(view, name) { + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); +} + +function* testAddProperty(view) { + info("Test creating a new property"); + let textProp = yield addProperty(view, 1, "text-align", "center"); + + is(textProp.value, "center", "Text prop should have been changed."); + ok(!textProp.overridden, "Property should not be overridden"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js new file mode 100644 index 000000000..7d782a309 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js @@ -0,0 +1,76 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing selector inplace-editor behaviors in the rule-view with unmatched +// selectors + +const TEST_URI = ` + <style type="text/css"> + .testclass { + text-align: center; + } + div { + } + </style> + <div class="testclass">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".testclass", inspector); + yield testEditClassSelector(view); + yield testEditDivSelector(view); +}); + +function* testEditClassSelector(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + editor.input.value = "body"; + let onRuleViewChanged = once(view, "ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + // Get the new rule editor that replaced the original + ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + + info("Check that the correct rules are visible"); + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); + ok(ruleEditor.element.getAttribute("unmatched"), "Rule editor is unmatched."); + is(getRuleViewRule(view, ".testclass"), undefined, + "Rule with .testclass selector should not exist."); + ok(getRuleViewRule(view, "body"), + "Rule with body selector exists."); + is(inplaceEditor(propEditor.nameSpan), + inplaceEditor(view.styleDocument.activeElement), + "Focus should have moved to the property name."); +} + +function* testEditDivSelector(view) { + let ruleEditor = getRuleViewRuleEditor(view, 2); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + editor.input.value = "asdf"; + let onRuleViewChanged = once(view, "ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + // Get the new rule editor that replaced the original + ruleEditor = getRuleViewRuleEditor(view, 2); + + info("Check that the correct rules are visible"); + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); + ok(ruleEditor.element.getAttribute("unmatched"), "Rule editor is unmatched."); + is(getRuleViewRule(view, "div"), undefined, + "Rule with div selector should not exist."); + ok(getRuleViewRule(view, "asdf"), + "Rule with asdf selector exists."); + is(inplaceEditor(ruleEditor.newPropSpan), + inplaceEditor(view.styleDocument.activeElement), + "Focus should have moved to the property name."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js new file mode 100644 index 000000000..81c7aad72 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view overridden search filter does not appear for an +// unmatched rule. + +const TEST_URI = ` + <style type="text/css"> + div { + height: 0px; + } + #testid { + height: 1px; + } + .testclass { + height: 10px; + } + </style> + <div id="testid">Styled Node</div> + <span class="testclass">This is a span</span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield selectNode("#testid", inspector); + yield testEditSelector(view, "span"); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + + info("Entering the commit key"); + let onRuleViewChanged = once(view, "ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + // Get the new rule editor that replaced the original + ruleEditor = getRuleViewRuleEditor(view, 1); + let rule = ruleEditor.rule; + let textPropEditor = rule.textProps[0].editor; + + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); + ok(ruleEditor.element.getAttribute("unmatched"), + "Rule with " + name + " does not match the current element."); + ok(textPropEditor.filterProperty.hidden, "Overridden search is hidden."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js new file mode 100644 index 000000000..33382e0de --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js @@ -0,0 +1,71 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that reverting a selector edit does the right thing. +// Bug 1241046. + +const TEST_URI = ` + <style type="text/css"> + span { + color: chartreuse; + } + </style> + <span> + <div id="testid" class="testclass">Styled Node</div> + </span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Selecting the test element"); + yield selectNode("#testid", inspector); + + let idRuleEditor = getRuleViewRuleEditor(view, 2); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, idRuleEditor.selectorText); + + is(inplaceEditor(idRuleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = "pre"; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + info("Re-focusing the selector name in the rule-view"); + idRuleEditor = getRuleViewRuleEditor(view, 2); + editor = yield focusEditableField(view, idRuleEditor.selectorText); + + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, "pre"), "Rule with pre selector exists."); + is(getRuleViewRuleEditor(view, 2).element.getAttribute("unmatched"), + "true", + "Rule with pre does not match the current element."); + + // Now change it back. + info("Re-entering original selector name and committing"); + editor.input.value = "span"; + + info("Waiting for rule view to update"); + onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + ok(getRuleViewRule(view, "span"), "Rule with span selector exists."); + is(getRuleViewRuleEditor(view, 2).element.getAttribute("unmatched"), + "false", "Rule with span matches the current element."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js new file mode 100644 index 000000000..a18ddc5ef --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js @@ -0,0 +1,110 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that editing a selector to an unmatched rule does set up the correct +// property on the rule, and that settings property in said rule does not +// lead to overriding properties from matched rules. +// Test that having a rule with both matched and unmatched selectors does work +// correctly. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: black; + } + .testclass { + background-color: white; + } + </style> + <div id="testid">Styled Node</div> + <span class="testclass">This is a span</span> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield selectNode("#testid", inspector); + yield testEditSelector(view, "span"); + yield testAddImportantProperty(view); + yield testAddMatchedRule(view, "span, div"); +}); + +function* testEditSelector(view, name) { + info("Test editing existing selector fields"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists."); + ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"), + "Rule with " + name + " does not match the current element."); + + // Escape the new property editor after editing the selector + let onBlur = once(view.styleDocument.activeElement, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onBlur; +} + +function* testAddImportantProperty(view) { + info("Test creating a new property with !important"); + let textProp = yield addProperty(view, 1, "color", "red !important"); + + is(textProp.value, "red", "Text prop should have been changed."); + is(textProp.priority, "important", + "Text prop has an \"important\" priority."); + ok(!textProp.overridden, "Property should not be overridden"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + let prop = ruleEditor.rule.textProps[0]; + ok(!prop.overridden, + "Existing property on matched rule should not be overridden"); +} + +function* testAddMatchedRule(view, name) { + info("Test adding a matching selector"); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + is(inplaceEditor(ruleEditor.selectorText), editor, + "The selector editor got focused"); + + info("Entering a new selector name and committing"); + editor.input.value = name; + + info("Waiting for rule view to update"); + let onRuleViewChanged = once(view, "ruleview-changed"); + + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + is(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"), "false", + "Rule with " + name + " does match the current element."); + + // Escape the new property editor after editing the selector + let onBlur = once(view.styleDocument.activeElement, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onBlur; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js new file mode 100644 index 000000000..d878dd516 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js @@ -0,0 +1,64 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Regression test for bug 1293616: make sure that editing a selector +// keeps the rule in the proper position. + +const TEST_URI = ` + <style type="text/css"> + #testid span, #testid p { + background: aqua; + } + span { + background: fuchsia; + } + </style> + <div id="testid"> + <span class="pickme"> + Styled Node + </span> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".pickme", inspector); + yield testEditSelector(view); +}); + +function* testEditSelector(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + editor.input.value = "#testid span"; + let onRuleViewChanged = once(view, "ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + // Escape the new property editor after editing the selector + let onBlur = once(view.styleDocument.activeElement, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onBlur; + + // Get the new rule editor that replaced the original + ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Check that the correct rules are visible"); + is(view._elementStyle.rules.length, 3, "Should have 3 rules."); + is(ruleEditor.element.getAttribute("unmatched"), "false", "Rule editor is matched."); + + let props = ruleEditor.rule.textProps; + is(props.length, 1, "Rule has correct number of properties"); + is(props[0].name, "background", "Found background property"); + ok(!props[0].overridden, "Background property is not overridden"); + + ruleEditor = getRuleViewRuleEditor(view, 2); + props = ruleEditor.rule.textProps; + is(props.length, 1, "Rule has correct number of properties"); + is(props[0].name, "background", "Found background property"); + ok(props[0].overridden, "Background property is overridden"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js new file mode 100644 index 000000000..9a1bdc8fa --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js @@ -0,0 +1,69 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Regression test for bug 1293616, where editing a selector should +// change the relative priority of the rule. + +const TEST_URI = ` + <style type="text/css"> + #testid { + background: aqua; + } + .pickme { + background: seagreen; + } + span { + background: fuchsia; + } + </style> + <div> + <span id="testid" class="pickme"> + Styled Node + </span> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".pickme", inspector); + yield testEditSelector(view); +}); + +function* testEditSelector(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + editor.input.value = ".pickme"; + let onRuleViewChanged = once(view, "ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + // Escape the new property editor after editing the selector + let onBlur = once(view.styleDocument.activeElement, "blur"); + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + yield onBlur; + + // Get the new rule editor that replaced the original + ruleEditor = getRuleViewRuleEditor(view, 1); + + info("Check that the correct rules are visible"); + is(view._elementStyle.rules.length, 4, "Should have 4 rules."); + is(ruleEditor.element.getAttribute("unmatched"), "false", "Rule editor is matched."); + + let props = ruleEditor.rule.textProps; + is(props.length, 1, "Rule has correct number of properties"); + is(props[0].name, "background", "Found background property"); + is(props[0].value, "aqua", "Background property is aqua"); + ok(props[0].overridden, "Background property is overridden"); + + ruleEditor = getRuleViewRuleEditor(view, 2); + props = ruleEditor.rule.textProps; + is(props.length, 1, "Rule has correct number of properties"); + is(props[0].name, "background", "Found background property"); + is(props[0].value, "seagreen", "Background property is seagreen"); + ok(!props[0].overridden, "Background property is not overridden"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js new file mode 100644 index 000000000..dbf59cba9 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js @@ -0,0 +1,107 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that clicking on swatch-preceeded value while editing the property name +// will result in editing the property value. Also tests that the value span is updated +// only if the property name has changed. See also Bug 1248274. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: red; + } + </style> + <div id="testid">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield selectNode("#testid", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + + yield testColorValueSpanClickWithoutNameChange(propEditor, view); + yield testColorValueSpanClickAfterNameChange(propEditor, view); +}); + +function* testColorValueSpanClickWithoutNameChange(propEditor, view) { + info("Test click on color span while focusing property name editor"); + let colorSpan = propEditor.valueSpan.querySelector(".ruleview-color"); + + info("Focus the color name span"); + yield focusEditableField(view, propEditor.nameSpan); + let editor = inplaceEditor(propEditor.doc.activeElement); + + // We add a click event to make sure the color span won't be cleared + // on nameSpan blur (which would lead to the click event not being triggered) + let onColorSpanClick = once(colorSpan, "click"); + + // The property-value-updated is emitted when the valueSpan markup is being + // re-populated, which should not be the case when not modifying the property name + let onPropertyValueUpdated = function () { + ok(false, "The \"property-value-updated\" should not be emitted"); + }; + view.on("property-value-updated", onPropertyValueUpdated); + + info("blur propEditor.nameSpan by clicking on the color span"); + EventUtils.synthesizeMouse(colorSpan, 1, 1, {}, propEditor.doc.defaultView); + + info("wait for the click event on the color span"); + yield onColorSpanClick; + ok(true, "Expected click event was emitted"); + + editor = inplaceEditor(propEditor.doc.activeElement); + is(inplaceEditor(propEditor.valueSpan), editor, + "The property value editor got focused"); + + // We remove this listener in order to not cause unwanted conflict in the next test + view.off("property-value-updated", onPropertyValueUpdated); + + info("blur valueSpan editor to trigger ruleview-changed event and prevent " + + "having pending request"); + let onRuleViewChanged = view.once("ruleview-changed"); + editor.input.blur(); + yield onRuleViewChanged; +} + +function* testColorValueSpanClickAfterNameChange(propEditor, view) { + info("Test click on color span after property name change"); + let colorSpan = propEditor.valueSpan.querySelector(".ruleview-color"); + + info("Focus the color name span"); + yield focusEditableField(view, propEditor.nameSpan); + let editor = inplaceEditor(propEditor.doc.activeElement); + + info("Modify the property to border-color to trigger the " + + "property-value-updated event"); + editor.input.value = "border-color"; + + let onRuleViewChanged = view.once("ruleview-changed"); + let onPropertyValueUpdate = view.once("property-value-updated"); + + info("blur propEditor.nameSpan by clicking on the color span"); + EventUtils.synthesizeMouse(colorSpan, 1, 1, {}, propEditor.doc.defaultView); + + info("wait for ruleview-changed event to be triggered to prevent pending requests"); + yield onRuleViewChanged; + + info("wait for the property value to be updated"); + yield onPropertyValueUpdate; + ok(true, "Expected \"property-value-updated\" event was emitted"); + + editor = inplaceEditor(propEditor.doc.activeElement); + is(inplaceEditor(propEditor.valueSpan), editor, + "The property value editor got focused"); + + info("blur valueSpan editor to trigger ruleview-changed event and prevent " + + "having pending request"); + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.blur(); + yield onRuleViewChanged; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js new file mode 100644 index 000000000..372ed7477 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js @@ -0,0 +1,65 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that hitting shift + click on color swatch while editing the property +// name will only change the color unit and not lead to edit the property value. +// See also Bug 1248274. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: red; + background: linear-gradient( + 90deg, + rgb(183,222,237), + rgb(33,180,226), + rgb(31,170,217), + rgba(200,170,140,0.5)); + } + </style> + <div id="testid">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Test shift + click on color swatch while editing property name"); + + yield selectNode("#testid", inspector); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[1].editor; + let swatchSpan = propEditor.valueSpan.querySelectorAll(".ruleview-colorswatch")[2]; + + info("Focus the background name span"); + yield focusEditableField(view, propEditor.nameSpan); + let editor = inplaceEditor(propEditor.doc.activeElement); + + info("Modify the property to background-image to trigger the " + + "property-value-updated event"); + editor.input.value = "background-image"; + + let onPropertyValueUpdate = view.once("property-value-updated"); + let onSwatchUnitChange = swatchSpan.once("unit-change"); + let onRuleViewChanged = view.once("ruleview-changed"); + + info("blur propEditor.nameSpan by clicking on the color swatch"); + EventUtils.synthesizeMouseAtCenter(swatchSpan, {shiftKey: true}, + propEditor.doc.defaultView); + + info("wait for ruleview-changed event to be triggered to prevent pending requests"); + yield onRuleViewChanged; + + info("wait for the color unit to change"); + yield onSwatchUnitChange; + ok(true, "the color unit was changed"); + + info("wait for the property value to be updated"); + yield onPropertyValueUpdate; + + ok(!inplaceEditor(propEditor.valueSpan), "The inplace editor wasn't shown " + + "as a result of the color swatch shift + click"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js new file mode 100644 index 000000000..041a45a3e --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js @@ -0,0 +1,69 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that clicking on color swatch while editing the property name +// will show the color tooltip with the correct value. See also Bug 1248274. + +const TEST_URI = ` + <style type="text/css"> + #testid { + color: red; + background: linear-gradient( + 90deg, + rgb(183,222,237), + rgb(33,180,226), + rgb(31,170,217), + rgba(200,170,140,0.5)); + } + </style> + <div id="testid">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Test click on color swatch while editing property name"); + + yield selectNode("#testid", inspector); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[1].editor; + let swatchSpan = propEditor.valueSpan.querySelectorAll( + ".ruleview-colorswatch")[3]; + let colorPicker = view.tooltips.colorPicker; + + info("Focus the background name span"); + yield focusEditableField(view, propEditor.nameSpan); + let editor = inplaceEditor(propEditor.doc.activeElement); + + info("Modify the background property to background-image to trigger the " + + "property-value-updated event"); + editor.input.value = "background-image"; + + let onRuleViewChanged = view.once("ruleview-changed"); + let onPropertyValueUpdate = view.once("property-value-updated"); + let onReady = colorPicker.once("ready"); + + info("blur propEditor.nameSpan by clicking on the color swatch"); + EventUtils.synthesizeMouseAtCenter(swatchSpan, {}, + propEditor.doc.defaultView); + + info("wait for ruleview-changed event to be triggered to prevent pending requests"); + yield onRuleViewChanged; + + info("wait for the property value to be updated"); + yield onPropertyValueUpdate; + + info("wait for the color picker to be shown"); + yield onReady; + + ok(true, "The color picker was shown on click of the color swatch"); + ok(!inplaceEditor(propEditor.valueSpan), + "The inplace editor wasn't shown as a result of the color swatch click"); + + let spectrum = colorPicker.spectrum; + is(spectrum.rgb, "200,170,140,0.5", "The correct color picker was shown"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js new file mode 100644 index 000000000..fa4d8e6e2 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js @@ -0,0 +1,62 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that clicking on a property's value URL while editing the property name +// will open the link in a new tab. See also Bug 1248274. + +const TEST_URI = ` + <style type="text/css"> + #testid { + background: url("chrome://global/skin/icons/warning-64.png"), linear-gradient(white, #F06 400px); + } + </style> + <div id="testid">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Test click on background-image url while editing property name"); + + yield selectNode("#testid", inspector); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + let anchor = propEditor.valueSpan.querySelector(".ruleview-propertyvalue .theme-link"); + + info("Focus the background name span"); + yield focusEditableField(view, propEditor.nameSpan); + let editor = inplaceEditor(propEditor.doc.activeElement); + + info("Modify the property to background to trigger the " + + "property-value-updated event"); + editor.input.value = "background-image"; + + let onRuleViewChanged = view.once("ruleview-changed"); + let onPropertyValueUpdate = view.once("property-value-updated"); + let onTabOpened = waitForTab(); + + info("blur propEditor.nameSpan by clicking on the link"); + // The url can be wrapped across multiple lines, and so we click the lower left corner + // of the anchor to make sure to target the link. + let rect = anchor.getBoundingClientRect(); + EventUtils.synthesizeMouse(anchor, 2, rect.height - 2, {}, propEditor.doc.defaultView); + + info("wait for ruleview-changed event to be triggered to prevent pending requests"); + yield onRuleViewChanged; + + info("wait for the property value to be updated"); + yield onPropertyValueUpdate; + + info("wait for the image to be open in a new tab"); + let tab = yield onTabOpened; + ok(true, "A new tab opened"); + + is(tab.linkedBrowser.currentURI.spec, anchor.href, + "The URL for the new tab is correct"); + + gBrowser.removeTab(tab); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js new file mode 100644 index 000000000..c9c7cd3d2 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js @@ -0,0 +1,94 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the correct editable fields are focused when tabbing and entering +// through the rule view. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + color: red; + margin: 0; + padding: 0; + } + div { + border-color: red + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testEditableFieldFocus(inspector, view, "VK_RETURN"); + yield testEditableFieldFocus(inspector, view, "VK_TAB"); +}); + +function* testEditableFieldFocus(inspector, view, commitKey) { + info("Click on the selector of the inline style ('element')"); + let ruleEditor = getRuleViewRuleEditor(view, 0); + let onFocus = once(ruleEditor.element, "focus", true); + ruleEditor.selectorText.click(); + yield onFocus; + assertEditor(view, ruleEditor.newPropSpan, + "Focus should be in the element property span"); + + info("Focus the next field with " + commitKey); + ruleEditor = getRuleViewRuleEditor(view, 1); + yield focusNextEditableField(view, ruleEditor, commitKey); + assertEditor(view, ruleEditor.selectorText, + "Focus should have moved to the next rule selector"); + + for (let i = 0; i < ruleEditor.rule.textProps.length; i++) { + let textProp = ruleEditor.rule.textProps[i]; + let propEditor = textProp.editor; + + info("Focus the next field with " + commitKey); + // Expect a ruleview-changed event if we are moving from a property value + // to the next property name (which occurs after the first iteration, as for + // i=0, the previous field is the selector). + let onRuleViewChanged = i > 0 ? view.once("ruleview-changed") : null; + yield focusNextEditableField(view, ruleEditor, commitKey); + yield onRuleViewChanged; + assertEditor(view, propEditor.nameSpan, + "Focus should have moved to the property name"); + + info("Focus the next field with " + commitKey); + yield focusNextEditableField(view, ruleEditor, commitKey); + assertEditor(view, propEditor.valueSpan, + "Focus should have moved to the property value"); + } + + // Expect a ruleview-changed event again as we're bluring a property value. + let onRuleViewChanged = view.once("ruleview-changed"); + yield focusNextEditableField(view, ruleEditor, commitKey); + yield onRuleViewChanged; + assertEditor(view, ruleEditor.newPropSpan, + "Focus should have moved to the new property span"); + + ruleEditor = getRuleViewRuleEditor(view, 2); + + yield focusNextEditableField(view, ruleEditor, commitKey); + assertEditor(view, ruleEditor.selectorText, + "Focus should have moved to the next rule selector"); + + info("Blur the selector field"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); +} + +function* focusNextEditableField(view, ruleEditor, commitKey) { + let onFocus = once(ruleEditor.element, "focus", true); + EventUtils.synthesizeKey(commitKey, {}, view.styleWindow); + yield onFocus; +} + +function assertEditor(view, element, message) { + let editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(element), editor, message); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js new file mode 100644 index 000000000..13ad221f0 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js @@ -0,0 +1,84 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the correct editable fields are focused when shift tabbing +// through the rule view. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + color: red; + margin: 0; + padding: 0; + } + div { + border-color: red + } + </style> + <div id='testid'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testEditableFieldFocus(inspector, view, "VK_TAB", { shiftKey: true }); +}); + +function* testEditableFieldFocus(inspector, view, commitKey, options = {}) { + let ruleEditor = getRuleViewRuleEditor(view, 2); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + is(inplaceEditor(ruleEditor.selectorText), editor, + "Focus should be in the 'div' rule selector"); + + ruleEditor = getRuleViewRuleEditor(view, 1); + + yield focusNextField(view, ruleEditor, commitKey, options); + assertEditor(view, ruleEditor.newPropSpan, + "Focus should have moved to the new property span"); + + for (let textProp of ruleEditor.rule.textProps.slice(0).reverse()) { + let propEditor = textProp.editor; + + yield focusNextField(view, ruleEditor, commitKey, options); + yield assertEditor(view, propEditor.valueSpan, + "Focus should have moved to the property value"); + + yield focusNextFieldAndExpectChange(view, ruleEditor, commitKey, options); + yield assertEditor(view, propEditor.nameSpan, + "Focus should have moved to the property name"); + } + + ruleEditor = getRuleViewRuleEditor(view, 1); + + yield focusNextField(view, ruleEditor, commitKey, options); + yield assertEditor(view, ruleEditor.selectorText, + "Focus should have moved to the '#testid' rule selector"); + + ruleEditor = getRuleViewRuleEditor(view, 0); + + yield focusNextField(view, ruleEditor, commitKey, options); + assertEditor(view, ruleEditor.newPropSpan, + "Focus should have moved to the new property span"); +} + +function* focusNextFieldAndExpectChange(view, ruleEditor, commitKey, options) { + let onRuleViewChanged = view.once("ruleview-changed"); + yield focusNextField(view, ruleEditor, commitKey, options); + yield onRuleViewChanged; +} + +function* focusNextField(view, ruleEditor, commitKey, options) { + let onFocus = once(ruleEditor.element, "focus", true); + EventUtils.synthesizeKey(commitKey, options, view.styleWindow); + yield onFocus; +} + +function* assertEditor(view, element, message) { + let editor = inplaceEditor(view.styleDocument.activeElement); + is(inplaceEditor(element), editor, message); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_eyedropper.js b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js new file mode 100644 index 000000000..0762066e3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js @@ -0,0 +1,123 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test opening the eyedropper from the color picker. Pressing escape to close it, and +// clicking the page to select a color. + +const TEST_URI = ` + <style type="text/css"> + body { + background-color: white; + padding: 0px + } + + #div1 { + background-color: #ff5; + width: 20px; + height: 20px; + } + + #div2 { + margin-left: 20px; + width: 20px; + height: 20px; + background-color: #f09; + } + </style> + <body><div id="div1"></div><div id="div2"></div></body> +`; + +// #f09 +const ORIGINAL_COLOR = "rgb(255, 0, 153)"; +// #ff5 +const EXPECTED_COLOR = "rgb(255, 255, 85)"; + +add_task(function* () { + info("Add the test tab, open the rule-view and select the test node"); + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {testActor, inspector, view} = yield openRuleView(); + yield selectNode("#div2", inspector); + + info("Get the background-color property from the rule-view"); + let property = getRuleViewProperty(view, "#div2", "background-color"); + let swatch = property.valueSpan.querySelector(".ruleview-colorswatch"); + ok(swatch, "Color swatch is displayed for the bg-color property"); + + info("Open the eyedropper from the colorpicker tooltip"); + yield openEyedropper(view, swatch); + + let tooltip = view.tooltips.colorPicker.tooltip; + ok(!tooltip.isVisible(), "color picker tooltip is closed after opening eyedropper"); + + info("Test that pressing escape dismisses the eyedropper"); + yield testESC(swatch, inspector, testActor); + + info("Open the eyedropper again"); + yield openEyedropper(view, swatch); + + info("Test that a color can be selected with the eyedropper"); + yield testSelect(view, swatch, inspector, testActor); + + let onHidden = tooltip.once("hidden"); + tooltip.hide(); + yield onHidden; + ok(!tooltip.isVisible(), "color picker tooltip is closed"); + + yield waitForTick(); +}); + +function* testESC(swatch, inspector, testActor) { + info("Press escape"); + let onCanceled = new Promise(resolve => { + inspector.inspector.once("color-pick-canceled", resolve); + }); + yield testActor.synthesizeKey({key: "VK_ESCAPE", options: {}}); + yield onCanceled; + + let color = swatch.style.backgroundColor; + is(color, ORIGINAL_COLOR, "swatch didn't change after pressing ESC"); +} + +function* testSelect(view, swatch, inspector, testActor) { + info("Click at x:10px y:10px"); + let onPicked = new Promise(resolve => { + inspector.inspector.once("color-picked", resolve); + }); + // The change to the content is done async after rule view change + let onRuleViewChanged = view.once("ruleview-changed"); + + yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10, + options: {type: "mousemove"}}); + yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10, + options: {type: "mousedown"}}); + yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10, + options: {type: "mouseup"}}); + + yield onPicked; + yield onRuleViewChanged; + + let color = swatch.style.backgroundColor; + is(color, EXPECTED_COLOR, "swatch changed colors"); + + is((yield getComputedStyleProperty("div", null, "background-color")), + EXPECTED_COLOR, + "div's color set to body color after dropper"); +} + +function* openEyedropper(view, swatch) { + let tooltip = view.tooltips.colorPicker.tooltip; + + info("Click on the swatch"); + let onColorPickerReady = view.tooltips.colorPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + let dropperButton = tooltip.doc.querySelector("#eyedropper-button"); + + info("Click on the eyedropper icon"); + let onOpened = tooltip.once("eyedropper-opened"); + dropperButton.click(); + yield onOpened; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js b/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js new file mode 100644 index 000000000..21eeebb36 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the that Filter Editor Tooltip opens by clicking on filter swatches + +const TEST_URL = URL_ROOT + "doc_filter.html"; + +add_task(function* () { + yield addTab(TEST_URL); + + let {view} = yield openRuleView(); + + info("Getting the filter swatch element"); + let swatch = getRuleViewProperty(view, "body", "filter").valueSpan + .querySelector(".ruleview-filterswatch"); + + let filterTooltip = view.tooltips.filterEditor; + // Clicking on a cssfilter swatch sets the current filter value in the tooltip + // which, in turn, makes the FilterWidget emit an "updated" event that causes + // the rule-view to refresh. So we must wait for the ruleview-changed event. + let onRuleViewChanged = view.once("ruleview-changed"); + swatch.click(); + yield onRuleViewChanged; + + ok(true, "The shown event was emitted after clicking on swatch"); + ok(!inplaceEditor(swatch.parentNode), + "The inplace editor wasn't shown as a result of the filter swatch click"); + + yield hideTooltipAndWaitForRuleViewChanged(filterTooltip, view); + + yield waitForTick(); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js b/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js new file mode 100644 index 000000000..127a20843 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the Filter Editor Tooltip committing changes on ENTER + +const TEST_URL = URL_ROOT + "doc_filter.html"; + +add_task(function* () { + yield addTab(TEST_URL); + let {view} = yield openRuleView(); + + info("Get the filter swatch element"); + let swatch = getRuleViewProperty(view, "body", "filter").valueSpan + .querySelector(".ruleview-filterswatch"); + + info("Click on the filter swatch element"); + // Clicking on a cssfilter swatch sets the current filter value in the tooltip + // which, in turn, makes the FilterWidget emit an "updated" event that causes + // the rule-view to refresh. So we must wait for the ruleview-changed event. + let onRuleViewChanged = view.once("ruleview-changed"); + swatch.click(); + yield onRuleViewChanged; + + info("Get the cssfilter widget instance"); + let filterTooltip = view.tooltips.filterEditor; + let widget = filterTooltip.widget; + + info("Set a new value in the cssfilter widget"); + onRuleViewChanged = view.once("ruleview-changed"); + widget.setCssValue("blur(2px)"); + yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)"); + yield onRuleViewChanged; + ok(true, "Changes previewed on the element"); + + info("Press RETURN to commit changes"); + // Pressing return in the cssfilter tooltip triggeres 2 ruleview-changed + onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2); + EventUtils.sendKey("RETURN", widget.styleWindow); + yield onRuleViewChanged; + + is((yield getComputedStyleProperty("body", null, "filter")), "blur(2px)", + "The elemenet's filter was kept after RETURN"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js b/devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js new file mode 100644 index 000000000..0302f40a9 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that changes made to the Filter Editor Tooltip are reverted when +// ESC is pressed + +const TEST_URL = URL_ROOT + "doc_filter.html"; + +add_task(function* () { + yield addTab(TEST_URL); + let {view} = yield openRuleView(); + yield testPressingEscapeRevertsChanges(view); + yield testPressingEscapeRevertsChangesAndDisables(view); +}); + +function* testPressingEscapeRevertsChanges(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch"); + + yield clickOnFilterSwatch(swatch, view); + yield setValueInFilterWidget("blur(2px)", view); + + yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)"); + is(propEditor.valueSpan.textContent, "blur(2px)", + "Got expected property value."); + + yield pressEscapeToCloseTooltip(view); + + yield waitForComputedStyleProperty("body", null, "filter", + "blur(2px) contrast(2)"); + is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)", + "Got expected property value."); +} + +function* testPressingEscapeRevertsChangesAndDisables(view) { + let ruleEditor = getRuleViewRuleEditor(view, 1); + let propEditor = ruleEditor.rule.textProps[0].editor; + + info("Disabling filter property"); + let onRuleViewChanged = view.once("ruleview-changed"); + propEditor.enable.click(); + yield onRuleViewChanged; + + ok(propEditor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(propEditor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!propEditor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + ok(!propEditor.prop.enabled, + "filter property is disabled."); + let newValue = yield getRulePropertyValue("filter"); + is(newValue, "", "filter should have been unset."); + + let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch"); + yield clickOnFilterSwatch(swatch, view); + + ok(!propEditor.element.classList.contains("ruleview-overridden"), + "property overridden is not displayed."); + is(propEditor.enable.style.visibility, "hidden", + "property enable checkbox is hidden."); + + yield setValueInFilterWidget("blur(2px)", view); + yield pressEscapeToCloseTooltip(view); + + ok(propEditor.element.classList.contains("ruleview-overridden"), + "property is overridden."); + is(propEditor.enable.style.visibility, "visible", + "property enable checkbox is visible."); + ok(!propEditor.enable.getAttribute("checked"), + "property enable checkbox is not checked."); + ok(!propEditor.prop.enabled, "filter property is disabled."); + newValue = yield getRulePropertyValue("filter"); + is(newValue, "", "filter should have been unset."); + is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)", + "Got expected property value."); +} + +function* getRulePropertyValue(name) { + let propValue = yield executeInContent("Test:GetRulePropertyValue", { + styleSheetIndex: 0, + ruleIndex: 0, + name: name + }); + return propValue; +} + +function* clickOnFilterSwatch(swatch, view) { + info("Clicking on a css filter swatch to open the tooltip"); + + // Clicking on a cssfilter swatch sets the current filter value in the tooltip + // which, in turn, makes the FilterWidget emit an "updated" event that causes + // the rule-view to refresh. So we must wait for the ruleview-changed event. + let onRuleViewChanged = view.once("ruleview-changed"); + swatch.click(); + yield onRuleViewChanged; +} + +function* setValueInFilterWidget(value, view) { + info("Setting the CSS filter value in the tooltip"); + + let filterTooltip = view.tooltips.filterEditor; + let onRuleViewChanged = view.once("ruleview-changed"); + filterTooltip.widget.setCssValue(value); + yield onRuleViewChanged; +} + +function* pressEscapeToCloseTooltip(view) { + info("Pressing ESCAPE to close the tooltip"); + + let filterTooltip = view.tooltips.filterEditor; + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.sendKey("ESCAPE", filterTooltip.widget.styleWindow); + yield onRuleViewChanged; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js new file mode 100644 index 000000000..617eb00da --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js @@ -0,0 +1,41 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that grid highlighter is hidden on page navigation. + +const TEST_URI = ` + <style type='text/css'> + #grid { + display: grid; + } + </style> + <div id="grid"> + <div id="cell1">cell1</div> + <div id="cell2">cell2</div> + </div> +`; + +const TEST_URI_2 = "data:text/html,<html><body>test</body></html>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let highlighters = view.highlighters; + + yield selectNode("#grid", inspector); + let container = getRuleViewProperty(view, "#grid", "display").valueSpan; + let gridToggle = container.querySelector(".ruleview-grid"); + + info("Toggling ON the CSS grid highlighter from the rule-view."); + let onHighlighterShown = highlighters.once("highlighter-shown"); + gridToggle.click(); + yield onHighlighterShown; + + ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown."); + + yield navigateTo(inspector, TEST_URI_2); + ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js new file mode 100644 index 000000000..a6780a94a --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js @@ -0,0 +1,53 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that a grid highlighter showing grid gaps can be displayed after reloading the +// page (Bug 1342051). + +const TEST_URI = ` + <style type='text/css'> + #grid { + display: grid; + grid-gap: 10px; + } + </style> + <div id="grid"> + <div id="cell1">cell1</div> + <div id="cell2">cell2</div> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + + info("Check that the grid highlighter can be displayed"); + yield checkGridHighlighter(); + + info("Close the toolbox before reloading the tab"); + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.closeToolbox(target); + + yield refreshTab(gBrowser.selectedTab); + + info("Check that the grid highlighter can be displayed after reloading the page"); + yield checkGridHighlighter(); +}); + +function* checkGridHighlighter() { + let {inspector, view} = yield openRuleView(); + let {highlighters} = view; + + yield selectNode("#grid", inspector); + let container = getRuleViewProperty(view, "#grid", "display").valueSpan; + let gridToggle = container.querySelector(".ruleview-grid"); + + info("Toggling ON the CSS grid highlighter from the rule-view."); + let onHighlighterShown = highlighters.once("highlighter-shown"); + gridToggle.click(); + yield onHighlighterShown; + + ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js new file mode 100644 index 000000000..04534522b --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js @@ -0,0 +1,64 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test toggling the grid highlighter in the rule view and the display of the +// grid highlighter. + +const TEST_URI = ` + <style type='text/css'> + #grid { + display: grid; + } + </style> + <div id="grid"> + <div id="cell1">cell1</div> + <div id="cell2">cell2</div> + </div> +`; + +const HIGHLIGHTER_TYPE = "CssGridHighlighter"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let highlighters = view.highlighters; + + yield selectNode("#grid", inspector); + let container = getRuleViewProperty(view, "#grid", "display").valueSpan; + let gridToggle = container.querySelector(".ruleview-grid"); + + info("Checking the initial state of the CSS grid toggle in the rule-view."); + ok(gridToggle, "Grid highlighter toggle is visible."); + ok(!gridToggle.classList.contains("active"), + "Grid highlighter toggle button is not active."); + ok(!highlighters.highlighters[HIGHLIGHTER_TYPE], + "No CSS grid highlighter exists in the rule-view."); + ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown."); + + info("Toggling ON the CSS grid highlighter from the rule-view."); + let onHighlighterShown = highlighters.once("highlighter-shown"); + gridToggle.click(); + yield onHighlighterShown; + + info("Checking the CSS grid highlighter is created and toggle button is active in " + + "the rule-view."); + ok(gridToggle.classList.contains("active"), + "Grid highlighter toggle is active."); + ok(highlighters.highlighters[HIGHLIGHTER_TYPE], + "CSS grid highlighter created in the rule-view."); + ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown."); + + info("Toggling OFF the CSS grid highlighter from the rule-view."); + let onHighlighterHidden = highlighters.once("highlighter-hidden"); + gridToggle.click(); + yield onHighlighterHidden; + + info("Checking the CSS grid highlighter is not shown and toggle button is not active " + + "in the rule-view."); + ok(!gridToggle.classList.contains("active"), + "Grid highlighter toggle button is not active."); + ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js new file mode 100644 index 000000000..5c339e892 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js @@ -0,0 +1,73 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test toggling the grid highlighter in the rule view from an overridden 'display: grid' +// declaration. + +const TEST_URI = ` + <style type='text/css'> + #grid { + display: grid; + } + div, ul { + display: grid; + } + </style> + <ul id="grid"> + <li id="cell1">cell1</li> + <li id="cell2">cell2</li> + </ul> +`; + +const HIGHLIGHTER_TYPE = "CssGridHighlighter"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let highlighters = view.highlighters; + + yield selectNode("#grid", inspector); + let container = getRuleViewProperty(view, "#grid", "display").valueSpan; + let gridToggle = container.querySelector(".ruleview-grid"); + let overriddenContainer = getRuleViewProperty(view, "div, ul", "display").valueSpan; + let overriddenGridToggle = overriddenContainer.querySelector(".ruleview-grid"); + + info("Checking the initial state of the CSS grid toggle in the rule-view."); + ok(gridToggle && overriddenGridToggle, "Grid highlighter toggles are visible."); + ok(!gridToggle.classList.contains("active") && + !overriddenGridToggle.classList.contains("active"), + "Grid highlighter toggle buttons are not active."); + ok(!highlighters.highlighters[HIGHLIGHTER_TYPE], + "No CSS grid highlighter exists in the rule-view."); + ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown."); + + info("Toggling ON the CSS grid highlighter from the overridden rule in the rule-view."); + let onHighlighterShown = highlighters.once("highlighter-shown"); + overriddenGridToggle.click(); + yield onHighlighterShown; + + info("Checking the CSS grid highlighter is created and toggle buttons are active in " + + "the rule-view."); + ok(gridToggle.classList.contains("active") && + overriddenGridToggle.classList.contains("active"), + "Grid highlighter toggle is active."); + ok(highlighters.highlighters[HIGHLIGHTER_TYPE], + "CSS grid highlighter created in the rule-view."); + ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown."); + + info("Toggling off the CSS grid highlighter from the normal grid declaration in the " + + "rule-view."); + let onHighlighterHidden = highlighters.once("highlighter-hidden"); + gridToggle.click(); + yield onHighlighterHidden; + + info("Checking the CSS grid highlighter is not shown and toggle buttons are not " + + "active in the rule-view."); + ok(!gridToggle.classList.contains("active") && + !overriddenGridToggle.classList.contains("active"), + "Grid highlighter toggle buttons are not active."); + ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js new file mode 100644 index 000000000..a908d6a97 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js @@ -0,0 +1,96 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test toggling the grid highlighter in the rule view with multiple grids in the page. + +const TEST_URI = ` + <style type='text/css'> + .grid { + display: grid; + } + </style> + <div id="grid1" class="grid"> + <div class="cell1">cell1</div> + <div class="cell2">cell2</div> + </div> + <div id="grid2" class="grid"> + <div class="cell1">cell1</div> + <div class="cell2">cell2</div> + </div> +`; + +const HIGHLIGHTER_TYPE = "CssGridHighlighter"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let highlighters = view.highlighters; + + info("Selecting the first grid container."); + yield selectNode("#grid1", inspector); + let container = getRuleViewProperty(view, ".grid", "display").valueSpan; + let gridToggle = container.querySelector(".ruleview-grid"); + + info("Checking the state of the CSS grid toggle for the first grid container in the " + + "rule-view."); + ok(gridToggle, "Grid highlighter toggle is visible."); + ok(!gridToggle.classList.contains("active"), + "Grid highlighter toggle button is not active."); + ok(!highlighters.highlighters[HIGHLIGHTER_TYPE], + "No CSS grid highlighter exists in the rule-view."); + ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown."); + + info("Toggling ON the CSS grid highlighter for the first grid container from the " + + "rule-view."); + let onHighlighterShown = highlighters.once("highlighter-shown"); + gridToggle.click(); + yield onHighlighterShown; + + info("Checking the CSS grid highlighter is created and toggle button is active in " + + "the rule-view."); + ok(gridToggle.classList.contains("active"), + "Grid highlighter toggle is active."); + ok(highlighters.highlighters[HIGHLIGHTER_TYPE], + "CSS grid highlighter created in the rule-view."); + ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown."); + + info("Selecting the second grid container."); + yield selectNode("#grid2", inspector); + let firstGridHighterShown = highlighters.gridHighlighterShown; + container = getRuleViewProperty(view, ".grid", "display").valueSpan; + gridToggle = container.querySelector(".ruleview-grid"); + + info("Checking the state of the CSS grid toggle for the second grid container in the " + + "rule-view."); + ok(gridToggle, "Grid highlighter toggle is visible."); + ok(!gridToggle.classList.contains("active"), + "Grid highlighter toggle button is not active."); + ok(highlighters.gridHighlighterShown, "CSS grid highlighter is still shown."); + + info("Toggling ON the CSS grid highlighter for the second grid container from the " + + "rule-view."); + onHighlighterShown = highlighters.once("highlighter-shown"); + gridToggle.click(); + yield onHighlighterShown; + + info("Checking the CSS grid highlighter is created for the second grid container and " + + "toggle button is active in the rule-view."); + ok(gridToggle.classList.contains("active"), + "Grid highlighter toggle is active."); + ok(highlighters.gridHighlighterShown != firstGridHighterShown, + "Grid highlighter for the second grid container is shown."); + + info("Selecting the first grid container."); + yield selectNode("#grid1", inspector); + container = getRuleViewProperty(view, ".grid", "display").valueSpan; + gridToggle = container.querySelector(".ruleview-grid"); + + info("Checking the state of the CSS grid toggle for the first grid container in the " + + "rule-view."); + ok(gridToggle, "Grid highlighter toggle is visible."); + ok(!gridToggle.classList.contains("active"), + "Grid highlighter toggle button is not active."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js b/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js new file mode 100644 index 000000000..ba2a1d7fb --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that we can guess indentation from a style sheet, not just a +// rule. + +// Use a weird indentation depth to avoid accidental success. +const TEST_URI = ` + <style type='text/css'> +div { + background-color: blue; +} + +* { +} +</style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +const expectedText = ` +div { + background-color: blue; +} + +* { + color: chartreuse; +} +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Add a new property in the rule-view"); + yield addProperty(view, 2, "color", "chartreuse"); + + info("Switch to the style-editor"); + let { UI } = yield toolbox.selectTool("styleeditor"); + + let styleEditor = yield UI.editors[0].getSourceEditor(); + let text = styleEditor.sourceEditor.getText(); + is(text, expectedText, "style inspector changes are synced"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js new file mode 100644 index 000000000..d1f6d7f45 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that inherited properties appear for a nested element in the +// rule view. + +const TEST_URI = ` + <style type="text/css"> + #test2 { + background-color: green; + color: purple; + } + </style> + <div id="test2"><div id="test1">Styled Node</div></div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#test1", inspector); + yield simpleInherit(inspector, view); +}); + +function* simpleInherit(inspector, view) { + let elementStyle = view._elementStyle; + is(elementStyle.rules.length, 2, "Should have 2 rules."); + + let elementRule = elementStyle.rules[0]; + ok(!elementRule.inherited, + "Element style attribute should not consider itself inherited."); + + let inheritRule = elementStyle.rules[1]; + is(inheritRule.selectorText, "#test2", + "Inherited rule should be the one that includes inheritable properties."); + ok(!!inheritRule.inherited, "Rule should consider itself inherited."); + is(inheritRule.textProps.length, 2, + "Rule should have two styles"); + let bgcProp = inheritRule.textProps[0]; + is(bgcProp.name, "background-color", + "background-color property should exist"); + ok(bgcProp.invisible, "background-color property should be invisible"); + let inheritProp = inheritRule.textProps[1]; + is(inheritProp.name, "color", "color should have been inherited."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js new file mode 100644 index 000000000..db9662eee --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js @@ -0,0 +1,34 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that no inherited properties appear when the property does not apply +// to the nested element. + +const TEST_URI = ` + <style type="text/css"> + #test2 { + background-color: green; + } + </style> + <div id="test2"><div id="test1">Styled Node</div></div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#test1", inspector); + yield emptyInherit(inspector, view); +}); + +function* emptyInherit(inspector, view) { + // No inheritable styles, this rule shouldn't show up. + let elementStyle = view._elementStyle; + is(elementStyle.rules.length, 1, "Should have 1 rule."); + + let elementRule = elementStyle.rules[0]; + ok(!elementRule.inherited, + "Element style attribute should not consider itself inherited."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js new file mode 100644 index 000000000..d6075f6f4 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js @@ -0,0 +1,40 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that inline inherited properties appear in the nested element. + +var {ELEMENT_STYLE} = require("devtools/shared/specs/styles"); + +const TEST_URI = ` + <div id="test2" style="color: red"> + <div id="test1">Styled Node</div> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#test1", inspector); + yield elementStyleInherit(inspector, view); +}); + +function* elementStyleInherit(inspector, view) { + let elementStyle = view._elementStyle; + is(elementStyle.rules.length, 2, "Should have 2 rules."); + + let elementRule = elementStyle.rules[0]; + ok(!elementRule.inherited, + "Element style attribute should not consider itself inherited."); + + let inheritRule = elementStyle.rules[1]; + is(inheritRule.domRule.type, ELEMENT_STYLE, + "Inherited rule should be an element style, not a rule."); + ok(!!inheritRule.inherited, "Rule should consider itself inherited."); + is(inheritRule.textProps.length, 1, + "Should only display one inherited style"); + let inheritProp = inheritRule.textProps[0]; + is(inheritProp.name, "color", "color should have been inherited."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_inline-source-map.js b/devtools/client/inspector/rules/test/browser_rules_inline-source-map.js new file mode 100644 index 000000000..05109d8c6 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_inline-source-map.js @@ -0,0 +1,26 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that when a source map comment appears in an inline stylesheet, the +// rule-view still appears correctly. +// Bug 1255787. + +const TESTCASE_URI = URL_ROOT + "doc_inline_sourcemap.html"; +const PREF = "devtools.styleeditor.source-maps-enabled"; + +add_task(function* () { + Services.prefs.setBoolPref(PREF, true); + + yield addTab(TESTCASE_URI); + let {inspector, view} = yield openRuleView(); + + yield selectNode("div", inspector); + + let ruleEl = getRuleViewRule(view, "div"); + ok(ruleEl, "The 'div' rule exists in the rule-view"); + + Services.prefs.clearUserPref(PREF); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js b/devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js new file mode 100644 index 000000000..825f48a96 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js @@ -0,0 +1,44 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that when a source map is missing/invalid, the rule view still loads +// correctly. + +const TESTCASE_URI = URL_ROOT + "doc_invalid_sourcemap.html"; +const PREF = "devtools.styleeditor.source-maps-enabled"; +const CSS_LOC = "doc_invalid_sourcemap.css:1"; + +add_task(function* () { + Services.prefs.setBoolPref(PREF, true); + + yield addTab(TESTCASE_URI); + let {inspector, view} = yield openRuleView(); + + yield selectNode("div", inspector); + + let ruleEl = getRuleViewRule(view, "div"); + ok(ruleEl, "The 'div' rule exists in the rule-view"); + + let prop = getRuleViewProperty(view, "div", "color"); + ok(prop, "The 'color' property exists in this rule"); + + let value = getRuleViewPropertyValue(view, "div", "color"); + is(value, "gold", "The 'color' property has the right value"); + + yield verifyLinkText(view, CSS_LOC); + + Services.prefs.clearUserPref(PREF); +}); + +function verifyLinkText(view, text) { + info("Verifying that the rule-view stylesheet link is " + text); + let label = getRuleViewLinkByIndex(view, 1) + .querySelector(".ruleview-rule-source-label"); + return waitForSuccess( + () => label.textContent == text, + "Link text changed to display correct location: " + text + ); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_invalid.js b/devtools/client/inspector/rules/test/browser_rules_invalid.js new file mode 100644 index 000000000..e664f68ac --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_invalid.js @@ -0,0 +1,33 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that an invalid property still lets us display the rule view +// Bug 1235603. + +const TEST_URI = ` + <style> + div { + background: #fff; + font-family: sans-serif; + url(display-table.min.htc); + } + </style> + <body> + <div id="testid" class="testclass">Styled Node</div> + </body> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + is(view._elementStyle.rules.length, 2, "Should have 2 rules."); + // Have to actually get the rule in order to ensure that the + // elements were created. + ok(getRuleViewRule(view, "div"), "Rule with div selector exists"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_keybindings.js b/devtools/client/inspector/rules/test/browser_rules_keybindings.js new file mode 100644 index 000000000..84fdeff85 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_keybindings.js @@ -0,0 +1,49 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that focus doesn't leave the style editor when adding a property +// (bug 719916) + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8,<h1>Some header text</h1>"); + let {inspector, view} = yield openRuleView(); + yield selectNode("h1", inspector); + + info("Getting the ruleclose brace element"); + let brace = view.styleDocument.querySelector(".ruleview-ruleclose"); + + info("Focus the new property editable field to create a color property"); + let ruleEditor = getRuleViewRuleEditor(view, 0); + let editor = yield focusNewRuleViewProperty(ruleEditor); + editor.input.value = "color"; + + info("Typing ENTER to focus the next field: property value"); + let onFocus = once(brace.parentNode, "focus", true); + let onRuleViewChanged = view.once("ruleview-changed"); + + EventUtils.sendKey("return"); + + yield onFocus; + yield onRuleViewChanged; + ok(true, "The value field was focused"); + + info("Entering a property value"); + editor = getCurrentInplaceEditor(view); + editor.input.value = "green"; + + info("Typing ENTER again should focus a new property name"); + onFocus = once(brace.parentNode, "focus", true); + onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.sendKey("return"); + yield onFocus; + yield onRuleViewChanged; + ok(true, "The new property name field was focused"); + getCurrentInplaceEditor(view).input.blur(); +}); + +function getCurrentInplaceEditor(view) { + return inplaceEditor(view.styleDocument.activeElement); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js b/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js new file mode 100644 index 000000000..ebbde08ac --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js @@ -0,0 +1,25 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that editing a rule will update the line numbers of subsequent +// rules in the rule view. + +const TESTCASE_URI = URL_ROOT + "doc_keyframeLineNumbers.html"; + +add_task(function* () { + yield addTab(TESTCASE_URI); + let { inspector, view } = yield openRuleView(); + yield selectNode("#outer", inspector); + + info("Insert a new property, which will affect the line numbers"); + yield addProperty(view, 1, "font-size", "72px"); + + yield selectNode("#inner", inspector); + + let value = getRuleViewLinkTextByIndex(view, 3); + // Note that this is relative to the <style>. + is(value.slice(-3), ":27", "rule line number is 27"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js new file mode 100644 index 000000000..8d4b436c5 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js @@ -0,0 +1,106 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that keyframe rules and gutters are displayed correctly in the +// rule view. + +const TEST_URI = URL_ROOT + "doc_keyframeanimation.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield testPacman(inspector, view); + yield testBoxy(inspector, view); + yield testMoxy(inspector, view); +}); + +function* testPacman(inspector, view) { + info("Test content and gutter in the keyframes rule of #pacman"); + + yield assertKeyframeRules("#pacman", inspector, view, { + elementRulesNb: 2, + keyframeRulesNb: 2, + keyframesRules: ["pacman", "pacman"], + keyframeRules: ["100%", "100%"] + }); + + assertGutters(view, { + guttersNbs: 2, + gutterHeading: ["Keyframes pacman", "Keyframes pacman"] + }); +} + +function* testBoxy(inspector, view) { + info("Test content and gutter in the keyframes rule of #boxy"); + + yield assertKeyframeRules("#boxy", inspector, view, { + elementRulesNb: 3, + keyframeRulesNb: 3, + keyframesRules: ["boxy", "boxy", "boxy"], + keyframeRules: ["10%", "20%", "100%"] + }); + + assertGutters(view, { + guttersNbs: 1, + gutterHeading: ["Keyframes boxy"] + }); +} + +function* testMoxy(inspector, view) { + info("Test content and gutter in the keyframes rule of #moxy"); + + yield assertKeyframeRules("#moxy", inspector, view, { + elementRulesNb: 3, + keyframeRulesNb: 4, + keyframesRules: ["boxy", "boxy", "boxy", "moxy"], + keyframeRules: ["10%", "20%", "100%", "100%"] + }); + + assertGutters(view, { + guttersNbs: 2, + gutterHeading: ["Keyframes boxy", "Keyframes moxy"] + }); +} + +function* assertKeyframeRules(selector, inspector, view, expected) { + yield selectNode(selector, inspector); + let elementStyle = view._elementStyle; + + let rules = { + elementRules: elementStyle.rules.filter(rule => !rule.keyframes), + keyframeRules: elementStyle.rules.filter(rule => rule.keyframes) + }; + + is(rules.elementRules.length, expected.elementRulesNb, selector + + " has the correct number of non keyframe element rules"); + is(rules.keyframeRules.length, expected.keyframeRulesNb, selector + + " has the correct number of keyframe rules"); + + let i = 0; + for (let keyframeRule of rules.keyframeRules) { + ok(keyframeRule.keyframes.name == expected.keyframesRules[i], + keyframeRule.keyframes.name + " has the correct keyframes name"); + ok(keyframeRule.domRule.keyText == expected.keyframeRules[i], + keyframeRule.domRule.keyText + " selector heading is correct"); + i++; + } +} + +function assertGutters(view, expected) { + let gutters = view.element.querySelectorAll(".theme-gutter"); + + is(gutters.length, expected.guttersNbs, + "There are " + gutters.length + " gutter headings"); + + let i = 0; + for (let gutter of gutters) { + is(gutter.textContent, expected.gutterHeading[i], + "Correct " + gutter.textContent + " gutter headings"); + i++; + } + + return gutters; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js new file mode 100644 index 000000000..b7652ecaa --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js @@ -0,0 +1,92 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that verifies the content of the keyframes rule and property changes +// to keyframe rules. + +const TEST_URI = URL_ROOT + "doc_keyframeanimation.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield testPacman(inspector, view); + yield testBoxy(inspector, view); +}); + +function* testPacman(inspector, view) { + info("Test content in the keyframes rule of #pacman"); + + let rules = yield getKeyframeRules("#pacman", inspector, view); + + info("Test text properties for Keyframes #pacman"); + + is(convertTextPropsToString(rules.keyframeRules[0].textProps), + "left: 750px", + "Keyframe pacman (100%) property is correct" + ); + + // Dynamic changes test disabled because of Bug 1050940 + // If this part of the test is ever enabled again, it should be changed to + // use addProperty (in head.js) and stop using _applyingModifications + + // info("Test dynamic changes to keyframe rule for #pacman"); + + // let defaultView = element.ownerDocument.defaultView; + // let ruleEditor = view.element.children[5].childNodes[0]._ruleEditor; + // ruleEditor.addProperty("opacity", "0", true); + + // yield ruleEditor._applyingModifications; + // yield once(element, "animationend"); + + // is + // ( + // convertTextPropsToString(rules.keyframeRules[1].textProps), + // "left: 750px; opacity: 0", + // "Keyframe pacman (100%) property is correct" + // ); + + // is(defaultView.getComputedStyle(element).getPropertyValue("opacity"), "0", + // "Added opacity property should have been used."); +} + +function* testBoxy(inspector, view) { + info("Test content in the keyframes rule of #boxy"); + + let rules = yield getKeyframeRules("#boxy", inspector, view); + + info("Test text properties for Keyframes #boxy"); + + is(convertTextPropsToString(rules.keyframeRules[0].textProps), + "background-color: blue", + "Keyframe boxy (10%) property is correct" + ); + + is(convertTextPropsToString(rules.keyframeRules[1].textProps), + "background-color: green", + "Keyframe boxy (20%) property is correct" + ); + + is(convertTextPropsToString(rules.keyframeRules[2].textProps), + "opacity: 0", + "Keyframe boxy (100%) property is correct" + ); +} + +function convertTextPropsToString(textProps) { + return textProps.map(t => t.name + ": " + t.value).join("; "); +} + +function* getKeyframeRules(selector, inspector, view) { + yield selectNode(selector, inspector); + let elementStyle = view._elementStyle; + + let rules = { + elementRules: elementStyle.rules.filter(rule => !rule.keyframes), + keyframeRules: elementStyle.rules.filter(rule => rule.keyframes) + }; + + return rules; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js b/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js new file mode 100644 index 000000000..3b09209f5 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js @@ -0,0 +1,29 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that editing a rule will update the line numbers of subsequent +// rules in the rule view. + +const TESTCASE_URI = URL_ROOT + "doc_ruleLineNumbers.html"; + +add_task(function* () { + yield addTab(TESTCASE_URI); + let { inspector, view } = yield openRuleView(); + yield selectNode("#testid", inspector); + + let bodyRuleEditor = getRuleViewRuleEditor(view, 3); + let value = getRuleViewLinkTextByIndex(view, 2); + // Note that this is relative to the <style>. + is(value.slice(-2), ":6", "initial rule line number is 6"); + + let onLocationChanged = once(bodyRuleEditor.rule.domRule, "location-changed"); + yield addProperty(view, 1, "font-size", "23px"); + yield onLocationChanged; + + let newBodyTitle = getRuleViewLinkTextByIndex(view, 2); + // Note that this is relative to the <style>. + is(newBodyTitle.slice(-2), ":7", "updated rule line number is 7"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_livepreview.js b/devtools/client/inspector/rules/test/browser_rules_livepreview.js new file mode 100644 index 000000000..1f1302a70 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_livepreview.js @@ -0,0 +1,72 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that changes are previewed when editing a property value. + +const TEST_URI = ` + <style type="text/css"> + #testid { + display:block; + } + </style> + <div id="testid">Styled Node</div><span>inline element</span> +`; + +// Format +// { +// value : what to type in the field +// expected : expected computed style on the targeted element +// } +const TEST_DATA = [ + {value: "inline", expected: "inline"}, + {value: "inline-block", expected: "inline-block"}, + + // Invalid property values should not apply, and should fall back to default + {value: "red", expected: "block"}, + {value: "something", expected: "block"}, + + {escape: true, value: "inline", expected: "block"} +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + for (let data of TEST_DATA) { + yield testLivePreviewData(data, view, "#testid"); + } +}); + +function* testLivePreviewData(data, ruleView, selector) { + let rule = getRuleViewRuleEditor(ruleView, 1).rule; + let propEditor = rule.textProps[0].editor; + + info("Focusing the property value inplace-editor"); + let editor = yield focusEditableField(ruleView, propEditor.valueSpan); + is(inplaceEditor(propEditor.valueSpan), editor, + "The focused editor is the value"); + + info("Entering value in the editor: " + data.value); + let onPreviewDone = ruleView.once("ruleview-changed"); + EventUtils.sendString(data.value, ruleView.styleWindow); + ruleView.throttle.flush(); + yield onPreviewDone; + + let onValueDone = ruleView.once("ruleview-changed"); + if (data.escape) { + EventUtils.synthesizeKey("VK_ESCAPE", {}); + } else { + EventUtils.synthesizeKey("VK_RETURN", {}); + } + yield onValueDone; + + // While the editor is still focused in, the display should have + // changed already + is((yield getComputedStyleProperty(selector, null, "display")), + data.expected, + "Element should be previewed as " + data.expected); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js new file mode 100644 index 000000000..ab10fadfe --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js @@ -0,0 +1,56 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly based on the +// specificity of the rule. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + .testclass { + background-color: green; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let idRule = getRuleViewRuleEditor(view, 1).rule; + let idProp = idRule.textProps[0]; + is(idProp.name, "background-color", + "First ID property should be background-color"); + is(idProp.value, "blue", "First ID property value should be blue"); + ok(!idProp.overridden, "ID prop should not be overridden."); + ok(!idProp.editor.element.classList.contains("ruleview-overridden"), + "ID property editor should not have ruleview-overridden class"); + + let classRule = getRuleViewRuleEditor(view, 2).rule; + let classProp = classRule.textProps[0]; + is(classProp.name, "background-color", + "First class prop should be background-color"); + is(classProp.value, "green", "First class property value should be green"); + ok(classProp.overridden, "Class property should be overridden."); + ok(classProp.editor.element.classList.contains("ruleview-overridden"), + "Class property editor should have ruleview-overridden class"); + + // Override background-color by changing the element style. + let elementProp = yield addProperty(view, 0, "background-color", "purple"); + + ok(!elementProp.overridden, + "Element style property should not be overridden"); + ok(idProp.overridden, "ID property should be overridden"); + ok(idProp.editor.element.classList.contains("ruleview-overridden"), + "ID property editor should have ruleview-overridden class"); + ok(classProp.overridden, "Class property should be overridden"); + ok(classProp.editor.element.classList.contains("ruleview-overridden"), + "Class property editor should have ruleview-overridden class"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js new file mode 100644 index 000000000..c71fc7211 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js @@ -0,0 +1,45 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly for short hand +// properties and the computed list properties + +const TEST_URI = ` + <style type='text/css'> + #testid { + margin-left: 1px; + } + .testclass { + margin: 2px; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testMarkOverridden(inspector, view); +}); + +function* testMarkOverridden(inspector, view) { + let elementStyle = view._elementStyle; + + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, + "Class prop shouldn't be overridden, some props are still being used."); + + for (let computed of classProp.computed) { + if (computed.name.indexOf("margin-left") == 0) { + ok(computed.overridden, "margin-left props should be overridden."); + } else { + ok(!computed.overridden, + "Non-margin-left props should not be overridden."); + } + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js new file mode 100644 index 000000000..b99bab8b4 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js @@ -0,0 +1,41 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly based on the +// priority for the rule + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + .testclass { + background-color: green !important; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let idRule = getRuleViewRuleEditor(view, 1).rule; + let idProp = idRule.textProps[0]; + ok(idProp.overridden, "Not-important rule should be overridden."); + + let classRule = getRuleViewRuleEditor(view, 2).rule; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, "Important rule should not be overridden."); + + ok(idProp.overridden, "ID property should be overridden."); + + // FIXME: re-enable these 2 assertions when bug 1247737 is fixed. + // let elementProp = yield addProperty(view, 0, "background-color", "purple"); + // ok(!elementProp.overridden, "New important prop should not be overriden."); + // ok(classProp.overridden, "Class property should be overridden."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js new file mode 100644 index 000000000..fbce1ebf4 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js @@ -0,0 +1,36 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly if a property gets +// disabled + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: blue; + } + .testclass { + background-color: green; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let idRule = getRuleViewRuleEditor(view, 1).rule; + let idProp = idRule.textProps[0]; + + yield togglePropStatus(view, idProp); + + let classRule = getRuleViewRuleEditor(view, 2).rule; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, + "Class prop should not be overridden after id prop was disabled."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js new file mode 100644 index 000000000..11ecd72ff --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js @@ -0,0 +1,33 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly based on the +// order of the property. + +const TEST_URI = ` + <style type='text/css'> + #testid { + background-color: green; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + let rule = getRuleViewRuleEditor(view, 1).rule; + + yield addProperty(view, 1, "background-color", "red"); + + let firstProp = rule.textProps[0]; + let secondProp = rule.textProps[1]; + + ok(firstProp.overridden, "First property should be overridden."); + ok(!secondProp.overridden, "Second property should not be overridden."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js new file mode 100644 index 000000000..c2e71fe49 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js @@ -0,0 +1,60 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly after +// editing the selector. + +const TEST_URI = ` + <style type='text/css'> + div { + background-color: blue; + background-color: chartreuse; + } + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testMarkOverridden(inspector, view); +}); + +function* testMarkOverridden(inspector, view) { + let elementStyle = view._elementStyle; + let rule = elementStyle.rules[1]; + checkProperties(rule); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + info("Focusing an existing selector name in the rule-view"); + let editor = yield focusEditableField(view, ruleEditor.selectorText); + + info("Entering a new selector name and committing"); + editor.input.value = "div[class]"; + + let onRuleViewChanged = once(view, "ruleview-changed"); + info("Entering the commit key"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + view.searchField.focus(); + checkProperties(rule); +} + +// A helper to perform a repeated set of checks. +function checkProperties(rule) { + let prop = rule.textProps[0]; + is(prop.name, "background-color", + "First property should be background-color"); + is(prop.value, "blue", "First property value should be blue"); + ok(prop.overridden, "prop should be overridden."); + prop = rule.textProps[1]; + is(prop.name, "background-color", + "Second property should be background-color"); + is(prop.value, "chartreuse", "First property value should be chartreuse"); + ok(!prop.overridden, "prop should not be overridden."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js new file mode 100644 index 000000000..9480ddd47 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js @@ -0,0 +1,72 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view marks overridden rules correctly based on the +// specificity of the rule. + +const TEST_URI = ` + <style type='text/css'> + #testid { + margin-left: 23px; + } + + div { + margin-right: 23px; + margin-left: 1px !important; + } + + body { + margin-right: 1px !important; + font-size: 79px; + } + + span { + font-size: 12px; + } + </style> + <body> + <span> + <div id='testid' class='testclass'>Styled Node</div> + </span> + </body> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testMarkOverridden(inspector, view); +}); + +function* testMarkOverridden(inspector, view) { + let elementStyle = view._elementStyle; + + let RESULTS = [ + // We skip the first element + [], + [{name: "margin-left", value: "23px", overridden: true}], + [{name: "margin-right", value: "23px", overridden: false}, + {name: "margin-left", value: "1px", overridden: false}], + [{name: "font-size", value: "12px", overridden: false}], + [{name: "margin-right", value: "1px", overridden: true}, + {name: "font-size", value: "79px", overridden: true}] + ]; + + for (let i = 1; i < RESULTS.length; ++i) { + let idRule = elementStyle.rules[i]; + + for (let propIndex in RESULTS[i]) { + let expected = RESULTS[i][propIndex]; + let prop = idRule.textProps[propIndex]; + + info("Checking rule " + i + ", property " + propIndex); + + is(prop.name, expected.name, "check property name"); + is(prop.value, expected.value, "check property value"); + is(prop.overridden, expected.overridden, "check property overridden"); + } + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_mathml-element.js b/devtools/client/inspector/rules/test/browser_rules_mathml-element.js new file mode 100644 index 000000000..f8a1e8572 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_mathml-element.js @@ -0,0 +1,53 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule-view displays correctly on MathML elements. + +const TEST_URI = ` + <div> + <math xmlns=\http://www.w3.org/1998/Math/MathML\> + <mfrac> + <msubsup> + <mi>a</mi> + <mi>i</mi> + <mi>j</mi> + </msubsup> + <msub> + <mi>x</mi> + <mn>0</mn> + </msub> + </mfrac> + </math> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + info("Select the DIV node and verify the rule-view shows rules"); + yield selectNode("div", inspector); + ok(view.element.querySelectorAll(".ruleview-rule").length, + "The rule-view shows rules for the div element"); + + info("Select various MathML nodes and verify the rule-view is empty"); + yield selectNode("math", inspector); + ok(!view.element.querySelectorAll(".ruleview-rule").length, + "The rule-view is empty for the math element"); + + yield selectNode("msubsup", inspector); + ok(!view.element.querySelectorAll(".ruleview-rule").length, + "The rule-view is empty for the msubsup element"); + + yield selectNode("mn", inspector); + ok(!view.element.querySelectorAll(".ruleview-rule").length, + "The rule-view is empty for the mn element"); + + info("Select again the DIV node and verify the rule-view shows rules"); + yield selectNode("div", inspector); + ok(view.element.querySelectorAll(".ruleview-rule").length, + "The rule-view shows rules for the div element"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_media-queries.js b/devtools/client/inspector/rules/test/browser_rules_media-queries.js new file mode 100644 index 000000000..57ab19163 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_media-queries.js @@ -0,0 +1,26 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that we correctly display appropriate media query titles in the +// rule view. + +const TEST_URI = URL_ROOT + "doc_media_queries.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + let elementStyle = view._elementStyle; + + let inline = STYLE_INSPECTOR_L10N.getStr("rule.sourceInline"); + + is(elementStyle.rules.length, 3, "Should have 3 rules."); + is(elementStyle.rules[0].title, inline, "check rule 0 title"); + is(elementStyle.rules[1].title, inline + + ":9 @media screen and (min-width: 1px)", "check rule 1 title"); + is(elementStyle.rules[2].title, inline + ":2", "check rule 2 title"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js new file mode 100644 index 000000000..c820dd73f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js @@ -0,0 +1,68 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view behaves correctly when entering mutliple and/or +// unfinished properties/values in inplace-editors + +const TEST_URI = "<div>Test Element</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 0); + // Note that we wait for a markup mutation here because this new rule will end + // up creating a style attribute on the node shown in the markup-view. + // (we also wait for the rule-view to refresh). + let onMutation = inspector.once("markupmutation"); + let onRuleViewChanged = view.once("ruleview-changed"); + yield createNewRuleViewProperty(ruleEditor, + "color:red;color:orange;color:yellow;color:green;color:blue;color:indigo;" + + "color:violet;"); + yield onMutation; + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 7, + "Should have created new text properties."); + is(ruleEditor.propertyList.children.length, 8, + "Should have created new property editors."); + + is(ruleEditor.rule.textProps[0].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[0].value, "red", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[1].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[1].value, "orange", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[2].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[2].value, "yellow", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[3].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[3].value, "green", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[4].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[4].value, "blue", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[5].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[5].value, "indigo", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[6].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[6].value, "violet", + "Should have correct property value"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js new file mode 100644 index 000000000..f7d98b768 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view behaves correctly when entering mutliple and/or +// unfinished properties/values in inplace-editors. + +const TEST_URI = "<div>Test Element</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 0); + // Note that we wait for a markup mutation here because this new rule will end + // up creating a style attribute on the node shown in the markup-view. + // (we also wait for the rule-view to refresh). + let onMutation = inspector.once("markupmutation"); + let onRuleViewChanged = view.once("ruleview-changed"); + yield createNewRuleViewProperty(ruleEditor, + "color:red;width:100px;height: 100px;"); + yield onMutation; + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 3, + "Should have created new text properties."); + is(ruleEditor.propertyList.children.length, 4, + "Should have created new property editors."); + + is(ruleEditor.rule.textProps[0].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[0].value, "red", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[1].name, "width", + "Should have correct property name"); + is(ruleEditor.rule.textProps[1].value, "100px", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[2].name, "height", + "Should have correct property name"); + is(ruleEditor.rule.textProps[2].value, "100px", + "Should have correct property value"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js new file mode 100644 index 000000000..deaf16029 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js @@ -0,0 +1,62 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view behaves correctly when entering multiple and/or +// unfinished properties/values in inplace-editors + +const TEST_URI = "<div>Test Element</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + yield testCreateNewMultiUnfinished(inspector, view); +}); + +function* testCreateNewMultiUnfinished(inspector, view) { + let ruleEditor = getRuleViewRuleEditor(view, 0); + let onMutation = inspector.once("markupmutation"); + let onRuleViewChanged = view.once("ruleview-changed"); + yield createNewRuleViewProperty(ruleEditor, + "color:blue;background : orange ; text-align:center; border-color: "); + yield onMutation; + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 4, + "Should have created new text properties."); + is(ruleEditor.propertyList.children.length, 4, + "Should have created property editors."); + + EventUtils.sendString("red", view.styleWindow); + onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 4, + "Should have the same number of text properties."); + is(ruleEditor.propertyList.children.length, 5, + "Should have added the changed value editor."); + + is(ruleEditor.rule.textProps[0].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[0].value, "blue", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[1].name, "background", + "Should have correct property name"); + is(ruleEditor.rule.textProps[1].value, "orange", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[2].name, "text-align", + "Should have correct property name"); + is(ruleEditor.rule.textProps[2].value, "center", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[3].name, "border-color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[3].value, "red", + "Should have correct property value"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js new file mode 100644 index 000000000..dd1360b96 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js @@ -0,0 +1,71 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view behaves correctly when entering mutliple and/or +// unfinished properties/values in inplace-editors + +const TEST_URI = "<div>Test Element</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + // Turn off throttling, which can cause intermittents. Throttling is used by + // the TextPropertyEditor. + view.throttle = () => {}; + + yield selectNode("div", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 0); + // Note that we wait for a markup mutation here because this new rule will end + // up creating a style attribute on the node shown in the markup-view. + // (we also wait for the rule-view to refresh). + let onMutation = inspector.once("markupmutation"); + let onRuleViewChanged = view.once("ruleview-changed"); + yield createNewRuleViewProperty(ruleEditor, "width: 100px; heig"); + yield onMutation; + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 2, + "Should have created a new text property."); + is(ruleEditor.propertyList.children.length, 2, + "Should have created a property editor."); + + // Value is focused, lets add multiple rules here and make sure they get added + onMutation = inspector.once("markupmutation"); + onRuleViewChanged = view.once("ruleview-changed"); + let valueEditor = ruleEditor.propertyList.children[1] + .querySelector(".styleinspector-propertyeditor"); + valueEditor.value = "10px;background:orangered;color: black;"; + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onMutation; + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 4, + "Should have added the changed value."); + is(ruleEditor.propertyList.children.length, 5, + "Should have added the changed value editor."); + + is(ruleEditor.rule.textProps[0].name, "width", + "Should have correct property name"); + is(ruleEditor.rule.textProps[0].value, "100px", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[1].name, "heig", + "Should have correct property name"); + is(ruleEditor.rule.textProps[1].value, "10px", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[2].name, "background", + "Should have correct property name"); + is(ruleEditor.rule.textProps[2].value, "orangered", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[3].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[3].value, "black", + "Should have correct property value"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js new file mode 100644 index 000000000..2801df652 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js @@ -0,0 +1,53 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view behaves correctly when entering mutliple and/or +// unfinished properties/values in inplace-editors. + +const TEST_URI = "<div>Test Element</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 0); + // Note that we wait for a markup mutation here because this new rule will end + // up creating a style attribute on the node shown in the markup-view. + // (we also wait for the rule-view to refresh). + let onMutation = inspector.once("markupmutation"); + let onRuleViewChanged = view.once("ruleview-changed"); + yield createNewRuleViewProperty(ruleEditor, + "color:blue;background : orange ; text-align:center; " + + "border-color: green;"); + yield onMutation; + yield onRuleViewChanged; + + is(ruleEditor.rule.textProps.length, 4, + "Should have created a new text property."); + is(ruleEditor.propertyList.children.length, 5, + "Should have created a new property editor."); + + is(ruleEditor.rule.textProps[0].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[0].value, "blue", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[1].name, "background", + "Should have correct property name"); + is(ruleEditor.rule.textProps[1].value, "orange", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[2].name, "text-align", + "Should have correct property name"); + is(ruleEditor.rule.textProps[2].value, "center", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[3].name, "border-color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[3].value, "green", + "Should have correct property value"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js new file mode 100644 index 000000000..ce6f1909f --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js @@ -0,0 +1,54 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule-view behaves correctly when entering mutliple and/or +// unfinished properties/values in inplace-editors + +const TEST_URI = "<div>Test Element</div>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + let ruleEditor = getRuleViewRuleEditor(view, 0); + let onDone = view.once("ruleview-changed"); + yield createNewRuleViewProperty(ruleEditor, "width:"); + yield onDone; + + is(ruleEditor.rule.textProps.length, 1, + "Should have created a new text property."); + is(ruleEditor.propertyList.children.length, 1, + "Should have created a property editor."); + + // Value is focused, lets add multiple rules here and make sure they get added + onDone = view.once("ruleview-changed"); + let onMutation = inspector.once("markupmutation"); + let input = view.styleDocument.activeElement; + input.value = "height: 10px;color:blue"; + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onMutation; + yield onDone; + + is(ruleEditor.rule.textProps.length, 2, + "Should have added the changed value."); + is(ruleEditor.propertyList.children.length, 3, + "Should have added the changed value editor."); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow); + is(ruleEditor.propertyList.children.length, 2, + "Should have removed the value editor."); + + is(ruleEditor.rule.textProps[0].name, "width", + "Should have correct property name"); + is(ruleEditor.rule.textProps[0].value, "height: 10px", + "Should have correct property value"); + + is(ruleEditor.rule.textProps[1].name, "color", + "Should have correct property name"); + is(ruleEditor.rule.textProps[1].value, "blue", + "Should have correct property value"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_original-source-link.js b/devtools/client/inspector/rules/test/browser_rules_original-source-link.js new file mode 100644 index 000000000..09dad9a86 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_original-source-link.js @@ -0,0 +1,85 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the stylesheet links in the rule view are correct when source maps +// are involved. + +const TESTCASE_URI = URL_ROOT + "doc_sourcemaps.html"; +const PREF = "devtools.styleeditor.source-maps-enabled"; +const SCSS_LOC = "doc_sourcemaps.scss:4"; +const CSS_LOC = "doc_sourcemaps.css:1"; + +add_task(function* () { + info("Setting the " + PREF + " pref to true"); + Services.prefs.setBoolPref(PREF, true); + + yield addTab(TESTCASE_URI); + let {toolbox, inspector, view} = yield openRuleView(); + + info("Selecting the test node"); + yield selectNode("div", inspector); + + yield verifyLinkText(SCSS_LOC, view); + + info("Setting the " + PREF + " pref to false"); + Services.prefs.setBoolPref(PREF, false); + yield verifyLinkText(CSS_LOC, view); + + info("Setting the " + PREF + " pref to true again"); + Services.prefs.setBoolPref(PREF, true); + + yield testClickingLink(toolbox, view); + yield checkDisplayedStylesheet(toolbox); + + info("Clearing the " + PREF + " pref"); + Services.prefs.clearUserPref(PREF); +}); + +function* testClickingLink(toolbox, view) { + info("Listening for switch to the style editor"); + let onStyleEditorReady = toolbox.once("styleeditor-ready"); + + info("Finding the stylesheet link and clicking it"); + let link = getRuleViewLinkByIndex(view, 1); + link.scrollIntoView(); + link.click(); + yield onStyleEditorReady; +} + +function checkDisplayedStylesheet(toolbox) { + let def = defer(); + + let panel = toolbox.getCurrentPanel(); + panel.UI.on("editor-selected", (event, editor) => { + // The style editor selects the first sheet at first load before + // selecting the desired sheet. + if (editor.styleSheet.href.endsWith("scss")) { + info("Original source editor selected"); + editor.getSourceEditor().then(editorSelected) + .then(def.resolve, def.reject); + } + }); + + return def.promise; +} + +function editorSelected(editor) { + let href = editor.styleSheet.href; + ok(href.endsWith("doc_sourcemaps.scss"), + "selected stylesheet is correct one"); + + let {line} = editor.sourceEditor.getCursor(); + is(line, 3, "cursor is at correct line number in original source"); +} + +function verifyLinkText(text, view) { + info("Verifying that the rule-view stylesheet link is " + text); + let label = getRuleViewLinkByIndex(view, 1) + .querySelector(".ruleview-rule-source-label"); + return waitForSuccess(function* () { + return label.textContent == text; + }, "Link text changed to display correct location: " + text); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js new file mode 100644 index 000000000..e98b5437c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js @@ -0,0 +1,260 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that pseudoelements are displayed correctly in the rule view + +const TEST_URI = URL_ROOT + "doc_pseudoelement.html"; +const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements"; + +add_task(function* () { + yield pushPref(PSEUDO_PREF, true); + + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + + yield testTopLeft(inspector, view); + yield testTopRight(inspector, view); + yield testBottomRight(inspector, view); + yield testBottomLeft(inspector, view); + yield testParagraph(inspector, view); + yield testBody(inspector, view); +}); + +function* testTopLeft(inspector, view) { + let id = "#topleft"; + let rules = yield assertPseudoElementRulesNumbers(id, + inspector, view, { + elementRulesNb: 4, + firstLineRulesNb: 2, + firstLetterRulesNb: 1, + selectionRulesNb: 0, + afterRulesNb: 1, + beforeRulesNb: 2 + } + ); + + let gutters = assertGutters(view); + + info("Make sure that clicking on the twisty hides pseudo elements"); + let expander = gutters[0].querySelector(".ruleview-expander"); + ok(!view.element.children[1].hidden, "Pseudo Elements are expanded"); + + expander.click(); + ok(view.element.children[1].hidden, + "Pseudo Elements are collapsed by twisty"); + + expander.click(); + ok(!view.element.children[1].hidden, "Pseudo Elements are expanded again"); + + info("Make sure that dblclicking on the header container also toggles " + + "the pseudo elements"); + EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2}, + view.styleWindow); + ok(view.element.children[1].hidden, + "Pseudo Elements are collapsed by dblclicking"); + + let elementRuleView = getRuleViewRuleEditor(view, 3); + + let elementFirstLineRule = rules.firstLineRules[0]; + let elementFirstLineRuleView = + [...view.element.children[1].children].filter(e => { + return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule; + })[0]._ruleEditor; + + is(convertTextPropsToString(elementFirstLineRule.textProps), + "color: orange", + "TopLeft firstLine properties are correct"); + + let onAdded = view.once("ruleview-changed"); + let firstProp = elementFirstLineRuleView.addProperty("background-color", + "rgb(0, 255, 0)", "", true); + yield onAdded; + + onAdded = view.once("ruleview-changed"); + let secondProp = elementFirstLineRuleView.addProperty("font-style", + "italic", "", true); + yield onAdded; + + is(firstProp, + elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2], + "First added property is on back of array"); + is(secondProp, + elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1], + "Second added property is on back of array"); + + is((yield getComputedStyleProperty(id, ":first-line", "background-color")), + "rgb(0, 255, 0)", "Added property should have been used."); + is((yield getComputedStyleProperty(id, ":first-line", "font-style")), + "italic", "Added property should have been used."); + is((yield getComputedStyleProperty(id, null, "text-decoration")), + "none", "Added property should not apply to element"); + + yield togglePropStatus(view, firstProp); + + is((yield getComputedStyleProperty(id, ":first-line", "background-color")), + "rgb(255, 0, 0)", "Disabled property should now have been used."); + is((yield getComputedStyleProperty(id, null, "background-color")), + "rgb(221, 221, 221)", "Added property should not apply to element"); + + yield togglePropStatus(view, firstProp); + + is((yield getComputedStyleProperty(id, ":first-line", "background-color")), + "rgb(0, 255, 0)", "Added property should have been used."); + is((yield getComputedStyleProperty(id, null, "text-decoration")), + "none", "Added property should not apply to element"); + + onAdded = view.once("ruleview-changed"); + firstProp = elementRuleView.addProperty("background-color", + "rgb(0, 0, 255)", "", true); + yield onAdded; + + is((yield getComputedStyleProperty(id, null, "background-color")), + "rgb(0, 0, 255)", "Added property should have been used."); + is((yield getComputedStyleProperty(id, ":first-line", "background-color")), + "rgb(0, 255, 0)", "Added prop does not apply to pseudo"); +} + +function* testTopRight(inspector, view) { + yield assertPseudoElementRulesNumbers("#topright", inspector, view, { + elementRulesNb: 4, + firstLineRulesNb: 1, + firstLetterRulesNb: 1, + selectionRulesNb: 0, + beforeRulesNb: 2, + afterRulesNb: 1 + }); + + let gutters = assertGutters(view); + + let expander = gutters[0].querySelector(".ruleview-expander"); + ok(!view.element.firstChild.classList.contains("show-expandable-container"), + "Pseudo Elements remain collapsed after switching element"); + + expander.scrollIntoView(); + expander.click(); + ok(!view.element.children[1].hidden, + "Pseudo Elements are shown again after clicking twisty"); +} + +function* testBottomRight(inspector, view) { + yield assertPseudoElementRulesNumbers("#bottomright", inspector, view, { + elementRulesNb: 4, + firstLineRulesNb: 1, + firstLetterRulesNb: 1, + selectionRulesNb: 0, + beforeRulesNb: 3, + afterRulesNb: 1 + }); +} + +function* testBottomLeft(inspector, view) { + yield assertPseudoElementRulesNumbers("#bottomleft", inspector, view, { + elementRulesNb: 4, + firstLineRulesNb: 1, + firstLetterRulesNb: 1, + selectionRulesNb: 0, + beforeRulesNb: 2, + afterRulesNb: 1 + }); +} + +function* testParagraph(inspector, view) { + let rules = + yield assertPseudoElementRulesNumbers("#bottomleft p", inspector, view, { + elementRulesNb: 3, + firstLineRulesNb: 1, + firstLetterRulesNb: 1, + selectionRulesNb: 1, + beforeRulesNb: 0, + afterRulesNb: 0 + }); + + assertGutters(view); + + let elementFirstLineRule = rules.firstLineRules[0]; + is(convertTextPropsToString(elementFirstLineRule.textProps), + "background: blue", + "Paragraph first-line properties are correct"); + + let elementFirstLetterRule = rules.firstLetterRules[0]; + is(convertTextPropsToString(elementFirstLetterRule.textProps), + "color: red; font-size: 130%", + "Paragraph first-letter properties are correct"); + + let elementSelectionRule = rules.selectionRules[0]; + is(convertTextPropsToString(elementSelectionRule.textProps), + "color: white; background: black", + "Paragraph first-letter properties are correct"); +} + +function* testBody(inspector, view) { + yield testNode("body", inspector, view); + + let gutters = getGutters(view); + is(gutters.length, 0, "There are no gutter headings"); +} + +function convertTextPropsToString(textProps) { + return textProps.map(t => t.name + ": " + t.value).join("; "); +} + +function* testNode(selector, inspector, view) { + yield selectNode(selector, inspector); + let elementStyle = view._elementStyle; + return elementStyle; +} + +function* assertPseudoElementRulesNumbers(selector, inspector, view, ruleNbs) { + let elementStyle = yield testNode(selector, inspector, view); + + let rules = { + elementRules: elementStyle.rules.filter(rule => !rule.pseudoElement), + firstLineRules: elementStyle.rules.filter(rule => + rule.pseudoElement === ":first-line"), + firstLetterRules: elementStyle.rules.filter(rule => + rule.pseudoElement === ":first-letter"), + selectionRules: elementStyle.rules.filter(rule => + rule.pseudoElement === ":-moz-selection"), + beforeRules: elementStyle.rules.filter(rule => + rule.pseudoElement === ":before"), + afterRules: elementStyle.rules.filter(rule => + rule.pseudoElement === ":after"), + }; + + is(rules.elementRules.length, ruleNbs.elementRulesNb, + selector + " has the correct number of non pseudo element rules"); + is(rules.firstLineRules.length, ruleNbs.firstLineRulesNb, + selector + " has the correct number of :first-line rules"); + is(rules.firstLetterRules.length, ruleNbs.firstLetterRulesNb, + selector + " has the correct number of :first-letter rules"); + is(rules.selectionRules.length, ruleNbs.selectionRulesNb, + selector + " has the correct number of :selection rules"); + is(rules.beforeRules.length, ruleNbs.beforeRulesNb, + selector + " has the correct number of :before rules"); + is(rules.afterRules.length, ruleNbs.afterRulesNb, + selector + " has the correct number of :after rules"); + + return rules; +} + +function getGutters(view) { + return view.element.querySelectorAll(".theme-gutter"); +} + +function assertGutters(view) { + let gutters = getGutters(view); + + is(gutters.length, 3, + "There are 3 gutter headings"); + is(gutters[0].textContent, "Pseudo-elements", + "Gutter heading is correct"); + is(gutters[1].textContent, "This Element", + "Gutter heading is correct"); + is(gutters[2].textContent, "Inherited from body", + "Gutter heading is correct"); + + return gutters; +} diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js new file mode 100644 index 000000000..f69c328db --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js @@ -0,0 +1,29 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that pseudoelements are displayed correctly in the markup view. + +const TEST_URI = URL_ROOT + "doc_pseudoelement.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector} = yield openRuleView(); + + let node = yield getNodeFront("#topleft", inspector); + let children = yield inspector.markup.walker.children(node); + + is(children.nodes.length, 3, "Element has correct number of children"); + + let beforeElement = children.nodes[0]; + is(beforeElement.tagName, "_moz_generated_content_before", + "tag name is correct"); + yield selectNode(beforeElement, inspector); + + let afterElement = children.nodes[children.nodes.length - 1]; + is(afterElement.tagName, "_moz_generated_content_after", + "tag name is correct"); + yield selectNode(afterElement, inspector); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js b/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js new file mode 100644 index 000000000..d795ba5f3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js @@ -0,0 +1,131 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view pseudo lock options work properly. + +const TEST_URI = ` + <style type='text/css'> + div { + color: red; + } + div:hover { + color: blue; + } + div:active { + color: yellow; + } + div:focus { + color: green; + } + </style> + <div>test div</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + + yield assertPseudoPanelClosed(view); + + info("Toggle the pseudo class panel open"); + view.pseudoClassToggle.click(); + yield assertPseudoPanelOpened(view); + + info("Toggle each pseudo lock and check that the pseudo lock is added"); + yield togglePseudoClass(inspector, view.hoverCheckbox); + yield assertPseudoAdded(inspector, view, ":hover", 3, 1); + yield togglePseudoClass(inspector, view.hoverCheckbox); + yield assertPseudoRemoved(inspector, view, 2); + + yield togglePseudoClass(inspector, view.activeCheckbox); + yield assertPseudoAdded(inspector, view, ":active", 3, 1); + yield togglePseudoClass(inspector, view.activeCheckbox); + yield assertPseudoRemoved(inspector, view, 2); + + yield togglePseudoClass(inspector, view.focusCheckbox); + yield assertPseudoAdded(inspector, view, ":focus", 3, 1); + yield togglePseudoClass(inspector, view.focusCheckbox); + yield assertPseudoRemoved(inspector, view, 2); + + info("Toggle all pseudo lock and check that the pseudo lock is added"); + yield togglePseudoClass(inspector, view.hoverCheckbox); + yield togglePseudoClass(inspector, view.activeCheckbox); + yield togglePseudoClass(inspector, view.focusCheckbox); + yield assertPseudoAdded(inspector, view, ":focus", 5, 1); + yield assertPseudoAdded(inspector, view, ":active", 5, 2); + yield assertPseudoAdded(inspector, view, ":hover", 5, 3); + yield togglePseudoClass(inspector, view.hoverCheckbox); + yield togglePseudoClass(inspector, view.activeCheckbox); + yield togglePseudoClass(inspector, view.focusCheckbox); + yield assertPseudoRemoved(inspector, view, 2); + + info("Select a null element"); + yield view.selectElement(null); + ok(!view.hoverCheckbox.checked && view.hoverCheckbox.disabled, + ":hover checkbox is unchecked and disabled"); + ok(!view.activeCheckbox.checked && view.activeCheckbox.disabled, + ":active checkbox is unchecked and disabled"); + ok(!view.focusCheckbox.checked && view.focusCheckbox.disabled, + ":focus checkbox is unchecked and disabled"); + + info("Toggle the pseudo class panel close"); + view.pseudoClassToggle.click(); + yield assertPseudoPanelClosed(view); +}); + +function* togglePseudoClass(inspector, pseudoClassOption) { + info("Toggle the pseudoclass, wait for it to be applied"); + let onRefresh = inspector.once("rule-view-refreshed"); + pseudoClassOption.click(); + yield onRefresh; +} + +function* assertPseudoAdded(inspector, view, pseudoClass, numRules, + childIndex) { + info("Check that the ruleview contains the pseudo-class rule"); + is(view.element.children.length, numRules, + "Should have " + numRules + " rules."); + is(getRuleViewRuleEditor(view, childIndex).rule.selectorText, + "div" + pseudoClass, "rule view is showing " + pseudoClass + " rule"); +} + +function* assertPseudoRemoved(inspector, view, numRules) { + info("Check that the ruleview no longer contains the pseudo-class rule"); + is(view.element.children.length, numRules, + "Should have " + numRules + " rules."); + is(getRuleViewRuleEditor(view, 1).rule.selectorText, "div", + "Second rule is div"); +} + +function* assertPseudoPanelOpened(view) { + info("Check the opened state of the pseudo class panel"); + + ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened"); + ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled"); + ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled"); + ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled"); + + is(view.hoverCheckbox.getAttribute("tabindex"), "0", + ":hover checkbox has a tabindex of 0"); + is(view.activeCheckbox.getAttribute("tabindex"), "0", + ":active checkbox has a tabindex of 0"); + is(view.focusCheckbox.getAttribute("tabindex"), "0", + ":focus checkbox has a tabindex of 0"); +} + +function* assertPseudoPanelClosed(view) { + info("Check the closed state of the pseudo clas panel"); + + ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden"); + + is(view.hoverCheckbox.getAttribute("tabindex"), "-1", + ":hover checkbox has a tabindex of -1"); + is(view.activeCheckbox.getAttribute("tabindex"), "-1", + ":active checkbox has a tabindex of -1"); + is(view.focusCheckbox.getAttribute("tabindex"), "-1", + ":focus checkbox has a tabindex of -1"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js b/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js new file mode 100644 index 000000000..25ea3d972 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js @@ -0,0 +1,39 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule view does not go blank while selecting a new node. + +const TESTCASE_URI = "data:text/html;charset=utf-8," + + "<div id=\"testdiv\" style=\"font-size:10px;\">" + + "Test div!</div>"; + +add_task(function* () { + yield addTab(TESTCASE_URI); + + info("Opening the rule view and selecting the test node"); + let {inspector, view} = yield openRuleView(); + let testdiv = yield getNodeFront("#testdiv", inspector); + yield selectNode(testdiv, inspector); + + let htmlBefore = view.element.innerHTML; + ok(htmlBefore.indexOf("font-size") > -1, + "The rule view should contain a font-size property."); + + // Do the selectNode call manually, because otherwise it's hard to guarantee + // that we can make the below checks at a reasonable time. + info("refreshing the node"); + let p = view.selectElement(testdiv, true); + is(view.element.innerHTML, htmlBefore, + "The rule view is unchanged during selection."); + ok(view.element.classList.contains("non-interactive"), + "The rule view is marked non-interactive."); + yield p; + + info("node refreshed"); + ok(!view.element.classList.contains("non-interactive"), + "The rule view is marked interactive again."); +}); + diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js new file mode 100644 index 000000000..381a6bda2 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js @@ -0,0 +1,61 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that changing the current element's attributes refreshes the rule-view + +const TEST_URI = ` + <style type="text/css"> + #testid { + background-color: blue; + } + .testclass { + background-color: green; + } + </style> + <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;"> + Styled Node + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view, testActor} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Checking that the rule-view has the element, #testid and " + + ".testclass selectors"); + checkRuleViewContent(view, ["element", "#testid", ".testclass"]); + + info("Changing the node's ID attribute and waiting for the " + + "rule-view refresh"); + let ruleViewRefreshed = inspector.once("rule-view-refreshed"); + yield testActor.setAttribute("#testid", "id", "differentid"); + yield ruleViewRefreshed; + + info("Checking that the rule-view doesn't have the #testid selector anymore"); + checkRuleViewContent(view, ["element", ".testclass"]); + + info("Reverting the ID attribute change"); + ruleViewRefreshed = inspector.once("rule-view-refreshed"); + yield testActor.setAttribute("#differentid", "id", "testid"); + yield ruleViewRefreshed; + + info("Checking that the rule-view has all the selectors again"); + checkRuleViewContent(view, ["element", "#testid", ".testclass"]); +}); + +function checkRuleViewContent(view, expectedSelectors) { + let selectors = view.styleDocument + .querySelectorAll(".ruleview-selectorcontainer"); + + is(selectors.length, expectedSelectors.length, + expectedSelectors.length + " selectors are displayed"); + + for (let i = 0; i < expectedSelectors.length; i++) { + is(selectors[i].textContent.indexOf(expectedSelectors[i]), 0, + "Selector " + (i + 1) + " is " + expectedSelectors[i]); + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js new file mode 100644 index 000000000..6ee385faa --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js @@ -0,0 +1,153 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that changing the current element's style attribute refreshes the +// rule-view + +const TEST_URI = ` + <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;"> + Styled Node + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view, testActor} = yield openRuleView(); + yield selectNode("#testid", inspector); + + yield testPropertyChanges(inspector, view); + yield testPropertyChange0(inspector, view, "#testid", testActor); + yield testPropertyChange1(inspector, view, "#testid", testActor); + yield testPropertyChange2(inspector, view, "#testid", testActor); + yield testPropertyChange3(inspector, view, "#testid", testActor); + yield testPropertyChange4(inspector, view, "#testid", testActor); + yield testPropertyChange5(inspector, view, "#testid", testActor); + yield testPropertyChange6(inspector, view, "#testid", testActor); +}); + +function* testPropertyChanges(inspector, ruleView) { + info("Adding a second margin-top value in the element selector"); + let ruleEditor = ruleView._elementStyle.rules[0].editor; + let onRefreshed = inspector.once("rule-view-refreshed"); + ruleEditor.addProperty("margin-top", "5px", "", true); + yield onRefreshed; + + let rule = ruleView._elementStyle.rules[0]; + validateTextProp(rule.textProps[0], false, "margin-top", "1px", + "Original margin property active"); +} + +function* testPropertyChange0(inspector, ruleView, selector, testActor) { + yield changeElementStyle(selector, "margin-top: 1px; padding-top: 5px", + inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, + "Correct number of properties"); + validateTextProp(rule.textProps[0], true, "margin-top", "1px", + "First margin property re-enabled"); + validateTextProp(rule.textProps[2], false, "margin-top", "5px", + "Second margin property disabled"); +} + +function* testPropertyChange1(inspector, ruleView, selector, testActor) { + info("Now set it back to 5px, the 5px value should be re-enabled."); + yield changeElementStyle(selector, "margin-top: 5px; padding-top: 5px;", + inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, + "Correct number of properties"); + validateTextProp(rule.textProps[0], false, "margin-top", "1px", + "First margin property re-enabled"); + validateTextProp(rule.textProps[2], true, "margin-top", "5px", + "Second margin property disabled"); +} + +function* testPropertyChange2(inspector, ruleView, selector, testActor) { + info("Set the margin property to a value that doesn't exist in the editor."); + info("Should reuse the currently-enabled element (the second one.)"); + yield changeElementStyle(selector, "margin-top: 15px; padding-top: 5px;", + inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, + "Correct number of properties"); + validateTextProp(rule.textProps[0], false, "margin-top", "1px", + "First margin property re-enabled"); + validateTextProp(rule.textProps[2], true, "margin-top", "15px", + "Second margin property disabled"); +} + +function* testPropertyChange3(inspector, ruleView, selector, testActor) { + info("Remove the padding-top attribute. Should disable the padding " + + "property but not remove it."); + yield changeElementStyle(selector, "margin-top: 5px;", inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, + "Correct number of properties"); + validateTextProp(rule.textProps[1], false, "padding-top", "5px", + "Padding property disabled"); +} + +function* testPropertyChange4(inspector, ruleView, selector, testActor) { + info("Put the padding-top attribute back in, should re-enable the " + + "padding property."); + yield changeElementStyle(selector, "margin-top: 5px; padding-top: 25px", + inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, + "Correct number of properties"); + validateTextProp(rule.textProps[1], true, "padding-top", "25px", + "Padding property enabled"); +} + +function* testPropertyChange5(inspector, ruleView, selector, testActor) { + info("Add an entirely new property"); + yield changeElementStyle(selector, + "margin-top: 5px; padding-top: 25px; padding-left: 20px;", + inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4, + "Added a property"); + validateTextProp(rule.textProps[3], true, "padding-left", "20px", + "Padding property enabled"); +} + +function* testPropertyChange6(inspector, ruleView, selector, testActor) { + info("Add an entirely new property again"); + yield changeElementStyle(selector, "background: red " + + "url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%", + inspector, testActor); + + let rule = ruleView._elementStyle.rules[0]; + is(rule.editor.element.querySelectorAll(".ruleview-property").length, 5, + "Added a property"); + validateTextProp(rule.textProps[4], true, "background", + "red url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%", + "shortcut property correctly set"); +} + +function* changeElementStyle(selector, style, inspector, testActor) { + let onRefreshed = inspector.once("rule-view-refreshed"); + yield testActor.setAttribute(selector, "style", style); + yield onRefreshed; +} + +function validateTextProp(prop, enabled, name, value, desc) { + is(prop.enabled, enabled, desc + ": enabled."); + is(prop.name, name, desc + ": name."); + is(prop.value, value, desc + ": value."); + + is(prop.editor.enable.hasAttribute("checked"), enabled, + desc + ": enabled checkbox."); + is(prop.editor.nameSpan.textContent, name, desc + ": name span."); + is(prop.editor.valueSpan.textContent, + value, desc + ": value span."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js new file mode 100644 index 000000000..81ff9d4d5 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js @@ -0,0 +1,38 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule view refreshes when the current node has its style +// changed + +const TEST_URI = "<div id='testdiv' style='font-size: 10px;''>Test div!</div>"; + +add_task(function* () { + Services.prefs.setCharPref("devtools.defaultColorUnit", "name"); + + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view, testActor} = yield openRuleView(); + yield selectNode("#testdiv", inspector); + + let fontSize = getRuleViewPropertyValue(view, "element", "font-size"); + is(fontSize, "10px", "The rule view shows the right font-size"); + + info("Changing the node's style and waiting for the update"); + let onUpdated = inspector.once("rule-view-refreshed"); + yield testActor.setAttribute("#testdiv", "style", + "font-size: 3em; color: lightgoldenrodyellow; " + + "text-align: right; text-transform: uppercase"); + yield onUpdated; + + let textAlign = getRuleViewPropertyValue(view, "element", "text-align"); + is(textAlign, "right", "The rule view shows the new text align."); + let color = getRuleViewPropertyValue(view, "element", "color"); + is(color, "lightgoldenrodyellow", "The rule view shows the new color."); + fontSize = getRuleViewPropertyValue(view, "element", "font-size"); + is(fontSize, "3em", "The rule view shows the new font size."); + let textTransform = getRuleViewPropertyValue(view, "element", + "text-transform"); + is(textTransform, "uppercase", "The rule view shows the new text transform."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js new file mode 100644 index 000000000..f4c47bba0 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js @@ -0,0 +1,156 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter and clear button works properly in +// the computed list. + +const TEST_URI = ` + <style type="text/css"> + #testid { + margin: 4px 0px; + } + .testclass { + background-color: red; + } + </style> + <h1 id="testid" class="testclass">Styled Node</h1> +`; + +const TEST_DATA = [ + { + desc: "Tests that the search filter works properly in the computed list " + + "for property names", + search: "margin", + isExpanderOpen: false, + isFilterOpen: false, + isMarginHighlighted: true, + isMarginTopHighlighted: true, + isMarginRightHighlighted: true, + isMarginBottomHighlighted: true, + isMarginLeftHighlighted: true + }, + { + desc: "Tests that the search filter works properly in the computed list " + + "for property values", + search: "0px", + isExpanderOpen: false, + isFilterOpen: false, + isMarginHighlighted: true, + isMarginTopHighlighted: false, + isMarginRightHighlighted: true, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: true + }, + { + desc: "Tests that the search filter works properly in the computed list " + + "for property line input", + search: "margin-top:4px", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, + { + desc: "Tests that the search filter works properly in the computed list " + + "for parsed name", + search: "margin-top:", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, + { + desc: "Tests that the search filter works properly in the computed list " + + "for parsed property value", + search: ":4px", + isExpanderOpen: false, + isFilterOpen: false, + isMarginHighlighted: true, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: true, + isMarginLeftHighlighted: false + } +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + for (let data of TEST_DATA) { + info(data.desc); + yield setSearchFilter(view, data.search); + yield checkRules(view, data); + yield clearSearchAndCheckRules(view); + } +} + +function* checkRules(view, data) { + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let textPropEditor = rule.textProps[0].editor; + let computed = textPropEditor.computed; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + is(!!textPropEditor.expander.getAttribute("open"), data.isExpanderOpen, + "Got correct expander state."); + is(computed.hasAttribute("filter-open"), data.isFilterOpen, + "Got correct expanded state for margin computed list."); + is(textPropEditor.container.classList.contains("ruleview-highlight"), + data.isMarginHighlighted, + "Got correct highlight for margin text property."); + + is(computed.children[0].classList.contains("ruleview-highlight"), + data.isMarginTopHighlighted, + "Got correct highlight for margin-top computed property."); + is(computed.children[1].classList.contains("ruleview-highlight"), + data.isMarginRightHighlighted, + "Got correct highlight for margin-right computed property."); + is(computed.children[2].classList.contains("ruleview-highlight"), + data.isMarginBottomHighlighted, + "Got correct highlight for margin-bottom computed property."); + is(computed.children[3].classList.contains("ruleview-highlight"), + data.isMarginLeftHighlighted, + "Got correct highlight for margin-left computed property."); +} + +function* clearSearchAndCheckRules(view) { + let win = view.styleWindow; + let searchField = view.searchField; + let searchClearButton = view.searchClearButton; + + let rule = getRuleViewRuleEditor(view, 1).rule; + let textPropEditor = rule.textProps[0].editor; + let computed = textPropEditor.computed; + + info("Clearing the search filter"); + EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win); + yield view.inspector.once("ruleview-filtered"); + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared"); + ok(!view.styleDocument.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted"); + + ok(!textPropEditor.expander.getAttribute("open"), "Expander is closed."); + ok(!computed.hasAttribute("filter-open"), + "margin computed list is closed."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js new file mode 100644 index 000000000..911f09ff3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js @@ -0,0 +1,93 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly in the computed list +// when modifying the existing search filter value + +const SEARCH = "margin-"; + +const TEST_URI = ` + <style type="text/css"> + #testid { + margin: 4px 0px; + } + .testclass { + background-color: red; + } + </style> + <h1 id="testid" class="testclass">Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); + yield testRemoveTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let ruleEditor = rule.textProps[0].editor; + let computed = ruleEditor.computed; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(ruleEditor.expander.getAttribute("open"), "Expander is open."); + ok(!ruleEditor.container.classList.contains("ruleview-highlight"), + "margin text property is not highlighted."); + ok(computed.hasAttribute("filter-open"), "margin computed list is open."); + + ok(computed.children[0].classList.contains("ruleview-highlight"), + "margin-top computed property is correctly highlighted."); + ok(computed.children[1].classList.contains("ruleview-highlight"), + "margin-right computed property is correctly highlighted."); + ok(computed.children[2].classList.contains("ruleview-highlight"), + "margin-bottom computed property is correctly highlighted."); + ok(computed.children[3].classList.contains("ruleview-highlight"), + "margin-left computed property is correctly highlighted."); +} + +function* testRemoveTextInFilter(inspector, view) { + info("Press backspace and set filter text to \"margin\""); + + let win = view.styleWindow; + let searchField = view.searchField; + + searchField.focus(); + EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win); + yield inspector.once("ruleview-filtered"); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let ruleEditor = rule.textProps[0].editor; + let computed = ruleEditor.computed; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(!ruleEditor.expander.getAttribute("open"), "Expander is closed."); + ok(ruleEditor.container.classList.contains("ruleview-highlight"), + "margin text property is correctly highlighted."); + ok(!computed.hasAttribute("filter-open"), "margin computed list is closed."); + + ok(computed.children[0].classList.contains("ruleview-highlight"), + "margin-top computed property is correctly highlighted."); + ok(computed.children[1].classList.contains("ruleview-highlight"), + "margin-right computed property is correctly highlighted."); + ok(computed.children[2].classList.contains("ruleview-highlight"), + "margin-bottom computed property is correctly highlighted."); + ok(computed.children[3].classList.contains("ruleview-highlight"), + "margin-left computed property is correctly highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js new file mode 100644 index 000000000..1d8063419 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js @@ -0,0 +1,49 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly in the computed list +// for color values. + +// The color format here is chosen to match the default returned by +// CssColor.toString. +const SEARCH = "background-color: rgb(243, 243, 243)"; + +const TEST_URI = ` + <style type="text/css"> + .testclass { + background: rgb(243, 243, 243) none repeat scroll 0% 0%; + } + </style> + <div class="testclass">Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode(".testclass", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let ruleEditor = rule.textProps[0].editor; + let computed = ruleEditor.computed; + + is(rule.selectorText, ".testclass", "Second rule is .testclass."); + ok(ruleEditor.expander.getAttribute("open"), "Expander is open."); + ok(!ruleEditor.container.classList.contains("ruleview-highlight"), + "background property is not highlighted."); + ok(computed.hasAttribute("filter-open"), "background computed list is open."); + ok(computed.children[0].classList.contains("ruleview-highlight"), + "background-color computed property is highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js new file mode 100644 index 000000000..05b8b01eb --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js @@ -0,0 +1,63 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly in the computed list +// for newly modified property values. + +const SEARCH = "0px"; + +const TEST_URI = ` + <style type='text/css'> + #testid { + margin: 4px; + top: 0px; + } + </style> + <h1 id='testid'>Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testModifyPropertyValueFilter(inspector, view); +}); + +function* testModifyPropertyValueFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let propEditor = rule.textProps[0].editor; + let computed = propEditor.computed; + let editor = yield focusEditableField(view, propEditor.valueSpan); + + info("Check that the correct rules are visible"); + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(!propEditor.container.classList.contains("ruleview-highlight"), + "margin text property is not highlighted."); + ok(rule.textProps[1].editor.container.classList + .contains("ruleview-highlight"), + "top text property is correctly highlighted."); + + let onBlur = once(editor.input, "blur"); + let onModification = view.once("ruleview-changed"); + EventUtils.sendString("4px 0px", view.styleWindow); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onBlur; + yield onModification; + + ok(propEditor.container.classList.contains("ruleview-highlight"), + "margin text property is correctly highlighted."); + ok(!computed.hasAttribute("filter-open"), "margin computed list is closed."); + ok(!computed.children[0].classList.contains("ruleview-highlight"), + "margin-top computed property is not highlighted."); + ok(computed.children[1].classList.contains("ruleview-highlight"), + "margin-right computed property is correctly highlighted."); + ok(!computed.children[2].classList.contains("ruleview-highlight"), + "margin-bottom computed property is not highlighted."); + ok(computed.children[3].classList.contains("ruleview-highlight"), + "margin-left computed property is correctly highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js new file mode 100644 index 000000000..c8b1e0869 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js @@ -0,0 +1,92 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the expanded computed list for a property remains open after +// clearing the rule view search filter. + +const SEARCH = "0px"; + +const TEST_URI = ` + <style type="text/css"> + #testid { + margin: 4px 0px; + } + .testclass { + background-color: red; + } + </style> + <h1 id="testid" class="testclass">Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testOpenExpanderAndAddTextInFilter(inspector, view); + yield testClearSearchFilter(inspector, view); +}); + +function* testOpenExpanderAndAddTextInFilter(inspector, view) { + let rule = getRuleViewRuleEditor(view, 1).rule; + let ruleEditor = rule.textProps[0].editor; + let computed = ruleEditor.computed; + + info("Opening the computed list of margin property"); + ruleEditor.expander.click(); + + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(ruleEditor.expander.getAttribute("open"), "Expander is open."); + ok(ruleEditor.container.classList.contains("ruleview-highlight"), + "margin text property is correctly highlighted."); + ok(!computed.hasAttribute("filter-open"), + "margin computed list does not contain filter-open class."); + ok(computed.hasAttribute("user-open"), + "margin computed list contains user-open attribute."); + + ok(!computed.children[0].classList.contains("ruleview-highlight"), + "margin-top computed property is not highlighted."); + ok(computed.children[1].classList.contains("ruleview-highlight"), + "margin-right computed property is correctly highlighted."); + ok(!computed.children[2].classList.contains("ruleview-highlight"), + "margin-bottom computed property is not highlighted."); + ok(computed.children[3].classList.contains("ruleview-highlight"), + "margin-left computed property is correctly highlighted."); +} + +function* testClearSearchFilter(inspector, view) { + info("Clearing the search filter"); + + let searchField = view.searchField; + let searchClearButton = view.searchClearButton; + let onRuleViewFiltered = inspector.once("ruleview-filtered"); + + EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, + view.styleWindow); + + yield onRuleViewFiltered; + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared"); + ok(!view.styleDocument.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted"); + + let ruleEditor = getRuleViewRuleEditor(view, 1).rule.textProps[0].editor; + let computed = ruleEditor.computed; + + ok(ruleEditor.expander.getAttribute("open"), "Expander is open."); + ok(!computed.hasAttribute("filter-open"), + "margin computed list does not contain filter-open class."); + ok(computed.hasAttribute("user-open"), + "margin computed list contains user-open attribute."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js new file mode 100644 index 000000000..3e634b76e --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js @@ -0,0 +1,74 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view overriden search filter works properly for +// overridden properties. + +const TEST_URI = ` + <style type='text/css'> + #testid { + width: 100%; + } + h1 { + width: 50%; + } + </style> + <h1 id='testid' class='testclass'>Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testFilterOverriddenProperty(inspector, view); +}); + +function* testFilterOverriddenProperty(inspector, ruleView) { + info("Check that the correct rules are visible"); + is(ruleView.element.children.length, 3, "Should have 3 rules."); + + let rule = getRuleViewRuleEditor(ruleView, 1).rule; + let textPropEditor = rule.textProps[0].editor; + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(!textPropEditor.element.classList.contains("ruleview-overridden"), + "width property is not overridden."); + ok(textPropEditor.filterProperty.hidden, + "Overridden search button is hidden."); + + rule = getRuleViewRuleEditor(ruleView, 2).rule; + textPropEditor = rule.textProps[0].editor; + is(rule.selectorText, "h1", "Third rule is h1."); + ok(textPropEditor.element.classList.contains("ruleview-overridden"), + "width property is overridden."); + ok(!textPropEditor.filterProperty.hidden, + "Overridden search button is not hidden."); + + let searchField = ruleView.searchField; + let onRuleViewFiltered = inspector.once("ruleview-filtered"); + + info("Click the overridden search"); + textPropEditor.filterProperty.click(); + yield onRuleViewFiltered; + + info("Check that the overridden search is applied"); + is(searchField.value, "`width`", "The search field value is width."); + + rule = getRuleViewRuleEditor(ruleView, 1).rule; + textPropEditor = rule.textProps[0].editor; + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(textPropEditor.container.classList.contains("ruleview-highlight"), + "width property is correctly highlighted."); + + rule = getRuleViewRuleEditor(ruleView, 2).rule; + textPropEditor = rule.textProps[0].editor; + is(rule.selectorText, "h1", "Third rule is h1."); + ok(textPropEditor.container.classList.contains("ruleview-highlight"), + "width property is correctly highlighted."); + ok(textPropEditor.element.classList.contains("ruleview-overridden"), + "width property is overridden."); + ok(!textPropEditor.filterProperty.hidden, + "Overridden search button is not hidden."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js new file mode 100644 index 000000000..4dd1c951d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js @@ -0,0 +1,91 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter and clear button works properly. + +const TEST_URI = ` + <style type="text/css"> + #testid, h1 { + background-color: #00F !important; + } + .testclass { + width: 100%; + } + </style> + <h1 id="testid" class="testclass">Styled Node</h1> +`; + +const TEST_DATA = [ + { + desc: "Tests that the search filter works properly for property names", + search: "color" + }, + { + desc: "Tests that the search filter works properly for property values", + search: "00F" + }, + { + desc: "Tests that the search filter works properly for property line input", + search: "background-color:#00F" + }, + { + desc: "Tests that the search filter works properly for parsed property " + + "names", + search: "background:" + }, + { + desc: "Tests that the search filter works properly for parsed property " + + "values", + search: ":00F" + }, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + for (let data of TEST_DATA) { + info(data.desc); + yield setSearchFilter(view, data.search); + yield checkRules(view); + yield clearSearchAndCheckRules(view); + } +} + +function* checkRules(view) { + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + + is(rule.selectorText, "#testid, h1", "Second rule is #testid, h1."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "background-color text property is correctly highlighted."); +} + +function* clearSearchAndCheckRules(view) { + let doc = view.styleDocument; + let win = view.styleWindow; + let searchField = view.searchField; + let searchClearButton = view.searchClearButton; + + info("Clearing the search filter"); + EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win); + yield view.inspector.once("ruleview-filtered"); + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared."); + ok(!doc.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_02.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_02.js new file mode 100644 index 000000000..c23e7be62 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_02.js @@ -0,0 +1,32 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for keyframe rule +// selectors. + +const SEARCH = "20%"; +const TEST_URI = URL_ROOT + "doc_keyframeanimation.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode("#boxy", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let ruleEditor = getRuleViewRuleEditor(view, 2, 0); + + is(ruleEditor.rule.domRule.keyText, "20%", "Second rule is 20%."); + ok(ruleEditor.selectorText.classList.contains("ruleview-highlight"), + "20% selector is highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_03.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_03.js new file mode 100644 index 000000000..89280f0eb --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_03.js @@ -0,0 +1,39 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for inline styles. + +const SEARCH = "color"; + +const TEST_URI = ` + <style type="text/css"> + #testid { + width: 100%; + } + </style> + <div id="testid" style="background-color:aliceblue">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 1, "Should have 1 rule."); + + let rule = getRuleViewRuleEditor(view, 0).rule; + + is(rule.selectorText, "element", "First rule is inline element."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "background-color text property is correctly highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js new file mode 100644 index 000000000..5804d74ac --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js @@ -0,0 +1,76 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly when modifying the +// existing search filter value. + +const SEARCH = "00F"; + +const TEST_URI = ` + <style type="text/css"> + #testid { + background-color: #00F; + } + .testclass { + width: 100%; + } + </style> + <div id="testid" class="testclass">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); + yield testRemoveTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "background-color text property is correctly highlighted."); +} + +function* testRemoveTextInFilter(inspector, view) { + info("Press backspace and set filter text to \"00\""); + + let win = view.styleWindow; + let searchField = view.searchField; + + searchField.focus(); + EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win); + yield inspector.once("ruleview-filtered"); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 3, "Should have 3 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "background-color text property is correctly highlighted."); + + rule = getRuleViewRuleEditor(view, 2).rule; + + is(rule.selectorText, ".testclass", "Second rule is .testclass."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "width text property is correctly highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_05.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_05.js new file mode 100644 index 000000000..9388dd47e --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_05.js @@ -0,0 +1,33 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for stylesheet source. + +const SEARCH = "doc_urls_clickable.css"; +const TEST_URI = URL_ROOT + "doc_urls_clickable.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode(".relative1", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let source = rule.textProps[0].editor.ruleEditor.source; + + is(rule.selectorText, ".relative1", "Second rule is .relative1."); + ok(source.classList.contains("ruleview-highlight"), + "stylesheet source is correctly highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_06.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_06.js new file mode 100644 index 000000000..67b02ab73 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_06.js @@ -0,0 +1,27 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter does not highlight the source with +// input that could be parsed as a property line. + +const SEARCH = "doc_urls_clickable.css: url"; +const TEST_URI = URL_ROOT + "doc_urls_clickable.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode(".relative1", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 1, "Should have 1 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js new file mode 100644 index 000000000..16b047d8d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js @@ -0,0 +1,62 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for newly modified +// property name. + +const SEARCH = "e"; + +const TEST_URI = ` + <style type='text/css'> + #testid { + width: 100%; + height: 50%; + } + </style> + <h1 id='testid'>Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Enter the test value in the search filter"); + yield setSearchFilter(view, SEARCH); + + info("Focus the width property name"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let rule = ruleEditor.rule; + let propEditor = rule.textProps[0].editor; + yield focusEditableField(view, propEditor.nameSpan); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(!propEditor.container.classList.contains("ruleview-highlight"), + "width text property is not highlighted."); + ok(rule.textProps[1].editor.container.classList + .contains("ruleview-highlight"), + "height text property is correctly highlighted."); + + info("Change the width property to margin-left"); + EventUtils.sendString("margin-left", view.styleWindow); + + info("Submit the change"); + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + ok(propEditor.container.classList.contains("ruleview-highlight"), + "margin-left text property is correctly highlighted."); + + // After pressing return on the property name, the value has been focused + // automatically. Blur it now and wait for the rule-view to refresh to avoid + // pending requests. + onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield onRuleViewChanged; +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js new file mode 100644 index 000000000..1a3c0de59 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js @@ -0,0 +1,53 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for newly modified +// property value. + +const SEARCH = "100%"; + +const TEST_URI = ` + <style type='text/css'> + #testid { + width: 100%; + height: 50%; + } + </style> + <h1 id='testid'>Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Enter the test value in the search filter"); + yield setSearchFilter(view, SEARCH); + + info("Focus the height property value"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let rule = ruleEditor.rule; + let propEditor = rule.textProps[1].editor; + yield focusEditableField(view, propEditor.valueSpan); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "width text property is correctly highlighted."); + ok(!propEditor.container.classList.contains("ruleview-highlight"), + "height text property is not highlighted."); + + info("Change the height property value to 100%"); + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.sendString("100%", view.styleWindow); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield onRuleViewChanged; + + ok(propEditor.container.classList.contains("ruleview-highlight"), + "height text property is correctly highlighted."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js new file mode 100644 index 000000000..620e5d336 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js @@ -0,0 +1,73 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for newly added +// property. + +const SEARCH = "100%"; + +const TEST_URI = ` + <style type='text/css'> + #testid { + width: 100%; + height: 50%; + } + </style> + <h1 id='testid'>Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + + info("Enter the test value in the search filter"); + yield setSearchFilter(view, SEARCH); + + info("Start entering a new property in the rule"); + let ruleEditor = getRuleViewRuleEditor(view, 1); + let rule = ruleEditor.rule; + let editor = yield focusNewRuleViewProperty(ruleEditor); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "width text property is correctly highlighted."); + ok(!rule.textProps[1].editor.container.classList + .contains("ruleview-highlight"), + "height text property is not highlighted."); + + info("Test creating a new property"); + + info("Entering margin-left in the property name editor"); + // Changing the value doesn't cause a rule-view refresh, no need to wait for + // ruleview-changed here. + editor.input.value = "margin-left"; + + info("Pressing return to commit and focus the new value field"); + let onRuleViewChanged = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onRuleViewChanged; + + // Getting the new value editor after focus + editor = inplaceEditor(view.styleDocument.activeElement); + let propEditor = ruleEditor.rule.textProps[2].editor; + + info("Entering a value and bluring the field to expect a rule change"); + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.value = "100%"; + view.throttle.flush(); + yield onRuleViewChanged; + + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.blur(); + yield onRuleViewChanged; + + ok(propEditor.container.classList.contains("ruleview-highlight"), + "margin-left text property is correctly highlighted."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js new file mode 100644 index 000000000..ac336591d --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js @@ -0,0 +1,84 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter works properly for rule selectors. + +const TEST_URI = ` + <style type="text/css"> + html, body, div { + background-color: #00F; + } + #testid { + width: 100%; + } + </style> + <div id="testid" class="testclass">Styled Node</div> +`; + +const TEST_DATA = [ + { + desc: "Tests that the search filter works properly for a single rule " + + "selector", + search: "#test", + selectorText: "#testid", + index: 0 + }, + { + desc: "Tests that the search filter works properly for multiple rule " + + "selectors", + search: "body", + selectorText: "html, body, div", + index: 2 + } +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + for (let data of TEST_DATA) { + info(data.desc); + yield setSearchFilter(view, data.search); + yield checkRules(view, data); + yield clearSearchAndCheckRules(view); + } +} + +function* checkRules(view, data) { + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + is(ruleEditor.rule.selectorText, data.selectorText, + "Second rule is " + data.selectorText + "."); + ok(ruleEditor.selectorText.children[data.index].classList + .contains("ruleview-highlight"), + data.selectorText + " selector is highlighted."); +} + +function* clearSearchAndCheckRules(view) { + let doc = view.styleDocument; + let win = view.styleWindow; + let searchField = view.searchField; + let searchClearButton = view.searchClearButton; + + info("Clearing the search filter"); + EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win); + yield view.inspector.once("ruleview-filtered"); + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared."); + ok(!doc.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js new file mode 100644 index 000000000..349f1b9b3 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js @@ -0,0 +1,83 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test rule view search filter context menu works properly. + +const TEST_INPUT = "h1"; +const TEST_URI = "<h1>test filter context menu</h1>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {toolbox, inspector, view} = yield openRuleView(); + yield selectNode("h1", inspector); + + let win = view.styleWindow; + let searchField = view.searchField; + let searchContextMenu = toolbox.textBoxContextMenuPopup; + ok(searchContextMenu, + "The search filter context menu is loaded in the rule view"); + + let cmdUndo = searchContextMenu.querySelector("[command=cmd_undo]"); + let cmdDelete = searchContextMenu.querySelector("[command=cmd_delete]"); + let cmdSelectAll = searchContextMenu.querySelector("[command=cmd_selectAll]"); + let cmdCut = searchContextMenu.querySelector("[command=cmd_cut]"); + let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]"); + let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]"); + + info("Opening context menu"); + + emptyClipboard(); + + let onFocus = once(searchField, "focus"); + searchField.focus(); + yield onFocus; + + let onContextMenuPopup = once(searchContextMenu, "popupshowing"); + EventUtils.synthesizeMouse(searchField, 2, 2, + {type: "contextmenu", button: 2}, win); + yield onContextMenuPopup; + + is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled"); + is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled"); + is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled"); + + // Cut/Copy items are enabled in context menu even if there + // is no selection. See also Bug 1303033 + is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled"); + is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled"); + + if (isWindows()) { + // emptyClipboard only works on Windows (666254), assert paste only for this OS. + is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled"); + } + + info("Closing context menu"); + let onContextMenuHidden = once(searchContextMenu, "popuphidden"); + searchContextMenu.hidePopup(); + yield onContextMenuHidden; + + info("Copy text in search field using the context menu"); + searchField.value = TEST_INPUT; + searchField.select(); + EventUtils.synthesizeMouse(searchField, 2, 2, + {type: "contextmenu", button: 2}, win); + yield onContextMenuPopup; + yield waitForClipboardPromise(() => cmdCopy.click(), TEST_INPUT); + searchContextMenu.hidePopup(); + yield onContextMenuHidden; + + info("Reopen context menu and check command properties"); + EventUtils.synthesizeMouse(searchField, 2, 2, + {type: "contextmenu", button: 2}, win); + yield onContextMenuPopup; + + is(cmdUndo.getAttribute("disabled"), "", "cmdUndo is enabled"); + is(cmdDelete.getAttribute("disabled"), "", "cmdDelete is enabled"); + is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled"); + is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled"); + is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled"); + is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js new file mode 100644 index 000000000..21848dce8 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js @@ -0,0 +1,65 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view search filter escape keypress will clear the search +// field. + +const SEARCH = "00F"; + +const TEST_URI = ` + <style type="text/css"> + #testid { + background-color: #00F; + } + .testclass { + width: 100%; + } + </style> + <div id="testid" class="testclass">Styled Node</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); + yield testEscapeKeypress(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(rule.textProps[0].editor.container.classList + .contains("ruleview-highlight"), + "background-color text property is correctly highlighted."); +} + +function* testEscapeKeypress(inspector, view) { + info("Pressing the escape key on search filter"); + + let doc = view.styleDocument; + let win = view.styleWindow; + let searchField = view.searchField; + let onRuleViewFiltered = inspector.once("ruleview-filtered"); + + searchField.focus(); + EventUtils.synthesizeKey("VK_ESCAPE", {}, win); + yield onRuleViewFiltered; + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared"); + ok(!doc.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js new file mode 100644 index 000000000..b3f4ef364 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js @@ -0,0 +1,171 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that properties can be selected and copied from the rule view + +const osString = Services.appinfo.OS; + +const TEST_URI = ` + <style type="text/css"> + html { + color: #000000; + } + span { + font-variant: small-caps; color: #000000; + } + .nomatches { + color: #ff0000; + } + </style> + <div id="first" style="margin: 10em; + font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA"> + <h1>Some header text</h1> + <p id="salutation" style="font-size: 12pt">hi.</p> + <p id="body" style="font-size: 12pt">I am a test-case. This text exists + solely to provide some things to <span style="color: yellow"> + highlight</span> and <span style="font-weight: bold">count</span> + style list-items in the box at right. If you are reading this, + you should go do something else instead. Maybe read a book. Or better + yet, write some test-cases for another bit of code. + <span style="font-style: italic">some text</span></p> + <p id="closing">more text</p> + <p>even more text</p> + </div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("div", inspector); + yield checkCopySelection(view); + yield checkSelectAll(view); + yield checkCopyEditorValue(view); +}); + +function* checkCopySelection(view) { + info("Testing selection copy"); + + let contentDoc = view.styleDocument; + let win = view.styleWindow; + let prop = contentDoc.querySelector(".ruleview-property"); + let values = contentDoc.querySelectorAll(".ruleview-propertyvaluecontainer"); + + let range = contentDoc.createRange(); + range.setStart(prop, 0); + range.setEnd(values[4], 2); + win.getSelection().addRange(range); + info("Checking that _Copy() returns the correct clipboard value"); + + let expectedPattern = " margin: 10em;[\\r\\n]+" + + " font-size: 14pt;[\\r\\n]+" + + " font-family: helvetica, sans-serif;[\\r\\n]+" + + " color: #AAA;[\\r\\n]+" + + "}[\\r\\n]+" + + "html {[\\r\\n]+" + + " color: #000000;[\\r\\n]*"; + + let allMenuItems = openStyleContextMenuAndGetAllItems(view, prop); + let menuitemCopy = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy")); + + ok(menuitemCopy.visible, + "Copy menu item is displayed as expected"); + + try { + yield waitForClipboardPromise(() => menuitemCopy.click(), + () => checkClipboardData(expectedPattern)); + } catch (e) { + failedClipboard(expectedPattern); + } +} + +function* checkSelectAll(view) { + info("Testing select-all copy"); + + let contentDoc = view.styleDocument; + let prop = contentDoc.querySelector(".ruleview-property"); + + info("Checking that _SelectAll() then copy returns the correct " + + "clipboard value"); + view._contextmenu._onSelectAll(); + let expectedPattern = "element {[\\r\\n]+" + + " margin: 10em;[\\r\\n]+" + + " font-size: 14pt;[\\r\\n]+" + + " font-family: helvetica, sans-serif;[\\r\\n]+" + + " color: #AAA;[\\r\\n]+" + + "}[\\r\\n]+" + + "html {[\\r\\n]+" + + " color: #000000;[\\r\\n]+" + + "}[\\r\\n]*"; + + let allMenuItems = openStyleContextMenuAndGetAllItems(view, prop); + let menuitemCopy = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy")); + + ok(menuitemCopy.visible, + "Copy menu item is displayed as expected"); + + try { + yield waitForClipboardPromise(() => menuitemCopy.click(), + () => checkClipboardData(expectedPattern)); + } catch (e) { + failedClipboard(expectedPattern); + } +} + +function* checkCopyEditorValue(view) { + info("Testing CSS property editor value copy"); + + let ruleEditor = getRuleViewRuleEditor(view, 0); + let propEditor = ruleEditor.rule.textProps[0].editor; + + let editor = yield focusEditableField(view, propEditor.valueSpan); + + info("Checking that copying a css property value editor returns the correct" + + " clipboard value"); + + let expectedPattern = "10em"; + + let allMenuItems = openStyleContextMenuAndGetAllItems(view, editor.input); + let menuitemCopy = allMenuItems.find(item => item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy")); + + ok(menuitemCopy.visible, + "Copy menu item is displayed as expected"); + + try { + yield waitForClipboardPromise(() => menuitemCopy.click(), + () => checkClipboardData(expectedPattern)); + } catch (e) { + failedClipboard(expectedPattern); + } +} + +function checkClipboardData(expectedPattern) { + let actual = SpecialPowers.getClipboardData("text/unicode"); + let expectedRegExp = new RegExp(expectedPattern, "g"); + return expectedRegExp.test(actual); +} + +function failedClipboard(expectedPattern) { + // Format expected text for comparison + let terminator = osString == "WINNT" ? "\r\n" : "\n"; + expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator); + expectedPattern = expectedPattern.replace(/\\\(/g, "("); + expectedPattern = expectedPattern.replace(/\\\)/g, ")"); + + let actual = SpecialPowers.getClipboardData("text/unicode"); + + // Trim the right hand side of our strings. This is because expectedPattern + // accounts for windows sometimes adding a newline to our copied data. + expectedPattern = expectedPattern.trimRight(); + actual = actual.trimRight(); + + dump("TEST-UNEXPECTED-FAIL | Clipboard text does not match expected ... " + + "results (escaped for accurate comparison):\n"); + info("Actual: " + escape(actual)); + info("Expected: " + escape(expectedPattern)); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js new file mode 100644 index 000000000..54e25c399 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js @@ -0,0 +1,38 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the selector highlighter is hidden on page navigation. + +const TEST_URI = ` + <style type="text/css"> + body, p, td { + background: red; + } + </style> + Test the selector highlighter +`; + +const TEST_URI_2 = "data:text/html,<html><body>test</body></html>"; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + let highlighters = view.highlighters; + + info("Clicking on a selector icon"); + let icon = getRuleViewSelectorHighlighterIcon(view, "body, p, td"); + + let onToggled = view.once("ruleview-selectorhighlighter-toggled"); + EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow); + let isVisible = yield onToggled; + + ok(highlighters.selectorHighlighterShown, "The selectorHighlighterShown is set."); + ok(view.selectorHighlighter, "The selectorhighlighter instance was created"); + ok(isVisible, "The toggle event says the highlighter is visible"); + + yield navigateTo(inspector, TEST_URI_2); + ok(!highlighters.selectorHighlighterShown, "The selectorHighlighterShown is unset."); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js new file mode 100644 index 000000000..4c8853e02 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js @@ -0,0 +1,35 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the selector highlighter is created when clicking on a selector +// icon in the rule view. + +const TEST_URI = ` + <style type="text/css"> + body, p, td { + background: red; + } + </style> + Test the selector highlighter +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {view} = yield openRuleView(); + + ok(!view.selectorHighlighter, + "No selectorhighlighter exist in the rule-view"); + + info("Clicking on a selector icon"); + let icon = getRuleViewSelectorHighlighterIcon(view, "body, p, td"); + + let onToggled = view.once("ruleview-selectorhighlighter-toggled"); + EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow); + let isVisible = yield onToggled; + + ok(view.selectorHighlighter, "The selectorhighlighter instance was created"); + ok(isVisible, "The toggle event says the highlighter is visible"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js new file mode 100644 index 000000000..33f73e587 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js @@ -0,0 +1,78 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the selector highlighter is shown when clicking on a selector icon +// in the rule-view + +// Note that in this test, we mock the highlighter front, merely testing the +// behavior of the style-inspector UI for now + +const TEST_URI = ` + <style type="text/css"> + body { + background: red; + } + p { + color: white; + } + </style> + <p>Testing the selector highlighter</p> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + // Mock the highlighter front to get the reference of the NodeFront + let HighlighterFront = { + isShown: false, + nodeFront: null, + options: null, + show: function (nodeFront, options) { + this.nodeFront = nodeFront; + this.options = options; + this.isShown = true; + }, + hide: function () { + this.nodeFront = null; + this.options = null; + this.isShown = false; + } + }; + + // Inject the mock highlighter in the rule-view + view.selectorHighlighter = HighlighterFront; + + let icon = getRuleViewSelectorHighlighterIcon(view, "body"); + + info("Checking that the HighlighterFront's show/hide methods are called"); + + info("Clicking once on the body selector highlighter icon"); + yield clickSelectorIcon(icon, view); + ok(HighlighterFront.isShown, "The highlighter is shown"); + + info("Clicking once again on the body selector highlighter icon"); + yield clickSelectorIcon(icon, view); + ok(!HighlighterFront.isShown, "The highlighter is hidden"); + + info("Checking that the right NodeFront reference and options are passed"); + yield selectNode("p", inspector); + icon = getRuleViewSelectorHighlighterIcon(view, "p"); + + yield clickSelectorIcon(icon, view); + is(HighlighterFront.nodeFront.tagName, "P", + "The right NodeFront is passed to the highlighter (1)"); + is(HighlighterFront.options.selector, "p", + "The right selector option is passed to the highlighter (1)"); + + yield selectNode("body", inspector); + icon = getRuleViewSelectorHighlighterIcon(view, "body"); + yield clickSelectorIcon(icon, view); + is(HighlighterFront.nodeFront.tagName, "BODY", + "The right NodeFront is passed to the highlighter (2)"); + is(HighlighterFront.options.selector, "body", + "The right selector option is passed to the highlighter (2)"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js new file mode 100644 index 000000000..1ffbac012 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js @@ -0,0 +1,78 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the selector highlighter toggling mechanism works correctly. + +// Note that in this test, we mock the highlighter front, merely testing the +// behavior of the style-inspector UI for now + +const TEST_URI = ` + <style type="text/css"> + div {text-decoration: underline;} + .node-1 {color: red;} + .node-2 {color: green;} + </style> + <div class="node-1">Node 1</div> + <div class="node-2">Node 2</div> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + // Mock the highlighter front. + let HighlighterFront = { + isShown: false, + show: function () { + this.isShown = true; + }, + hide: function () { + this.isShown = false; + } + }; + + // Inject the mock highlighter in the rule-view + view.selectorHighlighter = HighlighterFront; + + info("Select .node-1 and click on the .node-1 selector icon"); + yield selectNode(".node-1", inspector); + let icon = getRuleViewSelectorHighlighterIcon(view, ".node-1"); + yield clickSelectorIcon(icon, view); + ok(HighlighterFront.isShown, "The highlighter is shown"); + + info("With .node-1 still selected, click again on the .node-1 selector icon"); + yield clickSelectorIcon(icon, view); + ok(!HighlighterFront.isShown, "The highlighter is now hidden"); + + info("With .node-1 still selected, click on the div selector icon"); + icon = getRuleViewSelectorHighlighterIcon(view, "div"); + yield clickSelectorIcon(icon, view); + ok(HighlighterFront.isShown, "The highlighter is shown again"); + + info("With .node-1 still selected, click again on the .node-1 selector icon"); + icon = getRuleViewSelectorHighlighterIcon(view, ".node-1"); + yield clickSelectorIcon(icon, view); + ok(HighlighterFront.isShown, + "The highlighter is shown again since the clicked selector was different"); + + info("Selecting .node-2"); + yield selectNode(".node-2", inspector); + ok(HighlighterFront.isShown, + "The highlighter is still shown after selection"); + + info("With .node-2 selected, click on the div selector icon"); + icon = getRuleViewSelectorHighlighterIcon(view, "div"); + yield clickSelectorIcon(icon, view); + ok(HighlighterFront.isShown, + "The highlighter is shown still since the selected was different"); + + info("Switching back to .node-1 and clicking on the div selector"); + yield selectNode(".node-1", inspector); + icon = getRuleViewSelectorHighlighterIcon(view, "div"); + yield clickSelectorIcon(icon, view); + ok(!HighlighterFront.isShown, + "The highlighter is hidden now that the same selector was clicked"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js new file mode 100644 index 000000000..b770f8127 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js @@ -0,0 +1,53 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the selector highlighter is shown when clicking on a selector icon +// for the 'element {}' rule + +// Note that in this test, we mock the highlighter front, merely testing the +// behavior of the style-inspector UI for now + +const TEST_URI = ` +<p>Testing the selector highlighter for the 'element {}' rule</p> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + // Mock the highlighter front to get the reference of the NodeFront + let HighlighterFront = { + isShown: false, + nodeFront: null, + options: null, + show: function (nodeFront, options) { + this.nodeFront = nodeFront; + this.options = options; + this.isShown = true; + }, + hide: function () { + this.nodeFront = null; + this.options = null; + this.isShown = false; + } + }; + // Inject the mock highlighter in the rule-view + view.selectorHighlighter = HighlighterFront; + + info("Checking that the right NodeFront reference and options are passed"); + yield selectNode("p", inspector); + let icon = getRuleViewSelectorHighlighterIcon(view, "element"); + + yield clickSelectorIcon(icon, view); + is(HighlighterFront.nodeFront.tagName, "P", + "The right NodeFront is passed to the highlighter (1)"); + is(HighlighterFront.options.selector, "body > p:nth-child(1)", + "The right selector option is passed to the highlighter (1)"); + ok(HighlighterFront.isShown, "The toggle event says the highlighter is visible"); + + yield clickSelectorIcon(icon, view); + ok(!HighlighterFront.isShown, "The toggle event says the highlighter is not visible"); +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js b/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js new file mode 100644 index 000000000..91422d57a --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js @@ -0,0 +1,144 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view selector text is highlighted correctly according +// to the components of the selector. + +const TEST_URI = [ + "<style type='text/css'>", + " h1 {}", + " h1#testid {}", + " h1 + p {}", + " div[hidden=\"true\"] {}", + " div[title=\"test\"][checked=true] {}", + " p:empty {}", + " p:lang(en) {}", + " .testclass:active {}", + " .testclass:focus {}", + " .testclass:hover {}", + "</style>", + "<h1>Styled Node</h1>", + "<p>Paragraph</p>", + "<h1 id=\"testid\">Styled Node</h1>", + "<div hidden=\"true\"></div>", + "<div title=\"test\" checked=\"true\"></div>", + "<p></p>", + "<p lang=\"en\">Paragraph<p>", + "<div class=\"testclass\">Styled Node</div>" +].join("\n"); + +const SELECTOR_ATTRIBUTE = "ruleview-selector-attribute"; +const SELECTOR_ELEMENT = "ruleview-selector"; +const SELECTOR_PSEUDO_CLASS = "ruleview-selector-pseudo-class"; +const SELECTOR_PSEUDO_CLASS_LOCK = "ruleview-selector-pseudo-class-lock"; + +const TEST_DATA = [ + { + node: "h1", + expected: [ + { value: "h1", class: SELECTOR_ELEMENT } + ] + }, + { + node: "h1 + p", + expected: [ + { value: "h1 + p", class: SELECTOR_ELEMENT } + ] + }, + { + node: "h1#testid", + expected: [ + { value: "h1#testid", class: SELECTOR_ELEMENT } + ] + }, + { + node: "div[hidden='true']", + expected: [ + { value: "div", class: SELECTOR_ELEMENT }, + { value: "[hidden=\"true\"]", class: SELECTOR_ATTRIBUTE } + ] + }, + { + node: "div[title=\"test\"][checked=\"true\"]", + expected: [ + { value: "div", class: SELECTOR_ELEMENT }, + { value: "[title=\"test\"]", class: SELECTOR_ATTRIBUTE }, + { value: "[checked=\"true\"]", class: SELECTOR_ATTRIBUTE } + ] + }, + { + node: "p:empty", + expected: [ + { value: "p", class: SELECTOR_ELEMENT }, + { value: ":empty", class: SELECTOR_PSEUDO_CLASS } + ] + }, + { + node: "p:lang(en)", + expected: [ + { value: "p", class: SELECTOR_ELEMENT }, + { value: ":lang(en)", class: SELECTOR_PSEUDO_CLASS } + ] + }, + { + node: ".testclass", + pseudoClass: ":active", + expected: [ + { value: ".testclass", class: SELECTOR_ELEMENT }, + { value: ":active", class: SELECTOR_PSEUDO_CLASS_LOCK } + ] + }, + { + node: ".testclass", + pseudoClass: ":focus", + expected: [ + { value: ".testclass", class: SELECTOR_ELEMENT }, + { value: ":focus", class: SELECTOR_PSEUDO_CLASS_LOCK } + ] + }, + { + node: ".testclass", + pseudoClass: ":hover", + expected: [ + { value: ".testclass", class: SELECTOR_ELEMENT }, + { value: ":hover", class: SELECTOR_PSEUDO_CLASS_LOCK } + ] + }, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + for (let {node, pseudoClass, expected} of TEST_DATA) { + yield selectNode(node, inspector); + + if (pseudoClass) { + let onRefresh = inspector.once("rule-view-refreshed"); + inspector.togglePseudoClass(pseudoClass); + yield onRefresh; + } + + let selectorContainer = + getRuleViewRuleEditor(view, 1).selectorText.firstChild; + + if (selectorContainer.children.length === expected.length) { + for (let i = 0; i < expected.length; i++) { + is(expected[i].value, selectorContainer.children[i].textContent, + "Got expected selector value: " + expected[i].value + " == " + + selectorContainer.children[i].textContent); + is(expected[i].class, selectorContainer.children[i].className, + "Got expected class name: " + expected[i].class + " == " + + selectorContainer.children[i].className); + } + } else { + for (let selector of selectorContainer.children) { + info("Actual selector components: { value: " + selector.textContent + + ", class: " + selector.className + " }\n"); + } + } + } +}); diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js new file mode 100644 index 000000000..dea9fff32 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js @@ -0,0 +1,182 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view strict search filter and clear button works properly +// in the computed list + +const TEST_URI = ` + <style type="text/css"> + #testid { + margin: 4px 0px 10px 44px; + } + .testclass { + background-color: red; + } + </style> + <h1 id="testid" class="testclass">Styled Node</h1> +`; + +const TEST_DATA = [ + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for property names", + search: "`margin-left`", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: false, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: true + }, + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for property values", + search: "`0px`", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: false, + isMarginRightHighlighted: true, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for parsed property names", + search: "`margin-left`:", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: false, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: true + }, + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for parsed property values", + search: ":`4px`", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for property line input", + search: "`margin-top`:`4px`", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for a parsed strict property name and non-strict " + + "property value", + search: "`margin-top`:4px", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, + { + desc: "Tests that the strict search filter works properly in the " + + "computed list for a parsed strict property value and non-strict " + + "property name", + search: "i:`4px`", + isExpanderOpen: true, + isFilterOpen: true, + isMarginHighlighted: false, + isMarginTopHighlighted: true, + isMarginRightHighlighted: false, + isMarginBottomHighlighted: false, + isMarginLeftHighlighted: false + }, +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + for (let data of TEST_DATA) { + info(data.desc); + yield setSearchFilter(view, data.search); + yield checkRules(view, data); + yield clearSearchAndCheckRules(view); + } +} + +function* checkRules(view, data) { + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let textPropEditor = rule.textProps[0].editor; + let computed = textPropEditor.computed; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + is(!!textPropEditor.expander.getAttribute("open"), data.isExpanderOpen, + "Got correct expander state."); + is(computed.hasAttribute("filter-open"), data.isFilterOpen, + "Got correct expanded state for margin computed list."); + is(textPropEditor.container.classList.contains("ruleview-highlight"), + data.isMarginHighlighted, + "Got correct highlight for margin text property."); + + is(computed.children[0].classList.contains("ruleview-highlight"), + data.isMarginTopHighlighted, + "Got correct highlight for margin-top computed property."); + is(computed.children[1].classList.contains("ruleview-highlight"), + data.isMarginRightHighlighted, + "Got correct highlight for margin-right computed property."); + is(computed.children[2].classList.contains("ruleview-highlight"), + data.isMarginBottomHighlighted, + "Got correct highlight for margin-bottom computed property."); + is(computed.children[3].classList.contains("ruleview-highlight"), + data.isMarginLeftHighlighted, + "Got correct highlight for margin-left computed property."); +} + +function* clearSearchAndCheckRules(view) { + let win = view.styleWindow; + let searchField = view.searchField; + let searchClearButton = view.searchClearButton; + + let rule = getRuleViewRuleEditor(view, 1).rule; + let textPropEditor = rule.textProps[0].editor; + let computed = textPropEditor.computed; + + info("Clearing the search filter"); + EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win); + yield view.inspector.once("ruleview-filtered"); + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared"); + ok(!view.styleDocument.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted"); + + ok(!textPropEditor.expander.getAttribute("open"), "Expander is closed."); + ok(!computed.hasAttribute("filter-open"), + "margin computed list is closed."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js new file mode 100644 index 000000000..50948e174 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js @@ -0,0 +1,130 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view strict search filter works properly for property +// names. + +const TEST_URI = ` + <style type="text/css"> + #testid { + width: 2%; + color: red; + } + .testclass { + width: 22%; + background-color: #00F; + } + </style> + <h1 id="testid" class="testclass">Styled Node</h1> +`; + +const TEST_DATA = [ + { + desc: "Tests that the strict search filter works properly for property " + + "names", + search: "`color`", + ruleCount: 2, + propertyIndex: 1 + }, + { + desc: "Tests that the strict search filter works properly for property " + + "values", + search: "`2%`", + ruleCount: 2, + propertyIndex: 0 + }, + { + desc: "Tests that the strict search filter works properly for parsed " + + "property names", + search: "`color`:", + ruleCount: 2, + propertyIndex: 1 + }, + { + desc: "Tests that the strict search filter works properly for parsed " + + "property values", + search: ":`2%`", + ruleCount: 2, + propertyIndex: 0 + }, + { + desc: "Tests that the strict search filter works properly for property " + + "line input", + search: "`width`:`2%`", + ruleCount: 2, + propertyIndex: 0 + }, + { + desc: "Tests that the search filter works properly for a parsed strict " + + "property name and non-strict property value.", + search: "`width`:2%", + ruleCount: 3, + propertyIndex: 0 + }, + { + desc: "Tests that the search filter works properly for a parsed strict " + + "property value and non-strict property name.", + search: "i:`2%`", + ruleCount: 2, + propertyIndex: 0 + } +]; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + for (let data of TEST_DATA) { + info(data.desc); + yield setSearchFilter(view, data.search); + yield checkRules(view, data); + yield clearSearchAndCheckRules(view); + } +} + +function* checkRules(view, data) { + info("Check that the correct rules are visible"); + is(view.element.children.length, data.ruleCount, + "Should have " + data.ruleCount + " rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + + is(rule.selectorText, "#testid", "Second rule is #testid."); + ok(rule.textProps[data.propertyIndex].editor.container.classList + .contains("ruleview-highlight"), + "Text property is correctly highlighted."); + + if (data.ruleCount > 2) { + rule = getRuleViewRuleEditor(view, 2).rule; + is(rule.selectorText, ".testclass", "Third rule is .testclass."); + ok(rule.textProps[data.propertyIndex].editor.container.classList + .contains("ruleview-highlight"), + "Text property is correctly highlighted."); + } +} + +function* clearSearchAndCheckRules(view) { + let doc = view.styleDocument; + let win = view.styleWindow; + let searchField = view.searchField; + let searchClearButton = view.searchClearButton; + + info("Clearing the search filter"); + EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win); + yield view.inspector.once("ruleview-filtered"); + + info("Check the search filter is cleared and no rules are highlighted"); + is(view.element.children.length, 3, "Should have 3 rules."); + ok(!searchField.value, "Search filter is cleared."); + ok(!doc.querySelectorAll(".ruleview-highlight").length, + "No rules are higlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js new file mode 100644 index 000000000..0c76f0518 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js @@ -0,0 +1,34 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view strict search filter works properly for stylesheet +// source. + +const SEARCH = "`doc_urls_clickable.css:1`"; +const TEST_URI = URL_ROOT + "doc_urls_clickable.html"; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNode(".relative1", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let rule = getRuleViewRuleEditor(view, 1).rule; + let source = rule.textProps[0].editor.ruleEditor.source; + + is(rule.selectorText, ".relative1", "Second rule is .relative1."); + ok(source.classList.contains("ruleview-highlight"), + "stylesheet source is correctly highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js new file mode 100644 index 000000000..0326b0e9c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js @@ -0,0 +1,44 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the rule view strict search filter works properly for selector +// values. + +const SEARCH = "`.testclass`"; + +const TEST_URI = ` + <style type="text/css"> + .testclass1 { + background-color: #00F; + } + .testclass { + color: red; + } + </style> + <h1 id="testid" class="testclass testclass1">Styled Node</h1> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + yield selectNode("#testid", inspector); + yield testAddTextInFilter(inspector, view); +}); + +function* testAddTextInFilter(inspector, view) { + yield setSearchFilter(view, SEARCH); + + info("Check that the correct rules are visible"); + is(view.element.children.length, 2, "Should have 2 rules."); + is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element", + "First rule is inline element."); + + let ruleEditor = getRuleViewRuleEditor(view, 1); + + is(ruleEditor.rule.selectorText, ".testclass", "Second rule is .testclass."); + ok(ruleEditor.selectorText.children[0].classList + .contains("ruleview-highlight"), ".testclass selector is highlighted."); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js new file mode 100644 index 000000000..927deb8ce --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js @@ -0,0 +1,203 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// FIXME: Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source"); + +// Test the links from the rule-view to the styleeditor + +const STYLESHEET_URL = "data:text/css," + encodeURIComponent( + ["#first {", + "color: blue", + "}"].join("\n")); + +const EXTERNAL_STYLESHEET_FILE_NAME = "doc_style_editor_link.css"; +const EXTERNAL_STYLESHEET_URL = URL_ROOT + EXTERNAL_STYLESHEET_FILE_NAME; + +const DOCUMENT_URL = "data:text/html;charset=utf-8," + encodeURIComponent(` + <html> + <head> + <title>Rule view style editor link test</title> + <style type="text/css"> + html { color: #000000; } + div { font-variant: small-caps; color: #000000; } + .nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; + font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA"> + </style> + <style> + div { font-weight: bold; } + </style> + <link rel="stylesheet" type="text/css" href="${STYLESHEET_URL}"> + <link rel="stylesheet" type="text/css" href="${EXTERNAL_STYLESHEET_URL}"> + </head> + <body> + <h1>Some header text</h1> + <p id="salutation" style="font-size: 12pt">hi.</p> + <p id="body" style="font-size: 12pt">I am a test-case. This text exists + solely to provide some things to + <span style="color: yellow" class="highlight"> + highlight</span> and <span style="font-weight: bold">count</span> + style list-items in the box at right. If you are reading this, + you should go do something else instead. Maybe read a book. Or better + yet, write some test-cases for another bit of code. + <span style="font-style: italic">some text</span></p> + <p id="closing">more text</p> + <p>even more text</p> + </div> + </body> + </html> +`); + +add_task(function* () { + yield addTab(DOCUMENT_URL); + let {toolbox, inspector, view, testActor} = yield openRuleView(); + yield selectNode("div", inspector); + + yield testInlineStyle(view); + yield testFirstInlineStyleSheet(view, toolbox, testActor); + yield testSecondInlineStyleSheet(view, toolbox, testActor); + yield testExternalStyleSheet(view, toolbox, testActor); + yield testDisabledStyleEditor(view, toolbox); +}); + +function* testInlineStyle(view) { + info("Testing inline style"); + + let onTab = waitForTab(); + info("Clicking on the first link in the rule-view"); + clickLinkByIndex(view, 0); + + let tab = yield onTab; + + let tabURI = tab.linkedBrowser.documentURI.spec; + ok(tabURI.startsWith("view-source:"), "View source tab is open"); + info("Closing tab"); + gBrowser.removeTab(tab); +} + +function* testFirstInlineStyleSheet(view, toolbox, testActor) { + info("Testing inline stylesheet"); + + info("Listening for toolbox switch to the styleeditor"); + let onSwitch = waitForStyleEditor(toolbox); + + info("Clicking an inline stylesheet"); + clickLinkByIndex(view, 4); + let editor = yield onSwitch; + + ok(true, "Switched to the style-editor panel in the toolbox"); + + yield validateStyleEditorSheet(editor, 0, testActor); +} + +function* testSecondInlineStyleSheet(view, toolbox, testActor) { + info("Testing second inline stylesheet"); + + info("Waiting for the stylesheet editor to be selected"); + let panel = toolbox.getCurrentPanel(); + let onSelected = panel.UI.once("editor-selected"); + + info("Switching back to the inspector panel in the toolbox"); + yield toolbox.selectTool("inspector"); + + info("Clicking on second inline stylesheet link"); + testRuleViewLinkLabel(view); + clickLinkByIndex(view, 3); + let editor = yield onSelected; + + is(toolbox.currentToolId, "styleeditor", + "The style editor is selected again"); + yield validateStyleEditorSheet(editor, 1, testActor); +} + +function* testExternalStyleSheet(view, toolbox, testActor) { + info("Testing external stylesheet"); + + info("Waiting for the stylesheet editor to be selected"); + let panel = toolbox.getCurrentPanel(); + let onSelected = panel.UI.once("editor-selected"); + + info("Switching back to the inspector panel in the toolbox"); + yield toolbox.selectTool("inspector"); + + info("Clicking on an external stylesheet link"); + testRuleViewLinkLabel(view); + clickLinkByIndex(view, 1); + let editor = yield onSelected; + + is(toolbox.currentToolId, "styleeditor", + "The style editor is selected again"); + yield validateStyleEditorSheet(editor, 2, testActor); +} + +function* validateStyleEditorSheet(editor, expectedSheetIndex, testActor) { + info("validating style editor stylesheet"); + is(editor.styleSheet.styleSheetIndex, expectedSheetIndex, + "loaded stylesheet index matches document stylesheet"); + + let href = editor.styleSheet.href || editor.styleSheet.nodeHref; + + let expectedHref = yield testActor.eval( + `content.document.styleSheets[${expectedSheetIndex}].href || + content.document.location.href`); + + is(href, expectedHref, "loaded stylesheet href matches document stylesheet"); +} + +function* testDisabledStyleEditor(view, toolbox) { + info("Testing with the style editor disabled"); + + info("Switching to the inspector panel in the toolbox"); + yield toolbox.selectTool("inspector"); + + info("Disabling the style editor"); + Services.prefs.setBoolPref("devtools.styleeditor.enabled", false); + gDevTools.emit("tool-unregistered", "styleeditor"); + + info("Clicking on a link"); + testUnselectableRuleViewLink(view, 1); + clickLinkByIndex(view, 1); + + is(toolbox.currentToolId, "inspector", "The click should have no effect"); + + info("Enabling the style editor"); + Services.prefs.setBoolPref("devtools.styleeditor.enabled", true); + gDevTools.emit("tool-registered", "styleeditor"); + + info("Clicking on a link"); + let onStyleEditorSelected = toolbox.once("styleeditor-selected"); + clickLinkByIndex(view, 1); + yield onStyleEditorSelected; + is(toolbox.currentToolId, "styleeditor", "Style Editor should be selected"); + + Services.prefs.clearUserPref("devtools.styleeditor.enabled"); +} + +function testRuleViewLinkLabel(view) { + let link = getRuleViewLinkByIndex(view, 2); + let labelElem = link.querySelector(".ruleview-rule-source-label"); + let value = labelElem.textContent; + let tooltipText = labelElem.getAttribute("title"); + + is(value, EXTERNAL_STYLESHEET_FILE_NAME + ":1", + "rule view stylesheet display value matches filename and line number"); + is(tooltipText, EXTERNAL_STYLESHEET_URL + ":1", + "rule view stylesheet tooltip text matches the full URI path"); +} + +function testUnselectableRuleViewLink(view, index) { + let link = getRuleViewLinkByIndex(view, index); + let unselectable = link.hasAttribute("unselectable"); + + ok(unselectable, "Rule view is unselectable"); +} + +function clickLinkByIndex(view, index) { + let link = getRuleViewLinkByIndex(view, index); + link.scrollIntoView(); + link.click(); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_urls-clickable.js b/devtools/client/inspector/rules/test/browser_rules_urls-clickable.js new file mode 100644 index 000000000..fb1211e3c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_urls-clickable.js @@ -0,0 +1,70 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests to make sure that URLs are clickable in the rule view + +const TEST_URI = URL_ROOT + "doc_urls_clickable.html"; +const TEST_IMAGE = URL_ROOT + "doc_test_image.png"; +const BASE_64_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAA" + + "FCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAA" + + "BJRU5ErkJggg=="; + +add_task(function* () { + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + yield selectNodes(inspector, view); +}); + +function* selectNodes(inspector, ruleView) { + let relative1 = ".relative1"; + let relative2 = ".relative2"; + let absolute = ".absolute"; + let inline = ".inline"; + let base64 = ".base64"; + let noimage = ".noimage"; + let inlineresolved = ".inline-resolved"; + + yield selectNode(relative1, inspector); + let relativeLink = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(relativeLink, "Link exists for relative1 node"); + is(relativeLink.getAttribute("href"), TEST_IMAGE, "href matches"); + + yield selectNode(relative2, inspector); + relativeLink = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(relativeLink, "Link exists for relative2 node"); + is(relativeLink.getAttribute("href"), TEST_IMAGE, "href matches"); + + yield selectNode(absolute, inspector); + let absoluteLink = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(absoluteLink, "Link exists for absolute node"); + is(absoluteLink.getAttribute("href"), TEST_IMAGE, "href matches"); + + yield selectNode(inline, inspector); + let inlineLink = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(inlineLink, "Link exists for inline node"); + is(inlineLink.getAttribute("href"), TEST_IMAGE, "href matches"); + + yield selectNode(base64, inspector); + let base64Link = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(base64Link, "Link exists for base64 node"); + is(base64Link.getAttribute("href"), BASE_64_URL, "href matches"); + + yield selectNode(inlineresolved, inspector); + let inlineResolvedLink = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(inlineResolvedLink, "Link exists for style tag node"); + is(inlineResolvedLink.getAttribute("href"), TEST_IMAGE, "href matches"); + + yield selectNode(noimage, inspector); + let noimageLink = ruleView.styleDocument + .querySelector(".ruleview-propertyvaluecontainer a"); + ok(!noimageLink, "There is no link for the node with no background image"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js new file mode 100644 index 000000000..e1bafff9b --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js @@ -0,0 +1,58 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that user agent styles are never editable via +// the UI + +const TEST_URI = ` + <blockquote type=cite> + <pre _moz_quote=true> + inspect <a href='foo' style='color:orange'>user agent</a> styles + </pre> + </blockquote> +`; + +var PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles"; + +add_task(function* () { + info("Starting the test with the pref set to true before toolbox is opened"); + Services.prefs.setBoolPref(PREF_UA_STYLES, true); + + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view} = yield openRuleView(); + + yield userAgentStylesUneditable(inspector, view); + + info("Resetting " + PREF_UA_STYLES); + Services.prefs.clearUserPref(PREF_UA_STYLES); +}); + +function* userAgentStylesUneditable(inspector, view) { + info("Making sure that UI is not editable for user agent styles"); + + yield selectNode("a", inspector); + let uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable); + + for (let rule of uaRules) { + ok(rule.editor.element.hasAttribute("uneditable"), + "UA rules have uneditable attribute"); + + let firstProp = rule.textProps.filter(p => !p.invisible)[0]; + + ok(!firstProp.editor.nameSpan._editable, + "nameSpan is not editable"); + ok(!firstProp.editor.valueSpan._editable, + "valueSpan is not editable"); + ok(!rule.editor.closeBrace._editable, "closeBrace is not editable"); + + let colorswatch = rule.editor.element + .querySelector(".ruleview-colorswatch"); + if (colorswatch) { + ok(!view.tooltips.colorPicker.swatches.has(colorswatch), + "The swatch is not editable"); + } + } +} diff --git a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js new file mode 100644 index 000000000..6852e3c03 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js @@ -0,0 +1,183 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that user agent styles are inspectable via rule view if +// it is preffed on. + +var PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles"; +const { PrefObserver } = require("devtools/client/styleeditor/utils"); + +const TEST_URI = URL_ROOT + "doc_author-sheet.html"; + +const TEST_DATA = [ + { + selector: "blockquote", + numUserRules: 1, + numUARules: 0 + }, + { + selector: "pre", + numUserRules: 1, + numUARules: 0 + }, + { + selector: "input[type=range]", + numUserRules: 1, + numUARules: 0 + }, + { + selector: "input[type=number]", + numUserRules: 1, + numUARules: 0 + }, + { + selector: "input[type=color]", + numUserRules: 1, + numUARules: 0 + }, + { + selector: "input[type=text]", + numUserRules: 1, + numUARules: 0 + }, + { + selector: "progress", + numUserRules: 1, + numUARules: 0 + }, + // Note that some tests below assume that the "a" selector is the + // last test in TEST_DATA. + { + selector: "a", + numUserRules: 3, + numUARules: 0 + } +]; + +add_task(function* () { + requestLongerTimeout(4); + + info("Starting the test with the pref set to true before toolbox is opened"); + yield setUserAgentStylesPref(true); + + yield addTab(TEST_URI); + let {inspector, view} = yield openRuleView(); + + info("Making sure that UA styles are visible on initial load"); + yield userAgentStylesVisible(inspector, view); + + info("Making sure that setting the pref to false hides UA styles"); + yield setUserAgentStylesPref(false); + yield userAgentStylesNotVisible(inspector, view); + + info("Making sure that resetting the pref to true shows UA styles again"); + yield setUserAgentStylesPref(true); + yield userAgentStylesVisible(inspector, view); + + info("Resetting " + PREF_UA_STYLES); + Services.prefs.clearUserPref(PREF_UA_STYLES); +}); + +function* setUserAgentStylesPref(val) { + info("Setting the pref " + PREF_UA_STYLES + " to: " + val); + + // Reset the pref and wait for PrefObserver to callback so UI + // has a chance to get updated. + let oncePrefChanged = defer(); + let prefObserver = new PrefObserver("devtools."); + prefObserver.on(PREF_UA_STYLES, oncePrefChanged.resolve); + Services.prefs.setBoolPref(PREF_UA_STYLES, val); + yield oncePrefChanged.promise; + prefObserver.off(PREF_UA_STYLES, oncePrefChanged.resolve); +} + +function* userAgentStylesVisible(inspector, view) { + info("Making sure that user agent styles are currently visible"); + + let userRules; + let uaRules; + + for (let data of TEST_DATA) { + yield selectNode(data.selector, inspector); + yield compareAppliedStylesWithUI(inspector, view, "ua"); + + userRules = view._elementStyle.rules.filter(rule=>rule.editor.isEditable); + uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable); + is(userRules.length, data.numUserRules, "Correct number of user rules"); + ok(uaRules.length > data.numUARules, "Has UA rules"); + } + + ok(userRules.some(rule => rule.matchedSelectors.length === 1), + "There is an inline style for element in user styles"); + + // These tests rely on the "a" selector being the last test in + // TEST_DATA. + ok(uaRules.some(rule => { + return rule.matchedSelectors.indexOf(":any-link") !== -1; + }), "There is a rule for :any-link"); + ok(uaRules.some(rule => { + return rule.matchedSelectors.indexOf("*|*:link") !== -1; + }), "There is a rule for *|*:link"); + ok(uaRules.some(rule => { + return rule.matchedSelectors.length === 1; + }), "Inline styles for ua styles"); +} + +function* userAgentStylesNotVisible(inspector, view) { + info("Making sure that user agent styles are not currently visible"); + + let userRules; + let uaRules; + + for (let data of TEST_DATA) { + yield selectNode(data.selector, inspector); + yield compareAppliedStylesWithUI(inspector, view); + + userRules = view._elementStyle.rules.filter(rule=>rule.editor.isEditable); + uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable); + is(userRules.length, data.numUserRules, "Correct number of user rules"); + is(uaRules.length, data.numUARules, "No UA rules"); + } +} + +function* compareAppliedStylesWithUI(inspector, view, filter) { + info("Making sure that UI is consistent with pageStyle.getApplied"); + + let entries = yield inspector.pageStyle.getApplied( + inspector.selection.nodeFront, + { + inherited: true, + matchedSelectors: true, + filter: filter + } + ); + + // We may see multiple entries that map to a given rule; filter the + // duplicates here to match what the UI does. + let entryMap = new Map(); + for (let entry of entries) { + entryMap.set(entry.rule, entry); + } + entries = [...entryMap.values()]; + + let elementStyle = view._elementStyle; + is(elementStyle.rules.length, entries.length, + "Should have correct number of rules (" + entries.length + ")"); + + entries = entries.sort((a, b) => { + return (a.pseudoElement || "z") > (b.pseudoElement || "z"); + }); + + entries.forEach((entry, i) => { + let elementStyleRule = elementStyle.rules[i]; + is(elementStyleRule.inherited, entry.inherited, + "Same inherited (" + entry.inherited + ")"); + is(elementStyleRule.isSystem, entry.isSystem, + "Same isSystem (" + entry.isSystem + ")"); + is(elementStyleRule.editor.isEditable, !entry.isSystem, + "Editor isEditable opposite of UA (" + entry.isSystem + ")"); + }); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js b/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js new file mode 100644 index 000000000..62b1d927c --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js @@ -0,0 +1,90 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that user set style properties can be changed from the markup-view and +// don't survive page reload + +const TEST_URI = ` + <p id='id1' style='width:200px;'>element 1</p> + <p id='id2' style='width:100px;'>element 2</p> +`; + +add_task(function* () { + yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let {inspector, view, testActor} = yield openRuleView(); + + yield selectNode("#id1", inspector); + yield modifyRuleViewWidth("300px", view, inspector); + yield assertRuleAndMarkupViewWidth("id1", "300px", view, inspector); + + yield selectNode("#id2", inspector); + yield assertRuleAndMarkupViewWidth("id2", "100px", view, inspector); + yield modifyRuleViewWidth("50px", view, inspector); + yield assertRuleAndMarkupViewWidth("id2", "50px", view, inspector); + + yield reloadPage(inspector, testActor); + + yield selectNode("#id1", inspector); + yield assertRuleAndMarkupViewWidth("id1", "200px", view, inspector); + yield selectNode("#id2", inspector); + yield assertRuleAndMarkupViewWidth("id2", "100px", view, inspector); +}); + +function getStyleRule(ruleView) { + return ruleView.styleDocument.querySelector(".ruleview-rule"); +} + +function* modifyRuleViewWidth(value, ruleView, inspector) { + info("Getting the property value element"); + let valueSpan = getStyleRule(ruleView) + .querySelector(".ruleview-propertyvalue"); + + info("Focusing the property value to set it to edit mode"); + let editor = yield focusEditableField(ruleView, valueSpan.parentNode); + + ok(editor.input, "The inplace-editor field is ready"); + info("Setting the new value"); + editor.input.value = value; + + info("Pressing return and waiting for the field to blur and for the " + + "markup-view to show the mutation"); + let onBlur = once(editor.input, "blur", true); + let onStyleChanged = waitForStyleModification(inspector); + EventUtils.sendKey("return"); + yield onBlur; + yield onStyleChanged; + + info("Escaping out of the new property field that has been created after " + + "the value was edited"); + let onNewFieldBlur = once(ruleView.styleDocument.activeElement, "blur", true); + EventUtils.sendKey("escape"); + yield onNewFieldBlur; +} + +function* getContainerStyleAttrValue(id, {walker, markup}) { + let front = yield walker.querySelector(walker.rootNode, "#" + id); + let container = markup.getContainer(front); + + let attrIndex = 0; + for (let attrName of container.elt.querySelectorAll(".attr-name")) { + if (attrName.textContent === "style") { + return container.elt.querySelectorAll(".attr-value")[attrIndex]; + } + attrIndex++; + } + return undefined; +} + +function* assertRuleAndMarkupViewWidth(id, value, ruleView, inspector) { + let valueSpan = getStyleRule(ruleView) + .querySelector(".ruleview-propertyvalue"); + is(valueSpan.textContent, value, + "Rule-view style width is " + value + " as expected"); + + let attr = yield getContainerStyleAttrValue(id, inspector); + is(attr.textContent.replace(/\s/g, ""), + "width:" + value + ";", "Markup-view style attribute width is " + value); +} diff --git a/devtools/client/inspector/rules/test/doc_author-sheet.html b/devtools/client/inspector/rules/test/doc_author-sheet.html new file mode 100644 index 000000000..f8c2eadd5 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_author-sheet.html @@ -0,0 +1,39 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>authored sheet test</title> + + <style> + pre a { + color: orange; + } + </style> + + <script> + "use strict"; + var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService); + + var style = "data:text/css,a { background-color: seagreen; }"; + var uri = gIOService.newURI(style, null, null); + var windowUtils = SpecialPowers.wrap(window) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); + windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET); + </script> + +</head> +<body> + <input type=text placeholder=test></input> + <input type=color></input> + <input type=range></input> + <input type=number></input> + <progress></progress> + <blockquote type=cite> + <pre _moz_quote=true> + inspect <a href="foo">user agent</a> styles + </pre> + </blockquote> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_blob_stylesheet.html b/devtools/client/inspector/rules/test/doc_blob_stylesheet.html new file mode 100644 index 000000000..c9973993b --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_blob_stylesheet.html @@ -0,0 +1,39 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +</html> +<html> +<head> + <meta charset="utf-8"> + <title>Blob stylesheet sourcemap</title> +</head> +<body> +<h1>Test</h1> +<script> +"use strict"; + +var cssContent = `body { + background-color: black; +} +body > h1 { + color: white; +} +` + +"/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtYX" + +"BwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLOztBQUN2QixTQUFPO0VBQ0" + +"wsS0FBSyxFQUFFLEtBQUsiLAoic291cmNlcyI6IFsidGVzdC5zY3NzIl0sCiJzb3VyY2VzQ29udG" + +"VudCI6IFsiYm9keSB7XG4gIGJhY2tncm91bmQtY29sb3I6IGJsYWNrO1xuICAmID4gaDEge1xuIC" + +"AgIGNvbG9yOiB3aGl0ZTsgIFxuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc3" + +"QuY3NzIgp9Cg== */"; +var cssBlob = new Blob([cssContent], {type: "text/css"}); +var url = URL.createObjectURL(cssBlob); + +var head = document.querySelector("head"); +var link = document.createElement("link"); +link.rel = "stylesheet"; +link.type = "text/css"; +link.href = url; +head.appendChild(link); +</script> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet.html b/devtools/client/inspector/rules/test/doc_content_stylesheet.html new file mode 100644 index 000000000..3ea65f606 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_content_stylesheet.html @@ -0,0 +1,35 @@ +<html> +<head> + <title>test</title> + + <link href="./doc_content_stylesheet_linked.css" rel="stylesheet" type="text/css"> + + <script> + /* eslint no-unused-vars: [2, {"vars": "local"}] */ + "use strict"; + // Load script.css + function loadCSS() { + let link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "./doc_content_stylesheet_script.css"; + document.getElementsByTagName("head")[0].appendChild(link); + } + </script> + + <style> + table { + border: 1px solid #000; + } + </style> +</head> +<body onload="loadCSS();"> + <table id="target"> + <tr> + <td> + <h3>Simple test</h3> + </td> + </tr> + </table> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css new file mode 100644 index 000000000..ea1a3d986 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css @@ -0,0 +1,5 @@ +@import url("./doc_content_stylesheet_imported2.css"); + +#target { + text-decoration: underline; +} diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css new file mode 100644 index 000000000..77c73299e --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css @@ -0,0 +1,3 @@ +#target { + text-decoration: underline; +} diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css new file mode 100644 index 000000000..712ba78fb --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css @@ -0,0 +1,3 @@ +table { + border-collapse: collapse; +} diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_script.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_script.css new file mode 100644 index 000000000..5aa5e2c6c --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_script.css @@ -0,0 +1,5 @@ +@import url("./doc_content_stylesheet_imported.css"); + +table { + opacity: 1; +} diff --git a/devtools/client/inspector/rules/test/doc_copystyles.css b/devtools/client/inspector/rules/test/doc_copystyles.css new file mode 100644 index 000000000..83f0c87b1 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_copystyles.css @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +html, body, #testid { + color: #F00; + background-color: #00F; + font-size: 12px; + border-color: #00F !important; + --var: "*/"; +} diff --git a/devtools/client/inspector/rules/test/doc_copystyles.html b/devtools/client/inspector/rules/test/doc_copystyles.html new file mode 100644 index 000000000..da1b4c0b3 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_copystyles.html @@ -0,0 +1,11 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + <title>Test case for copying stylesheet in rule-view</title> + <link rel="stylesheet" type="text/css" href="doc_copystyles.css"/> + </head> + <body> + <div id='testid'>Styled Node</div> + </body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_cssom.html b/devtools/client/inspector/rules/test/doc_cssom.html new file mode 100644 index 000000000..28de66d7d --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_cssom.html @@ -0,0 +1,22 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>CSSOM test</title> + + <script> + "use strict"; + window.onload = function () { + let x = document.styleSheets[0]; + x.insertRule("div { color: seagreen; }", 1); + }; + </script> + + <style> + span { } + </style> +</head> +<body> + <div id="target"> the ocean </div> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_custom.html b/devtools/client/inspector/rules/test/doc_custom.html new file mode 100644 index 000000000..09bf501d5 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_custom.html @@ -0,0 +1,33 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + <style> + #testidSimple { + --background-color: blue; + } + .testclassSimple { + --background-color: green; + } + + .testclassImportant { + --background-color: green !important; + } + #testidImportant { + --background-color: blue; + } + + #testidDisable { + --background-color: blue; + } + .testclassDisable { + --background-color: green; + } + </style> + </head> + <body> + <div id="testidSimple" class="testclassSimple">Styled Node</div> + <div id="testidImportant" class="testclassImportant">Styled Node</div> + <div id="testidDisable" class="testclassDisable">Styled Node</div> + </body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_filter.html b/devtools/client/inspector/rules/test/doc_filter.html new file mode 100644 index 000000000..cb2df9feb --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_filter.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<html> +<head> + <title>Bug 1055181 - CSS Filter Editor Widget</title> + <style> + body { + filter: blur(2px) contrast(2); + } + </style> +</head> diff --git a/devtools/client/inspector/rules/test/doc_frame_script.js b/devtools/client/inspector/rules/test/doc_frame_script.js new file mode 100644 index 000000000..88da043f1 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_frame_script.js @@ -0,0 +1,113 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* globals addMessageListener, sendAsyncMessage */ + +"use strict"; + +// A helper frame-script for brower/devtools/styleinspector tests. +// +// Most listeners in the script expect "Test:"-namespaced messages from chrome, +// then execute code upon receiving, and immediately send back a message. +// This is so that chrome test code can execute code in content and wait for a +// response this way: +// let response = yield executeInContent(browser, "Test:msgName", data, true); +// The response message should have the same name "Test:msgName" +// +// Some listeners do not send a response message back. + +var {utils: Cu} = Components; + +var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +var defer = require("devtools/shared/defer"); + +/** + * Get a value for a given property name in a css rule in a stylesheet, given + * their indexes + * @param {Object} data Expects a data object with the following properties + * - {Number} styleSheetIndex + * - {Number} ruleIndex + * - {String} name + * @return {String} The value, if found, null otherwise + */ +addMessageListener("Test:GetRulePropertyValue", function (msg) { + let {name, styleSheetIndex, ruleIndex} = msg.data; + let value = null; + + dumpn("Getting the value for property name " + name + " in sheet " + + styleSheetIndex + " and rule " + ruleIndex); + + let sheet = content.document.styleSheets[styleSheetIndex]; + if (sheet) { + let rule = sheet.cssRules[ruleIndex]; + if (rule) { + value = rule.style.getPropertyValue(name); + } + } + + sendAsyncMessage("Test:GetRulePropertyValue", value); +}); + +/** + * Get the property value from the computed style for an element. + * @param {Object} data Expects a data object with the following properties + * - {String} selector: The selector used to obtain the element. + * - {String} pseudo: pseudo id to query, or null. + * - {String} name: name of the property + * @return {String} The value, if found, null otherwise + */ +addMessageListener("Test:GetComputedStylePropertyValue", function (msg) { + let {selector, pseudo, name} = msg.data; + let element = content.document.querySelector(selector); + let value = content.document.defaultView.getComputedStyle(element, pseudo) + .getPropertyValue(name); + sendAsyncMessage("Test:GetComputedStylePropertyValue", value); +}); + +/** + * Wait the property value from the computed style for an element and + * compare it with the expected value + * @param {Object} data Expects a data object with the following properties + * - {String} selector: The selector used to obtain the element. + * - {String} pseudo: pseudo id to query, or null. + * - {String} name: name of the property + * - {String} expected: the expected value for property + */ +addMessageListener("Test:WaitForComputedStylePropertyValue", function (msg) { + let {selector, pseudo, name, expected} = msg.data; + let element = content.document.querySelector(selector); + waitForSuccess(() => { + let value = content.document.defaultView.getComputedStyle(element, pseudo) + .getPropertyValue(name); + + return value === expected; + }).then(() => { + sendAsyncMessage("Test:WaitForComputedStylePropertyValue"); + }); +}); + +var dumpn = msg => dump(msg + "\n"); + +/** + * Polls a given function waiting for it to return true. + * + * @param {Function} validatorFn A validator function that returns a boolean. + * This is called every few milliseconds to check if the result is true. When + * it is true, the promise resolves. + * @return a promise that resolves when the function returned true or rejects + * if the timeout is reached + */ +function waitForSuccess(validatorFn) { + let def = defer(); + + function wait(fn) { + if (fn()) { + def.resolve(); + } else { + setTimeout(() => wait(fn), 200); + } + } + wait(validatorFn); + + return def.promise; +} diff --git a/devtools/client/inspector/rules/test/doc_inline_sourcemap.html b/devtools/client/inspector/rules/test/doc_inline_sourcemap.html new file mode 100644 index 000000000..cb107d424 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_inline_sourcemap.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> +<head> + <title>CSS source maps in inline stylesheets</title> +</head> +<body> + <div>CSS source maps in inline stylesheets</div> + <style> +div { + color: #ff0066; } + +span { + background-color: #EEE; } + +/*# sourceMappingURL=doc_sourcemaps.css.map */ + </style> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_invalid_sourcemap.css b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.css new file mode 100644 index 000000000..ff96a6b54 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.css @@ -0,0 +1,3 @@ +div { color: gold; }
+
+/*# sourceMappingURL=this-source-map-does-not-exist.css.map */
\ No newline at end of file diff --git a/devtools/client/inspector/rules/test/doc_invalid_sourcemap.html b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.html new file mode 100644 index 000000000..2e6422bec --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.html @@ -0,0 +1,11 @@ +<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Invalid source map</title>
+ <link rel="stylesheet" type="text/css" href="doc_invalid_sourcemap.css">
+</head>
+<body>
+ <div>invalid source map</div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html b/devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html new file mode 100644 index 000000000..8fce04584 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>keyframe line numbers test</title> + <style type="text/css"> +div { + animation-duration: 1s; + animation-iteration-count: infinite; + animation-direction: alternate; + animation-name: CC; +} + +span { + animation-duration: 3s; + animation-iteration-count: infinite; + animation-direction: alternate; + animation-name: DD; +} + +@keyframes CC { + from { + background: #ffffff; + } + to { + background: #f0c; + } +} + +@keyframes DD { + from { + background: seagreen; + } + to { + background: chartreuse; + } +} + </style> +</head> +<body> + <div id="outer"> + <span id="inner">lizards</div> + </div> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_keyframeanimation.css b/devtools/client/inspector/rules/test/doc_keyframeanimation.css new file mode 100644 index 000000000..64582ed35 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_keyframeanimation.css @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +.box { + height: 50px; + width: 50px; +} + +.circle { + width: 20px; + height: 20px; + border-radius: 10px; + background-color: #FFCB01; +} + +#pacman { + width: 0px; + height: 0px; + border-right: 60px solid transparent; + border-top: 60px solid #FFCB01; + border-left: 60px solid #FFCB01; + border-bottom: 60px solid #FFCB01; + border-top-left-radius: 60px; + border-bottom-left-radius: 60px; + border-top-right-radius: 60px; + border-bottom-right-radius: 60px; + top: 120px; + left: 150px; + position: absolute; + animation-name: pacman; + animation-fill-mode: forwards; + animation-timing-function: linear; + animation-duration: 15s; +} + +#boxy { + top: 170px; + left: 450px; + position: absolute; + animation: 4s linear 0s normal none infinite boxy; +} + + +#moxy { + animation-name: moxy, boxy; + animation-delay: 3.5s; + animation-duration: 2s; + top: 170px; + left: 650px; + position: absolute; +} + +@-moz-keyframes pacman { + 100% { + left: 750px; + } +} + +@keyframes pacman { + 100% { + left: 750px; + } +} + +@keyframes boxy { + 10% { + background-color: blue; + } + + 20% { + background-color: green; + } + + 100% { + opacity: 0; + } +} + +@keyframes moxy { + to { + opacity: 0; + } +} diff --git a/devtools/client/inspector/rules/test/doc_keyframeanimation.html b/devtools/client/inspector/rules/test/doc_keyframeanimation.html new file mode 100644 index 000000000..4e02c32f0 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_keyframeanimation.html @@ -0,0 +1,13 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + <title>test case for keyframes rule in rule-view</title> + <link rel="stylesheet" type="text/css" href="doc_keyframeanimation.css"/> + </head> + <body> + <div id="pacman"></div> + <div id="boxy" class="circle"></div> + <div id="moxy" class="circle"></div> + </body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_media_queries.html b/devtools/client/inspector/rules/test/doc_media_queries.html new file mode 100644 index 000000000..1adb8bc7a --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_media_queries.html @@ -0,0 +1,24 @@ +<html> +<head> + <title>test</title> + <script type="application/javascript;version=1.7"> + + </script> + <style> + div { + width: 1000px; + height: 100px; + background-color: #f00; + } + + @media screen and (min-width: 1px) { + div { + width: 200px; + } + } + </style> +</head> +<body> +<div></div> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_pseudoelement.html b/devtools/client/inspector/rules/test/doc_pseudoelement.html new file mode 100644 index 000000000..6145d4bf1 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_pseudoelement.html @@ -0,0 +1,131 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + <style> + +body { + color: #333; +} + +.box { + float:left; + width: 128px; + height: 128px; + background: #ddd; + padding: 32px; + margin: 32px; + position:relative; +} + +.box:first-line { + color: orange; + background: red; +} + +.box:first-letter { + color: green; +} + +* { + cursor: default; +} + +nothing { + cursor: pointer; +} + +p::-moz-selection { + color: white; + background: black; +} +p::selection { + color: white; + background: black; +} + +p:first-line { + background: blue; +} +p:first-letter { + color: red; + font-size: 130%; +} + +.box:before { + background: green; + content: " "; + position: absolute; + height:32px; + width:32px; +} + +.box:after { + background: red; + content: " "; + position: absolute; + border-radius: 50%; + height:32px; + width:32px; + top: 50%; + left: 50%; + margin-top: -16px; + margin-left: -16px; +} + +.topleft:before { + top:0; + left:0; +} + +.topleft:first-line { + color: orange; +} +.topleft::selection { + color: orange; +} + +.topright:before { + top:0; + right:0; +} + +.bottomright:before { + bottom:10px; + right:10px; + color: red; +} + +.bottomright:before { + bottom:0; + right:0; +} + +.bottomleft:before { + bottom:0; + left:0; +} + + </style> + </head> + <body> + <h1>ruleview pseudoelement($("test"));</h1> + + <div id="topleft" class="box topleft"> + <p>Top Left<br />Position</p> + </div> + + <div id="topright" class="box topright"> + <p>Top Right<br />Position</p> + </div> + + <div id="bottomright" class="box bottomright"> + <p>Bottom Right<br />Position</p> + </div> + + <div id="bottomleft" class="box bottomleft"> + <p>Bottom Left<br />Position</p> + </div> + + </body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_ruleLineNumbers.html b/devtools/client/inspector/rules/test/doc_ruleLineNumbers.html new file mode 100644 index 000000000..5a157f384 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_ruleLineNumbers.html @@ -0,0 +1,19 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>simple testcase</title> + <style type="text/css"> + #testid { + background-color: seagreen; + } + + body { + color: chartreuse; + } + </style> +</head> +<body> + <div id="testid">simple testcase</div> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.css b/devtools/client/inspector/rules/test/doc_sourcemaps.css new file mode 100644 index 000000000..a9b437a40 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_sourcemaps.css @@ -0,0 +1,7 @@ +div { + color: #ff0066; } + +span { + background-color: #EEE; } + +/*# sourceMappingURL=doc_sourcemaps.css.map */
\ No newline at end of file diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.css.map b/devtools/client/inspector/rules/test/doc_sourcemaps.css.map new file mode 100644 index 000000000..0f7486fd9 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_sourcemaps.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAGA,GAAI;EACF,KAAK,EAHU,OAAI;;AAMrB,IAAK;EACH,gBAAgB,EAAE,IAAI", +"sources": ["doc_sourcemaps.scss"], +"names": [], +"file": "doc_sourcemaps.css" +} diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.html b/devtools/client/inspector/rules/test/doc_sourcemaps.html new file mode 100644 index 000000000..0014e55fe --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_sourcemaps.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> +<head> + <title>testcase for testing CSS source maps</title> + <link rel="stylesheet" type="text/css" href="simple.css"/> + <link rel="stylesheet" type="text/css" href="doc_sourcemaps.css"/> +</head> +<body> + <div>source maps <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.scss b/devtools/client/inspector/rules/test/doc_sourcemaps.scss new file mode 100644 index 000000000..0ff6c471b --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_sourcemaps.scss @@ -0,0 +1,10 @@ + +$paulrougetpink: #f06; + +div { + color: $paulrougetpink; +} + +span { + background-color: #EEE; +}
\ No newline at end of file diff --git a/devtools/client/inspector/rules/test/doc_style_editor_link.css b/devtools/client/inspector/rules/test/doc_style_editor_link.css new file mode 100644 index 000000000..e49e1f587 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_style_editor_link.css @@ -0,0 +1,3 @@ +div { + opacity: 1; +}
\ No newline at end of file diff --git a/devtools/client/inspector/rules/test/doc_test_image.png b/devtools/client/inspector/rules/test/doc_test_image.png Binary files differnew file mode 100644 index 000000000..769c63634 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_test_image.png diff --git a/devtools/client/inspector/rules/test/doc_urls_clickable.css b/devtools/client/inspector/rules/test/doc_urls_clickable.css new file mode 100644 index 000000000..04315b2c3 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_urls_clickable.css @@ -0,0 +1,9 @@ +.relative1 { + background-image: url(./doc_test_image.png); +} +.absolute { + background: url("http://example.com/browser/devtools/client/inspector/rules/test/doc_test_image.png"); +} +.base64 { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='); +} diff --git a/devtools/client/inspector/rules/test/doc_urls_clickable.html b/devtools/client/inspector/rules/test/doc_urls_clickable.html new file mode 100644 index 000000000..b0265a703 --- /dev/null +++ b/devtools/client/inspector/rules/test/doc_urls_clickable.html @@ -0,0 +1,30 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + + <link href="./doc_urls_clickable.css" rel="stylesheet" type="text/css"> + + <style> + .relative2 { + background-image: url(doc_test_image.png); + } + </style> + </head> + <body> + + <div class="relative1">Background image #1 with relative path (loaded from external css)</div> + + <div class="relative2">Background image #2 with relative path (loaded from style tag)</div> + + <div class="absolute">Background image with absolute path (loaded from external css)</div> + + <div class="base64">Background image with base64 url (loaded from external css)</div> + + <div class="inline" style="background: url(doc_test_image.png);">Background image with relative path (loaded from style attribute)</div> + + <div class="inline-resolved" style="background-image: url(./doc_test_image.png)">Background image with resolved relative path (loaded from style attribute)</div> + + <div class="noimage">No background image :(</div> + </body> +</html> diff --git a/devtools/client/inspector/rules/test/head.js b/devtools/client/inspector/rules/test/head.js new file mode 100644 index 000000000..5e5ede09b --- /dev/null +++ b/devtools/client/inspector/rules/test/head.js @@ -0,0 +1,840 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* import-globals-from ../../test/head.js */ +"use strict"; + +// Import the inspector's head.js first (which itself imports shared-head.js). +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js", + this); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.defaultColorUnit"); +}); + +var {getInplaceEditorForSpan: inplaceEditor} = + require("devtools/client/shared/inplace-editor"); + +const ROOT_TEST_DIR = getRootDirectory(gTestPath); +const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js"; + +const STYLE_INSPECTOR_L10N + = new LocalizationHelper("devtools/shared/locales/styleinspector.properties"); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.defaultColorUnit"); +}); + +/** + * The rule-view tests rely on a frame-script to be injected in the content test + * page. So override the shared-head's addTab to load the frame script after the + * tab was added. + * FIXME: Refactor the rule-view tests to use the testActor instead of a frame + * script, so they can run on remote targets too. + */ +var _addTab = addTab; +addTab = function (url) { + return _addTab(url).then(tab => { + info("Loading the helper frame script " + FRAME_SCRIPT_URL); + let browser = tab.linkedBrowser; + browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); + return tab; + }); +}; + +/** + * Wait for a content -> chrome message on the message manager (the window + * messagemanager is used). + * + * @param {String} name + * The message name + * @return {Promise} A promise that resolves to the response data when the + * message has been received + */ +function waitForContentMessage(name) { + info("Expecting message " + name + " from content"); + + let mm = gBrowser.selectedBrowser.messageManager; + + let def = defer(); + mm.addMessageListener(name, function onMessage(msg) { + mm.removeMessageListener(name, onMessage); + def.resolve(msg.data); + }); + return def.promise; +} + +/** + * Send an async message to the frame script (chrome -> content) and wait for a + * response message with the same name (content -> chrome). + * + * @param {String} name + * The message name. Should be one of the messages defined + * in doc_frame_script.js + * @param {Object} data + * Optional data to send along + * @param {Object} objects + * Optional CPOW objects to send along + * @param {Boolean} expectResponse + * If set to false, don't wait for a response with the same name + * from the content script. Defaults to true. + * @return {Promise} Resolves to the response data if a response is expected, + * immediately resolves otherwise + */ +function executeInContent(name, data = {}, objects = {}, + expectResponse = true) { + info("Sending message " + name + " to content"); + let mm = gBrowser.selectedBrowser.messageManager; + + mm.sendAsyncMessage(name, data, objects); + if (expectResponse) { + return waitForContentMessage(name); + } + + return promise.resolve(); +} + +/** + * Send an async message to the frame script and get back the requested + * computed style property. + * + * @param {String} selector + * The selector used to obtain the element. + * @param {String} pseudo + * pseudo id to query, or null. + * @param {String} name + * name of the property. + */ +function* getComputedStyleProperty(selector, pseudo, propName) { + return yield executeInContent("Test:GetComputedStylePropertyValue", + {selector, + pseudo, + name: propName}); +} + +/** + * Get an element's inline style property value. + * @param {TestActor} testActor + * @param {String} selector + * The selector used to obtain the element. + * @param {String} name + * name of the property. + */ +function getStyle(testActor, selector, propName) { + return testActor.eval(` + content.document.querySelector("${selector}") + .style.getPropertyValue("${propName}"); + `); +} + +/** + * Send an async message to the frame script and wait until the requested + * computed style property has the expected value. + * + * @param {String} selector + * The selector used to obtain the element. + * @param {String} pseudo + * pseudo id to query, or null. + * @param {String} prop + * name of the property. + * @param {String} expected + * expected value of property + * @param {String} name + * the name used in test message + */ +function* waitForComputedStyleProperty(selector, pseudo, name, expected) { + return yield executeInContent("Test:WaitForComputedStylePropertyValue", + {selector, + pseudo, + expected, + name}); +} + +/** + * Given an inplace editable element, click to switch it to edit mode, wait for + * focus + * + * @return a promise that resolves to the inplace-editor element when ready + */ +var focusEditableField = Task.async(function* (ruleView, editable, xOffset = 1, + yOffset = 1, options = {}) { + let onFocus = once(editable.parentNode, "focus", true); + info("Clicking on editable field to turn to edit mode"); + EventUtils.synthesizeMouse(editable, xOffset, yOffset, options, + editable.ownerDocument.defaultView); + yield onFocus; + + info("Editable field gained focus, returning the input field now"); + let onEdit = inplaceEditor(editable.ownerDocument.activeElement); + + return onEdit; +}); + +/** + * When a tooltip is closed, this ends up "commiting" the value changed within + * the tooltip (e.g. the color in case of a colorpicker) which, in turn, ends up + * setting the value of the corresponding css property in the rule-view. + * Use this function to close the tooltip and make sure the test waits for the + * ruleview-changed event. + * @param {SwatchBasedEditorTooltip} editorTooltip + * @param {CSSRuleView} view + */ +function* hideTooltipAndWaitForRuleViewChanged(editorTooltip, view) { + let onModified = view.once("ruleview-changed"); + let onHidden = editorTooltip.tooltip.once("hidden"); + editorTooltip.hide(); + yield onModified; + yield onHidden; +} + +/** + * Polls a given generator function waiting for it to return true. + * + * @param {Function} validatorFn + * A validator generator function that returns a boolean. + * This is called every few milliseconds to check if the result is true. + * When it is true, the promise resolves. + * @param {String} name + * Optional name of the test. This is used to generate + * the success and failure messages. + * @return a promise that resolves when the function returned true or rejects + * if the timeout is reached + */ +var waitForSuccess = Task.async(function* (validatorFn, desc = "untitled") { + let i = 0; + while (true) { + info("Checking: " + desc); + if (yield validatorFn()) { + ok(true, "Success: " + desc); + break; + } + i++; + if (i > 10) { + ok(false, "Failure: " + desc); + break; + } + yield new Promise(r => setTimeout(r, 200)); + } +}); + +/** + * Get the DOMNode for a css rule in the rule-view that corresponds to the given + * selector + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view for which the rule + * object is wanted + * @return {DOMNode} + */ +function getRuleViewRule(view, selectorText) { + let rule; + for (let r of view.styleDocument.querySelectorAll(".ruleview-rule")) { + let selector = r.querySelector(".ruleview-selectorcontainer, " + + ".ruleview-selector-matched"); + if (selector && selector.textContent === selectorText) { + rule = r; + break; + } + } + + return rule; +} + +/** + * Get references to the name and value span nodes corresponding to a given + * selector and property name in the rule-view + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for the property in + * @param {String} propertyName + * The name of the property + * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode} + */ +function getRuleViewProperty(view, selectorText, propertyName) { + let prop; + + let rule = getRuleViewRule(view, selectorText); + if (rule) { + // Look for the propertyName in that rule element + for (let p of rule.querySelectorAll(".ruleview-property")) { + let nameSpan = p.querySelector(".ruleview-propertyname"); + let valueSpan = p.querySelector(".ruleview-propertyvalue"); + + if (nameSpan.textContent === propertyName) { + prop = {nameSpan: nameSpan, valueSpan: valueSpan}; + break; + } + } + } + return prop; +} + +/** + * Get the text value of the property corresponding to a given selector and name + * in the rule-view + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for the property in + * @param {String} propertyName + * The name of the property + * @return {String} The property value + */ +function getRuleViewPropertyValue(view, selectorText, propertyName) { + return getRuleViewProperty(view, selectorText, propertyName) + .valueSpan.textContent; +} + +/** + * Get a reference to the selector DOM element corresponding to a given selector + * in the rule-view + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for + * @return {DOMNode} The selector DOM element + */ +function getRuleViewSelector(view, selectorText) { + let rule = getRuleViewRule(view, selectorText); + return rule.querySelector(".ruleview-selector, .ruleview-selector-matched"); +} + +/** + * Get a reference to the selectorhighlighter icon DOM element corresponding to + * a given selector in the rule-view + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for + * @return {DOMNode} The selectorhighlighter icon DOM element + */ +function getRuleViewSelectorHighlighterIcon(view, selectorText) { + let rule = getRuleViewRule(view, selectorText); + return rule.querySelector(".ruleview-selectorhighlighter"); +} + +/** + * Simulate a color change in a given color picker tooltip, and optionally wait + * for a given element in the page to have its style changed as a result. + * Note that this function assumes that the colorpicker popup is already open + * and it won't close it after having selected the new color. + * + * @param {RuleView} ruleView + * The related rule view instance + * @param {SwatchColorPickerTooltip} colorPicker + * @param {Array} newRgba + * The new color to be set [r, g, b, a] + * @param {Object} expectedChange + * Optional object that needs the following props: + * - {String} selector The selector to the element in the page that + * will have its style changed. + * - {String} name The style name that will be changed + * - {String} value The expected style value + * The style will be checked like so: getComputedStyle(element)[name] === value + */ +var simulateColorPickerChange = Task.async(function* (ruleView, colorPicker, + newRgba, expectedChange) { + let onComputedStyleChanged; + if (expectedChange) { + let {selector, name, value} = expectedChange; + onComputedStyleChanged = waitForComputedStyleProperty(selector, null, name, value); + } + let onRuleViewChanged = ruleView.once("ruleview-changed"); + info("Getting the spectrum colorpicker object"); + let spectrum = colorPicker.spectrum; + info("Setting the new color"); + spectrum.rgb = newRgba; + info("Applying the change"); + spectrum.updateUI(); + spectrum.onChange(); + info("Waiting for rule-view to update"); + yield onRuleViewChanged; + + if (expectedChange) { + info("Waiting for the style to be applied on the page"); + yield onComputedStyleChanged; + } +}); + +/** + * Open the color picker popup for a given property in a given rule and + * simulate a color change. Optionally wait for a given element in the page to + * have its style changed as a result. + * + * @param {RuleView} view + * The related rule view instance + * @param {Number} ruleIndex + * Which rule to target in the rule view + * @param {Number} propIndex + * Which property to target in the rule + * @param {Array} newRgba + * The new color to be set [r, g, b, a] + * @param {Object} expectedChange + * Optional object that needs the following props: + * - {String} selector The selector to the element in the page that + * will have its style changed. + * - {String} name The style name that will be changed + * - {String} value The expected style value + * The style will be checked like so: getComputedStyle(element)[name] === value + */ +var openColorPickerAndSelectColor = Task.async(function* (view, ruleIndex, + propIndex, newRgba, expectedChange) { + let ruleEditor = getRuleViewRuleEditor(view, ruleIndex); + let propEditor = ruleEditor.rule.textProps[propIndex].editor; + let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch"); + let cPicker = view.tooltips.colorPicker; + + info("Opening the colorpicker by clicking the color swatch"); + let onColorPickerReady = cPicker.once("ready"); + swatch.click(); + yield onColorPickerReady; + + yield simulateColorPickerChange(view, cPicker, newRgba, expectedChange); + + return {propEditor, swatch, cPicker}; +}); + +/** + * Open the cubicbezier popup for a given property in a given rule and + * simulate a curve change. Optionally wait for a given element in the page to + * have its style changed as a result. + * + * @param {RuleView} view + * The related rule view instance + * @param {Number} ruleIndex + * Which rule to target in the rule view + * @param {Number} propIndex + * Which property to target in the rule + * @param {Array} coords + * The new coordinates to be used, e.g. [0.1, 2, 0.9, -1] + * @param {Object} expectedChange + * Optional object that needs the following props: + * - {String} selector The selector to the element in the page that + * will have its style changed. + * - {String} name The style name that will be changed + * - {String} value The expected style value + * The style will be checked like so: getComputedStyle(element)[name] === value + */ +var openCubicBezierAndChangeCoords = Task.async(function* (view, ruleIndex, + propIndex, coords, expectedChange) { + let ruleEditor = getRuleViewRuleEditor(view, ruleIndex); + let propEditor = ruleEditor.rule.textProps[propIndex].editor; + let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch"); + let bezierTooltip = view.tooltips.cubicBezier; + + info("Opening the cubicBezier by clicking the swatch"); + let onBezierWidgetReady = bezierTooltip.once("ready"); + swatch.click(); + yield onBezierWidgetReady; + + let widget = yield bezierTooltip.widget; + + info("Simulating a change of curve in the widget"); + let onRuleViewChanged = view.once("ruleview-changed"); + widget.coordinates = coords; + yield onRuleViewChanged; + + if (expectedChange) { + info("Waiting for the style to be applied on the page"); + let {selector, name, value} = expectedChange; + yield waitForComputedStyleProperty(selector, null, name, value); + } + + return {propEditor, swatch, bezierTooltip}; +}); + +/** + * Get a rule-link from the rule-view given its index + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {Number} index + * The index of the link to get + * @return {DOMNode} The link if any at this index + */ +function getRuleViewLinkByIndex(view, index) { + let links = view.styleDocument.querySelectorAll(".ruleview-rule-source"); + return links[index]; +} + +/** + * Get rule-link text from the rule-view given its index + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {Number} index + * The index of the link to get + * @return {String} The string at this index + */ +function getRuleViewLinkTextByIndex(view, index) { + let link = getRuleViewLinkByIndex(view, index); + return link.querySelector(".ruleview-rule-source-label").textContent; +} + +/** + * Simulate adding a new property in an existing rule in the rule-view. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {Number} ruleIndex + * The index of the rule to use. Note that if ruleIndex is 0, you might + * want to also listen to markupmutation events in your test since + * that's going to change the style attribute of the selected node. + * @param {String} name + * The name for the new property + * @param {String} value + * The value for the new property + * @param {String} commitValueWith + * Which key should be used to commit the new value. VK_RETURN is used by + * default, but tests might want to use another key to test cancelling + * for exemple. + * @param {Boolean} blurNewProperty + * After the new value has been added, a new property would have been + * focused. This parameter is true by default, and that causes the new + * property to be blurred. Set to false if you don't want this. + * @return {TextProperty} The instance of the TextProperty that was added + */ +var addProperty = Task.async(function* (view, ruleIndex, name, value, + commitValueWith = "VK_RETURN", + blurNewProperty = true) { + info("Adding new property " + name + ":" + value + " to rule " + ruleIndex); + + let ruleEditor = getRuleViewRuleEditor(view, ruleIndex); + let editor = yield focusNewRuleViewProperty(ruleEditor); + let numOfProps = ruleEditor.rule.textProps.length; + + info("Adding name " + name); + editor.input.value = name; + let onNameAdded = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onNameAdded; + + // Focus has moved to the value inplace-editor automatically. + editor = inplaceEditor(view.styleDocument.activeElement); + let textProps = ruleEditor.rule.textProps; + let textProp = textProps[textProps.length - 1]; + + is(ruleEditor.rule.textProps.length, numOfProps + 1, + "A new test property was added"); + is(editor, inplaceEditor(textProp.editor.valueSpan), + "The inplace editor appeared for the value"); + + info("Adding value " + value); + // Setting the input value schedules a preview to be shown in 10ms which + // triggers a ruleview-changed event (see bug 1209295). + let onPreview = view.once("ruleview-changed"); + editor.input.value = value; + view.throttle.flush(); + yield onPreview; + + let onValueAdded = view.once("ruleview-changed"); + EventUtils.synthesizeKey(commitValueWith, {}, view.styleWindow); + yield onValueAdded; + + if (blurNewProperty) { + view.styleDocument.activeElement.blur(); + } + + return textProp; +}); + +/** + * Simulate changing the value of a property in a rule in the rule-view. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {TextProperty} textProp + * The instance of the TextProperty to be changed + * @param {String} value + * The new value to be used. If null is passed, then the value will be + * deleted + * @param {Boolean} blurNewProperty + * After the value has been changed, a new property would have been + * focused. This parameter is true by default, and that causes the new + * property to be blurred. Set to false if you don't want this. + */ +var setProperty = Task.async(function* (view, textProp, value, + blurNewProperty = true) { + yield focusEditableField(view, textProp.editor.valueSpan); + + let onPreview = view.once("ruleview-changed"); + if (value === null) { + EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow); + } else { + EventUtils.sendString(value, view.styleWindow); + } + view.throttle.flush(); + yield onPreview; + + let onValueDone = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onValueDone; + + if (blurNewProperty) { + view.styleDocument.activeElement.blur(); + } +}); + +/** + * Simulate removing a property from an existing rule in the rule-view. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {TextProperty} textProp + * The instance of the TextProperty to be removed + * @param {Boolean} blurNewProperty + * After the property has been removed, a new property would have been + * focused. This parameter is true by default, and that causes the new + * property to be blurred. Set to false if you don't want this. + */ +var removeProperty = Task.async(function* (view, textProp, + blurNewProperty = true) { + yield focusEditableField(view, textProp.editor.nameSpan); + + let onModifications = view.once("ruleview-changed"); + info("Deleting the property name now"); + EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + yield onModifications; + + if (blurNewProperty) { + view.styleDocument.activeElement.blur(); + } +}); + +/** + * Simulate clicking the enable/disable checkbox next to a property in a rule. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {TextProperty} textProp + * The instance of the TextProperty to be enabled/disabled + */ +var togglePropStatus = Task.async(function* (view, textProp) { + let onRuleViewRefreshed = view.once("ruleview-changed"); + textProp.editor.enable.click(); + yield onRuleViewRefreshed; +}); + +/** + * Click on a rule-view's close brace to focus a new property name editor + * + * @param {RuleEditor} ruleEditor + * An instance of RuleEditor that will receive the new property + * @return a promise that resolves to the newly created editor when ready and + * focused + */ +var focusNewRuleViewProperty = Task.async(function* (ruleEditor) { + info("Clicking on a close ruleEditor brace to start editing a new property"); + + // Use bottom alignment to avoid scrolling out of the parent element area. + ruleEditor.closeBrace.scrollIntoView(false); + let editor = yield focusEditableField(ruleEditor.ruleView, + ruleEditor.closeBrace); + + is(inplaceEditor(ruleEditor.newPropSpan), editor, + "Focused editor is the new property editor."); + + return editor; +}); + +/** + * Create a new property name in the rule-view, focusing a new property editor + * by clicking on the close brace, and then entering the given text. + * Keep in mind that the rule-view knows how to handle strings with multiple + * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3". + * + * @param {RuleEditor} ruleEditor + * The instance of RuleEditor that will receive the new property(ies) + * @param {String} inputValue + * The text to be entered in the new property name field + * @return a promise that resolves when the new property name has been entered + * and once the value field is focused + */ +var createNewRuleViewProperty = Task.async(function* (ruleEditor, inputValue) { + info("Creating a new property editor"); + let editor = yield focusNewRuleViewProperty(ruleEditor); + + info("Entering the value " + inputValue); + editor.input.value = inputValue; + + info("Submitting the new value and waiting for value field focus"); + let onFocus = once(ruleEditor.element, "focus", true); + EventUtils.synthesizeKey("VK_RETURN", {}, + ruleEditor.element.ownerDocument.defaultView); + yield onFocus; +}); + +/** + * Set the search value for the rule-view filter styles search box. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} searchValue + * The filter search value + * @return a promise that resolves when the rule-view is filtered for the + * search term + */ +var setSearchFilter = Task.async(function* (view, searchValue) { + info("Setting filter text to \"" + searchValue + "\""); + let win = view.styleWindow; + let searchField = view.searchField; + searchField.focus(); + synthesizeKeys(searchValue, win); + yield view.inspector.once("ruleview-filtered"); +}); + +/** + * Reload the current page and wait for the inspector to be initialized after + * the navigation + * + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox + * @param {TestActor} testActor + * The current instance of the TestActor + */ +function* reloadPage(inspector, testActor) { + let onNewRoot = inspector.once("new-root"); + yield testActor.reload(); + yield onNewRoot; + yield inspector.markup._waitForChildren(); +} + +/** + * Create a new rule by clicking on the "add rule" button. + * This will leave the selector inplace-editor active. + * + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox + * @param {CssRuleView} view + * The instance of the rule-view panel + * @return a promise that resolves after the rule has been added + */ +function* addNewRule(inspector, view) { + info("Adding the new rule using the button"); + view.addRuleButton.click(); + + info("Waiting for rule view to change"); + yield view.once("ruleview-changed"); +} + +/** + * Create a new rule by clicking on the "add rule" button, dismiss the editor field and + * verify that the selector is correct. + * + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} expectedSelector + * The value we expect the selector to have + * @param {Number} expectedIndex + * The index we expect the rule to have in the rule-view + * @return a promise that resolves after the rule has been added + */ +function* addNewRuleAndDismissEditor(inspector, view, expectedSelector, expectedIndex) { + yield addNewRule(inspector, view); + + info("Getting the new rule at index " + expectedIndex); + let ruleEditor = getRuleViewRuleEditor(view, expectedIndex); + let editor = ruleEditor.selectorText.ownerDocument.activeElement; + is(editor.value, expectedSelector, + "The editor for the new selector has the correct value: " + expectedSelector); + + info("Pressing escape to leave the editor"); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + + is(ruleEditor.selectorText.textContent, expectedSelector, + "The new selector has the correct text: " + expectedSelector); +} + +/** + * Simulate a sequence of non-character keys (return, escape, tab) and wait for + * a given element to receive the focus. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {DOMNode} element + * The element that should be focused + * @param {Array} keys + * Array of non-character keys, the part that comes after "DOM_VK_" eg. + * "RETURN", "ESCAPE" + * @return a promise that resolves after the element received the focus + */ +function* sendKeysAndWaitForFocus(view, element, keys) { + let onFocus = once(element, "focus", true); + for (let key of keys) { + EventUtils.sendKey(key, view.styleWindow); + } + yield onFocus; +} + +/** + * Open the style editor context menu and return all of it's items in a flat array + * @param {CssRuleView} view + * The instance of the rule-view panel + * @return An array of MenuItems + */ +function openStyleContextMenuAndGetAllItems(view, target) { + let menu = view._contextmenu._openMenu({target: target}); + + // Flatten all menu items into a single array to make searching through it easier + let allItems = [].concat.apply([], menu.items.map(function addItem(item) { + if (item.submenu) { + return addItem(item.submenu.items); + } + return item; + })); + + return allItems; +} + +/** + * Wait for a markupmutation event on the inspector that is for a style modification. + * @param {InspectorPanel} inspector + * @return {Promise} + */ +function waitForStyleModification(inspector) { + return new Promise(function (resolve) { + function checkForStyleModification(name, mutations) { + for (let mutation of mutations) { + if (mutation.type === "attributes" && mutation.attributeName === "style") { + inspector.off("markupmutation", checkForStyleModification); + resolve(); + return; + } + } + } + inspector.on("markupmutation", checkForStyleModification); + }); +} + +/** + * Click on the selector icon + * @param {DOMNode} icon + * @param {CSSRuleView} view + */ +function* clickSelectorIcon(icon, view) { + let onToggled = view.once("ruleview-selectorhighlighter-toggled"); + EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow); + yield onToggled; +} + +/** + * Make sure window is properly focused before sending a key event. + * @param {Window} win + * @param {Event} key + */ +function focusAndSendKey(win, key) { + win.document.documentElement.focus(); + EventUtils.sendKey(key, win); +} |