A host from the web site; the same page kept once; deleting from the Notebooks chooser

From the development repository at 299f73d, 2026-10-11:
hosting from the site documented; a page loaded from a notebook and left
unchanged is not kept again; notebooks and pages are deleted from the
chooser, to the system's Trash (--trash); the Notebooks button.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ackandClaude Fable 5.1 committed 2026-10-11 16:59:03 +02:00
1 parent 4e3a281d65
commit 0d01e23d7c
15 files changed
+711 -63

No files matched your search

+95 -3
View File
@@ -521,6 +521,8 @@ class EndToEndTest(unittest.TestCase):
cls.processes = []
cls.directory = tempfile.TemporaryDirectory()
cls.notebooks = pathlib.Path(cls.directory.name) / "notebooks"
# Deleted notebooks and pages go here, not to the real Trash.
cls.trash = pathlib.Path(cls.directory.name) / "trash"
relay_port, driver_port = free_port(), free_port()
cls.start([sys.executable, str(PROJECT / "relay" / "kokkoja_relay.py"),
"--port", str(relay_port)])
@@ -552,7 +554,8 @@ class EndToEndTest(unittest.TestCase):
site_option = ["--site", cls.site] if name == "Host" else []
cls.start([sys.executable, str(PROJECT / "kokkoja.py"), "--no-browser",
"--port", str(port), "--relay", f"ws://127.0.0.1:{relay_port}",
"--notebooks", str(cls.notebooks), "--config", str(config), *site_option])
"--notebooks", str(cls.notebooks), "--config", str(config),
"--trash", str(cls.trash), *site_option])
cls.programs[name] = (f"http://{host}:{port}/", config)
cls.start(["geckodriver", "--port", str(driver_port)])
try:
@@ -824,6 +827,21 @@ class EndToEndTest(unittest.TestCase):
lambda v: v.startswith("Already kept"), "the second keep to be reported")
time.sleep(1)
self.assertEqual(sorted(p.name for p in self.downloads.iterdir()), [f"{today}_01.svg"])
# The browser has no trash: a deleted page is gone, and the
# notebook with it when it is deleted whole.
self.assertEqual(firefox.run_until_done("""
const [name, done] = arguments;
(async () => {
const m = window.kokkoja.notebook;
const listed = await m.listNotebooks();
await m.discardPage('Guest notebook', name);
const after = await m.listKeptPages('Guest notebook');
await m.discardNotebook('Guest notebook');
done({trash: listed.trash, after,
notebooks: (await m.listNotebooks()).notebooks,
current: await m.readCurrentPage('Guest notebook')});
})().catch((e) => done({error: String(e)}));
""", f"{today}_01.svg"), {"trash": False, "after": [], "notebooks": [], "current": None})
# Exporting downloads a picture.
firefox.run("document.getElementById('export_page').click();")
@@ -2072,11 +2090,11 @@ class EndToEndTest(unittest.TestCase):
# is the student's again.
firefox.run("document.getElementById('load_notebook_page').click();")
notebooks = self.wait_until_true(lambda: firefox.run("""
return [...document.querySelectorAll('#chooser_notebooks button')].map((b) => b.textContent);
return [...document.querySelectorAll('#chooser_notebooks button.notebook_pick')].map((b) => b.textContent);
"""), lambda names: "Teacher notebook" in names, "the chooser to list the notebooks")
self.assertEqual(notebooks, sorted(notebooks))
firefox.run("""
[...document.querySelectorAll('#chooser_notebooks button')]
[...document.querySelectorAll('#chooser_notebooks button.notebook_pick')]
.find((b) => b.textContent === 'Teacher notebook').click();
""")
cards = self.wait_until_true(lambda: firefox.run("""
@@ -2140,6 +2158,80 @@ class EndToEndTest(unittest.TestCase):
"""), lambda n: n >= count, "the chooser to list the kept pages again")
self.assertEqual(loaded["images"], stacked["images"])
self.assertEqual(loaded["page"]["height"], 3608)
# Each page loaded on the way was left unchanged, so none was kept
# again (the file names a page anew at every load; the contents
# are compared without the names).
self.wait_until_true(
lambda: (self.notebooks / "Teacher notebook" / "Current.svg").read_text(),
lambda text: f'data-number="{loaded["page"]["number"]}"' in text, "the loaded page to be written")
self.assertEqual(len(self.kept_pages("Teacher notebook")), count)
# Deleting from the chooser: a mark on each page and notebook, one
# Delete button that asks for a second press, the entries moved
# to the Trash. The notebook being written in cannot be
# marked; another notebook goes with everything in it.
firefox.run("document.getElementById('load_notebook_page').click();")
self.wait_until_true(lambda: firefox.run("""
return document.querySelectorAll('#chooser_pages .page_slot button.mark').length;
"""), lambda n: n == count, "the chooser to list the kept pages with marks")
self.assertTrue(firefox.run("return document.getElementById('chooser_delete').hidden;"))
before = self.kept_pages("Teacher notebook")
firefox.run("""
const marks = document.querySelectorAll('#chooser_pages .page_slot button.mark');
marks[0].click(); marks[1].click(); marks[2].click(); marks[2].click();
""")
delete = lambda: firefox.run("""
const b = document.getElementById('chooser_delete');
return b.hidden ? null : b.textContent;
""")
self.assertEqual(delete(), "Delete 2 pages?")
self.assertEqual(firefox.run("""
return [...document.querySelectorAll('#chooser_pages .page_slot')].map((s) => s.classList.contains('marked'));
""")[:3], [True, True, False])
firefox.run("document.getElementById('chooser_delete').click();")
self.assertEqual(delete(), "Press again to move 2 pages to the Trash")
# Marking something else takes the first press back.
firefox.run("document.querySelectorAll('#chooser_pages .page_slot button.mark')[2].click();")
self.assertEqual(delete(), "Delete 3 pages?")
firefox.run("document.getElementById('chooser_delete').click();")
firefox.run("document.getElementById('chooser_delete').click();")
self.wait_until_true(lambda: firefox.run("return document.getElementById('chooser_message').textContent;"),
lambda text: text == "3 pages moved to the Trash.", "the deletion to be reported")
self.assertEqual(self.kept_pages("Teacher notebook"), before[3:])
self.assertEqual(sorted(p.name for p in self.trash.iterdir()), before[:3])
self.assertEqual(firefox.run("return document.querySelectorAll('#chooser_pages .page_slot').length;"),
count - 3)
self.assertIsNone(delete())
# The notebook being written in: its mark explains instead.
firefox.run("""
[...document.querySelectorAll('#chooser_notebooks .notebook_row')]
.find((r) => r.querySelector('.notebook_pick').textContent === 'Teacher notebook')
.querySelector('button.mark').click();
""")
self.assertIn("writing in this notebook", firefox.run("return document.getElementById('chooser_message').textContent;"))
self.assertIsNone(delete())
# Another notebook, with its pages, goes as one; the toolbar's list loses it.
self.assertTrue((self.notebooks / "Other notebook" / "Current.svg").exists())
firefox.run("""
[...document.querySelectorAll('#chooser_notebooks .notebook_row')]
.find((r) => r.querySelector('.notebook_pick').textContent === 'Other notebook')
.querySelector('button.mark').click();
""")
self.assertEqual(delete(), "Delete 1 notebook?")
firefox.run("document.getElementById('chooser_delete').click();")
self.assertEqual(delete(), "Press again to move 1 notebook to the Trash")
firefox.run("document.getElementById('chooser_delete').click();")
self.wait_until_true(lambda: firefox.run("return document.getElementById('chooser_message').textContent;"),
lambda text: text == "1 notebook moved to the Trash.", "the notebook's deletion to be reported")
self.assertFalse((self.notebooks / "Other notebook").exists())
self.assertTrue((self.trash / "Other notebook" / "Current.svg").exists())
self.assertNotIn("Other notebook", firefox.run("""
return [...document.querySelectorAll('#chooser_notebooks button.notebook_pick')].map((b) => b.textContent);
"""))
self.wait_until_true(lambda: firefox.run("""
return [...document.getElementById('session_notebook').options].map((o) => o.textContent);
"""), lambda names: "Other notebook" not in names, "the toolbar's list to lose the notebook")
firefox.run("document.getElementById('chooser_close').click();")
# A page that its images have made longer than a sheet is first
# shown at the scale at which one sheet fits the window.
self.assertEqual(firefox.run(VIEW), long_view)