mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-08-30 19:52:29 +00:00
224 lines
7.5 KiB
JavaScript
224 lines
7.5 KiB
JavaScript
// This code adapted from Nic Bradley's R20 test framework from the WFRP4e official sheet.
|
|
const _ = require('underscore');
|
|
const environment = {
|
|
attributes: {},
|
|
triggers: [],
|
|
};
|
|
global.environment = environment;
|
|
const on = jest.fn((trigger, func) => {
|
|
environment.triggers.push({ trigger, func });
|
|
});
|
|
global.on = on;
|
|
const getAttrs = jest.fn((query, callback) => {
|
|
let values = {};
|
|
for (const attr of query) {
|
|
if (attr in environment.attributes) values[attr] = environment.attributes[attr];
|
|
}
|
|
if (typeof callback === "function") callback(values);
|
|
});
|
|
global.getAttrs = getAttrs;
|
|
const setAttrs = jest.fn((submit, params, callback) => {
|
|
if (!callback && typeof params === "function") callback = params;
|
|
for (const attr in submit) {
|
|
environment.attributes[attr] = submit[attr];
|
|
}
|
|
if (typeof callback === "function") callback();
|
|
});
|
|
global.setAttrs = setAttrs;
|
|
const getSectionIDs = jest.fn((section, callback) => {
|
|
const ids = [];
|
|
const sectionName = section.indexOf("repeating_") === 0 ? section : `repeating_${section}`;
|
|
const attributes = environment.attributes;
|
|
for (const attr in attributes) {
|
|
if (attr.indexOf(sectionName) === 0) ids.push(attr.split("_")[2]);
|
|
}
|
|
const idMap = [...new Set(ids)];
|
|
if (typeof callback === "function") callback(idMap);
|
|
});
|
|
global.getSectionIDs = getSectionIDs;
|
|
const getSectionIDsSync = jest.fn((section) => {
|
|
const ids = [];
|
|
const sectionName = section.indexOf("repeating_") === 0 ? section : `repeating_${section}`;
|
|
const attributes = environment.attributes;
|
|
for (const attr in attributes) {
|
|
if (attr.indexOf(sectionName) === 0) ids.push(attr.split("_")[2]);
|
|
}
|
|
const idMap = [...new Set(ids)];
|
|
return idMap;
|
|
});
|
|
global.getSectionIDsSync = getSectionIDsSync;
|
|
const removeRepeatingRow = jest.fn((id) => {
|
|
const attributes = environment.attributes;
|
|
for (const attr in attributes) {
|
|
if (attr.indexOf(id) > -1) delete environment.attributes[attr];
|
|
}
|
|
});
|
|
global.removeRepeatingRow = removeRepeatingRow;
|
|
const getCompendiumPage = jest.fn((request, callback) => {
|
|
const pages = compendiumData;
|
|
if (!pages)
|
|
throw new Error(
|
|
"Tried to use getCompendiumPage, but testing environment does not contain compendiumData."
|
|
);
|
|
if (typeof request === "string") {
|
|
const [category, pageName] = request.split(":");
|
|
const response = {
|
|
Name: pageName,
|
|
Category: category,
|
|
data: {},
|
|
};
|
|
if (pages[request]) response.data = pages[request].data;
|
|
if (typeof callback === "function") callback(response);
|
|
} else if (Array.isArray(request)) {
|
|
const pageArray = [];
|
|
for (const page of request) {
|
|
if (pages[request] && pages[request].Category === category) pageArray.push(pages[pageName]);
|
|
}
|
|
if (typeof callback === "function") callback(pageArray);
|
|
}
|
|
});
|
|
global.getCompendiumPage = getCompendiumPage;
|
|
const generateUUID = jest.fn(() => {
|
|
var a = 0,
|
|
b = [];
|
|
return (function () {
|
|
var c = new Date().getTime() + 0,
|
|
d = c === a;
|
|
a = c;
|
|
for (var e = Array(8), f = 7; 0 <= f; f--)
|
|
(e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64)),
|
|
(c = Math.floor(c / 64));
|
|
c = e.join("");
|
|
if (d) {
|
|
for (f = 11; 0 <= f && 63 === b[f]; f--) b[f] = 0;
|
|
b[f]++;
|
|
} else for (f = 0; 12 > f; f++) b[f] = Math.floor(64 * Math.random());
|
|
for (f = 0; 12 > f; f++)
|
|
c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]);
|
|
return c.replace(/_/g, "z");
|
|
})();
|
|
});
|
|
global.generateUUID = generateUUID;
|
|
const generateRowID = jest.fn(() => {
|
|
return generateUUID().replace(/_/g, "Z");
|
|
});
|
|
global.generateRowID = generateRowID;
|
|
const simulateEvent = jest.fn((event) => {
|
|
environment.triggers.forEach((trigger) => {
|
|
const splitTriggers = trigger.trigger.split(" ") || [trigger.trigger];
|
|
splitTriggers.forEach((singleTrigger) => {
|
|
if (event === singleTrigger) {
|
|
trigger.func({
|
|
sourceAttribute: "test",
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
global.simulateEvent = simulateEvent;
|
|
const getTranslationByKey = jest.fn((key) => key);
|
|
global.getTranslationByKey = getTranslationByKey;
|
|
// Roll Handlingglobal.getTranslationByKey = getTranslationByKey;
|
|
|
|
const extractRollTemplate = (rollString) => {
|
|
const rollTemplate = rollString.match(/&\{template:(.*?)\}/)?.[1];
|
|
environment.attributes.__rolltemplate = rollTemplate;
|
|
};
|
|
|
|
const cleanRollElements = (value) => value.replace(/(\{\{|\}})/gi, "").split("=");
|
|
|
|
const extractRollElements = (rollString) => {
|
|
const rollElements = rollString.match(/\{\{(.*?)\}{2,}/g);
|
|
if (!rollElements || rollElements.length < 1) throw new Error("No roll elements provided in roll string");
|
|
return Object.fromEntries(rollElements.map(cleanRollElements));
|
|
};
|
|
|
|
const getExpression = (element) => element.replace(/(\[\[|\]\])/gi, "");
|
|
|
|
const getDiceOrHalf = (size) => {
|
|
const diceStack = environment.diceStack;
|
|
if (!diceStack?.[size] || diceStack[size].length < 0) return size / 2;
|
|
return environment.diceStack[size].pop();
|
|
};
|
|
|
|
const getDiceRolls = (expression) => {
|
|
const rolls = expression.match(/([0-9]+)?d([0-9]+)/gi);
|
|
if (!rolls) return [];
|
|
const allRolls = [];
|
|
rolls.forEach((roll) => {
|
|
const [number, size] = roll.split(/d/i);
|
|
for (let i = 1; i <= number; i++) {
|
|
const dice = getDiceOrHalf(size);
|
|
allRolls.push(dice);
|
|
}
|
|
});
|
|
return allRolls;
|
|
};
|
|
|
|
const calculateResult = (expression, dice) => {
|
|
const rolls = expression.match(/([0-9]+)?d([0-9]+)/gi);
|
|
if (!rolls) return eval(expression);
|
|
rolls.forEach((roll, index) => {
|
|
const [number, size] = roll.split(/d/i);
|
|
let total = 0;
|
|
for (let i = 1; i <= number; i++) {
|
|
total += +dice.shift();
|
|
}
|
|
expression = expression.replace(/([0-9]+d[0-9]+([+\-*/][0-9]+)?)(.*?)$/gi, "$1");
|
|
const regex = new RegExp(roll, "gi");
|
|
expression = expression.replace(regex, total);
|
|
});
|
|
|
|
return eval(expression);
|
|
};
|
|
|
|
const replaceAttributes = (element) => {
|
|
const test = /@\{(.*?)\}/i;
|
|
while (test.test(element)) {
|
|
element = element.replace(/@\{(.*?)\}/gi, (sub, ...args) => {
|
|
const attributeName = args[0];
|
|
const attributeValue = environment.attributes[attributeName];
|
|
const attributeExists = typeof attributeValue !== "undefined";
|
|
const possibleAttributes = Object.keys(environment.attributes);
|
|
if (attributeExists) return attributeValue;
|
|
else
|
|
throw new Error(
|
|
`Roll called ${sub} but no corresponding attribute "${attributeName}" was found. Attributes are: ${possibleAttributes.join(
|
|
", "
|
|
)}`
|
|
);
|
|
});
|
|
}
|
|
return element;
|
|
};
|
|
|
|
const calculateRollResult = (rollElements) => {
|
|
const results = {};
|
|
for (const key in rollElements) {
|
|
const element = rollElements[key];
|
|
if (element.indexOf("[[") === -1) continue;
|
|
const attributeFilled = replaceAttributes(element);
|
|
const expression = getExpression(attributeFilled);
|
|
const dice = getDiceRolls(expression);
|
|
const result = calculateResult(expression, [...dice]);
|
|
results[key] = {
|
|
result,
|
|
dice,
|
|
expression,
|
|
};
|
|
}
|
|
return results;
|
|
};
|
|
|
|
const startRoll = jest.fn(async (rollString) => {
|
|
if (!rollString) throw new Error("startRoll expected a Roll String but none was provided.");
|
|
const rollResult = { results: {} };
|
|
extractRollTemplate(rollString);
|
|
const rollElements = extractRollElements(rollString);
|
|
rollResult.results = calculateRollResult(rollElements);
|
|
rollResult.rollId = generateUUID();
|
|
return rollResult;
|
|
});
|
|
global.startRoll = startRoll;
|
|
const finishRoll = jest.fn(() => {});
|
|
global.finishRoll = finishRoll; |