Files
2026-09-30 10:22:42 +02:00

189 lines
4.7 KiB
Go

package escpos
import (
"bytes"
"image/png"
"os"
"path/filepath"
"sync"
"testing"
)
type recorder struct {
mu sync.Mutex
buf []byte
}
func (r *recorder) reply(b []byte) {
r.mu.Lock()
r.buf = append(r.buf, b...)
r.mu.Unlock()
}
func (r *recorder) take() []byte {
r.mu.Lock()
defer r.mu.Unlock()
b := r.buf
r.buf = nil
return b
}
func newTestPrinter(t *testing.T, key string) *Printer {
t.Helper()
prof, ok := FindProfile(key)
if !ok {
t.Fatalf("perfil %q no existe", key)
}
p, err := NewPrinter(prof)
if err != nil {
t.Fatal(err)
}
return p
}
func blackPixels(p *Printer) int {
n := 0
for _, t := range p.Tickets() {
for _, v := range t.Image.Pix {
if v < 0x80 {
n++
}
}
}
return n
}
func TestTextAndCut(t *testing.T) {
p := newTestPrinter(t, "80mm")
s := p.NewSession("test", nil)
s.Write([]byte("\x1b@Hola mundo\n\x1dVA\x00"))
if got := len(p.Tickets()); got != 1 {
t.Fatalf("tickets = %d, want 1", got)
}
if blackPixels(p) == 0 {
t.Fatal("el ticket está en blanco")
}
}
func TestCutWithoutFeedLeavesTail(t *testing.T) {
p := newTestPrinter(t, "80mm")
s := p.NewSession("test", nil)
s.Write([]byte("linea\n\x1dV\x00"))
if got := len(p.Tickets()); got != 0 {
t.Fatalf("con una sola línea bajo la cuchilla no debe salir ticket, salen %d", got)
}
s.Write([]byte("\x1dVA\x00"))
if got := len(p.Tickets()); got != 1 {
t.Fatalf("tickets = %d, want 1", got)
}
}
func TestCommandSplitAcrossWrites(t *testing.T) {
p := newTestPrinter(t, "80mm")
s := p.NewSession("test", nil)
img := []byte{0x1d, 'v', '0', 0, 2, 0, 3, 0, 0xff, 0xff, 0x81, 0x81, 0xff, 0xff}
for _, c := range img {
s.Write([]byte{c})
}
s.Write([]byte("\x1dVA\x00"))
if blackPixels(p) != 16+4+16 {
t.Fatalf("píxeles negros = %d, want 36", blackPixels(p))
}
}
func TestRealtimeStatusWhileCoverOpen(t *testing.T) {
p := newTestPrinter(t, "80mm")
var r recorder
s := p.NewSession("test", r.reply)
s.Write([]byte{dle, 0x04, 1})
if got := r.take(); !bytes.Equal(got, []byte{0x12}) {
t.Fatalf("DLE EOT 1 online = % X, want 12", got)
}
p.UpdateStatus(func(st *Status) { st.CoverOpen = true })
s.Write([]byte("retenido\n"))
s.Write([]byte{dle, 0x04, 2})
if got := r.take(); !bytes.Equal(got, []byte{0x12 | 0x04 | 0x40}) {
t.Fatalf("DLE EOT 2 con tapa abierta = % X, want 56", got)
}
if p.Snapshot(0).PendingBytes == 0 {
t.Fatal("los datos deberían seguir en el buffer")
}
p.UpdateStatus(func(st *Status) { st.CoverOpen = false })
if p.Snapshot(0).PendingBytes != 0 {
t.Fatal("al cerrar la tapa se debería imprimir el buffer")
}
}
func TestGSI(t *testing.T) {
p := newTestPrinter(t, "80mm")
var r recorder
s := p.NewSession("test", r.reply)
s.Write([]byte{gs, 'I', 67})
if got, want := r.take(), []byte("_ESCPOS-EMU-80\x00"); !bytes.Equal(got, want) {
t.Fatalf("GS I 67 = %q, want %q", got, want)
}
p58 := newTestPrinter(t, "58mm")
s58 := p58.NewSession("test", r.reply)
s58.Write([]byte{gs, 'I', 67})
if got := r.take(); len(got) != 0 {
t.Fatalf("el perfil 58mm no debería responder a GS I 67, responde %q", got)
}
}
func TestQRAndDrawer(t *testing.T) {
p := newTestPrinter(t, "80mm")
var r recorder
s := p.NewSession("test", r.reply)
data := "https://example.com"
n := len(data) + 3
cmd := []byte{gs, '(', 'k', byte(n), byte(n >> 8), 49, 80, 48}
cmd = append(cmd, data...)
cmd = append(cmd, gs, '(', 'k', 3, 0, 49, 67, 4)
cmd = append(cmd, gs, '(', 'k', 3, 0, 49, 81, 48)
cmd = append(cmd, esc, 'p', 0, 25, 250)
cmd = append(cmd, dle, 0x04, 1)
cmd = append(cmd, gs, 'V', 'A', 0)
s.Write(cmd)
if blackPixels(p) < 500 {
t.Fatalf("el QR no parece impreso (%d píxeles negros)", blackPixels(p))
}
if got := r.take(); !bytes.Equal(got, []byte{0x16}) {
t.Fatalf("DLE EOT 1 con cajón abierto = % X, want 16", got)
}
}
// TestSampleReceipt genera un ticket de ejemplo. Con ESCPOS_PNG=dir se
// guarda como PNG para inspeccionarlo a ojo.
func TestSampleReceipt(t *testing.T) {
p := newTestPrinter(t, "80mm")
s := p.NewSession("test", nil)
var b bytes.Buffer
b.WriteString("\x1b@\x1bt\x13\x1ba\x01\x1d!\x11TIENDA\n\x1d!\x00")
b.WriteString("\x1bE\x01Negrita\x1bE\x00 normal \x1b-\x01subrayado\x1b-\x00\n")
b.WriteString("\x1ba\x00Ca\xa4a 1,50 \xd5\n") // PC858: ñ y €
b.WriteString("\x1dB\x01 INVERTIDO \x1dB\x00\x1bM\x01 fuente B\x1bM\x00\n")
b.WriteString("\x1ba\x02Total: 12,00\n\x1ba\x00")
b.WriteString("Col1\tCol2\tCol3\n")
b.WriteString("\x1dVB\x10")
s.Write(b.Bytes())
tix := p.Tickets()
if len(tix) != 1 {
t.Fatalf("tickets = %d, want 1", len(tix))
}
if dir := os.Getenv("ESCPOS_PNG"); dir != "" {
f, err := os.Create(filepath.Join(dir, "sample.png"))
if err != nil {
t.Fatal(err)
}
defer f.Close()
if err := png.Encode(f, tix[0].Image); err != nil {
t.Fatal(err)
}
}
}