Adding more directory-related unit tests. Several more bug fixes and QoL improvements

This commit is contained in:
danielyxie 2019-05-14 01:35:37 -07:00
parent 1775ea86ff
commit fef7aaba8f
24 changed files with 423 additions and 85 deletions

@ -1,10 +1,10 @@
grow() Netscript Function
=========================
.. js:function:: grow(hostname/ip)
.. js:function:: grow(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to grow
:param object options: Optional parameters for configuring function behavior. Properties:
:param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.

@ -1,10 +1,10 @@
hack() Netscript Function
=========================
.. js:function:: hack(hostname/ip)
.. js:function:: hack(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to hack
:param object options: Optional parameters for configuring function behavior. Properties:
:param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.

@ -1,10 +1,10 @@
weaken() Netscript Function
===========================
.. js:function:: weaken(hostname/ip, options={})
.. js:function:: weaken(hostname/ip[, opts={}])
:param string hostname/ip: IP or hostname of the target server to weaken
:param object options: Optional parameters for configuring function behavior. Properties:
:param object opts: Optional parameters for configuring function behavior. Properties:
* threads (*number*) - Number of threads to use for this function.
Must be less than or equal to the number of threads the script is running with.

@ -1,13 +1,20 @@
attempt() Netscript Function
============================
.. js:function:: attempt(answer, fn[, hostname/ip=current ip])
.. js:function:: attempt(answer, fn[, hostname/ip=current ip, opts={}])
:param answer: Solution for the contract
:param string fn: Filename of the contract
:param string hostname/ip: Hostname or IP of the server containing the contract.
Optional. Defaults to current server if not provided
:param object opts: Optional parameters for configuring function behavior. Properties:
* returnReward (*boolean*) If truthy, then the function will return a string
that states the contract's reward when it is successfully solved.
Attempts to solve the Coding Contract with the provided solution.
:returns: Boolean indicating whether the solution was correct
:returns: Boolean indicating whether the solution was correct. If the :code:`returnReward`
option is configured, then the function will instead return a string. If the
contract is successfully solved, the string will contain a description of the
contract's reward. Otherwise, it will be an empty string.

@ -2311,6 +2311,13 @@ function displaySourceFiles(listElement, sourceFiles) {
}
}
export function isRepeatableAug(aug) {
const augName = (aug instanceof Augmentation) ? aug.name : aug;
if (augName === AugmentationNames.NeuroFluxGovernor) { return true; }
return false;
}
export {installAugmentations,
initAugmentations, applyAugmentation, augmentationExists,

@ -4173,7 +4173,7 @@ function initBladeburner() {
"results would be catastrophic.<br><br>" +
"We do not have the power or jurisdiction to shutdown this down " +
"through legal or political means, so we must resort to a covert " +
"operation. Your goal is to destroy this technology and eliminate" +
"operation. Your goal is to destroy this technology and eliminate " +
"anyone who was involved in its creation.",
baseDifficulty:15e3, reqdRank:30e3,
rankGain:750, rankLoss:60, hpLoss:1000,

@ -17,9 +17,6 @@ import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
/* Represents different types of problems that a Coding Contract can have */

@ -234,11 +234,16 @@ export let CONSTANTS: IMap<any> = {
** This is just a temporary patch until the mechanic gets re-worked
* hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)
* codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward
* Adjusted RAM costs of Netscript Singularity functions (mostly increased)
* Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function
* Netscript Singularity functions no longer cost extra RAM outside of BitNode-4
* Corporation employees no longer have an "age" stat
* Gang Wanted level gain rate capped at 100 (per employee)
* Script startup/kill is now processed every 3 seconds, instead of 6 seconds
* getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server
* Money/Income tracker now displays money lost from hospitalizations
* Exported saves now have a unique filename based on current BitNode and timestamp
* Bug Fix: Corporation employees stats should no longer become negative
* Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios
* Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server
@ -253,5 +258,7 @@ export let CONSTANTS: IMap<any> = {
* Bug Fix: Terminal 'wget' command now correctly evaluates directory paths
* Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in
* Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions
* Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button
* Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy
`
}

@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
import { FactionRoot } from "./ui/Root";
import { Augmentations } from "../Augmentation/Augmentations";
import { isRepeatableAug } from "../Augmentation/AugmentationHelpers";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@ -93,7 +94,12 @@ export function purchaseAugmentationBoxCreate(aug, fac) {
const yesBtn = yesNoBoxGetYesButton();
yesBtn.innerHTML = "Purchase";
yesBtn.addEventListener("click", function() {
if (!isRepeatableAug(aug) && Player.hasAugmentation(aug)) {
return;
}
purchaseAugmentation(aug, fac);
yesNoBoxClose();
});
const noBtn = yesNoBoxGetNoButton();
@ -204,7 +210,6 @@ export function purchaseAugmentation(aug, fac, sing=false) {
"Please report this to the game developer with an explanation of how to " +
"reproduce this.");
}
yesNoBoxClose();
}
export function getNextNeurofluxLevel() {

@ -700,7 +700,7 @@ GangMember.prototype.calculateWantedLevelGain = function(gang) {
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
// denominator is very small. Might want to rethink formula later
return Math.max(100, calc);
return Math.min(100, calc);
}
}

@ -61,7 +61,7 @@ export function purchaseHacknet() {
}
}
/* END INTERACTIVE TUTORIAL */
const numOwned = Player.hacknetNodes.length;
if (hasHacknetServers()) {
const cost = getCostOfNextHacknetServer();
@ -350,7 +350,7 @@ export function purchaseCoreUpgrade(node, levels=1) {
export function purchaseCacheUpgrade(node, levels=1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCoreUpgradeCost(sanitizedLevels);
const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}

@ -269,7 +269,7 @@ export const RamCosts: IMap<any> = {
getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2,
getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 5,
},
// Duplicate Sleeve API

@ -2142,29 +2142,23 @@ function NetscriptFunctions(workerScript) {
},
getHackTime: function(ip, hack, int) {
updateDynamicRam("getHackTime", getRamCost("getHackTime"));
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getHackTime() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getHackTime() failed. Invalid IP or hostname passed in: " + ip);
}
const server = safeGetServer(ip, "getHackTime");
if (failOnHacknetServer(server, "getHackTime")) { return Infinity; }
return calculateHackingTime(server, hack, int); // Returns seconds
},
getGrowTime: function(ip, hack, int) {
updateDynamicRam("getGrowTime", getRamCost("getGrowTime"));
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getGrowTime() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getGrowTime() failed. Invalid IP or hostname passed in: " + ip);
}
const server = safeGetServer(ip, "getGrowTime");
if (failOnHacknetServer(server, "getGrowTime")) { return Infinity; }
return calculateGrowTime(server, hack, int); // Returns seconds
},
getWeakenTime: function(ip, hack, int) {
updateDynamicRam("getWeakenTime", getRamCost("getWeakenTime"));
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getWeakenTime() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getWeakenTime() failed. Invalid IP or hostname passed in: " + ip);
}
const server = safeGetServer(ip, "getWeakenTime");
if (failOnHacknetServer(server, "getWeakenTime")) { return Infinity; }
return calculateWeakenTime(server, hack, int); // Returns seconds
},
getScriptIncome: function(scriptname, ip) {
@ -3453,7 +3447,13 @@ function NetscriptFunctions(workerScript) {
nsGang.checkGangApiAccess(workerScript, "getOtherGangInformation");
try {
return Object.assign(AllGangs);
// We have to make a deep copy
const cpy = {};
for (const gang in AllGangs) {
cpy[gang] = Object.assign({}, AllGangs[gang]);
}
return cpy;
} catch(e) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getOtherGangInformation", e));
}
@ -4137,7 +4137,7 @@ function NetscriptFunctions(workerScript) {
// Coding Contract API
codingcontract: {
attempt: function(answer, fn, ip=workerScript.serverIp) {
attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) {
updateDynamicRam("attempt", getRamCost("codingcontract", "attempt"));
const contract = getCodingContract(fn, ip);
if (contract == null) {
@ -4163,7 +4163,7 @@ function NetscriptFunctions(workerScript) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`);
serv.removeContract(fn);
return true;
return returnReward ? reward : true;
} else {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
@ -4172,7 +4172,8 @@ function NetscriptFunctions(workerScript) {
} else {
workerScript.log(`Coding Contract ${fn} failed. ${contract.getMaxNumTries() - contract.tries} attempts remaining`);
}
return false;
return returnReward ? "" : false;
}
},
getContractType: function(fn, ip=workerScript.serverIp) {

@ -413,34 +413,34 @@ function processNetscript1Imports(code, workerScript) {
return res;
}
//Loop through workerScripts and run every script that is not currently running
// Loop through workerScripts and run every script that is not currently running
export function runScriptsLoop() {
var scriptDeleted = false;
let scriptDeleted = false;
//Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
for (var i = workerScripts.length - 1; i >= 0; i--) {
// Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
for (let i = workerScripts.length - 1; i >= 0; i--) {
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
scriptDeleted = true;
//Delete script from the runningScripts array on its host serverIp
var ip = workerScripts[i].serverIp;
var name = workerScripts[i].name;
// Delete script from the runningScripts array on its host serverIp
const ip = workerScripts[i].serverIp;
const name = workerScripts[i].name;
//recalculate ram used
// Recalculate ram used
AllServers[ip].ramUsed = 0;
for(let j = 0; j < workerScripts.length; j++) {
if(workerScripts[j].serverIp !== ip) {
continue
for (let j = 0; j < workerScripts.length; j++) {
if (workerScripts[j].serverIp !== ip) {
continue;
}
if(j === i) { // not this one
continue
if (j === i) { // not this one
continue;
}
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
}
//Delete script from Active Scripts
// Delete script from Active Scripts
deleteActiveScriptsItem(workerScripts[i]);
for (var j = 0; j < AllServers[ip].runningScripts.length; j++) {
for (let j = 0; j < AllServers[ip].runningScripts.length; j++) {
if (AllServers[ip].runningScripts[j].filename == name &&
compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
AllServers[ip].runningScripts.splice(j, 1);
@ -448,16 +448,16 @@ export function runScriptsLoop() {
}
}
//Delete script from workerScripts
// Delete script from workerScripts
workerScripts.splice(i, 1);
}
}
if (scriptDeleted) {updateActiveScriptsItems();} //Force Update
if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
//Run any scripts that haven't been started
for (var i = 0; i < workerScripts.length; i++) {
//If it isn't running, start the script
// Run any scripts that haven't been started
for (let i = 0; i < workerScripts.length; i++) {
// If it isn't running, start the script
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
let p = null; // p is the script's result promise.
if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) {
@ -467,8 +467,8 @@ export function runScriptsLoop() {
if (!(p instanceof Promise)) { continue; }
}
//Once the code finishes (either resolved or rejected, doesnt matter), set its
//running status to false
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
p.then(function(w) {
console.log("Stopping script " + w.name + " because it finished running naturally");
w.running = false;
@ -480,9 +480,9 @@ export function runScriptsLoop() {
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
return;
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
//Script ends with a return statement
// Script ends with a return statement
console.log("Script returning with value: " + w[1]);
//TODO maybe do something with this in the future
// TODO maybe do something with this in the future
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
@ -517,7 +517,7 @@ export function runScriptsLoop() {
}
}
setTimeoutRef(runScriptsLoop, 6000);
setTimeoutRef(runScriptsLoop, 3e3);
}
/**

@ -1,3 +1,4 @@
import * as augmentationMethods from "./PlayerObjectAugmentationMethods";
import * as bladeburnerMethods from "./PlayerObjectBladeburnerMethods";
import * as corporationMethods from "./PlayerObjectCorporationMethods";
import * as gangMethods from "./PlayerObjectGangMethods";
@ -209,7 +210,8 @@ Object.assign(
serverMethods,
bladeburnerMethods,
corporationMethods,
gangMethods
gangMethods,
augmentationMethods
);
PlayerObject.prototype.toJSON = function() {

@ -0,0 +1,20 @@
/**
* Augmentation-related methods for the Player class (PlayerObject)
*/
import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation";
export function hasAugmentation(this: IPlayer, aug: string | Augmentation): boolean {
const augName: string = (aug instanceof Augmentation) ? aug.name : aug;
for (const owned of this.augmentations) {
if (owned.name === augName) { return true; }
}
for (const owned of this.queuedAugmentations) {
if (owned.name === augName) { return true; }
}
return false;
}

@ -219,6 +219,11 @@ export function prestigeSourceFile() {
}
}
const characterMenuHeader = document.getElementById("character-menu-header");
if (characterMenuHeader instanceof HTMLElement) {
characterMenuHeader.click(); characterMenuHeader.click();
}
this.isWorking = false;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
@ -1567,7 +1572,9 @@ export function hospitalize() {
);
}
this.loseMoney(this.max_hp * CONSTANTS.HospitalCostPerHp);
const cost = this.max_hp * CONSTANTS.HospitalCostPerHp
this.loseMoney(cost);
this.recordMoneySource(-1 * cost, "hospitalization");
this.hp = this.max_hp;
}

@ -26,6 +26,7 @@ import {
loadSpecialServerIps,
SpecialServerIps
} from "./Server/SpecialServerIps";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { createStatusText } from "./ui/createStatusText";
@ -541,23 +542,12 @@ function loadImportedGame(saveObj, saveString) {
}
BitburnerSaveObject.prototype.exportGame = function() {
this.PlayerSave = JSON.stringify(Player);
this.AllServersSave = JSON.stringify(AllServers);
this.CompaniesSave = JSON.stringify(Companies);
this.FactionsSave = JSON.stringify(Factions);
this.SpecialServerIpsSave = JSON.stringify(SpecialServerIps);
this.AliasesSave = JSON.stringify(Aliases);
this.GlobalAliasesSave = JSON.stringify(GlobalAliases);
this.MessagesSave = JSON.stringify(Messages);
this.StockMarketSave = JSON.stringify(StockMarket);
this.SettingsSave = JSON.stringify(Settings);
this.VersionSave = JSON.stringify(CONSTANTS.Version);
if (Player.inGang()) {
this.AllGangsSave = JSON.stringify(AllGangs);
}
const saveString = this.getSaveString();
var saveString = btoa(unescape(encodeURIComponent(JSON.stringify(this))));
var filename = "bitburnerSave.json";
// Save file name is based on current timestamp and BitNode
const epochTime = Math.round(Date.now() / 1000);
const bn = Player.bitNodeN;
const filename = `bitburnerSave_BN${bn}x${SourceFileFlags[bn]}_${epochTime}.json`;
var file = new Blob([saveString], {type: 'text/plain'});
if (window.navigator.msSaveOrOpenBlob) {// IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
@ -565,7 +555,7 @@ BitburnerSaveObject.prototype.exportGame = function() {
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = "bitburnerSave.json";
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeoutRef(function() {

@ -37,8 +37,8 @@ export function removeTrailingSlash(s: string): string {
*/
export function isValidFilename(filename: string): boolean {
// Allows alphanumerics, hyphens, underscores.
// Must have a file exntesion
const regex = /^[.a-zA-Z0-9_-]+[.][.a-zA-Z0-9_-]+$/;
// Must have a file extension
const regex = /^[.a-zA-Z0-9_-]+[.][.a-zA-Z0-9]+$/;
// match() returns null if no match is found
return filename.match(regex) != null;
@ -98,6 +98,7 @@ export function isValidDirectoryPath(path: string): boolean {
* proper formatting. It dose NOT check if the file actually exists on Terminal
*/
export function isValidFilePath(path: string): boolean {
if (path == null || typeof path !== "string") { return false; }
let t_path = path;
// Impossible for filename to have less than length of 3

@ -1179,6 +1179,7 @@ const Engine = {
initAugmentations();
initMessages();
initLiterature();
updateSourceFileFlags(Player);
// Open main menu accordions for new game
const hackingHdr = document.getElementById("hacking-menu-header");

@ -1,6 +1,6 @@
/**
* This is an object that is used to keep track of where all of the player's
* money is coming from
* money is coming from (or going to)
*/
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
@ -17,6 +17,7 @@ export class MoneySourceTracker {
gang: number = 0;
hacking: number = 0;
hacknetnode: number = 0;
hospitalization: number = 0;
infiltration: number = 0;
stock: number = 0;
total: number = 0;

@ -0,0 +1,291 @@
import * as dirHelpers from "../../src/Terminal/DirectoryHelpers";
import { expect } from "chai";
console.log("Beginning Terminal Directory Tests");
describe("Terminal Directory Tests", function() {
describe("removeLeadingSlash()", function() {
const removeLeadingSlash = dirHelpers.removeLeadingSlash;
it("should remove first slash in a string", function() {
expect(removeLeadingSlash("/")).to.equal("");
expect(removeLeadingSlash("/foo.txt")).to.equal("foo.txt");
expect(removeLeadingSlash("/foo/file.txt")).to.equal("foo/file.txt");
});
it("should only remove one slash", function() {
expect(removeLeadingSlash("///")).to.equal("//");
expect(removeLeadingSlash("//foo")).to.equal("/foo");
});
it("should do nothing for a string that doesn't start with a slash", function() {
expect(removeLeadingSlash("foo.txt")).to.equal("foo.txt");
expect(removeLeadingSlash("foo/test.txt")).to.equal("foo/test.txt");
});
it("should not fail on an empty string", function() {
expect(removeLeadingSlash.bind(null, "")).to.not.throw();
expect(removeLeadingSlash("")).to.equal("");
});
});
describe("removeTrailingSlash()", function() {
const removeTrailingSlash = dirHelpers.removeTrailingSlash;
it("should remove last slash in a string", function() {
expect(removeTrailingSlash("/")).to.equal("");
expect(removeTrailingSlash("foo.txt/")).to.equal("foo.txt");
expect(removeTrailingSlash("foo/file.txt/")).to.equal("foo/file.txt");
});
it("should only remove one slash", function() {
expect(removeTrailingSlash("///")).to.equal("//");
expect(removeTrailingSlash("foo//")).to.equal("foo/");
});
it("should do nothing for a string that doesn't end with a slash", function() {
expect(removeTrailingSlash("foo.txt")).to.equal("foo.txt");
expect(removeTrailingSlash("foo/test.txt")).to.equal("foo/test.txt");
});
it("should not fail on an empty string", function() {
expect(removeTrailingSlash.bind(null, "")).to.not.throw();
expect(removeTrailingSlash("")).to.equal("");
});
});
describe("isValidFilename()", function() {
const isValidFilename = dirHelpers.isValidFilename;
it("should return true for valid filenames", function() {
expect(isValidFilename("test.txt")).to.equal(true);
expect(isValidFilename("123.script")).to.equal(true);
expect(isValidFilename("foo123.b")).to.equal(true);
expect(isValidFilename("my_script.script")).to.equal(true);
expect(isValidFilename("my-script.script")).to.equal(true);
expect(isValidFilename("_foo.lit")).to.equal(true);
expect(isValidFilename("mult.periods.script")).to.equal(true);
expect(isValidFilename("mult.per-iods.again.script")).to.equal(true);
});
it("should return false for invalid filenames", function() {
expect(isValidFilename("foo")).to.equal(false);
expect(isValidFilename("my script.script")).to.equal(false);
expect(isValidFilename("a^.txt")).to.equal(false);
expect(isValidFilename("b#.lit")).to.equal(false);
expect(isValidFilename("lib().js")).to.equal(false);
expect(isValidFilename("foo.script_")).to.equal(false);
expect(isValidFilename("foo._script")).to.equal(false);
expect(isValidFilename("foo.hyphened-ext")).to.equal(false);
expect(isValidFilename("")).to.equal(false);
});
});
describe("isValidDirectoryName()", function() {
const isValidDirectoryName = dirHelpers.isValidDirectoryName;
it("should return true for valid directory names", function() {
expect(isValidDirectoryName("a")).to.equal(true);
expect(isValidDirectoryName("foo")).to.equal(true);
expect(isValidDirectoryName("foo-dir")).to.equal(true);
expect(isValidDirectoryName("foo_dir")).to.equal(true);
expect(isValidDirectoryName(".a")).to.equal(true);
expect(isValidDirectoryName("1")).to.equal(true);
expect(isValidDirectoryName("a1")).to.equal(true);
expect(isValidDirectoryName(".a1")).to.equal(true);
expect(isValidDirectoryName("._foo")).to.equal(true);
expect(isValidDirectoryName("_foo")).to.equal(true);
});
it("should return false for invalid directory names", function() {
expect(isValidDirectoryName("")).to.equal(false);
expect(isValidDirectoryName("foo.dir")).to.equal(false);
expect(isValidDirectoryName("1.")).to.equal(false);
expect(isValidDirectoryName("foo.")).to.equal(false);
expect(isValidDirectoryName("dir#")).to.equal(false);
expect(isValidDirectoryName("dir!")).to.equal(false);
expect(isValidDirectoryName("dir*")).to.equal(false);
expect(isValidDirectoryName(".")).to.equal(false);
});
});
describe("isValidDirectoryPath()", function() {
const isValidDirectoryPath = dirHelpers.isValidDirectoryPath;
it("should return false for empty strings", function() {
expect(isValidDirectoryPath("")).to.equal(false);
});
it("should return true only for the forward slash if the string has length 1", function() {
expect(isValidDirectoryPath("/")).to.equal(true);
expect(isValidDirectoryPath(" ")).to.equal(false);
expect(isValidDirectoryPath(".")).to.equal(false);
expect(isValidDirectoryPath("a")).to.equal(false);
});
it("should return true for valid directory paths", function() {
expect(isValidDirectoryPath("/a")).to.equal(true);
expect(isValidDirectoryPath("/dir/a")).to.equal(true);
expect(isValidDirectoryPath("/dir/foo")).to.equal(true);
expect(isValidDirectoryPath("/.dir/foo-dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/foo_dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/.a")).to.equal(true);
expect(isValidDirectoryPath("/dir1/1")).to.equal(true);
expect(isValidDirectoryPath("/dir1/a1")).to.equal(true);
expect(isValidDirectoryPath("/dir1/.a1")).to.equal(true);
expect(isValidDirectoryPath("/dir_/._foo")).to.equal(true);
expect(isValidDirectoryPath("/dir-/_foo")).to.equal(true);
});
it("should return false if the path does not have a leading slash", function() {
expect(isValidDirectoryPath("a")).to.equal(false);
expect(isValidDirectoryPath("dir/a")).to.equal(false);
expect(isValidDirectoryPath("dir/foo")).to.equal(false);
expect(isValidDirectoryPath(".dir/foo-dir")).to.equal(false);
expect(isValidDirectoryPath(".dir/foo_dir")).to.equal(false);
expect(isValidDirectoryPath(".dir/.a")).to.equal(false);
expect(isValidDirectoryPath("dir1/1")).to.equal(false);
expect(isValidDirectoryPath("dir1/a1")).to.equal(false);
expect(isValidDirectoryPath("dir1/.a1")).to.equal(false);
expect(isValidDirectoryPath("dir_/._foo")).to.equal(false);
expect(isValidDirectoryPath("dir-/_foo")).to.equal(false);
});
it("should accept dot notation", function() {
expect(isValidDirectoryPath("/dir/./a")).to.equal(true);
expect(isValidDirectoryPath("/dir/../foo")).to.equal(true);
expect(isValidDirectoryPath("/.dir/./foo-dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/../foo_dir")).to.equal(true);
expect(isValidDirectoryPath("/.dir/./.a")).to.equal(true);
expect(isValidDirectoryPath("/dir1/1/.")).to.equal(true);
expect(isValidDirectoryPath("/dir1/a1/..")).to.equal(true);
expect(isValidDirectoryPath("/dir1/.a1/..")).to.equal(true);
expect(isValidDirectoryPath("/dir_/._foo/.")).to.equal(true);
expect(isValidDirectoryPath("/./dir-/_foo")).to.equal(true);
expect(isValidDirectoryPath("/../dir-/_foo")).to.equal(true);
});
});
describe("isValidFilePath()", function() {
const isValidFilePath = dirHelpers.isValidFilePath;
it("should return false for strings that are too short", function() {
expect(isValidFilePath("/a")).to.equal(false);
expect(isValidFilePath("a.")).to.equal(false);
expect(isValidFilePath(".a")).to.equal(false);
expect(isValidFilePath("/.")).to.equal(false);
});
it("should return true for arguments that are just filenames", function() {
expect(isValidFilePath("test.txt")).to.equal(true);
expect(isValidFilePath("123.script")).to.equal(true);
expect(isValidFilePath("foo123.b")).to.equal(true);
expect(isValidFilePath("my_script.script")).to.equal(true);
expect(isValidFilePath("my-script.script")).to.equal(true);
expect(isValidFilePath("_foo.lit")).to.equal(true);
expect(isValidFilePath("mult.periods.script")).to.equal(true);
expect(isValidFilePath("mult.per-iods.again.script")).to.equal(true);
});
it("should return true for valid filepaths", function() {
expect(isValidFilePath("/foo/test.txt")).to.equal(true);
expect(isValidFilePath("/../123.script")).to.equal(true);
expect(isValidFilePath("/./foo123.b")).to.equal(true);
expect(isValidFilePath("/dir/my_script.script")).to.equal(true);
expect(isValidFilePath("/dir1/dir2/dir3/my-script.script")).to.equal(true);
expect(isValidFilePath("/dir1/dir2/././../_foo.lit")).to.equal(true);
expect(isValidFilePath("/.dir1/./../.dir2/mult.periods.script")).to.equal(true);
expect(isValidFilePath("/_dir/../dir2/mult.per-iods.again.script")).to.equal(true);
});
it("should return false for strings that end with a slash", function() {
expect(isValidFilePath("/foo/")).to.equal(false);
expect(isValidFilePath("foo.txt/")).to.equal(false);
expect(isValidFilePath("/")).to.equal(false);
expect(isValidFilePath("/_dir/")).to.equal(false);
});
it("should return false for invalid arguments", function() {
expect(isValidFilePath(null)).to.equal(false);
expect(isValidFilePath()).to.equal(false);
expect(isValidFilePath(5)).to.equal(false);
expect(isValidFilePath({})).to.equal(false);
})
});
describe("getFirstParentDirectory()", function() {
const getFirstParentDirectory = dirHelpers.getFirstParentDirectory;
it("should return the first parent directory in a filepath", function() {
expect(getFirstParentDirectory("/dir1/foo.txt")).to.equal("dir1/");
expect(getFirstParentDirectory("/dir1/dir2/dir3/dir4/foo.txt")).to.equal("dir1/");
expect(getFirstParentDirectory("/_dir1/dir2/foo.js")).to.equal("_dir1/");
});
it("should return '/' if there is no first parent directory", function() {
expect(getFirstParentDirectory("")).to.equal("/");
expect(getFirstParentDirectory(" ")).to.equal("/");
expect(getFirstParentDirectory("/")).to.equal("/");
expect(getFirstParentDirectory("//")).to.equal("/");
expect(getFirstParentDirectory("foo.script")).to.equal("/");
expect(getFirstParentDirectory("/foo.txt")).to.equal("/");
});
});
describe("getAllParentDirectories()", function() {
const getAllParentDirectories = dirHelpers.getAllParentDirectories;
it("should return all parent directories in a filepath", function() {
expect(getAllParentDirectories("/")).to.equal("/");
expect(getAllParentDirectories("/home/var/foo.txt")).to.equal("/home/var/");
expect(getAllParentDirectories("/home/var/")).to.equal("/home/var/");
expect(getAllParentDirectories("/home/var/test/")).to.equal("/home/var/test/");
});
it("should return an empty string if there are no parent directories", function() {
expect(getAllParentDirectories("foo.txt")).to.equal("");
});
});
describe("isInRootDirectory()", function() {
const isInRootDirectory = dirHelpers.isInRootDirectory;
it("should return true for filepaths that refer to a file in the root directory", function() {
expect(isInRootDirectory("a.b")).to.equal(true);
expect(isInRootDirectory("foo.txt")).to.equal(true);
expect(isInRootDirectory("/foo.txt")).to.equal(true);
});
it("should return false for filepaths that refer to a file that's NOT in the root directory", function() {
expect(isInRootDirectory("/dir/foo.txt")).to.equal(false);
expect(isInRootDirectory("dir/foo.txt")).to.equal(false);
expect(isInRootDirectory("/./foo.js")).to.equal(false);
expect(isInRootDirectory("../foo.js")).to.equal(false);
expect(isInRootDirectory("/dir1/dir2/dir3/foo.txt")).to.equal(false);
});
it("should return false for invalid inputs (inputs that aren't filepaths)", function() {
expect(isInRootDirectory(null)).to.equal(false);
expect(isInRootDirectory(undefined)).to.equal(false);
expect(isInRootDirectory("")).to.equal(false);
expect(isInRootDirectory(" ")).to.equal(false);
expect(isInRootDirectory("a")).to.equal(false);
expect(isInRootDirectory("/dir")).to.equal(false);
expect(isInRootDirectory("/dir/")).to.equal(false);
expect(isInRootDirectory("/dir/foo")).to.equal(false);
});
});
describe("evaluateDirectoryPath()", function() {
const evaluateDirectoryPath = dirHelpers.evaluateDirectoryPath;
// TODO
});
describe("evaluateFilePath()", function() {
const evaluateFilePath = dirHelpers.evaluateFilePath;
// TODO
})
});

@ -1,3 +1,4 @@
export * from "./Netscript/DynamicRamCalculationTests";
export * from "./Netscript/StaticRamCalculationTests";
export * from "./StockMarketTests";
export * from "./Terminal/DirectoryTests";

@ -14,7 +14,7 @@ export let yesNoBoxOpen: boolean = false;
const yesNoBoxContainer: HTMLElement | null = document.getElementById("yes-no-box-container");
const yesNoBoxTextElement: HTMLElement | null = document.getElementById("yes-no-box-text");
export function yesNoBoxHotkeyHandler(e: KeyboardEvent) {
function yesNoBoxHotkeyHandler(e: KeyboardEvent) {
if (e.keyCode === KEY.ESC) {
yesNoBoxClose();
} else if (e.keyCode === KEY.ENTER) {