Skip to content

Commit f404287

Browse files
author
mgabor3141
committed
test: add viewport synchronized-output regression coverage (fails until fix)
1 parent c294695 commit f404287

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

src/browser/Viewport.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
3+
* @license MIT
4+
*/
5+
6+
import { assert } from 'chai';
7+
import jsdom = require('jsdom');
8+
import { Viewport } from 'browser/Viewport';
9+
import { MockThemeService } from 'browser/TestUtils.test';
10+
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
11+
import { Emitter } from 'common/Event';
12+
import { MockBufferService, MockCoreService, MockMouseStateService, MockOptionsService } from 'common/TestUtils.test';
13+
import { css } from 'common/Color';
14+
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
15+
import type { ICoreBrowserService, IRenderService } from 'browser/services/Services';
16+
17+
class TestRenderService implements IRenderService {
18+
public serviceBrand: undefined;
19+
private readonly _onRender = new Emitter<{ start: number, end: number }>();
20+
21+
public onDimensionsChange = new Emitter<IRenderDimensions>().event;
22+
public onRenderedViewportChange = new Emitter<{ start: number, end: number }>().event;
23+
public onRender = this._onRender.event;
24+
public onRefreshRequest = new Emitter<{ start: number, end: number }>().event;
25+
public dimensions: IRenderDimensions = createRenderDimensions();
26+
27+
constructor() {
28+
this.dimensions.css.cell.height = 10;
29+
this.dimensions.css.canvas.height = 100;
30+
}
31+
32+
public fireRender(): void {
33+
this._onRender.fire({ start: 0, end: 0 });
34+
}
35+
36+
public addRefreshCallback(callback: FrameRequestCallback): number {
37+
callback(0);
38+
return 1;
39+
}
40+
public refreshRows(start: number, end: number, sync?: boolean): void { }
41+
public clearTextureAtlas(): void { }
42+
public resize(cols: number, rows: number): void { }
43+
public hasRenderer(): boolean { return true; }
44+
public setRenderer(renderer: any): void { }
45+
public handleDevicePixelRatioChange(): void { }
46+
public handleResize(cols: number, rows: number): void { }
47+
public handleCharSizeChanged(): void { }
48+
public handleBlur(): void { }
49+
public handleFocus(): void { }
50+
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { }
51+
public handleCursorMove(): void { }
52+
public clear(): void { }
53+
public dispose(): void { }
54+
}
55+
56+
describe('Viewport', () => {
57+
function setup(): {
58+
viewport: Viewport;
59+
renderService: TestRenderService;
60+
coreService: MockCoreService;
61+
dispose: () => void;
62+
} {
63+
const dom = new jsdom.JSDOM();
64+
const previousWindow = (globalThis as any).window;
65+
const previousDocument = (globalThis as any).document;
66+
(globalThis as any).window = dom.window;
67+
(globalThis as any).document = dom.window.document;
68+
69+
const element = dom.window.document.createElement('div');
70+
const screenElement = dom.window.document.createElement('div');
71+
72+
const optionsService = new MockOptionsService();
73+
const bufferService = new MockBufferService(80, 5, optionsService);
74+
const coreBrowserService: ICoreBrowserService = {
75+
serviceBrand: undefined,
76+
isFocused: true,
77+
onDprChange: new Emitter<number>().event,
78+
onWindowChange: new Emitter<Window & typeof globalThis>().event,
79+
window: dom.window as unknown as Window & typeof globalThis,
80+
mainDocument: dom.window.document,
81+
dpr: 1
82+
};
83+
84+
const coreService = new MockCoreService();
85+
const mouseStateService = new MockMouseStateService();
86+
const themeService = new MockThemeService();
87+
(themeService.colors as any).scrollbarSliderBackground = css.toColor('#333333');
88+
(themeService.colors as any).scrollbarSliderHoverBackground = css.toColor('#444444');
89+
(themeService.colors as any).scrollbarSliderActiveBackground = css.toColor('#555555');
90+
const renderService = new TestRenderService();
91+
92+
const viewport = new Viewport(
93+
element,
94+
screenElement,
95+
bufferService,
96+
coreBrowserService,
97+
coreService,
98+
mouseStateService,
99+
themeService,
100+
optionsService,
101+
renderService
102+
);
103+
104+
return {
105+
viewport,
106+
renderService,
107+
coreService,
108+
dispose: () => {
109+
viewport.dispose();
110+
dom.window.close();
111+
(globalThis as any).window = previousWindow;
112+
(globalThis as any).document = previousDocument;
113+
}
114+
};
115+
}
116+
117+
it('should defer DOM sync during synchronized output and flush on render', () => {
118+
const test = setup();
119+
let dimSyncCalls = 0;
120+
let posSyncCalls = 0;
121+
122+
const scrollableElement = (test.viewport as any)._scrollableElement;
123+
scrollableElement.setScrollDimensions = () => { dimSyncCalls++; };
124+
scrollableElement.setScrollPosition = () => { posSyncCalls++; };
125+
126+
test.coreService.decPrivateModes.synchronizedOutput = true;
127+
(test.viewport as any)._sync(3);
128+
129+
assert.equal(dimSyncCalls, 0);
130+
assert.equal(posSyncCalls, 0);
131+
assert.equal((test.viewport as any)._needsSyncOnRender, true);
132+
133+
test.coreService.decPrivateModes.synchronizedOutput = false;
134+
test.renderService.fireRender();
135+
136+
assert.equal(dimSyncCalls, 1);
137+
assert.equal(posSyncCalls, 1);
138+
assert.equal((test.viewport as any)._needsSyncOnRender, false);
139+
140+
test.dispose();
141+
});
142+
143+
it('should sync DOM immediately when synchronized output is disabled', () => {
144+
const test = setup();
145+
let dimSyncCalls = 0;
146+
let posSyncCalls = 0;
147+
148+
const scrollableElement = (test.viewport as any)._scrollableElement;
149+
scrollableElement.setScrollDimensions = () => { dimSyncCalls++; };
150+
scrollableElement.setScrollPosition = () => { posSyncCalls++; };
151+
152+
test.coreService.decPrivateModes.synchronizedOutput = false;
153+
(test.viewport as any)._sync(2);
154+
155+
assert.equal(dimSyncCalls, 1);
156+
assert.equal(posSyncCalls, 1);
157+
assert.equal((test.viewport as any)._needsSyncOnRender, false);
158+
159+
test.dispose();
160+
});
161+
});

0 commit comments

Comments
 (0)