text: # -*- coding: utf-8 -*-
  #
  # Copyright © Spyder Project Contributors
  # Licensed under the terms of the MIT License
  #
  
  """
  Tests syncing between the EditorStack and OutlineExplorerWidget.
  """
  
  # Standard library imports
  import os
  import json
  import os.path as osp
  import sys
  from unittest.mock import Mock
  
  # Qt imports
  from qtpy.QtCore import Qt
  from qtpy.QtGui import QTextCursor
  
  # Third party imports
  import pytest
  
  # Local imports
  from spyder.config.base import running_in_ci
  from spyder.plugins.editor.widgets import editor
  from spyder.plugins.outlineexplorer.editor import OutlineExplorerProxyEditor
  from spyder.plugins.outlineexplorer.main_widget import OutlineExplorerWidget
  
  
  HERE = osp.dirname(osp.abspath(__file__))
  ASSETS = osp.join(HERE, 'assets')
  
  AVAILABLE_CASES = ['text']
  CASES = {
      case: {
          'file': osp.join(ASSETS, '{0}.py'.format(case)),
          'tree': osp.join(ASSETS, '{0}_trees.json'.format(case))
      }
      for case in AVAILABLE_CASES
  }
  
  
  def get_tree_elements(treewidget):
      """Get elements present in the Outline tree widget."""
      root_item = treewidget.get_top_level_items()[0]
      root_ref = root_item.ref
      filename = osp.basename(root_ref.name)
      root_tree = {filename: []}
      stack = [(root_tree[filename], node) for node in root_ref.children]
  
      while len(stack) > 0:
          parent_tree, node = stack.pop(0)
          this_tree = {node.name: []}
          parent_tree.append(this_tree)
          this_stack = [(this_tree[node.name], child)
                          for child in node.children]
          stack = this_stack + stack
      return root_tree
  
  
  # ---- Qt Test Fixtures
  @pytest.fixture(scope="module")
  def test_files(tmpdir_factory):
      """Create and save some python codes and text in temporary files."""
      tmpdir = tmpdir_factory.mktemp("files")
  
      filename1 = osp.join(tmpdir.strpath, 'foo1.py')
      with open(filename1, 'w') as f:
          f.write("# -*- coding: utf-8 -*-\n"
                  "def foo:\n"
                  "    print(Hello World!)\n")
  
      filename2 = osp.join(tmpdir.strpath, 'text1.txt')
      with open(filename2, 'w') as f:
          f.write("This is a simple text file for\n"
                  "testing the Outline Explorer.\n")
  
      filename3 = osp.join(tmpdir.strpath, 'foo2.py')
      with open(filename3, 'w') as f:
          f.write("# -*- coding: utf-8 -*-\n"
                  "# ---- a comment\n")
  
      return [filename1, filename2, filename3]
  
  
  @pytest.fixture
  def outlineexplorer(qtbot):
      """Set up an OutlineExplorerWidget."""
      outlineexplorer = OutlineExplorerWidget(None, None, None)
      outlineexplorer.set_conf('show_fullpath', False)
      outlineexplorer.set_conf('show_all_files', True)
      outlineexplorer.set_conf('group_cells', True)
      outlineexplorer.set_conf('show_comments', True)
      outlineexplorer.set_conf('sort_files_alphabetically', False)
      outlineexplorer.set_conf('display_variables', True)
  
      # Fix the size of the outline explorer to prevent an
      # 'Unable to set geometry ' warning if the test fails.
      outlineexplorer.setFixedSize(400, 350)
  
      qtbot.addWidget(outlineexplorer)
      outlineexplorer.show()
  
      return outlineexplorer
  
  
  @pytest.fixture
  def completions_codeeditor_outline(completions_codeeditor, outlineexplorer):
      editor, _ = completions_codeeditor
      editor.oe_proxy = OutlineExplorerProxyEditor(editor, editor.filename)
      outlineexplorer.register_editor(editor.oe_proxy)
      outlineexplorer.set_current_editor(
          editor.oe_proxy, update=False, clear=False)
      return editor, outlineexplorer
  
  
  @pytest.fixture
  def editorstack(qtbot, outlineexplorer):
      def _create_editorstack(files):
          editorstack = editor.EditorStack(None, [])
          editorstack.set_find_widget(Mock())
          editorstack.set_io_actions(Mock(), Mock(), Mock(), Mock())
          editorstack.analysis_timer = Mock()
          editorstack.save_dialog_on_tests = True
          editorstack.set_outlineexplorer(outlineexplorer)
  
          qtbot.addWidget(editorstack)
          editorstack.show()
  
          for index, file in enumerate(files):
              focus = index == 0
              editorstack.load(file, set_current=focus)
          return editorstack
      return _create_editorstack
  
  
  # ---- Test all files mode
  def test_load_files(editorstack, outlineexplorer, test_files):
      """
      Test that the content of the outline explorer is updated correctly
      after a file is loaded in the editor.
      """
      editorstack = editorstack([])
      treewidget = outlineexplorer.treewidget
  
      # Load the test files one by one and assert the content of the
      # outline explorer.
      expected_result = [['foo1.py'],
                         ['foo1.py', 'text1.txt'],
                         ['foo1.py', 'text1.txt', 'foo2.py']]
      for index, file in enumerate(test_files):
          editorstack.load(file)
          assert editorstack.get_current_filename() == file
          assert editorstack.get_stack_index() == index
  
          results = [item.text(0) for item in treewidget.get_visible_items()]
          assert results == expected_result[index]
          assert editorstack.get_stack_index() == index
  
  
  def test_close_editor(editorstack, outlineexplorer, test_files):
      """
      Test that the content of the outline explorer is empty after the
      editorstack has been closed.
  
      Regression test for spyder-ide/spyder#7798.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
      assert treewidget.get_visible_items()
  
      # Close the editor and assert that the outline explorer tree is empty.
      editorstack.close()
      assert not treewidget.get_visible_items()
  
  
  def test_close_a_file(editorstack, outlineexplorer, test_files):
      """
      Test that the content of the outline explorer is updated corrrectly
      after a file has been closed in the editorstack.
  
      Regression test for spyder-ide/spyder#7798.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
  
      # Close 'foo2.py' and assert that the content of the outline explorer
      # tree has been updated.
      editorstack.close_file(index=1)
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['foo1.py', 'foo2.py']
  
  
  def test_sort_file_alphabetically(editorstack, outlineexplorer, test_files):
      """
      Test that the option to sort the files in alphabetical order in the
      outline explorer is working as expected.
  
      This feature was introduced in spyder-ide/spyder#8015.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['foo1.py', 'text1.txt', 'foo2.py']
  
      # Set the option to sort files alphabetically to True.
      treewidget.toggle_sort_files_alphabetically(True)
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['foo1.py', 'foo2.py', 'text1.txt']
  
  
  def test_sync_file_order(editorstack, outlineexplorer, test_files):
      """
      Test that the order of the files in the Outline Explorer is updated when
      tabs are moved in the EditorStack.
  
      This feature was introduced in spyder-ide/spyder#8015.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['foo1.py', 'text1.txt', 'foo2.py']
  
      # Switch tab 1 with tab 2.
      editorstack.tabs.tabBar().moveTab(0, 1)
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['text1.txt', 'foo1.py', 'foo2.py']
  
  
  # ---- Test single file mode
  @pytest.mark.skipif(running_in_ci(), reason="Fails on CIs")
  def test_toggle_off_show_all_files(editorstack, outlineexplorer, test_files,
                                     qtbot):
      """
      Test that toggling off the option to show all files in the Outline Explorer
      hide all root file items but the one corresponding to the currently
      selected Editor and assert that the remaning root file item is
      expanded correctly.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
      assert editorstack.get_stack_index() == 0
  
      # Untoggle show all files option.
      treewidget.toggle_show_all_files(False)
      qtbot.wait(500)
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['foo1.py']
  
  
  @pytest.mark.skipif(sys.platform.startswith('linux'), reason="Fails on Linux")
  def test_single_file_sync(editorstack, outlineexplorer, test_files, qtbot):
      """
      Test that the content of the Outline Explorer is updated correctly
      when the current Editor in the Editorstack changes.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
      treewidget.toggle_show_all_files(False)
      assert editorstack.get_stack_index() == 0
  
      # Select the last file in the Editorstack.
      with qtbot.waitSignal(editorstack.editor_focus_changed):
          editorstack.tabs.setCurrentIndex(2)
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['foo2.py']
  
  
  def test_toggle_on_show_all_files(editorstack, outlineexplorer, test_files):
      """
      Test that toggling back the option to show all files, after the
      order of the files in the Editorstack was changed while it was in single
      file mode, show all the root file items in the correct order.
      """
      editorstack = editorstack(test_files)
      treewidget = outlineexplorer.treewidget
      treewidget.toggle_show_all_files(False)
  
      # Move the first file to the second position in the tabbar of the
      # Editorstack and toggle back the show all files option.
      editorstack.tabs.tabBar().moveTab(0, 1)
      treewidget.toggle_show_all_files(True)
      results = [item.text(0) for item in treewidget.get_visible_items()]
      assert results == ['text1.txt', 'foo1.py', 'foo2.py']
  
  
  @pytest.mark.slow
  @pytest.mark.order(2)
  def test_editor_outlineexplorer(qtbot, completions_codeeditor_outline):
      """Tests that the outline explorer reacts to editor changes."""
      code_editor, outlineexplorer = completions_codeeditor_outline
      treewidget = outlineexplorer.treewidget
  
      case_info = CASES['text']
      filename = case_info['file']
      tree_file = case_info['tree']
  
      with open(filename, 'r') as f:
          lines = f.read()
  
      with open(tree_file, 'r') as f:
          trees = json.load(f)
  
      code_editor.toggle_automatic_completions(False)
      code_editor.toggle_code_snippets(False)
  
      # Set cursor to start
      code_editor.set_text('')
      code_editor.go_to_line(1)
  
      # Put example text in editor
      code_editor.set_text(lines)
      qtbot.wait(3000)
  
      # Check that the outline tree was initialized successfully
      tree = trees[0]
      root_tree = get_tree_elements(treewidget)
      assert root_tree == tree
  
      # Remove "d" symbol
      code_editor.go_to_line(14)
      cursor = code_editor.textCursor()
      start = code_editor.get_position_line_number(13, -1)
      end = code_editor.get_position_line_number(17, 0)
      cursor.setPosition(start)
      cursor.setPosition(end, QTextCursor.KeepAnchor)
      code_editor.setTextCursor(cursor)
      code_editor.cut()
      qtbot.wait(3000)
  
      tree = trees[1]
      root_tree = get_tree_elements(treewidget)
      assert root_tree == tree
  
      # Add "d" symbol elsewhere
      code_editor.go_to_line(36)
  
      with qtbot.waitSignal(
              code_editor.completions_response_signal, timeout=30000):
          qtbot.keyPress(code_editor, Qt.Key_Return)
          qtbot.keyPress(code_editor, Qt.Key_Return)
  
      qtbot.keyPress(code_editor, Qt.Key_Up)
      code_editor.paste()
  
      qtbot.wait(3000)
  
      tree = trees[2]
      root_tree = get_tree_elements(treewidget)
      assert root_tree == tree
  
      # Move method1
      code_editor.go_to_line(56)
      cursor = code_editor.textCursor()
      start = code_editor.get_position_line_number(55, -1)
      end = code_editor.get_position_line_number(57, -1)
      cursor.setPosition(start)
      cursor.setPosition(end, QTextCursor.KeepAnchor)
      code_editor.setTextCursor(cursor)
      code_editor.cut()
      qtbot.wait(3000)
  
      tree = trees[3]
      root_tree = get_tree_elements(treewidget)
      assert root_tree == tree
  
      # Add method1
      code_editor.go_to_line(49)
  
      with qtbot.waitSignal(
              code_editor.completions_response_signal, timeout=30000):
          qtbot.keyPress(code_editor, Qt.Key_Return)
          qtbot.keyPress(code_editor, Qt.Key_Return)
  
      qtbot.keyPress(code_editor, Qt.Key_Up)
      code_editor.paste()
      qtbot.wait(3000)
  
      tree = trees[4]
      root_tree = get_tree_elements(treewidget)
      assert root_tree == tree
  
      # Add attribute "y"
      code_editor.go_to_line(48)
      cursor = code_editor.textCursor()
      cursor.movePosition(QTextCursor.EndOfBlock)
      code_editor.setTextCursor(cursor)
  
      with qtbot.waitSignal(
              code_editor.completions_response_signal, timeout=30000):
          qtbot.keyPress(code_editor, Qt.Key_Return)
          qtbot.keyClicks(code_editor, 'self.y = None')
          qtbot.keyPress(code_editor, Qt.Key_Return)
  
      with qtbot.waitSignal(treewidget.sig_tree_updated, timeout=30000):
          code_editor.request_symbols()
  
      tree = trees[5]
      root_tree = get_tree_elements(treewidget)
      assert root_tree == tree
  
  
  @pytest.mark.slow
  @pytest.mark.order(2)
  def test_empty_file(qtbot, completions_codeeditor_outline):
      """
      Test that the outline explorer is updated correctly when
      it's associated file is empty.
      """
      code_editor, outlineexplorer = completions_codeeditor_outline
      treewidget = outlineexplorer.treewidget
  
      code_editor.toggle_automatic_completions(False)
      code_editor.toggle_code_snippets(False)
  
      # Set empty contents
      code_editor.set_text('')
      code_editor.go_to_line(1)
      qtbot.wait(3000)
  
      # Assert the spinner is not shown.
      assert not outlineexplorer._spinner.isSpinning()
  
      # Add some content
      code_editor.set_text("""
  def foo():
      a = 10
      return a
  """)
      qtbot.wait(3000)
  
      root_tree = get_tree_elements(treewidget)
      assert root_tree == {'test.py': [{'foo': [{'a': []}]}]}
  
      # Remove content
      code_editor.selectAll()
      qtbot.keyPress(code_editor, Qt.Key_Delete)
  
      with qtbot.waitSignal(
              code_editor.completions_response_signal, timeout=30000):
          code_editor.document_did_change()
  
      qtbot.wait(3000)
  
      # Assert the tree is empty and the spinner is not shown.
      root_tree = get_tree_elements(treewidget)
      assert root_tree == {'test.py': []}
      assert not outlineexplorer._spinner.isSpinning()
  
  
  if __name__ == "__main__":
      import os
      pytest.main([os.path.basename(__file__), '-vv', '-rw'])
  