Repository: spyder-ide/spyder

Issue:
Outline pane navigates to the wrong file
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

- open a file with some cells (outline pane should work now)
- go to "save as" and use a new name
- try to navigate with the outline pane in your renamed file
--> spyder re-opens the old file and jumps to the selected cell



## Versions

* Spyder version: 4.2.3 
* Python version: 3.6.8
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 10

### Dependencies

```

# Mandatory:
atomicwrites >=1.2.0            :  1.4.0 (OK)
chardet >=2.0.0                 :  4.0.0 (OK)
cloudpickle >=0.5.0             :  1.6.0 (OK)
diff_match_patch >=20181111     :  20200713 (OK)
intervaltree >=3.0.2            :  3.1.0 (OK)
IPython >=7.6.0                 :  7.16.1 (OK)
jedi =0.17.2                    :  0.17.2 (OK)
jsonschema >=3.2.0              :  3.2.0 (OK)
keyring >=17.0.0                :  22.3.0 (OK)
nbconvert >=4.0                 :  6.0.7 (OK)
numpydoc >=0.6.0                :  1.1.0 (OK)
paramiko >=2.4.0                :  2.7.2 (OK)
parso =0.7.0                    :  0.7.0 (OK)
pexpect >=4.4.0                 :  4.8.0 (OK)
pickleshare >=0.4               :  0.7.5 (OK)
psutil >=5.3                    :  5.8.0 (OK)
pygments >=2.0                  :  2.8.1 (OK)
pylint >=1.0                    :  2.7.4 (OK)
pyls >=0.36.2;<1.0.0            :  0.36.2 (OK)
pyls_black >=0.4.6              :  0.4.6 (OK)
pyls_spyder >=0.3.2             :  0.3.2 (OK)
qdarkstyle >=2.8                :  2.8.1 (OK)
qtawesome >=0.5.7               :  1.0.2 (OK)
qtconsole >=5.0.1               :  5.0.3 (OK)
qtpy >=1.5.0                    :  1.9.0 (OK)
rtree >=0.8.3                   :  0.9.7 (OK)
setuptools >=39.0.0             :  52.0.0.post20210125 (OK)
sphinx >=0.6.6                  :  3.5.3 (OK)
spyder_kernels >=1.10.2;<1.11.0 :  1.10.2 (OK)
textdistance >=4.2.0            :  4.2.1 (OK)
three_merge >=0.1.1             :  0.1.1 (OK)
watchdog >=0.10.3;<2.0.0        :  1.0.2 (OK)
zmq >=17                        :  20.0.0 (OK)

# Optional:
cython >=0.21                   :  0.29.23 (OK)
matplotlib >=2.0.0              :  3.1.3 (OK)
numpy >=1.7                     :  1.19.5 (OK)
pandas >=1.1.1                  :  1.1.5 (OK)
scipy >=0.17.0                  :  1.5.3 (OK)
sympy >=0.7.3                   :  None (NOK)
```



Discussion:
Hey @flipsurf, thanks for reporting. We'll take a look at this in a future release.
FYI This has been an obstacle for quite some time.  One habit I have developed is to use "Save copy as...".  This will save with desired name and reopen the file.  You still have to manually close or otherwise deal with the unsaved/original file. 

Hint: If you mouse hover over the top level file name in Outline pane it will display the path of the file it will navigate to.  The issue is that it shows "untitled0.py" or whatever the wrong file name is.
I discovered this today as I found myself editing the wrong file, thought I was crazy, then figured out what was happening.

In case it helps, here is a repro:

1) Use this code and save as test_spyder_outline.py
```
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 31 11:43:08 2021

@author: Blaine Bateman
"""
#%% libraries
#
import pandas as pd
#
#%% data
#
my_data = pd.DataFrame({'index' : range(10),
                        'data' : range(10)})
#
#%% some stuff
#
my_data['data'] = my_data['data'] + 6
my_data['data'] = my_data['data']**2
#
```
2) Open the file, then save as 'test_spyder_outline_copy.py'
3) Click on a name in the Outline panel
4) The original file will open and navigate to that cell

Note that if the other file is already open, it will just shift to that tab in the editor and go to that cell. It's so fast, I missed it the first few times.
Update--it appears saving the project and reopening fixes this.

