Yet more tests.

This commit is contained in:
Dan Helfman
2026-05-29 11:52:02 -07:00
parent e82c1bb195
commit a876ce2058
10 changed files with 234 additions and 23 deletions
+14
View File
@@ -10,9 +10,18 @@ import borgmatic.actions.browse.workers
class Archives_list(textual.widgets.OptionList):
'''
A widget for selecting a single Borg archive from among the archives in a repository. The item
selection event is handled in a Carousel instance, the parent widget of an Archives_list.
'''
BINDINGS = borgmatic.actions.browse.bindings.OPTION_LIST_BINDINGS
def __init__(self, config, repository):
'''
Given a configuration dict and a repository dict, start loading the archives from the
repository for eventual display in this widget.
'''
self.config = config
self.repository = repository
@@ -31,5 +40,10 @@ class Archives_list(textual.widgets.OptionList):
)
def on_option_list_option_highlighted(self, event):
'''
When the highlighted option changes, record that fact. This flag is consumed in
borgmatic.actions.browse.workers.add_repository_archives() in order to retain the
highlighted option even as other options load around it.
'''
if self.highlighted not in {None, 0}:
self.highlighted_option_changed = True
+19 -11
View File
@@ -14,11 +14,14 @@ import borgmatic.actions.browse.workers
def get_relative_archive_path_components(archive_path, current_directory_path_components):
'''
Given an Archive_path instance and a tuple of path components for the currently browsed
directory, get the path components for the archive path relative to that directory.
directory, get the path components as a tuple for the archive path relative to that directory.
If the archive path is not actually relative to that directory, return None.
For instance, given an archive path with a path of 'foo/bar/baz/quux.txt' and current
directory path components of ('foo', 'bar'), return ('baz', 'quux.txt').
If the archive path is not actually relative to the current directory, return None.
'''
archive_path_components = archive_path.file_path.split(os.path.sep)
archive_path_components = tuple(archive_path.file_path.split(os.path.sep))
if not current_directory_path_components:
return archive_path_components
@@ -35,15 +38,20 @@ def get_relative_archive_path_components(archive_path, current_directory_path_co
return archive_path_components[len(current_directory_path_components) :]
def make_directory_list_option(archive_path, archive_path_components):
def make_directory_list_option(archive_path, relative_path_components):
'''
Given an Archive_path instance and a tuple of relative path components for it, make a
textual.widgets.option_list.Option for the path. Use an the icon based on whether this looks
like a terminal filename or a directory.
'''
pieces = (
borgmatic.actions.browse.icons.PATH_TYPE_ICONS.get(
archive_path.path_type if len(archive_path_components) == 1 else 'd', ''
archive_path.path_type if len(relative_path_components) == 1 else 'd', ''
),
archive_path_components[0],
relative_path_components[0],
) + (('', archive_path.link_target) if archive_path.link_target else ())
return textual.widgets.option_list.Option(' '.join(pieces), id=archive_path_components[0])
return textual.widgets.option_list.Option(prompt=' '.join(pieces), id=relative_path_components[0])
def add_archive_paths(
@@ -60,16 +68,16 @@ def add_archive_paths(
(
*directory_list.options,
*(
make_directory_list_option(archive_path, archive_path_components)
make_directory_list_option(archive_path, relative_path_components)
for archive_path in archive_paths
for archive_path_components in (
for relative_path_components in (
get_relative_archive_path_components(
archive_path,
directory_list.path_components,
),
)
if archive_path_components
if not archive_path_components[0] in directory_list._id_to_option
if relative_path_components
if not relative_path_components[0] in directory_list._id_to_option
),
),
key=lambda option: ((option.id == 'loading-indicator'), option.prompt),
+9 -11
View File
@@ -4,13 +4,15 @@ import os
import textual.binding
import textual.widgets
import borgmatic.actions.browse.archive
import borgmatic.actions.browse.loading
import borgmatic.actions.browse.icons
import borgmatic.actions.browse.workers
class File_preview(textual.widgets.RichLog):
'''
A widget for extracting and previewing the contents of a file stored in a Borg archive.
'''
BINDINGS = [
*textual.widgets.RichLog.BINDINGS,
textual.binding.Binding(
@@ -28,21 +30,17 @@ class File_preview(textual.widgets.RichLog):
]
def __init__(self, config, repository, archive_name, file_path):
'''
Given a configuration dict, a repository dict, an archive name, and the path of a file in
the archive, start loading the file's contents for eventual display in this widget.
'''
self.config = config
self.repository = repository
self.archive_name = archive_name
self.file_path = file_path
super().__init__(classes='panel')
self.border_title = ' '.join(
(
borgmatic.actions.browse.icons.PATH_TYPE_ICONS[
borgmatic.actions.browse.archive.Path_type.FILE.value
],
self.file_path,
'preview',
)
)
self.border_title = ' '.join(('📄', self.file_path, 'preview'))
self.auto_scroll = False
timer = borgmatic.actions.browse.loading.add_inline_loading_indicator(self)
@@ -10,9 +10,18 @@ import borgmatic.actions.browse.workers
class Repositories_list(textual.widgets.OptionList):
'''
A widget for selecting a single Borg repository from among the repositories in a borgmatic
configuratin file. The item selection event is handled in a Carousel instance, the parent widget
of a Repositories_list.
'''
BINDINGS = borgmatic.actions.browse.bindings.OPTION_LIST_BINDINGS
def __init__(self, config):
'''
Given a configuration dict, populate the repositories in this widget.
'''
self.config = config
self.repositories = config['repositories']
+2
View File
@@ -34,6 +34,8 @@ def add_repository_archives(browse_app, archives_list, config, repository, timer
loading_option,
),
)
# Retain the highlighted option position even as other options load around it.
archives_list.highlighted = (
archives_list.get_option_index(highlighted_option.id)
if highlighted_option and archives_list.highlighted_option_changed
@@ -0,0 +1,63 @@
from borgmatic.actions.browse import archives_list as module
from flexmock import flexmock
import textual.widgets.option_list
def test_archives_list_on_option_list_option_highlighted_with_highlighted_none_marks_it_unchanged():
flexmock(module.borgmatic.actions.browse.loading).should_receive('add_inline_loading_indicator')
flexmock(module.borgmatic.actions.browse.workers).should_receive('add_repository_archives')
flexmock(module.borgmatic.actions.browse.archives_list.Archives_list).should_receive(
'app'
).and_return(flexmock())
archives_list = module.Archives_list(config=flexmock(), repository=flexmock())
archives_list.highlighted = None
archives_list.on_option_list_option_highlighted(event=flexmock())
assert archives_list.highlighted_option_changed is False
def test_archives_list_on_option_list_option_highlighted_with_highlighted_zero_marks_it_unchanged():
flexmock(module.borgmatic.actions.browse.loading).should_receive('add_inline_loading_indicator')
flexmock(module.borgmatic.actions.browse.workers).should_receive('add_repository_archives')
flexmock(module.borgmatic.actions.browse.archives_list.Archives_list).should_receive(
'app'
).and_return(flexmock())
archives_list = module.Archives_list(config=flexmock(), repository=flexmock())
archives_list.highlighted = 0
archives_list.on_option_list_option_highlighted(event=flexmock())
assert archives_list.highlighted_option_changed is False
def test_archives_list_on_option_list_option_highlighted_with_highlighted_zero_marks_it_unchanged():
flexmock(module.borgmatic.actions.browse.loading).should_receive('add_inline_loading_indicator')
flexmock(module.borgmatic.actions.browse.workers).should_receive('add_repository_archives')
flexmock(module.borgmatic.actions.browse.archives_list.Archives_list).should_receive(
'app'
).and_return(flexmock())
archives_list = module.Archives_list(config=flexmock(), repository=flexmock())
archives_list.add_option(textual.widgets.option_list.Option('zero', id='zero'))
archives_list.highlighted = 0
archives_list.on_option_list_option_highlighted(event=flexmock())
assert archives_list.highlighted_option_changed is False
def test_archives_list_on_option_list_option_highlighted_with_highlighted_non_zero_marks_it_changed():
flexmock(module.borgmatic.actions.browse.loading).should_receive('add_inline_loading_indicator')
flexmock(module.borgmatic.actions.browse.workers).should_receive('add_repository_archives')
flexmock(module.borgmatic.actions.browse.archives_list.Archives_list).should_receive(
'app'
).and_return(flexmock())
archives_list = module.Archives_list(config=flexmock(), repository=flexmock())
archives_list.add_option(textual.widgets.option_list.Option('zero', id='zero'))
archives_list.add_option(textual.widgets.option_list.Option('one', id='one'))
archives_list.highlighted = 1
archives_list.on_option_list_option_highlighted(event=flexmock())
assert archives_list.highlighted_option_changed is True
@@ -0,0 +1,14 @@
from borgmatic.actions.browse import file_preview as module
from flexmock import flexmock
import textual.widgets.option_list
def test_file_preview_does_not_raise():
flexmock(module.borgmatic.actions.browse.loading).should_receive('add_inline_loading_indicator')
flexmock(module.borgmatic.actions.browse.workers).should_receive('load_file_preview')
flexmock(module.borgmatic.actions.browse.repositories_list.Repositories_list).should_receive(
'app'
).and_return(flexmock())
module.Repositories_list(config=flexmock())
@@ -0,0 +1,14 @@
from borgmatic.actions.browse import repositories_list as module
from flexmock import flexmock
def test_repositories_list_populates_options():
config = {'repositories': [{'path': 'test1.borg'}, {'path': 'test2.borg', 'label': 'two'}]}
repositories_list = module.Repositories_list(config=config)
assert len(repositories_list.options) == 2
assert repositories_list.options[0].prompt == 'test1.borg'
assert repositories_list.options[0].id == 0
assert repositories_list.options[1].prompt == 'two'
assert repositories_list.options[1].id == 1
@@ -0,0 +1,90 @@
from borgmatic.actions.browse import directory_list as module
from flexmock import flexmock
def test_get_relative_archive_path_components_strips_off_current_directory():
assert module.get_relative_archive_path_components(
flexmock(file_path='foo/bar/baz/quux.txt'), ('foo', 'bar')
) == ('baz', 'quux.txt')
def test_get_relative_archive_path_components_with_root_current_directory_strips_off_nothing():
assert module.get_relative_archive_path_components(
flexmock(file_path='foo/bar/baz/quux.txt'), ()
) == ('foo', 'bar', 'baz', 'quux.txt')
def test_get_relative_archive_path_components_with_non_matching_paths_returns_none():
assert (
module.get_relative_archive_path_components(
flexmock(file_path='foo/bar/baz/quux.txt'), ('etc',)
)
is None
)
def test_make_directory_list_option_with_file_path_makes_file_option():
flexmock(module.textual.widgets.option_list).should_receive('Option').replace_with(flexmock)
option = module.make_directory_list_option(
flexmock(path_type='-', file_path='foo/bar/baz.txt', link_target=''), ('baz.txt',)
)
assert option.prompt == '📄 baz.txt'
assert option.id == 'baz.txt'
def test_make_directory_list_option_with_directory_path_makes_directory_option():
flexmock(module.textual.widgets.option_list).should_receive('Option').replace_with(flexmock)
option = module.make_directory_list_option(
flexmock(path_type='d', file_path='foo/bar/baz', link_target=''), ('baz',)
)
assert option.prompt == '📁 baz'
assert option.id == 'baz'
def test_make_directory_list_option_with_contained_file_path_makes_directory_option():
flexmock(module.textual.widgets.option_list).should_receive('Option').replace_with(flexmock)
option = module.make_directory_list_option(
flexmock(path_type='d', file_path='foo/bar/baz.txt', link_target=''), ('bar', 'baz.txt',)
)
assert option.prompt == '📁 bar'
assert option.id == 'bar'
def test_make_directory_list_option_with_link_path_makes_link_option():
flexmock(module.textual.widgets.option_list).should_receive('Option').replace_with(flexmock)
option = module.make_directory_list_option(
flexmock(path_type='l', file_path='foo/bar/baz.txt', link_target='quux.txt'), ('baz.txt',)
)
assert option.prompt == '🔗 baz.txt → quux.txt'
assert option.id == 'baz.txt'
def test_make_directory_list_option_with_pipe_path_makes_pipe_option():
flexmock(module.textual.widgets.option_list).should_receive('Option').replace_with(flexmock)
option = module.make_directory_list_option(
flexmock(path_type='p', file_path='foo/bar/baz.txt', link_target=''), ('baz.txt',)
)
assert option.prompt == '🚰 baz.txt'
assert option.id == 'baz.txt'
def test_make_directory_list_option_with_unknown_path_makes_unknown_option():
flexmock(module.textual.widgets.option_list).should_receive('Option').replace_with(flexmock)
option = module.make_directory_list_option(
flexmock(path_type='wtf', file_path='foo/bar/baz.txt', link_target=''), ('baz.txt',)
)
assert option.prompt == '❓ baz.txt'
assert option.id == 'baz.txt'
-1
View File
@@ -2132,7 +2132,6 @@ def test_collect_highlander_action_summary_logs_error_on_run_browse_failure():
assert {log.levelno for log in logs} == {logging.CRITICAL}
def test_collect_configuration_run_summary_logs_info_for_success():
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
flexmock(module.command).should_receive('filter_hooks').with_args(