terminal: fix web font related rendering issue with xterm.js

xterm.js caches text atlas and paints on a canvas, so we need to:

1. Change the font family after the font (or at least the latin part)
   has loaded
2. Refresh the rendering if new font is loaded (e.g. new non-latin
   character was just printed)

Bug: b/236205389
Change-Id: Ida9709f29518e7861e9b9b97638891112f39f3b7
Reviewed-on: https://chromium-review.googlesource.com/c/apps/libapps/+/3831832
Reviewed-by: Joel Hockey <joelhockey@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/terminal/js/terminal_emulator_tests.js b/terminal/js/terminal_emulator_tests.js
new file mode 100644
index 0000000..04b1b8d
--- /dev/null
+++ b/terminal/js/terminal_emulator_tests.js
@@ -0,0 +1,88 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Unit tests for terminal_emulator.js.
+ */
+
+import {sleep} from './terminal_common.js';
+import {XtermTerminal, XtermTerminalTestParams} from './terminal_emulator.js';
+import {MockObject} from './terminal_test_mocks.js';
+
+describe('terminal_emulator_tests.js', function() {
+  describe('XtermTerminal', function() {
+    beforeEach(async function() {
+      this.mocks = {
+        term: new MockObject({options: {}}),
+        fontManager: new MockObject(),
+        fitAddon: new MockObject(),
+      };
+      const testParams = {};
+      for (const prop in this.mocks) {
+        testParams[prop] = this.mocks[prop].proxy;
+      }
+
+      this.terminal = new XtermTerminal({
+        storage: new lib.Storage.Memory(),
+        profileId: 'test',
+        enableWebGL: true,
+        testParams: /** @type {!XtermTerminalTestParams} */(testParams),
+      });
+    });
+
+    describe('updateFont_()', function() {
+      it('updates font', async function() {
+        const updateFontPromise = this.terminal.updateFont_('font one');
+        assert.deepEqual(
+            await this.mocks.fontManager.whenCalled('loadFont'),
+            [['font one']]);
+        assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
+        assert.isNotNull(this.terminal.pendingFont_);
+        assert.deepEqual(this.mocks.fitAddon.getMethodHistory('fit'), []);
+
+        await updateFontPromise;
+        assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one');
+        assert.isNull(this.terminal.pendingFont_);
+        await sleep(0);
+        assert.deepEqual(this.mocks.fitAddon.getMethodHistory('fit'), [[]]);
+      });
+
+      it('refresh font when the font is the same', async function() {
+        this.mocks.term.baseObj.options.fontFamily = 'font one';
+        const updateFontPromise = this.terminal.updateFont_('font one');
+        assert.deepEqual(
+            await this.mocks.fontManager.whenCalled('loadFont'),
+            [['font one']]);
+        assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one');
+        assert.isNotNull(this.terminal.pendingFont_);
+        assert.deepEqual(this.mocks.fitAddon.getMethodHistory('fit'), []);
+
+        await updateFontPromise;
+        // Note the extra space at the end.
+        assert.equal(this.mocks.term.baseObj.options.fontFamily, 'font one ');
+        assert.isNull(this.terminal.pendingFont_);
+        await sleep(0);
+        assert.deepEqual(this.mocks.fitAddon.getMethodHistory('fit'), [[]]);
+      });
+
+      it('aborts if pendingFont_ was changed', async function() {
+        const updateFontPromise = this.terminal.updateFont_('font one');
+        assert.deepEqual(
+            await this.mocks.fontManager.whenCalled('loadFont'),
+            [['font one']]);
+        assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
+        assert.isNotNull(this.terminal.pendingFont_);
+        assert.deepEqual(this.mocks.fitAddon.getMethodHistory('fit'), []);
+
+        this.terminal.pendingFont_ = 'font two';
+
+        await updateFontPromise;
+        assert.equal(this.mocks.term.baseObj.options.fontFamily, undefined);
+        assert.equal(this.terminal.pendingFont_, 'font two');
+        await sleep(0);
+        assert.deepEqual(this.mocks.fitAddon.getMethodHistory('fit'), []);
+      });
+    });
+  });
+});