Resolving patch:
diff --git a/spyder/plugins/outlineexplorer/editor.py b/spyder/plugins/outlineexplorer/editor.py
--- a/spyder/plugins/outlineexplorer/editor.py
+++ b/spyder/plugins/outlineexplorer/editor.py
@@ -6,9 +6,6 @@
 
 """Outline explorer editor server"""
 
-# Third-party imports
-from intervaltree import IntervalTree
-
 # Local imports
 from spyder.plugins.outlineexplorer.api import OutlineExplorerProxy
 
diff --git a/spyder/plugins/outlineexplorer/widgets.py b/spyder/plugins/outlineexplorer/widgets.py
--- a/spyder/plugins/outlineexplorer/widgets.py
+++ b/spyder/plugins/outlineexplorer/widgets.py
@@ -15,14 +15,12 @@
 from intervaltree import IntervalTree
 from pkg_resources import parse_version
 from qtpy import PYSIDE2
-from qtpy.compat import from_qvariant
 from qtpy.QtCore import Qt, QTimer, Signal, Slot
 from qtpy.QtWidgets import QTreeWidgetItem, QTreeWidgetItemIterator
 
 # Local imports
 from spyder.api.config.decorators import on_conf_change
 from spyder.config.base import _
-from spyder.py3compat import to_text_string
 from spyder.utils.icon_manager import ima
 from spyder.plugins.completion.api import SymbolKind, SYMBOL_KIND_ICON
 from spyder.utils.qthelpers import set_item_user_text
@@ -93,7 +91,10 @@ def delete(self):
             self.parent.remove_node(self)
             self.parent = None
 
-        if self.node.parent is not None:
+        if (
+            self.node.parent is not None
+            and hasattr(self.node.parent, 'remove_children')
+        ):
             self.node.parent.remove_children(self.node)
 
     def add_node(self, node):
@@ -153,6 +154,10 @@ def create_node(self):
                                self.position[0] + 1, self.status,
                                self.selected)
 
+    def set_path(self, new_path):
+        self.name = new_path
+        self.path = new_path
+
     def __repr__(self):
         return str(self)
 
@@ -215,61 +220,6 @@ def update_info(self, name, kind, position, status, selected):
         self.setSelected(selected)
 
 
-class TreeItem(QTreeWidgetItem):
-    """Class browser item base class."""
-    def __init__(self, oedata, parent, preceding):
-        if preceding is None:
-            QTreeWidgetItem.__init__(self, parent, QTreeWidgetItem.Type)
-        else:
-            if preceding is not parent:
-                # Preceding must be either the same as item's parent
-                # or have the same parent as item
-                while preceding.parent() is not parent:
-                    preceding = preceding.parent()
-                    if preceding is None:
-                        break
-            if preceding is None:
-                QTreeWidgetItem.__init__(self, parent, QTreeWidgetItem.Type)
-            else:
-                QTreeWidgetItem.__init__(self, parent, preceding,
-                                         QTreeWidgetItem.Type)
-        self.parent_item = parent
-        self.oedata = oedata
-        oedata.sig_update.connect(self.update)
-        self.update()
-
-    def level(self):
-        """Get fold level."""
-        return self.oedata.fold_level
-
-    def get_name(self):
-        """Get the item name."""
-        return self.oedata.def_name
-
-    def set_icon(self, icon):
-        self.setIcon(0, icon)
-
-    def setup(self):
-        self.setToolTip(0, _("Line %s") % str(self.line))
-
-    @property
-    def line(self):
-        """Get line number."""
-        block_number = self.oedata.get_block_number()
-        if block_number is not None:
-            return block_number + 1
-        return None
-
-    def update(self):
-        """Update the tree element."""
-        name = self.get_name()
-        self.setText(0, name)
-        parent_text = from_qvariant(self.parent_item.data(0, Qt.UserRole),
-                                    to_text_string)
-        set_item_user_text(self, parent_text + '/' + name)
-        self.setup()
-
-
 # ---- Treewidget
 # -----------------------------------------------------------------------------
 class OutlineExplorerTreeWidget(OneColumnTree):
@@ -513,12 +463,26 @@ def file_renamed(self, editor, new_filename):
         if editor is None:
             # This is needed when we can't find an editor to attach
             # the outline explorer to.
-            # Fix spyder-ide/spyder#8813.
+            # Fixes spyder-ide/spyder#8813.
             return
+
         editor_id = editor.get_id()
         if editor_id in list(self.editor_ids.values()):
