@@ -256,6 +256,44 @@ describe('DocxZipper - updateContentTypes', () => {
256256 expect ( updatedContentTypes ) . toContain ( '/word/header1.xml' ) ;
257257 expect ( updatedContentTypes ) . toContain ( '/word/footer1.xml' ) ;
258258 } ) ;
259+
260+ it ( 'removes stale comment overrides when updated docs mark comment files as deleted' , async ( ) => {
261+ const zipper = new DocxZipper ( ) ;
262+ const zip = new JSZip ( ) ;
263+
264+ const contentTypes = `<?xml version="1.0" encoding="UTF-8"?>
265+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
266+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
267+ <Default Extension="xml" ContentType="application/xml"/>
268+ <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
269+ <Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>
270+ <Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>
271+ <Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>
272+ <Override PartName="/word/commentsIds.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml"/>
273+ <Override PartName="/word/commentsExtensible.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml"/>
274+ </Types>` ;
275+ zip . file ( '[Content_Types].xml' , contentTypes ) ;
276+ zip . file (
277+ 'word/document.xml' ,
278+ '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>' ,
279+ ) ;
280+
281+ const updatedDocs = {
282+ 'word/comments.xml' : null ,
283+ 'word/commentsExtended.xml' : null ,
284+ 'word/commentsIds.xml' : null ,
285+ 'word/commentsExtensible.xml' : null ,
286+ } ;
287+
288+ await zipper . updateContentTypes ( zip , { } , false , updatedDocs ) ;
289+
290+ const updatedContentTypes = await zip . file ( '[Content_Types].xml' ) . async ( 'string' ) ;
291+ expect ( updatedContentTypes ) . toContain ( 'PartName="/word/document.xml"' ) ;
292+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/comments.xml"' ) ;
293+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsExtended.xml"' ) ;
294+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsIds.xml"' ) ;
295+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsExtensible.xml"' ) ;
296+ } ) ;
259297} ) ;
260298
261299describe ( 'DocxZipper - exportFromCollaborativeDocx media handling' , ( ) => {
@@ -299,3 +337,119 @@ describe('DocxZipper - exportFromCollaborativeDocx media handling', () => {
299337 expect ( Array . from ( img2 ) ) . toEqual ( [ 87 , 111 , 114 , 108 , 100 ] ) ;
300338 } ) ;
301339} ) ;
340+
341+ describe ( 'DocxZipper - comment file deletion' , ( ) => {
342+ const contentTypesWithComments = `<?xml version="1.0" encoding="UTF-8"?>
343+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
344+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
345+ <Default Extension="xml" ContentType="application/xml"/>
346+ <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
347+ <Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>
348+ <Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>
349+ <Override PartName="/word/commentsIds.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml"/>
350+ <Override PartName="/word/commentsExtensible.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml"/>
351+ </Types>` ;
352+
353+ const updatedDocsWithCommentDeletes = {
354+ 'word/document.xml' : '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>' ,
355+ 'word/comments.xml' : null ,
356+ 'word/commentsExtended.xml' : null ,
357+ 'word/commentsIds.xml' : null ,
358+ 'word/commentsExtensible.xml' : null ,
359+ } ;
360+
361+ it ( 'removes stale comment files in collaborative export path when null sentinels are provided' , async ( ) => {
362+ const zipper = new DocxZipper ( ) ;
363+ const docx = [
364+ { name : '[Content_Types].xml' , content : contentTypesWithComments } ,
365+ {
366+ name : 'word/document.xml' ,
367+ content : '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>' ,
368+ } ,
369+ {
370+ name : 'word/comments.xml' ,
371+ content : '<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>' ,
372+ } ,
373+ {
374+ name : 'word/commentsExtended.xml' ,
375+ content : '<w15:commentsEx xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"/>' ,
376+ } ,
377+ {
378+ name : 'word/commentsIds.xml' ,
379+ content : '<w16cid:commentsIds xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"/>' ,
380+ } ,
381+ {
382+ name : 'word/commentsExtensible.xml' ,
383+ content : '<w16cex:commentsExtensible xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"/>' ,
384+ } ,
385+ ] ;
386+
387+ const result = await zipper . updateZip ( {
388+ docx,
389+ updatedDocs : updatedDocsWithCommentDeletes ,
390+ media : { } ,
391+ fonts : { } ,
392+ isHeadless : true ,
393+ } ) ;
394+
395+ const readBack = await new JSZip ( ) . loadAsync ( result ) ;
396+ expect ( readBack . file ( 'word/comments.xml' ) ) . toBeNull ( ) ;
397+ expect ( readBack . file ( 'word/commentsExtended.xml' ) ) . toBeNull ( ) ;
398+ expect ( readBack . file ( 'word/commentsIds.xml' ) ) . toBeNull ( ) ;
399+ expect ( readBack . file ( 'word/commentsExtensible.xml' ) ) . toBeNull ( ) ;
400+
401+ const updatedContentTypes = await readBack . file ( '[Content_Types].xml' ) . async ( 'string' ) ;
402+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/comments.xml"' ) ;
403+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsExtended.xml"' ) ;
404+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsIds.xml"' ) ;
405+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsExtensible.xml"' ) ;
406+ } ) ;
407+
408+ it ( 'removes stale comment files in original-file export path when null sentinels are provided' , async ( ) => {
409+ const zipper = new DocxZipper ( ) ;
410+ const originalZip = new JSZip ( ) ;
411+ originalZip . file ( '[Content_Types].xml' , contentTypesWithComments ) ;
412+ originalZip . file (
413+ 'word/document.xml' ,
414+ '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>' ,
415+ ) ;
416+ originalZip . file (
417+ 'word/comments.xml' ,
418+ '<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>' ,
419+ ) ;
420+ originalZip . file (
421+ 'word/commentsExtended.xml' ,
422+ '<w15:commentsEx xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"/>' ,
423+ ) ;
424+ originalZip . file (
425+ 'word/commentsIds.xml' ,
426+ '<w16cid:commentsIds xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"/>' ,
427+ ) ;
428+ originalZip . file (
429+ 'word/commentsExtensible.xml' ,
430+ '<w16cex:commentsExtensible xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"/>' ,
431+ ) ;
432+ const originalDocxFile = await originalZip . generateAsync ( { type : 'nodebuffer' } ) ;
433+
434+ const result = await zipper . updateZip ( {
435+ docx : [ ] ,
436+ updatedDocs : updatedDocsWithCommentDeletes ,
437+ originalDocxFile,
438+ media : { } ,
439+ fonts : { } ,
440+ isHeadless : true ,
441+ } ) ;
442+
443+ const readBack = await new JSZip ( ) . loadAsync ( result ) ;
444+ expect ( readBack . file ( 'word/comments.xml' ) ) . toBeNull ( ) ;
445+ expect ( readBack . file ( 'word/commentsExtended.xml' ) ) . toBeNull ( ) ;
446+ expect ( readBack . file ( 'word/commentsIds.xml' ) ) . toBeNull ( ) ;
447+ expect ( readBack . file ( 'word/commentsExtensible.xml' ) ) . toBeNull ( ) ;
448+
449+ const updatedContentTypes = await readBack . file ( '[Content_Types].xml' ) . async ( 'string' ) ;
450+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/comments.xml"' ) ;
451+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsExtended.xml"' ) ;
452+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsIds.xml"' ) ;
453+ expect ( updatedContentTypes ) . not . toContain ( 'PartName="/word/commentsExtensible.xml"' ) ;
454+ } ) ;
455+ } ) ;
0 commit comments