test: add viewport synchronized-output regression coverage (fails until fix)

This commit is contained in:
mgabor3141
2026-03-12 18:50:41 +01:00
parent c294695444
commit f4042876c6
+161
View File
@@ -0,0 +1,161 @@
/**
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import jsdom = require('jsdom');
import { Viewport } from 'browser/Viewport';
import { MockThemeService } from 'browser/TestUtils.test';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { Emitter } from 'common/Event';
import { MockBufferService, MockCoreService, MockMouseStateService, MockOptionsService } from 'common/TestUtils.test';
import { css } from 'common/Color';
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService } from 'browser/services/Services';
class TestRenderService implements IRenderService {
public serviceBrand: undefined;
private readonly _onRender = new Emitter<{ start: number, end: number }>();
public onDimensionsChange = new Emitter<IRenderDimensions>().event;
public onRenderedViewportChange = new Emitter<{ start: number, end: number }>().event;
public onRender = this._onRender.event;
public onRefreshRequest = new Emitter<{ start: number, end: number }>().event;
public dimensions: IRenderDimensions = createRenderDimensions();
constructor() {
this.dimensions.css.cell.height = 10;
this.dimensions.css.canvas.height = 100;
}
public fireRender(): void {
this._onRender.fire({ start: 0, end: 0 });
}
public addRefreshCallback(callback: FrameRequestCallback): number {
callback(0);
return 1;
}
public refreshRows(start: number, end: number, sync?: boolean): void { }
public clearTextureAtlas(): void { }
public resize(cols: number, rows: number): void { }
public hasRenderer(): boolean { return true; }
public setRenderer(renderer: any): void { }
public handleDevicePixelRatioChange(): void { }
public handleResize(cols: number, rows: number): void { }
public handleCharSizeChanged(): void { }
public handleBlur(): void { }
public handleFocus(): void { }
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { }
public handleCursorMove(): void { }
public clear(): void { }
public dispose(): void { }
}
describe('Viewport', () => {
function setup(): {
viewport: Viewport;
renderService: TestRenderService;
coreService: MockCoreService;
dispose: () => void;
} {
const dom = new jsdom.JSDOM();
const previousWindow = (globalThis as any).window;
const previousDocument = (globalThis as any).document;
(globalThis as any).window = dom.window;
(globalThis as any).document = dom.window.document;
const element = dom.window.document.createElement('div');
const screenElement = dom.window.document.createElement('div');
const optionsService = new MockOptionsService();
const bufferService = new MockBufferService(80, 5, optionsService);
const coreBrowserService: ICoreBrowserService = {
serviceBrand: undefined,
isFocused: true,
onDprChange: new Emitter<number>().event,
onWindowChange: new Emitter<Window & typeof globalThis>().event,
window: dom.window as unknown as Window & typeof globalThis,
mainDocument: dom.window.document,
dpr: 1
};
const coreService = new MockCoreService();
const mouseStateService = new MockMouseStateService();
const themeService = new MockThemeService();
(themeService.colors as any).scrollbarSliderBackground = css.toColor('#333333');
(themeService.colors as any).scrollbarSliderHoverBackground = css.toColor('#444444');
(themeService.colors as any).scrollbarSliderActiveBackground = css.toColor('#555555');
const renderService = new TestRenderService();
const viewport = new Viewport(
element,
screenElement,
bufferService,
coreBrowserService,
coreService,
mouseStateService,
themeService,
optionsService,
renderService
);
return {
viewport,
renderService,
coreService,
dispose: () => {
viewport.dispose();
dom.window.close();
(globalThis as any).window = previousWindow;
(globalThis as any).document = previousDocument;
}
};
}
it('should defer DOM sync during synchronized output and flush on render', () => {
const test = setup();
let dimSyncCalls = 0;
let posSyncCalls = 0;
const scrollableElement = (test.viewport as any)._scrollableElement;
scrollableElement.setScrollDimensions = () => { dimSyncCalls++; };
scrollableElement.setScrollPosition = () => { posSyncCalls++; };
test.coreService.decPrivateModes.synchronizedOutput = true;
(test.viewport as any)._sync(3);
assert.equal(dimSyncCalls, 0);
assert.equal(posSyncCalls, 0);
assert.equal((test.viewport as any)._needsSyncOnRender, true);
test.coreService.decPrivateModes.synchronizedOutput = false;
test.renderService.fireRender();
assert.equal(dimSyncCalls, 1);
assert.equal(posSyncCalls, 1);
assert.equal((test.viewport as any)._needsSyncOnRender, false);
test.dispose();
});
it('should sync DOM immediately when synchronized output is disabled', () => {
const test = setup();
let dimSyncCalls = 0;
let posSyncCalls = 0;
const scrollableElement = (test.viewport as any)._scrollableElement;
scrollableElement.setScrollDimensions = () => { dimSyncCalls++; };
scrollableElement.setScrollPosition = () => { posSyncCalls++; };
test.coreService.decPrivateModes.synchronizedOutput = false;
(test.viewport as any)._sync(2);
assert.equal(dimSyncCalls, 1);
assert.equal(posSyncCalls, 1);
assert.equal((test.viewport as any)._needsSyncOnRender, false);
test.dispose();
});
});