-            root_item = self.editor_items[editor_id].node
+            items = self.editor_items[editor_id]
+
+            # Set path for items
+            items.set_path(new_filename)
+
+            # Change path of root item (i.e. the file name)
+            root_item = items.node
             root_item.set_path(new_filename, fullpath=self.show_fullpath)
+
+            # Clear and re-populate the tree again.
+            # Fixes spyder-ide/spyder#15517
+            items.delete()
+            editor.request_symbols()
+
+            # Resort root items
             self.__sort_toplevel_items()
 
     def update_editors(self, language):


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_save.py b/spyder/plugins/editor/widgets/tests/test_save.py
--- a/spyder/plugins/editor/widgets/tests/test_save.py
+++ b/spyder/plugins/editor/widgets/tests/test_save.py
@@ -10,6 +10,7 @@
 
 # Standard library imports
 import os.path as osp
+from textwrap import dedent
 from unittest.mock import Mock
 
 # Third party imports
@@ -297,38 +298,86 @@ def test_save_as(editor_bot, mocker):
 
 
 @pytest.mark.show_save_dialog
-def test_save_as_with_outline(editor_bot, mocker, tmpdir):
+def test_save_as_with_outline(completions_editor, mocker, qtbot, tmpdir):
     """
-    Test EditorStack.save_as() when the outline explorer is not None.
+    Test EditorStack.save_as() when the outline explorer is active.
 
-    Regression test for spyder-ide/spyder#7754.
+    Regression test for issues spyder-ide/spyder#7754 and
+    spyder-ide/spyder#15517.
     """
-    editorstack, qtbot = editor_bot
-
-    # Set and assert the initial state.
-    editorstack.tabs.setCurrentIndex(1)
-    assert editorstack.get_current_filename() == 'secondtab.py'
-
-    # Add an outline explorer to the editor stack and refresh it.
-    editorstack.set_outlineexplorer(OutlineExplorerWidget(None, None, None))
+    file_path, editorstack, code_editor, completion_plugin = completions_editor
+    proxy = code_editor.oe_proxy
+
+    # Set outline explorer to editor stack and refresh it.
+    outline_explorer = OutlineExplorerWidget(None, None, None)
+    treewidget = outline_explorer.treewidget
+    outline_explorer.show()
+    editorstack.set_outlineexplorer(outline_explorer)
     qtbot.addWidget(editorstack.outlineexplorer)
-    for finfo in editorstack.data:
-        editorstack.outlineexplorer.register_editor(finfo.editor.oe_proxy)
+    editorstack.outlineexplorer.register_editor(proxy)
+
+    outline_explorer.start_symbol_services('python')
     editorstack.refresh()
 
+    # Add some code to the test file and save it
+    code = dedent("""
+        def foo(x):
+            return x
+
+        def bar(y):
+            return y
+    """)
+
+    code_editor.set_text(code)
+    editorstack.save(force=True)
+
+    # Notify changes
+    with qtbot.waitSignal(
+        code_editor.completions_response_signal, timeout=30000
+    ):
+        code_editor.document_did_change()
+
+    # Wait until the outline is filled
+    qtbot.waitUntil(
+        lambda: len(treewidget.editor_tree_cache[proxy.get_id()]) > 0,
+        timeout=5000
+    )
+
     # No save name.
     mocker.patch.object(editorstack, 'select_savename', return_value=None)
     assert editorstack.save_as() is False
-    assert editorstack.get_filenames() == ['foo.py', 'secondtab.py', __file__]
 
-    # Save secondtab.py as foo2.py in a tmpdir
+    # Save file as foo2.py in tmpdir
     new_filename = osp.join(tmpdir.strpath, 'foo2.py')
     editorstack.select_savename.return_value = new_filename
     assert not osp.exists(new_filename)
-    assert editorstack.save_as() is True
-    assert editorstack.get_filenames() == ['foo.py', new_filename, __file__]
+
+    # Symbols should have been requested for the renamed file, which emits the
+    # signals below.
+    with qtbot.waitSignals(
+        [proxy.sig_start_outline_spinner,
+         proxy.sig_outline_explorer_data_changed],
+        timeout=30000
+    ):
+        assert editorstack.save_as() is True
+
+    assert editorstack.get_filenames() == [new_filename]
     assert osp.exists(new_filename)
 
