Skip to content

Commit 5c6196c

Browse files
developer0hyeclaude
andcommitted
feat: US-064 - WASM integration test with wasm-pack
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
1 parent b7aea7d commit 5c6196c

File tree

4 files changed

+219
-1
lines changed

4 files changed

+219
-1
lines changed

crates/office2pdf/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ wasm-bindgen = { version = "0.2", optional = true }
3131
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
3232
getrandom = { version = "0.3", features = ["wasm_js"] }
3333

34+
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
35+
wasm-bindgen-test = "0.3"
36+
3437
[dev-dependencies]
3538
criterion = { version = "0.5", features = ["html_reports"] }
3639
paste = "1"

crates/office2pdf/src/wasm.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@
33
//! This module is only available when the `wasm` feature is enabled.
44
//! It exports JavaScript-callable functions for converting Office documents
55
//! to PDF in browser or Node.js environments.
6+
//!
7+
//! # Running WASM integration tests
8+
//!
9+
//! WASM integration tests use `wasm-bindgen-test` and require `wasm-pack`:
10+
//!
11+
//! ```bash
12+
//! # Install wasm-pack (one-time setup)
13+
//! cargo install wasm-pack
14+
//!
15+
//! # Run WASM tests in Node.js
16+
//! cd crates/office2pdf
17+
//! wasm-pack test --node --features wasm
18+
//!
19+
//! # Or run in a headless browser
20+
//! wasm-pack test --headless --chrome --features wasm
21+
//! ```
22+
//!
23+
//! These tests verify end-to-end WASM conversion by building the library as
24+
//! a WASM module, loading it, and calling the exported functions.
625
726
use wasm_bindgen::prelude::*;
827

