respira/src/formats/pen/encoder.test.ts
Jan-Henrik Bruhn a253901fb4
Some checks are pending
Build, Test, and Lint / Build, Test, and Lint (push) Waiting to run
Draft Release / Draft Release (push) Waiting to run
Draft Release / Build Web App (push) Blocked by required conditions
Draft Release / Build Release - macos-latest (push) Blocked by required conditions
Draft Release / Build Release - ubuntu-latest (push) Blocked by required conditions
Draft Release / Build Release - windows-latest (push) Blocked by required conditions
Draft Release / Upload to GitHub Release (push) Blocked by required conditions
fix: run linter
2025-12-18 11:39:22 +01:00

676 lines
22 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
encodeStitchPosition,
calculateLockDirection,
generateLockStitches,
encodeStitchesToPen,
LOCK_STITCH_JUMP_SIZE,
} from "./encoder";
import { decodeAllPenStitches } from "./decoder";
import { STITCH, MOVE, TRIM, END } from "../import/constants";
// PEN format flag constants for testing
const PEN_FEED_DATA = 0x01;
const PEN_CUT_DATA = 0x02;
describe("encodeStitchPosition", () => {
it("should encode position (0, 0) correctly", () => {
const result = encodeStitchPosition(0, 0);
expect(result).toEqual([0x00, 0x00, 0x00, 0x00]);
});
it("should shift coordinates left by 3 bits", () => {
// Position (1, 1) should become (8, 8) after shifting
const result = encodeStitchPosition(1, 1);
expect(result).toEqual([0x08, 0x00, 0x08, 0x00]);
});
it("should handle negative coordinates", () => {
// -1 in 16-bit signed = 0xFFFF, shifted left 3 = 0xFFF8
const result = encodeStitchPosition(-1, -1);
expect(result).toEqual([0xf8, 0xff, 0xf8, 0xff]);
});
it("should encode multi-byte coordinates correctly", () => {
// Position (128, 0) -> shifted = 1024 = 0x0400
const result = encodeStitchPosition(128, 0);
expect(result).toEqual([0x00, 0x04, 0x00, 0x00]);
});
it("should round fractional coordinates", () => {
const result = encodeStitchPosition(1.5, 2.4);
// 2 << 3 = 16, 2 << 3 = 16
expect(result).toEqual([0x10, 0x00, 0x10, 0x00]);
});
});
describe("calculateLockDirection", () => {
it("should look ahead for forward direction", () => {
const stitches = [
[0, 0, STITCH, 0],
[10, 0, STITCH, 0],
[20, 0, STITCH, 0],
];
const result = calculateLockDirection(stitches, 0, true);
// Should accumulate forward stitches
expect(result.dirX).toBeGreaterThan(0);
expect(result.dirY).toBe(0);
// Result should have magnitude ~8.0
const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
expect(magnitude).toBeCloseTo(8.0, 1);
});
it("should look backward for backward direction", () => {
const stitches = [
[0, 0, STITCH, 0],
[10, 0, STITCH, 0],
[20, 0, STITCH, 0],
];
const result = calculateLockDirection(stitches, 2, false);
// Should accumulate backward stitches
expect(result.dirX).toBeLessThan(0);
expect(result.dirY).toBe(0);
// Result should have magnitude ~8.0
const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
expect(magnitude).toBeCloseTo(8.0, 1);
});
it("should skip MOVE stitches when accumulating", () => {
const stitches = [
[0, 0, STITCH, 0],
[5, 0, MOVE, 0], // Should be skipped
[10, 0, STITCH, 0],
[15, 0, STITCH, 0],
];
const result = calculateLockDirection(stitches, 0, true);
// Should skip the MOVE stitch and only count actual stitches
expect(result.dirX).toBeGreaterThan(0);
});
it("should return fallback diagonal for empty or short stitch sequences", () => {
const stitches = [[0, 0, STITCH, 0]];
const result = calculateLockDirection(stitches, 0, true);
// Should return diagonal fallback
const expectedMag = 8.0 / Math.sqrt(2);
expect(result.dirX).toBeCloseTo(expectedMag, 1);
expect(result.dirY).toBeCloseTo(expectedMag, 1);
});
it("should normalize accumulated vector to magnitude 8.0", () => {
const stitches = [
[0, 0, STITCH, 0],
[3, 4, STITCH, 0], // Distance = 5
[6, 8, STITCH, 0], // Accumulated: (6, 8), length = 10
];
const result = calculateLockDirection(stitches, 0, true);
// Should normalize (6, 8) to magnitude 8.0
// Expected: (6 * 8 / 10, 8 * 8 / 10) = (4.8, 6.4)
expect(result.dirX).toBeCloseTo(4.8, 1);
expect(result.dirY).toBeCloseTo(6.4, 1);
const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
expect(magnitude).toBeCloseTo(8.0, 1);
});
it("should stop accumulating after reaching target length", () => {
// Create a long chain of stitches
const stitches = [
[0, 0, STITCH, 0],
[2, 0, STITCH, 0],
[4, 0, STITCH, 0],
[6, 0, STITCH, 0],
[8, 0, STITCH, 0],
[10, 0, STITCH, 0],
[100, 0, STITCH, 0], // This should not be reached
];
const result = calculateLockDirection(stitches, 0, true);
// Should stop once accumulated length >= 8.0
const magnitude = Math.sqrt(result.dirX ** 2 + result.dirY ** 2);
expect(magnitude).toBeCloseTo(8.0, 1);
});
});
describe("generateLockStitches", () => {
it("should generate 8 lock stitches (32 bytes)", () => {
const result = generateLockStitches(0, 0, 8.0, 0);
expect(result.length).toBe(32); // 8 stitches * 4 bytes each
});
it("should alternate between +dir and -dir", () => {
const result = generateLockStitches(0, 0, 8.0, 0);
expect(result.length).toBe(32); // 8 stitches * 4 bytes
// With a larger base position, verify the pattern still generates correctly
const result2 = generateLockStitches(100, 100, 8.0, 0);
expect(result2.length).toBe(32);
});
it("should rotate stitches in the given direction", () => {
// Direction pointing right (8, 0)
const result = generateLockStitches(0, 0, 8.0, 0);
// Scale: 0.4 / 8.0 = 0.05
// Scaled direction: (0.4, 0)
// Positions should alternate between (+0.4, 0) and (-0.4, 0)
expect(result.length).toBe(32);
// With diagonal direction (8/√2, 8/√2)
const diag = 8.0 / Math.sqrt(2);
const result2 = generateLockStitches(0, 0, diag, diag);
expect(result2.length).toBe(32);
});
});
describe("encodeStitchesToPen", () => {
it("should encode a simple stitch sequence", () => {
const stitches = [
[0, 0, STITCH, 0],
[10, 0, STITCH, 0],
[20, 0, STITCH | END, 0], // Last stitch with both STITCH and END flags
];
const result = encodeStitchesToPen(stitches);
expect(result.penBytes.length).toBeGreaterThan(0);
expect(result.penBytes.length % 4).toBe(0); // Should be multiple of 4 (4 bytes per stitch)
expect(result.bounds.minX).toBe(0);
expect(result.bounds.maxX).toBe(20);
});
it("should track bounds correctly", () => {
const stitches = [
[10, 20, STITCH, 0],
[-5, 30, STITCH, 0],
[15, -10, STITCH, 0],
[0, 0, END, 0],
];
const result = encodeStitchesToPen(stitches);
expect(result.bounds.minX).toBe(-5);
expect(result.bounds.maxX).toBe(15);
expect(result.bounds.minY).toBe(-10);
expect(result.bounds.maxY).toBe(30);
});
it("should mark the last stitch with DATA_END flag", () => {
const stitches = [
[0, 0, STITCH, 0],
[10, 0, END, 0],
];
const result = encodeStitchesToPen(stitches);
// Last stitch should have DATA_END (0x05) in low 3 bits of X coordinate
const lastStitchStart = result.penBytes.length - 4;
const xLow = result.penBytes[lastStitchStart];
expect(xLow & 0x07).toBe(0x05); // DATA_END flag
});
it("should handle color changes with lock stitches", () => {
const stitches = [
[0, 0, STITCH, 0], // Color 0
[10, 0, STITCH, 0], // Color 0
[20, 0, STITCH, 0], // Color 0 - last stitch before color change
[20, 0, STITCH, 1], // Color 1 - first stitch of new color
[30, 0, STITCH, 1], // Color 1
[40, 0, END, 1], // Color 1 - last stitch
];
const result = encodeStitchesToPen(stitches);
// Should include:
// - Regular stitches for color 0 (3 stitches = 12 bytes)
// - Finishing lock stitches (32 bytes)
// - Cut command (4 bytes)
// - COLOR_END marker (4 bytes)
// - Starting lock stitches (32 bytes)
// - Regular stitches for color 1 (3 stitches = 12 bytes)
// Total: 96+ bytes
expect(result.penBytes.length).toBeGreaterThan(90); // Should have many bytes from lock stitches
});
it("should encode color change sequence in correct order", () => {
// Test the exact sequence of operations for a color change
const stitches = [
[0, 0, STITCH, 0], // Color 0
[10, 0, STITCH, 0], // Color 0 - last stitch before color change
[10, 0, STITCH, 1], // Color 1 - first stitch (same position)
[20, 0, STITCH | END, 1], // Color 1 - last stitch
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
// Expected sequence:
// 0. 8 starting lock stitches at (0, 0)
// 1. Stitch at (0, 0) - color 0
// 2. Stitch at (10, 0) - color 0 (last before change)
// 3. 8 finishing lock stitches around (10, 0)
// 4. Cut command at (10, 0)
// 5. COLOR_END marker at (10, 0) - no jump needed since next color is at same position
// 6. 8 starting lock stitches around (10, 0)
// 7. Stitch at (10, 0) - color 1
// 8. Stitch at (20, 0) - color 1 (last, with END flag)
let idx = 0;
// 1. First stitch (0, 0)
expect(decoded[idx].x).toBe(0);
expect(decoded[idx].y).toBe(0);
expect(decoded[idx].isFeed).toBe(false);
expect(decoded[idx].isCut).toBe(false);
// 0. 8 starting lock stitches at (0, 0)
for (; idx < 9; idx++) {
expect(decoded[idx].x).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
}
// 2. Second stitch (10, 0) - last before color change
expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0);
idx++;
// 3. 8 finishing lock stitches (should be around position 10, 0)
for (let i = 0; i < 8; i++) {
const lockStitch = decoded[idx];
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE); // Allow some deviation due to rotation
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
expect(lockStitch.isFeed).toBe(false);
expect(lockStitch.isCut).toBe(false);
idx++;
}
// 4. COLOR_END + CUT command (combined on same stitch)
const colorEndCutStitch = decoded[idx];
expect(colorEndCutStitch.x).toBe(10);
expect(colorEndCutStitch.y).toBe(0);
expect(colorEndCutStitch.isCut).toBe(true);
expect(colorEndCutStitch.isColorEnd).toBe(true);
idx++;
// 5. 8 starting lock stitches for new color
for (let i = 0; i < 8; i++) {
const lockStitch = decoded[idx];
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
idx++;
}
// 6. First stitch of new color
expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0);
idx++;
// 8. Last stitch with DATA_END flag
expect(decoded[idx].x).toBe(20);
expect(decoded[idx].y).toBe(0);
expect(decoded[idx].isDataEnd).toBe(true);
});
it("should encode color change with jump in correct order", () => {
// Test color change when next color is at a different position
const stitches = [
[0, 0, STITCH, 0], // Color 0
[10, 0, STITCH, 0], // Color 0 - last before change
[30, 10, STITCH, 1], // Color 1 - different position, requires jump
[40, 10, STITCH | END, 1],
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
let idx = 10; // Skip 8 starting locks + first two regular stitches
// After second stitch, should have:
// 1. 8 finishing lock stitches at (10, 0)
for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
idx++;
}
// 2. COLOR_END + CUT command at (10, 0) - combined on same stitch
expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0);
expect(decoded[idx].isCut).toBe(true);
expect(decoded[idx].isColorEnd).toBe(true);
idx++;
// 3. Jump to new position (30, 10)
expect(decoded[idx].x).toBe(30);
expect(decoded[idx].y).toBe(10);
expect(decoded[idx].isFeed).toBe(true);
idx++;
// 4. 8 starting lock stitches at (30, 10)
for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(30, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
idx++;
}
// 5. Continue with new color stitches
expect(decoded[idx].x).toBe(30);
expect(decoded[idx].y).toBe(10);
});
it("should encode color change followed by explicit JUMP in correct order", () => {
// Test when PES data has a JUMP stitch immediately after color change
// This is a common pattern: color change, then jump to new location
const stitches = [
[0, 0, STITCH, 0], // Color 0
[10, 0, STITCH, 0], // Color 0 - last before change
[50, 20, MOVE, 1], // Color 1 - JUMP to new location (50, 20)
[50, 20, STITCH, 1], // Color 1 - first actual stitch at new location
[60, 20, STITCH | END, 1],
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
// Expected sequence:
// 1. Stitch at (0, 0) - color 0
// 2. Stitch at (10, 0) - color 0 (last before change)
// 3. 8 finishing lock stitches at (10, 0)
// 4. Cut command at (10, 0)
// 5. Jump to (50, 20) - from MOVE stitch (the encoder skips this MOVE in the loop with i++)
// 6. COLOR_END marker at (50, 20)
// 7. 8 starting lock stitches at (50, 20)
// 8. First stitch of new color at (50, 20)
// 9. Last stitch at (60, 20) with END flag
let idx = 0;
// 1-2. First two stitches
expect(decoded[idx++].x).toBe(0);
idx = 9; // skip startingLock
expect(decoded[idx++].x).toBe(10);
// 3. 8 finishing lock stitches at (10, 0)
for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
idx++;
}
// 4. COLOR_END + CUT command at (10, 0) - combined on same stitch
expect(decoded[idx].x).toBe(10);
expect(decoded[idx].y).toBe(0);
expect(decoded[idx].isCut).toBe(true);
expect(decoded[idx].isColorEnd).toBe(true);
idx++;
// 5. Jump to new location (50, 20) - extracted from the MOVE stitch
expect(decoded[idx].x).toBe(50);
expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isFeed).toBe(true);
idx++;
// 6. 8 starting lock stitches at (50, 20)
for (let i = 0; i < 8; i++) {
expect(decoded[idx].x).to.be.closeTo(50, LOCK_STITCH_JUMP_SIZE);
expect(decoded[idx].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
idx++;
}
// 7. First actual stitch of new color at (50, 20)
expect(decoded[idx].x).toBe(50);
expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isFeed).toBe(false);
idx++;
// 8. Last stitch with DATA_END
expect(decoded[idx].x).toBe(60);
expect(decoded[idx].y).toBe(20);
expect(decoded[idx].isDataEnd).toBe(true);
});
it("should handle long jumps with lock stitches and cut in correct order", () => {
// Test the exact sequence for a long jump (distance > 50)
const stitches = [
[0, 0, STITCH, 0],
[10, 0, STITCH, 0],
[100, 0, MOVE, 0], // Long jump (distance = 90 > 50)
[110, 0, STITCH, 0],
[120, 0, STITCH | END, 0],
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
// Expected sequence:
// 1. Stitch at (0, 0)
// 2. Stitch at (10, 0)
// 3. 8 finishing lock stitches at (10, 0)
// 4. Jump to (100, 0) with FEED and CUT flags
// 5. 8 starting lock stitches at (100, 0)
// 6. Stitch at (110, 0)
// 7. Stitch at (120, 0) with END flag
let idx = 0;
// 1-2. First two stitches
expect(decoded[idx++].x).toBe(0);
idx = 9; // Skip 8 starting lock stitches
expect(decoded[idx++].x).toBe(10);
// 3. 8 finishing lock stitches at (10, 0)
for (let i = 0; i < 8; i++) {
const lockStitch = decoded[idx];
expect(lockStitch.x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
expect(lockStitch.isFeed).toBe(false);
expect(lockStitch.isCut).toBe(false);
idx++;
}
// 4. Jump to (100, 0) with BOTH FEED and CUT flags
const jumpStitch = decoded[idx];
expect(jumpStitch.x).toBe(100);
expect(jumpStitch.y).toBe(0);
expect(jumpStitch.isFeed).toBe(true);
expect(jumpStitch.isCut).toBe(true);
expect(jumpStitch.yFlags).toBe(PEN_FEED_DATA | PEN_CUT_DATA); // 0x03
idx++;
// 5. 8 starting lock stitches at (100, 0)
for (let i = 0; i < 8; i++) {
const lockStitch = decoded[idx];
expect(lockStitch.x).to.be.closeTo(100, LOCK_STITCH_JUMP_SIZE);
expect(lockStitch.y).to.be.closeTo(0, LOCK_STITCH_JUMP_SIZE);
idx++;
}
// 6-7. Final two stitches
expect(decoded[idx].x).toBe(110);
expect(decoded[idx].y).toBe(0);
idx++;
expect(decoded[idx].x).toBe(120);
expect(decoded[idx].isDataEnd).toBe(true);
});
it("should encode MOVE flag for jump stitches", () => {
const stitches = [
[0, 0, STITCH, 0],
[10, 0, MOVE, 0], // Short jump (no lock stitches)
[20, 0, END, 0],
];
const result = encodeStitchesToPen(stitches);
// Second stitch (jump) should have FEED_DATA flag (0x01) in Y low byte
// Stitch format: [xLow, xHigh, yLow, yHigh]
// We need to find the jump stitch - it's the second one encoded
const jumpStitchStart = 36; // Skip 8 starting locks (32 bytes) + first stitch (4 bytes)
const yLow = result.penBytes[jumpStitchStart + 2];
expect(yLow & 0x01).toBe(0x01); // FEED_DATA flag
});
it("should not include MOVE stitches in bounds calculation", () => {
const stitches = [
[0, 0, STITCH, 0],
[100, 100, MOVE, 0], // Jump - should not affect bounds
[10, 10, STITCH, 0],
[20, 20, STITCH | END, 0], // Last stitch with both STITCH and END flags
];
const result = encodeStitchesToPen(stitches);
// Bounds should only include STITCH positions, not MOVE
expect(result.bounds.minX).toBe(0);
expect(result.bounds.maxX).toBe(20);
expect(result.bounds.minY).toBe(0);
expect(result.bounds.maxY).toBe(20);
});
it("should handle TRIM flag correctly", () => {
const stitches = [
[0, 0, STITCH, 0],
[10, 0, TRIM, 0],
[20, 0, STITCH | END, 0],
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
let idx = 0;
// Verify sequence:
// 1. Regular stitch at (0, 0)
const firstIdx = idx;
expect(decoded[idx++].x).toBe(0);
idx = 9; // Skip 8 starting lock stitches
expect(decoded[firstIdx].isCut).toBe(false);
// 2. TRIM command at (10, 0) - should have CUT flag
const trimIdx = idx;
expect(decoded[idx++].x).toBe(10);
expect(decoded[trimIdx].y).toBe(0);
expect(decoded[trimIdx].isCut).toBe(true);
expect(decoded[trimIdx].isFeed).toBe(false); // TRIM doesn't include FEED
expect(decoded[trimIdx].yFlags).toBe(PEN_CUT_DATA); // Only CUT flag
// 3. Final stitch with DATA_END
expect(decoded[idx].x).toBe(20);
expect(decoded[idx].isDataEnd).toBe(true);
});
it("should handle empty stitch array", () => {
const stitches: number[][] = [];
const result = encodeStitchesToPen(stitches);
expect(result.penBytes.length).toBe(0);
expect(result.bounds.minX).toBe(0);
expect(result.bounds.maxX).toBe(0);
expect(result.bounds.minY).toBe(0);
expect(result.bounds.maxY).toBe(0);
});
it("should handle single stitch", () => {
const stitches = [[5, 10, END, 0]];
const result = encodeStitchesToPen(stitches);
expect(result.penBytes.length).toBe(36); // 8 starting locks (32 bytes) + 1 stitch (4 bytes)
expect(result.bounds.minX).toBe(5);
expect(result.bounds.maxX).toBe(5);
expect(result.bounds.minY).toBe(10);
expect(result.bounds.maxY).toBe(10);
// END stitches update bounds (they're not MOVE stitches)
});
it("should add DATA_END flag to last stitch even without END flag in input", () => {
// Test that the encoder automatically marks the last stitch with DATA_END
// even if the input stitches don't have an END flag
const stitches = [
[0, 0, STITCH, 0],
[10, 0, STITCH, 0],
[20, 0, STITCH, 0], // Last stitch - NO END flag
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
// First two stitches (after 8 starting locks) should NOT have DATA_END flag
expect(decoded[8].isDataEnd).toBe(false);
expect(decoded[9].isDataEnd).toBe(false);
// Last stitch SHOULD have DATA_END flag automatically added
expect(decoded[10].isDataEnd).toBe(true);
expect(decoded[10].x).toBe(20);
expect(decoded[10].y).toBe(0);
});
it("should add DATA_END flag when input has explicit END flag", () => {
// Verify that END flag in input also results in DATA_END flag in output
const stitches = [
[0, 0, STITCH, 0],
[10, 0, STITCH, 0],
[20, 0, STITCH | END, 0], // Explicit END flag
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
// Last stitch should have DATA_END flag
expect(decoded[10].isDataEnd).toBe(true);
expect(decoded[10].x).toBe(20);
expect(decoded[10].y).toBe(0);
});
it("should add lock stitches at the very start of the pattern", () => {
// Matching C# behavior: Nuihajime_TomeDataPlus is called when counter <= 2
// This adds starting lock stitches to secure the thread at pattern start
const stitches = [
[10, 20, MOVE, 0],
[20, 20, STITCH, 0],
[30, 20, STITCH | END, 0],
];
const result = encodeStitchesToPen(stitches);
const decoded = decodeAllPenStitches(result.penBytes);
// Expected sequence:
// 0. Feed move to proper location (should always happen here)
// 1. 8 starting lock stitches at (10, 20)
// 2. First actual stitch at (10, 20)
// 3. Second stitch at (20, 20)
// 4. Last stitch at (30, 20)
expect(decoded[0].x).toBe(10);
expect(decoded[0].y).toBe(20);
expect(decoded[0].isFeed).toBe(true);
// First 8 stitches should be lock stitches around the starting position
for (let i = 1; i < 1 + 8; i++) {
expect(decoded[i].x).to.be.closeTo(10, LOCK_STITCH_JUMP_SIZE);
expect(decoded[i].y).to.be.closeTo(20, LOCK_STITCH_JUMP_SIZE);
expect(decoded[i].isFeed).toBe(false);
expect(decoded[i].isCut).toBe(false);
}
// Then the actual stitches
expect(decoded[9].x).toBe(20);
expect(decoded[9].y).toBe(20);
expect(decoded[10].x).toBe(30);
expect(decoded[10].y).toBe(20);
expect(decoded[10].isDataEnd).toBe(true);
});
});