From ce19035689cccbe1353659e5c6eb1e8354888cae Mon Sep 17 00:00:00 2001 From: Timothy Allen Date: Sun, 27 Sep 2026 08:31:26 +0200 Subject: [PATCH] Show a revised-chapters count on the story list --- bindery/static/js/stories.js | 1 + bindery/store.py | 4 +++- tests/js/flows.test.mjs | 14 ++++++++++++++ tests/test_store.py | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/bindery/static/js/stories.js b/bindery/static/js/stories.js index cd488c3..d040961 100644 --- a/bindery/static/js/stories.js +++ b/bindery/static/js/stories.js @@ -172,6 +172,7 @@ function storyRow(s) { { 'data-label': 'Chapters' }, String(s.chapter_count), s.offsite_count > 0 && [' ', badge('warn', `${s.offsite_count} removed`, 'Chapters the site no longer lists')], + s.revised_count > 0 && [' ', badge('info', `${s.revised_count} revised`, 'Chapters the site changed since Bindery added them')], ), h( 'td', diff --git a/bindery/store.py b/bindery/store.py index 3914a5e..3796c05 100644 --- a/bindery/store.py +++ b/bindery/store.py @@ -187,7 +187,9 @@ class Store: s.message, s.last_checked, s.last_changed, s.ingested_at, s.locked_fields, (SELECT COUNT(*) FROM chapters c WHERE c.story_id = s.id AND c.included = 1) AS chapter_count, (SELECT COUNT(*) FROM chapters c WHERE c.story_id = s.id AND c.included = 1 - AND c.on_site = 0) AS offsite_count + AND c.on_site = 0) AS offsite_count, + (SELECT COUNT(*) FROM chapters c WHERE c.story_id = s.id AND c.included = 1 + AND (SELECT COUNT(*) FROM revisions r WHERE r.chapter_id = c.id) > 1) AS revised_count FROM stories s ORDER BY s.title COLLATE NOCASE""").fetchall() return [_story(r) for r in rows] diff --git a/tests/js/flows.test.mjs b/tests/js/flows.test.mjs index ff145fb..a19212b 100644 --- a/tests/js/flows.test.mjs +++ b/tests/js/flows.test.mjs @@ -263,6 +263,20 @@ test('story list stacks on a narrow screen instead of scrolling sideways', async assert.deepEqual(dataLabels, [undefined, 'Author', 'Site', 'Chapters', 'Status', 'Last check']); }); +test('the story list shows removed and revised chapter counts, only when there are any', async () => { + details.story.offsite_count = 0; + details.story.revised_count = 0; + await mountStories(); + assert.doesNotMatch(main.textContent, /removed/); + assert.doesNotMatch(main.textContent, /revised/); + cleanups.pop()(); + details.story.offsite_count = 2; + details.story.revised_count = 3; + await mountStories(); + assert.match(main.textContent, /2 removed/); + assert.match(main.textContent, /3 revised/); +}); + test('story download modes send selected chapters and clear forced fields after success', async () => { await mountStory(); api.on('POST', 'stories/1/download', { mode_label: 'Queued' }); diff --git a/tests/test_store.py b/tests/test_store.py index c5bbbb1..421b64a 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -33,6 +33,20 @@ def test_chapters_revisions_and_rules(tmp_path): assert store.rules(None) != [] +def test_list_stories_counts_offsite_and_revised_chapters(tmp_path): + store = Store(tmp_path / "db.sqlite") + sid = store.create_story("https://example/s/1", "example", "T", "A", []) + c1 = store.add_chapter(sid, "u1", "One", "

1

", "h1", "new", 0) + c2 = store.add_chapter(sid, "u2", "Two", "

2

", "h2", "new", 1) + store.add_chapter(sid, "u3", "Three", "

3

", "h3", "new", 2) + store.update_chapter(c1, on_site=0) + store.add_revision(c2, "

2b

", "h2b", "recheck") + + story = store.list_stories()[0] + assert story["offsite_count"] == 1 + assert story["revised_count"] == 1 + + def test_cover_replaces_previous_cover(tmp_path): store = Store(tmp_path / "db.sqlite") sid = store.create_story("u", "s", "T", "A", [])