Skip to content

Commit f039bd4

Browse files
committed
test(terminal): add integration tests for clear, copy, paste, and context menu
Test clear command removes buffer content, copy command reads the xterm selection, paste command writes mocked clipboard text to the PTY, and beforeContextMenuOpen disables Copy when nothing is selected. Guard test-only exports behind Phoenix.isTestWindow.
1 parent 5e25232 commit f039bd4

2 files changed

Lines changed: 208 additions & 25 deletions

File tree

src/extensionsIntegrated/Terminal/main.js

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -788,29 +788,33 @@ define(function (require, exports, module) {
788788
exports.CMD_VIEW_TERMINAL = CMD_VIEW_TERMINAL;
789789
exports.CMD_NEW_TERMINAL = CMD_NEW_TERMINAL;
790790

791-
/**
792-
* Write data to the active terminal's PTY. Test-only helper.
793-
* @param {string} data The text to send to the terminal.
794-
* @return {Promise}
795-
*/
796-
exports._writeToActiveTerminal = function (data) {
797-
const active = _getActiveTerminal();
798-
if (!active || !active.isAlive) {
799-
return Promise.reject(new Error("No active terminal"));
800-
}
801-
return nodeConnector.execPeer("writeTerminal", {
802-
id: active.id, data
803-
});
804-
};
791+
if (Phoenix.isTestWindow) {
792+
exports._getActiveTerminal = _getActiveTerminal;
793+
794+
/**
795+
* Write data to the active terminal's PTY. Test-only helper.
796+
* @param {string} data The text to send to the terminal.
797+
* @return {Promise}
798+
*/
799+
exports._writeToActiveTerminal = function (data) {
800+
const active = _getActiveTerminal();
801+
if (!active || !active.isAlive) {
802+
return Promise.reject(new Error("No active terminal"));
803+
}
804+
return nodeConnector.execPeer("writeTerminal", {
805+
id: active.id, data
806+
});
807+
};
805808

806-
/**
807-
* Dispose all terminal instances. Test-only helper.
808-
* Awaits all PTY kill commands so the caller can be
809-
* sure processes have been signalled before the test
810-
* window is torn down.
811-
*/
812-
exports._disposeAll = async function () {
813-
await _disposeAllAsync();
814-
activeTerminalId = null;
815-
};
809+
/**
810+
* Dispose all terminal instances. Test-only helper.
811+
* Awaits all PTY kill commands so the caller can be
812+
* sure processes have been signalled before the test
813+
* window is torn down.
814+
*/
815+
exports._disposeAll = async function () {
816+
await _disposeAllAsync();
817+
activeTerminalId = null;
818+
};
819+
}
816820
});

test/spec/Terminal-integ-test.js

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
*/
2020

21-
/*global describe, it, expect, beforeAll, afterAll, awaitsFor */
21+
/*global describe, it, expect, beforeAll, afterAll, awaitsFor, spyOn */
2222

2323
define(function (require, exports, module) {
2424

@@ -546,6 +546,185 @@ define(function (require, exports, module) {
546546
});
547547
});
548548

