Skip to content

Commit c890af7

Browse files
author
FolderView Plus Test
committed
Harden docker update ownership and event-driven browser coverage
1 parent 95c0869 commit c890af7

15 files changed

Lines changed: 435 additions & 70 deletions

archive/folderview.plus-2026.04.08.04.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.

archive/folderview.plus-2026.04.08.05.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ddbc38f1084d18b217c541a93a1f2babca89d20fdfa61acaa6dd3263c1e0293d folderview.plus-2026.04.14.08.txz
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
f65670eea4ac34c5dea138f59f3044a76e1068a52716572cc66378ab59bf3a42 folderview.plus-2026.04.14.09.txz

docs/releases/2026.04.14.08.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Fix: Docker folder rows, member rows, tooltips, and support-bundle expected-action checks now resolve update-state through shared helper logic instead of separate inline rules, which reduces stale or divergent Docker update rendering paths.
2+
- Fix: Docker basic and advanced mode syncing now reacts to `docker_listview_mode` cookie writes and passive page lifecycle events instead of a 500ms polling loop, which hardens live row refreshes while removing continuous observer churn.
3+
- Quality: Browser smoke now exercises the Docker `loadlist()` rebuild path and persisted diagnostics records, including request-bundle traces, page snapshots, and live list-view toggles.

folderview.plus.plg

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/dev/folderview.plus.plg">
9-
<!ENTITY version "2026.04.14.07">
10-
<!ENTITY md5 "07796eaadfd04908c991b4a21095b943">
9+
<!ENTITY version "2026.04.14.09">
10+
<!ENTITY md5 "086dddefcb67c1f05be00d59b46a5ac6">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-icon.png" support="https://forums.unraid.net/topic/197631-plugin-folderview-plus/" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.04.14.09
17+
- Fix: Docker runtime rows, folder state, and container interactions.
18+
- Quality: Release automation, CI smoke coverage, and packaging guards.
19+
- Docs: Project documentation and support guidance.
20+
21+
22+
###2026.04.14.08
23+
- Fix: Docker folder rows, member rows, tooltips, and support-bundle expected-action checks now resolve update-state through shared helper logic instead of separate inline rules, which reduces stale or divergent Docker update rendering paths.
24+
- Fix: Docker basic and advanced mode syncing now reacts to `docker_listview_mode` cookie writes and passive page lifecycle events instead of a 500ms polling loop, which hardens live row refreshes while removing continuous observer churn.
25+
- Quality: Browser smoke now exercises the Docker `loadlist()` rebuild path and persisted diagnostics records, including request-bundle traces, page snapshots, and live list-view toggles.
26+
27+
1628
###2026.04.14.07
1729
- Fix: Docker runtime rows, folder state, and container interactions.
1830
- Quality: Release automation, CI smoke coverage, and packaging guards.

scripts/browser_smoke.mjs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const sanitizeToken = (value) => String(value || '')
5959
const runtimeReports = [];
6060
const dashboardReports = [];
6161
const folderEditorReports = [];
62+
const dockerDiagnosticsReports = [];
6263

