fix(01-02): TS-strict narrowing for EvalResult discriminated union
Rule 1 deviation: TS strict mode (noImplicitAny + strict) couldn't narrow
the EvalResult union via 'r.ok' direct access because the {unknown:true,raw}
variant has no 'ok' property. Added explicit type-guard helpers (isUnknown,
isOk, isFail) for both production and spec narrowing. Runtime behavior
unchanged; tsc --noEmit now exits clean for the leveling lib.
Files:
- prereq-evaluator.ts: 3 type-guard functions, used in OR/AND walkers
- prereq-evaluator.spec.ts: isFail() in lieu of result.ok=== checks
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { evaluatePrereq } from './prereq-evaluator';
|
||||
import type { CharacterContext } from './types';
|
||||
import type { CharacterContext, EvalResult } from './types';
|
||||
|
||||
function isFail(r: EvalResult): r is { ok: false; reason: string } {
|
||||
return 'ok' in r && r.ok === false;
|
||||
}
|
||||
|
||||
function makeCtx(overrides: Partial<CharacterContext> = {}): CharacterContext {
|
||||
return {
|
||||
@@ -38,8 +42,8 @@ describe('evaluatePrereq — skill rank', () => {
|
||||
it('returns ok:false with German reason when skill rank below requirement', () => {
|
||||
const ctx = makeCtx({ skills: { Athletics: 'UNTRAINED' } });
|
||||
const result = evaluatePrereq('Trained in Athletics', ctx);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok === false) {
|
||||
expect(isFail(result)).toBe(true);
|
||||
if (isFail(result)) {
|
||||
expect(result.reason).toMatch(/Athletics/i);
|
||||
// German wording check — must contain a German keyword
|
||||
expect(result.reason).toMatch(/(benötig|fehlt|Voraussetzung|mindestens)/);
|
||||
@@ -49,7 +53,7 @@ describe('evaluatePrereq — skill rank', () => {
|
||||
it('returns ok:false when skill is missing entirely', () => {
|
||||
const ctx = makeCtx({ skills: {} });
|
||||
const result = evaluatePrereq('Trained in Athletics', ctx);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(isFail(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +71,7 @@ describe('evaluatePrereq — disjunctive (OR-list)', () => {
|
||||
'Trained in Arcana, Trained in Nature, or Trained in Religion',
|
||||
ctx,
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(isFail(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,7 +86,7 @@ describe('evaluatePrereq — conjunctive (semicolon AND)', () => {
|
||||
it('returns ok:false when one clause is missing', () => {
|
||||
const ctx = makeCtx({ skills: { Deception: 'TRAINED' } });
|
||||
const result = evaluatePrereq('Trained in Deception; Trained in Stealth', ctx);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(isFail(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,7 +99,7 @@ describe('evaluatePrereq — bare feat name', () => {
|
||||
it('returns ok:false when the feat is missing', () => {
|
||||
const ctx = makeCtx({ feats: new Set() });
|
||||
const result = evaluatePrereq('Power Attack', ctx);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(isFail(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user