const { test } = require('node:test'); const assert = require('node:assert'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const { execFileSync } = require('node:child_process'); global.PDFLib = require('pdf-lib'); const { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName, formatTeacher, truncateToWidth } = require('../pdf.js'); const sampleData = { header: { fullName: 'ADRIAN ALFRED CHIONG BONPIN', studentId: '22103625', program: 'BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY', yearLevel: '4', semester: '1ST Semester 2026', dateEnrolled: '8/5/2024', dateRange: 'AUGUST - JANUARY' }, courses: [ { group: '7', courseCode: 'CIS 2101', description: 'DATA STRUCTURES AND ALGORITHMS', units: '3.00', teacher: 'CHRISTIAN MADERAZO', schedule: 'M,W 03:00 PM - 05:30 PM LB443TC', status: 'REGULAR' }, { group: '2', courseCode: 'IT 3103N', description: 'SYSTEMS INTEGRATION AND ARCHITECTURE', units: '3.00', teacher: 'KHENT DELA PAZ', schedule: 'T,Th 01:30 PM - 03:00 PM DCISMVR3TC', status: 'REGULAR' } ], totalUnits: '6.00' }; function extractPdfText(bytes) { const tmp = path.join(os.tmpdir(), 'study-load-test.pdf'); fs.writeFileSync(tmp, Buffer.from(bytes)); const script = [ 'import fitz', `doc = fitz.open('${tmp}')`, 'text = ""', 'for page in doc:', ' text += page.get_text()', 'print(text)' ].join('\n'); return execFileSync('python3', ['-c', script], { encoding: 'utf8' }); } test('buildStudyLoadPdf returns valid PDF bytes', async () => { const bytes = await buildStudyLoadPdf(sampleData); assert.ok(bytes instanceof Uint8Array); const head = Buffer.from(bytes.slice(0, 5)).toString('latin1'); assert.strictEqual(head, '%PDF-'); const doc = await PDFLib.PDFDocument.load(bytes); assert.strictEqual(doc.getPageCount(), 1); const text = extractPdfText(bytes); assert.ok(text.includes('CIS 2101'), 'PDF text should contain course code CIS 2101'); assert.ok(text.includes("STUDENT'S STUDY LOAD"), 'PDF text should contain the title'); assert.ok(text.includes('CHRISTIAN MADERAZO'), 'PDF text should contain the teacher'); assert.ok(text.includes('BS IT'), 'PDF text should contain the compacted program'); }); test('compactProgram shortens degree names', () => { assert.strictEqual(compactProgram('BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY'), 'BS IT'); assert.strictEqual(compactProgram('BACHELOR OF SCIENCE IN COMPUTER SCIENCE'), 'BS CS'); assert.strictEqual(compactProgram('BACHELOR OF ARTS IN POLITICAL SCIENCE'), 'AB PS'); assert.strictEqual(compactProgram('BACHELOR OF SCIENCE IN ACCOUNTANCY'), 'BS Accountancy'); assert.strictEqual(compactProgram('UNKNOWN PROGRAM'), 'UNKNOWN PROGRAM'); }); test('formatSchedule normalizes schedule text', () => { assert.strictEqual(formatSchedule('M W 07:30 AM - 10:00 AM LB447TC TC'), 'M,W 07:30 AM - 10:00 AM LB447TC'); assert.strictEqual(formatSchedule('T Th 09:00 AM - 10:30 AM LB443TC TC'), 'T,Th 09:00 AM - 10:30 AM LB443TC'); assert.strictEqual(formatSchedule('M,W 03:00 PM - 05:30 PM LB443TC'), 'M,W 03:00 PM - 05:30 PM LB443TC'); assert.strictEqual( formatSchedule('T 07:30 AM - 10:30 AM LB448TC TC Th 07:30 AM - 10:30 AM LB448TC'), 'T 07:30 AM - 10:30 AM LB448TC\nTh 07:30 AM - 10:30 AM LB448TC' ); assert.strictEqual( formatSchedule('W 01:30 PM - 04:30 PM LB448TC TC M 01:30 PM - 04:30 PM LB448TC'), 'W 01:30 PM - 04:30 PM LB448TC\nM 01:30 PM - 04:30 PM LB448TC' ); }); test('formatFullName reorders first-last names to last-first', () => { assert.strictEqual(formatFullName('ADRIAN ALFRED CHIONG BONPIN'), 'BONPIN, ADRIAN ALFRED CHIONG'); assert.strictEqual(formatFullName('BONPIN, ADRIAN ALFRED CHIONG'), 'BONPIN, ADRIAN ALFRED CHIONG'); assert.strictEqual(formatFullName('JOHN DOE'), 'DOE, JOHN'); assert.strictEqual(formatFullName(''), ''); }); test('formatTeacher renders names First [MiddleInitial] Last like the reference', () => { assert.strictEqual(formatTeacher('FILLO, FR. PETER'), 'FR. PETER FILLO'); assert.strictEqual(formatTeacher('BACALSO, REUELLA J. AWAYAN'), 'REUELLA J. BACALSO'); assert.strictEqual(formatTeacher('PEPITO, GLENN BULLECER'), 'GLENN PEPITO'); assert.strictEqual(formatTeacher('MADERAZO, CHRISTIAN'), 'CHRISTIAN MADERAZO'); assert.strictEqual(formatTeacher('DELA PAZ, KHENT'), 'KHENT DELA PAZ'); assert.strictEqual(formatTeacher('CHRISTIAN MADERAZO'), 'CHRISTIAN MADERAZO'); assert.strictEqual(formatTeacher(''), ''); }); test('deriveDateRange maps semesters', () => { assert.strictEqual(deriveDateRange('1ST Semester 2026'), 'AUGUST - JANUARY'); assert.strictEqual(deriveDateRange('2ND Semester 2026'), 'JANUARY - MAY'); assert.strictEqual(deriveDateRange('Summer 2026'), 'JUNE - JULY'); assert.strictEqual(deriveDateRange(''), ''); }); test('truncateToWidth hard-cuts text to fit within maxWidth', async () => { const doc = await global.PDFLib.PDFDocument.create(); const font = doc.embedStandardFont(global.PDFLib.StandardFonts.Helvetica); const longText = 'APPLICATION DEVELOPMENT AND EMERGING TECHNOLOGIES'; const tr = truncateToWidth(longText, font, 8, 100); assert.ok(tr.length < longText.length, 'long text should be cut down'); assert.ok(longText.startsWith(tr), 'result must be a prefix of the input'); assert.ok(font.widthOfTextAtSize(tr, 8) <= 100, 'truncated line must fit maxWidth'); const short = 'DATA STRUCTURES'; assert.strictEqual(truncateToWidth(short, font, 8, 200), short); assert.strictEqual(truncateToWidth('', font, 8, 200), ''); });