Initiales Projekt-Skelett appRobotFileservice
Ausgelagertes Programm-/File-Handling (vormals GCode.receiveFC im appRobotDriver, ToDo_4 / ToDo_6b). Express-Service mit .gcode + .json-Storage, aktivem Programm + Cursor, Teaching (FPoint) und Playback. Speicherung in Grad, driver-nativ (Radian) zum Driver. Konzept/API unter doc/draft_filehandeling*.md. Tests: jest (13 gruen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
80
test/activeState.test.js
Normal file
80
test/activeState.test.js
Normal file
@@ -0,0 +1,80 @@
|
||||
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 = 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(a.next().cursor).toBe(1);
|
||||
expect(() => a.next()).toThrow(); // über das Ende → CURSOR_OUT_OF_RANGE
|
||||
});
|
||||
|
||||
test('Cursor wird beim Speichern als !-Kommentar abgelegt (genau eine Zeile)', 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');
|
||||
a.next(); // cursor → 1 (kein Persist)
|
||||
await a.appendLine('G4 P0.1'); // persistiert, cursor → 2
|
||||
|
||||
const prog = await store.read('cur_1');
|
||||
const marked = prog.lines.filter(units.hasCursorMarker);
|
||||
expect(marked).toHaveLength(1);
|
||||
expect(units.splitComment(marked[0]).code).toBe('G4 P0.1');
|
||||
});
|
||||
|
||||
test('Aktion ohne aktives Programm → NO_ACTIVE_PROGRAM', async () => {
|
||||
const a = new ActiveState();
|
||||
expect(() => a.next()).toThrow(); // NO_ACTIVE_PROGRAM
|
||||
await expect(a.appendPoint({ x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, e: 0 })).rejects.toMatchObject({
|
||||
code: 'NO_ACTIVE_PROGRAM',
|
||||
});
|
||||
});
|
||||
50
test/fileStore.test.js
Normal file
50
test/fileStore.test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fsp = require('fs/promises');
|
||||
const cfg = require('../src/config');
|
||||
const store = require('../src/store/fileStore');
|
||||
|
||||
let tmp;
|
||||
beforeAll(async () => {
|
||||
tmp = await fsp.mkdtemp(path.join(os.tmpdir(), 'fsvc-store-'));
|
||||
cfg.storageDir = tmp; // Storage-Verzeichnis für den Test umbiegen
|
||||
});
|
||||
afterAll(async () => {
|
||||
await fsp.rm(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('write + read + list + remove', async () => {
|
||||
await store.write('demo_a', {
|
||||
name: 'Demo A',
|
||||
lines: ['G90 G1 x0 y0 z0 a0 b0 c0 e0 f1000 ;1'],
|
||||
});
|
||||
expect(await store.exists('demo_a')).toBe(true);
|
||||
|
||||
const prog = await store.read('demo_a');
|
||||
expect(prog.name).toBe('Demo A');
|
||||
expect(prog.lines).toHaveLength(1);
|
||||
expect(prog.meta.angleUnit).toBe('deg');
|
||||
expect(prog.meta.createdAt).toBeTruthy();
|
||||
|
||||
const ls = await store.list();
|
||||
expect(ls.find((p) => p.id === 'demo_a')).toBeTruthy();
|
||||
|
||||
await store.remove('demo_a');
|
||||
expect(await store.exists('demo_a')).toBe(false);
|
||||
});
|
||||
|
||||
test('read von unbekanntem Programm → PROGRAM_NOT_FOUND', async () => {
|
||||
await expect(store.read('gibtsnicht')).rejects.toMatchObject({ code: 'PROGRAM_NOT_FOUND' });
|
||||
});
|
||||
|
||||
test('slugify entfernt Pfade & Sonderzeichen', () => {
|
||||
expect(store.slugify('../etc/passwd')).toBe('etc_passwd');
|
||||
expect(store.slugify('Demo C!')).toBe('demo_c');
|
||||
expect(store.slugify(' Mehr Worte ')).toBe('mehr_worte');
|
||||
});
|
||||
|
||||
test('assertValidId lehnt Pfad-Trenner ab', () => {
|
||||
expect(() => store.assertValidId('a/b')).toThrow();
|
||||
expect(() => store.assertValidId('..')).toThrow();
|
||||
expect(store.assertValidId('ok_123')).toBe('ok_123');
|
||||
});
|
||||
51
test/units.test.js
Normal file
51
test/units.test.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const units = require('../src/gcode/units');
|
||||
|
||||
test('degToRad / radToDeg', () => {
|
||||
expect(units.degToRad(180)).toBeCloseTo(Math.PI, 10);
|
||||
expect(units.radToDeg(Math.PI)).toBeCloseTo(180, 10);
|
||||
});
|
||||
|
||||
test('toExecutable: a/b/c/e Grad→Radian, Kommentar entfernt, mm/Feedrate unverändert', () => {
|
||||
const stored = 'G90 G1 x10 y20 z0 a90.00 b-90.00 c0.00 e0.00 f1000 ;1759566014';
|
||||
const exec = units.toExecutable(stored);
|
||||
|
||||
expect(exec).not.toMatch(/;/); // kein Kommentar mehr
|
||||
expect(exec).toContain('x10'); // mm unverändert
|
||||
expect(exec).toContain('f1000'); // Feedrate unverändert
|
||||
expect(exec.startsWith('G90 G1')).toBe(true);
|
||||
|
||||
const val = (axis) => Number(exec.split(/\s+/).find((t) => t.startsWith(axis)).slice(1));
|
||||
expect(val('a')).toBeCloseTo(Math.PI / 2, 4);
|
||||
expect(val('b')).toBeCloseTo(-Math.PI / 2, 4);
|
||||
expect(val('c')).toBeCloseTo(0, 6);
|
||||
});
|
||||
|
||||
test('formatPointLine: Grad-Zeile mit Zeitstempel-Kommentar', () => {
|
||||
const pose = { x: 0, y: 300, z: 0, a: Math.PI / 2, b: -Math.PI / 2, c: 0, e: 0 };
|
||||
const line = units.formatPointLine(pose, 1000, 1759566014000);
|
||||
expect(line.startsWith('G90 G1 ')).toBe(true);
|
||||
expect(line).toContain('a90.00');
|
||||
expect(line).toContain('b-90.00');
|
||||
expect(line).toContain('f1000');
|
||||
expect(line).toContain(';1759566014000');
|
||||
});
|
||||
|
||||
test('Round-trip Pose → gespeichert (Grad) → ausführbar (Radian)', () => {
|
||||
const pose = { x: 5, y: 100, z: -3, a: 1, b: -0.5, c: 0.25, e: 0.1 };
|
||||
const exec = units.toExecutable(units.formatPointLine(pose, 800));
|
||||
const val = (axis) => Number(exec.split(/\s+/).find((t) => t.startsWith(axis)).slice(1));
|
||||
expect(val('a')).toBeCloseTo(1, 3);
|
||||
expect(val('b')).toBeCloseTo(-0.5, 3);
|
||||
expect(val('e')).toBeCloseTo(0.1, 3);
|
||||
});
|
||||
|
||||
test('Cursor-Marker hinzufügen/entfernen/erkennen', () => {
|
||||
const base = 'G90 G1 x0 y0 z0 a0 b0 c0 e0 f1000 ;123';
|
||||
const withCursor = units.addCursorMarker(base);
|
||||
expect(withCursor.endsWith('!')).toBe(true);
|
||||
expect(units.hasCursorMarker(withCursor)).toBe(true);
|
||||
|
||||
const without = units.removeCursorMarker(withCursor);
|
||||
expect(units.hasCursorMarker(without)).toBe(false);
|
||||
expect(without).toBe(base);
|
||||
});
|
||||
Reference in New Issue
Block a user