blob: 29f5dc8fdea6d736b85eb3d17fc7f2504c5f7986 [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',
Jason Linabad7562022-08-22 14:49:05 +100041 testParams: /** @type {!XtermTerminalTestParams} */(testParams),
42 });
Jason Linc2504ae2022-09-02 13:03:31 +100043
Jason Lin97a04282023-03-06 10:36:56 +110044 this.terminal.io.onVTKeystroke = this.mocks.onVTKeystroke.proxy;
45
Jason Linc2504ae2022-09-02 13:03:31 +100046 // Some hacking because we don't run the decorate() function. Maybe we
47 // should just run it.
48 this.terminal.container_ = /** @type {!Element} */({
49 offsetWidth: 1000,
50 offsetHeight: 500,
51 });
Jason Lin8de3d282022-09-01 21:29:05 +100052 this.terminal.inited_ = true;
Jason Linabad7562022-08-22 14:49:05 +100053 });
54
55 describe('updateFont_()', function() {
56 it('updates font', async function() {
57 const updateFontPromise = this.terminal.updateFont_('font one');
58 assert.deepEqual(
59 await this.mocks.fontManager.whenCalled('loadFont'),
60 [['font one']]);
61 assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
62 assert.isNotNull(this.terminal.pendingFont_);
Jason Linabad7562022-08-22 14:49:05 +100063
64 await updateFontPromise;
65 assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one');
66 assert.isNull(this.terminal.pendingFont_);
67 await sleep(0);
Jason Linabad7562022-08-22 14:49:05 +100068 });
69
70 it('refresh font when the font is the same', async function() {
71 this.mocks.term.baseObj.options.fontFamily = 'font one';
72 const updateFontPromise = this.terminal.updateFont_('font one');
73 assert.deepEqual(
74 await this.mocks.fontManager.whenCalled('loadFont'),
75 [['font one']]);
76 assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one');
77 assert.isNotNull(this.terminal.pendingFont_);
Jason Linabad7562022-08-22 14:49:05 +100078
79 await updateFontPromise;
80 // Note the extra space at the end.
81 assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one ');
82 assert.isNull(this.terminal.pendingFont_);
83 await sleep(0);
Jason Linabad7562022-08-22 14:49:05 +100084 });
85
86 it('aborts if pendingFont_ was changed', async function() {
87 const updateFontPromise = this.terminal.updateFont_('font one');
88 assert.deepEqual(
89 await this.mocks.fontManager.whenCalled('loadFont'),
90 [['font one']]);
91 assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
92 assert.isNotNull(this.terminal.pendingFont_);
Jason Linabad7562022-08-22 14:49:05 +100093
94 this.terminal.pendingFont_ = 'font two';
95
96 await updateFontPromise;
97 assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
98 assert.equal(this.terminal.pendingFont_, 'font two');
99 await sleep(0);
Jason Linabad7562022-08-22 14:49:05 +1000100 });
101 });
Jason Lin5690e752022-08-30 15:36:45 +1000102
Jason Lin97a04282023-03-06 10:36:56 +1100103 describe('handleKeyEvent_', function() {
104 it('keyDownHandlers_', async function() {
105 const mockHandler = new MockFunction();
106 const fakeEvent = {
107 type: 'keydown',
108 keyCode: 65,
109 ctrlKey: true,
110 };
111 this.terminal.keyDownHandlers_.set(encodeKeyCombo(Modifier.Ctrl, 65),
112 mockHandler.proxy);
113 assert.isTrue(this.terminal.handleKeyEvent_(fakeEvent));
114 const history = mockHandler.popHistory();
115 assert.equal(history.length, 1);
116 assert.equal(history[0][0], fakeEvent);
Jason Lin5690e752022-08-30 15:36:45 +1000117
Jason Lin97a04282023-03-06 10:36:56 +1100118 assert.isTrue(this.terminal.handleKeyEvent_({...fakeEvent,
119 type: 'keypress'}));
120 assert.isEmpty(mockHandler.popHistory());
Jason Lin5690e752022-08-30 15:36:45 +1000121
Jason Lin97a04282023-03-06 10:36:56 +1100122 assert.isFalse(this.terminal.handleKeyEvent_({...fakeEvent,
123 shiftKey: true}));
124 assert.isEmpty(mockHandler.popHistory());
Jason Lin5690e752022-08-30 15:36:45 +1000125
Jason Lin97a04282023-03-06 10:36:56 +1100126 assert.isFalse(this.terminal.handleKeyEvent_({...fakeEvent,
127 keyCode: 66}));
128 assert.isEmpty(mockHandler.popHistory());
Jason Lin5690e752022-08-30 15:36:45 +1000129
Jason Lin97a04282023-03-06 10:36:56 +1100130 assert.isFalse(this.terminal.handleKeyEvent_({...fakeEvent,
131 ctrlKey: false}));
132 assert.isEmpty(mockHandler.popHistory());
133 });
134
135 it('arrow keys and 6 pack keys', async function() {
136 const check = (ev, handled, vtKeystroke) => {
137 const mockPreventDefault = new MockFunction();
138 const mockStopPropagation = new MockFunction();
139 assert.equal(this.terminal.handleKeyEvent_({
140 type: 'keydown',
141 preventDefault: mockPreventDefault.proxy,
142 stopPropagation: mockStopPropagation.proxy,
143 ...ev,
144 }), handled);
145 const history = this.mocks.onVTKeystroke.popHistory();
146 if (vtKeystroke) {
147 assert.deepEqual(history, [[vtKeystroke]]);
148 } else {
149 assert.isEmpty(history);
150 }
151 assert.equal(mockPreventDefault.getHistory().length, handled ? 1 : 0);
152 assert.equal(mockStopPropagation.getHistory().length,
153 handled ? 1 : 0);
154 };
155
156 check({keyCode: keyCodes.UP}, false, null);
157 check({keyCode: keyCodes.UP, shiftKey: true}, true, '\x1b[1;2A');
158 check({keyCode: keyCodes.UP, altKey: true}, true, '\x1b[1;3A');
159 check({keyCode: keyCodes.UP, shiftKey: true, altKey: true}, true,
160 '\x1b[1;4A');
161
162 check({keyCode: keyCodes.INSERT}, false, null);
163 check({keyCode: keyCodes.INSERT, altKey: true}, true, '\x1b[2;3~');
164 check({keyCode: keyCodes.INSERT, shiftKey: true, altKey: true}, true,
165 '\x1b[2;4~');
166
167 check({keyCode: keyCodes.HOME}, false, null);
168 check({keyCode: keyCodes.HOME, altKey: true}, true, '\x1b[1;3H');
169 check({keyCode: keyCodes.HOME, shiftKey: true, altKey: true}, true,
170 '\x1b[1;4H');
171
172 // Shift+HOME should scroll the page.
173 assert.equal(this.mocks.term.getMethodHistory('scrollToTop').length, 0);
174 check({keyCode: keyCodes.HOME, shiftKey: true}, true, null);
175 assert.equal(this.mocks.term.getMethodHistory('scrollToTop').length, 1);
176
177 // For non-keydown event, if a modifier key is depressed, we do nothing
178 // but `handleKeyEvent_()` will return true to prevent xterm.js from
179 // handling it.
180 check({type: 'keypress', keyCode: keyCodes.HOME, altKey: true}, true,
181 undefined);
182 // If there is no modifiers, we still pass through it to xterm.js.
183 check({type: 'keypress', keyCode: keyCodes.HOME}, false, null);
184 });
Jason Lin5690e752022-08-30 15:36:45 +1000185 });
Jason Linabad7562022-08-22 14:49:05 +1000186 });
Jason Lina8adea52022-10-25 13:14:14 +1100187
188 describe('A11yButtons', () => {
189 const ROWS = 5;
190
191 beforeEach(function() {
192 this.elem = document.createElement('div');
193 this.elem.style.height = '500px';
194 this.elem.style.width = '500px';
195 document.body.appendChild(this.elem);
196
197 this.terminal = new Terminal({cols: 80, rows: ROWS,
198 allowProposedApi: true});
199 this.htermA11yReaderMock = new MockObject();
Jason Linc0f14fe2022-10-25 15:31:29 +1100200 this.a11yButtons = new A11yButtons(this.terminal,
Jason Lina8adea52022-10-25 13:14:14 +1100201 /** @type {!hterm.AccessibilityReader} */(
202 this.htermA11yReaderMock.proxy));
203
204 this.write = async (content) => {
205 return new Promise((resolve) => this.terminal.write(content, resolve));
206 };
207 });
208
209 afterEach(function() {
210 this.terminal.dispose();
211 document.body.removeChild(this.elem);
212 });
213
214 it('announceScreenContent_', async function() {
215 this.a11yButtons.announceScreenContent_();
216 assert.deepEqual(
217 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
218 [['100% scrolled,']]);
219
220 await this.write('hello');
221 this.a11yButtons.announceScreenContent_();
222 assert.deepEqual(
223 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
224 [['100% scrolled,\nhello']]);
225
226 await this.write('\r\nworld');
227 this.a11yButtons.announceScreenContent_();
228 assert.deepEqual(
229 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
230 [['100% scrolled,\nhello\nworld']]);
231
232 for (let i = 0; i < ROWS; ++i) {
233 await this.write(`\r\n${i}`);
234 }
235 this.a11yButtons.announceScreenContent_();
236 assert.deepEqual(
237 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
238 [['100% scrolled,\n0\n1\n2\n3\n4']]);
239
240 this.terminal.scrollLines(-1);
241 this.a11yButtons.announceScreenContent_();
242 assert.deepEqual(
243 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
244 [['50% scrolled,\nworld\n0\n1\n2\n3']]);
245
246 this.terminal.scrollLines(-1);
247 this.a11yButtons.announceScreenContent_();
248 assert.deepEqual(
249 this.htermA11yReaderMock.popMethodHistory('assertiveAnnounce'),
250 [['0% scrolled,\nhello\nworld\n0\n1\n2']]);
251 });
252 });
Jason Linabad7562022-08-22 14:49:05 +1000253});