def test_get_voice_for_field(qtbot):
    # pytest test_languagetools.py -k test_get_voice_for_field

    # valid voice
    # ===========

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_audio')

    dntf = config_gen.get_dntf_chinese()
    voice = mock_language_tools.get_voice_for_field(dntf)
    assert voice != None
    assert voice['voice_key'] == config_gen.chinese_voice_key

    # no language mapping
    # ===================
    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'no_language_mapping')

    dntf = config_gen.get_dntf_chinese()

    testcase_instance = unittest.TestCase()
    testcase_instance.assertRaises(errors.FieldLanguageMappingError,
                                   mock_language_tools.get_voice_for_field,
                                   dntf)
def test_add_translation_transliteration_no_language_mapping(qtbot):
    # pytest test_dialogs.py -rPP -k test_add_translation_transliteration_no_language_mapping

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'no_language_mapping')

    deck_note_type = mock_language_tools.deck_utils.build_deck_note_type(
        config_gen.deck_id, config_gen.model_id)

    note_id_list = [42, 43]

    testcase_instance = unittest.TestCase()

    # translation
    testcase_instance.assertRaises(
        errors.LanguageMappingError,
        dialog_batchtransformation.BatchConversionDialog, mock_language_tools,
        deck_note_type, note_id_list, constants.TransformationType.Translation)

    # transliteration
    testcase_instance.assertRaises(
        errors.LanguageMappingError,
        dialog_batchtransformation.BatchConversionDialog, mock_language_tools,
        deck_note_type, note_id_list,
        constants.TransformationType.Transliteration)
def test_editor_audio(qtbot):
    # pytest test_editor.py -rPP -k test_editor_audio

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_audio')

    mock_language_tools.cloud_language_tools.transliteration_map = {
        '老人': 'laoren'
    }

    editor = config_gen.get_mock_editor_with_note(config_gen.note_id_1)
    editor_manager = editor_processing.EditorManager(mock_language_tools)

    field_index = 0
    note_id = config_gen.note_id_1
    field_value = '老人'  # short version
    bridge_str = f'key:{field_index}:{note_id}:{field_value}'
    editor_manager.process_field_update(editor, bridge_str)

    # verify outputs

    # sound should have been played
    assert mock_language_tools.anki_utils.played_sound['text'] == '老人'

    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 1
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[0][
        'field_name'] == 'Sound'  # sound
    assert '.mp3' in mock_language_tools.anki_utils.editor_set_field_value_calls[
        0]['text']
def test_process_choosetranslation(qtbot):
    # pytest test_editor.py -rPP -k test_process_choosetranslation

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_translation')

    mock_language_tools.cloud_language_tools.translate_all_result = {
        '老人家': {
            'serviceA': 'first translation A',
            'serviceB': 'second translation B'
        }
    }

    # when the choose translation dialog comes up, we should pick serviceB
    mock_language_tools.anki_utils.display_dialog_behavior = 'choose_serviceB'

    editor = config_gen.get_mock_editor_with_note(config_gen.note_id_1)
    editor_manager = editor_processing.EditorManager(mock_language_tools)
    editor_manager.run_choose_translation(editor, 'English')

    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 1
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[0][
        'field_name'] == 'English'
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[0][
        'text'] == 'second translation B'
def test_voice_selection_no_voices(qtbot):
    # pytest test_dialogs.py -rPP -k test_voice_selection_no_voices

    # make sure the dialog comes up
    # -----------------------------

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'get_config_language_no_voices')

    voice_list = mock_language_tools.cloud_language_tools.get_tts_voice_list(
        'yoyo')
    voice_selection_dialog = dialog_voiceselection.prepare_voice_selection_dialog(
        mock_language_tools, voice_list)

    languages_combobox = voice_selection_dialog.findChild(
        aqt.qt.QComboBox, 'languages_combobox')
    assert languages_combobox.count() == 3
    assert languages_combobox.itemText(2) == 'Malagasy'

    qtbot.keyClicks(languages_combobox, 'Malagasy')

    # try to play audio
    play_sample_button = voice_selection_dialog.findChild(
        aqt.qt.QPushButton, f'play_sample_0')
    qtbot.mouseClick(play_sample_button, aqt.qt.Qt.LeftButton)

    # should have been a critical messages
    assert mock_language_tools.anki_utils.critical_message_received == 'No voice available for Malagasy'
def test_dialog_breakdown_chinese(qtbot):
    # pytest test_dialogs.py -rPP -k test_dialog_breakdown_chinese

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    source_text = '老人家'

    # the response which should come
    mock_language_tools.cloud_language_tools.breakdown_map = {
        '老人家': [
            {
                'token': '老',
                'lemma': '老',
                'translation': 'old',
                'transliteration': 'lao'
            },
            {
                'token': '人家',
                'lemma': '人家',
                'translation': 'people',
                'transliteration': 'renjia'
            },
        ]
    }

    deck_note_type = deck_utils.DeckNoteType(config_gen.deck_id,
                                             config_gen.deck_name,
                                             config_gen.model_id,
                                             config_gen.model_name)
    dialog = dialog_breakdown.prepare_dialog(mock_language_tools, source_text,
                                             'zh_cn', None, deck_note_type)

    # wanted languages should be populated
    assert_combobox_items_equal(dialog.target_language_dropdown,
                                ['English', 'Chinese'])
    assert dialog.target_language_dropdown.currentText() == 'Chinese'

    # there should be 1 translation option (Azure)
    assert_combobox_items_equal(dialog.translation_dropdown, ['Azure'])

    # there should be 2 transliteration options
    assert_combobox_items_equal(dialog.transliteration_dropdown,
                                ['pinyin1', 'pinyin2'])

    # there should be 2 tokenization options
    assert_combobox_items_equal(dialog.tokenization_dropdown, [
        'Chinese (Simplified) (Characters) Spacy',
        'Chinese (Simplified) (Jieba (words)) Spacy'
    ])

    # run breakdown
    qtbot.mouseClick(dialog.load_button, aqt.qt.Qt.LeftButton)

    # check that result label has been populated
    result_text = dialog.breakdown_result.text()
    result_lines = result_text.split('<br/>')
    assert len(result_lines) == 2
