From 23a402ada12c971b8b5216ae3d24e4efaae5976b Mon Sep 17 00:00:00 2001 From: Alistair-Afton Date: Wed, 16 Sep 2026 18:52:12 +0200 Subject: [PATCH] `sort`: apply justice filters to the visible cases tab get_unit_list() looked up the "Open cases" unit list by name first, but named widget lookup finds hidden tabs too, so the filter was always installed on (and refreshes poked) the hidden list while the Cold cases tab was shown. Iterate the Tabs children and use the tab flagged VISIBILITY_VISIBLE, matching Gui.cpp's focus-string logic. Fixes #5577 --- docs/changelog.txt | 1 + plugins/lua/sort/info.lua | 12 +++-- test/plugins/sort.lua | 111 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 test/plugins/sort.lua diff --git a/docs/changelog.txt b/docs/changelog.txt index 78e8a56cc3..18dec0f08a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -68,6 +68,7 @@ Template for new versions: - `3dveins`: fix failure on embarks that cross midmap tiles - `aquifer`: make ``--skip-top`` and top-relative ``--levels`` take effect for the ``drain``, ``convert``, and ``add`` actions instead of being silently ignored - `autodump`: ``destroy`` no longer leaves the contents of destroyed containers in limbo or crashes when destroying unit-held items +- `sort`: fix justice filters applying to the hidden "Open cases" unit list when the "Cold cases" tab is active - `timestream`: deal properly with units who have breathing difficulties ## Misc Improvements diff --git a/plugins/lua/sort/info.lua b/plugins/lua/sort/info.lua index 4f0462dfb7..84ec2ba057 100644 --- a/plugins/lua/sort/info.lua +++ b/plugins/lua/sort/info.lua @@ -611,12 +611,18 @@ end local function get_unit_list(which) local tabs = dfhack.gui.getWidget(justice, 'Tabs') - return dfhack.gui.getWidget(tabs, 'Open cases', 'Right panel', which) or - dfhack.gui.getWidget(tabs, 'Cold cases', 'Right panel', which) + for _,tab in ipairs(dfhack.gui.getWidgetChildren(tabs)) do + if tab.flag.VISIBILITY_VISIBLE then + return dfhack.gui.getWidget(tab, 'Right panel', which) + end + end end local function poke_list(which) - get_unit_list(which).sort_flags.NEEDS_RESORTED = true + local list = get_unit_list(which) + if list then + list.sort_flags.NEEDS_RESORTED = true + end end JusticeOverlay = defclass(JusticeOverlay, overlay.OverlayWidget) diff --git a/test/plugins/sort.lua b/test/plugins/sort.lua new file mode 100644 index 0000000000..a64be615fa --- /dev/null +++ b/test/plugins/sort.lua @@ -0,0 +1,111 @@ +config.mode = 'fortress' +config.target = 'sort' + +local info = require('plugins.sort.info') +local sort = require('plugins.sort') + +local overlay_widgets = package.loaded['plugins.overlay'] + +local mocked_tabs + +local function make_list() + return {sort_flags={NEEDS_RESORTED=false}} +end + +local function make_tab(visible) + local lists = { + Interrogate=make_list(), + Convict=make_list(), + } + return { + flag={VISIBILITY_VISIBLE=visible}, + children_by_name={ + ['Right panel']={children_by_name=lists}, + }, + }, lists +end + +local function make_widget_tree(cold_visible) + local open_tab, open_lists = make_tab(not cold_visible) + local cold_tab, cold_lists = make_tab(cold_visible) + mocked_tabs = { + children={open_tab, cold_tab}, + children_by_name={ + ['Open cases']=open_tab, + ['Cold cases']=cold_tab, + }, + } + return open_lists, cold_lists +end + +local function get_widget(parent, ...) + local widget = parent + for _,key in ipairs{...} do + if type(widget) == 'table' then + widget = widget.children_by_name[key] + else + -- real df container: only the top-level justice Tabs lookup is mocked + widget = key == 'Tabs' and mocked_tabs or nil + end + if widget == nil then return nil end + end + return widget +end + +local function get_widget_children(container) + return type(container) == 'table' and container.children or {} +end + +local function with_mocks(fn, extra_patches) + local patches = { + {dfhack.gui, 'getWidget', get_widget}, + {dfhack.gui, 'getWidgetChildren', get_widget_children}, + {info, 'interrogate_instance', info.interrogate_instance}, + {info, 'convict_instance', info.convict_instance}, + } + for _,patch in ipairs(extra_patches or {}) do + table.insert(patches, patch) + end + mock.patch(patches, fn) +end + +function test.justice_filter_pokes_visible_cold_cases_list() + local open_lists, cold_lists = make_widget_tree(true) + with_mocks(function() + info.InterrogationOverlay{}.subviews.subset.on_change() + end) + expect.false_(open_lists.Interrogate.sort_flags.NEEDS_RESORTED) + expect.true_(cold_lists.Interrogate.sort_flags.NEEDS_RESORTED) +end + +function test.justice_filter_pokes_visible_open_cases_list() + local open_lists, cold_lists = make_widget_tree(false) + with_mocks(function() + info.InterrogationOverlay{}.subviews.subset.on_change() + end) + expect.true_(open_lists.Interrogate.sort_flags.NEEDS_RESORTED) + expect.false_(cold_lists.Interrogate.sort_flags.NEEDS_RESORTED) +end + +function test.justice_conviction_pokes_visible_cold_cases_list() + local open_lists, cold_lists = make_widget_tree(true) + with_mocks(function() + info.ConvictionOverlay{}.subviews.subset.on_change() + end) + expect.false_(open_lists.Convict.sort_flags.NEEDS_RESORTED) + expect.true_(cold_lists.Convict.sort_flags.NEEDS_RESORTED) +end + +function test.justice_filter_installed_on_visible_list() + local _, cold_lists = make_widget_tree(true) + local passed_list + local set_filter = mock.observe_func(function(list) passed_list = list end) + with_mocks(function() + info.InterrogationOverlay{}:render() + end, { + {sort, 'sort_set_justice_filter_fn', set_filter}, + {overlay_widgets.OverlayWidget, 'render', mock.func()}, + }) + expect.eq(1, set_filter.call_count) + expect.eq(cold_lists.Interrogate, passed_list) +end