More testing of action system, now with test coverage

This commit is contained in:
Thaum Rystra
2024-02-27 07:30:40 +02:00
parent 5141704e23
commit b13ca8c64b
20 changed files with 236 additions and 249 deletions

View File

@@ -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
View File

@@ -3,6 +3,9 @@
.demeteorized
.cache
.vscode
.coverage
.nyc_output
.DS_Store
fileStorage
settings.json
public/components

View File

@@ -51,3 +51,5 @@ zodern:types
zodern:fix-async-stubs
typescript
ecmascript
lmieulet:meteor-legacy-coverage
lmieulet:meteor-coverage

View File

@@ -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

View File

@@ -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);
});
});

View File

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

View File

@@ -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 },
}]
});
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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": [