def test_add_audio(qtbot):
    # pytest test_dialogs.py -rPP -k test_add_audio

    config_gen = testing_utils.TestConfigGenerator()

    # test 1 - everything setup but no prior setting
    # ----------------------------------------------
    mock_language_tools = config_gen.build_languagetools_instance('default')

    deck_note_type = mock_language_tools.deck_utils.build_deck_note_type(
        config_gen.deck_id, config_gen.model_id)

    note_id_list = [42, 43]
    add_audio_dialog = dialogs.AddAudioDialog(mock_language_tools,
                                              deck_note_type, note_id_list)
    add_audio_dialog.setupUi()

    # do some checks on the from field combo box
    assert_combobox_items_equal(add_audio_dialog.from_field_combobox,
                                ['Chinese', 'English'])
    assert_combobox_items_equal(add_audio_dialog.to_field_combobox,
                                config_gen.all_fields)
    assert add_audio_dialog.voice_label.text(
    ) == '<b>' + config_gen.chinese_voice_description + '</b>'

    # test 2 - some defaults already exist
    # ------------------------------------
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_audio')

    add_audio_dialog = dialogs.AddAudioDialog(mock_language_tools,
                                              deck_note_type, note_id_list)
    add_audio_dialog.setupUi()
    assert_combobox_items_equal(add_audio_dialog.from_field_combobox,
                                ['Chinese', 'English'])
    assert_combobox_items_equal(add_audio_dialog.to_field_combobox,
                                config_gen.all_fields)

    # verify that selected fields match expectation
    assert add_audio_dialog.from_field_combobox.currentText(
    ) == config_gen.field_chinese
    assert add_audio_dialog.to_field_combobox.currentText(
    ) == config_gen.field_sound

    # test 3 - no language mapping done for any field
    # -----------------------------------------------
    mock_language_tools = config_gen.build_languagetools_instance(
        'no_language_mapping')

    # add_audio_dialog = dialogs.AddAudioDialog(mock_language_tools, deck_note_type, note_id_list)
    testcase_instance = unittest.TestCase()
    testcase_instance.assertRaises(errors.LanguageMappingError,
                                   dialogs.AddAudioDialog, mock_language_tools,
                                   deck_note_type, note_id_list)
def test_choose_translation(qtbot):
    # pytest test_dialogs.py -rPP -k test_choose_translation

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    original_text = 'translate this'
    from_language = 'zh_cn'
    to_language = 'en'

    all_translations = {
        'serviceA': 'first translation',
        'serviceB': 'second translation'
    }

    dialog = dialog_choosetranslation.prepare_dialog(mock_language_tools,
                                                     original_text,
                                                     from_language,
                                                     to_language,
                                                     all_translations)

    assert dialog.original_text_label.text() == original_text
    assert dialog.from_language_label.text() == 'Chinese'
    assert dialog.to_language_label.text() == 'English'

    # first, apply button should be disabled
    assert dialog.apply_button.isEnabled() == False

    # check that services and translations are present
    # services
    service_1 = dialog.findChild(aqt.qt.QLabel, 'service_label_0')
    assert service_1.text() == '<b>serviceA</b>'
    service_2 = dialog.findChild(aqt.qt.QLabel, 'service_label_1')
    assert service_2.text() == '<b>serviceB</b>'
    # translations
    translation_1 = dialog.findChild(aqt.qt.QLabel, 'translation_label_0')
    assert translation_1.text() == 'first translation'
    translation_2 = dialog.findChild(aqt.qt.QLabel, 'translation_label_1')
    assert translation_2.text() == 'second translation'

    # pick second translation
    radio_button = dialog.findChild(aqt.qt.QRadioButton, 'radio_button_1')
    radio_button.click()

    # apply button should be enabled
    assert dialog.apply_button.isEnabled() == True
    qtbot.mouseClick(dialog.apply_button, aqt.qt.Qt.LeftButton)

    # verify translation retained
    assert dialog.selected_translation == 'second translation'
def test_get_transliteration(qtbot):
    # pytest test_languagetools.py -k test_get_transliteration

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'text_replacement')

    source_text = 'unter etw'
    mock_language_tools.cloud_language_tools.transliteration_map = {
        'unter etwas': 'ˈʊntɐ ˈɛtvas'
    }

    transliterated_text = mock_language_tools.get_transliteration(
        source_text, {'transliteration_key': 'de to en'})
    assert transliterated_text == 'ˈʊntɐ ˈɛtvas'
def test_process_choosetranslation_empty(qtbot):
    # pytest test_editor.py -s -rPP -k test_process_choosetranslation_empty

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_translation')

    editor = config_gen.get_mock_editor_with_note(config_gen.note_id_3)
    editor_manager = editor_processing.EditorManager(mock_language_tools)
    editor_manager.run_choose_translation(editor, 'English')

    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 0

    assert mock_language_tools.anki_utils.last_action == 'choosing translation'
    assert isinstance(mock_language_tools.anki_utils.last_exception,
                      errors.LanguageToolsValidationFieldEmpty)
def test_get_tts_audio(qtbot):
    # pytest test_languagetools.py -k test_get_tts_audio

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'text_replacement')

    filename = mock_language_tools.get_tts_audio('unter etw', 'Azure', 'de_de',
                                                 {'name': 'voice1'}, {})
    file_contents = open(filename, 'r').read()
    data = json.loads(file_contents)

    assert data['text'] == 'unter etwas'  # after text replacement
    assert data['service'] == 'Azure'
    assert data['language_code'] == 'de_de'
    assert data['voice_key'] == {'name': 'voice1'}
    assert data['options'] == {}
def test_process_choosetranslation_request_error(qtbot):
    # pytest test_editor.py -s -rPP -k test_process_choosetranslation_request_error

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_translation')

    mock_language_tools.cloud_language_tools.translation_unhandled_exception_map = {
        '老人家': 'unhandled exception translate all'
    }

    editor = config_gen.get_mock_editor_with_note(config_gen.note_id_1)
    editor_manager = editor_processing.EditorManager(mock_language_tools)
    editor_manager.run_choose_translation(editor, 'English')

    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 0

    assert mock_language_tools.anki_utils.last_action == 'choosing translation'
    assert isinstance(mock_language_tools.anki_utils.last_exception, Exception)
    assert str(mock_language_tools.anki_utils.last_exception
               ) == 'unhandled exception translate all'
