aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_search_files_unmerged.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-02 16:21:48 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-02 16:21:48 +0200
commit10f8266e7152d7dc38dbfe2449327829bf020ad1 (patch)
tree5e72ac45b2e79008812663e251a4fcab62dc0650 /packages/meshbay-hub/tests/test_search_files_unmerged.py
parent313b72f15e8788ba3abcd3e44b5f7785fbc779fe (diff)
downloadmeshbay-10f8266e7152d7dc38dbfe2449327829bf020ad1.tar.gz
fix(hub): merge duplicate sources in Search's Music and Photos too
Phases 5-8 of docs/refactoring-search.md, extending the Videos merge outward. A library shared by two groups now lists each track once inside an album and each photo once inside a photo album, and a card served by several groups says "N sources" instead of naming one of them. Units come from each application's own grouping, never a copy of its keys. For Music that meant exporting foldKey: groupMusicEntries folds case to group but keeps the first-seen spelling to display, and which group is seen first is whichever index arrived first — so keying a unit on the display strings would let the chosen source change between page loads. A group whose connection fails is marked down and stops being chosen, so a unit fails over to another group that has the file. Eviction is not a failure. Every source being down still yields an entry: a tile that fails to load beats a film that vanished from the grid. sourceLabel now takes the whole unit rather than one entry. A show's poster entry is picked for its thumbnail, so a show in two groups whose cover episode sits in only one of them would have claimed a single source. SourceTag lives in group-name.js — source-merge.js must keep importing nothing (its test executes it standalone), and a copy in each of the three apps is three chances to disagree. test_search_files_unmerged.py holds the one thing that must not change: the Files explorer is not merged, because there each group is a folder and merging would remove a file from one of them. It also asserts the other three lists are merged, or deleting the merge outright would leave it passing and saying nothing. One plan item was dropped as wrong rather than built: the Music queue in onPreview needed no change. It filters by groupId and is reachable only from FilesPanel, which is not merged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbwJDbNTkiRUh7HTWEoyss
Diffstat (limited to 'packages/meshbay-hub/tests/test_search_files_unmerged.py')
-rw-r--r--packages/meshbay-hub/tests/test_search_files_unmerged.py80
1 files changed, 80 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_search_files_unmerged.py b/packages/meshbay-hub/tests/test_search_files_unmerged.py
new file mode 100644
index 0000000..6956dde
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_search_files_unmerged.py
@@ -0,0 +1,80 @@
+"""
+The Files explorer in the Search view is not merged, and must not become so.
+
+The Search view folds a file several groups share into one entry, so a film
+shared by two groups is one poster instead of two. The Files tab is the one
+place where that would be wrong: there each group is a top-level folder, the
+two copies live in two different folders, and walking into one is how a member
+browses *that group*. Merging would silently delete one of the two branches of
+the tree.
+
+This is the guarantee a later refactor is most likely to break — the four entry
+lists in `search-page.js` are near-identical, and tidying them into one shared
+builder is the obvious cleanup. It would also be the last thing anyone tests by
+hand, because the Files tab looks unchanged until you notice a group's folder
+has fewer files in it than the group does.
+
+So: read the source, and refuse a build where `fileEntries` goes through the
+merge. Weak evidence, and the only kind available for the SPA — but the failure
+it guards against is a one-line edit, which is exactly what a source-reading
+test catches well.
+
+See docs/refactoring-search.md §6.1.
+"""
+
+import re
+from pathlib import Path
+
+import pytest
+
+STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
+SEARCH_PAGE = STATIC / "search-page.js"
+
+pytestmark = pytest.mark.skipif(
+ not SEARCH_PAGE.exists(), reason="the SPA sources are not available")
+
+MERGE_CALL = "mergeUnitEntries"
+
+
+def _memo(name):
+ """The body of `const <name> = useMemo(() => { ... }, [...]);`."""
+ src = SEARCH_PAGE.read_text()
+ m = re.search(
+ r"^ const " + re.escape(name) + r" = useMemo\(\(\) => \{.*?^ \}, \[.*?\]\);",
+ src, re.M | re.S)
+ assert m, (
+ f"{name} is no longer a useMemo where this test reads it — the Files "
+ "explorer's exemption from the merge is untested until this is fixed")
+ return m.group(0)
+
+
+def test_the_files_list_is_not_merged():
+ body = _memo("fileEntries")
+ assert MERGE_CALL not in body, (
+ "fileEntries now goes through the source merge. The Files explorer "
+ "shows one folder per group and a member navigates into it; merging "
+ "two groups' copies of a file would remove it from one of those "
+ "folders. See docs/refactoring-search.md §6.1")
+
+
+@pytest.mark.parametrize("name", ["videoEntries", "musicEntries", "photoEntries"])
+def test_the_media_lists_are_merged(name):
+ """
+ The other half of the check. Without it, deleting the merge outright would
+ leave the test above passing and saying nothing.
+ """
+ assert MERGE_CALL in _memo(name), (
+ f"{name} no longer goes through the source merge — a file shared by "
+ "two groups is two entries again")
+
+
+def test_the_files_list_still_carries_one_group_per_entry():
+ """
+ What makes the explorer work: the path is prefixed with the group's name,
+ so the top level of the tree is the set of groups. A merged entry could not
+ be prefixed with anything, having several.
+ """
+ body = _memo("fileEntries")
+ assert "data.groupName + (e.path ? '/' + e.path : '')" in body, (
+ "fileEntries no longer prefixes paths with the group name — the "
+ "explorer's per-group top level is what this whole exemption is for")