import { getCurrentDocumentFile, DOCX_CONTENT_TYPE } from "./fileService"; interface FileMockState { getFileCalls: number; requestedSlices: number[]; closeCalls: number; } /** mock Office.context.document.getFileAsync + File.getSliceAsync/closeAsync */ function installOfficeFileMock( slices: Uint8Array[], options: { failSliceIndex?: number; failClose?: boolean; /** รูปร่างของ slice.data ที่ host จริงส่งกลับ — ต่างกันตามแพลตฟอร์ม (Win=ArrayBuffer, Mac=string/array) */ sliceDataForm?: "buffer" | "view" | "array" | "base64"; } = {} ): FileMockState { const state: FileMockState = { getFileCalls: 0, requestedSlices: [], closeCalls: 0 }; const toSliceData = (bytes: Uint8Array): unknown => { switch (options.sliceDataForm) { case "view": return bytes; case "array": return Array.from(bytes); case "base64": return Buffer.from(bytes).toString("base64"); default: return bytes.buffer; } }; const file = { sliceCount: slices.length, getSliceAsync: (index: number, callback: (result: unknown) => void) => { state.requestedSlices.push(index); if (options.failSliceIndex === index) { callback({ status: "failed", error: { message: `อ่าน slice ${index} พัง (mock)` } }); } else { callback({ status: "succeeded", value: { index, data: toSliceData(slices[index]), size: slices[index].byteLength }, }); } }, closeAsync: (callback?: (result: unknown) => void) => { state.closeCalls++; callback?.({ status: options.failClose ? "failed" : "succeeded" }); }, }; (globalThis as unknown as Record).Office = { FileType: { Compressed: "compressed", Text: "text" }, AsyncResultStatus: { Succeeded: "succeeded", Failed: "failed" }, context: { document: { getFileAsync: ( _fileType: unknown, _options: unknown, callback: (result: unknown) => void ) => { state.getFileCalls++; callback({ status: "succeeded", value: file }); }, }, }, }; return state; } function bytes(...values: number[]): Uint8Array { return new Uint8Array(values); } describe("getCurrentDocumentFile", () => { it("ประกอบ slice หลากขนาดเป็น Blob เดียว อ่านครบทุก slice ตามลำดับ และ closeAsync หนึ่งครั้ง", async () => { const state = installOfficeFileMock([ bytes(1, 2, 3), bytes(4, 5), new Uint8Array(2048).fill(9), ]); const blob = await getCurrentDocumentFile(); expect(blob.type).toBe(DOCX_CONTENT_TYPE); expect(blob.size).toBe(3 + 2 + 2048); expect(state.requestedSlices).toEqual([0, 1, 2]); expect(state.getFileCalls).toBe(1); expect(state.closeCalls).toBe(1); }); it("slice มาเป็น typed array view (ไม่ใช่ ArrayBuffer) ก็ประกอบได้", async () => { const state = installOfficeFileMock([bytes(10, 20), bytes(30)], { sliceDataForm: "view" }); const blob = await getCurrentDocumentFile(); expect(blob.size).toBe(3); expect(state.closeCalls).toBe(1); }); it("slice มาเป็น byte array ธรรมดา number[] (Word บน Mac) ก็ประกอบได้", async () => { const state = installOfficeFileMock([bytes(10, 20), bytes(30, 40)], { sliceDataForm: "array" }); const blob = await getCurrentDocumentFile(); expect(blob.size).toBe(4); expect(state.closeCalls).toBe(1); }); it("slice มาเป็น base64 string ก็ decode แล้วประกอบได้", async () => { const state = installOfficeFileMock([bytes(200, 100, 50), bytes(255)], { sliceDataForm: "base64", }); const blob = await getCurrentDocumentFile(); expect(blob.size).toBe(4); expect(state.closeCalls).toBe(1); }); it("อ่าน slice กลางที่พัง → reject พร้อม error แต่ยังเรียก closeAsync เสมอ", async () => { const state = installOfficeFileMock([bytes(1), bytes(2), bytes(3)], { failSliceIndex: 1 }); await expect(getCurrentDocumentFile()).rejects.toThrow("อ่าน slice 1 พัง (mock)"); expect(state.closeCalls).toBe(1); expect(state.requestedSlices).toEqual([0, 1]); }); it("closeAsync fail เอง → ไม่ทำ flow พัง (blob ยังคืนมา)", async () => { installOfficeFileMock([bytes(7, 8, 9)], { failClose: true }); const blob = await getCurrentDocumentFile(); expect(blob.size).toBe(3); }); });