// 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;