def test_dialog_textprocessing_simple(qtbot):
    # pytest test_dialogs.py -rPP -k test_dialog_textprocessing_simple

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    dialog = dialog_textprocessing.prepare_text_processing_dialog(
        mock_language_tools)

    # check table model
    # =================
    # should have 0 rows
    assert dialog.textReplacementTableModel.rowCount(None) == 0

    # check processing preview
    qtbot.keyClicks(dialog.sample_text_input, 'abdc1234 [9]+')
    assert dialog.sample_text_transformed_label.text(
    ) == '<b>abdc1234 [9]+</b>'

    # add a text transformation rule
    qtbot.mouseClick(dialog.add_replace_simple_button, aqt.qt.Qt.LeftButton)
    # enter pattern and replacement
    row = 0
    index_pattern = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_PATTERN)
    dialog.textReplacementTableModel.setData(
        index_pattern, ' [9]+',
        aqt.qt.Qt.EditRole)  # should not get interpreted as regexp
    index_replacement = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_REPLACEMENT)
    dialog.textReplacementTableModel.setData(index_replacement, 'rep',
                                             aqt.qt.Qt.EditRole)

    # dialog.exec_()

    # verify preview
    assert dialog.sample_text_transformed_label.text() == '<b>abdc1234rep</b>'