+    # Wait until the outline is filled
+    qtbot.waitUntil(
+        lambda: len(treewidget.editor_tree_cache[proxy.get_id()]) > 0,
+        timeout=5000
+    )
+
+    # Assert root and symbol items have the right path.
+    items = treewidget.editor_items[proxy.get_id()]
+    root_item = items.node
+
+    assert root_item.path == new_filename
+    assert items.path == new_filename
+    assert all([item.path == new_filename for item in items.children])
+
 
 def test_save_copy_as(editor_bot, mocker):
     """Test EditorStack.save_copy as()."""
diff --git a/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json b/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json
--- a/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json
+++ b/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json
@@ -1,50 +1,106 @@
 {
     "text_test_widgets.py": [
         {
-            "d": [
+            "functions": [
                 {
-                    "inner": []
-                }
-            ]
-        },
-        {
-            "func1": [
+                    "d": [
+                        {
+                            "inner": [],
+                            "kind": 12
+                        }
+                    ],
+                    "kind": 12
+                },
                 {
-                    "i": []
-                }
-            ]
-        },
-        {
-            "func2": []
-        },
-        {
-            "a": []
-        },
-        {
-            "b": []
-        },
-        {
-            "c": []
-        },
-        {
-            "Class1": [
+                    "func 1 and 2": [],
+                    "kind": 224
+                },
                 {
-                    "__init__": [
+                    "func1": [
                         {
-                            "x": []
+                            "i": [],
+                            "kind": 13
                         }
-                    ]
+                    ],
+                    "kind": 12
                 },
                 {
-                    "method3": []
+                    "func2": [],
+                    "kind": 12
                 },
                 {
-                    "method2": []
+                    "other functions": [],
+                    "kind": 224
                 },
                 {
-                    "method1": []
+                    "cell level 2": [
+                        {
+                            "a": [],
+                            "kind": 12
+                        },
+                        {
+                            "cell level 3": [
+                                {
+                                    "b": [],
+                                    "kind": 12
+                                },
+                                {
+                                    "cell level 4": [
+                                        {
+                                            "c": [],
+                                            "kind": 12
+                                        }
+                                    ],
+                                    "kind": 225
+                                }
+                            ],
+                            "kind": 225
+                        },
+                        {
+                            "cell level 3-1": [
+                                {
+                                    "func3": [],
+                                    "kind": 12
+                                }
+                            ],
+                            "kind": 225
+                        }
+                    ],
+                    "kind": 225
+                }
+            ],
+            "kind": 225
+        },
+        {
+            "classes": [
+                {
+                    "Class1": [
+                        {
+                            "__init__": [
+                                {
+                                    "x": [],
+                                    "kind": 8
+                                }
+                            ],
+                            "kind": 6
+                        },
+                        {
+                            "method3": [],
+                            "kind": 6
+                        },
+                        {
+                            "method2": [],
+                            "kind": 6
+                        },
+                        {
+                            "method1": [],
+                            "kind": 6
+                        }
+                    ],
+                    "kind": 5
                 }
-            ]
+            ],
+            "kind": 225
         }
     ]
 }
\ No newline at end of file
diff --git a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json
--- a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json
+++ b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json
@@ -1,7 +1,7 @@
 [
     {
         "name": "d",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
@@ -37,7 +37,7 @@
     },
     {
         "name": "func1",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
@@ -73,7 +73,7 @@
     },
     {
         "name": "func2",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
@@ -91,16 +91,16 @@
     },
     {
         "name": "a",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 32,
+                    "line": 33,
                     "character": 0
                 },
                 "end": {
-                    "line": 34,
+                    "line": 35,
                     "character": 0
                 }
             }
@@ -109,16 +109,16 @@
     },
     {
         "name": "b",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 35,
+                    "line": 37,
                     "character": 0
                 },
                 "end": {
-                    "line": 37,
+                    "line": 39,
                     "character": 0
                 }
             }
@@ -127,16 +127,34 @@
     },
     {
         "name": "c",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 38,
+                    "line": 41,
                     "character": 0
                 },
                 "end": {
-                    "line": 40,
+                    "line": 43,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 12
+    },
+    {
+        "name": "func3",
+        "containerName": "None",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 45,
+                    "character": 0
+                },
+                "end": {
+                    "line": 47,
                     "character": 0
                 }
             }
@@ -145,16 +163,16 @@
     },
     {
         "name": "Class1",
-        "containerName": null,
+        "containerName": "None",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 42,
+                    "line": 49,
                     "character": 0
                 },
                 "end": {
-                    "line": 55,
+                    "line": 62,
                     "character": 0
                 }
             }
