blob: 18a0a59c393070023a302c71f4f2a5769ce4dbf4 [file] [log] [blame]
Mike Frysinger598e8012022-09-07 08:38:34 -04001// Copyright 2022 The ChromiumOS Authors
Jason Linabad7562022-08-22 14:49:05 +10002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Unit tests for terminal_emulator.js.
7 */
8
Jason Linc0f14fe2022-10-25 15:31:29 +11009import {hterm, lib} from './deps_local.concat.js';
Mike Frysinger75895da2022-10-04 00:42:28 +054510
Jason Linabad7562022-08-22 14:49:05 +100011import {sleep} from './terminal_common.js';
Jason Lina8adea52022-10-25 13:14:14 +110012import {A11yButtons, Modifier, XtermTerminal, XtermTerminalTestParams,
Jason Lin97a04282023-03-06 10:36:56 +110013 encodeKeyCombo, keyCodes} from './terminal_emulator.js';
Jason Lin5690e752022-08-30 15:36:45 +100014import {MockFunction, MockObject} from './terminal_test_mocks.js';
Jason Lina8adea52022-10-25 13:14:14 +110015import {Terminal} from './xterm.js';
Jason Linabad7562022-08-22 14:49:05 +100016
17describe('terminal_emulator_tests.js', function() {
18 describe('XtermTerminal', function() {
19 beforeEach(async function() {
20 this.mocks = {
Jason Line9231bc2022-09-01 13:54:02 +100021 term: new MockObject({
22 options: {},
23 parser: {
24 registerOscHandler: () => {},
25 },
26 }),
Jason Linabad7562022-08-22 14:49:05 +100027 fontManager: new MockObject(),
Jason Lin2649da22022-10-12 10:16:44 +110028 xtermInternal: new MockObject({
29 getActualCellDimensions: () => ({width: 9, height: 22}),
30 }),
Jason Lin97a04282023-03-06 10:36:56 +110031 onVTKeystroke: new MockFunction(),
Jason Linabad7562022-08-22 14:49:05 +100032 };
33 const testParams = {};
Jason Lin97a04282023-03-06 10:36:56 +110034 for (const prop of ['term', 'fontManager', 'xtermInternal']) {
Jason Linabad7562022-08-22 14:49:05 +100035 testParams[prop] = this.mocks[prop].proxy;
36 }
37
38 this.terminal = new XtermTerminal({
39 storage: new lib.Storage.Memory(),
40 profileId: 'test',
41 enableWebGL: true,
42 testParams: /** @type {!XtermTerminalTestParams} */(testParams),
43 });
Jason Linc2504ae2022-09-02 13:03:31 +100044
Jason Lin97a04282023-03-06 10:36:56 +110045 this.terminal.io.onVTKeystroke = this.mocks.onVTKeystroke.proxy;
46
Jason Linc2504ae2022-09-02 13:03:31 +100047 // Some hacking because we don't run the decorate() function. Maybe we
48 // should just run it.
49 this.terminal.container_ = /** @type {!Element} */({
50 offsetWidth: 1000,
51 offsetHeight: 500,
52 });
Jason Lin8de3d282022-09-01 21:29:05 +100053 this.terminal.inited_ = true;
Jason Linabad7562022-08-22 14:49:05 +100054 });
55
56 describe('updateFont_()', function() {
57 it('updates font', async function() {
58 const updateFontPromise = this.terminal.updateFont_('font one');
59 assert.deepEqual(
60 await this.mocks.fontManager.whenCalled('loadFont'),
61 [['font one']]);
62 assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
63 assert.isNotNull(this.terminal.pendingFont_);
Jason Linabad7562022-08-22 14:49:05 +100064
65 await updateFontPromise;
66 assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one');
67 assert.isNull(this.terminal.pendingFont_);
68 await sleep(0);
Jason Linabad7562022-08-22 14:49:05 +100069 });
70
71 it('refresh font when the font is the same', async function() {
72 this.mocks.term.baseObj.options.fontFamily = 'font one';
73 const updateFontPromise = this.terminal.updateFont_('font one');
74 assert.deepEqual(
75 await this.mocks.fontManager.whenCalled('loadFont'),
76 [['font one']]);
77 assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one');
78 assert.isNotNull(this.terminal.pendingFont_);
Jason Linabad7562022-08-22 14:49:05 +100079
80 await updateFontPromise;
81 // Note the extra space at the end.
82 assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one ');
83 assert.isNull(this.terminal.pendingFont_);
84 await sleep(0);
Jason Linabad7562022-08-22 14:49:05 +100085 });
86
87 it('aborts if pendingFont_ was changed', async function() {
88 const updateFontPromise = this.terminal.updateFont_('font one');
89 assert.deepEqual(
90 await this.mocks.fontManager.whenCalled('loadFont'),
91 [['font one']]);
92 assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
93 assert.isNotNull(this.terminal.pendingFont_);
Jason Linabad7562022-08-22 14:49:05 +100094
95 this.terminal.pendingFont_ = 'font two';
96
97 await updateFontPromise;
98 assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
99 assert.equal(this.terminal.pendingFont_, 'font two');
100 await sleep(0);
Jason Linabad7562022-08-22 14:49:05 +1000101 });
102 });
Jason Lin5690e752022-08-30 15:36:45 +1000103
Jason Lin97a04282023-03-06 10:36:56 +1100104 describe('handleKeyEvent_', function() {
105 it('keyDownHandlers_', async function() {
106 const mockHandler = new MockFunction();
107 const fakeEvent = {
108 type: 'keydown',
109 keyCode: 65,
110 ctrlKey: true,
111 };
112 this.terminal.keyDownHandlers_.set(encodeKeyCombo(Modifier.Ctrl, 65),
113 mockHandler.proxy);
114 assert.isTrue(this.terminal.handleKeyEvent_(fakeEvent));
115 const history = mockHandler.popHistory();
116 assert.equal(history.length, 1);
117 assert.equal(history[0][0], fakeEvent);
Jason Lin5690e752022-08-30 15:36:45 +1000118
Jason Lin97a04282023-03-06 10:36:56 +1100119 assert.isTrue(this.terminal.handleKeyEvent_({...fakeEvent,
120 type: 'keypress'}));
121 assert.isEmpty(mockHandler.popHistory());
Jason Lin5690e752022-08-30 15:36:45 +1000122
Jason Lin97a04282023-03-06 10:36:56 +1100123 assert.isFalse(this.terminal.handleKeyEvent_({...fakeEvent,
124 shiftKey: true}));
125 assert.isEmpty(mockHandler.popHistory());
Jason Lin5690e752022-08-30 15:36:45 +1000126
Jason Lin97a04282023-03-06 10:36:56 +1100127 assert.isFalse(this.terminal.handleKeyEvent_({...fakeEvent,
128 keyCode: 66}));
129 assert.isEmpty(mockHandler.popHistory());
Jason Lin5690e752022-08-30 15:36:45 +1000130
Jason Lin97a04282023-03-06 10:36:56 +1100131 assert.isFalse(this.terminal.handleKeyEvent_({...fakeEvent,
132 ctrlKey: false}));
133 assert.isEmpty(mockHandler.popHistory());
134 });
135
136 it('arrow keys and 6 pack keys', async function() {
137 const check = (ev, handled, vtKeystroke) => {
138 const mockPreventDefault = new MockFunction();
139 const mockStopPropagation = new MockFunction();
140 assert.equal(this.terminal.handleKeyEvent_({
141 type: 'keydown',
142 preventDefault: mockPreventDefault.proxy,
143 stopPropagation: mockStopPropagation.proxy,
144 ...ev,
145 }), handled);
146 const history = this.mocks.onVTKeystroke.popHistory();
147 if (vtKeystroke) {
148 assert.deepEqual(history, [[vtKeystroke]]);
149 } else {
150 assert.isEmpty(history);
151 }
152 assert.equal(mockPreventDefault.getHistory().length, handled ? 1 : 0);
153 assert.equal(mockStopPropagation.getHistory().length,
154 handled ? 1 : 0);
155 };
156
157 check({keyCode: keyCodes.UP}, false, null);
158 check({keyCode: keyCodes.UP, shiftKey: true}, true, '\x1b[1;2A');
159 check({keyCode: keyCodes.UP, altKey: true}, true, '\x1b[1;3A');
160 check({keyCode: keyCodes.UP, shiftKey: true, altKey: true}, true,
161 '\x1b[1;4A');
162
163 check({keyCode: keyCodes.INSERT}, false, null);
164 check({keyCode: keyCodes.INSERT, altKey: true}, true, '\x1b[2;3~');
165 check({keyCode: keyCodes.INSERT, shiftKey: true, altKey: true}, true,
166 '\x1b[2;4~');
167
168 check({keyCode: keyCodes.HOME}, false, null);
169 check({keyCode: keyCodes.HOME, altKey: true}, true, '\x1b[1;3H');
170 check({keyCode: keyCodes.HOME, shiftKey: true, altKey: true}, true,
171 '\x1b[1;4H');
172
173 // Shift+HOME should scroll the page.
174 assert.equal(this.mocks.term.getMethodHistory('scrollToTop').length, 0);
175 check({keyCode: keyCodes.HOME, shiftKey: true}, true, null);
176 assert.equal(this.mocks.term.getMethodHistory('scrollToTop').length, 1);
177
178 // For non-keydown event, if a modifier key is depressed, we do nothing
179 // but `handleKeyEvent_()` will return true to prevent xterm.js from
180 // handling it.
181 check({type: 'keypress', keyCode: keyCodes.HOME, altKey: true}, true,
182 undefined);
183 // If there is no modifiers, we still pass through it to xterm.js.
184 check({type: 'keypress', keyCode: keyCodes.HOME}, false, null);
185 });
Jason Lin5690e752022-08-30 15:36:45 +1000186 });
Jason Linabad7562022-08-22 14:49:05 +1000187 });
Jason Lina8adea52022-10-25 13:14:14 +1100188
189 describe('A11yButtons', () => {
190 const ROWS = 5;
191
192 beforeEach(function() {
193 this.elem = document.createElement('div');
194 this.elem.style.height = '500px';
195 this.elem.style.width = '500px';
196 document.body.appendChild(this.elem);
197
198 this.terminal = new Terminal({cols: 80, rows: ROWS,
199 allowProposedApi: true});
200 this.htermA11yReaderMock = new MockObject();
Jason Linc0f14fe2022-10-25 15:31:29 +1100201 this.a11yButtons = new A11yButtons(this.terminal,
Jason Lina8adea52022-10-25 13:14:14 +1100202 /** @type {!hterm.AccessibilityReader} */(
203 this.htermA11yReaderMock.proxy));
204
205 this.write = async (content) => {
206 return new Promise((resolve) => this.terminal.write(content, resolve));
207 };
208 });
209
210 afterEach(function() {
211 this.terminal.dispose();
212 document.body.removeChild(this.elem);
213 });
214
215 it('announceScreenContent_', async function() {
216 this.a11yButtons.announceScreenContent_();
217 assert.deepEqual(
218 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
219 [['100% scrolled,']]);
220
221 await this.write('hello');
222 this.a11yButtons.announceScreenContent_();
223 assert.deepEqual(
224 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
225 [['100% scrolled,\nhello']]);
226
227 await this.write('\r\nworld');
228 this.a11yButtons.announceScreenContent_();
229 assert.deepEqual(
230 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
231 [['100% scrolled,\nhello\nworld']]);
232
233 for (let i = 0; i < ROWS; ++i) {
234 await this.write(`\r\n${i}`);
235 }
236 this.a11yButtons.announceScreenContent_();
237 assert.deepEqual(
238 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
239 [['100% scrolled,\n0\n1\n2\n3\n4']]);
240
241 this.terminal.scrollLines(-1);
242 this.a11yButtons.announceScreenContent_();
243 assert.deepEqual(
244 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
245 [['50% scrolled,\nworld\n0\n1\n2\n3']]);
246
247 this.terminal.scrollLines(-1);
248 this.a11yButtons.announceScreenContent_();
249 assert.deepEqual(
250 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
251 [['0% scrolled,\nhello\nworld\n0\n1\n2']]);
252 });
253 });
Jason Linabad7562022-08-22 14:49:05 +1000254});