bitburner-src/test/jest/Terminal/tabCompletion.test.ts
Snarling e0272ad4af
FILES: Path rework & typesafety (#479)
* Added new types for various file paths, all in the Paths folder.
* TypeSafety and other helper functions related to these types
* Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands
* Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way
* Server.textFiles is now a map
* TextFile no longer uses a fn property, now it is filename
* Added a shared ContentFile interface for shared functionality between TextFile and Script.
* related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa.
* File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root.
* Singularized the MessageFilename and LiteratureName enums
* Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath).
* Fix several issues with tab completion, which included pretty much a complete rewrite
* Changed the autocomplete display options so there's less chance it clips outside the display area.
* Turned CompletedProgramName into an enum.
* Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine.
* For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
2023-04-24 10:26:57 -04:00

223 lines
8.3 KiB
TypeScript

/* eslint-disable no-await-in-loop */
import { Player } from "../../../src/Player";
import { getTabCompletionPossibilities } from "../../../src/Terminal/getTabCompletionPossibilities";
import { Server } from "../../../src/Server/Server";
import { AddToAllServers, prestigeAllServers } from "../../../src/Server/AllServers";
import { LocationName } from "../../../src/Enums";
import { CodingContract } from "../../../src/CodingContracts";
import { asFilePath } from "../../../src/Paths/FilePath";
import { Directory, isAbsolutePath, isDirectoryPath, root } from "../../../src/Paths/Directory";
import { hasTextExtension } from "../../../src/Paths/TextFilePath";
import { hasScriptExtension } from "../../../src/Paths/ScriptFilePath";
import { LiteratureName } from "../../../src/Literature/data/LiteratureNames";
import { MessageFilename } from "../../../src/Message/MessageHelpers";
import { Terminal } from "../../../src/Terminal";
describe("getTabCompletionPossibilities", function () {
let closeServer: Server;
let farServer: Server;
beforeEach(() => {
prestigeAllServers();
Player.init();
closeServer = new Server({
ip: "8.8.8.8",
hostname: "near",
hackDifficulty: 1,
moneyAvailable: 70000,
numOpenPortsRequired: 0,
organizationName: LocationName.NewTokyoNoodleBar,
requiredHackingSkill: 1,
serverGrowth: 3000,
});
farServer = new Server({
ip: "4.4.4.4",
hostname: "far",
hackDifficulty: 1,
moneyAvailable: 70000,
numOpenPortsRequired: 0,
organizationName: LocationName.Sector12JoesGuns,
requiredHackingSkill: 1,
serverGrowth: 3000,
});
Player.getHomeComputer().serversOnNetwork.push(closeServer.hostname);
closeServer.serversOnNetwork.push(Player.getHomeComputer().hostname);
closeServer.serversOnNetwork.push(farServer.hostname);
farServer.serversOnNetwork.push(closeServer.hostname);
AddToAllServers(closeServer);
AddToAllServers(farServer);
});
it("completes the connect command, regardless of folder", async () => {
let options = await getTabCompletionPossibilities("connect ", root);
expect(options).toEqual(["near"]);
options = await getTabCompletionPossibilities("connect ", asDirectory("folder1/"));
expect(options).toEqual(["near"]);
Terminal.connectToServer("near");
options = await getTabCompletionPossibilities("connect ", root);
expect(options).toEqual(["home", "far"]);
options = await getTabCompletionPossibilities("connect h", asDirectory("folder1/"));
// Also test completion of a partially completed text
expect(options).toEqual(["home"]);
});
it("completes the buy command", async () => {
let options = await getTabCompletionPossibilities("buy ", root);
expect(options.sort()).toEqual(
[
"BruteSSH.exe",
"FTPCrack.exe",
"relaySMTP.exe",
"HTTPWorm.exe",
"SQLInject.exe",
"DeepscanV1.exe",
"DeepscanV2.exe",
"AutoLink.exe",
"ServerProfiler.exe",
"Formulas.exe",
].sort(),
);
// Also test that darkweb items will be completed if they have incorrect capitalization in progress
options = await getTabCompletionPossibilities("buy de", root);
expect(options.sort()).toEqual(["DeepscanV1.exe", "DeepscanV2.exe"].sort());
});
it("completes the scp command", async () => {
writeFiles();
let options = await getTabCompletionPossibilities("scp ", root);
expect(options.sort()).toEqual(
[
"note.txt",
"folder1/text.txt",
"folder1/text2.txt",
"hack.js",
"weaken.js",
"grow.js",
"old.script",
"folder1/test.js",
"anotherFolder/win.js",
LiteratureName.AGreenTomorrow,
].sort(),
);
// Test the second command argument (server name)
options = await getTabCompletionPossibilities("scp note.txt ", root);
expect(options).toEqual(["home", "near", "far"]);
});
it("completes the kill, tail, mem, and check commands", async () => {
writeFiles();
for (const command of ["kill", "tail", "mem", "check"]) {
let options = await getTabCompletionPossibilities(`${command} `, root);
expect(options.sort()).toEqual(scriptFilePaths);
// From a directory, show only the options in that directory
options = await getTabCompletionPossibilities(`${command} `, asDirectory("folder1/"));
expect(options.sort()).toEqual(["test.js"]);
// From a directory but with relative path .., show stuff in the resolved directory with the relative pathing included
options = await getTabCompletionPossibilities(`${command} ../`, asDirectory("folder1/"));
expect(options.sort()).toEqual(
[...scriptFilePaths.map((path) => "../" + path), "../folder1/", "../anotherFolder/"].sort(),
);
options = await getTabCompletionPossibilities(`${command} ../folder1/../anotherFolder/`, asDirectory("folder1/"));
expect(options.sort()).toEqual(["../folder1/../anotherFolder/win.js"]);
}
});
it("completes the nano commands", async () => {
writeFiles();
const contentFilePaths = [...scriptFilePaths, ...textFilePaths].sort();
const options = await getTabCompletionPossibilities("nano ", root);
expect(options.sort()).toEqual(contentFilePaths);
});
it("completes the rm command", async () => {
writeFiles();
const removableFilePaths = [
...scriptFilePaths,
...textFilePaths,
...contractFilePaths,
LiteratureName.AGreenTomorrow,
"NUKE.exe",
].sort();
const options = await getTabCompletionPossibilities("rm ", root);
expect(options.sort()).toEqual(removableFilePaths);
});
it("completes the run command", async () => {
writeFiles();
const runnableFilePaths = [...scriptFilePaths, ...contractFilePaths, "NUKE.exe"].sort();
let options = await getTabCompletionPossibilities("run ", root);
expect(options.sort()).toEqual(runnableFilePaths);
// Also check the same files
options = await getTabCompletionPossibilities("./", root);
expect(options.sort()).toEqual(
[...runnableFilePaths.map((path) => "./" + path), "./folder1/", "./anotherFolder/"].sort(),
);
});
it("completes the cat command", async () => {
writeFiles();
const cattableFilePaths = [
...scriptFilePaths,
...textFilePaths,
MessageFilename.TruthGazer,
LiteratureName.AGreenTomorrow,
].sort();
const options = await getTabCompletionPossibilities("cat ", root);
expect(options.sort()).toEqual(cattableFilePaths);
});
it("completes the download and mv commands", async () => {
writeFiles();
writeFiles();
const contentFilePaths = [...scriptFilePaths, ...textFilePaths].sort();
for (const command of ["download", "mv"]) {
const options = await getTabCompletionPossibilities(`${command} `, root);
expect(options.sort()).toEqual(contentFilePaths);
}
});
it("completes the ls and cd commands", async () => {
writeFiles();
for (const command of ["ls", "cd"]) {
const options = await getTabCompletionPossibilities(`${command} `, root);
expect(options.sort()).toEqual(["folder1/", "anotherFolder/"].sort());
}
});
});
function asDirectory(dir: string): Directory {
if (!isAbsolutePath(dir) || !isDirectoryPath(dir)) throw new Error(`Directory ${dir} failed typechecking`);
return dir;
}
const textFilePaths = ["note.txt", "folder1/text.txt", "folder1/text2.txt"];
const scriptFilePaths = [
"hack.js",
"weaken.js",
"grow.js",
"old.script",
"folder1/test.js",
"anotherFolder/win.js",
].sort();
const contractFilePaths = ["testContract.cct", "anothercontract.cct"];
function writeFiles() {
const home = Player.getHomeComputer();
for (const filename of textFilePaths) {
if (!hasTextExtension(filename)) {
throw new Error(`Text file ${filename} had the wrong extension.`);
}
home.writeToTextFile(asFilePath(filename), `File content for ${filename}`);
}
for (const filename of scriptFilePaths) {
if (!hasScriptExtension(filename)) {
throw new Error(`Script file ${filename} had the wrong extension.`);
}
home.writeToScriptFile(asFilePath(filename), `File content for ${filename}`);
}
for (const filename of contractFilePaths) {
home.contracts.push(new CodingContract(filename));
}
home.messages.push(LiteratureName.AGreenTomorrow, MessageFilename.TruthGazer);
}