def test_batch_transformation_error_handling(qtbot):
    # pytest test_dialogs.py -rPP -k test_batch_transformation_error_handling

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    deck_note_type = deck_utils.DeckNoteType(config_gen.deck_id,
                                             config_gen.deck_name,
                                             config_gen.model_id,
                                             config_gen.model_name)
    note_id_list = config_gen.get_note_id_list()
    transformation_type = constants.TransformationType.Translation

    dialog = dialog_batchtransformation.prepare_batch_transformation_dialogue(
        mock_language_tools, deck_note_type, note_id_list, transformation_type)

    # assertions
    # ==========
    assert_combobox_items_equal(dialog.from_combobox, ['Chinese', 'English'])
    assert_combobox_items_equal(dialog.to_combobox, ['Chinese', 'English'])
    assert_combobox_items_equal(dialog.service_combobox, ['Azure'])

    # set from to Chinese
    qtbot.keyClicks(dialog.from_combobox, 'Chinese')
    qtbot.keyClicks(dialog.to_combobox, 'English')

    # load translations button should be enabled
    assert dialog.load_translations_button.isEnabled() == True
    # apply to notes should be disabled
    assert dialog.applyButton.isEnabled() == False
    # cancel button should be enabled
    assert dialog.cancelButton.isEnabled() == True

    # check table model
    # =================

    # headers
    assert dialog.noteTableModel.headerData(
        0, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Chinese'
    assert dialog.noteTableModel.headerData(
        1, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'English'
    # data - input
    column = 0
    index = dialog.noteTableModel.createIndex(0, column)
    assert dialog.noteTableModel.data(
        index, aqt.qt.Qt.ItemDataRole.DisplayRole) == '老人家'
    index = dialog.noteTableModel.createIndex(1, column)  # second row
    assert dialog.noteTableModel.data(
        index, aqt.qt.Qt.ItemDataRole.DisplayRole) == '你好'
    # data - output
    column = 1
    index = dialog.noteTableModel.createIndex(0, column)
    assert dialog.noteTableModel.data(
        index, aqt.qt.Qt.ItemDataRole.DisplayRole) == None
    index = dialog.noteTableModel.createIndex(1, column)  # second row
    assert dialog.noteTableModel.data(
        index, aqt.qt.Qt.ItemDataRole.DisplayRole) == None

    # load translations
    # =================
    mock_language_tools.cloud_language_tools.translation_error_map = {
        '老人家': 'translation error 42'
    }
    mock_language_tools.cloud_language_tools.translation_map = {
        '你好': 'translation 2'
    }

    qtbot.mouseClick(dialog.load_translations_button, aqt.qt.Qt.LeftButton)

    # ensure translations are displayed on the table
    # data - output
    column = 1
    index = dialog.noteTableModel.createIndex(0, column)
    assert dialog.noteTableModel.data(
        index, aqt.qt.Qt.ItemDataRole.DisplayRole
    ) == None  # should be empty, there was an error
    index = dialog.noteTableModel.createIndex(1, column)  # second row
    assert dialog.noteTableModel.data(
        index, aqt.qt.Qt.ItemDataRole.DisplayRole) == 'translation 2'

    # dialog.exec_()

    # # apply button should be enabled now
    assert dialog.applyButton.isEnabled() == True

    # apply to notes
    # ==============
    qtbot.mouseClick(dialog.applyButton, aqt.qt.Qt.LeftButton)

    # verify error message
    assert 'Could not load translation: translation error 42 (1 times)' in mock_language_tools.anki_utils.critical_message_received

    # # verify effect on notes
    note_1 = config_gen.notes_by_id[config_gen.note_id_1]
    assert note_1.set_values == {}
    assert note_1.flush_called == False
    note_2 = config_gen.notes_by_id[config_gen.note_id_2]
    assert note_2.set_values == {'English': 'translation 2'}
    assert note_2.flush_called == True
def test_dialog_textprocessing(qtbot):
    # pytest test_dialogs.py -rPP -k test_dialog_textprocessing

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    dialog = dialog_textprocessing.prepare_text_processing_dialog(
        mock_language_tools)

    # check table model
    # =================
    # headers
    assert dialog.textReplacementTableModel.headerData(
        0, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Type'
    assert dialog.textReplacementTableModel.headerData(
        1, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Pattern'
    assert dialog.textReplacementTableModel.headerData(
        2, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Replacement'
    assert dialog.textReplacementTableModel.headerData(
        3, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Translation'
    assert dialog.textReplacementTableModel.headerData(
        4, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Transliteration'
    assert dialog.textReplacementTableModel.headerData(
        5, aqt.qt.Qt.Orientation.Horizontal,
        aqt.qt.Qt.ItemDataRole.DisplayRole) == 'Audio'
    # should have 0 rows
    assert dialog.textReplacementTableModel.rowCount(None) == 0

    # check processing preview
    qtbot.keyClicks(dialog.sample_text_input, 'abdc1234')
    assert dialog.sample_text_transformed_label.text() == '<b>abdc1234</b>'

    # add a text transformation rule
    qtbot.mouseClick(dialog.add_replace_regex_button, aqt.qt.Qt.LeftButton)
    # enter pattern and replacement
    row = 0
    index_pattern = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_PATTERN)
    dialog.textReplacementTableModel.setData(index_pattern, '1234',
                                             aqt.qt.Qt.EditRole)
    index_replacement = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_REPLACEMENT)
    dialog.textReplacementTableModel.setData(index_replacement, '5678',
                                             aqt.qt.Qt.EditRole)

    # verify preview
    assert dialog.sample_text_transformed_label.text() == '<b>abdc5678</b>'

    # add another transformation rule
    qtbot.mouseClick(dialog.add_replace_regex_button, aqt.qt.Qt.LeftButton)
    # enter pattern and replacement
    row = 1
    index_pattern = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_PATTERN)
    dialog.textReplacementTableModel.setData(index_pattern, ' / ',
                                             aqt.qt.Qt.EditRole)
    index_replacement = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_REPLACEMENT)
    dialog.textReplacementTableModel.setData(index_replacement, ' ',
                                             aqt.qt.Qt.EditRole)

    # check processing preview
    dialog.sample_text_input.clear()
    qtbot.keyClicks(dialog.sample_text_input, 'word1 / word2')
    assert dialog.sample_text_transformed_label.text() == '<b>word1 word2</b>'

    # go back to previous preview string
    dialog.sample_text_input.clear()
    qtbot.keyClicks(dialog.sample_text_input, 'abdc1234')
    assert dialog.sample_text_transformed_label.text() == '<b>abdc5678</b>'

    # remove the first rule
    dialog.table_view.selectRow(0)
    index_first_row = dialog.textReplacementTableModel.createIndex(
        0, dialog_textprocessing.COL_INDEX_PATTERN)
    dialog.table_view.selectionModel().select(
        index_first_row, aqt.qt.QItemSelectionModel.Select)
    qtbot.mouseClick(dialog.remove_replace_button, aqt.qt.Qt.LeftButton)

    # there should only be one row left
    assert dialog.textReplacementTableModel.rowCount(None) == 1

    # check the rule at row 0
    row = 0
    index_pattern = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_PATTERN)
    assert dialog.textReplacementTableModel.data(
        index_pattern, aqt.qt.Qt.ItemDataRole.DisplayRole) == '\" / \"'
    index_replacement = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_REPLACEMENT)
    assert dialog.textReplacementTableModel.data(
        index_replacement, aqt.qt.Qt.ItemDataRole.DisplayRole) == '\" \"'

    # check the preview
    assert dialog.sample_text_transformed_label.text() == '<b>abdc1234</b>'

    # try a regexp rule
    qtbot.mouseClick(dialog.add_replace_regex_button, aqt.qt.Qt.LeftButton)
    row = 1
    index_pattern = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_PATTERN)
    dialog.textReplacementTableModel.setData(index_pattern, '[0-9]+',
                                             aqt.qt.Qt.EditRole)
    index_replacement = dialog.textReplacementTableModel.createIndex(
        row, dialog_textprocessing.COL_INDEX_REPLACEMENT)
    dialog.textReplacementTableModel.setData(index_replacement, 'number',
                                             aqt.qt.Qt.EditRole)
    # this transformation will only apply to transliteration
    index_translation = dialog.textReplacementTableModel.createIndex(row, 3)
    dialog.textReplacementTableModel.setData(index_translation,
                                             aqt.qt.Qt.Unchecked,
                                             aqt.qt.Qt.CheckStateRole)
    index_transliteration = dialog.textReplacementTableModel.createIndex(
        row, 4)
    dialog.textReplacementTableModel.setData(index_transliteration,
                                             aqt.qt.Qt.Checked,
                                             aqt.qt.Qt.CheckStateRole)
    index_audio = dialog.textReplacementTableModel.createIndex(row, 5)
    dialog.textReplacementTableModel.setData(index_audio, aqt.qt.Qt.Unchecked,
                                             aqt.qt.Qt.CheckStateRole)

    # check the preview in different transformation types
    dialog.sample_transformation_type_combo_box.setCurrentText(
        'Transliteration')
    assert dialog.sample_text_transformed_label.text() == '<b>abdcnumber</b>'

    dialog.sample_transformation_type_combo_box.setCurrentText('Translation')
    assert dialog.sample_text_transformed_label.text() == '<b>abdc1234</b>'

    dialog.sample_transformation_type_combo_box.setCurrentText('Audio')
    assert dialog.sample_text_transformed_label.text() == '<b>abdc1234</b>'

    # now, click OK to the dialog
    qtbot.mouseClick(dialog.applyButton, aqt.qt.Qt.LeftButton)

    # ensure that config has been written
    actual_written_config = mock_language_tools.anki_utils.written_config
    actual_written_config_text_processing = actual_written_config[
        constants.CONFIG_TEXT_PROCESSING]
    expected_written_config_text_processing = {
        'replacements': [
            {
                'Audio': True,
                'Transliteration': True,
                'Translation': True,
                'pattern': ' / ',
                'replace': ' ',
                'replace_type': 'regex'
            },
            {
                'Audio': False,
                'Transliteration': True,
                'Translation': False,
                'pattern': '[0-9]+',
                'replace': 'number',
                'replace_type': 'regex'
            },
        ]
    }

    assert actual_written_config_text_processing['replacements'][
        0] == expected_written_config_text_processing['replacements'][0]
    assert actual_written_config_text_processing['replacements'][
        1] == expected_written_config_text_processing['replacements'][1]

    # try to generate some translations
    source_text = 'word1 / word2'
    mock_language_tools.cloud_language_tools.translation_map = {
        'word1 word2': 'word3 word4'
    }
    translated_text = mock_language_tools.get_translation(
        source_text, {'translation_key': 'de to en'})
    assert translated_text == 'word3 word4'

    # try to generate transliteration
    source_text = 'word1 / word2'
    mock_language_tools.cloud_language_tools.transliteration_map = {
        'wordnumber wordnumber': 'correct correct'
    }
    translated_text = mock_language_tools.get_transliteration(
        source_text, {'translation_key': 'de to en'})
    assert translated_text == 'correct correct'
