Merge pull request 'feat(viewer): persist hide/show instructions state in chrome.storage.local' (#10) from feature/durable-instructions-visibility into master
CI / test (push) Successful in 6m18s

Reviewed-on: http://192.168.1.74:30008/kfj001/goon_plugin/pulls/10
Reviewed-by: kfj001 <kfj001@gmail.com>
This commit was merged in pull request #10.
This commit is contained in:
kfj001 committed 2026-09-30 18:54:19 -07:00
commit f46285fe4e
2 files changed
+95 -12

No files matched your search

+75 -10
View File
@@ -130,16 +130,26 @@ function installPopupDom() {
}; };
} }
async function mountPopup() { async function mountPopupWithStorage(storageGet = async () => ({}), captureWrite = null) {
const { dom, cleanup } = installPopupDom(); const { dom, cleanup } = installPopupDom();
// Override the chrome.storage.local stub so we can seed a value and capture writes.
const writes = [];
globalThis.chrome.storage.local.get = async (...args) => {
const res = await storageGet(...args);
return res;
};
globalThis.chrome.storage.local.set = async (items) => {
if (captureWrite) captureWrite(items); else writes.push(items);
};
const moduleUrl = new URL('../src/viewer/index.js?test=' + Date.now(), import.meta.url); const moduleUrl = new URL('../src/viewer/index.js?test=' + Date.now(), import.meta.url);
await import(moduleUrl.href); await import(moduleUrl.href);
return { dom, cleanup }; const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
return { dom, cleanup, writes, flush };
} }
describe('Viewer popup instructions', () => { describe('Viewer popup instructions', () => {
it('renders the drafted instructions copy on mount with accessible labeling', async () => { it('renders the drafted instructions copy on mount with accessible labeling', async () => {
const { dom, cleanup } = await mountPopup(); const { dom, cleanup } = await mountPopupWithStorage();
try { try {
const doc = dom.window.document; const doc = dom.window.document;
const region = doc.querySelector('.instructions-panel'); const region = doc.querySelector('.instructions-panel');
@@ -180,7 +190,7 @@ describe('Viewer popup instructions', () => {
}); });
it('toggles the instructions panel open and closed', async () => { it('toggles the instructions panel open and closed', async () => {
const { dom, cleanup } = await mountPopup(); const { dom, cleanup } = await mountPopupWithStorage();
try { try {
const doc = dom.window.document; const doc = dom.window.document;
const toggle = doc.getElementById('instructions-toggle'); const toggle = doc.getElementById('instructions-toggle');
@@ -194,11 +204,66 @@ describe('Viewer popup instructions', () => {
if (toggle.textContent !== 'Show instructions') throw new Error('Collapsed label should switch to Show instructions'); if (toggle.textContent !== 'Show instructions') throw new Error('Collapsed label should switch to Show instructions');
toggle.click(); toggle.click();
if (toggle.getAttribute('aria-expanded') !== 'true') throw new Error('Toggle should expand the instructions'); if (toggle.getAttribute('aria-expanded') !== 'true') throw new Error('Toggle should expand the instructions');
if (body.hidden) throw new Error('Instructions body should be visible after expanding'); if (body.hidden) throw new Error('Instructions body should be visible after expanding');
if (toggle.textContent !== 'Hide instructions') throw new Error('Expanded label should switch back to Hide instructions'); if (toggle.textContent !== 'Hide instructions') throw new Error('Expanded label should switch back to Hide instructions');
} finally { } finally {
cleanup(); cleanup();
} }
}); });
it('honors a stored hidden state on mount', async () => {
const { dom, cleanup } = await mountPopupWithStorage(
async () => ({ goon_instructions_visible: false })
);
try {
const doc = dom.window.document;
const toggle = doc.getElementById('instructions-toggle');
const body = doc.getElementById('instructions-body');
// Wait for the async storage.get() to resolve and apply.
await new Promise((r) => setTimeout(r, 0));
if (toggle.getAttribute('aria-expanded') !== 'false') throw new Error('Stored hidden state should start collapsed');
if (!body.hidden) throw new Error('Stored hidden state should start hidden');
if (toggle.textContent !== 'Show instructions') throw new Error('Stored hidden state should show the "Show instructions" label');
} finally {
cleanup();
}
});
it('persists the new visibility on every user toggle', async () => {
let written = null;
const { dom, cleanup, flush } = await mountPopupWithStorage(
async () => ({}),
(items) => { written = items; }
);
try {
const doc = dom.window.document;
const toggle = doc.getElementById('instructions-toggle');
// Collapse -> must persist false.
toggle.click();
await flush();
if (written === null) throw new Error('Collapsing the panel must write to chrome.storage.local');
if (written.goon_instructions_visible !== false) throw new Error('Collapsing must persist goon_instructions_visible=false');
// Expand -> must persist true.
toggle.click();
await flush();
if (written.goon_instructions_visible !== true) throw new Error('Expanding must persist goon_instructions_visible=true');
} finally {
cleanup();
}
});
it('defaults a first-time user (no stored key) to shown', async () => {
const { dom, cleanup } = await mountPopupWithStorage(async () => ({}));
try {
const doc = dom.window.document;
const toggle = doc.getElementById('instructions-toggle');
const body = doc.getElementById('instructions-body');
await new Promise((r) => setTimeout(r, 0));
if (toggle.getAttribute('aria-expanded') !== 'true') throw new Error('First-time user should start expanded');
if (body.hidden) throw new Error('First-time instructions body should be visible on mount');
} finally {
cleanup();
}
});
}); });
+20 -2
View File
@@ -6,6 +6,11 @@
const STORAGE_KEY = 'goon_videos'; const STORAGE_KEY = 'goon_videos';
const TAGGED_KEY = 'goon_tagged'; const TAGGED_KEY = 'goon_tagged';
// Durable "hide instructions" toggle. Absent key === SHOWN (true), matching the
// first-time "expanded on mount" behavior. chrome.storage.local is extension-wide,
// so the viewer window and every popup instance read/write the same value — the
// preference is respected application-wide and survives reloads / SW termination.
const INSTRUCTIONS_VISIBLE_KEY = 'goon_instructions_visible';
// Set to true to enable verbose console output in the popup. // Set to true to enable verbose console output in the popup.
const DEBUG = true; // TEMP: enabled for debugging — revert to false when done const DEBUG = true; // TEMP: enabled for debugging — revert to false when done
@@ -48,9 +53,22 @@ function setInstructionsExpanded(expanded) {
} }
if (instructionsToggle && instructionsBody) { if (instructionsToggle && instructionsBody) {
setInstructionsExpanded(true); // On mount, read the stored visibility. An absent key means SHOWN (true), so a
// first-time user still sees the steps. The viewer window reads the same
// extension-wide key, so the choice is respected across the whole app.
chrome.storage.local.get(INSTRUCTIONS_VISIBLE_KEY).then(stored => {
const visible = stored[INSTRUCTIONS_VISIBLE_KEY];
setInstructionsExpanded(visible === undefined ? true : visible);
}).catch(() => {
// Any read failure falls back to shown.
setInstructionsExpanded(true);
});
// On every user toggle, apply in-DOM and persist. Both directions write, so
// the state survives reloads and SW termination until the user flips it again.
instructionsToggle.addEventListener('click', () => { instructionsToggle.addEventListener('click', () => {
setInstructionsExpanded(instructionsBody.hidden); const next = instructionsBody.hidden;
setInstructionsExpanded(next);
chrome.storage.local.set({ [INSTRUCTIONS_VISIBLE_KEY]: next });
}); });
} }