html-editor.py 9.83 KB
Newer Older
cahrens committed
1
# disable missing docstring
2
# pylint: disable=missing-docstring
cahrens committed
3

4 5
from collections import OrderedDict

cahrens committed
6
from lettuce import world, step
7
from nose.tools import assert_in, assert_false, assert_true, assert_equal  # pylint: disable=no-name-in-module
Abdallah committed
8 9 10
from common import type_in_codemirror, get_codemirror_value

CODEMIRROR_SELECTOR_PREFIX = "$('iframe').contents().find"
cahrens committed
11

12

cahrens committed
13
@step('I have created a Blank HTML Page$')
cahrens committed
14
def i_created_blank_html_page(step):
15
    step.given('I am in Studio editing a new unit')
16
    world.create_component_instance(
17 18 19
        step=step,
        category='html',
        component_type='Text'
20 21
    )

cahrens committed
22

23 24
@step('I have created a raw HTML component')
def i_created_raw_html(step):
25
    step.given('I am in Studio editing a new unit')
26 27 28 29 30 31 32
    world.create_component_instance(
        step=step,
        category='html',
        component_type='Raw HTML'
    )


33
@step('I see the HTML component settings$')
cahrens committed
34
def i_see_only_the_html_display_name(step):
35 36 37 38 39 40
    world.verify_all_setting_entries(
        [
            ['Display Name', "Text", False],
            ['Editor', "Visual", False]
        ]
    )
41 42 43


@step('I have created an E-text Written in LaTeX$')
44
def i_created_etext_in_latex(step):
45
    step.given('I am in Studio editing a new unit')
46
    step.given('I have enabled latex compiler')
47
    world.create_component_instance(
48 49 50
        step=step,
        category='html',
        component_type='E-text Written in LaTeX'
51
    )
David Adams committed
52 53


Abdallah committed
54
@step('I edit the page$')
David Adams committed
55 56 57 58
def i_click_on_edit_icon(step):
    world.edit_component()


Abdallah committed
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
@step('I add an image with static link "(.*)" via the Image Plugin Icon$')
def i_click_on_image_plugin_icon(step, path):
    use_plugin(
        '.mce-i-image',
        lambda: world.css_fill('.mce-textbox', path, 0)
    )


@step('the link is shown as "(.*)" in the Image Plugin$')
def check_link_in_image_plugin(step, path):
    use_plugin(
        '.mce-i-image',
        lambda: assert_equal(path, world.css_find('.mce-textbox')[0].value)
    )


@step('I add a link with static link "(.*)" via the Link Plugin Icon$')
def i_click_on_link_plugin_icon(step, path):
    def fill_in_link_fields():
        world.css_fill('.mce-textbox', path, 0)
        world.css_fill('.mce-textbox', 'picture', 1)

    use_plugin('.mce-i-link', fill_in_link_fields)
82 83


Abdallah committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
@step('the link is shown as "(.*)" in the Link Plugin$')
def check_link_in_link_plugin(step, path):
    # Ensure caret position is within the link just created.
    script = """
    var editor = tinyMCE.activeEditor;
    editor.selection.select(editor.dom.select('a')[0]);"""
    world.browser.driver.execute_script(script)
    world.wait_for_ajax_complete()

    use_plugin(
        '.mce-i-link',
        lambda: assert_equal(path, world.css_find('.mce-textbox')[0].value)
    )


@step('type "(.*)" in the code editor and press OK$')
def type_in_codemirror_plugin(step, text):
101 102 103 104
    # Verify that raw code editor is not visible.
    assert_true(world.css_has_class('.CodeMirror', 'is-inactive'))
    # Verify that TinyMCE editor is present
    assert_true(world.is_css_present('.tiny-mce'))
Abdallah committed
105 106 107 108 109 110 111 112 113 114
    use_code_editor(
        lambda: type_in_codemirror(0, text, CODEMIRROR_SELECTOR_PREFIX)
    )


@step('and the code editor displays "(.*)"$')
def verify_code_editor_text(step, text):
    use_code_editor(
        lambda: assert_equal(text, get_codemirror_value(0, CODEMIRROR_SELECTOR_PREFIX))
    )
115 116


Abdallah committed
117 118
@step('I save the page$')
def i_click_on_save(step):
119
    world.save_component()
Abdallah committed
120 121 122 123 124 125 126


@step('the page text contains:')
def check_page_text(step):
    assert_in(step.multiline, world.css_find('.xmodule_HtmlModule').html)


127 128 129 130 131
@step('the Raw Editor contains exactly:')
def check_raw_editor_text(step):
    assert_equal(step.multiline, get_codemirror_value(0))


132
@step('the src link is rewritten to the asset link "(.*)"$')
Abdallah committed
133
def image_static_link_is_rewritten(step, path):
David Adams committed
134 135 136
    # Find the TinyMCE iframe within the main window
    with world.browser.get_iframe('mce_0_ifr') as tinymce:
        image = tinymce.find_by_tag('img').first
137
        assert_in(unicode(world.scenario_dict['COURSE'].id.make_asset_key('asset', path)), image['src'])
138

Abdallah committed
139

140
@step('the href link is rewritten to the asset link "(.*)"$')
Abdallah committed
141 142 143 144
def link_static_link_is_rewritten(step, path):
    # Find the TinyMCE iframe within the main window
    with world.browser.get_iframe('mce_0_ifr') as tinymce:
        link = tinymce.find_by_tag('a').first
145
        assert_in(unicode(world.scenario_dict['COURSE'].id.make_asset_key('asset', path)), link['href'])
Abdallah committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199


