Follow-up to PR #21: - The header shows All / Pictures / Videos beside the search (only once the output folder has a video) instead of twelve chips; the formats move to Settings -> Gallery, stored as hidden so a format added later shows. - GET /anomalous/gallery_images lists pictures only unless media= says otherwise, so the recipe cover picker and the MCP image search are unchanged; hide= drops formats (jpg covers .jpeg); has_videos tells the gallery whether to offer the switch. .avi is left out (Chrome cannot play it), .avif is a picture format in the filter too. - The viewer stops the video however it closes (Esc included). - Video cards have no View parameters, star or right-click menu, and the workbench steps through pictures only. - 图库, not 画廊; the 1.59 CHANGELOG entry credits cocorang and wkdtjs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
4.6 KiB
Python
87 lines
4.6 KiB
Python
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
PLUGIN = Path(__file__).resolve().parents[1]
|
|
sys.path[:0] = [str(PLUGIN), str(PLUGIN.parents[1])]
|
|
from api import materials, utils
|
|
|
|
|
|
def material(name, node_type='KSampler'):
|
|
return {'name': name, 'workflow': {'nodes': [{'id': 1, 'type': node_type, 'widgets_values': [1]}]}, 'timestamp': 1}
|
|
|
|
|
|
class ListingIndexTests(unittest.TestCase):
|
|
def setUp(self):
|
|
materials._cached_material_summary.cache_clear()
|
|
utils._invalidate_gallery_snapshot()
|
|
|
|
def test_warm_summary_avoids_workflow_reads_and_detects_file_changes(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory, 'one.json')
|
|
path.write_text(json.dumps(material('first')))
|
|
with mock.patch.object(materials, '_read_material', wraps=materials._read_material) as read:
|
|
first = materials._list_materials(directory)
|
|
first[0]['name'] = 'mutated caller'
|
|
self.assertEqual(materials._list_materials(directory)[0]['name'], 'first')
|
|
self.assertEqual(read.call_count, 1)
|
|
path.write_text(json.dumps(material('changed name')))
|
|
self.assertEqual(materials._list_materials(directory)[0]['name'], 'changed name')
|
|
self.assertEqual(read.call_count, 2)
|
|
path.unlink()
|
|
self.assertEqual(materials._list_materials(directory), [])
|
|
|
|
def test_node_lookup_reads_only_matching_records_after_index_warmup(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
Path(directory, 'one.json').write_text(json.dumps(material('one', 'KSampler')))
|
|
Path(directory, 'two.json').write_text(json.dumps(material('two', 'CLIPTextEncode')))
|
|
materials._list_materials(directory)
|
|
request = type('Request', (), {'query': {'type': 'KSampler'}})()
|
|
with mock.patch.object(materials, 'get_materials_dir', return_value=directory), mock.patch.object(materials, '_read_material', wraps=materials._read_material) as read:
|
|
response = asyncio.run(materials.api_get_materials_by_node_type(request))
|
|
self.assertEqual(len(json.loads(response.text)['materials']), 1)
|
|
self.assertEqual(read.call_count, 1)
|
|
|
|
def test_gallery_pagination_reuses_snapshot_and_refresh_sees_new_output(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
Path(directory, 'one.png').write_bytes(b'image')
|
|
with mock.patch.object(utils, '_collect_gallery_images', wraps=utils._collect_gallery_images) as collect:
|
|
self.assertEqual(len(utils._gallery_images(directory)), 1)
|
|
Path(directory, 'two.png').write_bytes(b'image')
|
|
self.assertEqual(len(utils._gallery_images(directory)), 1)
|
|
self.assertEqual(collect.call_count, 1)
|
|
self.assertEqual(len(utils._gallery_images(directory, refresh=True)), 2)
|
|
utils._invalidate_gallery_snapshot()
|
|
utils._gallery_images(directory)
|
|
self.assertEqual(collect.call_count, 3)
|
|
|
|
def test_gallery_ttl_and_root_change_invalidate_snapshot(self):
|
|
with tempfile.TemporaryDirectory() as first, tempfile.TemporaryDirectory() as second:
|
|
with mock.patch.object(utils, '_collect_gallery_images', wraps=utils._collect_gallery_images) as collect:
|
|
utils._gallery_images(first)
|
|
utils._gallery_images(second)
|
|
utils._gallery_snapshot['expires'] = 0
|
|
utils._gallery_images(second)
|
|
self.assertEqual(collect.call_count, 3)
|
|
|
|
|
|
def test_gallery_lists_pictures_unless_asked_and_hides_formats(self):
|
|
from api import gallery_routes
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
for name in ('a.png', 'b.jpeg', 'c.mp4', 'd.mkv', 'e.avi', 'f.txt'):
|
|
Path(directory, name).write_bytes(b'x')
|
|
names = lambda items: sorted(item['filename'] for item in items)
|
|
self.assertEqual(names(gallery_routes._gallery_listing(directory, True, '')), ['a.png', 'b.jpeg'])
|
|
self.assertEqual(names(gallery_routes._gallery_listing(directory, False, '', 'video')), ['c.mp4', 'd.mkv'])
|
|
self.assertEqual(names(gallery_routes._gallery_listing(directory, False, '', 'all', frozenset({'jpg', 'mkv'}))),
|
|
['a.png', 'c.mp4'])
|
|
images, has_videos = gallery_routes._gallery_page_source(directory, False, '', 'image', frozenset())
|
|
self.assertEqual((len(images), has_videos), (2, True))
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|