Skip to content

Commit 94a6c90

Browse files
committed
Bug 41719: Add rendering test coverage for Labels and Patroncards
Extends t/db_dependent/Labels/t_Label.t with subtests for: - draw_label_text() layout math (text_llx/text_lly pinned to the _BIB geometry contract, downward text flow) - draw_guide_box() PDF stream output - All supported barcode types (CODE39, CODE39MOD, CODE39MOD10, COOP2OF5, INDUSTRIAL2OF5, EAN13), asserting no barcode-generation warning fires per type (Label.pm converts generation failures to warns, so absence-of-death alone cannot detect breakage) - create_label() printing type orchestration (BIB/BAR/BIBBAR/BARBIB), including the geometric contract distinguishing BIBBAR from BARBIB Creates t/db_dependent/Patroncards/t_Patroncard.t with subtests for: - draw_guide_box(), draw_guide_grid(), draw_text() - draw_barcode() across all five dispatched types; the CODE39MOD and CODE39MOD10 rows fail until Bug 43095 lands (missing Algorithm::CheckDigits import in Patroncard.pm, surfaced by this coverage work) - draw_image() - End-to-end PDF output, asserting Koha-originated content (text blocks at layout-computed coordinates, guide-box rectangle) rather than only the PDF header PDF::Reuse emits unconditionally Both files skip cleanly with a clear message when PDF::Reuse < 0.43 is installed (Bug 41717), instead of failing with opaque internals. A shared capture_pdf_end() helper centralises the End() output capture and dies on End() failure instead of swallowing it. Known Bug 41718 and PDF::Reuse prJpeg warnings are filtered against their exact observed text; unexpected warnings are forwarded to Test::NoWarnings. Test plan: 1) Apply Bug 41717 (or cpanm PDF::Reuse@0.43 PDF::Reuse::Barcode@0.09) 2) prove t/db_dependent/Labels/t_Label.t -- all tests pass 3) prove t/db_dependent/Patroncards/t_Patroncard.t -- draw_barcode() subtest fails on CODE39MOD/CODE39MOD10 with "Undefined subroutine ... CheckDigits", demonstrating Bug 43095 4) Apply Bug 43095 and re-run step 3 -- all tests pass AI Assistance: Claude Opus 4.5 drafted the original test code and debugged a PDF::Reuse issue (GitHub cnighswonger/PDF-Reuse#24, fixed in 0.43). Claude Fable 5 (Anthropic) reviewed the first revision, identified tautological assertions, unexercised checksum paths, and the latent Bug 43095 crash, and drafted the revised tests. OpenAI Codex reviewed the revision (helper error propagation, warning-filter precision). Human author directed the approach, rejected an initial implementation that merely exercised PDF::Reuse rather than Koha code, chose to surface rather than skip the Bug 43095 failure, reviewed all changes, and verified test behavior. Assisted-by: Claude Opus 4.5 (Anthropic) Assisted-by: Claude Fable 5 (Anthropic) Assisted-by: Codex (OpenAI)
1 parent 1534574 commit 94a6c90

2 files changed

Lines changed: 681 additions & 5 deletions

File tree

t/db_dependent/Labels/t_Label.t

Lines changed: 215 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# This file is part of Koha.
44
#
5-
# Copyright 2020 Koha Development team
5+
# Copyright 2020, 2026 Koha Development team
66
# Copyright (C) 2017 Mark Tompsett
77
#
88
# Koha is free software; you can redistribute it and/or modify it
@@ -20,26 +20,58 @@
2020

2121
use Modern::Perl;
2222

23+
BEGIN {
24+
eval { require PDF::Reuse; PDF::Reuse->VERSION(0.43) };
25+
if ($@) {
26+
require Test::More;
27+
Test::More::plan( skip_all => 'PDF::Reuse >= 0.43 required (see Bug 41717)' );
28+
}
29+
}
30+
2331
use Test::NoWarnings;
24-
use Test::More tests => 7;
32+
use Test::More tests => 11;
33+
use Test::Warn;
2534
use t::lib::TestBuilder;
2635
use t::lib::Mocks;
2736

2837
use MARC::Record;
2938
use MARC::Field;
3039
use Data::Dumper;
40+
use File::Temp qw( tempfile );
3141

3242
use C4::Items;
3343
use C4::Biblio;
3444
use C4::Labels::Layout;
45+
use C4::Creators::PDF;
3546

3647
use Koha::Database;
3748

3849
use_ok('C4::Labels::Label');
3950

40-
my $database = Koha::Database->new();
41-
my $schema = $database->schema();
42-
$schema->storage->txn_begin();
51+
# Slurp whatever C4::Creators::PDF::End() prints to the currently selected
52+
# filehandle, return it as a string. Centralises the tempfile/select/End
53+
# dance so a future End() refactor only needs one edit. Dies if End()
54+
# dies (after restoring the selected filehandle) so cleanup-only callers
55+
# cannot silently pass through an End() regression.
56+
sub capture_pdf_end {
57+
my ($pdf) = @_;
58+
my ( $fh, $path ) = tempfile( SUFFIX => '.pdf', UNLINK => 1 );
59+
open( my $out, '>', $path ) or die "Cannot open $path for write: $!";
60+
my $orig = select $out;
61+
eval { $pdf->End() };
62+
my $end_err = $@;
63+
select $orig;
64+
close $out;
65+
die "pdf End() failed: $end_err" if $end_err;
66+
open( my $in, '<', $path ) or die "Cannot open $path for read: $!";
67+
local $/;
68+
my $content = <$in>;
69+
close $in;
70+
return $content;
71+
}
72+
73+
my $schema = Koha::Database->new->schema;
74+
$schema->storage->txn_begin;
4375

4476
my $batch_id;
4577
my ( $llx, $lly ) = ( 0, 0 );
@@ -152,6 +184,184 @@ is_deeply(
152184
is( $barcode_width, '2.104', );
153185
is( $barcode_height, '0.01', );
154186

187+
# ========================================
188+
# Subtest: draw_label_text() deep assertions
189+
# ========================================
190+
subtest 'draw_label_text() returns correct text structure' => sub {
191+
plan tests => 6;
192+
193+
# Local fixture: deterministic two-field format so this subtest does
194+
# not depend on whatever was last assigned to the top-level $format_string.
195+
my $local_format = '100a 245a,enumchron copynumber';
196+
197+
my $label_bib = C4::Labels::Label->new(
198+
%$label_info,
199+
format_string => $local_format,
200+
printing_type => 'BIB',
201+
justify => 'L',
202+
);
203+
my $text_result = $label_bib->create_label();
204+
205+
ok( defined $text_result, 'create_label(BIB) returns defined value' );
206+
is( ref $text_result, 'ARRAY', 'Return value is an arrayref' );
207+
cmp_ok( scalar @$text_result, '>=', 2, 'Two-field format yields at least two text lines' );
208+
209+
my $first_line = $text_result->[0];
210+
my $expected_llx = $label_info->{llx} + $label_info->{left_text_margin};
211+
my $expected_first_y = $label_info->{lly} + $label_info->{height} - $label_info->{top_text_margin};
212+
213+
is(
214+
$first_line->{text_llx}, $expected_llx,
215+
'First line text_llx = llx + left_text_margin (justify L)'
216+
);
217+
is(
218+
$first_line->{text_lly}, $expected_first_y,
219+
'First line text_lly = lly + height - top_text_margin (per _BIB)'
220+
);
221+
cmp_ok(
222+
$text_result->[1]{text_lly}, '<', $first_line->{text_lly},
223+
'Second line text_lly is less than first (text flows downward)'
224+
);
225+
};
226+
227+
# ========================================
228+
# Subtest: draw_guide_box()
229+
# ========================================
230+
subtest 'draw_guide_box() returns PDF stream when enabled' => sub {
231+
plan tests => 3;
232+
233+
my $label_with_box = C4::Labels::Label->new(
234+
%$label_info,
235+
format_string => $format_string,
236+
guidebox => 1,
237+
llx => 10,
238+
lly => 20,
239+
width => 100,
240+
height => 50,
241+
);
242+
my $box_stream = $label_with_box->draw_guide_box();
243+
ok( $box_stream, 'draw_guide_box returns truthy when guidebox enabled' );
244+
like( $box_stream, qr/10 20 100 50 re/, 'PDF stream contains expected rectangle coordinates' );
245+
246+
my $label_no_box = C4::Labels::Label->new(
247+
%$label_info,
248+
format_string => $format_string,
249+
guidebox => 0,
250+
);
251+
ok( !$label_no_box->draw_guide_box(), 'draw_guide_box returns falsy when guidebox disabled' );
252+
};
253+
254+
# ========================================
255+
# Subtest: barcode() with all supported types
256+
# ========================================
257+
subtest 'barcode() generates all supported barcode types' => sub {
258+
plan tests => 7;
259+
260+
# Local fixture; do not inherit top-level $format_string state.
261+
my $local_format = '100a 245a';
262+
263+
my $pdf = C4::Creators::PDF->new( InitVars => 1 );
264+
$pdf->Page();
265+
266+
# C4::Labels::Label::barcode() converts every PDF::Reuse::Barcode::*
267+
# failure to a warn (Label.pm:556,573,590,609), so a per-type test that
268+
# only checks "did it die" cannot distinguish success from a silent
269+
# barcode-generation failure. We therefore assert no barcode-generation
270+
# warning fires for each type, which is the actual breakage signal.
271+
# CODE39MOD10 (modulo-10 / 'siret') needs numeric data; alpha input
272+
# yields an empty checksum and silently degenerates.
273+
my @barcode_tests = (
274+
{ type => 'CODE39', barcode => 'TEST97531', desc => 'CODE39' },
275+
{ type => 'CODE39MOD', barcode => 'TEST97531', desc => 'CODE39MOD (modulo43 checksum)' },
276+
{ type => 'CODE39MOD10', barcode => '97531', desc => 'CODE39MOD10 (modulo10 checksum)' },
277+
{ type => 'COOP2OF5', barcode => '97531', desc => 'COOP2OF5 (numeric)' },
278+
{ type => 'INDUSTRIAL2OF5', barcode => '97531', desc => 'INDUSTRIAL2OF5 (numeric)' },
279+
{ type => 'EAN13', barcode => '5901234123457', desc => 'EAN13 (13-digit)' },
280+
);
281+
282+
for my $bc_test (@barcode_tests) {
283+
my $bc_label = C4::Labels::Label->new(
284+
%$label_info,
285+
format_string => $local_format,
286+
printing_type => 'BAR',
287+
barcode_type => $bc_test->{type},
288+
barcode => $bc_test->{barcode},
289+
);
290+
warnings_are { $bc_label->create_label() } [],
291+
"barcode() $bc_test->{desc} emits no barcode-generation warning";
292+
}
293+
294+
# Slurp the PDF and assert it contains at least one barcode graphic
295+
# stroke (Code39 / 2-of-5 / EAN13 all emit rectangle 're' operators),
296+
# so the per-type checks above can't all pass against an empty document.
297+
my $content = capture_pdf_end($pdf);
298+
like( $content, qr/\bre\b/, 'PDF contains barcode rectangle operators' );
299+
};
300+
301+
# ========================================
302+
# Subtest: create_label() printing type orchestration
303+
# ========================================
304+
subtest 'create_label() printing type orchestration' => sub {
305+
plan tests => 5;
306+
307+
my $local_format = '100a 245a';
308+
309+
my $pdf = C4::Creators::PDF->new( InitVars => 1 );
310+
$pdf->Page();
311+
312+
# BIB - returns label_text (text only)
313+
my $label_bib = C4::Labels::Label->new(
314+
%$label_info,
315+
format_string => $local_format,
316+
printing_type => 'BIB',
317+
);
318+
my $bib_result = $label_bib->create_label();
319+
ok( defined $bib_result, 'BIB printing type returns label text' );
320+
321+
# BAR - returns undef (barcode only)
322+
my $label_bar = C4::Labels::Label->new(
323+
%$label_info,
324+
format_string => $local_format,
325+
printing_type => 'BAR',
326+
barcode_type => 'CODE39',
327+
barcode => 'ORCHTEST1',
328+
);
329+
my $bar_result = $label_bar->create_label();
330+
ok( !defined $bar_result, 'BAR printing type returns undef (barcode only)' );
331+
332+
# BIBBAR - text-above-barcode geometry (text near top of label)
333+
my $label_bibbar = C4::Labels::Label->new(
334+
%$label_info,
335+
format_string => $local_format,
336+
printing_type => 'BIBBAR',
337+
barcode_type => 'CODE39',
338+
barcode => 'ORCHTEST2',
339+
);
340+
my $bibbar_result = $label_bibbar->create_label();
341+
ok( defined $bibbar_result, 'BIBBAR printing type returns label text' );
342+
343+
# BARBIB - barcode-above-text geometry (text shifted downward)
344+
my $label_barbib = C4::Labels::Label->new(
345+
%$label_info,
346+
format_string => $local_format,
347+
printing_type => 'BARBIB',
348+
barcode_type => 'CODE39',
349+
barcode => 'ORCHTEST3',
350+
);
351+
my $barbib_result = $label_barbib->create_label();
352+
ok( defined $barbib_result, 'BARBIB printing type returns label text' );
353+
354+
# If _BIBBAR and _BARBIB were swapped, both return arrays with defined
355+
# entries and the four checks above would still pass. Pin the geometric
356+
# contract: BIBBAR puts text above BARBIB's text.
357+
cmp_ok(
358+
$bibbar_result->[0]{text_lly}, '>', $barbib_result->[0]{text_lly},
359+
'BIBBAR places first text line above BARBIB (text_lly higher)'
360+
);
361+
362+
capture_pdf_end($pdf);
363+
};
364+
155365
$schema->storage->txn_rollback();
156366

157367
1;

0 commit comments

Comments
 (0)