@@ -168,87 +186,231 @@
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 43,
+                    "line": 50,
                     "character": 4
                 },
                 "end": {
-                    "line": 46,
+                    "line": 53,
                     "character": 0
                 }
             }
         },
-        "kind": 12
+        "kind": 6
     },
     {
-        "name": "x",
-        "containerName": "__init__",
+        "name": "method3",
+        "containerName": "Class1",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 45,
-                    "character": 8
+                    "line": 54,
+                    "character": 4
                 },
                 "end": {
-                    "line": 45,
-                    "character": 18
+                    "line": 56,
+                    "character": 0
                 }
             }
         },
-        "kind": 13
+        "kind": 6
     },
     {
-        "name": "method3",
+        "name": "method2",
         "containerName": "Class1",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 47,
+                    "line": 57,
                     "character": 4
                 },
                 "end": {
-                    "line": 49,
+                    "line": 59,
                     "character": 0
                 }
             }
         },
-        "kind": 12
+        "kind": 6
     },
     {
-        "name": "method2",
+        "name": "method1",
         "containerName": "Class1",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 50,
+                    "line": 60,
                     "character": 4
                 },
+                "end": {
+                    "line": 62,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 6
+    },
+    {
+        "name": "x",
+        "containerName": "__init__",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 52,
+                    "character": 8
+                },
                 "end": {
                     "line": 52,
+                    "character": 18
+                }
+            }
+        },
+        "kind": 8
+    },
+    {
+        "name": "functions",
+        "containerName": "",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 12,
+                    "character": 0
+                },
+                "end": {
+                    "line": 47,
                     "character": 0
                 }
             }
         },
-        "kind": 12
+        "kind": 225
     },
     {
-        "name": "method1",
-        "containerName": "Class1",
+        "name": "func 1 and 2",
+        "containerName": "",
+        "location": {
+            "uri":"file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 19,
+                    "character": 0
+                },
+                "end": {
+                    "line": 19,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 224
+    },
+    {
+        "name": "other functions",
+        "containerName": "",
         "location": {
             "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
             "range": {
                 "start": {
-                    "line": 53,
-                    "character": 4
+                    "line": 30,
+                    "character": 0
                 },
                 "end": {
-                    "line": 55,
+                    "line": 30,
                     "character": 0
                 }
             }
         },
-        "kind": 12
+        "kind": 224
+    },
+    {
+        "name": "cell level 2",
+        "containerName": "",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 32,
+                    "character": 0
+                },
+                "end": {
+                    "line": 47,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 225
+    },
+    {
+        "name": "cell level 3",
+        "containerName": "",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 36,
+                    "character": 0
+                },
+                "end": {
+                    "line": 43,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 225
+    },
+    {
+        "name": "cell level 4",
+        "containerName": "",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 40,
+                    "character": 0
+                },
+                "end": {
+                    "line": 43,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 225
+    },
+    {
+        "name": "cell level 3-1",
+        "containerName": "",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 44,
+                    "character": 0
+                },
+                "end": {
+                    "line": 47,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 225
+    },
+    {
+        "name": "classes",
+        "containerName": "",
+        "location": {
+            "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py",
+            "range": {
+                "start": {
+                    "line": 48,
+                    "character": 0
+                },
+                "end": {
+                    "line": 61,
+                    "character": 0
+                }
+            }
+        },
+        "kind": 225
     }
 ]
\ No newline at end of file
diff --git a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py
--- a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py
+++ b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py
@@ -30,15 +30,22 @@ def func2():
 
 # ---- other functions
 
+# %%% cell level 2
 def a():
     pass
 
+# %%%% cell level 3
 def b():
     pass
 
+# %%%%% cell level 4
 def c():
     pass
 
+# %%%% cell level 3-1
+def func3():
+    pass
+
 # %% classes
 class Class1:
     def __init__(self):
diff --git a/spyder/plugins/outlineexplorer/tests/test_widgets.py b/spyder/plugins/outlineexplorer/tests/test_widgets.py
--- a/spyder/plugins/outlineexplorer/tests/test_widgets.py
+++ b/spyder/plugins/outlineexplorer/tests/test_widgets.py
@@ -5,13 +5,13 @@
 #
 
 """
-Tests for editortool.py
+Tests for the Outline explorer widgets.
 """