6364
const resolveRuntimeUrl = (baseUrl, type) => {
6465
try {
@@ -422,6 +423,153 @@ const runRuntimeLayoutSmoke = async (page, { browserName, type, url }) => {
422423
};
423424
};
424425

426+
const runDockerDiagnosticsSmoke = async (page, { browserName, url }) => {
427+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
428+
await page.waitForTimeout(1200);
429+
430+
const report = await page.evaluate(async () => {
431+
const pageKey = 'fv.support.bundle.docker.page.v1';
432+
const requestKey = 'fv.support.bundle.docker.requestBundleTrace.v1';
433+
const traceHealthKey = 'fv.support.bundle.docker.traceHealth.v1';
434+
const readRecord = (storageKey) => {
435+
try {
436+
const raw = window.localStorage?.getItem(storageKey);
437+
return raw ? JSON.parse(raw) : null;
438+
} catch (_error) {
439+
return null;
440+
}
441+
};
442+
const waitForRecord = async (predicate, timeoutMs = 6000, stepMs = 100) => {
443+
const startedAt = Date.now();
444+
while ((Date.now() - startedAt) < timeoutMs) {
445+
const value = predicate();
446+
if (value) {
447+
return value;
448+
}
449+
await new Promise((resolve) => window.setTimeout(resolve, stepMs));
450+
}
451+
return null;
452+
};
453+
const rows = Array.from(document.querySelectorAll('#docker_list > tr, #docker_view > tr'))
454+
.filter((row) => row instanceof HTMLElement && row.offsetParent !== null);
455+
if (!rows.length) {
456+
return {
457+
skipped: true,
458+
reason: 'No visible Docker rows found for diagnostics smoke.'
459+
};
460+
}
461+
if (typeof window.loadlist !== 'function') {
462+
return {
463+
skipped: true,
464+
reason: 'window.loadlist is unavailable on the Docker page.'
465+
};
466+
}
467+
const currentMode = /\bdocker_listview_mode=advanced\b/i.test(String(document.cookie || ''))
468+
? 'advanced'
469+
: 'basic';
470+
const nextMode = currentMode === 'advanced' ? 'basic' : 'advanced';
471+
const canToggleMode = typeof window.$?.cookie === 'function';
472+
const beforeRequestTrace = readRecord(requestKey);
473+
const beforeRequestCount = Number(beforeRequestTrace?.count || 0);
474+
475+
window.loadlist();
476+
const requestTrace = await waitForRecord(() => {
477+
const record = readRecord(requestKey);
478+
if (!record || Number(record?.count || 0) < beforeRequestCount) {
479+
return null;
480+
}
481+
const entries = Array.isArray(record?.entries) ? record.entries : [];
482+
const sawLoadlist = entries.some((entry) => entry?.eventType === 'loadlist');
483+
const sawBuildReq = entries.some((entry) => entry?.eventType === 'buildDockerFolderReq');
484+
const sawListview = entries.some((entry) => entry?.eventType === 'listview');
485+
return sawLoadlist && sawBuildReq && sawListview ? record : null;
486+
});
487+
const pageSnapshotAfterLoadlist = await waitForRecord(() => {
488+
const record = readRecord(pageKey);
489+
return record?.currentPage === '/Docker' ? record : null;
490+
});
491+
492+
let toggleResult = {
493+
supported: canToggleMode,
494+
toggledTo: null,
495+
toggleObserved: false,
496+
restoreObserved: false
497+
};
498+
if (canToggleMode) {
499+
window.$.cookie('docker_listview_mode', nextMode);
500+
const toggledSnapshot = await waitForRecord(() => {
501+
const record = readRecord(pageKey);
502+
return record?.listViewMode === nextMode ? record : null;
503+
}, 5000);
504+
toggleResult = {
505+
...toggleResult,
506+
toggledTo: nextMode,
507+
toggleObserved: Boolean(toggledSnapshot),
508+
restoreObserved: false
509+
};
510+
window.$.cookie('docker_listview_mode', currentMode);
511+
const restoredSnapshot = await waitForRecord(() => {
512+
const record = readRecord(pageKey);
513+
return record?.listViewMode === currentMode ? record : null;
514+
}, 5000);
515+
toggleResult.restoreObserved = Boolean(restoredSnapshot);
516+
}
517+
518+
const finalPageSnapshot = readRecord(pageKey);
519+
const traceHealth = readRecord(traceHealthKey);
520+
const finalRequestTrace = readRecord(requestKey);
521+
522+
return {
523+
skipped: false,
524+
pass: Boolean(requestTrace) && Boolean(pageSnapshotAfterLoadlist)
525+
&& (!canToggleMode || (toggleResult.toggleObserved && toggleResult.restoreObserved)),
526+
listViewMode: currentMode,
527+
visibleFolderRows: Number(finalPageSnapshot?.summary?.visibleFolderRows || 0),
528+
folderActionMismatchCount: Number(finalPageSnapshot?.summary?.folderActionMismatchCount || 0),
529+
memberActionMismatchCount: Number(finalPageSnapshot?.summary?.memberActionMismatchCount || 0),
530+
requestTraceAvailable: Boolean(finalRequestTrace),
531+
requestTraceCount: Number(finalRequestTrace?.count || 0),
532+
traceHealthAvailable: Boolean(traceHealth),
533+
requestTraceWriteSucceeded: traceHealth?.requestBundleTrace?.lastWriteSucceeded === true,
534+
pageSnapshotWriteSucceeded: traceHealth?.pageSnapshot?.lastWriteSucceeded === true,
535+
toggleResult
536+
};
537+
});
538+
539+
const screenshotName = `${sanitizeToken(scenarioLabel)}-${sanitizeToken(browserName)}-docker-diagnostics.png`;
540+
const screenshotPath = path.join(artifactRoot, screenshotName);
541+
await page.screenshot({ path: screenshotPath, fullPage: true });
542+
543+
if (report?.skipped) {
544+
console.warn(`Docker diagnostics smoke skipped for ${browserName}: ${report.reason}`);
545+
return {
546+
browserName,
547+
url,
548+
skipped: true,
549+
pass: false,
550+
reason: report.reason,
551+
screenshotPath
552+
};
553+
}
554+
555+
if (!report?.pass) {
556+
throw new Error(
557+
`Docker diagnostics smoke failed for ${browserName}: ${JSON.stringify(report)}. `
558+
+ `Screenshot: ${screenshotPath}`
559+
);
560+
}
561+
562+
console.log(`Docker diagnostics smoke passed: ${browserName} ${JSON.stringify(report)}`);
563+
return {
564+
browserName,
565+
url,
566+
skipped: false,
567+
pass: true,
568+
screenshotPath,
569+
...report
570+
};
571+
};
572+
425573
const runDashboardQuickRailSmoke = async (page, { browserName, url }) => {
426574
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
427575
await page.waitForTimeout(1200);
@@ -1015,6 +1163,17 @@ const runBrowserSmoke = async (browserName, browserType) => {
10151163
}
10161164
}
10171165

1166+
const dockerRuntimeTarget = runtimeTargets.find((entry) => entry.type === 'docker');
1167+
if (dockerRuntimeTarget) {
1168+
const diagnosticsReport = await runDockerDiagnosticsSmoke(page, {
1169+
browserName,
1170+
url: dockerRuntimeTarget.url
1171+
});
1172+
if (diagnosticsReport) {
1173+
dockerDiagnosticsReports.push(diagnosticsReport);
1174+
}
1175+
}
1176+
10181177
if (dashboardUrl) {
10191178
const dashboardReport = await runDashboardQuickRailSmoke(page, {
10201179
browserName,
@@ -1063,6 +1222,7 @@ try {
10631222
generatedAt: new Date().toISOString(),
10641223
scenarioLabel,
10651224
reports: runtimeReports,
1225+
dockerDiagnosticsReports,
10661226
dashboardReports,
10671227
folderEditorReports
10681228
};

0 commit comments

Comments
 (0)