More testing of action system, now with test coverage
This commit is contained in:
16
app/.github/pull_request_template.md
vendored
16
app/.github/pull_request_template.md
vendored
@@ -1,16 +0,0 @@
|
||||
# Checklists
|
||||
|
||||
## Adding features
|
||||
|
||||
- [ ] My new pull request has zero code changes
|
||||
- [ ] I have described the feature I intend to work on
|
||||
- [ ] I have described how I intend to implement the feature
|
||||
- [ ] I will wait for comment from the project's maintainers before submitting code changes
|
||||
|
||||
## Fixing bugs
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have included a link to the relevant github issue or discord post in the description
|
||||
|
||||
# Description
|
||||
|
||||
`Detailed description of your changes`
|
||||
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@@ -3,6 +3,9 @@
|
||||
.demeteorized
|
||||
.cache
|
||||
.vscode
|
||||
.coverage
|
||||
.nyc_output
|
||||
.DS_Store
|
||||
fileStorage
|
||||
settings.json
|
||||
public/components
|
||||
|
||||
@@ -51,3 +51,5 @@ zodern:types
|
||||
zodern:fix-async-stubs
|
||||
typescript
|
||||
ecmascript
|
||||
lmieulet:meteor-legacy-coverage
|
||||
lmieulet:meteor-coverage
|
||||
|
||||
@@ -52,6 +52,8 @@ inter-process-messaging@0.1.1
|
||||
lai:collection-extensions@0.4.0
|
||||
launch-screen@2.0.0
|
||||
littledata:synced-cron@1.5.1
|
||||
lmieulet:meteor-coverage@4.1.0
|
||||
lmieulet:meteor-legacy-coverage@0.2.0
|
||||
localstorage@1.2.0
|
||||
logging@1.3.3
|
||||
mdg:validated-method@1.3.0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { assert } from 'chai';
|
||||
import {
|
||||
allLogContent,
|
||||
allMutations,
|
||||
allUpdates,
|
||||
createTestCreature,
|
||||
@@ -7,12 +8,13 @@ import {
|
||||
removeAllCreaturesAndProps,
|
||||
runActionById
|
||||
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
|
||||
import { Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import { LogContent, Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import Alea from 'alea';
|
||||
|
||||
const [
|
||||
creatureId, targetCreatureId, targetCreature2Id,
|
||||
emptyActionId, attackActionId, usesActionId, attackMissId,
|
||||
creatureId, targetCreatureId, targetCreature2Id, emptyActionId, selfActionId, attackActionId,
|
||||
usesActionId, attackMissId, attackNoTargetId, usesResourcesActionId, ammoId, resourceAttId, consumeAmmoId,
|
||||
consumeResourceId, noUsesActionId, insufficientResourcesActionId
|
||||
] = randomIds;
|
||||
|
||||
const actionTestCreature = {
|
||||
@@ -22,6 +24,13 @@ const actionTestCreature = {
|
||||
{
|
||||
_id: emptyActionId,
|
||||
type: 'action',
|
||||
summary: { text: 'Summary text 1 + 1 = {1 + 1}' }
|
||||
},
|
||||
// Attack that targets self
|
||||
{
|
||||
_id: selfActionId,
|
||||
type: 'action',
|
||||
target: 'self',
|
||||
},
|
||||
// Attack that hits
|
||||
{
|
||||
@@ -35,6 +44,12 @@ const actionTestCreature = {
|
||||
type: 'action',
|
||||
attackRoll: { calculation: '-5' },
|
||||
},
|
||||
// Attack that has no target
|
||||
{
|
||||
_id: attackNoTargetId,
|
||||
type: 'action',
|
||||
attackRoll: { calculation: '1' },
|
||||
},
|
||||
// Disable crits
|
||||
{
|
||||
type: 'attribute',
|
||||
@@ -56,6 +71,57 @@ const actionTestCreature = {
|
||||
usesUsed: 1,
|
||||
reset: 'longRest',
|
||||
},
|
||||
// Not enough uses
|
||||
{
|
||||
_id: noUsesActionId,
|
||||
type: 'action',
|
||||
uses: { calculation: '5' },
|
||||
usesUsed: 5,
|
||||
reset: 'longRest',
|
||||
},
|
||||
// Uses Resources
|
||||
{
|
||||
_id: ammoId,
|
||||
type: 'item',
|
||||
quantity: 12,
|
||||
tags: ['ammo']
|
||||
},
|
||||
{
|
||||
_id: resourceAttId,
|
||||
type: 'attribute',
|
||||
name: 'Resource Name',
|
||||
attributeType: 'stat',
|
||||
baseValue: { calculation: '7' },
|
||||
variableName: 'resourceVar',
|
||||
},
|
||||
{
|
||||
_id: usesResourcesActionId,
|
||||
type: 'action',
|
||||
resources: {
|
||||
itemsConsumed: [{
|
||||
_id: consumeAmmoId,
|
||||
tag: 'ammo',
|
||||
quantity: { calculation: '3' },
|
||||
itemId: ammoId,
|
||||
}],
|
||||
attributesConsumed: [{
|
||||
_id: consumeResourceId,
|
||||
variableName: 'resourceVar',
|
||||
quantity: { calculation: '2' },
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: insufficientResourcesActionId,
|
||||
type: 'action',
|
||||
resources: {
|
||||
attributesConsumed: [{
|
||||
_id: consumeResourceId,
|
||||
variableName: 'resourceVar',
|
||||
quantity: { calculation: '9001' },
|
||||
}]
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -106,11 +172,43 @@ describe('Apply Action Properties', function () {
|
||||
const action = await runActionById(emptyActionId);
|
||||
assert.exists(action);
|
||||
assert.deepEqual(allMutations(action), [{
|
||||
contents: [{ name: 'Action' }],
|
||||
contents: [{
|
||||
name: 'Action',
|
||||
value: 'Summary text 1 + 1 = 2',
|
||||
}],
|
||||
targetIds: [],
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should target self when set', async function () {
|
||||
const action = await runActionById(selfActionId);
|
||||
assert.exists(action);
|
||||
assert.deepEqual(allMutations(action), [{
|
||||
contents: [{
|
||||
name: 'Action',
|
||||
}],
|
||||
targetIds: [creatureId],
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should make attack rolls against no targets', async function () {
|
||||
const action = await runActionById(attackNoTargetId, []);
|
||||
const expectedMutations: Mutation[] = [
|
||||
{
|
||||
contents: [{ name: 'Action' }],
|
||||
targetIds: [],
|
||||
}, {
|
||||
contents: [{
|
||||
name: 'To Hit',
|
||||
value: '1d20 [10] + 1\n**11**',
|
||||
inline: true,
|
||||
}],
|
||||
targetIds: [],
|
||||
}
|
||||
];
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
})
|
||||
|
||||
it('should make attack rolls against multiple creatures', async function () {
|
||||
const action = await runActionById(attackActionId, [
|
||||
targetCreatureId,
|
||||
@@ -151,6 +249,19 @@ describe('Apply Action Properties', function () {
|
||||
assert.deepEqual(allUpdates(action), expectedUpdates);
|
||||
});
|
||||
|
||||
it('should fail to make attacks that have no uses left', async function () {
|
||||
const action = await runActionById(noUsesActionId, [targetCreatureId]);
|
||||
const expectedContent: LogContent[] = [
|
||||
{
|
||||
name: 'Action'
|
||||
}, {
|
||||
name: 'Error',
|
||||
value: 'Action does not have enough uses left'
|
||||
}
|
||||
]
|
||||
assert.deepEqual(allLogContent(action), expectedContent);
|
||||
});
|
||||
|
||||
it('should make attack rolls that miss', async function () {
|
||||
const action = await runActionById(attackMissId, [targetCreatureId]);
|
||||
const expectedMutations: Mutation[] = [
|
||||
@@ -169,4 +280,59 @@ describe('Apply Action Properties', function () {
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
it('actions should consume resources', async function () {
|
||||
const action = await runActionById(usesResourcesActionId, []);
|
||||
const expectedMutations: Mutation[] = [
|
||||
{
|
||||
contents: [{ name: 'Action' }],
|
||||
targetIds: []
|
||||
},
|
||||
{
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Attribute damaged',
|
||||
value: '−2 Resource Name',
|
||||
}],
|
||||
targetIds: [creatureId],
|
||||
updates: [{
|
||||
inc: {
|
||||
damage: 2,
|
||||
value: -2
|
||||
},
|
||||
propId: resourceAttId,
|
||||
type: 'attribute'
|
||||
}],
|
||||
},
|
||||
{
|
||||
targetIds: [],
|
||||
updates: [
|
||||
{
|
||||
inc: {
|
||||
quantity: -3
|
||||
},
|
||||
propId: ammoId,
|
||||
type: 'item',
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
it('should handle insufficient resources', async function () {
|
||||
const action = await runActionById(insufficientResourcesActionId, []);
|
||||
const expectedMutations: Mutation[] = [
|
||||
{
|
||||
contents: [{
|
||||
name: 'Action'
|
||||
}, {
|
||||
name: 'Error',
|
||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||
}],
|
||||
targetIds: [],
|
||||
},
|
||||
];
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -21,33 +21,36 @@ export default async function applyActionProperty(
|
||||
const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
|
||||
//Log the name and summary, check that the property has enough resources to fire
|
||||
const content: LogContent = { name: getPropertyTitle(prop) };
|
||||
if (prop.summary?.text) {
|
||||
await recalculateInlineCalculations(prop.summary, action, 'reduce', userInput);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
if (prop.silent) content.silenced = true;
|
||||
result.appendLog(content, targetIds);
|
||||
result.appendLog({
|
||||
name: getPropertyTitle(prop),
|
||||
...prop.summary && { value: prop.summary.value },
|
||||
...prop.silent && { silenced: true },
|
||||
}, targetIds);
|
||||
|
||||
// Check Uses
|
||||
if (prop.usesLeft <= 0) {
|
||||
if (!prop.silent) result.appendLog({
|
||||
result.appendLog({
|
||||
name: 'Error',
|
||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||
value: `${getPropertyTitle(prop)} does not have enough uses left`,
|
||||
...prop.silent && { silenced: true },
|
||||
}, targetIds);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check Resources
|
||||
if (prop.insufficientResources) {
|
||||
if (!prop.silent) result.appendLog({
|
||||
result.appendLog({
|
||||
name: 'Error',
|
||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||
...prop.silent && { silenced: true },
|
||||
}, targetIds);
|
||||
return;
|
||||
}
|
||||
|
||||
spendResources(action, prop, targetIds, result, userInput);
|
||||
await spendResources(action, prop, targetIds, result, userInput);
|
||||
|
||||
const attack: CalculatedField = prop.attackRoll || prop.attackRollBonus;
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ import { applyTriggers } from '/imports/api/engine/action/functions/applyTaskGro
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
|
||||
export default async function applyDamagePropTask(
|
||||
task: DamagePropTask, action: EngineAction, result: TaskResult, userInput
|
||||
): Promise<number> {
|
||||
|
||||
const prop = task.prop;
|
||||
|
||||
if (task.targetIds.length > 1) {
|
||||
@@ -22,7 +24,7 @@ export default async function applyDamagePropTask(
|
||||
|
||||
// Set the scope properties
|
||||
result.pushScope = {};
|
||||
if (prop.operation === 'increment') {
|
||||
if (operation === 'increment') {
|
||||
if (value >= 0) {
|
||||
result.pushScope['~damage'] = { value };
|
||||
} else {
|
||||
@@ -67,7 +69,7 @@ export default async function applyDamagePropTask(
|
||||
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
inline: true,
|
||||
silenced: prop.silent,
|
||||
...prop.silent && { silenced: true },
|
||||
}, task.targetIds);
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ export default async function applyDamagePropTask(
|
||||
name: title,
|
||||
value: `${getPropertyTitle(targetProp)} set to ${value}`,
|
||||
inline: true,
|
||||
silenced: prop.silent,
|
||||
...prop.silent && { silenced: true },
|
||||
}]
|
||||
});
|
||||
} else if (operation === 'increment') {
|
||||
@@ -120,10 +122,10 @@ export default async function applyDamagePropTask(
|
||||
type: targetProp.type,
|
||||
}],
|
||||
contents: [{
|
||||
name: 'Attribute damage',
|
||||
value: `${getPropertyTitle(targetProp)} ${value}`,
|
||||
name: 'Attribute damaged',
|
||||
value: `${numberToSignedString(-value)} ${getPropertyTitle(targetProp)}`,
|
||||
inline: true,
|
||||
silenced: prop.silent,
|
||||
...prop.silent && { silenced: true },
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,11 +43,13 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
|
||||
type: 'item',
|
||||
}],
|
||||
// Log the item name as a heading if it has child properties to apply
|
||||
contents: itemChildren.length ? [{
|
||||
name: getPropertyTitle(item) || 'Ammo',
|
||||
inline: false,
|
||||
silenced: prop.silent,
|
||||
}] : undefined,
|
||||
...itemChildren.length && {
|
||||
contents: [{
|
||||
name: getPropertyTitle(item) || 'Ammo',
|
||||
inline: false,
|
||||
silenced: prop.silent,
|
||||
}]
|
||||
},
|
||||
});
|
||||
|
||||
await applyTriggers(action, item, [action.creatureId], 'ammo.after', userInput);
|
||||
|
||||
@@ -53,7 +53,7 @@ export default async function applyTask(
|
||||
|
||||
// Create a result an push it to the action results, pass it to the apply function to modify
|
||||
const result = new TaskResult(task.prop._id, task.targetIds);
|
||||
result.scope[`#${prop.type}`] = prop;
|
||||
result.scope[`#${prop.type}`] = { _propId: prop._id };
|
||||
action.results.push(result);
|
||||
|
||||
// Apply the property
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
console.log('call.ts imports')
|
||||
import error from '/imports/parser/parseTree/error';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import functions, { ParserFunction } from '/imports/parser/functions';
|
||||
|
||||
9
app/package-lock.json
generated
9
app/package-lock.json
generated
@@ -1512,6 +1512,15 @@
|
||||
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/simpl-schema": {
|
||||
"version": "1.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/simpl-schema/-/simpl-schema-1.12.7.tgz",
|
||||
"integrity": "sha512-GhXOCJqKcDeawYoIe4Jly7C5ePR3Uh3jaswb1U+Ruh1x7EtZqOJMMyxnoVvJUIl5b+v7yoFs4RVny/ZowyXBDw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/meteor": "*"
|
||||
}
|
||||
},
|
||||
"@types/sizzle": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"debug": "meteor --inspect",
|
||||
"bundle-viz": "meteor --extra-packages bundle-visualizer --production",
|
||||
"test": "meteor test --driver-package meteortesting:mocha --port 3001",
|
||||
"test:coverage": "COVERAGE=1 COVERAGE_OUT_LCOVONLY=1 COVERAGE_OUT_REMAP=1 COVERAGE_APP_FOLDER=$PWD/ meteor test --once --driver-package meteortesting:mocha",
|
||||
"test:watch:coverage": "COVERAGE=1 COVERAGE_OUT_LCOVONLY=1 COVERAGE_OUT_REMAP=1 COVERAGE_APP_FOLDER=$PWD/ TEST_WATCH=1 meteor test --driver-package meteortesting:mocha",
|
||||
"build": "meteor build ../build --architecture os.linux.x86_64"
|
||||
},
|
||||
"engines": {
|
||||
@@ -68,6 +70,7 @@
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/simpl-schema": "^1.12.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vue/compiler-dom": "^3.4.19",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"module": "esNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
"target": "es2018",
|
||||
"lib": ["esnext", "dom"],
|
||||
"jsx": "preserve",
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"baseUrl": ".",
|
||||
@@ -22,15 +24,19 @@
|
||||
"meteor/aldeed:collection2": [
|
||||
"packages/collection2/collection2.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node", "mocha"],
|
||||
"esModuleInterop": true,
|
||||
},
|
||||
"vueCompilerOptions": {
|
||||
"target": 2 // For Vue version <= 2.6.14
|
||||
"target": 2
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*",
|
||||
".meteor"
|
||||
".meteor",
|
||||
"./packages/**"
|
||||
],
|
||||
"typeAcquisition": {
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user