Repository: spyder-ide/spyder

Issue:
Changing the filename filters doesn't update files shown if filters are enabled until the button is toggled off and back on
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

## Problem Description

Originally discussed on spyder-ide/spyder-docs#146

If filters are enabled in the Files pane (the button is toggled), changing the extensions in the dialog doesn't update the filter accordingly; the button has to be manually toggled on and off again to update them, which is quite unintuitive and may cause the user some frustration unless and until they figure out why there changes aren't being reflected in the filtered output. Therefore, the OK button should send a signal that's connected to whatever slot that triggers an update of the filters (or simply toggles them off and back on, if there isn't a separate function to update them).

### What steps reproduce the problem?

1. In the files pane, Browse to a directory containing files included in the default filename filters
2. Enable the filtering by clicking the toggle in the toolbar
3. Remove one of the patterns (i.e. extensions) in the `Edit filename filters` dialog under the Files pane options that match one of the files shown
4. Click OK in the dialog. The files matching the removed pattern are still shown.
5. Toggle filters off and on again with the toolbar button, and now the files are hidden

### What is the expected output? What do you see instead?

Expected that clicking OK in the dialog actually updates the files shown to reflect the new patterns, but in fact it requires the user manually "turn it on and off again".

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 4.2.0
* Python version: 3.7.9
* Qt version: 5.9.7
* PyQt version: 5.9.2
* Operating System name/version: Windows 8.1 x64



Discussion:

Resolving patch:
diff --git a/spyder/plugins/explorer/widgets/explorer.py b/spyder/plugins/explorer/widgets/explorer.py
--- a/spyder/plugins/explorer/widgets/explorer.py
+++ b/spyder/plugins/explorer/widgets/explorer.py
@@ -154,6 +154,7 @@ class DirView(QTreeView):
     sig_new_file = Signal(str)
     sig_open_interpreter = Signal(str)
     redirect_stdio = Signal(bool)
+    sig_update_filters = Signal()
 
     def __init__(self, parent=None):
         super(DirView, self).__init__(parent)
@@ -416,6 +417,7 @@ def handle_ok():
             self.parent_widget.sig_option_changed.emit(
                 'name_filters', filter_text)
             self.set_name_filters(filter_text)
+            self.sig_update_filters.emit()
             dialog.accept()
 
         def handle_reset():
@@ -1786,6 +1788,7 @@ def __init__(self, parent=None, name_filters=['*.py', '*.pyw'],
         self.treewidget.set_previous_enabled.connect(
                                                previous_action.setEnabled)
         self.treewidget.set_next_enabled.connect(next_action.setEnabled)
+        self.treewidget.sig_update_filters.connect(self.filter_files)
         self.sig_option_changed.connect(self.refresh_actions)
 
     def change_filter_state(self):
@@ -1796,6 +1799,11 @@ def change_filter_state(self):
             _("Deactivate filename filters") if self.filter_on else _(
                 "Activate filename filters"))
         self.filter_button.setToolTip(tip_message)
+        self.filter_files()
+
+    @Slot()
+    def filter_files(self):
+        """Update filters in the tree widget."""
         self.treewidget.filter_files(self.filter_on)
 
     def refresh_actions(self, option, value):


Test patch:
diff --git a/spyder/plugins/explorer/widgets/tests/test_explorer.py b/spyder/plugins/explorer/widgets/tests/test_explorer.py
--- a/spyder/plugins/explorer/widgets/tests/test_explorer.py
+++ b/spyder/plugins/explorer/widgets/tests/test_explorer.py
@@ -17,7 +17,8 @@
 import pytest
 from qtpy.QtCore import QEvent, QPoint, Qt, QTimer
 from qtpy.QtGui import QMouseEvent
-from qtpy.QtWidgets import QApplication, QInputDialog, QMenu, QMessageBox
+from qtpy.QtWidgets import (QApplication, QDialog, QDialogButtonBox,
+                            QInputDialog, QMenu, QMessageBox, QTextEdit)
 
 # Local imports
 from spyder.plugins.explorer.widgets.explorer import (FileExplorerTest,
@@ -27,10 +28,14 @@
 from spyder.py3compat import PY2
 
 
+HERE = osp.abspath(osp.dirname(__file__))
+
+
 @pytest.fixture
 def file_explorer(qtbot):
     """Set up FileExplorerTest."""
     widget = FileExplorerTest()
+    widget.show()
     qtbot.addWidget(widget)
     return widget
 
@@ -259,7 +264,6 @@ def test_get_file_associations(qtbot, file_explorer_associations):
 
 
 @pytest.mark.first
-@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!')
 def test_create_file_manage_actions(qtbot, file_explorer_associations,
                                     tmp_path):
     widget = widget = file_explorer_associations.explorer.treewidget
@@ -290,7 +294,6 @@ def test_create_file_manage_actions(qtbot, file_explorer_associations,
 
 
 @pytest.mark.first
-@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!')
 def test_clicked(qtbot, file_explorer_associations, tmp_path):
     widget = file_explorer_associations.explorer.treewidget
     some_dir = tmp_path / 'some_dir'
@@ -323,7 +326,6 @@ def interact_2():
 
 
 @pytest.mark.first
-@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!')
 def test_check_launch_error_codes(qtbot, file_explorer_associations):
     widget = file_explorer_associations.explorer.treewidget
 
@@ -355,7 +357,6 @@ def interact_2():
 
 
 @pytest.mark.first
-@pytest.mark.skipif(os.name == 'nt' and PY2, reason='Fails on win and py2!')
 def test_open_association(qtbot, file_explorer_associations, tmp_path):
     widget = file_explorer_associations.explorer.treewidget
     some_dir = tmp_path / 'some_dir'
@@ -375,5 +376,52 @@ def interact():
     widget.open_association('some-app')
 
 
+@pytest.mark.first
+def test_update_filters(file_explorer, qtbot):
+    """
+    Test that view is updated if the filter button is activated and
+    filters are changed.
+
+    This is a regression test for spyder-ide/spyder#14328
+    """
+    explorer = file_explorer.explorer
+    widget = file_explorer.explorer.treewidget
+
+    # Assert explorer.py is present (in case we rename it)
+    explorer_file = osp.join(osp.dirname(HERE), 'explorer.py')
+    assert osp.isfile(explorer_file)
+
+    # Assert explorer.py is in view before applying the new filters
+    idx0 = widget.get_index(explorer_file)
+    assert idx0.isValid()
+
+    # Activate filters
+    explorer.filter_button.clicked.emit()
+
+    # Auxiliary function to interact with the filters dialog.
+    def interact():
+        dlg = widget.findChild(QDialog)
+        assert dlg
+
+        # Change filters
+        filters = dlg.findChild(QTextEdit)
+        filters.setPlainText('*.png')
+
+        # Apply settings
+        button_box = dlg.findChild(QDialogButtonBox)
+        button_box.button(QDialogButtonBox.Ok).clicked.emit()
+
+    # Edit filters
+    _ = create_timer(interact)
+    widget.edit_filter()
+
+    # Wait for filters to be applied
+    qtbot.wait(1000)
+
+    # Assert explorer.py is not view.
+    idx1 = widget.get_index(explorer_file)
+    assert not idx1.isValid()
+
+
 if __name__ == "__main__":
     pytest.main()