diff --git a/static/css/session-workspace.css b/static/css/session-workspace.css index 02469ab..9c7c178 100644 --- a/static/css/session-workspace.css +++ b/static/css/session-workspace.css @@ -195,6 +195,13 @@ white-space: nowrap; } +@media (pointer: coarse) { + .fm-embedded-mode .fm-file-item { + grid-template-columns: 40px 22px minmax(0, 1fr) auto; + min-height: 52px; + } +} + .notepad-panel { min-height: 0; } diff --git a/static/css/sftp-file-manager.css b/static/css/sftp-file-manager.css index 59599bf..85d1774 100644 --- a/static/css/sftp-file-manager.css +++ b/static/css/sftp-file-manager.css @@ -393,6 +393,9 @@ overflow-y: auto; overflow-x: hidden; padding: 8px; + overscroll-behavior: contain; + touch-action: pan-y; + -webkit-overflow-scrolling: touch; } .fm-file-item { @@ -406,6 +409,8 @@ transition: all 0.15s ease; user-select: none; border: 1px solid transparent; + touch-action: pan-y; + -webkit-tap-highlight-color: transparent; } .fm-file-item:hover { @@ -1260,8 +1265,13 @@ transform: translateY(0); } + .fm-action-sheet.gesture-active { + pointer-events: none; + } + .fm-action-sheet-item { display: flex; + width: 100%; align-items: center; gap: 12px; padding: 14px 16px; @@ -1272,6 +1282,8 @@ cursor: pointer; border-radius: 8px; min-height: 48px; + font-family: inherit; + text-align: left; } .fm-action-sheet-item:active { @@ -1282,6 +1294,11 @@ color: var(--error-color); } + .fm-action-sheet-item:disabled { + cursor: not-allowed; + opacity: 0.42; + } + .fm-action-sheet-cancel { margin-top: 8px; padding-top: 8px; @@ -2691,7 +2708,7 @@ .fm-file-list-header, #sftpFileManager.fm-workspace-mode .fm-file-item { - grid-template-columns: 24px 22px minmax(120px, 1fr) 74px; + grid-template-columns: 40px 22px minmax(120px, 1fr) 74px; } .fm-file-list-header span:nth-last-child(-n+2), @@ -2700,6 +2717,26 @@ display: none; } } + +@media (pointer: coarse) { + .fm-file-item[draggable="false"] { + -webkit-user-drag: none; + } + + .fm-file-checkbox { + width: 40px; + height: 40px; + font-size: 20px !important; + } + + .fm-pane-toolbar button, + .fm-nav-btn, + .fm-source-tab-add, + .fm-pane-tab { + min-width: 44px; + min-height: 44px; + } +} /* SMB quick connection mirrors the compact SFTP connection dialog. */ .smb-source-modal { z-index: 26000; diff --git a/static/js/i18n.js b/static/js/i18n.js index cb523ce..73b5084 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -243,6 +243,7 @@ const translations = { 'fm.workspace.openSourceIn': 'Open source in', 'fm.workspace.selectAll': 'Select all files', 'fm.workspace.selectItem': 'Select', + 'fm.workspace.fileActions': 'File actions', 'fm.workspace.transferBetween': 'Transfer between panes', 'fm.workspace.selectFiles': 'Select files', 'fm.workspace.dragToMove': 'Drag items onto a folder to move them', @@ -1475,6 +1476,7 @@ const translations = { 'fm.workspace.openSourceIn': 'Mở nguồn trong', 'fm.workspace.selectAll': 'Chọn tất cả tệp', 'fm.workspace.selectItem': 'Chọn', + 'fm.workspace.fileActions': 'Thao tác tệp', 'fm.workspace.transferBetween': 'Chuyển giữa các khung', 'fm.workspace.selectFiles': 'Chọn tệp', 'fm.workspace.dragToMove': 'Kéo các mục vào một thư mục để di chuyển', @@ -2706,6 +2708,7 @@ const translations = { 'fm.workspace.openSourceIn': 'Quelle öffnen in', 'fm.workspace.selectAll': 'Alle Dateien auswählen', 'fm.workspace.selectItem': 'Auswählen', + 'fm.workspace.fileActions': 'Dateiaktionen', 'fm.workspace.transferBetween': 'Zwischen Bereichen übertragen', 'fm.workspace.selectFiles': 'Dateien auswählen', 'fm.workspace.dragToMove': 'Elemente zum Verschieben auf einen Ordner ziehen', @@ -3936,6 +3939,7 @@ const translations = { 'fm.workspace.openSourceIn': 'Ouvrir une source dans', 'fm.workspace.selectAll': 'Sélectionner tous les fichiers', 'fm.workspace.selectItem': 'Sélectionner', + 'fm.workspace.fileActions': 'Actions sur les fichiers', 'fm.workspace.transferBetween': 'Transférer entre les volets', 'fm.workspace.selectFiles': 'Sélectionner des fichiers', 'fm.workspace.dragToMove': 'Faites glisser les éléments sur un dossier pour les déplacer', @@ -5166,6 +5170,7 @@ const translations = { 'fm.workspace.openSourceIn': 'Abrir origen en', 'fm.workspace.selectAll': 'Seleccionar todos los archivos', 'fm.workspace.selectItem': 'Seleccionar', + 'fm.workspace.fileActions': 'Acciones de archivo', 'fm.workspace.transferBetween': 'Transferir entre paneles', 'fm.workspace.selectFiles': 'Seleccionar archivos', 'fm.workspace.dragToMove': 'Arrastra los elementos a una carpeta para moverlos', @@ -6396,6 +6401,7 @@ const translations = { 'fm.workspace.openSourceIn': '打开来源于', 'fm.workspace.selectAll': '选择所有文件', 'fm.workspace.selectItem': '选择', + 'fm.workspace.fileActions': '文件操作', 'fm.workspace.transferBetween': '在面板之间传输', 'fm.workspace.selectFiles': '选择文件', 'fm.workspace.dragToMove': '将项目拖到文件夹上即可移动', diff --git a/static/js/sftp-file-manager.js b/static/js/sftp-file-manager.js index f8fa678..ae1a1fc 100644 --- a/static/js/sftp-file-manager.js +++ b/static/js/sftp-file-manager.js @@ -34,6 +34,9 @@ class SFTPFileManager { this.applyToAll = false; this.contextMenu = null; + this.suppressedItemClick = null; + this.actionSheetIndex = null; + this.actionSheetTrigger = null; this.draggedItems = []; this.dragSource = null; @@ -1117,17 +1120,17 @@ class SFTPFileManager {
- -
-
@@ -1183,17 +1186,17 @@ class SFTPFileManager {
- -
-
@@ -1277,35 +1280,35 @@ class SFTPFileManager {
-
-
- folder_open - Open -
-
- download - Download -
-
- swap_horiz - Transfer -
-
- edit - Rename -
-
- create_new_folder - New Folder -
-
- delete - Delete -
-
- close - Cancel -
+
`; @@ -2746,7 +2749,7 @@ class SFTPFileManager {
+ draggable="${this.supportsNativeFileDrag()}"> @@ -2778,7 +2781,12 @@ class SFTPFileManager { this.updatePaneStatus(pane); } - handleItemClick(e, pane, index) { + handleItemClick(e, pane, index, { ignoreSuppression = false } = {}) { + if (!ignoreSuppression && this.shouldSuppressItemClick(pane, index)) { + e.preventDefault?.(); + e.stopPropagation?.(); + return; + } e.stopPropagation(); this.setActivePane(pane); @@ -2809,6 +2817,11 @@ class SFTPFileManager { } handleItemDoubleClickEvent(e, pane, index) { + if (this.shouldSuppressItemClick(pane, index)) { + e.preventDefault?.(); + e.stopPropagation?.(); + return; + } if (e.target?.closest?.('.fm-file-checkbox')) return; e.preventDefault(); e.stopPropagation(); @@ -2877,6 +2890,43 @@ class SFTPFileManager { return window.innerWidth < 768; } + supportsNativeFileDrag() { + return typeof window.matchMedia !== 'function' + || !window.matchMedia('(pointer: coarse)').matches; + } + + suppressItemClick(pane, index, duration = 800) { + this.suppressedItemClick = { + pane, + index, + until: Date.now() + duration, + }; + } + + shouldSuppressItemClick(pane, index) { + const suppressed = this.suppressedItemClick; + if (!suppressed) return false; + if (suppressed.until <= Date.now()) { + this.suppressedItemClick = null; + return false; + } + return suppressed.pane === pane && suppressed.index === index; + } + + handleTouchItemTap(event, pane, index) { + const state = this.panes[pane]; + const checkboxTapped = Boolean(event.target?.closest?.('.fm-file-checkbox')); + const file = index >= 0 ? state.files[index] : null; + if (!checkboxTapped && (index === -1 || file?.is_dir)) { + event.preventDefault?.(); + event.stopPropagation?.(); + this.setActivePane(pane); + this.handleItemDblClick(pane, index); + return; + } + this.handleItemClick(event, pane, index, { ignoreSuppression: true }); + } + updateMobilePaneTabs(pane) { document.querySelectorAll('.fm-pane-tab').forEach(tab => { const active = tab.dataset.pane === pane; @@ -2886,64 +2936,154 @@ class SFTPFileManager { } setupLongPress() { + let gesture = null; let longPressTimer = null; const longPressDuration = 500; + const movementTolerance = 10; + + const clearLongPressTimer = () => { + if (longPressTimer) clearTimeout(longPressTimer); + longPressTimer = null; + }; + + const resetGesture = () => { + clearLongPressTimer(); + gesture = null; + }; ['left', 'right'].forEach(pane => { const listEl = document.getElementById(`fm${this.capitalize(pane)}List`); - listEl.addEventListener('touchstart', (e) => { - const item = e.target.closest('.fm-file-item'); + listEl.addEventListener('pointerdown', event => { + if (event.pointerType !== 'touch' || gesture) return; + const item = event.target.closest('.fm-file-item'); if (!item) return; - + const index = parseInt(item.dataset.index); + gesture = { + pane, + index, + item, + pointerId: event.pointerId, + startX: event.clientX, + startY: event.clientY, + moved: false, + longPressed: false, + }; longPressTimer = setTimeout(() => { - e.preventDefault(); - const index = parseInt(item.dataset.index); + if (!gesture || gesture.moved) return; + gesture.longPressed = true; this.setActivePane(pane); this.panes[pane].selected.clear(); - this.panes[pane].selected.add(index); + if (index >= 0) { + this.panes[pane].selected.add(index); + } this.updateSelectionVisual(pane); - this.showActionSheet(); + this.suppressItemClick(pane, index); + this.showActionSheet(pane, index, { + gestureActive: true, + trigger: item.querySelector('.fm-file-checkbox'), + }); }, longPressDuration); - }, { passive: false }); + }); - listEl.addEventListener('touchend', () => { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; - } + listEl.addEventListener('pointermove', event => { + if (!gesture || gesture.pointerId !== event.pointerId) return; + const deltaX = Math.abs(event.clientX - gesture.startX); + const deltaY = Math.abs(event.clientY - gesture.startY); + if (Math.max(deltaX, deltaY) <= movementTolerance) return; + gesture.moved = true; + clearLongPressTimer(); }); - listEl.addEventListener('touchmove', () => { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; + listEl.addEventListener('pointerup', event => { + if (!gesture || gesture.pointerId !== event.pointerId) return; + const completedGesture = gesture; + resetGesture(); + if (completedGesture.moved) return; + if (completedGesture.longPressed) { + this.releaseActionSheetGesture(); + return; } + const releasedItem = event.target.closest('.fm-file-item'); + if (releasedItem !== completedGesture.item) return; + this.handleTouchItemTap(event, pane, completedGesture.index); + this.suppressItemClick(pane, completedGesture.index); + }); + + listEl.addEventListener('pointercancel', () => { + const completedGesture = gesture; + resetGesture(); + if (completedGesture?.longPressed) this.releaseActionSheetGesture(); }); }); } - showActionSheet() { + showActionSheet( + pane = this.activePane, + index = null, + { gestureActive = false, trigger = null } = {}, + ) { const sheet = document.getElementById('fmActionSheet'); if (sheet) { + const state = this.panes[pane]; + const selectedCount = state?.selected?.size || 0; + const targetPane = pane === 'left' ? 'right' : 'left'; + const setDisabled = (action, disabled) => { + const button = sheet.querySelector(`[data-action="${action}"]`); + if (button) button.disabled = Boolean(disabled); + }; + this.actionSheetIndex = index; + this.actionSheetTrigger = trigger || document.activeElement; + setDisabled('open', index === null || (index >= 0 && selectedCount !== 1)); + setDisabled('download', selectedCount === 0 || !this.sourceCan(state, 'read')); + setDisabled('transfer', selectedCount === 0 || !this.canTransferBetweenPanes(pane, targetPane)); + setDisabled('rename', selectedCount !== 1 || !this.sourceCan(state, 'rename')); + setDisabled('newfolder', !this.sourceCan(state, 'mkdir')); + setDisabled('delete', selectedCount === 0 || !this.sourceCan(state, 'delete')); + sheet.classList.toggle('gesture-active', gestureActive); sheet.classList.add('visible'); + sheet.removeAttribute('inert'); + sheet.setAttribute('aria-hidden', 'false'); + if (!gestureActive) { + sheet.querySelector('.fm-action-sheet-item:not(:disabled)')?.focus(); + } } } + releaseActionSheetGesture() { + const sheet = document.getElementById('fmActionSheet'); + if (!sheet?.classList.contains('visible')) return; + setTimeout(() => { + if (!sheet.classList.contains('visible')) return; + sheet.classList.remove('gesture-active'); + sheet.querySelector('.fm-action-sheet-item:not(:disabled)')?.focus(); + }, 150); + } + hideActionSheet() { const sheet = document.getElementById('fmActionSheet'); + const trigger = this.actionSheetTrigger; if (sheet) { sheet.classList.remove('visible'); + sheet.classList.remove('gesture-active'); + sheet.setAttribute('inert', ''); + sheet.setAttribute('aria-hidden', 'true'); } + this.actionSheetIndex = null; + this.actionSheetTrigger = null; + if (trigger?.isConnected !== false) trigger?.focus?.(); } handleActionSheetAction(action) { + const actionSheetIndex = this.actionSheetIndex; this.hideActionSheet(); switch (action) { case 'open': const state = this.panes[this.activePane]; - if (state.selected.size === 1) { + if (Number.isInteger(actionSheetIndex)) { + this.handleItemDblClick(this.activePane, actionSheetIndex); + } else if (state.selected.size === 1) { const index = Array.from(state.selected)[0]; this.handleItemDblClick(this.activePane, index); } diff --git a/templates/admin.html b/templates/admin.html index 236f6cc..5a5ebdb 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -448,7 +448,7 @@

Final destru {% if not embedded %}
- + diff --git a/templates/change_password.html b/templates/change_password.html index f45793e..7d5ee05 100644 --- a/templates/change_password.html +++ b/templates/change_password.html @@ -119,7 +119,7 @@

Change Password

- + diff --git a/templates/index.html b/templates/index.html index f41fb77..6593534 100644 --- a/templates/index.html +++ b/templates/index.html @@ -18,8 +18,8 @@ - - + + @@ -1388,7 +1388,7 @@

File Preview

window.WEBSSH_TRANSFER_LIMITS = Object.freeze({{ transfer_limits | tojson }}); window.WEBSSH_SSH_INPUT_LIMITS = Object.freeze({{ ssh_input_limits | tojson }}); - + @@ -1423,7 +1423,7 @@

File Preview

- + diff --git a/templates/login.html b/templates/login.html index 146fd1e..4921065 100644 --- a/templates/login.html +++ b/templates/login.html @@ -320,7 +320,7 @@

Continue with GitHub

- + diff --git a/templates/register.html b/templates/register.html index 8923003..4472d10 100644 --- a/templates/register.html +++ b/templates/register.html @@ -136,7 +136,7 @@

Create a local WebSSH account

- + diff --git a/templates/security.html b/templates/security.html index 22dc4d5..127155c 100644 --- a/templates/security.html +++ b/templates/security.html @@ -412,7 +412,7 @@

Co
- + diff --git a/tests/e2e/file-workspace.spec.js b/tests/e2e/file-workspace.spec.js index 178ef18..8358bb2 100644 --- a/tests/e2e/file-workspace.spec.js +++ b/tests/e2e/file-workspace.spec.js @@ -376,3 +376,130 @@ test('splitting two single-view tabs distributes them across both file areas', a await expect(page.locator('#fmRightIdentity .fm-pane-label')).toHaveText('Right side'); await assertNoExternalRequests(page); }); + +test.describe('mobile touch File Manager', () => { + test.use({ + viewport: { width: 390, height: 844 }, + hasTouch: true, + isMobile: true, + }); + + test('opens folders, selects files, scrolls, and exposes long-press actions', async ({ page }) => { + await openWorkspaceWithSources(page); + await page.locator('[data-source-key="sftp-session:workspace-source"]').tap(); + await page.evaluate(() => { + const manager = window.sftpFileManager; + Object.assign(manager.panes.left, { + loading: false, + error: null, + autoHomeEligible: false, + pendingHomeRequestId: null, + path: '/srv/source', + files: [ + { name: 'archive', is_dir: true, size: 0 }, + ...Array.from({ length: 30 }, (_, index) => ({ + name: `report-${String(index).padStart(2, '0')}.txt`, + is_dir: false, + size: 12 + index, + })), + ], + selected: new Set(), + lastSelected: -1, + }); + manager.renderPane('left'); + }); + + const folder = page.locator('#fmLeftList .fm-file-item[data-index="0"]'); + await expect(folder).toHaveAttribute('draggable', 'false'); + await folder.tap(); + await expect.poll(() => page.evaluate( + () => window.sftpFileManager.panes.left.path, + )).toBe('/srv/source/archive'); + await page.evaluate(() => { + const manager = window.sftpFileManager; + Object.assign(manager.panes.left, { + loading: false, + pendingDirectoryRequestId: null, + pendingDirectoryPath: null, + files: [], + }); + manager.updatePathInput('left', manager.panes.left.path); + manager.renderPane('left'); + }); + + const parent = page.locator('#fmLeftList .fm-file-item[data-type="parent"]'); + await parent.tap(); + await expect.poll(() => page.evaluate( + () => window.sftpFileManager.panes.left.path, + )).toBe('/srv/source'); + + await page.evaluate(() => { + const manager = window.sftpFileManager; + Object.assign(manager.panes.left, { + loading: false, + pendingDirectoryRequestId: null, + pendingDirectoryPath: null, + files: [ + { name: 'archive', is_dir: true, size: 0 }, + ...Array.from({ length: 30 }, (_, index) => ({ + name: `report-${String(index).padStart(2, '0')}.txt`, + is_dir: false, + size: 12 + index, + })), + ], + }); + manager.updatePathInput('left', manager.panes.left.path); + manager.renderPane('left'); + }); + const file = page.locator('#fmLeftList .fm-file-item[data-index="1"]'); + await file.tap(); + await expect(file).toHaveClass(/selected/); + await file.locator('.fm-file-checkbox').tap(); + await expect(file).not.toHaveClass(/selected/); + + const list = page.locator('#fmLeftList'); + const listBox = await list.boundingBox(); + const client = await page.context().newCDPSession(page); + const x = listBox.x + (listBox.width / 2); + const startY = listBox.y + Math.min(listBox.height - 24, 300); + await client.send('Input.dispatchTouchEvent', { + type: 'touchStart', + touchPoints: [{ x, y: startY }], + }); + await client.send('Input.dispatchTouchEvent', { + type: 'touchMove', + touchPoints: [{ x, y: startY - 180 }], + }); + await client.send('Input.dispatchTouchEvent', { + type: 'touchEnd', + touchPoints: [], + }); + await expect.poll(() => list.evaluate(element => element.scrollTop)).toBeGreaterThan(0); + + const longPressFile = page.locator('#fmLeftList .fm-file-item[data-index="15"]'); + await longPressFile.scrollIntoViewIfNeeded(); + await page.waitForTimeout(300); + const longPressFileBox = await longPressFile.boundingBox(); + const fileX = longPressFileBox.x + (longPressFileBox.width / 2); + const fileY = longPressFileBox.y + (longPressFileBox.height / 2); + await client.send('Input.dispatchTouchEvent', { + type: 'touchStart', + touchPoints: [{ x: fileX, y: fileY }], + }); + await page.waitForTimeout(550); + await client.send('Input.dispatchTouchEvent', { + type: 'touchEnd', + touchPoints: [], + }); + await expect(page.locator('#fmActionSheet')).toHaveClass(/visible/); + await expect(page.locator('#fmActionSheet')).toHaveAttribute('aria-hidden', 'false'); + await expect(page.locator('#fmActionSheet [data-action="open"]')).toBeEnabled(); + await expect(page.locator('#fmActionSheet [data-action="download"]')).toBeEnabled(); + await expect(page.locator('#fmActionSheet [data-action="transfer"]')).toBeDisabled(); + await page.locator('#fmActionSheet [data-action="cancel"]').tap(); + await expect(page.locator('#fmActionSheet')).not.toHaveClass(/visible/); + await expect(page.locator('#fmActionSheet')).toHaveAttribute('aria-hidden', 'true'); + await expect(longPressFile.locator('.fm-file-checkbox')).toBeFocused(); + await assertNoExternalRequests(page); + }); +}); diff --git a/tests/js/sftp-transfer-queue.test.js b/tests/js/sftp-transfer-queue.test.js index c8d6f1a..5573ac9 100644 --- a/tests/js/sftp-transfer-queue.test.js +++ b/tests/js/sftp-transfer-queue.test.js @@ -1845,6 +1845,68 @@ test('file checkbox markup is keyboard accessible and double click never opens t assert.equal(opened, false); }); +test('coarse pointers use touch activation instead of native file dragging', () => { + const previousMatchMedia = global.window.matchMedia; + const manager = Object.create(SFTPFileManager.prototype); + + try { + global.window.matchMedia = query => ({ + matches: query === '(pointer: coarse)', + }); + assert.equal(manager.supportsNativeFileDrag(), false); + + global.window.matchMedia = () => ({ matches: false }); + assert.equal(manager.supportsNativeFileDrag(), true); + } finally { + if (previousMatchMedia === undefined) delete global.window.matchMedia; + else global.window.matchMedia = previousMatchMedia; + } +}); + +test('touch taps open folders while files and checkboxes remain selectable', () => { + const manager = Object.create(SFTPFileManager.prototype); + const opened = []; + const state = { + selected: new Set(), + lastSelected: -1, + files: [ + { name: 'archive', is_dir: true }, + { name: 'report.txt', is_dir: false }, + ], + }; + Object.assign(manager, { + activePane: 'left', + displayMode: 'embedded', + panes: { left: state }, + setActivePane() {}, + updateSelectionVisual() {}, + handleItemDblClick(pane, index) { opened.push([pane, index]); }, + }); + const rowEvent = { + target: { closest() { return null; } }, + preventDefault() {}, + stopPropagation() {}, + }; + + manager.handleTouchItemTap(rowEvent, 'left', 0); + assert.deepEqual(opened, [['left', 0]]); + assert.deepEqual([...state.selected], []); + + manager.handleTouchItemTap(rowEvent, 'left', 1); + assert.deepEqual([...state.selected], [1]); + + const checkboxEvent = { + target: { + closest(selector) { + return selector === '.fm-file-checkbox' ? this : null; + }, + }, + stopPropagation() {}, + }; + manager.handleTouchItemTap(checkboxEvent, 'left', 0); + assert.deepEqual([...state.selected].sort(), [0, 1]); +}); + test('an empty non-root folder still renders the parent directory action', () => { const previousDocument = global.document; let rendered = ''; diff --git a/tests/test_key_management_ui.py b/tests/test_key_management_ui.py index 19a4f1a..4a9906f 100644 --- a/tests/test_key_management_ui.py +++ b/tests/test_key_management_ui.py @@ -102,7 +102,7 @@ def test_key_replacement_event_updates_ui_and_asset_version(): assert "socket.on('key_replaced'" in APP assert 'ProfileManager.upsertKeySummary(data.key)' in APP assert "filename='js/profile-manager.js') }}?v=17" in TEMPLATE - assert "filename='js/i18n.js') }}?v=41" in TEMPLATE + assert "filename='js/i18n.js') }}?v=42" in TEMPLATE assert "filename='js/app.js') }}?v=28" in TEMPLATE assert "filename='css/style.css') }}?v=24" in TEMPLATE diff --git a/tests/test_profile_launcher_ui.py b/tests/test_profile_launcher_ui.py index c5bb255..30bd8bb 100644 --- a/tests/test_profile_launcher_ui.py +++ b/tests/test_profile_launcher_ui.py @@ -33,8 +33,8 @@ def test_merged_profile_frontend_assets_have_distinct_cache_versions(): template = read('templates/index.html') expected_versions = { "filename='css/style.css'": '?v=24', - "filename='css/sftp-file-manager.css'": '?v=18', - "filename='js/i18n.js'": '?v=41', + "filename='css/sftp-file-manager.css'": '?v=19', + "filename='js/i18n.js'": '?v=42', "filename='js/command-workspace.js'": '?v=3', "filename='js/command-palette-utils.js'": '?v=1', "filename='js/profile-launcher-utils.js'": '?v=5', @@ -45,7 +45,7 @@ def test_merged_profile_frontend_assets_have_distinct_cache_versions(): "filename='js/terminal-manager.js'": '?v=12', "filename='js/mobile-app-shell.js'": '?v=7', "filename='js/smb-source-dialog.js'": '?v=5', - "filename='js/sftp-file-manager.js'": '?v=33', + "filename='js/sftp-file-manager.js'": '?v=34', "filename='js/jump-host-manager.js'": '?v=4', "filename='js/command-library.js'": '?v=5', "filename='js/command-set-manager.js'": '?v=6', diff --git a/tests/test_webssh2_shell.py b/tests/test_webssh2_shell.py index e777bd2..4df79fa 100644 --- a/tests/test_webssh2_shell.py +++ b/tests/test_webssh2_shell.py @@ -119,7 +119,7 @@ def test_every_user_facing_page_uses_current_shared_asset_versions(app, client): assert response.status_code == 200 assert b'css/style.css?v=24' in response.data assert b'css/webssh-2.css?v=29' in response.data - assert b'js/i18n.js?v=41' in response.data + assert b'js/i18n.js?v=42' in response.data client.post("/logout") for path in ("/login", "/register"): @@ -127,7 +127,7 @@ def test_every_user_facing_page_uses_current_shared_asset_versions(app, client): assert response.status_code == 200 assert b'css/style.css?v=24' in response.data assert b'css/webssh-2.css?v=29' in response.data - assert b'js/i18n.js?v=41' in response.data + assert b'js/i18n.js?v=42' in response.data def test_compact_workspace_controls_keep_accessible_names_and_close_command_input(