@step('the expected toolbar buttons are displayed$')
def check_toolbar_buttons(step):
    dropdowns = world.css_find('.mce-listbox')
    assert_equal(2, len(dropdowns))

    # Format dropdown
    assert_equal('Paragraph', dropdowns[0].text)
    # Font dropdown
    assert_equal('Font Family', dropdowns[1].text)

    buttons = world.css_find('.mce-ico')

    # Note that the code editor icon is not present because we are now showing text instead of an icon.
    # However, other test points user the code editor, so we have already verified its presence.
    expected_buttons = [
        'bold',
        'italic',
        'underline',
        'forecolor',
        # This is our custom "code style" button, which uses an image instead of a class.
        'none',
        'bullist',
        'numlist',
        'outdent',
        'indent',
        'blockquote',
        'link',
        'unlink',
        'image'
    ]

    assert_equal(len(expected_buttons), len(buttons))

    for index, button in enumerate(expected_buttons):
        class_names = buttons[index]._element.get_attribute('class')
        assert_equal("mce-ico mce-i-" + button, class_names)


@step('I set the text to "(.*)" and I select the text$')
def set_text_and_select(step, text):
    script = """
    var editor = tinyMCE.activeEditor;
    editor.setContent(arguments[0]);
    editor.selection.select(editor.dom.select('p')[0]);"""
    world.browser.driver.execute_script(script, str(text))
    world.wait_for_ajax_complete()


@step('I select the code toolbar button$')
def select_code_button(step):
    # This is our custom "code style" button. It uses an image instead of a class.
    world.css_click(".mce-i-none")
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214


@step('type "(.*)" into the Raw Editor$')
def type_in_raw_editor(step, text):
    # Verify that CodeMirror editor is not hidden
    assert_false(world.css_has_class('.CodeMirror', 'is-inactive'))
    # Verify that TinyMCE Editor is not present
    assert_true(world.is_css_not_present('.tiny-mce'))
    type_in_codemirror(0, text)


@step('I edit the component and select the (Raw|Visual) Editor$')
def select_editor(step, editor):
    world.edit_component_and_select_settings()
    world.browser.select('Editor', editor)
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287


@step('I click font selection dropdown')
def click_font_dropdown(step):
    dropdowns = [drop for drop in world.css_find('.mce-listbox') if drop.text == 'Font Family']
    assert_equal(len(dropdowns), 1)
    dropdowns[0].click()


@step('I should see a list of available fonts')
def font_selector_dropdown_is_shown(step):
    font_panel = get_fonts_list_panel(world)
    expected_fonts = list(CUSTOM_FONTS.keys()) + list(TINYMCE_FONTS.keys())
    actual_fonts = [font.strip() for font in font_panel.text.split('\n')]
    assert_equal(actual_fonts, expected_fonts)


@step('"Default" option sets "(.*)" font family')
def default_options_sets_expected_font_family(step, expected_font_family):
    fonts = get_available_fonts(get_fonts_list_panel(world))
    assert_equal(fonts.get("Default", None), expected_font_family)


@step('all standard tinyMCE fonts should be available')
def check_standard_tinyMCE_fonts(step):
    fonts = get_available_fonts(get_fonts_list_panel(world))
    for label, expected_font in TINYMCE_FONTS.items():
        assert_equal(fonts.get(label, None), expected_font)

TINYMCE_FONTS = OrderedDict([
    ("Andale Mono", "'andale mono', times"),
    ("Arial", "arial, helvetica, sans-serif"),
    ("Arial Black", "'arial black', 'avant garde'"),
    ("Book Antiqua", "'book antiqua', palatino"),
    ("Comic Sans MS", "'comic sans ms', sans-serif"),
    ("Courier New", "'courier new', courier"),
    ("Georgia", "georgia, palatino"),
    ("Helvetica", "helvetica"),
    ("Impact", "impact, chicago"),
    ("Symbol", "symbol"),
    ("Tahoma", "tahoma, arial, helvetica, sans-serif"),
    ("Terminal", "terminal, monaco"),
    ("Times New Roman", "'times new roman', times"),
    ("Trebuchet MS", "'trebuchet ms', geneva"),
    ("Verdana", "verdana, geneva"),
    # tinyMCE does not set font-family on dropdown span for these two fonts
    ("Webdings", ""),  # webdings
    ("Wingdings", ""),  # wingdings, 'zapf dingbats'
])

CUSTOM_FONTS = OrderedDict([
    ('Default', "'Open Sans', Verdana, Arial, Helvetica, sans-serif"),
])


def use_plugin(button_class, action):
    # Click on plugin button
    world.css_click(button_class)
    perform_action_in_plugin(action)


def use_code_editor(action):
    # Click on plugin button
    buttons = world.css_find('div.mce-widget>button')

    code_editor = [button for button in buttons if button.text == 'HTML']
    assert_equal(1, len(code_editor))
    code_editor[0].click()

    perform_action_in_plugin(action)


def perform_action_in_plugin(action):
David Baumgold committed
288
    # Wait for the plugin window to open.
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
    world.wait_for_visible('.mce-window')

    # Trigger the action
    action()

    # Click OK
    world.css_click('.mce-primary')


def get_fonts_list_panel(world):
    menus = world.css_find('.mce-menu')
    return menus[0]


def get_available_fonts(font_panel):
    font_spans = font_panel.find_by_css('.mce-text')
    return {font_span.text: get_font_family(font_span) for font_span in font_spans}


def get_font_family(font_span):
    # get_attribute('style').replace('font-family: ', '').replace(';', '') is equivalent to
    # value_of_css_property('font-family'). However, for reason unknown value_of_css_property fails tests in CI
    # while works as expected in local development environment
    return font_span._element.get_attribute('style').replace('font-family: ', '').replace(';', '')