189 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|