From a29002c6c03caaa53b24668ff588738e044e6ea9 Mon Sep 17 00:00:00 2001 From: Ruslan Gilmullin Date: Thu, 21 May 2026 00:42:22 +0300 Subject: [PATCH] test(coverage): cover remaining reachable v7 branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three more tests for the v7 lines #171 didn't reach: - `State.toGraph` unbound-Reference catch in WRAPPER context (State.ts:463-464) — the existing test covered the non-wrapper branch; this one wraps the unbound-Ref state via `withOverriddenHaltState(...)` to hit the other try/catch - `parseWriteSymbolLabel` fallback return (graph.ts:209) — defensive return-as-is for unrecognized labels - `parsePatternString` blank-marker `?? cell` fallback (graph.ts:164) — defensive return when `alphabets[tapeIx]` is missing Plus the parser-fallback test in `parsePatternString` describe block (graph.ts:173) was already added in #171's first round; this PR finishes the symmetric `parseWriteSymbolLabel` describe block. Coverage: statements 98.72 → 99.14 (+0.42), branches 96.13 → 96.71 (+0.58), lines 98.32 → 99.21 (+0.89), State.ts 99.11 → 100% statements. Should clear Coveralls' remaining -0.5% drop on PR #167 by pulling v7 total above master. Remaining uncovered (not worth synthetic tests): - graphFormats.ts:344 — defensive throw inside `stripBrackets` closure, unreachable via fromMermaid's slice logic - introspection.ts:121 — `if (node)` guard in cycle detection --- packages/machine/src/classes/State.spec.ts | 26 +++++++++++++++++- packages/machine/src/utilities/graph.spec.ts | 29 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/machine/src/classes/State.spec.ts b/packages/machine/src/classes/State.spec.ts index f32939c..3c16b0a 100644 --- a/packages/machine/src/classes/State.spec.ts +++ b/packages/machine/src/classes/State.spec.ts @@ -208,7 +208,7 @@ describe('State.withOverriddenHaltState', () => { }); describe('State.toGraph — unbound Reference', () => { - test('skips a transition whose nextState is an unbound Reference', () => { + test('skips a transition whose nextState is an unbound Reference (non-wrapper context)', () => { // An unbound Reference throws when its `.ref` getter is read. State.toGraph // catches that and skips the transition rather than failing the whole walk. const unboundRef = new Reference(); @@ -222,6 +222,30 @@ describe('State.toGraph — unbound Reference', () => { // Only the haltState-bound transition survives; the unbound one is dropped. expect(graph.nodes[state.id].transitions).toHaveLength(1); }); + + test('skips a transition whose nextState is an unbound Reference (wrapper context)', () => { + // toGraph has a separate try/catch in its wrapper-context branch — when the + // bare being walked is inside a `withOverriddenHaltState` wrapper. Same + // skip-and-continue semantic. + const unboundRef = new Reference(); + const bare = new State({ + [symbol(['0'])]: {nextState: unboundRef}, + [symbol(['1'])]: {nextState: haltState}, + }, 'bare'); + const override = new State({ + [symbol(['0'])]: {nextState: haltState}, + [symbol(['1'])]: {nextState: haltState}, + }, 'override'); + const wrapped = bare.withOverriddenHaltState(override); + + const graph = State.toGraph(wrapped, tapeBlock); + + // The collapsed wrapper node retains only the haltState-bound transition; + // the unbound-Ref one is dropped. + const collapsedNode = graph.nodes[wrapped.id]; + + expect(collapsedNode.transitions).toHaveLength(1); + }); }); describe('State.fromGraph — cyclic override-halt chain', () => { diff --git a/packages/machine/src/utilities/graph.spec.ts b/packages/machine/src/utilities/graph.spec.ts index e8592fd..f2efd9c 100644 --- a/packages/machine/src/utilities/graph.spec.ts +++ b/packages/machine/src/utilities/graph.spec.ts @@ -4,6 +4,7 @@ import { decodeWriteSymbol, parseMovementLabel, parsePatternString, + parseWriteSymbolLabel, splitUnescaped, } from './graph'; import {fromMermaid, toMermaid} from './graphFormats'; @@ -214,6 +215,34 @@ describe('parsePatternString', () => { test('per-cell `B` becomes the tape blank symbol', () => { expect(parsePatternString("B,'a'", [[' ', '0'], [' ', 'a']])).toEqual([[' ', 'a']]); }); + + test('fallback: cell that is not marker/blank/quoted is returned as-is', () => { + // Defensive — the parser doesn't throw on unexpected cells; it returns + // them as-is, so consumer code can decide whether to reject. + expect(parsePatternString('Q', [[' ', '0']])).toEqual([['Q']]); + }); + + test('blank-marker fallback when alphabet for the tape is missing', () => { + // Defensive: if alphabets[tapeIx] is undefined, returns the marker + // string itself rather than throwing. + expect(parsePatternString('B', [])).toEqual([['B']]); + }); +}); + +describe('parseWriteSymbolLabel', () => { + test('maps K/E to upstream symbolCommands', () => { + expect(parseWriteSymbolLabel('K')).toBe(symbolCommands.keep); + expect(parseWriteSymbolLabel('E')).toBe(symbolCommands.erase); + }); + + test('strips single quotes from a literal alphabet symbol', () => { + expect(parseWriteSymbolLabel("'X'")).toBe('X'); + }); + + test('fallback: label that is not K/E/quoted is returned as-is', () => { + // Defensive — same shape as parsePatternString's fallback. + expect(parseWriteSymbolLabel('Z')).toBe('Z'); + }); }); describe('parseMovementLabel', () => {