@@ -252,3 +271,174 @@ mod tests {
252271
assert!(convert_format_inner(b"bad", Format::Xlsx).is_err());
253272
}
254273
}
274+
275+
// ---------------------------------------------------------------------------
276+
// WASM integration tests (run via `wasm-pack test --node --features wasm`)
277+
//
278+
// These tests compile ONLY when targeting wasm32 and are executed inside a
279+
// real WASM runtime (Node.js or headless browser). They call the actual
280+
// `#[wasm_bindgen]`-exported functions and verify end-to-end conversion.
281+
// ---------------------------------------------------------------------------
282+
#[cfg(all(target_arch = "wasm32", test))]
283+
mod wasm_tests {
284+
use super::*;
285+
use wasm_bindgen_test::*;
286+
287+
/// Helper: create a minimal valid DOCX via docx-rs builder.
288+
fn make_minimal_docx() -> Vec<u8> {
289+
use std::io::Cursor;
290+
let doc = docx_rs::Docx::new().add_paragraph(
291+
docx_rs::Paragraph::new().add_run(docx_rs::Run::new().add_text("Hello WASM")),
292+
);
293+
let mut buf = Cursor::new(Vec::new());
294+
doc.build().pack(&mut buf).unwrap();
295+
buf.into_inner()
296+
}
297+
298+
/// Helper: create a minimal valid PPTX.
299+
fn make_minimal_pptx() -> Vec<u8> {
300+
use std::io::{Cursor, Write};
301+
let cursor = Cursor::new(Vec::new());
302+
let mut zip = zip::ZipWriter::new(cursor);
303+
let options =
304+
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
305+
306+
zip.start_file("[Content_Types].xml", options).unwrap();
307+
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
308+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
309+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
310+
<Default Extension="xml" ContentType="application/xml"/>
311+
<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
312+
<Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
313+
</Types>"#)
314+
.unwrap();
315+
316+
zip.start_file("_rels/.rels", options).unwrap();
317+
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
318+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
319+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
320+
</Relationships>"#)
321+
.unwrap();
322+
323+
zip.start_file("ppt/presentation.xml", options).unwrap();
324+
zip.write_all(
325+
br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
326+
<p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
327+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
328+
<p:sldSz cx="9144000" cy="6858000"/>
329+
<p:sldIdLst>
330+
<p:sldId id="256" r:id="rId2"/>
331+
</p:sldIdLst>
332+
</p:presentation>"#,
333+
)
334+
.unwrap();
335+
336+
zip.start_file("ppt/_rels/presentation.xml.rels", options)
337+
.unwrap();
338+
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
339+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
340+
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
341+
</Relationships>"#)
342+
.unwrap();
343+
344+
zip.start_file("ppt/slides/slide1.xml", options).unwrap();
345+
zip.write_all(
346+
br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
347+
<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
348+
xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
349+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
350+
<p:cSld>
351+
<p:spTree>
352+
<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
353+
<p:grpSpPr/>
354+
<p:sp>
355+
<p:nvSpPr><p:cNvPr id="2" name="Title"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
356+
<p:spPr>
357+
<a:xfrm><a:off x="0" y="0"/><a:ext cx="9144000" cy="1000000"/></a:xfrm>
358+
</p:spPr>
359+
<p:txBody>
360+
<a:bodyPr/>
361+
<a:p><a:r><a:t>Hello WASM</a:t></a:r></a:p>
362+
</p:txBody>
363+
</p:sp>
364+
</p:spTree>
365+
</p:cSld>
366+
</p:sld>"#,
367+
)
368+
.unwrap();
369+
370+
zip.finish().unwrap().into_inner()
371+
}
372+
373+
/// Helper: create a minimal valid XLSX.
374+
fn make_minimal_xlsx() -> Vec<u8> {
375+
use std::io::Cursor;
376+
let mut book = umya_spreadsheet::new_file();
377+
let sheet = book.get_sheet_mut(&0).unwrap();
378+
sheet.get_cell_mut("A1").set_value("Hello WASM");
379+
let mut cursor = Cursor::new(Vec::new());
380+
umya_spreadsheet::writer::xlsx::write_writer(&book, &mut cursor).unwrap();
381+
cursor.into_inner()
382+
}
383+
384+
#[wasm_bindgen_test]
385+
fn wasm_convert_docx_to_pdf_produces_valid_pdf() {
386+
let docx = make_minimal_docx();
387+
let result = convert_docx_to_pdf(&docx);
388+
assert!(result.is_ok(), "DOCX to PDF conversion failed in WASM");
389+
let pdf = result.unwrap();
390+
assert!(
391+
pdf.starts_with(b"%PDF"),
392+
"Output should start with %PDF magic bytes"
393+
);
394+
assert!(pdf.len() > 100, "PDF output should have meaningful size");
395+
}
396+
397+
#[wasm_bindgen_test]
398+
fn wasm_convert_to_pdf_with_docx_format_string() {
399+
let docx = make_minimal_docx();
400+
let result = convert_to_pdf(&docx, "docx");
401+
assert!(
402+
result.is_ok(),
403+
"convert_to_pdf with 'docx' format failed in WASM"
404+
);
405+
let pdf = result.unwrap();
406+
assert!(pdf.starts_with(b"%PDF"));
407+
}
408+
409+
#[wasm_bindgen_test]
410+
fn wasm_convert_pptx_to_pdf_produces_valid_pdf() {
411+
let pptx = make_minimal_pptx();
412+
let result = convert_pptx_to_pdf(&pptx);
413+
assert!(result.is_ok(), "PPTX to PDF conversion failed in WASM");
414+
let pdf = result.unwrap();
415+
assert!(
416+
pdf.starts_with(b"%PDF"),
417+
"Output should start with %PDF magic bytes"
418+
);
419+
}
420+
421+
#[wasm_bindgen_test]
422+
fn wasm_convert_xlsx_to_pdf_produces_valid_pdf() {
423+
let xlsx = make_minimal_xlsx();
424+
let result = convert_xlsx_to_pdf(&xlsx);
425+
assert!(result.is_ok(), "XLSX to PDF conversion failed in WASM");
426+
let pdf = result.unwrap();
427+
assert!(
428+
pdf.starts_with(b"%PDF"),
429+
"Output should start with %PDF magic bytes"
430+
);
431+
}
432+
433+
#[wasm_bindgen_test]
434+
fn wasm_convert_to_pdf_invalid_data_returns_error() {
435+
let result = convert_docx_to_pdf(b"not a valid docx");
436+
assert!(result.is_err(), "Should fail on invalid input data");
437+
}
438+
439+
#[wasm_bindgen_test]
440+
fn wasm_convert_to_pdf_unsupported_format_returns_error() {
441+
let result = convert_to_pdf(b"dummy", "txt");
442+
assert!(result.is_err(), "Should fail on unsupported format string");
443+
}
444+
}

