M2: refactor DisplayView to storage adapter (red test first)

DisplayView missed in original M2 refactor — raw onSnapshot(doc(db,path))
survived. In ws/memory mode db is a stub sentinel, so raw SDK calls crash
('Expected first argument to collection() to be a CollectionReference...').
Reported by human testing player display after start combat.

TDD:
1. RED: DisplayView.characterization.test.js asserts adapter.subscribeDoc
   called for campaign + encounter + activeDisplay paths. Adapter recorder
   (getAdapterCalls/resetAdapterCalls in firebase.js) instruments subscribe
   calls — catches raw-SDK bypass that firebase mock alone cannot (mock db
   satisfies raw onSnapshot, hiding the bug).
2. Fix: 2 raw onSnapshot sites in DisplayView -> storage.subscribeDoc.
3. GREEN: 2 new tests pass, 116 total green.

Audit confirmed DisplayView was the ONLY remaining raw SDK site in App.js.
This commit is contained in:
david raistrick
2026-06-29 13:13:46 -04:00
parent 52866784b2
commit 84a8b78acd
3 changed files with 79 additions and 22 deletions
+9
View File
@@ -15,6 +15,13 @@ import {
onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, serverTimestamp,
} from 'firebase/firestore';
// Adapter call recorder (instrumentation, no behavior change).
// Tests assert adapter.subscribeDoc called (catches raw-SDK bypass like DisplayView).
const ADAPTER_CALLS = [];
function recordAdapterCall(entry) { ADAPTER_CALLS.push({ ...entry, ts: Date.now() }); }
export function getAdapterCalls() { return [...ADAPTER_CALLS]; }
export function resetAdapterCalls() { ADAPTER_CALLS.length = 0; }
// Path helpers mirror App.js getPath object.
const APP_ID = process.env.REACT_APP_TRACKER_APP_ID || 'ttrpg-initiative-tracker-default';
const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
@@ -112,12 +119,14 @@ export function createFirebaseStorage() {
// Subscribe = onSnapshot. cb fires immediately + on change. Returns unsubscribe.
subscribeDoc(path, cb) {
recordAdapterCall({ fn: 'subscribeDoc', path });
return onSnapshot(doc(db, path), (snap) => {
cb(snap.exists() ? { id: snap.id, ...snap.data() } : null);
}, (err) => console.error(`subscribeDoc ${path}:`, err));
},
subscribeCollection(collectionPath, cb, queryConstraints = []) {
recordAdapterCall({ fn: 'subscribeCollection', path: collectionPath });
const q = queryConstraints.length > 0
? query(collection(db, collectionPath), ...queryConstraints)
: collection(db, collectionPath);