def test_api_key(qtbot):
    # pytest test_dialogs.py -rPP -k test_api_key

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    # API key already populated
    # =========================

    dialog = dialog_apikey.prepare_api_key_dialog(mock_language_tools)
    # api key will be verified immediately

    assert mock_language_tools.cloud_language_tools.verify_api_key_called == True
    assert mock_language_tools.cloud_language_tools.verify_api_key_input == 'yoyo'

    assert dialog.applyButton.isEnabled() == True
    qtbot.mouseClick(dialog.applyButton, aqt.qt.Qt.LeftButton)

    # verify that API key in config is correct
    assert mock_language_tools.anki_utils.written_config['api_key'] == 'yoyo'

    # API key empty
    # =============
    mock_language_tools = config_gen.build_languagetools_instance('noapikey')
    assert mock_language_tools.api_key_checked == False
    dialog = dialog_apikey.prepare_api_key_dialog(mock_language_tools)

    assert mock_language_tools.cloud_language_tools.verify_api_key_called == False
    assert dialog.applyButton.isEnabled() == False

    # type in a fake API key
    mock_language_tools.cloud_language_tools.verify_api_key_is_valid = False
    qtbot.keyClicks(dialog.api_text_input, 'dummykey1')

    assert mock_language_tools.cloud_language_tools.verify_api_key_called == True
    assert mock_language_tools.cloud_language_tools.verify_api_key_input == 'dummykey1'

    assert dialog.applyButton.isEnabled() == False

    assert dialog.account_info_label.text() == ''

    # type in a valid API key
    mock_language_tools.cloud_language_tools.verify_api_key_is_valid = True
    dialog.api_text_input.clear()
    qtbot.keyClicks(dialog.api_text_input, 'validkey1')

    assert mock_language_tools.cloud_language_tools.verify_api_key_called == True
    assert mock_language_tools.cloud_language_tools.verify_api_key_input == 'validkey1'

    assert mock_language_tools.cloud_language_tools.account_info_called == True
    assert mock_language_tools.cloud_language_tools.account_info_api_key == 'validkey1'

    assert dialog.account_info_label.text(
    ) == """<b>type</b>: 250 chars<br/><b>email</b>: [email protected]"""

    assert dialog.applyButton.isEnabled() == True

    # click OK
    qtbot.mouseClick(dialog.applyButton, aqt.qt.Qt.LeftButton)

    # verify that API key in config is correct
    assert mock_language_tools.anki_utils.written_config[
        'api_key'] == 'validkey1'
    assert mock_language_tools.api_key_checked == True
def test_editor_translation(qtbot):
    # pytest test_editor.py -rPP -k test_editor_translation

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_translation')

    mock_language_tools.cloud_language_tools.translation_map = {
        '老人': 'old people (short)',
        '老人家': 'old people (long)',
        '电扇': 'electric fan'
    }

    editor = config_gen.get_mock_editor_with_note(config_gen.note_id_1)
    editor_manager = editor_processing.EditorManager(mock_language_tools)

    # regular example
    # ---------------

    field_index = 0
    note_id = config_gen.note_id_1
    field_value = '老人'  # short version
    bridge_str = f'key:{field_index}:{note_id}:{field_value}'
    editor_manager.process_field_update(editor, bridge_str)

    # verify outputs
    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 1
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[0][
        'field_name'] == 'English'
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[0][
        'text'] == 'old people (short)'

    # empty input
    # -----------
    field_value = ''  # empty
    bridge_str = f'key:{field_index}:{note_id}:{field_value}'
    editor_manager.process_field_update(editor, bridge_str)

    # verify outputs
    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 2
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[1][
        'field_name'] == 'English'
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[1][
        'text'] == ''

    # empty input (html tag)
    # ----------------------
    field_value = '<br/>'  # empty
    bridge_str = f'key:{field_index}:{note_id}:{field_value}'
    editor_manager.process_field_update(editor, bridge_str)

    # verify outputs
    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 3
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[2][
        'field_name'] == 'English'
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[2][
        'text'] == ''

    # disable live updates
    # --------------------

    bridge_str = f'languagetools:liveupdates:false'
    editor_manager.process_command(editor, bridge_str)

    # now send another field update
    field_value = 'yoyoyyo'  # short version
    bridge_str = f'key:{field_index}:{note_id}:{field_value}'
    editor_manager.process_field_update(editor, bridge_str)

    # nothing should have happened
    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 3

    # re-enable live updates
    bridge_str = f'languagetools:liveupdates:true'
    editor_manager.process_command(editor, bridge_str)

    # send field update
    field_value = '老人'  # short version
    bridge_str = f'key:{field_index}:{note_id}:{field_value}'
    editor_manager.process_field_update(editor, bridge_str)

    # verify outputs
    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 4
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[3][
        'field_name'] == 'English'
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[3][
        'text'] == 'old people (short)'

    # force update
    # ------------

    # disable updates again
    bridge_str = f'languagetools:liveupdates:false'
    editor_manager.process_command(editor, bridge_str)

    # force an update
    bridge_str = f'languagetools:{constants.COMMAND_FULLUPDATE}'
    editor_manager.process_command(editor, bridge_str)

    # verify outputs
    assert len(
        mock_language_tools.anki_utils.editor_set_field_value_calls) == 5
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[4][
        'field_name'] == 'English'
    assert mock_language_tools.anki_utils.editor_set_field_value_calls[4][
        'text'] == 'old people (long)'

    # change typing delay
    # -------------------

    bridge_str = f'languagetools:typingdelay:1750'
    editor_manager.process_command(editor, bridge_str)

    assert mock_language_tools.anki_utils.written_config[
        constants.CONFIG_LIVE_UPDATE_DELAY] == 1750
    assert editor_manager.field_change_timer.delay_ms == 1750
