aboutsummaryrefslogtreecommitdiff
path: root/modules/test
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-26 14:56:44 +0100
committerAgando <roduccomm@gmail.com>2026-10-26 14:56:44 +0100
commit979254cf303af562574e6a6a2c86416fe2d1e7e8 (patch)
treeb582a81720479bed25da1d181503a3c9a456e3d0 /modules/test
parentb79d70926ae88a3b10a13e2e029bbb7f487b8cf4 (diff)
feat(newos4): mobile download/share UX — deferred blob revoke, multi-select, Web Share, 44px targets
Executes .hermes/plans/2026-10-24_051600-newos4-mobile-download-share.md (approved via the feedback dashboard; marker autonomy: safe). Front-end only, v4.6.94 -> v4.6.95. T1 modules/ext/filemgr.js — URL.revokeObjectURL ran in the same tick as a.click(); iOS Safari and Android Chrome hand the blob: URL to the download manager asynchronously, so the transfer was aborted while the status line still said "Downloaded". Now revoked after 30 s. T2 download honours the multi-selection via _getSelectedPaths() (falling back to _selectedFile), reports "⬇ Downloaded N files" and "❌ Skipped <path>" instead of a silent partial success. T3 share: navigator.share -> clipboard.writeText -> inline read-only <input> (focused, selected). prompt() is suppressed in iOS standalone PWAs and most Android WebViews, which is why tapping 🔗 did nothing. Unresolvable space now reports "🔗 Share unavailable — no space resolved". T4 both preview toolbar buttons: min-height/min-width 44px, touch-action manipulation, 1em, gap 10px (was 24px/0.85em/6px — the smallest tap targets in the UI). T5 _previewShareUrl falls back _resolveSpaceName -> _detectSpaceName (webdav.js:22) -> page path, and returns null rather than emitting /spaces//<path>. T6 the URL helpers are now module-scope pure functions exported for Node, so modules/test/test-download-url.js tests the shipped code instead of a local copy (4 -> 6 cases). Also fixes a latent bug that made require() impossible: `typeof window.webx11` throws under Node because typeof does not protect a member access on an undefined global. Verified on the served bundle (filemgr.js is concatenated into critical-ext-bundle.js, NOT core-bundle.js — the plan's T7 grep pointed at the wrong file): HTTP 200, 129 867 B, 2x navigator.share, 3x fmBuildShareUrl, deferred revoke present, 11x min-height:44px. Live browser probe on /spaces/newos4/main.html: _previewShareUrl('docs/a.md') -> https://webx11.duckdns.org/spaces/newos4/docs/a.md (no double slash); with navigator.share absent and clipboard blocked, clicking 🔗 produced .fm-share-input containing that URL; button style ends min-height:44px;min-width:44px;touch-action:manipulation; toolbar gap:10px. node modules/test/test-download-url.js -> 6 PASS. lint-no-undef clean (55 files). maintain: PASS version v4.6.95 x3, syntax OK, HTTP 200 /main.html. Also adds admin/docs/session-overview-2026-10-26.md (glm-5.3-compressed session record).
Diffstat (limited to 'modules/test')
-rw-r--r--modules/test/test-download-url.js55
1 files changed, 38 insertions, 17 deletions
diff --git a/modules/test/test-download-url.js b/modules/test/test-download-url.js
index 63cabde..7815aae 100644
--- a/modules/test/test-download-url.js
+++ b/modules/test/test-download-url.js
@@ -1,7 +1,13 @@
// node modules/test/test-download-url.js
-// Regression guard for the download/share URL space (2026-10-21).
-// The URL must follow the WebDAV config (the logged-in user's space), NOT the
-// page path — /spaces/newos4/ is the app, not the user.
+// Regression guard for the download/share URL space (2026-10-21, tightened 2026-10-24).
+// The URL must follow the WebDAV config (the logged-in user's space), NOT the page path —
+// /spaces/newos4/ is the app, not the user.
+//
+// 2026-10-24 (T6): this test used to re-implement both helpers locally, so it passed 4/4
+// whatever filemgr.js did. It now requires the shipped functions.
+const { fmDetectSpaceName, fmExtractSpaceNameFromUrl, fmBuildShareUrl } =
+ require('../ext/filemgr.js');
+
const cases = [
{ page: '/spaces/newos4/', cfg: 'https://webx11.duckdns.org/spaces/agando/', want: 'agando' },
{ page: '/spaces/newos4/', cfg: 'https://webx11.duckdns.org/spaces/mindtech/', want: 'mindtech' },
@@ -9,24 +15,39 @@ const cases = [
{ page: '/spaces/newos4/', cfg: '', want: 'newos4' }, // no config → fall back to the page space
];
-function detectSpaceName(path) {
- const parts = path.split('/').filter(Boolean);
- const i = parts.indexOf('spaces');
- return (i >= 0 && i + 1 < parts.length) ? parts[i + 1] : '';
-}
-function extractSpaceNameFromUrl(url) {
- if (!url) return '';
- const parts = new URL(url).pathname.split('/').filter(Boolean);
- const i = parts.indexOf('spaces');
- return (i >= 0 && i + 1 < parts.length) ? parts[i + 1] : '';
-}
-
let bad = 0;
for (const c of cases) {
- const got = extractSpaceNameFromUrl(c.cfg) || detectSpaceName(c.page);
+ const got = fmExtractSpaceNameFromUrl(c.cfg) || fmDetectSpaceName(c.page);
const ok = got === c.want;
if (!ok) bad++;
console.log((ok ? 'PASS' : 'FAIL'), c.page, 'cfg=' + (c.cfg || '(none)'), '→', got, '(want', c.want + ')');
}
-console.log(bad ? `\n${bad} FAILED` : `\n${cases.length} PASS`);
+
+// T5: an unresolved space must produce null — never a "spaces//" URL.
+{
+ const got = fmBuildShareUrl('', 'docs/x.md');
+ const ok = got === null;
+ if (!ok) bad++;
+ console.log((ok ? 'PASS' : 'FAIL'), 'empty space →', got, '(want null)');
+}
+
+// Paths with spaces must be percent-encoded per segment.
+{
+ const got = fmBuildShareUrl('newos4', 'docs/a file/b&c.md');
+ const want = 'https://webx11.duckdns.org/spaces/newos4/docs/a%20file/b%26c.md';
+ const ok = got === want;
+ if (!ok) bad++;
+ console.log((ok ? 'PASS' : 'FAIL'), 'encoded path →', got);
+}
+
+// Belt and braces: no case may ever produce a double slash after /spaces/.
+for (const c of cases) {
+ const url = fmBuildShareUrl(fmExtractSpaceNameFromUrl(c.cfg) || fmDetectSpaceName(c.page), 'docs/x.md');
+ if (url && url.indexOf('/spaces//') !== -1) {
+ bad++;
+ console.log('FAIL double space slash →', url);
+ }
+}
+
+console.log(bad ? `\n${bad} FAILED` : `\n${cases.length + 2} PASS`);
process.exit(bad ? 1 : 0);