Add settings search

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-10-06 18:49:44 +08:00
parent 364ba76809
commit e8d5b342fd
5 changed files with 298 additions and 7 deletions
+1 -1
View File
@@ -114,7 +114,7 @@ worker. Choose tests by the changed contract rather than by filename alone.
| [test_main_window_geometry.py](test_main_window_geometry.py) | First-show lifecycle, restored/default/legacy geometry, main/routing window reuse, session-only navigation state, protection of never-shown windows' saved geometry. |
| [test_dialog_geometry.py](test_dialog_geometry.py) | Dialog show/open/exec preparation, sizing/centering, failure cleanup, specialized message-box geometry and transient destruction. |
| [test_layout_matrix.py](test_layout_matrix.py) | Navigation and message-box layouts in fresh processes at scale factors 1, 1.25, 1.5, and 2, under both themes. |
| [test_ui_behavior.py](test_ui_behavior.py) | Translation and editor mappings, settings organization, stable server/routing moves, real QR rendering/decoding, incremental log filtering/tail behavior, connected-routing change notices, message boxes and connection-error copy/log navigation, shared Home/tray state. |
| [test_ui_behavior.py](test_ui_behavior.py) | Translation and editor mappings, settings organization/search/platform gates, stable server/routing moves, real QR rendering/decoding, incremental log filtering/tail behavior, connected-routing change notices, message boxes and connection-error copy/log navigation, shared Home/tray state. |
| [test_qt_interactions.py](test_qt_interactions.py) | Real keyboard/mouse/focus and proxy mapping, scoped shortcuts, sorting/selection, stable subscription deletion confirmations, debounced Home search, Tests-menu selection color, shared settings, batched profile mutation and cancellation. |
| [test_qr_export_scalability.py](test_qr_export_scalability.py) | Production capture cap, immediate single export, incremental yielding, failure/cancel/close paths, immutable snapshots, window-owned state destruction. |
| [test_stylesheet_states.py](test_stylesheet_states.py) | Targeted rendering/alpha/geometry assertions for table/list insets, popup corners, clear buttons, focus/disabled states, and stylesheet composition. |
+135 -3
View File
@@ -89,7 +89,12 @@ from Furious.Models import (
Protocol,
ServerProfile,
)
from Furious.Plugins.API import RoutingOption
from Furious.Plugins.API import (
PluginSettingControl,
PluginSettingDescriptor,
PluginSettingsSection,
RoutingOption,
)
from Furious.Repository import Storage
from Furious.Repository.Routings import UserRoutings
from Furious.Qt import (
@@ -388,7 +393,9 @@ class SettingsPageOrganizationTest(unittest.TestCase):
"""Finish deferred Settings-page deletion between tests."""
collectAtBoundary()
def buildPage(self, *, platform='Windows', flatpakID='', isAdmin=False):
def buildPage(
self, *, platform='Windows', flatpakID='', isAdmin=False, pluginSections=()
):
"""Build one isolated Settings page for the requested platform state."""
callbacks = {
'checkForUpdates': mock.Mock(),
@@ -399,6 +406,10 @@ class SettingsPageOrganizationTest(unittest.TestCase):
proxyBypassDialog = mock.Mock()
networkTestDialog = mock.Mock()
def buildPluginSections(page):
for section in pluginSections:
page._addPluginDescriptorSection(section)
with (
isolatedSettings(),
mock.patch('Furious.Window.SettingsPage.PLATFORM', platform),
@@ -418,7 +429,9 @@ class SettingsPageOrganizationTest(unittest.TestCase):
'Furious.Window.SettingsPage.AppSettings.isStateON_',
return_value=False,
),
mock.patch.object(SettingsPage, '_buildPluginSections'),
mock.patch.object(
SettingsPage, '_buildPluginSections', buildPluginSections
),
mock.patch(
'Furious.Window.SettingsPage.AppSettingsController'
) as controllerFactory,
@@ -436,6 +449,125 @@ class SettingsPageOrganizationTest(unittest.TestCase):
return page, callbacks
def testSearchMatchesDescriptionsAndSectionsWithoutApplyingSettings(self):
with isolatedSettings():
AppSettings.set('Language', 'EN')
page, callbacks = self.buildPage()
page.show()
page.setConnectionControlsEnabled(False)
processQtEvents()
page.searchLineEdit.setText('OPERATING system')
QTest.keyClick(page.searchLineEdit, QtCore.Qt.Key.Key_Return)
processQtEvents()
self.assertFalse(page.systemProxyCard.isHidden())
self.assertTrue(page.generalSection.isHidden())
self.assertTrue(page.tunSection.isHidden())
self.assertFalse(page.systemProxyCard.comboBox.isEnabled())
self.assertFalse(page.emptySearchLabel.isVisible())
self.assertTrue(
all(callback.call_count == 0 for callback in callbacks.values())
)
page.searchLineEdit.setText('TUN')
page.searchLineEdit.applySearch()
self.assertTrue(all(not card.isHidden() for card in page.tunSection.cards))
self.assertFalse(page.tunBackendCard.isEnabled())
page.searchLineEdit.setText('[')
page.searchLineEdit.applySearch()
self.assertTrue(page.emptySearchLabel.isVisible())
page.searchLineEdit.clear()
self.assertFalse(page.emptySearchLabel.isVisible())
self.assertTrue(
all(not section.isHidden() for section in page._searchSections)
)
page.close()
page.deleteLater()
def testSearchIncludesPluginMetadataButNotControlValuesOrUnavailableSections(self):
callback = mock.Mock()
descriptor = PluginSettingsSection(
'fixture',
'Vendor [Example]',
(
PluginSettingDescriptor(
'diagnostics',
'Inspect endpoint',
description='Collect local diagnostics',
control=PluginSettingControl.Action,
callback=callback,
buttonText='private-control-value',
),
),
)
page, _callbacks = self.buildPage(
platform='Linux',
flatpakID='io.github.LorenEteval.Furious',
pluginSections=(descriptor,),
)
page.show()
processQtEvents()
page.searchLineEdit.setText('vendor [example]')
page.searchLineEdit.applySearch()
self.assertTrue(page.pluginSettingsTitleLabel.isVisible())
self.assertTrue(page.pluginSections[0].isVisible())
self.assertTrue(page.tunSection.isHidden())
page.searchLineEdit.setText('private-control-value')
page.searchLineEdit.applySearch()
self.assertTrue(page.emptySearchLabel.isVisible())
callback.assert_not_called()
page.searchLineEdit.clear()
self.assertTrue(page.tunSection.isHidden())
self.assertTrue(page.pluginSections[0].isVisible())
page.close()
page.deleteLater()
def testSearchFindShortcutLanguageSwitchAndHiddenDebounce(self):
with isolatedSettings():
AppSettings.set('Language', 'EN')
page, _callbacks = self.buildPage()
page.show()
page.activateWindow()
page.aboutCard.button.setFocus()
processQtEvents()
QTest.keySequence(page.aboutCard.button, page.findAction.shortcut())
self.assertTrue(page.searchLineEdit.hasFocus())
QTest.keyClicks(page.searchLineEdit, 'metrics')
self.assertTrue(page.searchLineEdit._searchTimer.isActive())
page.hide()
self.assertFalse(page.searchLineEdit._searchTimer.isActive())
page.show()
processQtEvents()
self.assertTrue(page.metricsCollectionCard.isVisible())
self.assertTrue(page.generalSection.isHidden())
AppSettings.set('Language', 'ZH')
Mixins.QTranslatable.retranslateAll()
processQtEvents()
page.searchLineEdit.setText(_('Enable Metrics Collection'))
page.searchLineEdit.applySearch()
self.assertTrue(page.metricsCollectionCard.isVisible())
page.searchLineEdit.setText('Enable Metrics Collection')
page.searchLineEdit.applySearch()
self.assertTrue(page.metricsCollectionCard.isVisible())
page.close()
page.deleteLater()
AppSettings.set('Language', 'EN')
Mixins.QTranslatable.retranslateAll()
def testGeneralEndsWithSystemAndEnvironmentActions(self):
"""Keep preferences first and Application focused on maintenance/about."""
page, _callbacks = self.buildPage()