def test_dialog_runrules(qtbot):
    # pytest test_dialogs.py -rPP -k test_dialog_runrules

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'batch_audio_translation_transliteration')

    deck_note_type = deck_utils.DeckNoteType(config_gen.deck_id,
                                             config_gen.deck_name,
                                             config_gen.model_id,
                                             config_gen.model_name)
    note_id_list = config_gen.get_note_id_list()

    # setup maps for translation / transliteration
    mock_language_tools.cloud_language_tools.translation_map = {
        '老人家': 'translation 1',
        '你好': 'translation 2'
    }
    mock_language_tools.cloud_language_tools.transliteration_map = {
        '老人家': 'transliteration 1',
        '你好': 'transliteration 2'
    }

    dialog = dialog_notesettings.RunRulesDialog(mock_language_tools,
                                                deck_note_type, note_id_list)
    dialog.setupUi()
    # dialog.exec_()

    # click apply button
    qtbot.mouseClick(dialog.applyButton, aqt.qt.Qt.LeftButton)

    # ideally we would check things like buttons disabled, progress bar, etc
    # but for now, just check the effect on notes

    # verify effect on notes
    note_1 = config_gen.notes_by_id[config_gen.note_id_1]
    note_1_set_values = note_1.set_values
    assert 'sound:' in note_1_set_values['Sound']
    del note_1_set_values['Sound']  # look at sound separately
    assert note_1_set_values == {
        'English': 'translation 1',
        'Pinyin': 'transliteration 1'
    }
    assert note_1.flush_called == True
    note_2 = config_gen.notes_by_id[config_gen.note_id_2]
    note_2_set_values = note_2.set_values
    assert 'sound:' in note_2_set_values['Sound']
    del note_2_set_values['Sound']
    assert note_2_set_values == {
        'English': 'translation 2',
        'Pinyin': 'transliteration 2'
    }
    assert note_2.flush_called == True

    note_3 = config_gen.notes_by_id[config_gen.note_id_3]
    assert note_3.set_values == {}  # no values set
    assert note_3.flush_called == False

    # check action stats
    expected_action_stats = {
        'adding translation to field English': {
            'success': 2,
            'error': {
                'Field is empty': 1
            }
        },
        'adding transliteration to field Pinyin': {
            'success': 2,
            'error': {
                'Field is empty': 1
            }
        },
        'adding audio to field Sound': {
            'success': 2,
            'error': {
                'Field is empty': 1
            }
        }
    }
    actual_action_stats = dialog.batch_error_manager.action_stats
    logging.info(actual_action_stats)
    assert expected_action_stats == actual_action_stats
def test_language_mapping(qtbot):
    # pytest test_dialogs.py -rPP -k test_language_mapping

    # make sure our deck appears
    # --------------------------

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance(
        'no_language_mapping')

    mapping_dialog = dialog_languagemapping.prepare_language_mapping_dialogue(
        mock_language_tools)

    # assert deck name, note type, and 3 fields
    deck_frame = mapping_dialog.findChild(aqt.qt.QFrame,
                                          f'frame_{config_gen.deck_name}')
    assert deck_frame != None
    deck_name_label = mapping_dialog.findChild(
        aqt.qt.QLabel, f'deck_name_{config_gen.deck_name}')
    assert deck_name_label.text() == config_gen.deck_name
    note_type_label = mapping_dialog.findChild(
        aqt.qt.QLabel,
        f'note_type_name_{config_gen.deck_name}_{config_gen.model_name}')
    assert note_type_label.text() == config_gen.model_name

    # look for labels on all 3 fields
    for field_name in config_gen.all_fields:
        field_label_obj_name = f'field_label_{config_gen.model_name} / {config_gen.deck_name} / {field_name}'
        field_label = mapping_dialog.findChild(aqt.qt.QLabel,
                                               field_label_obj_name)
        assert field_label.text() == field_name

    # none of the languages should be set
    for field_name in config_gen.all_fields:
        field_language_obj_name = f'field_language_{config_gen.model_name} / {config_gen.deck_name} / {field_name}'
        field_language = mapping_dialog.findChild(aqt.qt.QComboBox,
                                                  field_language_obj_name)
        assert field_language != None
        # ensure the "not set" option is selected
        assert field_language.currentText() == 'Not Set'

    # now, set languages manually
    # ---------------------------

    field_language_obj_name = f'field_language_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_chinese}'
    field_language_combobox = mapping_dialog.findChild(
        aqt.qt.QComboBox, field_language_obj_name)
    qtbot.keyClicks(field_language_combobox, 'Chinese')
    field_language_obj_name = f'field_language_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_english}'
    field_language_combobox = mapping_dialog.findChild(
        aqt.qt.QComboBox, field_language_obj_name)
    qtbot.keyClicks(field_language_combobox, 'English')

    apply_button = mapping_dialog.findChild(aqt.qt.QPushButton, 'apply')
    qtbot.mouseClick(apply_button, aqt.qt.Qt.LeftButton)

    # ensure configuration has been modified
    model_name = config_gen.model_name
    deck_name = config_gen.deck_name
    assert mock_language_tools.anki_utils.written_config[
        constants.CONFIG_DECK_LANGUAGES][model_name][deck_name][
            config_gen.field_chinese] == 'zh_cn'
    assert mock_language_tools.anki_utils.written_config[
        constants.CONFIG_DECK_LANGUAGES][model_name][deck_name][
            config_gen.field_english] == 'en'

    # run automatic detection
    # -----------------------

    mapping_dialog = dialog_languagemapping.prepare_language_mapping_dialogue(
        mock_language_tools)
    # apply button should be disabled
    apply_button = mapping_dialog.findChild(aqt.qt.QPushButton, 'apply')
    assert apply_button.isEnabled() == False

    autodetect_button = mapping_dialog.findChild(aqt.qt.QPushButton,
                                                 'run_autodetect')
    qtbot.mouseClick(autodetect_button, aqt.qt.Qt.LeftButton)

    # assert languages detected
    field_language_obj_name = f'field_language_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_chinese}'
    field_language = mapping_dialog.findChild(aqt.qt.QComboBox,
                                              field_language_obj_name)
    assert field_language.currentText() == 'Chinese'

    field_language_obj_name = f'field_language_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_english}'
    field_language = mapping_dialog.findChild(aqt.qt.QComboBox,
                                              field_language_obj_name)
    assert field_language.currentText() == 'English'

    # apply button should be enabled
    assert apply_button.isEnabled() == True

    # now , click the apply button
    qtbot.mouseClick(apply_button, aqt.qt.Qt.LeftButton)

    # ensure configuration has been modified
    model_name = config_gen.model_name
    deck_name = config_gen.deck_name
    assert mock_language_tools.anki_utils.written_config[
        constants.CONFIG_DECK_LANGUAGES][model_name][deck_name][
            config_gen.field_chinese] == 'zh_cn'
    assert mock_language_tools.anki_utils.written_config[
        constants.CONFIG_DECK_LANGUAGES][model_name][deck_name][
            config_gen.field_english] == 'en'

    # mapping_dialog.exec_()

    # show field samples
    # ==================

    # reset this
    mock_language_tools.anki_utils.written_config = None
    mapping_dialog = dialog_languagemapping.prepare_language_mapping_dialogue(
        mock_language_tools)

    field_samples_button_obj_name = f'field_samples_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_english}'
    autodetect_button = mapping_dialog.findChild(
        aqt.qt.QPushButton, field_samples_button_obj_name)
    qtbot.mouseClick(autodetect_button, aqt.qt.Qt.LeftButton)

    assert 'old people' in mock_language_tools.anki_utils.info_message_received
    assert 'hello' in mock_language_tools.anki_utils.info_message_received

    field_samples_button_obj_name = f'field_samples_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_sound}'
    autodetect_button = mapping_dialog.findChild(
        aqt.qt.QPushButton, field_samples_button_obj_name)
    qtbot.mouseClick(autodetect_button, aqt.qt.Qt.LeftButton)

    assert 'No usable field data found' in mock_language_tools.anki_utils.info_message_received

    # set one language manually
    field_language_obj_name = f'field_language_{config_gen.model_name} / {config_gen.deck_name} / {config_gen.field_chinese}'
    field_language_combobox = mapping_dialog.findChild(
        aqt.qt.QComboBox, field_language_obj_name)
    qtbot.keyClicks(field_language_combobox, 'Sound')

    # hit cancel
    cancel_button = mapping_dialog.findChild(aqt.qt.QPushButton, 'cancel')
    qtbot.mouseClick(cancel_button, aqt.qt.Qt.LeftButton)
    # there should not be any config change
    assert mock_language_tools.anki_utils.written_config == None
