org.xwiki.test.wysiwyg.MacroTest.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.test.wysiwyg.MacroTest.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.test.wysiwyg;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.xwiki.test.wysiwyg.framework.AbstractWysiwygTestCase;

import com.thoughtworks.selenium.Wait;

import static org.junit.Assert.*;

/**
 * Integration tests for macro support inside the WYSIWYG editor.
 * 
 * @version $Id: 506e0760409c0c1845c448d6c5f178aabfffb2ae $
 */
public class MacroTest extends AbstractWysiwygTestCase {
    public static final String MENU_MACRO = "Macro";

    public static final String MENU_REFRESH = "Refresh";

    public static final String MENU_COLLAPSE = "Collapse";

    public static final String MENU_COLLAPSE_ALL = "Collapse All";

    public static final String MENU_EXPAND = "Expand";

    public static final String MENU_EXPAND_ALL = "Expand All";

    public static final String MENU_EDIT = "Edit Macro Properties...";

    public static final String MENU_INSERT = "Insert Macro...";

    public static final String MACRO_CATEGORY_SELECTOR = "//select[@title='Select a macro category']";

    public static final String MACRO_LIVE_FILTER_SELECTOR = "//input[@title = 'Type to filter']";

    public static final String MACRO_SELECTOR_LIST = "//div[contains(@class, 'xListBox')]";

    /**
     * Tests that after deleting the last character before a macro the caret remains before the macro and not inside the
     * macro.
     */
    @Test
    public void testDeleteCharacterBeforeMacro() {
        switchToSource();
        setSourceText("a{{html}}b{{/html}}");
        switchToWysiwyg();
        typeDelete();
        typeText("x");
        switchToSource();
        assertSourceText("x{{html}}b{{/html}}");
    }

    /**
     * Tests that by holding the Delete key down before a macro the caret doesn't get inside the macro, but, instead,
     * the macro is deleted.
     */
    @Test
    public void testHoldDeleteKeyBeforeMacro() {
        switchToSource();
        setSourceText("bc{{html}}def{{/html}}g");
        switchToWysiwyg();
        // Place the caret between "b" and "c".
        moveCaret("document.body.firstChild.firstChild", 1);
        typeDelete(2, true);
        typeText("x");
        switchToSource();
        assertSourceText("bxg");
    }

    /**
     * Tests that after deleting with Backspace a text selection ending before a macro the caret remains before the
     * macro and not inside the macro.
     */
    @Test
    public void testSelectCharacterBeforeMacroAndPressBackspace() {
        switchToSource();
        setSourceText("g{{html}}h{{/html}}");
        switchToWysiwyg();
        // Select the character preceding the macro.
        selectNode("document.body.firstChild.firstChild");
        typeBackspace();
        typeText("x");
        switchToSource();
        assertSourceText("x{{html}}h{{/html}}");
    }

    /**
     * Tests that if we select the text before a macro and insert a symbol instead of it then the symbol is inserted
     * before the macro and not inside the macro.
     */
    @Test
    public void testSelectCharacterBeforeMacroAndInsertSymbol() {
        switchToSource();
        setSourceText("i{{html}}j{{/html}}");
        switchToWysiwyg();
        // Select the character preceding the macro.
        selectNode("document.body.firstChild.firstChild");
        clickSymbolButton();
        getSelenium().click("//div[@title='copyright sign']");
        typeText("x");
        switchToSource();
        assertSourceText("\u00A9x{{html}}j{{/html}}");
    }

    /**
     * Tests that a macro can be deleted by pressing Delete key when the caret is placed before that macro.
     */
    @Test
    public void testPressDeleteJustBeforeMacro() {
        switchToSource();
        setSourceText("j{{html}}k{{/html}}l");
        switchToWysiwyg();
        // Move the caret just before the macro.
        moveCaret("document.body.firstChild.firstChild", 1);
        typeDelete();
        typeText("x");
        switchToSource();
        assertSourceText("jxl");
    }

    /**
     * Tests that after deleting the last character after a macro the caret remains after the macro and not inside the
     * macro.
     */
    @Test
    public void testDeleteCharacterAfterMacro() {
        switchToSource();
        setSourceText("a{{html}}b{{/html}}c");
        switchToWysiwyg();
        // Move the caret at the end.
        moveCaret("document.body.firstChild.lastChild", 1);
        typeBackspace();
        typeText("x");
        switchToSource();
        assertSourceText("a{{html}}b{{/html}}x");
    }

    /**
     * Tests that by holding the Backspace key down after a macro the caret doesn't get inside the macro, but, instead,
     * the macro is deleted.
     */
    @Test
    public void testHoldBackspaceKeyAfterMacro() {
        switchToSource();
        setSourceText("c{{html}}def{{/html}}g");
        switchToWysiwyg();
        // Move the caret at the end.
        moveCaret("document.body.firstChild.lastChild", 1);
        typeBackspace(2, true);
        typeText("x");
        switchToSource();
        assertSourceText("cx");
    }

    /**
     * Tests that after deleting with Delete key a text selection starting after a macro the caret remains after the
     * macro and not inside the macro.
     */
    @Test
    public void testSelectCharacterAfterMacroAndPressDelete() {
        switchToSource();
        setSourceText("g{{html}}h{{/html}}i");
        switchToWysiwyg();
        // Select the character following the macro.
        selectNode("document.body.firstChild.lastChild");
        typeDelete();
        typeText("x");
        switchToSource();
        assertSourceText("g{{html}}h{{/html}}x");
    }

    /**
     * Tests that if we select the text after a macro and insert a symbol instead of it then the symbol is inserted
     * after the macro and not inside the macro.
     */
    @Test
    public void testSelectCharacterAfterMacroAndInsertSymbol() {
        switchToSource();
        setSourceText("i{{html}}j{{/html}}k");
        switchToWysiwyg();
        // Select the character following the macro.
        selectNode("document.body.firstChild.lastChild");
        clickSymbolButton();
        getSelenium().click("//div[@title='copyright sign']");
        typeText("x");
        switchToSource();
        assertSourceText("i{{html}}j{{/html}}\u00A9x");
    }

    /**
     * Tests that a macro can be deleted by pressing Backspace key when the caret is placed after that macro.
     */
    @Test
    public void testPressBackspaceJustAfterMacro() {
        switchToSource();
        setSourceText("k{{html}}l{{/html}}m");
        switchToWysiwyg();
        // Move the caret at the end.
        moveCaret("document.body.firstChild.lastChild", 0);
        typeBackspace();
        typeText("x");
        switchToSource();
        assertSourceText("kxm");
    }

    /**
     * Tests that Undo/Redo operations don't affect the macros present in the edited document.
     */
    @Test
    public void testUndoRedoWhenMacrosArePresent() {
        switchToSource();
        setSourceText("{{html}}pq{{/html}}");
        switchToWysiwyg();
        // We have to manually place the caret to be sure it is before the macro. The caret is before the macro when the
        // browser window is focused but inside the macro when the tests run in background.
        moveCaret("document.body", 0);
        typeText("uv");
        clickUndoButton();
        clickRedoButton();
        switchToSource();
        assertSourceText("uv{{html}}pq{{/html}}");
    }

    /**
     * Clicks on a macro and deletes it.
     */
    @Test
    public void testSelectAndDeleteMacro() {
        switchToSource();
        setSourceText("{{html}}<p>foo</p>{{/html}}\n\nbar");
        switchToWysiwyg();
        selectRichTextAreaFrame();
        try {
            getSelenium().click(getMacroLocator(0));
        } finally {
            selectTopFrame();
        }
        typeBackspace();
        switchToSource();
        assertSourceText("bar");
    }