scripts/ralph/prd.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"cargo clippy --workspace -- -D warnings passes"
8888
],
8989
"priority": 5,
90-
"passes": false,
90+
"passes": true,
9191
"notes": "Add dev-dependencies: wasm-bindgen-test = \"0.3\" (behind cfg). Create tests in src/wasm.rs or a separate test file. The test fixture can be a minimal valid DOCX (ZIP file with [Content_Types].xml and a minimal document.xml). If creating a real DOCX programmatically is too complex, use the smallest fixture from tests/fixtures/docx/. wasm-bindgen-test requires: cargo install wasm-pack, then wasm-pack test --node. If wasm-pack is unavailable in the CI environment, add the test as a separate optional CI step or document it."
9292
}
9393
]

scripts/ralph/progress.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,28 @@ Started: 2026년 2월 28일 토요일 01시 35분 02초 KST
166166
- WASM check only needs to run on one OS since WASM compilation is platform-independent
167167
- Two separate `cargo check` commands ensure both base library and wasm-bindgen feature compile for WASM
168168
---
169+
170+
## 2026-02-28 - US-064
171+
- What was implemented: WASM integration test with wasm-bindgen-test
172+
- Added `wasm-bindgen-test = "0.3"` as a wasm32-target-specific dev-dependency
173+
- Created 6 `#[wasm_bindgen_test]` integration tests in `src/wasm.rs::wasm_tests` module
174+
- Tests verify end-to-end WASM conversion for all 3 formats: DOCX, PPTX, XLSX
175+
- Tests call actual `#[wasm_bindgen]`-exported functions (`convert_docx_to_pdf`, `convert_pptx_to_pdf`, `convert_xlsx_to_pdf`, `convert_to_pdf`)
176+
- Tests verify PDF output starts with `%PDF` magic bytes and has meaningful size
177+
- Error case tests: invalid data returns error, unsupported format returns error
178+
- Tests are gated by `#[cfg(all(target_arch = "wasm32", test))]` — only run in WASM test context
179+
- Added module-level documentation with instructions for running WASM tests via `wasm-pack`
180+
- Native tests completely unaffected (507 lib + 7 CLI tests pass)
181+
- Files changed:
182+
- `crates/office2pdf/Cargo.toml` — added `[target.'cfg(target_arch = "wasm32")'.dev-dependencies]` with `wasm-bindgen-test`
183+
- `crates/office2pdf/src/wasm.rs` — added wasm_tests module and module-level docs
184+
- `scripts/ralph/prd.json` — marked US-064 passes: true
185+
- Dependencies added:
186+
- `wasm-bindgen-test` 0.3 (dev-dependency, wasm32 target only)
187+
- **Learnings for future iterations:**
188+
- `wasm-bindgen-test` tests use `#[wasm_bindgen_test]` attribute and are run via `wasm-pack test --node --features wasm`
189+
- Tests gated by `#[cfg(all(target_arch = "wasm32", test))]` are completely invisible to `cargo test` on native — no overhead
190+
- `[target.'cfg(target_arch = "wasm32")'.dev-dependencies]` keeps WASM-only test deps from affecting native builds
191+
- Test helpers can reuse the same DOCX/PPTX/XLSX creation patterns as native tests since those libraries all compile on wasm32
192+
- `wasm-pack test` can run in Node.js (`--node`) or headless browser (`--headless --chrome`)
193+
---

0 commit comments

Comments
 (0)