Fix talent outcome validation feedback
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { ConditionalMechanicDraftV1 } from './mechanic-editor';
|
||||
import { missingTalentDescriptionSlots, numberedTalentOutcomes } from './talent-description';
|
||||
|
||||
const draft = (outcomes: ConditionalMechanicDraftV1['clauses'][number]['outcomes']): ConditionalMechanicDraftV1 => ({
|
||||
version: 1,
|
||||
simulationReadiness: 'ready',
|
||||
scenarioInputs: [],
|
||||
pools: [],
|
||||
clauses: [{
|
||||
key: 'primary',
|
||||
label: 'Primary',
|
||||
event: 'enemy_killed',
|
||||
if: { kind: 'true' },
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [],
|
||||
outcomes
|
||||
}]
|
||||
});
|
||||
|
||||
describe('talent description outcomes', () => {
|
||||
it('accepts a numbered restoration outcome as a description value', () => {
|
||||
const outcomes = numberedTalentOutcomes(draft([{
|
||||
key: 'restore-health',
|
||||
label: 'Restore Health',
|
||||
phase: 'after_event',
|
||||
kind: 'restoration',
|
||||
scope: 'self',
|
||||
resource: 'health',
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
basis: 'max_health_percent',
|
||||
descriptionSlot: 1,
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 }
|
||||
}
|
||||
}]));
|
||||
|
||||
expect(outcomes).toHaveLength(1);
|
||||
expect(outcomes[0]).toMatchObject({ kind: 'restoration', descriptionSlot: 1 });
|
||||
expect(missingTalentDescriptionSlots('Restore {1} over time.', outcomes)).toEqual([]);
|
||||
});
|
||||
|
||||
it('reports exact missing slots instead of relying on outcome count', () => {
|
||||
const outcomes = numberedTalentOutcomes(draft([{
|
||||
key: 'damage',
|
||||
label: 'Damage',
|
||||
phase: 'on_event',
|
||||
kind: 'direct_damage',
|
||||
scope: 'target',
|
||||
basis: 'flat',
|
||||
descriptionSlot: 2,
|
||||
variants: {
|
||||
I: { value: 10 },
|
||||
II: { value: 14 }
|
||||
}
|
||||
}]));
|
||||
|
||||
expect(missingTalentDescriptionSlots('{1} then {2} and {3}.', outcomes)).toEqual([1, 3]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import type {
|
||||
ConditionalMechanicDraftV1,
|
||||
ConditionalMechanicOutcomeDraft
|
||||
} from '$lib/calculations/mechanic-editor';
|
||||
|
||||
const VALUE_BEARING_OUTCOME_KINDS = new Set([
|
||||
'attribute_modifier',
|
||||
'direct_damage',
|
||||
'restoration',
|
||||
'resource_cost'
|
||||
]);
|
||||
|
||||
export const numberedTalentOutcomes = (
|
||||
draft: ConditionalMechanicDraftV1
|
||||
): ConditionalMechanicOutcomeDraft[] => {
|
||||
const outcomes = draft.clauses
|
||||
.flatMap((clause) => clause.outcomes)
|
||||
.filter((outcome) =>
|
||||
VALUE_BEARING_OUTCOME_KINDS.has(outcome.kind)
|
||||
&& Number.isInteger(outcome.descriptionSlot)
|
||||
&& Number(outcome.descriptionSlot) > 0
|
||||
)
|
||||
.sort((left, right) => Number(left.descriptionSlot) - Number(right.descriptionSlot));
|
||||
|
||||
return [...new Map(outcomes.map((outcome) => [outcome.descriptionSlot, outcome])).values()];
|
||||
};
|
||||
|
||||
export const missingTalentDescriptionSlots = (
|
||||
template: string,
|
||||
outcomes: ConditionalMechanicOutcomeDraft[]
|
||||
) => {
|
||||
const availableSlots = new Set(outcomes.map((outcome) => Number(outcome.descriptionSlot)));
|
||||
return [...new Set(
|
||||
[...template.matchAll(/\{(\d+)\}/g)]
|
||||
.map((match) => Number(match[1]))
|
||||
.filter((slot) => !availableSlots.has(slot))
|
||||
)];
|
||||
};
|
||||
Reference in New Issue
Block a user