Bug 583041 - Style Editor integration; part 5 - tests; r=rcampbell
authorCedric Vivier <cedricv@neonux.com>
Wed, 26 Oct 2011 17:52:17 +0800
changeset 80304 1cac8411fdc68be72bbfdeccf44f360039d5cfa0
parent 80303 4119bee2221dea719ddaf61fd09103902c41a5e9
child 80305 d756772d3ed0185f475303692403e1e988d6c9cb
push id330
push userrcampbell@mozilla.com
push dateThu, 17 Nov 2011 22:33:35 +0000
treeherderfx-team@1cac8411fdc6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs583041
milestone11.0a1
Bug 583041 - Style Editor integration; part 5 - tests; r=rcampbell
browser/devtools/styleeditor/test/Makefile.in
browser/devtools/styleeditor/test/browser_styleeditor_enabled.js
browser/devtools/styleeditor/test/browser_styleeditor_import.js
browser/devtools/styleeditor/test/browser_styleeditor_init.js
browser/devtools/styleeditor/test/browser_styleeditor_loading.js
browser/devtools/styleeditor/test/browser_styleeditor_new.js
browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
browser/devtools/styleeditor/test/browser_styleeditor_readonly.js
browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
browser/devtools/styleeditor/test/browser_styleeditor_sv_filter.js
browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
browser/devtools/styleeditor/test/four.html
browser/devtools/styleeditor/test/head.js
browser/devtools/styleeditor/test/media-small.css
browser/devtools/styleeditor/test/media.html
browser/devtools/styleeditor/test/minified.html
browser/devtools/styleeditor/test/simple.css
browser/devtools/styleeditor/test/simple.css.gz
browser/devtools/styleeditor/test/simple.css.gz^headers^
browser/devtools/styleeditor/test/simple.gz.html
browser/devtools/styleeditor/test/simple.html
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/Makefile.in
@@ -0,0 +1,72 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Style Editor code.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation.
+#
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Cedric Vivier <cedricv@neonux.com> (original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = browser/devtools/styleeditor/test
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_TEST_FILES = \
+                 browser_styleeditor_enabled.js \
+                 browser_styleeditor_import.js \
+                 browser_styleeditor_init.js \
+                 browser_styleeditor_loading.js \
+                 browser_styleeditor_new.js \
+                 browser_styleeditor_pretty.js \
+                 browser_styleeditor_readonly.js \
+                 browser_styleeditor_reopen.js \
+                 browser_styleeditor_sv_filter.js \
+                 browser_styleeditor_sv_keynav.js \
+                 browser_styleeditor_sv_resize.js \
+                 four.html \
+                 head.js \
+                 media.html \
+                 media-small.css \
+                 minified.html \
+                 simple.css \
+                 simple.css.gz \
+                 simple.css.gz^headers^ \
+                 simple.gz.html \
+                 simple.html \
+                 $(NULL)
+
+libs::	$(_BROWSER_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_enabled.js
@@ -0,0 +1,101 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  let count = 0;
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onEditorAdded: function (aChrome, aEditor) {
+        count++;
+        if (count == 2) {
+          // we test against first stylesheet after all are ready
+          let editor = aChrome.editors[0];
+          if (!editor.sourceEditor) {
+            editor.addActionListener({
+              onAttach: function (aEditor) {
+                run(aChrome, aEditor);
+              }
+            });
+          } else {
+            run(aChrome, editor);
+          }
+        }
+      }
+    });
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome, aEditor)
+{
+  testEnabledToggle(aChrome, aEditor);
+}
+
+function testEnabledToggle(aChrome, aEditor)
+{
+  is(aEditor, aChrome.editors[0],
+     "stylesheet with index 0 is the first stylesheet listed in the UI");
+
+  let firstStyleSheetEditor = aEditor;
+  let firstStyleSheetUI = aChrome.getSummaryElementForEditor(aEditor);
+  let enabledToggle = firstStyleSheetUI.querySelector(".stylesheet-enabled");
+
+  is(firstStyleSheetEditor.contentDocument.styleSheets[0].disabled, false,
+     "first stylesheet is initially enabled");
+  is(firstStyleSheetEditor.hasFlag("disabled"), false,
+     "first stylesheet is initially enabled, it does not have DISABLED flag");
+  is(firstStyleSheetUI.classList.contains("disabled"), false,
+     "first stylesheet is initially enabled, UI does not have DISABLED class");
+
+  let disabledToggleCount = 0;
+  firstStyleSheetEditor.addActionListener({
+    onFlagChange: function (aEditor, aFlagName) {
+      if (aFlagName != "disabled") {
+        return;
+      }
+      disabledToggleCount++;
+
+      if (disabledToggleCount == 1) {
+        is(firstStyleSheetEditor, aEditor,
+           "FlagChange handler triggered for DISABLED flag on the first editor");
+        is(firstStyleSheetEditor.styleSheet.disabled, true,
+           "first stylesheet is now disabled");
+        is(firstStyleSheetEditor.hasFlag("disabled"), true,
+           "first stylesheet is now disabled, it has DISABLED flag");
+        is(firstStyleSheetUI.classList.contains("disabled"), true,
+           "first stylesheet is now disabled, UI has DISABLED class");
+
+        // now toggle it back to enabled
+        waitForFocus(function () {
+          EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
+        }, gChromeWindow);
+        return;
+      }
+
+      // disabledToggleCount == 2
+      is(firstStyleSheetEditor, aEditor,
+         "FlagChange handler triggered for DISABLED flag on the first editor (2)");
+      is(firstStyleSheetEditor.styleSheet.disabled, false,
+         "first stylesheet is now enabled again");
+      is(firstStyleSheetEditor.hasFlag("disabled"), false,
+         "first stylesheet is now enabled again, it does not have DISABLED flag");
+      is(firstStyleSheetUI.classList.contains("disabled"), false,
+         "first stylesheet is now enabled again, UI does not have DISABLED class");
+
+      finish();
+    }
+  });
+
+  waitForFocus(function () {
+    EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
+  }, gChromeWindow);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_import.js
@@ -0,0 +1,87 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
+
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+const FILENAME = "styleeditor-import-test.css";
+const SOURCE = "body{background:red;}";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run,
+      onEditorAdded: testEditorAdded
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 2,
+     "there is 2 stylesheets initially");
+}
+
+function testImport(aChrome, aEditor)
+{
+  // create file to import first
+  let file = FileUtils.getFile("ProfD", [FILENAME]);
+  let ostream = FileUtils.openSafeFileOutputStream(file);
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  let istream = converter.convertToInputStream(SOURCE);
+  NetUtil.asyncCopy(istream, ostream, function (status) {
+    FileUtils.closeSafeFileOutputStream(ostream);
+
+    // click the import button now that the file to import is ready
+    aChrome._mockImportFile = file;
+
+    waitForFocus(function () {
+      let document = gChromeWindow.document
+      let importButton = document.querySelector(".style-editor-importButton");
+      EventUtils.synthesizeMouseAtCenter(importButton, {}, gChromeWindow);
+    }, gChromeWindow);
+  });
+}
+
+let gAddedCount = 0;
+function testEditorAdded(aChrome, aEditor)
+{
+  if (++gAddedCount == 2) {
+    // test import after the 2 initial stylesheets have been loaded
+    if (!aChrome.editors[0].sourceEditor) {
+      aChrome.editors[0].addActionListener({
+        onAttach: function () {
+          testImport(aChrome);
+        }
+      });
+    } else {
+      testImport(aChrome);
+    }
+  }
+
+  if (!aEditor.hasFlag("imported")) {
+    return;
+  }
+
+  ok(!aEditor.hasFlag("inline"),
+     "imported stylesheet does not have INLINE flag");
+  ok(aEditor.savedFile,
+     "imported stylesheet will be saved directly into the same file");
+  is(aEditor.getFriendlyName(), FILENAME,
+     "imported stylesheet has the same name as the filename");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -0,0 +1,126 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run,
+      onEditorAdded: testEditorAdded
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let gContentAttachHandled = false;
+function run(aChrome)
+{
+  gContentAttachHandled = true;
+  is(aChrome.contentWindow.document.readyState, "complete",
+     "content document is complete");
+
+  let SEC = gChromeWindow.styleEditorChrome;
+  is(SEC, aChrome, "StyleEditorChrome object exists as new window property");
+
+  ok(gChromeWindow.document.title.indexOf("simple testcase") >= 0,
+     "the Style Editor window title contains the document's title");
+
+  // check editors are instantiated
+  is(SEC.editors.length, 2,
+     "there is two StyleEditor instances managed");
+  ok(SEC.editors[0].styleSheetIndex < SEC.editors[1].styleSheetIndex,
+     "editors are ordered by styleSheetIndex");
+
+  // check StyleEditorChrome is a singleton wrt to the same DOMWindow
+  let chromeWindow = StyleEditor.openChrome();
+  is(chromeWindow, gChromeWindow,
+     "attempt to edit the same document returns the same Style Editor window");
+}
+
+let gEditorAddedCount = 0;
+function testEditorAdded(aChrome, aEditor)
+{
+  if (!gEditorAddedCount) {
+    is(gContentAttachHandled, true,
+       "ContentAttach event triggered before EditorAdded");
+  }
+
+  if (aEditor.styleSheetIndex == 0) {
+    gEditorAddedCount++;
+    testFirstStyleSheetEditor(aChrome, aEditor);
+  }
+  if (aEditor.styleSheetIndex == 1) {
+    gEditorAddedCount++;
+    testSecondStyleSheetEditor(aChrome, aEditor);
+  }
+
+  if (gEditorAddedCount == 2) {
+    finish();
+  }
+}
+
+function testFirstStyleSheetEditor(aChrome, aEditor)
+{
+  //testing TESTCASE's simple.css stylesheet
+  is(aEditor.styleSheetIndex, 0,
+     "first stylesheet is at index 0");
+
+  is(aEditor, aChrome.editors[0],
+     "first stylesheet corresponds to StyleEditorChrome.editors[0]");
+
+  ok(!aEditor.hasFlag("inline"),
+     "first stylesheet does not have INLINE flag");
+
+  let summary = aChrome.getSummaryElementForEditor(aEditor);
+  ok(!summary.classList.contains("inline"),
+     "first stylesheet UI does not have INLINE class");
+
+  let name = summary.querySelector(".stylesheet-name").textContent;
+  is(name, "simple.css",
+     "first stylesheet's name is `simple.css`");
+
+  let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+  is(parseInt(ruleCount), 1,
+     "first stylesheet UI shows rule count as 1");
+
+  ok(summary.classList.contains("splitview-active"),
+     "first stylesheet UI is focused/active");
+}
+
+function testSecondStyleSheetEditor(aChrome, aEditor)
+{
+  //testing TESTCASE's inline stylesheet
+  is(aEditor.styleSheetIndex, 1,
+     "second stylesheet is at index 1");
+
+  is(aEditor, aChrome.editors[1],
+     "second stylesheet corresponds to StyleEditorChrome.editors[1]");
+
+  ok(aEditor.hasFlag("inline"),
+     "second stylesheet has INLINE flag");
+
+  let summary = aChrome.getSummaryElementForEditor(aEditor);
+  ok(summary.classList.contains("inline"),
+     "second stylesheet UI has INLINE class");
+
+  let name = summary.querySelector(".stylesheet-name").textContent;
+  ok(/^<.*>$/.test(name),
+     "second stylesheet's name is surrounded by `<>`");
+
+  let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+  is(parseInt(ruleCount), 3,
+     "second stylesheet UI shows rule count as 3");
+
+  ok(!summary.classList.contains("splitview-active"),
+     "second stylesheet UI is NOT focused/active");
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_loading.js
@@ -0,0 +1,39 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  // launch Style Editor right when the tab is created (before load)
+  // this checks that the Style Editor still launches correctly when it is opened
+  // *while* the page is still loading
+  launchStyleEditorChrome(function (aChrome) {
+    isnot(gBrowser.selectedBrowser.contentWindow.document.readyState, "complete",
+          "content document is still loading");
+
+    if (!aChrome.isContentAttached) {
+      aChrome.addChromeListener({
+        onContentAttach: run
+      });
+    } else {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.contentWindow.document.readyState, "complete",
+     "content document is complete");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_new.js
@@ -0,0 +1,117 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run,
+      onEditorAdded: testEditorAdded
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 2,
+     "there is 2 stylesheets initially");
+}
+
+let gAddedCount = 0;  // to add new stylesheet after the 2 initial stylesheets
+let gNewEditor;       // to make sure only one new stylesheet got created
+let gCommitCount = 0; // to make sure only one Commit event is triggered
+
+function testEditorAdded(aChrome, aEditor)
+{
+  gAddedCount++;
+  if (gAddedCount == 2) {
+    waitForFocus(function () { // create a new style sheet
+      let newButton = gChromeWindow.document.querySelector(".style-editor-newButton");
+      EventUtils.synthesizeMouseAtCenter(newButton, {}, gChromeWindow);
+    }, gChromeWindow);
+  }
+  if (gAddedCount != 3) {
+    return;
+  }
+
+  ok(!gNewEditor, "creating a new stylesheet triggers one EditorAdded event");
+  gNewEditor = aEditor; // above test will fail if we get a duplicate event
+
+  is(aChrome.editors.length, 3,
+     "creating a new stylesheet added a new StyleEditor instance");
+
+  let listener = {
+    onAttach: function (aEditor) {
+      waitForFocus(function () {
+        ok(aEditor.isLoaded,
+           "new editor is loaded when attached");
+        ok(aEditor.hasFlag("new"),
+           "new editor has NEW flag");
+        ok(!aEditor.hasFlag("unsaved"),
+           "new editor does not have UNSAVED flag");
+
+        ok(aEditor.inputElement,
+           "new editor has an input element attached");
+
+        ok(aEditor.sourceEditor.hasFocus(),
+           "new editor has focus");
+
+        let summary = aChrome.getSummaryElementForEditor(aEditor);
+        let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+        is(parseInt(ruleCount), 0,
+           "new editor initially shows 0 rules");
+
+        let computedStyle = content.getComputedStyle(content.document.body, null);
+        is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
+           "content's background color is initially white");
+
+        for each (let c in "body{background-color:red;}") {
+          EventUtils.synthesizeKey(c, {}, gChromeWindow);
+        }
+      }, gChromeWindow) ;
+    },
+
+    onCommit: function (aEditor) {
+      gCommitCount++;
+
+      ok(aEditor.hasFlag("new"),
+         "new editor still has NEW flag");
+      ok(aEditor.hasFlag("unsaved"),
+         "new editor has UNSAVED flag after modification");
+
+      let summary = aChrome.getSummaryElementForEditor(aEditor);
+      let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+      is(parseInt(ruleCount), 1,
+         "new editor shows 1 rule after modification");
+
+      let computedStyle = content.getComputedStyle(content.document.body, null);
+      is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
+         "content's background color has been updated to red");
+
+      executeSoon(function () {
+        is(gCommitCount, 1, "received only one Commit event (throttle)");
+
+        aEditor.removeActionListener(listener);
+
+        gNewEditor = null;
+        finish();
+      });
+    }
+  };
+
+  aEditor.addActionListener(listener);
+  if (aEditor.sourceEditor) {
+    listener.onAttach(aEditor);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
@@ -0,0 +1,56 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "minified.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onEditorAdded: function (aChrome, aEditor) {
+        if (aEditor.sourceEditor) {
+          run(aEditor); // already attached to input element
+        } else {
+          aEditor.addActionListener({
+            onAttach: run
+          });
+        }
+      }
+    });
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let editorTestedCount = 0;
+function run(aEditor)
+{
+  if (aEditor.styleSheetIndex == 0) {
+    let prettifiedSource = "body\{\r?\n\tbackground\:white;\r?\n\}\r?\n\r?\ndiv\{\r?\n\tfont\-size\:4em;\r?\n\tcolor\:red\r?\n\}\r?\n";
+    let prettifiedSourceRE = new RegExp(prettifiedSource);
+
+    ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
+       "minified source has been prettified automatically");
+    editorTestedCount++;
+    let chrome = gChromeWindow.styleEditorChrome;
+    let summary = chrome.getSummaryElementForEditor(chrome.editors[1]);
+    EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
+  }
+
+  if (aEditor.styleSheetIndex == 1) {
+    let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
+    let originalSourceRE = new RegExp(originalSource);
+
+    ok(originalSourceRE.test(aEditor.sourceEditor.getText()),
+       "non-minified source has been left untouched");
+    editorTestedCount++;
+  }
+
+  if (editorTestedCount == 2) {
+    finish();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_readonly.js
@@ -0,0 +1,74 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+let gEditorAddedCount = 0;
+let gEditorReadOnlyCount = 0;
+let gChromeListener = {
+  onEditorAdded: function (aChrome, aEditor) {
+    gEditorAddedCount++;
+    if (aEditor.readOnly) {
+      gEditorReadOnlyCount++;
+    }
+
+    if (gEditorAddedCount == aChrome.editors.length) {
+      // continue testing after all editors are ready
+
+      is(gEditorReadOnlyCount, 0,
+         "all editors are NOT read-only initially");
+
+      // all editors have been loaded, queue closing the content tab
+      executeSoon(function () {
+        gBrowser.removeCurrentTab();
+      });
+    }
+  },
+  onContentDetach: function (aChrome) {
+    // check that the UI has switched to read-only
+    run(aChrome);
+  }
+};
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.addTab(); // because we'll close the next one
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener(gChromeListener);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  let document = gChromeWindow.document;
+  let disabledCount;
+  let elements;
+
+  disabledCount = 0;
+  elements = document.querySelectorAll("button,input,select");
+  for (let i = 0; i < elements.length; ++i) {
+    if (elements[i].hasAttribute("disabled")) {
+      disabledCount++;
+    }
+  }
+  ok(elements.length && disabledCount == elements.length,
+     "all buttons, input and select elements are disabled");
+
+  disabledCount = 0;
+  aChrome.editors.forEach(function (aEditor) {
+    if (aEditor.readOnly) {
+      disabledCount++;
+    }
+  });
+  ok(aChrome.editors.length && disabledCount == aChrome.editors.length,
+     "all editors are read-only");
+
+  aChrome.removeChromeListener(gChromeListener);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
@@ -0,0 +1,155 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.gz.html";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onEditorAdded: function (aChrome, aEditor) {
+        if (aEditor.styleSheetIndex != 0) {
+          return; // we want to test against the first stylesheet
+        }
+
+        if (aEditor.sourceEditor) {
+          run(aEditor); // already attached to input element
+        } else {
+          aEditor.addActionListener({
+            onAttach: run
+          });
+        }
+      }
+    });
+
+    gChromeWindow.addEventListener("unload", function onClose() {
+      gChromeWindow.removeEventListener("unload", onClose, true);
+      gChromeWindow = null;
+      executeSoon(function () {
+        waitForFocus(function () {
+          // wait that browser has focus again
+          // open StyleEditorChrome again (a new one since we closed the previous one)
+          launchStyleEditorChrome(function (aChrome) {
+            is(gChromeWindow.document.documentElement.hasAttribute("data-marker"),
+               false,
+               "opened a completely new StyleEditorChrome window");
+
+            aChrome.addChromeListener({
+              onEditorAdded: function (aChrome, aEditor) {
+                if (aEditor.styleSheetIndex != 0) {
+                  return; // we want to test against the first stylesheet
+                }
+
+                if (aEditor.sourceEditor) {
+                  testNewChrome(aEditor); // already attached to input element
+                } else {
+                  aEditor.addActionListener({
+                    onAttach: testNewChrome
+                  });
+                }
+              }
+            });
+          });
+        });
+      });
+    }, true);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let gFilename;
+
+function run(aEditor)
+{
+  gFilename = FileUtils.getFile("ProfD", ["styleeditor-test.css"])
+
+  aEditor.saveToFile(gFilename, function (aFile) {
+    ok(aFile, "file got saved successfully");
+
+    aEditor.addActionListener({
+      onFlagChange: function (aEditor, aFlag) {
+        if (aFlag != "unsaved") {
+          return;
+        }
+
+        ok(aEditor.hasFlag("unsaved"),
+           "first stylesheet has UNSAVED flag after making a change");
+
+        // marker used to check it does not exist when we reopen
+        // ie. the window we opened is indeed a new one
+        gChromeWindow.document.documentElement.setAttribute("data-marker", "true");
+        gChromeWindow.close();
+      }
+    });
+
+    waitForFocus(function () {
+      // insert char so that this stylesheet has the UNSAVED flag
+      EventUtils.synthesizeKey("x", {}, gChromeWindow);
+    }, gChromeWindow);
+  });
+}
+
+function testNewChrome(aEditor)
+{
+  ok(aEditor.savedFile,
+     "first stylesheet editor will save directly into the same file");
+
+  is(aEditor.getFriendlyName(), gFilename.leafName,
+     "first stylesheet still has the filename as it was saved");
+  gFilename = null;
+
+  ok(aEditor.hasFlag("unsaved"),
+     "first stylesheet still has UNSAVED flag at reopening");
+
+  ok(!aEditor.hasFlag("inline"),
+     "first stylesheet does not have INLINE flag");
+
+  ok(!aEditor.hasFlag("error"),
+     "editor does not have error flag initially");
+  let hadError = false;
+
+  let onSaveCallback = function (aFile) {
+    aEditor.addActionListener({
+      onFlagChange: function (aEditor, aFlag) {
+        if (!hadError && aFlag == "error") {
+          ok(aEditor.hasFlag("error"),
+             "editor has ERROR flag after attempting to save with invalid path");
+          hadError = true;
+
+          // save using source editor key binding (previous successful path)
+          waitForFocus(function () {
+            EventUtils.synthesizeKey("S", {accelKey: true}, gChromeWindow);
+          }, gChromeWindow);
+          return;
+        }
+
+        if (hadError && aFlag == "unsaved") {
+          executeSoon(function () {
+            ok(!aEditor.hasFlag("unsaved"),
+               "first stylesheet has no UNSAVED flag after successful save");
+            ok(!aEditor.hasFlag("error"),
+               "ERROR flag has been removed since last operation succeeded");
+            finish();
+          });
+        }
+      }
+    });
+  }
+
+  let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (os == "WINNT") {
+    aEditor.saveToFile("C:\\I_DO_NOT_EXIST_42\\bogus.css", onSaveCallback);
+  } else {
+    aEditor.saveToFile("/I_DO_NOT_EXIST_42/bogos.css", onSaveCallback);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_filter.js
@@ -0,0 +1,101 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function getFilteredItemsCount(nav)
+{
+  let matches = nav.querySelectorAll("*.splitview-filtered");
+  return matches ? matches.length : 0;
+}
+
+function run(aChrome)
+{
+  aChrome.editors[0].addActionListener({onAttach: onFirstEditorAttach});
+  aChrome.editors[1].addActionListener({onAttach: onSecondEditorAttach});
+}
+
+function onFirstEditorAttach(aEditor)
+{
+  let filter = gChromeWindow.document.querySelector(".splitview-filter");
+  // force the command event on input since it is not possible to disable
+  // the search textbox's timeout.
+  let forceCommandEvent = function forceCommandEvent() {
+    let evt = gChromeWindow.document.createEvent("XULCommandEvent");
+    evt.initCommandEvent("command", true, true, gChromeWindow, 0, false, false,
+                         false, false, null);
+    filter.dispatchEvent(evt);
+  }
+  filter.addEventListener("input", forceCommandEvent, false);
+
+  let nav = gChromeWindow.document.querySelector(".splitview-nav");
+  nav.focus();
+
+  is(getFilteredItemsCount(nav), 0,
+     "there is 0 filtered item initially");
+
+  waitForFocus(function () {
+    // Search [s] (type-on-search since we focused nav above - not filter directly)
+    EventUtils.synthesizeKey("s", {}, gChromeWindow);
+
+    // the search space is "simple.css" and "inline stylesheet #1" (2 sheets)
+    is(getFilteredItemsCount(nav), 0,
+       "there is 0 filtered item if searching for 's'");
+
+    EventUtils.synthesizeKey("i", {}, gChromeWindow); // Search [si]
+
+    is(getFilteredItemsCount(nav), 1, // inline stylesheet is filtered
+       "there is 1 filtered item if searching for 's'");
+
+    // use uppercase to check that filtering is case-insensitive
+    EventUtils.synthesizeKey("X", {}, gChromeWindow); // Search [siX]
+    is(getFilteredItemsCount(nav), 2,
+       "there is 2 filtered items if searching for 's'"); // no match
+
+    // clear the search
+    EventUtils.synthesizeKey("VK_ESCAPE", {}, gChromeWindow);
+
+    is(filter.value, "",
+       "filter is back to empty");
+    is(getFilteredItemsCount(nav), 0,
+       "there is 0 filtered item when filter is empty again");
+
+    for each (let c in "inline") {
+      EventUtils.synthesizeKey(c, {}, gChromeWindow);
+    }
+
+    is(getFilteredItemsCount(nav), 1, // simple.css is filtered
+       "there is 1 filtered item if searching for 'inline'");
+
+    // auto-select the only result (enter the editor)
+    EventUtils.synthesizeKey("VK_ENTER", {}, gChromeWindow);
+
+    filter.removeEventListener("input", forceCommandEvent, false);
+  }, gChromeWindow);
+}
+
+function onSecondEditorAttach(aEditor)
+{
+  ok(aEditor.sourceEditor.hasFocus(),
+     "second editor has been selected and focused automatically.");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
@@ -0,0 +1,80 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "four.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let gChrome;
+
+function run(aChrome)
+{
+  gChrome = aChrome;
+  aChrome.editors[0].addActionListener({onAttach: onEditor0Attach});
+  aChrome.editors[2].addActionListener({onAttach: onEditor2Attach});
+}
+
+function getStylesheetNameLinkFor(aEditor)
+{
+  return gChrome.getSummaryElementForEditor(aEditor).querySelector(".stylesheet-name");
+}
+
+function onEditor0Attach(aEditor)
+{
+  waitForFocus(function () {
+    let summary = gChrome.getSummaryElementForEditor(aEditor);
+    EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
+
+    let item = getStylesheetNameLinkFor(gChrome.editors[0]);
+    is(gChromeWindow.document.activeElement, item,
+       "editor 0 item is the active element");
+
+    EventUtils.synthesizeKey("VK_DOWN", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[1]);
+    is(gChromeWindow.document.activeElement, item,
+       "editor 1 item is the active element");
+
+    EventUtils.synthesizeKey("VK_HOME", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[0]);
+    is(gChromeWindow.document.activeElement, item,
+       "fist editor item is the active element");
+
+    EventUtils.synthesizeKey("VK_END", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[3]);
+    is(gChromeWindow.document.activeElement, item,
+       "last editor item is the active element");
+
+    EventUtils.synthesizeKey("VK_UP", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[2]);
+    is(gChromeWindow.document.activeElement, item,
+       "editor 2 item is the active element");
+
+    EventUtils.synthesizeKey("VK_RETURN", {}, gChromeWindow);
+    // this will attach and give focus editor 2
+  }, gChromeWindow);
+}
+
+function onEditor2Attach(aEditor)
+{
+  ok(aEditor.sourceEditor.hasFocus(),
+     "editor 2 has focus");
+
+  gChrome = null;
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
@@ -0,0 +1,63 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+let gOriginalWidth; // these are set by run() when gChromeWindow is ready
+let gOriginalHeight;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    } else {
+      aChrome.addChromeListener({
+        onContentAttach: run
+      });
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 2,
+     "there is 2 stylesheets initially");
+
+  aChrome.editors[0].addActionListener({
+    onAttach: function onEditorAttached(aEditor) {
+      let originalSourceEditor = aEditor.sourceEditor;
+      aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
+
+      // queue a resize to inverse aspect ratio
+      // this will trigger a detach and reattach (to workaround bug 254144)
+      executeSoon(function () {
+        waitForFocus(function () {
+          gOriginalWidth = gChromeWindow.outerWidth;
+          gOriginalHeight = gChromeWindow.outerHeight;
+          gChromeWindow.resizeTo(120, 480);
+
+          executeSoon(function () {
+            is(aEditor.sourceEditor, originalSourceEditor,
+               "the editor still references the same SourceEditor instance");
+            is(aEditor.sourceEditor.getCaretOffset(), 4,
+               "the caret position has been preserved");
+
+            // queue a resize to original aspect ratio
+            waitForFocus(function () {
+              gChromeWindow.resizeTo(gOriginalWidth, gOriginalHeight);
+              executeSoon(function () {
+                finish();
+              });
+            }, gChromeWindow);
+          });
+        }, gChromeWindow);
+      });
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/four.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+  <title>four stylesheets</title>
+  <link rel="stylesheet" type="text/css" media="scren" href="simple.css"/>
+  <style type="text/css">
+  div {
+    font-size: 2em;
+  }
+  </style>
+  <style type="text/css">
+  span {
+    font-size: 3em;
+  }
+  </style>
+  <style type="text/css">
+  p {
+    font-size: 4em;
+  }
+  </style>
+</head>
+<body>
+	<div>four <span>stylesheets</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/head.js
@@ -0,0 +1,46 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/styleeditor/test/";
+const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleeditor/test/";
+const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleeditor/test/";
+
+let gChromeWindow;               //StyleEditorChrome window
+
+function cleanup()
+{
+  if (gChromeWindow) {
+    gChromeWindow.close();
+    gChromeWindow = null;
+  }
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+}
+
+function launchStyleEditorChrome(aCallback)
+{
+  gChromeWindow = StyleEditor.openChrome();
+  if (gChromeWindow.document.readyState != "complete") {
+    gChromeWindow.addEventListener("load", function onChromeLoad() {
+      gChromeWindow.removeEventListener("load", onChromeLoad, true);
+      gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
+      aCallback(gChromeWindow.styleEditorChrome);
+    }, true);
+  } else {
+    gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
+    aCallback(gChromeWindow.styleEditorChrome);
+  }
+}
+
+function addTabAndLaunchStyleEditorChromeWhenLoaded(aCallback)
+{
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    launchStyleEditorChrome(aCallback);
+  }, true);
+}
+
+registerCleanupFunction(cleanup);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/media-small.css
@@ -0,0 +1,5 @@
+/* this stylesheet applies when min-width<400px */
+body {
+	background: red;
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/media.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="simple.css" media="screen,print"/>
+  <link rel="stylesheet" type="text/css" href="media-small.css" media="screen and (min-width: 200px)"/>
+</head>
+<body>
+	<div>test for media labels</div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/minified.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+  <title>minified testcase</title>
+  <style type="text/css"><!--
+body{background:white;}div{font-size:4em;color:red}
+--></style>
+  <style type="text/css">body { background: red; }
+div {
+font-size: 5em;
+color: red
+}</style>
+</head>
+<body>
+	<div>minified <span>testcase</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.css
@@ -0,0 +1,8 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+body {
+  margin: 0;
+}
+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ee3b9efbc1db99982c1bd4d723a4d33694170ab2
GIT binary patch
literal 166
zc$@*I09pSZiwFodqB2eZ19NF@aBO8RV{>x=9gV>bf-n#T@BNB*=D?ySQ^Uc;5AYXC
z+rTEaOWGBJ#(x(%&1EJ-4HixoH7d0BXY8!&PF?#;XVH+M2DiMy%e)mHCk0o87}z_F
z0V>cb;_(`u>~WXmIJXACq&iz7U!qf9qL-8;*H+3%^C5@BrFSesr?#X2%M21WeD*%M
UGIF-)uI6jZ4+5k&-lqTn09FA?{r~^~
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.css.gz^headers^
@@ -0,0 +1,4 @@
+Vary: Accept-Encoding
+Content-Encoding: gzip
+Content-Type: text/css
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.gz.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<head>
+  <title>simple testcase</title>
+  <link rel="stylesheet" type="text/css" media="scren" href="simple.css.gz"/>
+  <style type="text/css">
+  body {
+    background: white;
+  }
+
+  div {
+    font-size: 4em;
+  }
+
+  div > span {
+     text-decoration: underline;
+  }
+  </style>
+</head>
+<body>
+	<div>simple <span>testcase</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<head>
+  <title>simple testcase</title>
+  <link rel="stylesheet" type="text/css" media="scren" href="simple.css"/>
+  <style type="text/css">
+  body {
+    background: white;
+  }
+
+  div {
+    font-size: 4em;
+  }
+
+  div > span {
+     text-decoration: underline;
+  }
+  </style>
+</head>
+<body>
+	<div>simple <span>testcase</span></div>
+</body>
+</html>