const os = require('os'); const path = require('path'); const fsp = require('fs/promises'); const cfg = require('../src/config'); const store = require('../src/store/fileStore'); const units = require('../src/gcode/units'); const { ActiveState } = require('../src/active/activeState'); let tmp; beforeEach(async () => { tmp = await fsp.mkdtemp(path.join(os.tmpdir(), 'fsvc-act-')); cfg.storageDir = tmp; }); afterEach(async () => { await fsp.rm(tmp, { recursive: true, force: true }); }); test('Teaching: load(leer) → appendPoint → reload findet Zeile + Cursor', async () => { const a = new ActiveState(); await a.load('teach_1'); // existiert nicht → leer angelegt expect(a.getState().lineCount).toBe(0); const pose = { x: 0, y: 300, z: 0, a: Math.PI / 2, b: -Math.PI / 2, c: 0, e: 0 }; const r = await a.appendPoint(pose, 1000); expect(r.index).toBe(0); expect(r.line).toContain('a90.00'); // in Grad gespeichert const b = new ActiveState(); await b.load('teach_1'); expect(b.getState().lineCount).toBe(1); expect(b.getState().cursor).toBe(0); }); test('Playback: stepping liefert driver-native (Radian) Zeilen, Grenzen werfen', async () => { await store.write('play_1', { name: 'Play 1', lines: [ 'G90 G1 x0 y300 z0 a90.00 b-90.00 c0.00 e0.00 f1000 ;1', 'G90 G1 x10 y300 z0 a0.00 b-90.00 c0.00 e0.00 f1000 ;2', ], }); const a = new ActiveState(); await a.load('play_1'); const first = await a.first(); expect(first.cursor).toBe(0); expect(first.line).not.toMatch(/;/); const aVal = Number(first.line.split(/\s+/).find((t) => t.startsWith('a')).slice(1)); expect(aVal).toBeCloseTo(Math.PI / 2, 4); expect((await a.next()).cursor).toBe(1); await expect(a.next()).rejects.toThrow(); // über das Ende → CURSOR_OUT_OF_RANGE }); test('Cursor liegt im .json-Sidecar, .gcode bleibt sauber (kein !-Marker)', async () => { await store.write('cur_1', { name: 'Cur', lines: [ 'G90 G1 x0 y0 z0 a0 b0 c0 e0 f1000 ;1', 'G90 G1 x1 y0 z0 a0 b0 c0 e0 f1000 ;2', ], }); const a = new ActiveState(); await a.load('cur_1'); await a.next(); // cursor → 1 (kein Persist) await a.appendLine('G4 P0.1'); // persistiert, cursor → 2 const prog = await store.read('cur_1'); // .gcode-Zeilen sind sauber — kein '!'-Marker const marked = prog.lines.filter(units.hasCursorMarker); expect(marked).toHaveLength(0); // Cursor steht im .json-Sidecar expect(prog.cursor).toBe(2); // Korrekte Zeile am Ende expect(units.splitComment(prog.lines[2]).code).toBe('G4 P0.1'); }); test('Stepping ohne aktives Programm → auto-lädt Default (leer → EMPTY_PROGRAM)', async () => { const a = new ActiveState(); // next() lädt automatisch das Default-Programm; da es (im tmp) leer ist → EMPTY_PROGRAM await expect(a.next()).rejects.toMatchObject({ code: 'EMPTY_PROGRAM' }); expect(a.programId).toBe(cfg.defaultProgramId); // Default wurde geladen }); test('FShow ohne FLoad → auto-lädt Default und liefert volle Zeilenliste', async () => { await store.write(cfg.defaultProgramId, { name: 'log', lines: [ 'G90 G1 x0 y300 z0 a90.00 b-90.00 c0.00 e0.00 f1000 ;1', 'G90 G1 x10 y300 z0 a0.00 b-90.00 c0.00 e0.00 f1000 ;2', ], }); const a = new ActiveState(); const state = await a.show(); // frisch, ohne vorheriges FLoad/FPoint expect(state.programId).toBe(cfg.defaultProgramId); expect(state.lineCount).toBe(2); expect(state.lines).toHaveLength(2); // volle Liste für die Anzeige expect(state.lines[0]).toContain('a90.00'); // in Grad, wie gespeichert }); test('Stepping nach Neustart liest Default-Programm von Disk (FFirst ohne FLoad)', async () => { // Simuliert: log.gcode liegt auf Disk, frischer ActiveState (wie nach Container-Neustart) await store.write(cfg.defaultProgramId, { name: 'log', lines: [ 'G90 G1 x0 y300 z0 a90.00 b-90.00 c0.00 e0.00 f1000 ;1', 'G90 G1 x10 y300 z0 a0.00 b-90.00 c0.00 e0.00 f1000 ;2', ], cursor: 1, }); const a = new ActiveState(); const r = await a.first(); // ohne vorheriges FLoad → Default wird geladen expect(a.programId).toBe(cfg.defaultProgramId); expect(r.cursor).toBe(0); expect(a.getState().lineCount).toBe(2); }); test('FPoint ohne aktives Programm → auto-lädt Default-Programm (log)', async () => { const a = new ActiveState(); // appendPoint lädt automatisch das Default-Programm und legt es leer an const r = await a.appendPoint({ x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, e: 0 }); expect(r.index).toBe(0); expect(a.programId).toBeTruthy(); // Default-Programm wurde geladen });