def test_voice_selection(qtbot):
    # pytest test_dialogs.py -rPP -k test_voice_selection

    # make sure the dialog comes up
    # -----------------------------

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    voice_list = mock_language_tools.cloud_language_tools.get_tts_voice_list(
        'yoyo')
    voice_selection_dialog = dialog_voiceselection.prepare_voice_selection_dialog(
        mock_language_tools, voice_list)

    # there should be two languages. English And Chinese
    # for each language, there should be two voices
    # they should both have two samples each

    languages_combobox = voice_selection_dialog.findChild(
        aqt.qt.QComboBox, 'languages_combobox')
    assert languages_combobox.count() == 2
    assert languages_combobox.itemText(0) == 'Chinese'
    assert languages_combobox.itemText(1) == 'English'

    voices_combobox = voice_selection_dialog.findChild(aqt.qt.QComboBox,
                                                       'voices_combobox')
    assert voices_combobox.count() == 2
    assert 'Xiaoxiao' in voices_combobox.itemText(0)
    assert 'Yunyang' in voices_combobox.itemText(1)

    # check samples
    assert voice_selection_dialog.sample_labels[0].text() == '老人家'
    assert voice_selection_dialog.sample_labels[1].text() == '你好'

    # now, select English
    qtbot.keyClicks(languages_combobox, 'English')

    assert voices_combobox.count() == 2
    assert 'Aria' in voices_combobox.itemText(0)
    assert 'Guy' in voices_combobox.itemText(1)

    # check samples
    assert voice_selection_dialog.sample_labels[0].text() == 'old people'
    assert voice_selection_dialog.sample_labels[1].text() == 'hello'

    # pick the Guy voice
    guy_voice = voices_combobox.itemText(1)
    qtbot.keyClicks(voices_combobox, guy_voice)

    # listen to samples
    play_sample_button = voice_selection_dialog.findChild(
        aqt.qt.QPushButton, f'play_sample_0')
    qtbot.mouseClick(play_sample_button, aqt.qt.Qt.LeftButton)
    # check that sample has been played
    assert mock_language_tools.anki_utils.played_sound['text'] == 'old people'
    assert 'Guy' in mock_language_tools.anki_utils.played_sound['voice_key'][
        'name']

    apply_button = voice_selection_dialog.findChild(aqt.qt.QPushButton,
                                                    'apply')
    qtbot.mouseClick(apply_button, aqt.qt.Qt.LeftButton)

    assert 'en' in mock_language_tools.anki_utils.written_config[
        constants.CONFIG_VOICE_SELECTION]
    assert 'Guy' in mock_language_tools.anki_utils.written_config[
        constants.CONFIG_VOICE_SELECTION]['en']['voice_key']['name']

    # use the written config as the new config
    mock_language_tools.config = mock_language_tools.anki_utils.written_config

    # open the dialog box again
    # =========================

    voice_selection_dialog = dialog_voiceselection.prepare_voice_selection_dialog(
        mock_language_tools, voice_list)
    apply_button = voice_selection_dialog.findChild(aqt.qt.QPushButton,
                                                    'apply')
    assert apply_button.isEnabled() == False  # should be disabled

    # switch language to english
    languages_combobox = voice_selection_dialog.findChild(
        aqt.qt.QComboBox, 'languages_combobox')
    qtbot.keyClicks(languages_combobox, 'English')

    # choose different voice
    voices_combobox = voice_selection_dialog.findChild(aqt.qt.QComboBox,
                                                       'voices_combobox')
    current_index = voices_combobox.currentIndex()
    assert voices_combobox.count() == 2
    assert 'Guy' in voices_combobox.itemText(
        current_index)  # check current english voice
    voice_wanted = voices_combobox.itemText(0)
    qtbot.keyClicks(voices_combobox, voice_wanted)

    assert apply_button.isEnabled() == True
    qtbot.mouseClick(apply_button, aqt.qt.Qt.LeftButton)

    assert 'Aria' in mock_language_tools.anki_utils.written_config[
        constants.CONFIG_VOICE_SELECTION]['en']['voice_key']['name']
