Index: test/feed-alt-view.test.js
===================================================================
--- test/feed-alt-view.test.js	(revision e46ebaa7d0ba257f3e7916d2a4ecfa52e68617da)
+++ test/feed-alt-view.test.js	(revision e46ebaa7d0ba257f3e7916d2a4ecfa52e68617da)
@@ -0,0 +1,104 @@
+// De tweede weergave van een site: Lezen, Tijdlijn, of allebei (mobiel/desktop).
+//
+// Tijdlijn was geen KEUZE meer sinds Lezen hem verving, maar de renderkant is
+// nooit weggehaald: home.ejs stuurt alle drie de secties mee en de CSS kiest op
+// body[data-feed-view]. Dit maakt er weer een instelling van.
+//
+// Deze tests gaan door de ECHTE routes heen, en niet langs de SQL met een
+// handgemaakte rij. Reden: bij het bouwen zette ik een waarde in de INSERT
+// zonder de kolom (te weinig vraagtekens) en een kolom in de UPDATE zonder de
+// waarde -- waarbij alles erna een plek opschuift en show_search de waarde van
+// feed_alt_view kreeg. Geen van beide had een test langs de zijkant gevangen.
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://test.example';
+const dbMod = await import('../src/config/database.js');
+const db = dbMod.default;
+dbMod.initializeDatabase();
+
+db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
+  .run('u1', 'baas', 'b@t.nl', 'x', 'god');
+
+const express = (await import('express')).default;
+const router = (await import('../src/routes/admin-sites.js')).default;
+const app = express();
+app.use(express.urlencoded({ extended: true }));
+app.use((req, _res, next) => { req.session = { user: { id: 'u1', role: 'god' } }; next(); });
+app.use('/admin/sites', router);
+
+const server = app.listen(0);
+server.unref();   // houdt de suite niet in leven als een test halverwege stopt
+const poort = server.address().port;
+// MET EEN TIJDSLIMIET, en dat is geen voorzorg maar een gemeten noodzaak: gaat er
+// in de route iets mis (een SQL-fout door een scheve parameterlijst), dan vangt
+// express dat in een async-handler niet af en komt er NOOIT een antwoord. Zonder
+// limiet hangt de hele suite dan op 124 in plaats van te falen -- precies wat er
+// gebeurde toen ik de controleproef deed. Een test die op de fout die hij moet
+// vangen blijft hangen, meldt niets.
+const post = async (pad, velden) => {
+  try {
+    return await fetch(`http://127.0.0.1:${poort}${pad}`, {
+      method: 'POST', redirect: 'manual',
+      headers: { 'content-type': 'application/x-www-form-urlencoded' },
+      body: new URLSearchParams(velden).toString(),
+      signal: AbortSignal.timeout(5000),
+    });
+  } catch (e) {
+    assert.fail(`de route antwoordde niet op ${pad} (${e.name}) -- vrijwel altijd een `
+      + 'worp in de handler, en bij dit formulier meestal een kolommenlijst die niet '
+      + 'meer bij de waarden past');
+  }
+};
+const siteVan = (slug) => db.prepare('SELECT * FROM sites WHERE slug = ?').get(slug);
+
+test('de kolom bestaat en staat standaard op Lezen', async () => {
+  const r = await post('/admin/sites/create', { slug: 'proef', title: 'Proef' });
+  assert.ok(r.status === 302 || r.status === 200, 'aanmaken mag niet stranden, kreeg ' + r.status);
+  const s = siteVan('proef');
+  assert.ok(s, 'de site hoort aangemaakt te zijn');
+  assert.equal(s.feed_alt_view, 'reader', 'een verse site biedt Lezen aan');
+});
+
+test('opslaan zet de tweede weergave, en raakt zijn buren niet', async () => {
+  // show_search is de kolom die in de UPDATE direct NA feed_alt_view komt. Zette
+  // je de kolom er wel bij en de waarde niet, dan kwam de checkbox-waarde in
+  // feed_alt_view terecht en schoof al het andere op. Daarom staat hij hier.
+  await post('/admin/sites/proef/save', {
+    title: 'Proef', feed_alt_view: 'timeline', feed_view_default: 'reader',
+    feed_view_switch: '1', show_search: '1', show_archive_link: '1',
+  });
+  const s = siteVan('proef');
+  assert.equal(s.feed_alt_view, 'timeline');
+  assert.equal(s.show_search, 1, 'de buurkolom mag niet meeschuiven');
+  assert.equal(s.show_archive_link, 1, 'en die erachter ook niet');
+  assert.equal(s.feed_view_switch, 1);
+});
+
+test('de standaardweergave volgt de tweede weergave, niet een vaste naam', () => {
+  // Hier hing het scheef: het aanmaakpad schreef 'reader' en het opslaanpad
+  // 'timeline', voor precies dezelfde keuze. De waarde betekende dus niet wat
+  // er stond, en de client vertaalde dat stil terug.
+  const s = siteVan('proef');
+  assert.equal(s.feed_view_default, 'timeline',
+    'kiest de site Tijdlijn, dan opent hij daar ook in');
+});
+
+test('Grid als standaard blijft Grid, wat de tweede weergave ook is', async () => {
+  await post('/admin/sites/proef/save', { title: 'Proef', feed_alt_view: 'auto', feed_view_default: 'grid' });
+  const s = siteVan('proef');
+  assert.equal(s.feed_alt_view, 'auto');
+  assert.equal(s.feed_view_default, 'grid');
+});
+
+test('onzin in het formulier valt terug op Lezen', async () => {
+  await post('/admin/sites/proef/save', { title: 'Proef', feed_alt_view: 'iets-anders', feed_view_default: 'reader' });
+  assert.equal(siteVan('proef').feed_alt_view, 'reader');
+});
+
+// closeAllConnections EERST: fetch houdt de verbinding open (keep-alive), en dan
+// wacht server.close() tot in de eeuwigheid. Bij een geslaagde run valt dat niet
+// op; valt er een test om, dan hangt de hele suite. Zo overkwam het me bij de
+// controleproef -- de test die de fout moest aantonen, hing erop.
+test.after(() => { server.closeAllConnections?.(); server.close(); });