+
 # Standard Libray Imports
 import json
 import os.path as osp
 import sys
-from textwrap import dedent
 from unittest.mock import MagicMock
 
 # Third party imports
@@ -23,10 +23,10 @@
 from spyder.plugins.outlineexplorer.editor import OutlineExplorerProxyEditor
 from spyder.plugins.outlineexplorer.main_widget import (
     OutlineExplorerWidget, OutlineExplorerToolbuttons)
-from spyder.plugins.outlineexplorer.widgets import (
-    FileRootItem, SymbolStatus, TreeItem)
 from spyder.plugins.editor.widgets.codeeditor import CodeEditor
 
+
+# ---- Constants
 HERE = osp.abspath(osp.dirname(__file__))
 ASSETS = osp.join(HERE, 'assets')
 SUFFIX = 'test_widgets'
@@ -41,97 +41,8 @@
     for case in AVAILABLE_CASES
 }
 
-CODE = """# -*- coding: utf-8 -*-
-
-    def function0(x):
-        return x
-
-    # %% Top level 1
-    def function1(x):
-        return x
-
-    x = function1(x)
-
-    # %%% Cell Level 1-1
-    q = 3
-    w = 'word'
-
-    # %%% Cell Level 1-2
-    def function2(x):
-        def inside(x):
-            return x
-        return x
-
-    y = function2(x)
-
-    # %%%% Cell Level 2
-    class Class2(x):
-        def __init__(x):
-            return x
-        async def medthod1(x):
-            if x:
-                return x
-
-    # %%%%%% Cell level 4
-    def function4(x):
-        return x
-
-    # %%%%% Cell Level 3
-    def function5(x):
-        return x
-
-    # %%%%%%%% Cell Level 6
-    class Class3(x):
-        def __init__(x):
-            return x
-        def medthod1(x):
-            if x:
-                return x
-
-    # %% Top level 2
-    class Class4(x):
-        def __init__(x):
-            return x
-        def medthod1(x):
-            if x:
-                return x
-
-    z = Class1(x)
-
-    if __name__ == "__main__":
-
-    # %% MGroup3
-        def function6(x):
-            return x
-    # %%% MGroup4
-        x = 'test'
-    # %% Unnamed Cell
-
-    # %%%
-
-    # %% Unnamed Cell
-
-    # %% Unnamed Cell, #1
-
-    # %%% Unnamed Cell, #1
-
-    # %%
-
-    # %% a
 
-    def a():
-        pass
-
-    # %% a
-
-    # %% b
-
-    def b():
-        pass
-"""
-
-
-# ---- Qt Test Fixtures
+# ---- Fixtures
 @pytest.fixture
 def create_outlineexplorer(qtbot):
     def _create_outlineexplorer(case, follow_cursor=False):
@@ -145,6 +56,7 @@ def _create_outlineexplorer(case, follow_cursor=False):
 
         code_editor = CodeEditor(None)
         code_editor.set_language('py', filename)
+        code_editor.show()
         code_editor.set_text(text)
 
         editor = OutlineExplorerProxyEditor(code_editor, filename)
@@ -168,11 +80,12 @@ def _create_outlineexplorer(case, follow_cursor=False):
 
         editor.update_outline_info(symbol_info)
         qtbot.addWidget(outlineexplorer)
+        qtbot.addWidget(code_editor)
         return outlineexplorer, expected_tree
     return _create_outlineexplorer
 
 
-# ---- Test OutlineExplorerWidget
+# ---- Tests
 @pytest.mark.parametrize('case', AVAILABLE_CASES)
 def test_outline_explorer(case, create_outlineexplorer):
     """
@@ -185,7 +98,6 @@ def test_outline_explorer(case, create_outlineexplorer):
 
     outlineexplorer.treewidget.expandAll()
     tree_widget = outlineexplorer.treewidget
-    editor = tree_widget.current_editor
 
     root_item = tree_widget.get_top_level_items()[0]
     root_ref = root_item.ref
@@ -195,7 +107,7 @@ def test_outline_explorer(case, create_outlineexplorer):
 
     while len(stack) > 0:
         parent_tree, node = stack.pop(0)
-        this_tree = {node.name: []}
+        this_tree = {node.name: [], 'kind': node.kind}
         parent_tree.append(this_tree)
         this_stack = [(this_tree[node.name], child) for child in node.children]
         stack = this_stack + stack
@@ -212,10 +124,11 @@ def test_go_to_cursor_position(create_outlineexplorer, qtbot):
     Regression test for spyder-ide/spyder#7729.
     """
     outlineexplorer, _ = create_outlineexplorer('text')
