thermark 0.33.0

Local sticker printing for pocket thermal printers over BLE/USB — QR links, inventory tags, badges, photos; no vendor app
Documentation
//! Integration-style tests (no hardware required).

use thermark::errors::PrinterFault;
use thermark::geometry::{LabelMm, PX_PER_MM};
use thermark::label::{
    QrLabelOptions, TextSide, make_qr_label_opts, max_qr_side, render_qr_square,
};
use thermark::packet::Packet;
use thermark::print_task::PrintTask;
use thermark::printer::{Pacing, PrinterClient};
use thermark::protocol::{self, Model};
use thermark::types::Density;

#[test]
fn b1_print_start_roundtrip() {
    let p = PrintTask::B1.print_start(1);
    assert_eq!(p.cmd, 0x01);
    assert_eq!(p.data.len(), 7);
    assert_eq!(&p.data[0..2], &1u16.to_be_bytes());
    let enc = p.encode().unwrap();
    let dec = Packet::decode(&enc).unwrap();
    assert_eq!(dec, p);
}

#[test]
fn b1_page_size_matches_50x30() {
    let profile = thermark::profile_for_model(Model::B1);
    let lp = LabelMm::parse("50x30")
        .unwrap()
        .to_pixels(profile.max_width_px, profile.pixels_per_mm());
    let p = PrintTask::B1.set_page_size(lp.height_px as u16, lp.width_px as u16, 1);
    assert_eq!(p.cmd, 0x13);
    assert_eq!(p.data.len(), 6);
    let rows = u16::from_be_bytes([p.data[0], p.data[1]]);
    let cols = u16::from_be_bytes([p.data[2], p.data[3]]);
    let copies = u16::from_be_bytes([p.data[4], p.data[5]]);
    assert_eq!(rows, 240);
    assert_eq!(cols, 384);
    assert_eq!(copies, 1);
    Packet::decode(&p.encode().unwrap()).unwrap();
}

#[test]
fn density_and_label_type_packets() {
    let d = protocol::set_density(4);
    assert_eq!(d.encode().unwrap()[2], 0x21);
    let t = protocol::set_label_type(1);
    assert_eq!(t.encode().unwrap()[2], 0x23);
}

#[test]
fn error_codes_cover_and_paper() {
    assert_eq!(PrinterFault::from_u8(0x01), PrinterFault::COVER_OPEN);
    assert_eq!(PrinterFault::from_u8(0x02), PrinterFault::NO_PAPER);
    let s = format!("{}", PrinterFault::from_u8(0x01));
    assert!(s.contains("Cover") || s.contains("open") || s.contains("0x01"));
}

#[test]
fn model_max_widths() {
    assert_eq!(thermark::profile_for_model(Model::B1).max_width_px, 384);
    assert_eq!(thermark::profile_for_model(Model::D11).max_width_px, 96);
    assert_eq!(Model::parse("b1"), Some(Model::B1));
    assert_eq!(Model::parse("B21PRO"), Some(Model::B21Pro));
    assert!(Model::parse("nope").is_none());
}

#[test]
fn qr_label_exact_canvas_and_square_qr() {
    let lp = LabelMm::parse("50x30").unwrap().to_pixels(384, 8.0);
    let img = make_qr_label_opts(&QrLabelOptions {
        url: "https://www.youtube.com".into(),
        side_text: "ABC\n123".into(),
        label: lp,
        safe: thermark::geometry::SafeArea::default(),
        text_side: TextSide::Right,
        border: false,
        font_path: Some(
            std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fonts/DejaVuSans.ttf"),
        ),
        font_name: None,
        font_size: None,
    })
    .expect("layout");
    assert_eq!(img.dimensions(), (384, 240));

    // The QR fills the content band inside the default registration inset.
    let safe = thermark::geometry::SafeArea::default();
    let printable_h = lp.height_px - safe.top - safe.bottom;
    let side = max_qr_side(lp, safe);
    assert!(
        side >= printable_h - 16,
        "QR should use most of the printable height {printable_h}, got {side}"
    );
    assert!(
        side <= printable_h,
        "QR {side} must not exceed the printable height {printable_h}"
    );
    let qr = render_qr_square("https://example.com", side).unwrap();
    assert_eq!(qr.dimensions(), (side, side));
}

#[test]
fn px_per_mm_constant() {
    assert!((PX_PER_MM - 8.0).abs() < f64::EPSILON);
    // 50mm * 8 = 400, clamped to 384
    assert_eq!(LabelMm::new(50.0, 30.0).to_pixels(384, 8.0).width_px, 384);
}

#[test]
fn packet_buffer_resync() {
    let good = Packet::new(0x40, vec![0x0b]).encode().unwrap();
    let mut buf = vec![0x00, 0xFF];
    buf.extend_from_slice(&good);
    let pkts = Packet::drain_buffer(&mut buf);
    assert_eq!(pkts.len(), 1);
    assert_eq!(pkts[0].cmd, 0x40);
}

#[tokio::test]
async fn low_level_commands_require_the_explicit_raw_api() {
    let mut client =
        PrinterClient::new(thermark::MockTransport::new(), Model::B1).with_pacing(Pacing::INSTANT);
    assert!(client.raw().set_density(Density::DARK).await.unwrap());
    assert_eq!(client.transport().tx_cmds(), [0x21]);
}