    /**
     * @see XWIKI-3221: New lines inside code macro are lost when saving
     */
    @Test
    public void testWhiteSpacesInsideCodeMacroArePreserved() {
        String wikiText = "{{code}}\nfunction foo() {\n    alert('bar');\n}\n{{/code}}";
        switchToSource();
        setSourceText(wikiText);
        switchToWysiwyg();
        switchToSource();
        assertSourceText(wikiText);
    }

    /**
     * Test if the user can collapse and expand all the macros using the macro menu and if this menu is synchronized
     * with the current state of the rich text area.
     */
    @Test
    public void testCollapseAndExpandAllMacros() {
        setContent("no macro");

        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_REFRESH));
        // If there's no macro present then the Collapse/Expand menu items must be disabled.
        assertFalse(isMenuEnabled(MENU_COLLAPSE_ALL));
        assertFalse(isMenuEnabled(MENU_EXPAND_ALL));
        closeMenuContaining(MENU_REFRESH);

        switchToSource();
        setSourceText("k\n\n{{html}}l{{/html}}\n\nm\n\n{{code}}n{{/code}}\n\no");
        switchToWysiwyg();
        clickMenu(MENU_MACRO);
        // By default all macros are expanded.
        assertTrue(isMenuEnabled(MENU_COLLAPSE_ALL));
        assertFalse(isMenuEnabled(MENU_EXPAND_ALL));

        // Let's collapse all macros.
        clickMenu(MENU_COLLAPSE_ALL);

        clickMenu(MENU_MACRO);
        // Now all macros should be collapsed.
        assertFalse(isMenuEnabled(MENU_COLLAPSE_ALL));
        assertTrue(isMenuEnabled(MENU_EXPAND_ALL));

        // Let's expand all macros.
        clickMenu(MENU_EXPAND_ALL);

        clickMenu(MENU_MACRO);
        // Now all macros should be expanded.
        assertTrue(isMenuEnabled(MENU_COLLAPSE_ALL));
        assertFalse(isMenuEnabled(MENU_EXPAND_ALL));
        closeMenuContaining(MENU_REFRESH);

        // Let's collapse the first macro by selecting it first and then using the shortcut key.
        selectMacro(0);
        collapseMacrosUsingShortcutKey();
        // Finally unselect the macro.
        clearMacroSelection();

        // Now let's check the menu. Both Collapse All and Expand All menu items should be enabled.
        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_COLLAPSE_ALL));
        assertTrue(isMenuEnabled(MENU_EXPAND_ALL));
    }

    /**
     * Test if the user can collapse and expand the selected macros using the macro menu and if this menu is
     * synchronized with the current state of the rich text area.
     */
    @Test
    public void testCollapseAndExpandSelectedMacros() {
        switchToSource();
        setSourceText("o\n\n{{html}}n{{/html}}\n\nm\n\n{{code}}l{{/code}}\n\nk");
        switchToWysiwyg();

        // If no macro is selected then Expand and Collapse menu entries shouldn't be present.
        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_REFRESH));
        assertFalse(isMenuEnabled(MENU_COLLAPSE));
        assertFalse(isMenuEnabled(MENU_EXPAND));
        closeMenuContaining(MENU_REFRESH);

        // Select the first macro and collapse it (by default macros should be expanded).
        selectMacro(0);
        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_COLLAPSE));
        assertFalse(isMenuEnabled(MENU_EXPAND));
        clickMenu(MENU_COLLAPSE);

        // Now expand it back.
        clickMenu(MENU_MACRO);
        assertFalse(isMenuEnabled(MENU_COLLAPSE));
        assertTrue(isMenuEnabled(MENU_EXPAND));
        clickMenu(MENU_EXPAND);

        // Let's select the second macro too.
        getSelenium().controlKeyDown();
        selectMacro(1);
        getSelenium().controlKeyUp();

        // Collapse both selected macros.
        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_COLLAPSE));
        assertFalse(isMenuEnabled(MENU_EXPAND));
        clickMenu(MENU_COLLAPSE);

        // Let's check if the menu reports them as collapsed.
        clickMenu(MENU_MACRO);
        assertFalse(isMenuEnabled(MENU_COLLAPSE));
        assertTrue(isMenuEnabled(MENU_EXPAND));

        // Expand both.
        clickMenu(MENU_EXPAND);

        // Let's check if the menu reports them as expanded.
        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_COLLAPSE));
        assertFalse(isMenuEnabled(MENU_EXPAND));
        closeMenuContaining(MENU_COLLAPSE);
    }

    /**
     * Test if the user can select a macro by clicking it and then toggle between collapsed and expanded states using
     * the space key.
     */
    @Test
    public void testClickToSelectMacroAndToggleCollapse() {
        // Let's use a macro without definition.
        switchToSource();
        setSourceText("{{foo}}bar{{/foo}}");
        switchToWysiwyg();

        // By default macros are expanded. Let's check this.
        // Note: We have to select the rich text area frame because the visibility of an element is evaluated relative
        // to the current window.
        selectRichTextAreaFrame();
        try {
            assertFalse(getSelenium().isVisible(getMacroPlaceHolderLocator(0)));
            assertTrue(getSelenium().isVisible(getMacroOutputLocator(0)));
        } finally {
            selectTopFrame();
        }

        // Select the macro.
        selectMacro(0);

        // Let's collapse the selected macro and check its state.
        toggleMacroCollapsedState();
        selectRichTextAreaFrame();
        try {
            assertTrue(getSelenium().isVisible(getMacroPlaceHolderLocator(0)));
            assertFalse(getSelenium().isVisible(getMacroOutputLocator(0)));
        } finally {
            selectTopFrame();
        }

        // Let's expand the selected macro and check its state.
        toggleMacroCollapsedState();
        selectRichTextAreaFrame();
        try {
            assertFalse(getSelenium().isVisible(getMacroPlaceHolderLocator(0)));
            assertTrue(getSelenium().isVisible(getMacroOutputLocator(0)));
        } finally {
            selectTopFrame();
        }
    }

    /**
     * Tests the refresh feature when there's no macro present in the edited document.
     */
    @Test
    public void testRefreshContentWithoutMacros() {
        String text = "a b";
        typeText(text);
        assertEquals(text, getRichTextArea().getText());

        // If no macros are present then the refresh shoudn't affect too much the edited content.
        refreshMacros();
        assertEquals(text, getRichTextArea().getText());
    }

    /**
     * Tests that the user can refresh all the macros from the edited document by using the Refresh menu.
     */
    @Test
    public void testRefreshMacros() {
        switchToSource();
        setSourceText("{{box}}p{{/box}}\n\n{{code}}q{{/code}}");
        switchToWysiwyg();

        // Collapse the second macro.
        selectRichTextAreaFrame();
        try {
            assertFalse(getSelenium().isVisible(getMacroPlaceHolderLocator(1)));
            assertTrue(getSelenium().isVisible(getMacroOutputLocator(1)));
        } finally {
            selectTopFrame();
        }
        selectMacro(1);
        toggleMacroCollapsedState();
        selectRichTextAreaFrame();
        try {
            assertTrue(getSelenium().isVisible(getMacroPlaceHolderLocator(1)));
            assertFalse(getSelenium().isVisible(getMacroOutputLocator(1)));
        } finally {
            selectTopFrame();
        }

        // Unselect the macro.
        clearMacroSelection();

        // Refresh the content
        refreshMacros();

        // Check if the second macro is expanded.
        selectRichTextAreaFrame();
        try {
            assertFalse(getSelenium().isVisible(getMacroPlaceHolderLocator(1)));
            assertTrue(getSelenium().isVisible(getMacroOutputLocator(1)));
        } finally {
            selectTopFrame();
        }
    }

    /**
     * Tests that the user can refresh the Table Of Contents macro after adding more headers.
     */
    @Test
    public void testRefreshTocMacro() {
        switchToSource();
        setSourceText("{{toc start=\"1\"/}}\n\n= Title 1\n\n== Title 2");
        switchToWysiwyg();

        // We should have two list items in the edited document.
        String listItemCountExpression = "return document.getElementsByTagName('li').length";
        assertEquals(2L, getRichTextArea().executeScript(listItemCountExpression));

        // Place the caret after the second heading and insert a new one.
        moveCaret("document.getElementsByTagName('h2')[0].firstChild.firstChild", 7);
        // Wait for the macro to be unselected.
        waitForSelectedMacroCount(0);
        typeEnter();
        typeText("Title 3");
        applyStyleTitle3();

        // Refresh the content and the TOC macro.
        refreshMacros();

        // We should have three list items in the edited document now.
        assertEquals(3L, getRichTextArea().executeScript(listItemCountExpression));
    }

    /**
     * Tests the edit macro feature by editing a HTML macro instance and changing its content and a parameter.
     */
    @Test
    public void testEditHTMLMacro() {
        switchToSource();
        setSourceText("{{html}}white{{/html}}");
        switchToWysiwyg();
        editMacro(0);

        // Change the content of the HTML macro.
        setFieldValue("pd-content-input", "black");

        // Set the Wiki parameter to true.
        getSelenium().select("pd-wiki-input", "yes");

        // Apply changes.
        applyMacroChanges();

        // Test if our changes have been applied.
        switchToSource();
        assertSourceText("{{html wiki=\"true\"}}\nblack\n{{/html}}");
        switchToWysiwyg();

        // Edit again, this time using the default value for the Wiki parameter.
        editMacro(0);

        // Set the Wiki parameter to its default value, false.
        assertEquals("true", getSelenium().getValue("pd-wiki-input"));
        getSelenium().select("pd-wiki-input", "no");

        // Apply changes.
        applyMacroChanges();

        // Test if our changes have been applied. This time the Wiki parameter is missing from the output because it has
        // the default value.
        switchToSource();
        assertSourceText("{{html}}\nblack\n{{/html}}");
    }

    /**
     * Tests if the edit macro feature doesn't fail when the user inputs special characters like {@code "} (used for
     * wrapping parameter values) or {@code |-|} (used to separate the macro name, parameter list and macro content in
     * macro serialization).
     * 
     * @see XWIKI-3270: Quotes inside macro parameter values need to be escaped
     */
    @Test
    public void testEditMacroWithSpecialCharactersInParameterValues() {
        switchToSource();
        setSourceText("{{box title =  \"1~\"2|-|3=~~~\"4~~\" }}=~\"|-|~~{{/box}}");
        switchToWysiwyg();
        editMacro(0);

        // Check if the content of the macro has the right value.
        assertEquals("=~\"|-|~~", getSelenium().getValue("pd-content-input"));

        // Check if the title parameter has the right value (it should be the first text input).
        assertEquals("1\"2|-|3=~\"4~", getSelenium().getValue("pd-title-input"));

        // Change the title parameter.
        setFieldValue("pd-title-input", "a\"b|-|c=~\"d~");
        applyMacroChanges();

        switchToSource();
        assertSourceText("{{box title=\"a~\"b|-|c=~~~\"d~~\"}}\n=~\"|-|~~\n{{/box}}");
    }

    /**
     * Tests if the edit macro feature doesn't fail when the user tries to edit an unregistered macro (a macro who's
     * descriptor can't be found).
     */
    @Test
    public void testEditUnregisteredMacro() {
        switchToSource();
        setSourceText("{{foo}}bar{{/foo}}");
        switchToWysiwyg();
        editMacro(0);

        // Check if the dialog shows the error message
        assertTrue(getSelenium().isVisible("//div[@class = 'xDialogBody']/div[contains(@class, 'errormessage')]"));
    }

    /**
     * Tests that macro edits can be undone.
     */
    @Test
    public void testUndoMacroEdit() {
        switchToSource();
        setSourceText("{{velocity}}$xcontext.user{{/velocity}}");
        switchToWysiwyg();

        // First edit.
        editMacro(0);
        setFieldValue("pd-content-input", "$datetool.date");
        applyMacroChanges();

        // Second edit.
        editMacro(0);
        setFieldValue("pd-content-input", "$xwiki.version");
        applyMacroChanges();

        waitForPushButton(TOOLBAR_BUTTON_UNDO_TITLE, true);
        clickUndoButton(2);
        waitForPushButton(TOOLBAR_BUTTON_REDO_TITLE, true);
        clickRedoButton();
        switchToSource();
        assertSourceText("{{velocity}}\n$datetool.date\n{{/velocity}}");
    }

    /**
     * Tests the basic insert macro scenario, using the code macro.
     */
    @Test
    public void testInsertCodeMacro() {
        insertMacro("Code");

        setFieldValue("pd-content-input", "function f(x) {\n  return x;\n}");
        applyMacroChanges();

        editMacro(0);
        setFieldValue("pd-title-input", "Identity function");
        applyMacroChanges();

        switchToSource();
        assertSourceText("{{code title=\"Identity function\"}}\nfunction f(x) {\n  return x;\n}\n{{/code}}");
    }

    /**
     * Tests if the ToC macro can be inserted in an empty paragraph without receiving the "Not an inline macro" error
     * message.
     * 
     * @see XWIKI-3551: Cannot insert standalone macros
     */
    @Test
    public void testInsertTOCMacro() {
        // Create two headings to be able to detect if the ToC macro has the right output.
        typeText("Title 1");
        applyStyleTitle1();

        typeEnter();
        typeText("Title 2");
        applyStyleTitle2();

        // Let's insert a ToC macro between the two headings.
        // First, place the caret at the end of first heading.
        moveCaret("document.body.getElementsByTagName('h1')[0].firstChild", 7);
        // Get out of the heading.
        typeEnter();
        // Insert the ToC macro
        insertMacro("Table Of Contents");
        // Make sure the ToC starts with level 2 headings.
        setFieldValue("pd-start-input", "2");
        applyMacroChanges();

        // Check the output of the ToC macro.
        assertEquals(1L, getRichTextArea().executeScript("return document.getElementsByTagName('li').length"));

        // Check the XWiki syntax.
        switchToSource();
        assertSourceText("= Title 1 =\n\n\n{{toc start=\"2\"/}}\n\n\n== Title 2 ==");
    }

    /**
     * @see XWIKI-4048: Automatically add empty new line before/after macros when inserting standalone macros
     */
    @Test
    public void testInsertStandAloneMacroInline() {
        switchToSource();
        setSourceText("= Heading =\n\nparagraph");
        switchToWysiwyg();

        // Place the caret inside the paragraph.
        moveCaret("document.body.lastChild.firstChild", 4);

        // Insert the ToC macro
        insertMacro("Table Of Contents");
        applyMacroChanges();

        // Check the output of the ToC macro.
        assertEquals(1L, getRichTextArea().executeScript("return document.getElementsByTagName('li').length"));

        // Check the XWiki syntax.
        switchToSource();
        assertSourceText("= Heading =\n\npara\n\n{{toc/}}\n\ngraph");
    }

    /**
     * Inserts a HTML macro, whose output contains block-level elements, in the middle of a paragraph's text and tests
     * if the macro can be fixed by separating it in an empty paragraph.
     * 
     * @see XWIKI-3551: Cannot insert standalone macros
     */
    @Test
    public void testInsertHTMLMacroWithBlockContentInANotEmptyParagraph() {
        // Create a paragraph with some text inside.
        typeText("beforeafter");
        applyStyleTitle1();
        applyStylePlainText();

        // Place the caret in the middle of the paragraph.
        moveCaret("document.body.firstChild.firstChild", 6);

        // Insert the HTML macro.
        insertMacro("HTML");
        // Make the macro render a list, which is forbidden inside a paragraph.
        setFieldValue("pd-content-input", "<ul><li>xwiki</li></ul>");
        applyMacroChanges();

        // At this point the macro should render an error message instead of the list.
        String listItemCountExpression = "return document.getElementsByTagName('li').length";
        assertEquals(0L, getRichTextArea().executeScript(listItemCountExpression));

        // Let's fix the macro by separating it in an empty paragraph.
        // Move the caret before the macro and press Enter to move it into a new paragraph.
        moveCaret("document.body.firstChild.firstChild", 6);
        typeEnter();
        // Move the caret after the macro and press Enter to move the following text in a new paragraph.
        moveCaret("document.body.lastChild.lastChild", 0);
        typeEnter();

        // Now the macro should be in an empty paragraph.
        // Let's refresh the content to see if the macro was fixed.
        refreshMacros();
        // Check the output of the HTML macro.
        assertEquals(1L, getRichTextArea().executeScript(listItemCountExpression));

        // Check the XWiki syntax.
        switchToSource();
        assertSourceText("before\n\n{{html}}\n<ul><li>xwiki</li></ul>\n{{/html}}\n\nafter");
    }

    /**
     * @see XWIKI-3570: Code macro fails to escape properly in GWT editor
     */
    @Test
    public void testInsertCodeMacroWithXMLComments() {
        // Insert the Code macro.
        insertMacro("Code");
        // Set the language parameter to XML.
        setFieldValue("pd-language-input", "xml");
        // Set the content. Include XML comments in the content.
        setFieldValue("pd-content-input",
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- this is a test -->\n<test>123</test>");
        applyMacroChanges();

        // Check the XWiki syntax.
        switchToSource();
        assertSourceText("{{code language=\"xml\"}}\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                + "<!-- this is a test -->\n<test>123</test>\n{{/code}}");
        switchToWysiwyg();

        // Edit the inserted macro.
        editMacro(0);
        assertEquals("xml", getSelenium().getValue("pd-language-input"));
        assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- this is a test -->\n<test>123</test>",
                getSelenium().getValue("pd-content-input"));
        closeDialog();
    }

    /**
     * @see XWIKI-3581: WYSIWYG editor treats macro parameter names as case sensitive
     */
    @Test
    public void testDetectMacroParameterNamesIgnoringCase() {
        switchToSource();
        setSourceText("{{box CSSClaSS=\"foo\"}}bar{{/box}}");
        switchToWysiwyg();
        // Edit the Box macro.
        editMacro(0);
        // See if the CSSClaSS parameter was correctly detected.
        assertEquals("foo", getSelenium().getValue("pd-cssClass-input"));
        // Change the value of the CSSClaSS parameter.
        setFieldValue("pd-cssClass-input", "xyz");
        applyMacroChanges();
        // Check the XWiki syntax.
        switchToSource();
        assertSourceText("{{box CSSClaSS=\"xyz\"}}\nbar\n{{/box}}");
    }

    /**
     * @see XWIKI-3735: Differentiate macros with empty content from macros without content.
     */
    @Test
    public void testDifferentiateMacrosWithEmptyContentFromMacrosWithoutContent() {
        StringBuffer macros = new StringBuffer();
        macros.append("{{code/}}");
        macros.append("{{code}}{{/code}}");
        macros.append("{{code title=\"1|-|2\"/}}");
        macros.append("{{code title=\"1|-|2\"}}{{/code}}");

        // Insert the macros.
        switchToSource();
        setSourceText(macros.toString());
        switchToWysiwyg();
        // See if the macro syntax is left unchanged when the macros are not edited.
        switchToSource();
        assertSourceText(macros.toString());
        switchToWysiwyg();

        // Edit the first macro (the one without content and without arguments).
        editMacro(0);
        setFieldValue("pd-content-input", "|-|");
        applyMacroChanges();
        switchToSource();
        assertSourceText(
                "{{code}}|-|{{/code}}{{code}}{{/code}}{{code title=\"1|-|2\"/}}{{code title=\"1|-|2\"}}{{/code}}");
        switchToWysiwyg();

        // Edit the second macro (the one with empty content but without arguments).
        editMacro(1);
        setFieldValue("pd-title-input", "|-|");
        setFieldValue("pd-content-input", "|-|");
        applyMacroChanges();

        // Edit the third macro (the one without content but with arguments).
        editMacro(2);
        setFieldValue("pd-title-input", "");
        setFieldValue("pd-content-input", "|-|");
        applyMacroChanges();

        // Edit the forth macro (the one with empty content and with arguments).
        editMacro(3);
        setFieldValue("pd-content-input", "|-|");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("{{code}}|-|{{/code}}{{code title=\"|-|\"}}|-|{{/code}}"
                + "{{code}}|-|{{/code}}{{code title=\"1|-|2\"}}|-|{{/code}}");
    }

    /**
     * @see XWIKI-4085: Content duplicated if i have a macro (toc, id..) in an html macro.
     */
    @Test
    public void testNestedMacrosAreNotDuplicated() {
        StringBuilder content = new StringBuilder();
        content.append("{{html wiki=\"true\"}}\n\n");
        content.append("= Hello title 1 =\n\n");
        content.append("{{toc start=\"2\"/}}\n\n");
        content.append("= Hello title 2 =\n\n");
        content.append("{{/html}}");
        switchToSource();
        setSourceText(content.toString());
        switchToWysiwyg();

        // Check if only one macro was detected (which should be the top level macro).
        assertEquals(1, getMacroCount());

        // Check if the top level macro was correctly detected.
        deleteMacro(0);
        switchToSource();
        assertSourceText("");

        // Reset the initial content.
        setSourceText(content.toString());
        switchToWysiwyg();

        // Check if the nested macro is duplicated.
        switchToSource();
        assertSourceText(content.toString());
    }

    /**
     * @see XWIKI-4155: Use double click or Enter to select the macro to insert.
     */
    @Test
    public void testDoubleClickToSelectMacroToInsert() {
        openSelectMacroDialog();

        // We have to wait for the specified macro to be displayed on the dialog because the loading indicator is
        // removed just before the list of macros is displayed and the Selenium click command can interfere.
        waitForMacroListItem("Info Message", true);
        // Each double click event should be preceded by a click event.
        getSelenium().click(getMacroListItemLocator("Info Message"));
        // Fire the double click event.
        getSelenium().doubleClick(getMacroListItemLocator("Info Message"));
        waitForDialogToLoad();

        // Fill the macro content.
        setFieldValue("pd-content-input", "x");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("{{info}}\nx\n{{/info}}");
    }

    /**
     * @see XWIKI-4155: Use double click or Enter to select the macro to insert.
     */
    @Test
    public void testPressEnterToSelectMacroToInsert() {
        openSelectMacroDialog();

        // We have to wait for the specified macro to be displayed on the dialog because the loading indicator is
        // removed just before the list of macros is displayed and the Selenium click command can interfere.
        waitForMacroListItem("HTML", true);
        // Select a macro.
        getSelenium().click(getMacroListItemLocator("HTML"));
        // Press Enter to choose the selected macro.
        getSelenium().typeKeys("//div[@class = 'xListBox']", "\\13");
        waitForDialogToLoad();

        // Fill the macro content.
        setFieldValue("pd-content-input", "a");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("{{html}}\na\n{{/html}}");
    }

    /**
     * @see XWIKI-4137: Pop up the "Edit macro properties" dialog when double-clicking on a macro block.
     */
    @Test
    public void testDoubleClickToEditMacro() {
        // Insert two macros.
        switchToSource();
        setSourceText("{{error}}x{{/error}}{{info}}y{{/info}}");
        switchToWysiwyg();

        // Double click to edit the second macro.
        selectRichTextAreaFrame();
        try {
            String infoMacroLocator = getMacroLocator(1);
            waitForElement(infoMacroLocator);
            getSelenium().doubleClick(infoMacroLocator);
        } finally {
            selectTopFrame();
        }
        waitForDialogToLoad();

        // Fill the macro content.
        setFieldValue("pd-content-input", "z");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("{{error}}x{{/error}}{{info}}z{{/info}}");
    }

    /**
     * Sometimes when you double click a macro the browser selects the macro container (i.e. the DOM selection wraps the
     * element that contains the macro output). Test if the macro edit box is still triggered.
     */
    @Test
    public void testDoubleClickToEditMacroWhenDOMSelectionWrapsTheMacroContainer() {
        // Insert a macro.
        switchToSource();
        setSourceText("before {{error}}currently{{/error}} after");
        switchToWysiwyg();

        // Double click to edit the macro.
        selectRichTextAreaFrame();
        try {
            // Fire the double click event on the macro.
            getSelenium().doubleClick(getMacroLocator(0));
        } finally {
            selectTopFrame();
        }
        waitForDialogToLoad();

        // Fill the macro content.
        setFieldValue("pd-content-input", "now");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("before {{error}}now{{/error}} after");
    }

    /**
     * @see XWIKI-6121: Double clicking on text makes macro edit box appear
     */
    @Test
    public void testDoubleClickOutsideSelectedMacro() {
        // Insert a macro.
        switchToSource();
        // Put the outside text before the macro to overcome an issue in Selenium's MouseMoveAction.
        // See http://code.google.com/p/selenium/issues/detail?id=4863 (Move mouse action fails inside an iframe if the
        // x/y coordinates of the target element on the screen are greater than the width/height of the iframe)
        setSourceText("(% id=\"outside\" %)after\n\n{{info}}before{{/info}}");
        switchToWysiwyg();

        // Select the macro.
        selectMacro(0);
        waitForSelectedMacroCount(1);

        // Double click on the paragraph outside of the macro output.
        selectRichTextAreaFrame();
        try {
            getSelenium().doubleClick("document.getElementById('outside')");
        } finally {
            selectTopFrame();
        }

        try {
            waitForDialogToLoad();
            fail("The macro edit box shouldn't be triggered by double clicking outside of the macro output.");
        } catch (TimeoutException e) {
            // Expected.
        }
    }

    /**
     * @see XWIKI-3437: List macros by category/library
     */
    @Test
    public void testSelectMacroFromCategory() {
        openSelectMacroDialog();

        // "All Macros" category should be selected by default.
        assertEquals("All Macros", getSelectedMacroCategory());

        // Make sure the "Code" and "Velocity" macros are present in "All Macros" category.
        waitForMacroListItem("Code", true);
        waitForMacroListItem("Velocity", true);

        // "Velocity" shouldn't be in the "Formatting" category.
        selectMacroCategory("Formatting");
        waitForMacroListItem("Code", true);
        waitForMacroListItem("Velocity", false);

        // "Code" shouldn't be in the "Development" category.
        selectMacroCategory("Development");
        waitForMacroListItem("Velocity", true);
        waitForMacroListItem("Code", false);

        // Select the "Velocity" macro.
        getSelenium().click(getMacroListItemLocator("Velocity"));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();

        // Set the content field.
        setFieldValue("pd-content-input", "$xwiki.version");
        applyMacroChanges();

        // Open the "Select Macro" dialog again to see if its state was preserved.
        openSelectMacroDialog();
        assertEquals("Development", getSelectedMacroCategory());
        closeDialog();

        // Check the result.
        switchToSource();
        assertSourceText("{{velocity}}\n$xwiki.version\n{{/velocity}}");
    }

    /**
     * @see XWIKI-4206: Add the ability to search in the list of macros
     */
    @Test
    public void testFilterMacrosFromCategory() {
        openSelectMacroDialog();

        // Make sure the current category is "All Macros".
        selectMacroCategory("All Macros");

        // Check if "Velocity", "Footnote" and "Error Message" macros are present.
        waitForMacroListItem("Velocity", true);
        waitForMacroListItem("Footnote", true);
        waitForMacroListItem("Error Message", true);

        // Check if the filter can make a difference.
        int expectedMacroCountAfterFilterAllMacros = getMacroListItemCount("note");
        assertTrue(expectedMacroCountAfterFilterAllMacros < getMacroListItemCount());

        // Filter the macros.
        filterMacrosContaining("note");

        // Check what macros are present.
        waitForMacroListItem("Velocity", false);
        waitForMacroListItem("Error Message", true);

        // Check the number of macros.
        assertEquals(expectedMacroCountAfterFilterAllMacros, getMacroListItemCount());

        // Check if the filter is preserved when switching the category.
        // Select the category of the "Footnote" macro.
        selectMacroCategory("Content");
        waitForMacroListItem("Footnote", true);
        waitForMacroListItem("Error Message", false);
        // Select the category of the "Error Message" macro.
        selectMacroCategory("Formatting");
        waitForMacroListItem("Error Message", true);
        waitForMacroListItem("Footnote", false);

        // Save the current macro list item count to be able to check if the filter state is preserved.
        int previousMacroListItemCount = getMacroListItemCount();

        // Select the "Error Message" macro.
        getSelenium().click(getMacroListItemLocator("Error Message"));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();

        // Set the content field.
        setFieldValue("pd-content-input", "test");
        applyMacroChanges();

        // Open the "Select Macro" dialog again to see if the filter was preserved.
        openSelectMacroDialog();
        waitForMacroListItem("Error Message", true);
        assertEquals("note", getMacroFilterValue());
        assertEquals(previousMacroListItemCount, getMacroListItemCount());
        assertEquals(previousMacroListItemCount, getMacroListItemCount("note"));
        closeDialog();

        // Check the result.
        switchToSource();
        assertSourceText("{{error}}\ntest\n{{/error}}");
    }

    /**
     * @see XWIKI-3434: Use the dialog wizard for insert macro UI
     */
    @Test
    public void testReturnToSelectMacroStep() {
        openSelectMacroDialog();
        waitForMacroListItem("Velocity", true);

        // Filter the macros.
        int scriptMacroCount = getMacroListItemCount("script");
        selectMacroCategory("Development");
        filterMacrosContaining("script");
        waitForMacroListItemCount(scriptMacroCount);

        // Select the "Groovy" macro.
        getSelenium().click(getMacroListItemLocator("Groovy"));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();

        // Return to the "Select Macro" step.
        getSelenium().click("//div[@class = 'xDialogContent']//button[text() = 'Previous']");
        waitForDialogToLoad();

        // Check if the state of the "Select Macro" dialog was preserved.
        assertEquals("Development", getSelectedMacroCategory());
        assertEquals("script", getMacroFilterValue());

        // Select a different macro.
        waitForMacroListItem("Velocity", true);
        getSelenium().click(getMacroListItemLocator("Velocity"));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();

        // Set the content field.
        setFieldValue("pd-content-input", "$xcontext.user");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("{{velocity}}\n$xcontext.user\n{{/velocity}}");
    }

    /**
     * Tests that the user can't move to the Edit Macro step without selection a macro first.
     */
    @Test
    public void testValidateSelectMacroStep() {
        openSelectMacroDialog();
        // Wait for the list of macros to be filled.
        waitForMacroListItem("Velocity", true);
        // Make sure no macro is selected.
        assertFalse(isMacroListItemSelected());
        // The validation message should be hidden.
        assertFieldErrorIsNotPresent();
        // Try to move to the next step.
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        // Check if the validation message is visible.
        assertFieldErrorIsPresent("Please select a macro from the list below.", MACRO_SELECTOR_LIST);

        // The validation message should be hidden when we change the macro category.
        selectMacroCategory("Navigation");
        // Wait for the list of macros to be updated (Velocity macro shouldn't be present in the updated list).
        waitForMacroListItem("Velocity", false);
        // Make sure no macro is selected.
        assertFalse(isMacroListItemSelected());
        // The validation message should be hidden.
        assertFieldErrorIsNotPresent();
        // Try to move to the next step.
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        // Check if the validation message is visible.
        assertFieldErrorIsPresent("Please select a macro from the list below.", MACRO_SELECTOR_LIST);

        // The validation message should be hidden when we filter the macros.
        filterMacrosContaining("anchor");
        // Wait for the list of macros to be filtered. The ToC macro should be filtered out.
        waitForMacroListItem("Table Of Contents", false);
        // Make sure no macro is selected.
        assertFalse(isMacroListItemSelected());
        // The validation message should be hidden.
        assertFieldErrorIsNotPresent();
        // Try to move to the next step.
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        // Check if the validation message is visible.
        assertFieldErrorIsPresent("Please select a macro from the list below.", MACRO_SELECTOR_LIST);

        // The validation message should be hidden when we cancel the dialog.
        closeDialog();
        openSelectMacroDialog();
        assertFieldErrorIsNotPresent();
        // Make sure no macro is selected.
        assertFalse(isMacroListItemSelected());
        // Try to move to the next step.
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        // Check if the validation message is visible.
        assertFieldErrorIsPresent("Please select a macro from the list below.", MACRO_SELECTOR_LIST);

        // Finally select a macro.
        getSelenium().click(getMacroListItemLocator("Id"));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();

        // The validation message shouldn't be visible (we moved to the next step).
        assertFieldErrorIsNotPresent();

        // Set the name field.
        setFieldValue("pd-name-input", "foo");
        applyMacroChanges();

        // Check the result.
        switchToSource();
        assertSourceText("{{id name=\"foo\"/}}");
    }

    /**
     * Tests if the user can select from the previously inserted macros.
     */
    @Test
    public void testSelectFromPreviouslyInsertedMacros() {
        openSelectMacroDialog();

        // The "Previously Inserted Macros" category should be initially empty.
        selectMacroCategory("Previously Inserted Macros");
        waitForMacroListItemCount(0);

        // Insert a macro to see if it appears under the "Previously Inserted Macros" category.
        selectMacroCategory("All Macros");
        waitForMacroListItem("HTML", true);
        getSelenium().click(getMacroListItemLocator("HTML"));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();
        setFieldValue("pd-content-input", "xwiki");
        applyMacroChanges();

        // Check if the inserted macro is listed under the "Previously Inserted Macros" category.
        openSelectMacroDialog();
        selectMacroCategory("Previously Inserted Macros");
        // Wait for the macro list to be updated.
        waitForMacroListItemCount(1);
        // Velocity macro shouldn't be present among the previously used macros.
        assertFalse(getDriver().hasElementWithoutWaiting(By.xpath(getMacroListItemLocator("Velocity"))));
        assertTrue(getDriver().hasElementWithoutWaiting(By.xpath(getMacroListItemLocator("HTML"))));

        // Close the dialog and check the result.
        closeDialog();
        switchToSource();
        assertSourceText("{{html}}\nxwiki\n{{/html}}");
    }

    /**
     * @see XWIKI-4415: Context document not set when refreshing macros.
     */
    @Test
    public void testRefreshContextSensitiveVelocity() {
        switchToSource();
        setSourceText("{{velocity}}$doc.fullName{{/velocity}}");
        switchToWysiwyg();
        String expected = getRichTextArea().getText();
        refreshMacrosUsingShortcutKey();
        assertEquals(expected, getRichTextArea().getText());
    }

    /**
     * @see XWIKI-4541: Links are removed when a macro is collapsed and the editor looses focus.
     */
    @Test
    public void testLinksArePreservedWhenMacroIsCollapsed() {
        switchToSource();
        setSourceText("before\n\n{{velocity}}abc [[123>>http://www.xwiki.org]] xyz{{/velocity}}\n\nafter");
        switchToWysiwyg();
        // Make sure the rich text area is focused. We need to do this to be sure the blur event has any effect.
        focusRichTextArea();
        // Check if the link is present before collapsing the macro.
        String linkCountExpression = "return document.getElementsByTagName('a').length";
        assertEquals(1L, getRichTextArea().executeScript(linkCountExpression));
        // Select the macro.
        selectMacro(0);
        // Collapse the macro.
        toggleMacroCollapsedState();
        // Blur and focus again the rich text area.
        blurRichTextArea();
        focusRichTextArea();
        // Expand the macro.
        toggleMacroCollapsedState();
        // The link should have been preserved.
        assertEquals(1L, getRichTextArea().executeScript(linkCountExpression));
    }

    /**
     * @see XWIKI-4613: Macros that output STYLE tags inside the HTML body generate wiki syntax garbage.
     */
    @Test
    public void testHTMLMacroWithStyleTag() {
        switchToSource();
        StringBuilder sourceText = new StringBuilder();
        sourceText.append("{{html clean=\"false\"}}\n");
        sourceText.append("<style type=\"text/css\">\n");
        sourceText.append(".test {\n");
        sourceText.append("  color: red;\n");
        sourceText.append("}\n");
        sourceText.append("</style>\n");
        sourceText.append("<div class=\"test\">This is a test.</div>\n");
        sourceText.append("{{/html}}");
        setSourceText(sourceText.toString());
        switchToWysiwyg();
        // Force re-rendering.
        refreshMacrosUsingShortcutKey();
        // Check the result.
        switchToSource();
        assertSourceText(sourceText.toString());
    }

    /**
     * @see XWIKI-4856: Charset errors on macro insertion
     */
    @Test
    public void testMacroWithUnicodeCharacters() {
        // Insert a macro with Unicode characters.
        switchToSource();
        String sourceText = "before {{info}}\u0103\u0219\u021B\u00E2\u00EE\u00E9\u00E8{{/info}} after";
        setSourceText(sourceText);
        switchToWysiwyg();
        // Do a round-trip to the server to re-parse and re-render the content.
        refreshMacros();
        // Check if the content is affected by undo operation.
        typeText("1 2");
        waitForPushButton(TOOLBAR_BUTTON_UNDO_TITLE, true);
        clickUndoButton(2);
        // Check the result.
        switchToSource();
        assertSourceText("1" + sourceText);
    }

    /**
     * @see XWIKI-4946: Default values for the required macro parameters should be send to the server by the WYSIWYG
     */
    @Test
    public void testDefaultValuesForMandatoryParametersAreSent() {
        open(this.getClass().getSimpleName(), getTestMethodName(), "edit", "editor=object");
        if (!isElementPresent("xclass_XWiki.WikiMacroClass")) {
            // Create the macro.
            getSelenium().select("classname", "WikiMacroClass");
            clickEditAddObject();
            setFieldValue("XWiki.WikiMacroClass_0_id", "now");
            setFieldValue("XWiki.WikiMacroClass_0_name", "Now");
            getSelenium().select("XWiki.WikiMacroClass_0_contentType", "No content");
            setFieldValue("XWiki.WikiMacroClass_0_code", "{{velocity}}$datetool.date{{/velocity}}");
            clickEditSaveAndContinue();
            // Create the mandatory parameter.
            getSelenium().select("classname", "WikiMacroParameterClass");
            clickEditAddObject();
            setFieldValue("XWiki.WikiMacroParameterClass_0_name", "format");
            getSelenium().select("XWiki.WikiMacroParameterClass_0_mandatory", "Yes");
            setFieldValue("XWiki.WikiMacroParameterClass_0_defaultValue", "yyyy.MM.dd");
            clickEditSaveAndContinue();
        }
        open(this.getClass().getSimpleName(), getTestMethodName(), "edit", "editor=wysiwyg");
        waitForEditorToLoad();
        // Insert the macro we just created.
        insertMacro("Now");
        applyMacroChanges();
        // Check the result.
        switchToSource();
        assertSourceText("{{now format=\"yyyy.MM.dd\"/}}");
    }

    /**
     * @see XWIKI-5013: HTML code visible when inserting velocity macro displaying a property
     */
    @Test
    public void testInsertVelocityMacroDisplayingAProperty() {
        insertMacro("Velocity");
        setFieldValue("pd-content-input", "$xwiki.getDocument(\"XWiki.Admin\").display(\"comment\")");
        applyMacroChanges();
        // Check the displayed text.
        // Trim the white-space that is coming from the macro selection boundary and which is not visible by the user.
        // There's also some white-space generate by the elements that wrap or follow the text area.
        assertEquals("Admin is the default Wiki Admin.", getRichTextArea().getText().trim());
    }

    /**
     * Tests that a macro can be inserted by clicking the associated tool bar button.
     */
    @Test
    public void testInsertMacroFromToolBar() {
        // The tool bar button for inserting the velocity macro has been added in WysiwygTestSetup.
        clickInsertMacroButton("velocity");
        waitForDialogToLoad();
        setFieldValue("pd-content-input", "$xwiki.version");
        applyMacroChanges();
        // Check if the macro was correctly inserted.
        switchToSource();
        assertSourceText("{{velocity}}\n$xwiki.version\n{{/velocity}}");
    }

    /**
     * @see XWIKI-4461: Cannot have a cursor around macro blocks in Wysiwyg
     * @see XWIKI-3335: Cannot move the caret with the right arrow key after a macro ending the document
     */
    @Test
    public void testTypeAroundMacro() {
        switchToSource();
        setSourceText("{{info}}one{{/info}}{{warning}}two{{/warning}}{{error}}three{{/error}}"
                + "\n\n{{info}}four{{/info}}\n\n{{warning}}five{{/warning}}");
        switchToWysiwyg();

        // Place the caret at the start of the first macro.
        moveCaret(getMacroOutputLocator(0) + ".firstChild.firstChild", 0);
        typeText("1");

        // Place the caret at the end of the first macro.
        moveCaret(getMacroOutputLocator(0) + ".firstChild.firstChild", 3);
        typeText(" ");
        // Check that Space didn't toggle the macro collapsed state.
        // Note: We have to select the rich text area frame because the visibility of an element is evaluated relative
        // to the current window.
        selectRichTextAreaFrame();
        try {
            assertFalse(getSelenium().isVisible(getMacroPlaceHolderLocator(0)));
            assertTrue(getSelenium().isVisible(getMacroOutputLocator(0)));
        } finally {
            selectTopFrame();
        }

        // Place the caret at the start of the third macro.
        moveCaret(getMacroOutputLocator(2) + ".firstChild.firstChild", 0);
        typeText("3");

        // Place the caret at the end of the third macro.
        moveCaret(getMacroOutputLocator(2) + ".firstChild.firstChild", 5);
        typeEnter();
        try {
            waitForDialogToLoad();
            fail("The macro edit box shouldn't be triggered by pressing Enter at the end of the output.");
        } catch (TimeoutException e) {
            // Expected.
        }
        typeText("2");

        // Place the caret at the start of the last macro.
        moveCaret(getMacroOutputLocator(4) + ".firstChild.firstChild", 0);
        typeEnter();
        typeText("4");

        // Place the caret at the end of the last macro.
        moveCaret(getMacroOutputLocator(4) + ".firstChild.firstChild", 4);
        typeEnter();
        typeText("5");

        switchToSource();
        assertSourceText("1{{info}}one{{/info}} {{warning}}two{{/warning}}3{{error}}three{{/error}}\n\n2"
                + "\n\n{{info}}\nfour\n{{/info}}\n\n4\n\n{{warning}}\nfive\n{{/warning}}\n\n5");
    }

    /**
     * @see XWIKI-7743: Wrong editor width when returning from full screen edit after editing/adding a macro
     */
    @Test
    public void testEditorWidthIsRestoredAfterFullScreenEdit() {
        clickEditInFullScreen();
        refreshMacros();
        clickExitFullScreen();
        // The width of the rich text area should be reset after exiting the full screen edit.
        assertEquals("", getSelenium().getEval("window.document.getElementsByTagName('iframe')[0].style.width"));
    }

    /**
     * @param index the index of a macro inside the edited document
     * @return a {@link String} representing a DOM locator for the specified macro
     */
    public String getMacroLocator(int index) {
        return "document.getElementsByClassName('macro')[" + index + "]";
    }

    /**
     * @return the number of macros detected in the edited document
     */
    public long getMacroCount() {
        return (Long) getRichTextArea().executeScript("return document.getElementsByClassName('macro').length");
    }

    /**
     * @return the number of selected macros in the edited document
     */
    public long getSelectedMacroCount() {
        return (Long) getRichTextArea()
                .executeScript("return document.getElementsByClassName('macro-selected').length");
    }

    /**
     * Wait for the specified number of macros to be selected.
     * 
     * @param count the number of selected macros to wait for
     */
    public void waitForSelectedMacroCount(final int count) {
        new Wait() {
            public boolean until() {
                return count == getSelectedMacroCount();
            }
        }.wait("The specified number of selected macros, " + count
                + ", hasn't been reached in a decent amount of time");
    }

    /**
     * The macro place holder is shown when the macro is collapsed. In this state the output of the macro is hidden.
     * 
     * @param index the index of a macro inside the edited document
     * @return a {@link String} representing a DOM locator for the place holder of the specified macro, relative to the
     *         edited document
     */
    public String getMacroPlaceHolderLocator(int index) {
        return "document.getElementsByClassName('macro-placeholder')[" + index + "]";
    }

    /**
     * The output of a macro is shown when the macro is expanded. In this state the macro place holder is hidden.
     * 
     * @param index the index of a macro inside the edited document
     * @return a {@link String} representing a DOM locator for the output of the specified macro, relative to the edited
     *         document
     */
    public String getMacroOutputLocator(int index) {
        return "document.getElementsByClassName('macro-output')[" + index + "]";
    }

    /**
     * Selects the macro with the specified index in the edited document.
     * 
     * @param index the index of the macro to be selected
     */
    public void selectMacro(int index) {
        String locator = getMacroLocator(index);
        selectRichTextAreaFrame();
        try {
            getSelenium().mouseOver(locator);
            getSelenium().mouseMove(locator);
            getSelenium().mouseDown(locator);
            getSelenium().mouseUp(locator);
            getSelenium().click(locator);
            getSelenium().mouseMove(locator);
            getSelenium().mouseOut(locator);
        } finally {
            selectTopFrame();
        }
        // Select the macro container to be sure that the shortcut keys (Enter, Space) work. The click usually places
        // the caret at the start of the macro content when typing is allowed.
        selectNodeContents(locator);
    }

    /**
     * Collapses the currently selected macro or, if none is selected, all the macros using the shortcut key.
     */
    public void collapseMacrosUsingShortcutKey() {
        getRichTextArea().sendKeys(Keys.chord(Keys.CONTROL, Keys.SHIFT, "c"));
    }

    /**
     * Expands the currently selected macro or, if none is selected, all the macros using the shortcut key.
     */
    public void expandMacrosUsingShortcutKey() {
        getRichTextArea().sendKeys(Keys.chord(Keys.CONTROL, Keys.SHIFT, "e"));
    }

    /**
     * Toggles the collapsed state of the currently selected macro.
     */
    public void toggleMacroCollapsedState() {
        getRichTextArea().sendKeys(Keys.SPACE);
    }

    /**
     * Opens the edit macro dialog to edit the specified macro.
     * 
     * @param index the index of the macro to be edited
     */
    public void editMacro(int index) {
        selectMacro(index);
        clickMenu(MENU_MACRO);
        clickMenu(MENU_EDIT);
        waitForDialogToLoad();
    }

    /**
     * Deletes the specified macro by selecting it and then pressing the Delete key.
     * 
     * @param index the index of the macro to be deleted
     */
    public void deleteMacro(int index) {
        selectMacro(index);
        typeDelete();
    }

    /**
     * Opens the insert macro dialog, chooses the specified macro and then opens the edit macro dialog to fill the
     * parameters of the selected macro.
     * 
     * @param macroName the name of the macro to insert
     */
    public void insertMacro(String macroName) {
        openSelectMacroDialog();

        // We have to wait for the specified macro to be displayed on the dialog because the loading indicator is
        // removed just before the list of macros is displayed and the Selenium click command can interfere.
        waitForMacroListItem(macroName, true);
        getSelenium().click(getMacroListItemLocator(macroName));
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Select']");
        waitForDialogToLoad();
    }

    /**
     * Applies the changes from the edit macro dialog.<br/>
     * NOTE: This method can be called after both edit and insert macro actions.
     */
    public void applyMacroChanges() {
        // The label of the finish button is "Apply" when we edit a macro and "Insert Macro" when we insert a macro.
        getSelenium().click("//div[@class = 'xDialogFooter']/button[text() = 'Apply' or text() = 'Insert Macro']");
        waitForEditorToLoad();
    }

    /**
     * Refreshes the macros present in the edited document using the menu.
     */
    public void refreshMacros() {
        clickMenu(MENU_MACRO);
        clickMenu(MENU_REFRESH);
        waitForEditorToLoad();
    }

    /**
     * Refreshes the macros present in the edited document using the shortcut key.
     */
    public void refreshMacrosUsingShortcutKey() {
        getRichTextArea().sendKeys(Keys.chord(Keys.CONTROL, Keys.SHIFT, "r"));
        waitForEditorToLoad();
    }

    /**
     * Selects the specified macro category.
     * <p>
     * NOTE: This method doesn't wait for the category to be loaded!
     * 
     * @param category the name of the macro category to select
     */
    public void selectMacroCategory(String category) {
        getSelenium().select(MACRO_CATEGORY_SELECTOR, category);
    }

    /**
     * @return the selected macro category
     */
    public String getSelectedMacroCategory() {
        return getSelenium().getSelectedLabel(MACRO_CATEGORY_SELECTOR);
    }

    /**
     * Waits for the specified macro to be displayed or hidden on the "Select Macro" dialog.
     * 
     * @param macroName the name of a macro
     * @param present {@code true} to wait for the specified macro to be present, {@code false} to wait for it to be
     *            hidden
     */
    public void waitForMacroListItem(final String macroName, final boolean present) {
        getDriver().waitUntilCondition(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                return present == MacroTest.this.getDriver()
                        .hasElementWithoutWaiting(By.xpath(getMacroListItemLocator(macroName)));
            }
        });
    }

    /**
     * Waits for the specified number of macros to be listed on the "Select Macro" dialog.
     * 
     * @param count the expected number of macros currently displayed on the "Select Macro" dialog
     */
    public void waitForMacroListItemCount(final int count) {
        new Wait() {
            public boolean until() {
                return count == getMacroListItemCount();
            }
        }.wait("The number of listed macros doesn't match.");
    }

    /**
     * @param macroName a macro name
     * @return the selector for the specified macro in the list of macros from the "Select Macro" dialog
     */
    public String getMacroListItemLocator(String macroName) {
        return "//div[contains(@class, 'xListBox')]//div[contains(@class, 'xMacroLabel') and text() = '" + macroName
                + "']";
    }

    /**
     * @return the number of macro list items on the "Select Macro" dialog.
     */
    public int getMacroListItemCount() {
        return getSelenium().getXpathCount("//div[contains(@class, 'xListItem xMacro')]").intValue();
    }

    /**
     * @param filter a text used to filter the macro list items
     * @return the number of macro list items containing the specified text
     */
    public int getMacroListItemCount(String filter) {
        return getSelenium()
                .getXpathCount("//div[contains(@class, 'xListItem xMacro') and contains(., '" + filter + "')]")
                .intValue();
    }

    /**
     * Sets the value of the live filter to the given string.
     * 
     * @param filter the value to set to the live macro filter
     */
    public void filterMacrosContaining(String filter) {
        getSelenium().typeKeys(MACRO_LIVE_FILTER_SELECTOR, filter);
    }

    /**
     * @return the value of the live macro filter
     */
    public String getMacroFilterValue() {
        return getSelenium().getValue(MACRO_LIVE_FILTER_SELECTOR);
    }

    /**
     * Opens the "Select Macro" dialog.
     */
    public void openSelectMacroDialog() {
        clickMenu(MENU_MACRO);
        assertTrue(isMenuEnabled(MENU_INSERT));
        clickMenu(MENU_INSERT);
        waitForDialogToLoad();
    }

    /**
     * @return {@code true} if there is a macro list item selected in the list of macros from the "Select Macro" dialog,
     *         {@code false} otherwise
     */
    public boolean isMacroListItemSelected() {
        return isElementPresent("//div[contains(@class, 'xMacro') and contains(@class, 'xListItem-selected')]");
    }

    /**
     * Clears the macro selection. No macro should be selected after calling this method.
     */
    public void clearMacroSelection() {
        moveCaret("document.body", 0);
        triggerToolbarUpdate();
        waitForSelectedMacroCount(0);
    }

    /**
     * Clicks the tool bar button corresponding to the specified macro.
     * 
     * @param macroId a macro identifier
     */
    public void clickInsertMacroButton(String macroId) {
        pushToolBarButton(String.format("Insert %s macro", macroId));
    }
}