-    # Move the mouse cursor in the editor to line 31 :
+
+    # Move the mouse cursor in the editor to line 15
     editor = outlineexplorer.treewidget.current_editor
     editor._editor.go_to_line(15)
-    assert editor._editor.get_text_line(15) == "        return 2"
+    assert editor._editor.get_text_line(14) == "    def inner():"
 
     # Click on the 'Go to cursor position' button of the outline explorer's
     # toolbar :
@@ -232,10 +145,13 @@ def test_follow_cursor(create_outlineexplorer, qtbot):
     Test that the cursor is followed.
     """
     outlineexplorer, _ = create_outlineexplorer('text', follow_cursor=True)
-    # Move the mouse cursor in the editor to line 45 :
+
+    # Move the mouse cursor in the editor to line 52
     editor = outlineexplorer.treewidget.current_editor
-    editor._editor.go_to_line(45)
-    assert editor._editor.get_text_line(45) == "        self.x = 2"
+    editor._editor.go_to_line(52)
+    assert editor._editor.get_text_line(51) == \
+           "        super(Class1, self).__init__()"
+
     # __init__ is collapsed
     assert outlineexplorer.treewidget.currentItem().text(0) == '__init__'
 
@@ -248,8 +164,10 @@ def test_follow_cursor(create_outlineexplorer, qtbot):
     editor._editor.go_to_line(1)
     text = outlineexplorer.treewidget.currentItem().text(0)
     assert text == CASES['text']['file']
+
     editor._editor.go_to_line(37)
-    assert outlineexplorer.treewidget.currentItem().text(0) == 'b'
+    assert editor._editor.get_text_line(36) == "# %%%% cell level 3"
+    assert outlineexplorer.treewidget.currentItem().text(0) == 'cell level 3'
 
 
 @flaky(max_runs=10)
@@ -298,120 +216,6 @@ def test_go_to_last_item(create_outlineexplorer, qtbot):
     assert outlineexplorer.treewidget.currentItem().text(0) == 'method1'
 
 
-@pytest.mark.skip(reason='Cell support is disabled temporarily')
-def test_code_cell_grouping(create_outlineexplorer):
-    """
-    Test to assert the outline explorer is initializing correctly and
-    is showing the expected number of items, the expected type of items, and
-    the expected text for each item. In addition this tests ancestry, code
-    cells comments, code cell grouping and disabling this feature.
-    """
-    outlineexplorer = create_outlineexplorer(dedent(CODE), 'test_file.py')
-    assert outlineexplorer
-
-    expected_results = [
-        ('test_file.py', FileRootItem),
-        ('function0', FunctionItem, 'test_file.py', 'test_file.py', False),
-        ('Top level 1', CellItem, 'test_file.py', 'test_file.py'),
-        ('function1', FunctionItem, 'Top level 1', 'test_file.py', False),
-        ('Cell Level 1-1', CellItem, 'Top level 1', 'test_file.py'),
-        ('Cell Level 1-2', CellItem, 'Top level 1', 'test_file.py'),
-        ('function2', FunctionItem, 'Cell Level 1-2', 'test_file.py', False),
-        ('inside', FunctionItem, 'function2', 'function2', False),
-        ('Cell Level 2', CellItem, 'Cell Level 1-2', 'test_file.py'),
-        ('Class2', ClassItem, 'Cell Level 2', 'test_file.py'),
-        ('__init__', FunctionItem, 'Class2', 'Class2', True),
-        ('medthod1', FunctionItem, 'Class2', 'Class2', True),
-        ('Cell level 4', CellItem, 'Cell Level 2', 'test_file.py'),
-        ('function4', FunctionItem, 'Cell level 4', 'test_file.py', False),
-        ('Cell Level 3', CellItem, 'Cell Level 1-2', 'test_file.py'),
-        ('function5', FunctionItem, 'Cell Level 3', 'test_file.py', False),
-        ('Cell Level 6', CellItem, 'Cell Level 3', 'test_file.py'),
-        ('Class3', ClassItem, 'Cell Level 6', 'test_file.py'),
-        ('__init__', FunctionItem, 'Class3', 'Class3', True),
-        ('medthod1', FunctionItem, 'Class3', 'Class3', True),
-        ('Top level 2', CellItem, 'test_file.py', 'test_file.py'),
-        ('Class4', ClassItem, 'Top level 2', 'test_file.py'),
-        ('__init__', FunctionItem, 'Class4', 'Class4', True),
-        ('medthod1', FunctionItem, 'Class4', 'Class4', True),
-        ('MGroup3', CellItem, 'test_file.py', 'test_file.py'),
-        ('function6', FunctionItem, 'MGroup3', 'MGroup3', False),
-        ('MGroup4', CellItem, 'MGroup3', 'test_file.py'),
-        ('Unnamed Cell, #2', CellItem, 'test_file.py', 'test_file.py'),
-        ('Unnamed Cell, #3', CellItem, 'Unnamed Cell, #2', 'test_file.py'),
-        ('Unnamed Cell, #4', CellItem, 'test_file.py', 'test_file.py'),
-        ('Unnamed Cell, #1, #1', CellItem, 'test_file.py', 'test_file.py'),
-        ('Unnamed Cell, #1, #2', CellItem, 'Unnamed Cell, #1, #1',
-         'test_file.py'),
-        ('Unnamed Cell, #5', CellItem, 'test_file.py', 'test_file.py'),
-        ('a, #1', CellItem, 'test_file.py', 'test_file.py'),
-        ('a', FunctionItem, 'a, #1', 'test_file.py', False),
-        ('a, #2', CellItem, 'test_file.py', 'test_file.py'),
-        ('b', CellItem, 'test_file.py', 'test_file.py'),
-        ('b', FunctionItem, 'b', 'test_file.py', False),
-        ]
-
-    outlineexplorer.treewidget.expandAll()
-    tree_widget = outlineexplorer.treewidget
-    cell_items = tree_widget.get_top_level_items() + tree_widget.get_items()
-
-    # Assert that the expected number, text, ancestry and type of cell items is
-    # displayed in the tree.
-    assert len(cell_items) == len(expected_results)
-    for item, expected_result in zip(cell_items, expected_results):
-        assert item.text(0) == expected_result[0]
-        assert type(item) == expected_result[1]
-        if type(item) != FileRootItem:
-            assert item.parent().text(0) == expected_result[2]
-        if type(item) == FunctionItem:
-            assert item.is_method() == expected_result[4]
-
-    # Disable cell groups
-    tree_widget.toggle_group_cells(False)
-    tree_widget.expandAll()
-    flat_items = tree_widget.get_top_level_items() + tree_widget.get_items()
-
-    # Assert that the expected number, text, ancestry and type of flat items is
-    # displayed in the tree.
-    assert len(flat_items) == len(expected_results)
-    for item, expected_result in zip(flat_items, expected_results):
-        assert item.text(0) == expected_result[0]
-        assert type(item) == expected_result[1]
-        if type(item) != FileRootItem:
-            assert item.parent().text(0) == expected_result[3]
-        if type(item) == FunctionItem:
-            assert item.is_method() == expected_result[4]
-
-    # Change back to cell groups
-    tree_widget.toggle_group_cells(True)
-    tree_widget.expandAll()
-    cell_items2 = tree_widget.get_top_level_items() + tree_widget.get_items()
-
-    # Assert that the expected number, text, ancestry and type of flat items is
-    # displayed in the tree.
-    assert len(cell_items2) == len(expected_results)
-    for item, expected_result in zip(cell_items2, expected_results):
-        assert item.text(0) == expected_result[0]
-        assert type(item) == expected_result[1]
-        if type(item) != FileRootItem:
-            assert item.parent().text(0) == expected_result[2]
-        if type(item) == FunctionItem:
-            assert item.is_method() == expected_result[4]
-
-# Code used to create expected_results
-# =============================================================================
-#     for item in cell_items2:
-#         if type(item) == FunctionItem:
-#             print(f"('{item.text(0)}', {type(item).__name__}, "
-#                   f"'{item.parent().text(0)}', {item.is_method()}),")
-#         elif type(item) == FileRootItem:
-#             print(f"('{item.text(0)}', {type(item).__name__}),")
-#         else:
-#             print(f"('{item.text(0)}', {type(item).__name__}, "
-#                   f"'{item.parent().text(0)}'),")
-# =============================================================================
-
-
 if __name__ == "__main__":
     import os
     pytest.main(['-x', os.path.basename(__file__), '-v', '-rw'])