def test_generate_audio_for_field(qtbot):

    # regular case
    # ============

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    # common variables
    note_id = config_gen.note_id_1
    from_field = config_gen.field_chinese
    to_field = config_gen.field_sound

    voice_list = mock_language_tools.get_tts_voice_list()
    chinese_voices = [x for x in voice_list if x['language_code'] == 'zh_cn']
    voice = chinese_voices[0]

    result = mock_language_tools.generate_audio_for_field(
        note_id, from_field, to_field, voice)
    assert result == True

    # get the note
    note = config_gen.notes_by_id[config_gen.note_id_1]

    # make sure sound was added
    assert config_gen.field_sound in note.set_values
    assert 'sound:languagetools-' in note.set_values[config_gen.field_sound]
    assert note.flush_called == True

    assert mock_language_tools.anki_utils.added_media_file != None
    assert 'languagetools-' in mock_language_tools.anki_utils.added_media_file

    # empty field
    # ===========

    config_gen = EmptyFieldConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    result = mock_language_tools.generate_audio_for_field(
        note_id, from_field, to_field, voice)
    assert result == False

    # get the note
    note = config_gen.notes_by_id[config_gen.note_id_1]

    # make sure no sound was added
    assert config_gen.field_sound not in note.set_values
    assert note.flush_called == False

    assert mock_language_tools.anki_utils.added_media_file == None

    # empty field but with html junk
    # ==============================

    config_gen = DummyHtmlEmptyFieldConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')

    result = mock_language_tools.generate_audio_for_field(
        note_id, from_field, to_field, voice)
    assert result == False

    # get the note
    note = config_gen.notes_by_id[config_gen.note_id_1]

    # make sure no sound was added
    assert config_gen.field_sound not in note.set_values
    assert note.flush_called == False

    assert mock_language_tools.anki_utils.added_media_file == None
def test_error_manager(qtbot):
    # pytest test_errors.py -s -rPP -k test_error_manager

    config_gen = testing_utils.TestConfigGenerator()
    mock_language_tools = config_gen.build_languagetools_instance('default')
    error_manager = errors.ErrorManager(mock_language_tools.anki_utils)

    dnt = deck_utils.DeckNoteType(1, 'Deck', 1, 'NoteType')
    dntf = deck_utils.DeckNoteTypeField(dnt, 'field1')

    # single actions
    # ==============

    with error_manager.get_single_action_context('single action 1'):
        logging.info('single action 1')
        raise errors.LanguageToolsValidationFieldEmpty()

    assert isinstance(mock_language_tools.anki_utils.last_exception,
                      errors.LanguageToolsValidationFieldEmpty)
    assert mock_language_tools.anki_utils.last_action == 'single action 1'
    mock_language_tools.anki_utils.reset_exceptions()

    with error_manager.get_single_action_context('single action 2'):
        logging.info('single action 2')
        logging.info('successful')

    assert mock_language_tools.anki_utils.last_exception == None
    mock_language_tools.anki_utils.reset_exceptions()

    # unhandled exception
    with error_manager.get_single_action_context('single action 3'):
        logging.info('single action 3')
        raise Exception('this is unhandled')

    assert isinstance(mock_language_tools.anki_utils.last_exception, Exception)
    assert mock_language_tools.anki_utils.last_action == 'single action 3'
    mock_language_tools.anki_utils.reset_exceptions()

    # batch actions
    # =============

    batch_error_manager = error_manager.get_batch_error_manager('batch test 1')

    with batch_error_manager.get_batch_action_context('batch iteration 1'):
        logging.info('batch iteration 1')
        raise errors.LanguageToolsValidationFieldEmpty()

    with batch_error_manager.get_batch_action_context('batch iteration 2'):
        logging.info('batch iteration 2')
        logging.info('ok')

    with batch_error_manager.get_batch_action_context('batch iteration 3'):
        logging.info('batch iteration 3')
        raise errors.FieldLanguageMappingError(dntf)

    expected_action_stats = {
        'batch iteration 1': {
            'success': 0,
            'error': {
                'Field is empty': 1
            }
        },
        'batch iteration 2': {
            'success': 1,
            'error': {}
        },
        'batch iteration 3': {
            'success': 0,
            'error': {
                'No language set for NoteType / Deck / field1. Please setup Language Mappings, from the Anki main screen: <b>Tools -> Language Tools: Language Mapping</b>':
                1
            }
        }
    }
    actual_action_stats = batch_error_manager.action_stats
    assert expected_action_stats == actual_action_stats

    # batch_error_manager.display_stats()

    # batch actions with unhandled exceptions
    # =======================================

    batch_error_manager = error_manager.get_batch_error_manager('batch test 2')

    with batch_error_manager.get_batch_action_context('batch iteration 1'):
        logging.info('batch iteration 1')
        logging.info('ok')

    with batch_error_manager.get_batch_action_context('batch iteration 2'):
        logging.info('batch iteration 2')
        raise Exception('this is unhandled')

    expected_action_stats = {
        'batch iteration 1': {
            'success': 1,
            'error': {}
        },
        'batch iteration 2': {
            'success': 0,
            'error': {
                'Unknown Error: this is unhandled': 1
            }
        }
    }