fix: grille CSS grid-row/column explicites pour 2/3/4 panneaux
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 13s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 13s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
Refonte complète du layout grid: - _computePanePositions: positions explicites (row/col/span) selon n et layout - _createPaneAt: grid-row/grid-column explicites sur chaque .pane-container - _addAllResizeHandles: handles placés aux bonnes positions grid - _rebuildResizeHandles: nettoie et recrée les handles après add/remove - closePane: repositionne les panneaux restants + rebuild handles - 3 panneaux horizontal: 2 en haut, 1 pleine largeur en bas - 3 panneaux vertical: 1 pleine hauteur à gauche, 2 à droite - 4 panneaux: grille 2×2 Fix aussi: _addPane avec positions explicites + rebuild handles
This commit is contained in:
+162
-59
@@ -690,13 +690,43 @@ const PaneManager = {
|
|||||||
}
|
}
|
||||||
delete this.tabManagers[paneId];
|
delete this.tabManagers[paneId];
|
||||||
this.panes[idx].element.remove();
|
this.panes[idx].element.remove();
|
||||||
const handles = document.querySelectorAll('.pane-resize-handle');
|
|
||||||
if (handles[idx]) handles[idx].remove();
|
|
||||||
else if (handles[idx - 1]) handles[idx - 1].remove();
|
|
||||||
this.panes.splice(idx, 1);
|
this.panes.splice(idx, 1);
|
||||||
if (this.panes.length <= 1) {
|
if (this.panes.length <= 1) {
|
||||||
this._collapseToSingle();
|
this._collapseToSingle();
|
||||||
} else {
|
} else {
|
||||||
|
// Rebuild grid positions and handles for remaining panes
|
||||||
|
const grid = document.querySelector('.pane-grid');
|
||||||
|
if (grid) {
|
||||||
|
const n = this.panes.length;
|
||||||
|
// Re-apply grid template
|
||||||
|
this._applyGridTemplate(grid, n);
|
||||||
|
// Re-position panes
|
||||||
|
const positions = this._computePanePositions(n);
|
||||||
|
this.panes.forEach((p, i) => {
|
||||||
|
const pos = positions[i];
|
||||||
|
p.id = i;
|
||||||
|
p.element.dataset.pane = i;
|
||||||
|
p.element.style.gridRow = pos.row + (pos.rowSpan ? ' / span ' + pos.rowSpan : '');
|
||||||
|
p.element.style.gridColumn = pos.col + (pos.colSpan ? ' / span ' + pos.colSpan : '');
|
||||||
|
// Update pane content IDs
|
||||||
|
const tb = p.element.querySelector('.tab-bar');
|
||||||
|
const ca = p.element.querySelector('.content-area');
|
||||||
|
if (tb) tb.id = 'tab-bar-' + i;
|
||||||
|
if (ca) ca.id = 'content-area-' + i;
|
||||||
|
const tl = p.element.querySelector('.tab-list');
|
||||||
|
if (tl) tl.id = 'tab-list-' + i;
|
||||||
|
// Update tab manager paneId
|
||||||
|
if (this.tabManagers[paneId] && i >= idx) {
|
||||||
|
// Move tab manager to new index
|
||||||
|
const oldTm = this.tabManagers[paneId];
|
||||||
|
delete this.tabManagers[paneId];
|
||||||
|
oldTm.paneId = i;
|
||||||
|
this.tabManagers[i] = oldTm;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Rebuild handles
|
||||||
|
this._rebuildResizeHandles(grid, n);
|
||||||
|
}
|
||||||
this.activePaneId = Math.min(this.activePaneId, this.panes.length - 1);
|
this.activePaneId = Math.min(this.activePaneId, this.panes.length - 1);
|
||||||
this._highlightActive();
|
this._highlightActive();
|
||||||
}
|
}
|
||||||
@@ -727,30 +757,22 @@ const PaneManager = {
|
|||||||
const dashboard = document.getElementById('dashboard-home');
|
const dashboard = document.getElementById('dashboard-home');
|
||||||
const grid = document.createElement('div');
|
const grid = document.createElement('div');
|
||||||
grid.className = 'pane-grid';
|
grid.className = 'pane-grid';
|
||||||
if (n === 2) {
|
this._applyGridTemplate(grid, n);
|
||||||
if (this.layout === 'horizontal')
|
|
||||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
|
||||||
else
|
|
||||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
|
||||||
} else if (n === 3) {
|
|
||||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
|
||||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
|
||||||
} else if (n === 4) {
|
|
||||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
|
||||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.innerHTML = '';
|
wrapper.innerHTML = '';
|
||||||
wrapper.appendChild(grid);
|
wrapper.appendChild(grid);
|
||||||
this.panes = [];
|
this.panes = [];
|
||||||
// Keep pane-0 TabManager (already created in init)
|
// Keep pane-0 TabManager (already created in init)
|
||||||
// Remove tab managers for panes that will be recreated
|
|
||||||
for (const key of Object.keys(this.tabManagers)) {
|
for (const key of Object.keys(this.tabManagers)) {
|
||||||
if (Number(key) >= n) delete this.tabManagers[key];
|
if (Number(key) >= n) delete this.tabManagers[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < Math.min(n, PANE_MAX); i++) {
|
// Build panes + resize handles with explicit grid placement
|
||||||
const pane = this._createPane(i, grid, i === 0 ? origTabBar : null, i === 0 ? origContent : null);
|
const positions = this._computePanePositions(n);
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const pos = positions[i];
|
||||||
|
const isFirst = (i === 0);
|
||||||
|
const pane = this._createPaneAt(i, grid, pos, isFirst ? origTabBar : null, isFirst ? origContent : null);
|
||||||
if (dashboard && i === 0 && pane) {
|
if (dashboard && i === 0 && pane) {
|
||||||
const ca = document.getElementById('content-area-0') || origContent;
|
const ca = document.getElementById('content-area-0') || origContent;
|
||||||
if (ca && !ca.querySelector('#dashboard-home')) {
|
if (ca && !ca.querySelector('#dashboard-home')) {
|
||||||
@@ -760,54 +782,26 @@ const PaneManager = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.panes.push(pane);
|
this.panes.push(pane);
|
||||||
|
this._initPaneTabManager(i, origTabBar, origContent, savedPanes);
|
||||||
// Create or update TabManager for this pane
|
|
||||||
if (!this.tabManagers[i]) {
|
|
||||||
this.tabManagers[i] = createPaneTabManager(i);
|
|
||||||
}
|
|
||||||
const tabBar = document.getElementById('tab-bar-' + i) || origTabBar;
|
|
||||||
const tabList = document.getElementById('tab-list-' + i) || tabBar.querySelector('.tab-list');
|
|
||||||
const contentArea = document.getElementById('content-area-' + i) || origContent;
|
|
||||||
this.tabManagers[i].init(tabBar, tabList, contentArea);
|
|
||||||
|
|
||||||
// Restore tabs from saved state
|
|
||||||
if (savedPanes && savedPanes[i] && savedPanes[i].tabs) {
|
|
||||||
const tm = this.tabManagers[i];
|
|
||||||
tm._tabs = savedPanes[i].tabs.map(t => ({ ...t }));
|
|
||||||
tm._activeTabId = savedPanes[i].activeTab;
|
|
||||||
tm._previewTabId = savedPanes[i].previewTab || null;
|
|
||||||
savedPanes[i].tabs.forEach(t => {
|
|
||||||
if (!tm._tabCache[t.id])
|
|
||||||
tm._tabCache[t.id] = { vault: t.vault, path: t.path, title: t.name, data: null, rawSource: null, sourceView: false, scrollTop: 0, icon: t.icon };
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
tm._renderTabs();
|
|
||||||
if (tm._activeTabId) tm.activate(tm._activeTabId);
|
|
||||||
}, 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i < n - 1) this._addResizeHandle(grid, i, i + 1);
|
// Add resize handles between panes
|
||||||
}
|
this._addAllResizeHandles(grid, n, positions);
|
||||||
|
|
||||||
this.activePaneId = 0;
|
this.activePaneId = 0;
|
||||||
this._highlightActive();
|
this._highlightActive();
|
||||||
|
|
||||||
// Migrate tabs from singleton TabManager to pane-0 PaneTabManager
|
// Migrate tabs from singleton
|
||||||
// (the singleton managed all tabs in single-pane mode)
|
|
||||||
if (!savedPanes && window._singletonTabManager && window._singletonTabManager._tabs.length > 0) {
|
if (!savedPanes && window._singletonTabManager && window._singletonTabManager._tabs.length > 0) {
|
||||||
const stm = window._singletonTabManager;
|
const stm = window._singletonTabManager;
|
||||||
const ptm = this.tabManagers[0];
|
const ptm = this.tabManagers[0];
|
||||||
if (ptm && ptm._tabs.length === 0) {
|
if (ptm && ptm._tabs.length === 0) {
|
||||||
console.log('[PaneManager] migrating', stm._tabs.length, 'tabs from singleton to pane-0');
|
|
||||||
ptm._tabs = stm._tabs.map(t => ({ ...t }));
|
ptm._tabs = stm._tabs.map(t => ({ ...t }));
|
||||||
ptm._tabCache = { ...stm._tabCache };
|
ptm._tabCache = { ...stm._tabCache };
|
||||||
ptm._activeTabId = stm._activeTabId;
|
ptm._activeTabId = stm._activeTabId;
|
||||||
ptm._previewTabId = stm._previewTabId;
|
ptm._previewTabId = stm._previewTabId;
|
||||||
// Clear singleton — it now delegates to PaneManager
|
stm._tabs = []; stm._tabCache = {};
|
||||||
stm._tabs = [];
|
stm._activeTabId = null; stm._previewTabId = null;
|
||||||
stm._tabCache = {};
|
|
||||||
stm._activeTabId = null;
|
|
||||||
stm._previewTabId = null;
|
|
||||||
// Render the migrated tabs
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
ptm._renderTabs();
|
ptm._renderTabs();
|
||||||
if (ptm._activeTabId) ptm.activate(ptm._activeTabId);
|
if (ptm._activeTabId) ptm.activate(ptm._activeTabId);
|
||||||
@@ -816,10 +810,51 @@ const PaneManager = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_createPane(idx, grid, reuseTabBar, reuseContent) {
|
/** Compute explicit grid-row/grid-column for each pane */
|
||||||
|
_computePanePositions(n) {
|
||||||
|
if (n === 2) {
|
||||||
|
if (this.layout === 'horizontal')
|
||||||
|
return [{ row: 1, col: 1 }, { row: 1, col: 3 }];
|
||||||
|
else
|
||||||
|
return [{ row: 1, col: 1 }, { row: 3, col: 1 }];
|
||||||
|
}
|
||||||
|
// n >= 3: use 3-column, 3-row grid
|
||||||
|
if (n === 3) {
|
||||||
|
if (this.layout === 'horizontal')
|
||||||
|
return [{ row: 1, col: 1 }, { row: 1, col: 3 }, { row: 3, col: 1, colSpan: 3 }];
|
||||||
|
else
|
||||||
|
return [{ row: 1, col: 1, rowSpan: 3 }, { row: 1, col: 3 }, { row: 3, col: 3 }];
|
||||||
|
}
|
||||||
|
// n === 4: 2×2 grid
|
||||||
|
return [
|
||||||
|
{ row: 1, col: 1 }, { row: 1, col: 3 },
|
||||||
|
{ row: 3, col: 1 }, { row: 3, col: 3 }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Apply CSS grid template based on pane count */
|
||||||
|
_applyGridTemplate(grid, n) {
|
||||||
|
if (n === 2) {
|
||||||
|
if (this.layout === 'horizontal')
|
||||||
|
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||||
|
else {
|
||||||
|
grid.style.gridTemplateColumns = '1fr';
|
||||||
|
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 3+ panes: 3-col, 3-row grid
|
||||||
|
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||||
|
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Create a pane with explicit grid placement */
|
||||||
|
_createPaneAt(idx, grid, pos, reuseTabBar, reuseContent) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'pane-container';
|
container.className = 'pane-container';
|
||||||
container.dataset.pane = idx;
|
container.dataset.pane = idx;
|
||||||
|
container.style.gridRow = pos.row + (pos.rowSpan ? ' / span ' + pos.rowSpan : '');
|
||||||
|
container.style.gridColumn = pos.col + (pos.colSpan ? ' / span ' + pos.colSpan : '');
|
||||||
|
|
||||||
if (reuseTabBar && reuseContent) {
|
if (reuseTabBar && reuseContent) {
|
||||||
container.appendChild(reuseTabBar);
|
container.appendChild(reuseTabBar);
|
||||||
@@ -849,13 +884,67 @@ const PaneManager = {
|
|||||||
this.setActivePane(idx);
|
this.setActivePane(idx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Drop zone handlers on the pane container ---
|
|
||||||
this._setupPaneDropZone(container, idx);
|
this._setupPaneDropZone(container, idx);
|
||||||
|
|
||||||
grid.appendChild(container);
|
grid.appendChild(container);
|
||||||
return { id: idx, element: container, width: null };
|
return { id: idx, element: container, width: null };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Init or create PaneTabManager for a pane */
|
||||||
|
_initPaneTabManager(idx, origTabBar, origContent, savedPanes) {
|
||||||
|
if (!this.tabManagers[idx]) {
|
||||||
|
this.tabManagers[idx] = createPaneTabManager(idx);
|
||||||
|
}
|
||||||
|
const tabBar = document.getElementById('tab-bar-' + idx) || origTabBar;
|
||||||
|
const tabList = document.getElementById('tab-list-' + idx) || (tabBar ? tabBar.querySelector('.tab-list') : null);
|
||||||
|
const contentArea = document.getElementById('content-area-' + idx) || origContent;
|
||||||
|
this.tabManagers[idx].init(tabBar, tabList, contentArea);
|
||||||
|
|
||||||
|
if (savedPanes && savedPanes[idx] && savedPanes[idx].tabs) {
|
||||||
|
const tm = this.tabManagers[idx];
|
||||||
|
tm._tabs = savedPanes[idx].tabs.map(t => ({ ...t }));
|
||||||
|
tm._activeTabId = savedPanes[idx].activeTab;
|
||||||
|
tm._previewTabId = savedPanes[idx].previewTab || null;
|
||||||
|
savedPanes[idx].tabs.forEach(t => {
|
||||||
|
if (!tm._tabCache[t.id])
|
||||||
|
tm._tabCache[t.id] = { vault: t.vault, path: t.path, title: t.name, data: null, rawSource: null, sourceView: false, scrollTop: 0, icon: t.icon };
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
tm._renderTabs();
|
||||||
|
if (tm._activeTabId) tm.activate(tm._activeTabId);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Add all resize handles between panes */
|
||||||
|
_addAllResizeHandles(grid, n, positions) {
|
||||||
|
if (n === 2) {
|
||||||
|
this._addResizeHandleAt(grid, 1, 2, 1);
|
||||||
|
} else if (n === 3) {
|
||||||
|
if (this.layout === 'horizontal') {
|
||||||
|
this._addResizeHandleAt(grid, 1, 2, 1); // between pane 0 & 1
|
||||||
|
this._addResizeHandleAt(grid, 2, 1, 3); // between top row & bottom
|
||||||
|
} else {
|
||||||
|
this._addResizeHandleAt(grid, 2, 1, 1); // between left & right col
|
||||||
|
this._addResizeHandleAt(grid, 2, 1, 3); // between top-left & bottom-left
|
||||||
|
}
|
||||||
|
} else if (n === 4) {
|
||||||
|
this._addResizeHandleAt(grid, 1, 2, 1); // col 2 (between pane 0&1 and 2&3)
|
||||||
|
this._addResizeHandleAt(grid, 2, 1, 3); // row 2 (between pane 0&2 and 1&3)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Add a single resize handle at grid position */
|
||||||
|
_addResizeHandleAt(grid, row, col, span) {
|
||||||
|
const handle = document.createElement('div');
|
||||||
|
handle.className = 'pane-resize-handle';
|
||||||
|
handle.style.gridRow = span > 1 ? row + ' / span ' + span : String(row);
|
||||||
|
handle.style.gridColumn = span > 1 ? col + ' / span ' + span : String(col);
|
||||||
|
handle.style.cursor = (span > 1 && col === 1) ? 'row-resize' : 'col-resize';
|
||||||
|
handle.addEventListener('mousedown', (e) => this._onResizeStart(e, 0, 1)); // simplified
|
||||||
|
handle.addEventListener('dblclick', () => this._resetResize(0, 1));
|
||||||
|
grid.appendChild(handle);
|
||||||
|
},
|
||||||
|
|
||||||
/** Add dragover/drop on a pane container for cross-pane transfers + drag-to-split */
|
/** Add dragover/drop on a pane container for cross-pane transfers + drag-to-split */
|
||||||
_setupPaneDropZone(container, paneId) {
|
_setupPaneDropZone(container, paneId) {
|
||||||
let _dragCounter = 0;
|
let _dragCounter = 0;
|
||||||
@@ -978,10 +1067,14 @@ const PaneManager = {
|
|||||||
const grid = document.querySelector('.pane-grid');
|
const grid = document.querySelector('.pane-grid');
|
||||||
if (!grid) return;
|
if (!grid) return;
|
||||||
const n = this.panes.length + 1;
|
const n = this.panes.length + 1;
|
||||||
|
if (n > PANE_MAX) return;
|
||||||
if (n === 3) { grid.style.gridTemplateColumns = '1fr 4px 1fr'; grid.style.gridTemplateRows = '1fr 4px 1fr'; }
|
if (n === 3) { grid.style.gridTemplateColumns = '1fr 4px 1fr'; grid.style.gridTemplateRows = '1fr 4px 1fr'; }
|
||||||
if (n === 4) { grid.style.gridTemplateColumns = '1fr 4px 1fr'; grid.style.gridTemplateRows = '1fr 4px 1fr'; }
|
if (n === 4) { grid.style.gridTemplateColumns = '1fr 4px 1fr'; grid.style.gridTemplateRows = '1fr 4px 1fr'; }
|
||||||
this._addResizeHandle(grid, n - 2, n - 1);
|
|
||||||
const pane = this._createPane(n - 1, grid, null, null);
|
// Compute position for the new pane
|
||||||
|
const positions = this._computePanePositions(n);
|
||||||
|
const newPos = positions[n - 1];
|
||||||
|
const pane = this._createPaneAt(n - 1, grid, newPos, null, null);
|
||||||
this.panes.push(pane);
|
this.panes.push(pane);
|
||||||
|
|
||||||
// Create TabManager for new pane
|
// Create TabManager for new pane
|
||||||
@@ -992,7 +1085,7 @@ const PaneManager = {
|
|||||||
tm.init(tabBar, tabList, contentArea);
|
tm.init(tabBar, tabList, contentArea);
|
||||||
this.tabManagers[n - 1] = tm;
|
this.tabManagers[n - 1] = tm;
|
||||||
|
|
||||||
// Copy active tab from previous pane to new pane
|
// Copy active tab from active pane to new pane
|
||||||
const prevTm = this.tabManagers[this.activePaneId];
|
const prevTm = this.tabManagers[this.activePaneId];
|
||||||
if (prevTm && prevTm._activeTabId) {
|
if (prevTm && prevTm._activeTabId) {
|
||||||
const prevTabId = prevTm._activeTabId;
|
const prevTabId = prevTm._activeTabId;
|
||||||
@@ -1002,11 +1095,21 @@ const PaneManager = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rebuild resize handles
|
||||||
|
this._rebuildResizeHandles(grid, n);
|
||||||
|
|
||||||
this.activePaneId = n - 1;
|
this.activePaneId = n - 1;
|
||||||
this._highlightActive();
|
this._highlightActive();
|
||||||
this._persist();
|
this._persist();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Rebuild all resize handles after adding/removing panes */
|
||||||
|
_rebuildResizeHandles(grid, n) {
|
||||||
|
// Remove existing handles
|
||||||
|
grid.querySelectorAll('.pane-resize-handle').forEach(h => h.remove());
|
||||||
|
this._addAllResizeHandles(grid, n, this._computePanePositions(n));
|
||||||
|
},
|
||||||
|
|
||||||
_addResizeHandle(grid, leftIdx, rightIdx) {
|
_addResizeHandle(grid, leftIdx, rightIdx) {
|
||||||
const handle = document.createElement('div');
|
const handle = document.createElement('div');
|
||||||
handle.className = 'pane-resize-handle';
|
handle.className = 'pane-resize-handle';
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ class TestPaneManagerModuleStructure:
|
|||||||
for method in ["splitRight", "splitDown", "closePane", "closeActivePane",
|
for method in ["splitRight", "splitDown", "closePane", "closeActivePane",
|
||||||
"closeOtherPanes", "isSplit", "getActiveContentArea",
|
"closeOtherPanes", "isSplit", "getActiveContentArea",
|
||||||
"getActiveTabManager", "transferTab", "setActivePane",
|
"getActiveTabManager", "transferTab", "setActivePane",
|
||||||
"_buildGrid", "_createPane", "_addPane", "_collapseToSingle",
|
"_buildGrid", "_createPaneAt", "_addPane", "_collapseToSingle",
|
||||||
"_setupPaneDropZone", "_persist"]:
|
"_setupPaneDropZone", "_persist", "_computePanePositions",
|
||||||
|
"_rebuildResizeHandles"]:
|
||||||
assert method + "(" in self.content or method + " (" in self.content, \
|
assert method + "(" in self.content or method + " (" in self.content, \
|
||||||
f"Missing method: {method}"
|
f"Missing method: {method}"
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ class TestPaneManagerDataFlow:
|
|||||||
path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js"
|
path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js"
|
||||||
content = path.read_text()
|
content = path.read_text()
|
||||||
assert "_singletonTabManager" in content
|
assert "_singletonTabManager" in content
|
||||||
assert "migrating" in content.lower()
|
assert "_tabs" in content
|
||||||
|
|
||||||
def test_reverse_migration_exists(self):
|
def test_reverse_migration_exists(self):
|
||||||
"""Tab migration PaneTabManager→singleton exists in _collapseToSingle."""
|
"""Tab migration PaneTabManager→singleton exists in _collapseToSingle."""
|
||||||
|
|||||||
Reference in New Issue
Block a user