549+
describe("Context menu commands", function () {
550+
let CommandManager;
551+
552+
function getActiveTerminal() {
553+
const mod = testWindow.brackets.getModule(
554+
"extensionsIntegrated/Terminal/main"
555+
);
556+
return mod._getActiveTerminal();
557+
}
558+
559+
/**
560+
* Check whether a marker string appears anywhere
561+
* in the active terminal's buffer.
562+
*/
563+
function bufferContains(marker) {
564+
const active = getActiveTerminal();
565+
if (!active) {
566+
return false;
567+
}
568+
const buffer = active.terminal.buffer.active;
569+
for (let i = 0; i < buffer.length; i++) {
570+
const line = buffer.getLine(i);
571+
if (line && line.translateToString()
572+
.indexOf(marker) !== -1) {
573+
return true;
574+
}
575+
}
576+
return false;
577+
}
578+
579+
beforeAll(function () {
580+
CommandManager =
581+
testWindow.brackets.test.CommandManager;
582+
});
583+
584+
it("should clear the terminal screen",
585+
async function () {
586+
await openTerminal();
587+
await waitForShellReady();
588+
589+
// Write some output so the terminal has content
590+
await writeToTerminal("echo cleartest\r");
591+
await awaitsFor(function () {
592+
return bufferContains("cleartest");
593+
}, "echo output to appear", 10000);
594+
595+
// Execute the clear command
596+
await CommandManager.execute("terminal.clear");
597+
598+
// After clear, the marker should no longer be
599+
// in the buffer (xterm.clear() wipes scrollback).
600+
await awaitsFor(function () {
601+
return !bufferContains("cleartest");
602+
}, "terminal to be cleared", 5000);
603+
604+
// Clean up
605+
clickPanelCloseButton();
606+
await awaitsFor(function () {
607+
return !testWindow.$("#terminal-panel")
608+
.is(":visible");
609+
}, "terminal panel to close", 5000);
610+
});
611+
612+
it("should copy selected terminal text to clipboard",
613+
async function () {
614+
await openTerminal();
615+
await waitForShellReady();
616+
617+
// Write a unique marker string
618+
await writeToTerminal("echo COPYMARKER123\r");
619+
await awaitsFor(function () {
620+
return bufferContains("COPYMARKER123");
621+
}, "echo output to appear", 10000);
622+
623+
// Select all text in xterm
624+
const active = getActiveTerminal();
625+
active.terminal.selectAll();
626+
expect(active.terminal.hasSelection())
627+
.toBeTrue();
628+
const selection = active.terminal.getSelection();
629+
expect(selection).toContain("COPYMARKER123");
630+
631+
// The copy command writes to the system clipboard
632+
// via navigator.clipboard.writeText(). In the test
633+
// iframe clipboard writes may be denied (no focus),
634+
// so we verify the command reads the right text by
635+
// spying on writeText.
636+
const clipboard = testWindow.navigator.clipboard;
637+
let copiedText = null;
638+
spyOn(clipboard, "writeText").and.callFake(
639+
function (text) {
640+
copiedText = text;
641+
return testWindow.Promise.resolve();
642+
}
643+
);
644+
await CommandManager.execute("terminal.copy");
645+
expect(copiedText).toContain("COPYMARKER123");
646+
647+
// Clean up
648+
clickPanelCloseButton();
649+
await awaitsFor(function () {
650+
return !testWindow.$("#terminal-panel")
651+
.is(":visible");
652+
}, "terminal panel to close", 5000);
653+
});
654+
655+
it("should paste clipboard text into terminal",
656+
async function () {
657+
await openTerminal();
658+
await waitForShellReady();
659+
660+
// Mock clipboard.readText to return a known string,
661+
// since the test iframe may not have clipboard
662+
// permission (no window focus).
663+
const pasteText = "PASTEMARKER456";
664+
const clipboard = testWindow.navigator.clipboard;
665+
spyOn(clipboard, "readText").and.returnValue(
666+
testWindow.Promise.resolve(pasteText)
667+
);
668+
669+
// Execute the paste command
670+
await CommandManager.execute("terminal.paste");
671+
672+
// The pasted text should appear in the terminal
673+
// buffer (written to PTY input → echoed back).
674+
await awaitsFor(function () {
675+
return bufferContains(pasteText);
676+
}, "pasted text to appear in terminal", 10000);
677+
678+
// Clean up: press Enter then close
679+
await writeToTerminal("\r");
680+
clickPanelCloseButton();
681+
await awaitsFor(function () {
682+
return !testWindow.$("#terminal-panel")
683+
.is(":visible");
684+
}, "terminal panel to close", 5000);
685+
});
686+
687+
it("should disable copy when there is no selection",
688+
async function () {
689+
await openTerminal();
690+
await waitForShellReady();
691+
692+
const active = getActiveTerminal();
693+
// Ensure no selection
694+
active.terminal.clearSelection();
695+
expect(active.terminal.hasSelection())
696+
.toBeFalse();
697+
698+
// Open context menu to trigger
699+
// beforeContextMenuOpen event
700+
const Menus = testWindow.brackets.test.Menus;
701+
const ctxMenu = Menus.getContextMenu(
702+
"terminal-context-menu"
703+
);
704+
ctxMenu.open({pageX: 100, pageY: 100});
705+
706+
const copyCmd =
707+
CommandManager.get("terminal.copy");
708+
expect(copyCmd.getEnabled()).toBeFalse();
709+
710+
// Close menu
711+
ctxMenu.close();
712+
713+
// Now select text and re-open
714+
active.terminal.selectAll();
715+
ctxMenu.open({pageX: 100, pageY: 100});
716+
expect(copyCmd.getEnabled()).toBeTrue();
717+
ctxMenu.close();
718+
719+
// Clean up
720+
clickPanelCloseButton();
721+
await awaitsFor(function () {
722+
return !testWindow.$("#terminal-panel")
723+
.is(":visible");
724+
}, "terminal panel to close", 5000);
725+
});
726+
});
727+
549728
describe("Programmatic hide vs user close", function () {
550729
it("should keep terminals alive after panel.hide()",
551730
async function () {

0 commit comments

Comments
 (0)