mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
- A floating portable panel saved its measured box, so dragging a collapsed window stored its 50 px header strip and a reload clamped it to the minimum. It now saves the inline size the user chose. A unit test and a collapse -> drag -> reload -> expand step in the panel gate pin it. - Coverage awaits the tile fetch and the one-time terrain table load together, so a tile that fails before the table loads is reported at once instead of surfacing as an unhandled rejection. Co-Authored-By: Claude Opus 5.5 <[email protected]>
581 lines
19 KiB
JavaScript
581 lines
19 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
import { PanelPositionControls } from './panelPositionControls.js';
|
||
|
||
/** Just enough DOM for PanelPositionControls' portable-panel paths. */
|
||
function fixture() {
|
||
const saved = Object.fromEntries(
|
||
['document', 'window', 'localStorage', 'performance', 'ResizeObserver'].map(
|
||
(key) => [key, globalThis[key]],
|
||
),
|
||
);
|
||
const store = new Map();
|
||
const element = () => {
|
||
const classes = new Set();
|
||
const style = {
|
||
removeProperty(name) {
|
||
// Like CSSStyleDeclaration: 'z-index' also clears style.zIndex.
|
||
delete this[name];
|
||
delete this[name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())];
|
||
},
|
||
getPropertyValue: () => '',
|
||
};
|
||
return Object.assign(new EventTarget(), {
|
||
id: '',
|
||
style,
|
||
dataset: {},
|
||
classList: {
|
||
add: (...names) => names.forEach((name) => classes.add(name)),
|
||
remove: (...names) => names.forEach((name) => classes.delete(name)),
|
||
contains: (name) => classes.has(name),
|
||
toggle: (name, on) => (on ? classes.add(name) : classes.delete(name)),
|
||
},
|
||
querySelector: () => null,
|
||
querySelectorAll: () => [],
|
||
closest: () => null,
|
||
setAttribute() {},
|
||
removeAttribute() {},
|
||
appendChild() {},
|
||
remove() {},
|
||
getBoundingClientRect: () => ({
|
||
left: 100,
|
||
top: 80,
|
||
width: 360,
|
||
height: 420,
|
||
right: 460,
|
||
bottom: 500,
|
||
}),
|
||
});
|
||
};
|
||
globalThis.document = Object.assign(new EventTarget(), {
|
||
getElementById: () => null,
|
||
querySelectorAll: () => [],
|
||
createElement: () => element(),
|
||
});
|
||
globalThis.window = Object.assign(new EventTarget(), {
|
||
innerWidth: 1440,
|
||
innerHeight: 900,
|
||
});
|
||
globalThis.localStorage = {
|
||
getItem: (key) => (store.has(key) ? store.get(key) : null),
|
||
setItem: (key, value) => store.set(key, String(value)),
|
||
removeItem: (key) => store.delete(key),
|
||
};
|
||
const calls = { layout: 0, resized: [] };
|
||
const owner = new PanelPositionControls({
|
||
syncPanelCollapseButton() {},
|
||
layoutRightPanels: () => calls.layout++,
|
||
syncCctvPanelViewport() {},
|
||
showToast() {},
|
||
onPanelResized: (id) => calls.resized.push(id),
|
||
});
|
||
return {
|
||
owner,
|
||
element,
|
||
store,
|
||
calls,
|
||
restore() {
|
||
owner.destroy();
|
||
Object.assign(globalThis, saved);
|
||
},
|
||
};
|
||
}
|
||
|
||
function floatingPanel(f, id, { dockOnCollapse = false } = {}) {
|
||
const panel = f.element();
|
||
const handle = f.element();
|
||
panel.id = id;
|
||
panel.classList.add('panel-floating', 'panel-draggable');
|
||
Object.assign(panel.style, {
|
||
left: '100px',
|
||
top: '80px',
|
||
width: '360px',
|
||
height: '420px',
|
||
});
|
||
f.store.set(`godsEyeView.v8.panelPos.${id}`, '{"floating":true}');
|
||
f.owner._portablePanels.set(id, {
|
||
panel,
|
||
min: { width: 320, height: 280 },
|
||
dockOnCollapse,
|
||
});
|
||
f.owner._makePanelDraggable(id, panel, handle);
|
||
return { panel, handle };
|
||
}
|
||
|
||
/** A left-button header press; `timeStamp` is a read-only getter on Event. */
|
||
function press(handle, { timeStamp, clientX = 200, clientY = 90 }) {
|
||
const event = new Event('pointerdown', { cancelable: true });
|
||
for (const [key, value] of Object.entries({
|
||
button: 0,
|
||
clientX,
|
||
clientY,
|
||
timeStamp,
|
||
}))
|
||
Object.defineProperty(event, key, { value });
|
||
handle.dispatchEvent(event);
|
||
}
|
||
|
||
test('dockPanel returns a floating window to its rail and forgets its place', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel } = floatingPanel(f, 'street-level-panel');
|
||
assert.equal(f.owner.dockPanel('street-level-panel'), true);
|
||
assert.equal(panel.classList.contains('panel-floating'), false);
|
||
assert.equal(panel.style.width, undefined);
|
||
assert.equal(panel.style.height, undefined);
|
||
assert.equal(
|
||
f.store.has('godsEyeView.v8.panelPos.street-level-panel'),
|
||
false,
|
||
);
|
||
assert.deepEqual(f.calls.resized, ['street-level-panel']);
|
||
assert.equal(
|
||
f.owner.dockPanel('street-level-panel'),
|
||
false,
|
||
'already docked',
|
||
);
|
||
assert.equal(f.owner.dockPanel('unknown'), false);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('only a dockOnCollapse panel docks when collapsed, and only on collapse', () => {
|
||
const f = fixture();
|
||
try {
|
||
const street = floatingPanel(f, 'street-level-panel', {
|
||
dockOnCollapse: true,
|
||
});
|
||
const cctv = floatingPanel(f, 'cctv-panel');
|
||
f.owner.onPanelCollapsed('street-level-panel', false);
|
||
assert.equal(
|
||
street.panel.classList.contains('panel-floating'),
|
||
true,
|
||
'expanding keeps it',
|
||
);
|
||
f.owner.onPanelCollapsed('cctv-panel', true);
|
||
assert.equal(
|
||
cctv.panel.classList.contains('panel-floating'),
|
||
true,
|
||
'CCTV did not opt in',
|
||
);
|
||
f.owner.onPanelCollapsed('street-level-panel', true);
|
||
assert.equal(street.panel.classList.contains('panel-floating'), false);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('two quick header presses snap a floating panel back; slow or distant ones do not', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, handle } = floatingPanel(f, 'cctv-panel');
|
||
press(handle, { timeStamp: 1000 });
|
||
window.dispatchEvent(new Event('pointerup'));
|
||
press(handle, { timeStamp: 1600 });
|
||
window.dispatchEvent(new Event('pointerup'));
|
||
assert.equal(
|
||
panel.classList.contains('panel-floating'),
|
||
true,
|
||
'600 ms apart is two clicks',
|
||
);
|
||
press(handle, { timeStamp: 1700, clientX: 260 });
|
||
window.dispatchEvent(new Event('pointerup'));
|
||
assert.equal(
|
||
panel.classList.contains('panel-floating'),
|
||
true,
|
||
'60 px apart is two clicks',
|
||
);
|
||
press(handle, { timeStamp: 1900, clientX: 262 });
|
||
assert.equal(
|
||
panel.classList.contains('panel-floating'),
|
||
false,
|
||
'a double press docks',
|
||
);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a restored floating window stacks above the docked DISPLAY panel (z 110)', () => {
|
||
const f = fixture();
|
||
try {
|
||
const panel = f.element();
|
||
panel.id = 'street-level-panel';
|
||
f.owner._portablePanels.set('street-level-panel', {
|
||
panel,
|
||
min: { width: 320, height: 280 },
|
||
dockOnCollapse: true,
|
||
});
|
||
f.store.set(
|
||
'godsEyeView.v8.panelPos.street-level-panel',
|
||
JSON.stringify({
|
||
left: 900,
|
||
top: 200,
|
||
width: 420,
|
||
height: 600,
|
||
floating: true,
|
||
}),
|
||
);
|
||
f.owner._restorePanelPosition('street-level-panel', panel);
|
||
assert.equal(panel.classList.contains('panel-floating'), true);
|
||
assert.ok(Number(panel.style.zIndex) > 110, `z ${panel.style.zIndex}`);
|
||
f.owner.dockPanel('street-level-panel');
|
||
assert.equal(panel.style.zIndex, undefined, 'docking drops the promotion');
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('renumbering the z ladder never drops a window below the docked panels', () => {
|
||
const f = fixture();
|
||
try {
|
||
const windows = [f.element(), f.element(), f.element()];
|
||
windows.forEach((node, index) => {
|
||
node.classList.add('panel-draggable');
|
||
node.style.zIndex = String(130 + index);
|
||
});
|
||
globalThis.document.querySelectorAll = (selector) =>
|
||
selector === '.panel-draggable' ? windows : [];
|
||
f.owner._panelZCounter = 139;
|
||
const top = f.element();
|
||
top.classList.add('panel-draggable');
|
||
f.owner._promotePanelZ(top);
|
||
for (const node of [...windows, top])
|
||
assert.ok(Number(node.style.zIndex) > 110, `z ${node.style.zIndex}`);
|
||
assert.ok(Number(top.style.zIndex) > Number(windows[2].style.zIndex));
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
/** A portable panel still in its rail, with its header and resize handles. */
|
||
function dockedPanel(f, id) {
|
||
const panel = f.element();
|
||
const handle = f.element();
|
||
panel.id = id;
|
||
const portable = { panel, min: { width: 320, height: 280 } };
|
||
f.owner._portablePanels.set(id, portable);
|
||
f.owner._makePanelDraggable(id, panel, handle);
|
||
f.owner._makePanelResizable(id, panel, portable.min);
|
||
const edges = Object.fromEntries(
|
||
f.owner._resizeHandles.map((node) => [node.dataset.dir, node]),
|
||
);
|
||
return { panel, handle, edges };
|
||
}
|
||
|
||
/** A pointer event; `pointerType` defaults to what a mouse reports. */
|
||
function pointer(target, type, { x = 200, y = 90, pointerType = 'mouse' }) {
|
||
const event = new Event(type, { cancelable: true });
|
||
for (const [key, value] of Object.entries({
|
||
button: 0,
|
||
clientX: x,
|
||
clientY: y,
|
||
pointerType,
|
||
}))
|
||
Object.defineProperty(event, key, { value });
|
||
target.dispatchEvent(event);
|
||
return event;
|
||
}
|
||
|
||
const POSITION_KEY = 'godsEyeView.v8.panelPos.street-level-panel';
|
||
|
||
test('a click on a docked panel’s resize edge leaves it docked and stores nothing', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, edges } = dockedPanel(f, 'street-level-panel');
|
||
pointer(edges.w, 'pointerdown', { x: 101, y: 200 });
|
||
pointer(window, 'pointermove', { x: 103, y: 201 }); // a jittery click
|
||
pointer(window, 'pointerup', { x: 103, y: 201 });
|
||
assert.equal(panel.classList.contains('panel-floating'), false);
|
||
assert.equal(panel.style.width, undefined, 'no size was pinned');
|
||
assert.equal(f.store.has(POSITION_KEY), false);
|
||
assert.deepEqual(f.calls.resized, []);
|
||
|
||
// A real resize still lifts it out and remembers the window.
|
||
pointer(edges.w, 'pointerdown', { x: 101, y: 200 });
|
||
pointer(window, 'pointermove', { x: 61, y: 200 });
|
||
pointer(window, 'pointerup', { x: 61, y: 200 });
|
||
assert.equal(panel.classList.contains('panel-floating'), true);
|
||
assert.equal(JSON.parse(f.store.get(POSITION_KEY)).floating, true);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a touch on the header or an edge does not lift the panel, and the rail can still scroll', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, handle, edges } = dockedPanel(f, 'street-level-panel');
|
||
const press = pointer(handle, 'pointerdown', { pointerType: 'touch' });
|
||
assert.equal(press.defaultPrevented, false, 'the swipe can scroll');
|
||
pointer(window, 'pointermove', { y: 190, pointerType: 'touch' });
|
||
pointer(window, 'pointerup', { y: 190, pointerType: 'touch' });
|
||
pointer(edges.s, 'pointerdown', { y: 500, pointerType: 'touch' });
|
||
pointer(window, 'pointermove', { y: 600, pointerType: 'touch' });
|
||
pointer(window, 'pointerup', { y: 600, pointerType: 'touch' });
|
||
assert.equal(panel.classList.contains('panel-floating'), false);
|
||
assert.equal(f.store.has(POSITION_KEY), false);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a pointercancel mid-drag saves nothing and puts the panel back', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, handle, edges } = dockedPanel(f, 'street-level-panel');
|
||
pointer(handle, 'pointerdown', {});
|
||
pointer(window, 'pointermove', { x: 260, y: 140 });
|
||
assert.equal(panel.classList.contains('panel-floating'), true, 'lifted');
|
||
pointer(window, 'pointercancel', { x: 260, y: 140 });
|
||
assert.equal(panel.classList.contains('panel-floating'), false, 'docked');
|
||
assert.equal(f.store.has(POSITION_KEY), false);
|
||
|
||
pointer(edges.e, 'pointerdown', { x: 459 });
|
||
pointer(window, 'pointermove', { x: 520 });
|
||
pointer(window, 'pointercancel', { x: 520 });
|
||
assert.equal(panel.classList.contains('panel-floating'), false);
|
||
assert.equal(f.store.has(POSITION_KEY), false);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a pointercancel while dragging a floating window returns it to where it was', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, handle } = floatingPanel(f, 'cctv-panel');
|
||
const stored = f.store.get('godsEyeView.v8.panelPos.cctv-panel');
|
||
pointer(handle, 'pointerdown', {});
|
||
pointer(window, 'pointermove', { x: 400, y: 300 });
|
||
assert.notEqual(panel.style.left, '100px', 'it moved');
|
||
pointer(window, 'pointercancel', { x: 400, y: 300 });
|
||
assert.equal(panel.style.left, '100px');
|
||
assert.equal(panel.style.top, '80px');
|
||
assert.equal(panel.classList.contains('panel-floating'), true);
|
||
assert.equal(f.store.get('godsEyeView.v8.panelPos.cctv-panel'), stored);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a header press that travels under the drag threshold is a click, not a lift', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, handle } = dockedPanel(f, 'street-level-panel');
|
||
pointer(handle, 'pointerdown', { x: 200, y: 90 });
|
||
pointer(window, 'pointermove', { x: 202, y: 90 });
|
||
pointer(window, 'pointerup', { x: 202, y: 90 });
|
||
assert.equal(panel.classList.contains('panel-floating'), false);
|
||
assert.equal(panel.classList.contains('panel-dragging'), false);
|
||
assert.equal(f.store.has(POSITION_KEY), false);
|
||
assert.equal(f.calls.layout, 0, 'the rail was not re-laid out');
|
||
assert.deepEqual(f.calls.resized, []);
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
/**
|
||
* Both portable panels wired through _initPanelDrag. Headers are the top
|
||
* 36 px; `obstacles` stand in for the command dock and voice pill.
|
||
* ResizeObserver callbacks run on `observe()`.
|
||
*/
|
||
function portableShell(f, { stored = {}, box = {}, obstacles = {} } = {}) {
|
||
const observers = [];
|
||
globalThis.ResizeObserver = class {
|
||
constructor(callback) {
|
||
this.callback = callback;
|
||
this.targets = [];
|
||
observers.push(this);
|
||
}
|
||
observe(target) {
|
||
this.targets.push(target);
|
||
}
|
||
disconnect() {}
|
||
};
|
||
const nodes = new Map();
|
||
const panels = {};
|
||
for (const id of ['cctv-panel', 'street-level-panel']) {
|
||
const panel = f.element();
|
||
const handle = f.element();
|
||
const natural = { width: 360, height: 420, ...box[id] };
|
||
panel.id = id;
|
||
panel.querySelector = (selector) =>
|
||
selector === '.panel-header' ? handle : null;
|
||
panel.getBoundingClientRect = () => {
|
||
const left = parseFloat(panel.style.left) || 0;
|
||
const top = parseFloat(panel.style.top) || 0;
|
||
const width = parseFloat(panel.style.width) || natural.width;
|
||
const height = parseFloat(panel.style.height) || natural.height;
|
||
return {
|
||
left,
|
||
top,
|
||
width,
|
||
height,
|
||
right: left + width,
|
||
bottom: top + height,
|
||
};
|
||
};
|
||
handle.getBoundingClientRect = () => {
|
||
const rect = panel.getBoundingClientRect();
|
||
return { ...rect, height: 36, bottom: rect.top + 36 };
|
||
};
|
||
if (stored[id])
|
||
f.store.set(
|
||
`godsEyeView.v8.panelPos.${id}`,
|
||
JSON.stringify({ floating: true, ...stored[id] }),
|
||
);
|
||
nodes.set(id, panel);
|
||
panels[id] = { panel, handle, natural };
|
||
}
|
||
const obstacle = (id, rect) =>
|
||
nodes.set(id, {
|
||
getBoundingClientRect: () => ({
|
||
...rect,
|
||
width: rect.right - rect.left,
|
||
height: rect.bottom - rect.top,
|
||
}),
|
||
});
|
||
for (const [id, rect] of Object.entries(obstacles)) obstacle(id, rect);
|
||
globalThis.document.getElementById = (id) => nodes.get(id) ?? null;
|
||
f.owner._initPanelDrag();
|
||
return {
|
||
...panels,
|
||
obstacle,
|
||
observed: (node) => observers.some(({ targets }) => targets.includes(node)),
|
||
contentResized: () => observers.forEach(({ callback }) => callback([])),
|
||
};
|
||
}
|
||
|
||
test('a floating window whose content grows is pulled back on-screen', () => {
|
||
const f = fixture();
|
||
try {
|
||
const shell = portableShell(f, {
|
||
stored: { 'cctv-panel': { left: 900, top: 380 } },
|
||
});
|
||
const { panel, natural } = shell['cctv-panel'];
|
||
assert.equal(panel.style.top, '380px');
|
||
assert.equal(shell.observed(panel), true, 'its size is watched');
|
||
assert.equal(shell.observed(shell['street-level-panel'].panel), true);
|
||
// Expanding the collapsed strip, or an image opening in Street Level.
|
||
natural.height = 800;
|
||
shell.contentResized();
|
||
assert.equal(panel.style.top, `${900 - 800 - 6}px`);
|
||
assert.equal(panel.style.left, '900px');
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a live window resize shrinks a floating window larger than the viewport', () => {
|
||
const f = fixture();
|
||
try {
|
||
const shell = portableShell(f, {
|
||
stored: {
|
||
'cctv-panel': { left: 100, top: 80, width: 1300, height: 700 },
|
||
},
|
||
});
|
||
const { panel } = shell['cctv-panel'];
|
||
assert.equal(panel.style.width, '1300px');
|
||
Object.assign(window, { innerWidth: 1000, innerHeight: 500 });
|
||
window.dispatchEvent(new Event('resize'));
|
||
assert.equal(panel.style.width, '988px');
|
||
assert.equal(panel.style.height, '488px');
|
||
assert.equal(panel.style.left, '6px');
|
||
assert.equal(panel.style.top, '6px');
|
||
// A window that already fits keeps the size the user gave it.
|
||
Object.assign(window, { innerWidth: 1440, innerHeight: 900 });
|
||
window.dispatchEvent(new Event('resize'));
|
||
assert.equal(panel.style.width, '988px');
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
const DOCK = { left: 300, top: 820, right: 1140, bottom: 882 };
|
||
|
||
test('a floating header never lands under the command dock or voice pill', () => {
|
||
const f = fixture();
|
||
try {
|
||
const shell = portableShell(f, {
|
||
stored: { 'cctv-panel': { left: 100, top: 80 } },
|
||
// A collapsed CCTV window is just its header strip.
|
||
box: { 'cctv-panel': { height: 36 } },
|
||
obstacles: { 'command-dock': DOCK },
|
||
});
|
||
const { panel, handle, natural } = shell['cctv-panel'];
|
||
const above = DOCK.top - 36 - 6;
|
||
|
||
// Drag: the strip dropped at the bottom centre stops above the dock.
|
||
pointer(handle, 'pointerdown', { x: 200, y: 90 });
|
||
pointer(window, 'pointermove', { x: 720, y: 880 });
|
||
assert.equal(panel.style.top, `${above}px`);
|
||
pointer(window, 'pointerup', { x: 720, y: 880 });
|
||
const key = 'godsEyeView.v8.panelPos.cctv-panel';
|
||
assert.equal(JSON.parse(f.store.get(key)).top, above);
|
||
|
||
// Restore: a place saved under the dock (older build) comes back clear.
|
||
f.store.set(key, JSON.stringify({ left: 620, top: 858, floating: true }));
|
||
f.owner._restorePanelPosition('cctv-panel', panel);
|
||
assert.equal(panel.style.top, `${above}px`);
|
||
|
||
// Re-clamp: the voice pill measured when the window resizes.
|
||
Object.assign(panel.style, { left: '1000px', top: '700px' });
|
||
shell.obstacle('gev-voice-control', {
|
||
left: 1190,
|
||
top: 690,
|
||
right: 1404,
|
||
bottom: 740,
|
||
});
|
||
window.dispatchEvent(new Event('resize'));
|
||
assert.equal(panel.style.top, `${690 - 36 - 6}px`);
|
||
|
||
// Only the header has to stay clear: a body over the dock is fine.
|
||
natural.height = 200;
|
||
Object.assign(panel.style, { left: '620px', top: '650px' });
|
||
window.dispatchEvent(new Event('resize'));
|
||
assert.equal(panel.style.top, '650px');
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|
||
|
||
test('a collapsed floating window keeps its chosen height through a drag and a reload', () => {
|
||
const f = fixture();
|
||
try {
|
||
const { panel, handle } = floatingPanel(f, 'cctv-panel');
|
||
panel.style.height = '500px';
|
||
panel.classList.add('collapsed');
|
||
// Collapsed, the window measures only its header strip.
|
||
panel.getBoundingClientRect = () => {
|
||
const left = parseFloat(panel.style.left);
|
||
const top = parseFloat(panel.style.top);
|
||
return {
|
||
left,
|
||
top,
|
||
width: 360,
|
||
height: 50,
|
||
right: left + 360,
|
||
bottom: top + 50,
|
||
};
|
||
};
|
||
pointer(handle, 'pointerdown', { x: 200, y: 90 });
|
||
pointer(window, 'pointermove', { x: 260, y: 140 });
|
||
pointer(window, 'pointerup', { x: 260, y: 140 });
|
||
const record = JSON.parse(
|
||
f.store.get('godsEyeView.v8.panelPos.cctv-panel'),
|
||
);
|
||
assert.equal(record.height, 500, 'the chosen height, not the strip');
|
||
assert.equal(record.width, 360);
|
||
|
||
// Reload: a fresh panel restores the size it expands to.
|
||
const reloaded = f.element();
|
||
reloaded.id = 'cctv-panel';
|
||
f.owner._restorePanelPosition('cctv-panel', reloaded);
|
||
assert.equal(reloaded.style.height, '500px');
|
||
} finally {
|
||
f.restore();
|
||
}
|
||
});
|