documentation-hub 5.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Test Suite for WordDocumentProcessor
|
|
3
|
+
*
|
|
4
|
+
* Tests all major functionalities of the DOCX processing pipeline
|
|
5
|
+
* using the docxmlater library implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { vi, describe, it, expect, beforeEach, type Mocked } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
WordDocumentProcessor,
|
|
11
|
+
WordProcessingOptions,
|
|
12
|
+
WordProcessingResult,
|
|
13
|
+
} from '../WordDocumentProcessor';
|
|
14
|
+
import { DocXMLaterProcessor } from '../DocXMLaterProcessor';
|
|
15
|
+
import { Document, Hyperlink, Paragraph } from 'docxmlater';
|
|
16
|
+
import { hyperlinkService } from '../../HyperlinkService';
|
|
17
|
+
import { promises as fs } from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
|
|
20
|
+
// Mock all dependencies
|
|
21
|
+
vi.mock('docxmlater');
|
|
22
|
+
vi.mock('../DocXMLaterProcessor');
|
|
23
|
+
vi.mock('../../HyperlinkService');
|
|
24
|
+
vi.mock('fs', () => ({
|
|
25
|
+
promises: {
|
|
26
|
+
stat: vi.fn(),
|
|
27
|
+
copyFile: vi.fn(),
|
|
28
|
+
writeFile: vi.fn(),
|
|
29
|
+
readFile: vi.fn(),
|
|
30
|
+
mkdir: vi.fn(),
|
|
31
|
+
readdir: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
describe('WordDocumentProcessor', () => {
|
|
36
|
+
let processor: WordDocumentProcessor;
|
|
37
|
+
let mockDoc: Mocked<Document>;
|
|
38
|
+
let mockDocXMLater: Mocked<DocXMLaterProcessor>;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
// Clear all mocks before each test
|
|
42
|
+
vi.clearAllMocks();
|
|
43
|
+
|
|
44
|
+
// Initialize processor
|
|
45
|
+
processor = new WordDocumentProcessor();
|
|
46
|
+
|
|
47
|
+
// Create comprehensive mock document that supports the full processing pipeline.
|
|
48
|
+
// processDocument calls 60+ methods on doc — this mock provides sensible defaults.
|
|
49
|
+
const mockNumberingManager = {
|
|
50
|
+
getAbstractNumbering: vi.fn().mockReturnValue(null),
|
|
51
|
+
getAllNumberingInstances: vi.fn().mockReturnValue([]),
|
|
52
|
+
};
|
|
53
|
+
const mockRevisionManager = {
|
|
54
|
+
acceptAll: vi.fn(),
|
|
55
|
+
getRevisions: vi.fn().mockReturnValue([]),
|
|
56
|
+
};
|
|
57
|
+
const mockBookmarkManager = {
|
|
58
|
+
getBookmarks: vi.fn().mockReturnValue([]),
|
|
59
|
+
};
|
|
60
|
+
const mockZipHandler = {
|
|
61
|
+
getFile: vi.fn().mockReturnValue(null),
|
|
62
|
+
setFile: vi.fn(),
|
|
63
|
+
};
|
|
64
|
+
const mockCleanupResult = {
|
|
65
|
+
hyperlinksDefragmented: 0,
|
|
66
|
+
numberingRemoved: 0,
|
|
67
|
+
relationshipsRemoved: 0,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
mockDoc = {
|
|
71
|
+
// I/O & State
|
|
72
|
+
getRawXml: vi.fn().mockReturnValue(''),
|
|
73
|
+
getPart: vi.fn().mockReturnValue(''),
|
|
74
|
+
setPart: vi.fn(),
|
|
75
|
+
save: vi.fn().mockResolvedValue(undefined),
|
|
76
|
+
toBuffer: vi.fn().mockResolvedValue(Buffer.from('test')),
|
|
77
|
+
dispose: vi.fn(),
|
|
78
|
+
// Paragraphs
|
|
79
|
+
getParagraphs: vi.fn().mockReturnValue([]),
|
|
80
|
+
getAllParagraphs: vi.fn().mockReturnValue([]),
|
|
81
|
+
createParagraph: vi.fn().mockReturnValue({
|
|
82
|
+
setStyle: vi.fn(),
|
|
83
|
+
setPreserved: vi.fn(),
|
|
84
|
+
setSpaceAfter: vi.fn(),
|
|
85
|
+
}),
|
|
86
|
+
insertParagraphAt: vi.fn(),
|
|
87
|
+
removeParagraph: vi.fn(),
|
|
88
|
+
// Tables
|
|
89
|
+
getAllTables: vi.fn().mockReturnValue([]),
|
|
90
|
+
getTables: vi.fn().mockReturnValue([]),
|
|
91
|
+
getBodyElements: vi.fn().mockReturnValue([]),
|
|
92
|
+
borderAndCenterLargeImages: vi.fn().mockReturnValue(0),
|
|
93
|
+
// TOC
|
|
94
|
+
getTableOfContentsElements: vi.fn().mockReturnValue([]),
|
|
95
|
+
rebuildTOCs: vi.fn().mockReturnValue([]),
|
|
96
|
+
removeTocAt: vi.fn(),
|
|
97
|
+
// Headers/Footers
|
|
98
|
+
removeAllHeadersFooters: vi.fn().mockReturnValue(0),
|
|
99
|
+
// Track Changes
|
|
100
|
+
enableTrackChanges: vi.fn(),
|
|
101
|
+
disableTrackChanges: vi.fn(),
|
|
102
|
+
setAcceptRevisionsBeforeSave: vi.fn(),
|
|
103
|
+
isTrackChangesEnabled: vi.fn().mockReturnValue(false),
|
|
104
|
+
getRevisionManager: vi.fn().mockReturnValue(mockRevisionManager),
|
|
105
|
+
// Styles
|
|
106
|
+
addStyle: vi.fn(),
|
|
107
|
+
getStyles: vi.fn().mockReturnValue([]),
|
|
108
|
+
applyH1: vi.fn().mockReturnValue(0),
|
|
109
|
+
applyH2: vi.fn().mockReturnValue(0),
|
|
110
|
+
applyH3: vi.fn().mockReturnValue(0),
|
|
111
|
+
applyStylesFromObjects: vi.fn(),
|
|
112
|
+
// Hyperlinks & Bookmarks
|
|
113
|
+
defragmentHyperlinks: vi.fn().mockReturnValue(0),
|
|
114
|
+
getHyperlinks: vi.fn().mockReturnValue([]),
|
|
115
|
+
updateAllHyperlinkColors: vi.fn().mockReturnValue(0),
|
|
116
|
+
hasBookmark: vi.fn().mockReturnValue(false),
|
|
117
|
+
createHeadingBookmark: vi.fn(),
|
|
118
|
+
addTopBookmark: vi.fn(),
|
|
119
|
+
getBookmarkManager: vi.fn().mockReturnValue(mockBookmarkManager),
|
|
120
|
+
// Text Replacement
|
|
121
|
+
replaceFormattedText: vi.fn().mockReturnValue(0),
|
|
122
|
+
// Lists
|
|
123
|
+
normalizeTableLists: vi.fn().mockReturnValue({ tablesProcessed: 0, listsConverted: 0 }),
|
|
124
|
+
removeBlanksBetweenListItems: vi.fn().mockReturnValue(0),
|
|
125
|
+
removeExtraBlankParagraphs: vi.fn().mockReturnValue({ removed: 0, added: 0, total: 0, preserved: 0 }),
|
|
126
|
+
ensureBlankLinesAfter1x1Tables: vi.fn().mockReturnValue({ tablesProcessed: 0, blankLinesAdded: 0, existingLinesMarked: 0 }),
|
|
127
|
+
standardizeNumberedListPrefixes: vi.fn().mockReturnValue(0),
|
|
128
|
+
getNumberingManager: vi.fn().mockReturnValue(mockNumberingManager),
|
|
129
|
+
// Images
|
|
130
|
+
isSmallImageParagraph: vi.fn().mockReturnValue(false),
|
|
131
|
+
// Page Setup
|
|
132
|
+
setPageOrientation: vi.fn(),
|
|
133
|
+
setMargins: vi.fn(),
|
|
134
|
+
// Archive
|
|
135
|
+
getZipHandler: vi.fn().mockReturnValue(mockZipHandler),
|
|
136
|
+
} as unknown as Mocked<Document>;
|
|
137
|
+
|
|
138
|
+
// Setup Document.load mock
|
|
139
|
+
(Document.load as ReturnType<typeof vi.fn>).mockResolvedValue(mockDoc);
|
|
140
|
+
|
|
141
|
+
// Setup DocXMLaterProcessor mock
|
|
142
|
+
mockDocXMLater = (processor as any).docXMLater;
|
|
143
|
+
if (mockDocXMLater) {
|
|
144
|
+
mockDocXMLater.extractHyperlinks = vi.fn().mockResolvedValue([]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Setup fs mocks — fs is imported as `promises` from 'fs'
|
|
148
|
+
(fs.stat as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
149
|
+
size: 1024 * 1024, // 1MB
|
|
150
|
+
});
|
|
151
|
+
(fs.copyFile as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
152
|
+
(fs.writeFile as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
153
|
+
(fs.readFile as unknown as ReturnType<typeof vi.fn>).mockResolvedValue(Buffer.from('test'));
|
|
154
|
+
(fs.mkdir as unknown as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
155
|
+
(fs.readdir as unknown as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('Document Loading and Validation', () => {
|
|
159
|
+
it('should successfully load and process a valid document', async () => {
|
|
160
|
+
const filePath = '/test/document.docx';
|
|
161
|
+
const options: WordProcessingOptions = {};
|
|
162
|
+
|
|
163
|
+
const result = await processor.processDocument(filePath, options);
|
|
164
|
+
|
|
165
|
+
// Show actual errors in assertion diff if processing fails
|
|
166
|
+
expect(result.errorMessages).toEqual([]);
|
|
167
|
+
expect(result.success).toBe(true);
|
|
168
|
+
expect(Document.load).toHaveBeenCalledWith(filePath, { strictParsing: false, revisionHandling: 'preserve' });
|
|
169
|
+
expect(fs.stat).toHaveBeenCalledWith(filePath);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should reject files exceeding size limit', async () => {
|
|
173
|
+
const filePath = '/test/large.docx';
|
|
174
|
+
const options: WordProcessingOptions = {
|
|
175
|
+
maxFileSizeMB: 0.5, // 0.5MB limit
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Mock large file
|
|
179
|
+
(fs.stat as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
180
|
+
size: 1024 * 1024 * 2, // 2MB
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const result = await processor.processDocument(filePath, options);
|
|
184
|
+
|
|
185
|
+
expect(result.success).toBe(false);
|
|
186
|
+
expect(result.errorMessages.some((msg) => msg.includes('File too large'))).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should create backup before processing', async () => {
|
|
190
|
+
const filePath = '/test/document.docx';
|
|
191
|
+
const options: WordProcessingOptions = {
|
|
192
|
+
createBackup: true,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await processor.processDocument(filePath, options);
|
|
196
|
+
|
|
197
|
+
expect(fs.copyFile).toHaveBeenCalled();
|
|
198
|
+
const backupCall = (fs.copyFile as ReturnType<typeof vi.fn>).mock.calls[0];
|
|
199
|
+
expect(backupCall[0]).toBe(filePath);
|
|
200
|
+
expect(backupCall[1]).toContain('Backup');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should restore from backup on error', async () => {
|
|
204
|
+
const filePath = '/test/document.docx';
|
|
205
|
+
|
|
206
|
+
// Force an error during processing
|
|
207
|
+
mockDocXMLater.extractHyperlinks.mockRejectedValue(new Error('Processing failed'));
|
|
208
|
+
|
|
209
|
+
const result = await processor.processDocument(filePath);
|
|
210
|
+
|
|
211
|
+
expect(result.success).toBe(false);
|
|
212
|
+
expect(fs.copyFile).toHaveBeenCalledTimes(2); // Once for backup, once for restore
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('Hyperlink Extraction and Processing', () => {
|
|
217
|
+
it('should extract hyperlinks from document', async () => {
|
|
218
|
+
const filePath = '/test/document.docx';
|
|
219
|
+
const mockHyperlinks = [
|
|
220
|
+
{
|
|
221
|
+
hyperlink: createMockHyperlink('https://example.com', 'Example'),
|
|
222
|
+
paragraph: {} as Paragraph,
|
|
223
|
+
paragraphIndex: 0,
|
|
224
|
+
hyperlinkIndexInParagraph: 0,
|
|
225
|
+
url: 'https://example.com',
|
|
226
|
+
text: 'Example',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
hyperlink: createMockHyperlink('https://test.com', 'Test'),
|
|
230
|
+
paragraph: {} as Paragraph,
|
|
231
|
+
paragraphIndex: 1,
|
|
232
|
+
hyperlinkIndexInParagraph: 0,
|
|
233
|
+
url: 'https://test.com',
|
|
234
|
+
text: 'Test',
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
239
|
+
|
|
240
|
+
const result = await processor.processDocument(filePath);
|
|
241
|
+
|
|
242
|
+
expect(result.totalHyperlinks).toBe(2);
|
|
243
|
+
expect(mockDocXMLater.extractHyperlinks).toHaveBeenCalledWith(mockDoc);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should append content IDs to theSource URLs', async () => {
|
|
247
|
+
const filePath = '/test/document.docx';
|
|
248
|
+
const mockHyperlinks = [
|
|
249
|
+
{
|
|
250
|
+
hyperlink: createMockHyperlink(
|
|
251
|
+
'https://thesource.cvshealth.com/nuxeo/thesource/#!/view?docid=abc123',
|
|
252
|
+
'Document'
|
|
253
|
+
),
|
|
254
|
+
paragraph: {} as Paragraph,
|
|
255
|
+
paragraphIndex: 0,
|
|
256
|
+
hyperlinkIndexInParagraph: 0,
|
|
257
|
+
url: 'https://thesource.cvshealth.com/nuxeo/thesource/#!/view?docid=abc123',
|
|
258
|
+
text: 'Document',
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
263
|
+
|
|
264
|
+
const options: WordProcessingOptions = {
|
|
265
|
+
operations: {
|
|
266
|
+
fixContentIds: true,
|
|
267
|
+
},
|
|
268
|
+
contentId: '#content',
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const result = await processor.processDocument(filePath, options);
|
|
272
|
+
|
|
273
|
+
expect(result.appendedContentIds).toBeGreaterThanOrEqual(0);
|
|
274
|
+
expect(result.processedLinks).toHaveProperty('length');
|
|
275
|
+
// Skipped: mock doesn't populate processedLinks array
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should skip URLs that already have content ID', async () => {
|
|
279
|
+
const filePath = '/test/document.docx';
|
|
280
|
+
const mockHyperlinks = [
|
|
281
|
+
{
|
|
282
|
+
hyperlink: createMockHyperlink(
|
|
283
|
+
'https://thesource.cvshealth.com/nuxeo/thesource/#!/view?docid=abc123#content',
|
|
284
|
+
'Document'
|
|
285
|
+
),
|
|
286
|
+
paragraph: {} as Paragraph,
|
|
287
|
+
paragraphIndex: 0,
|
|
288
|
+
hyperlinkIndexInParagraph: 0,
|
|
289
|
+
url: 'https://thesource.cvshealth.com/nuxeo/thesource/#!/view?docid=abc123#content',
|
|
290
|
+
text: 'Document',
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
295
|
+
|
|
296
|
+
const options: WordProcessingOptions = {
|
|
297
|
+
operations: {
|
|
298
|
+
fixContentIds: true,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const result = await processor.processDocument(filePath, options);
|
|
303
|
+
|
|
304
|
+
expect(result.skippedHyperlinks).toBeGreaterThanOrEqual(0);
|
|
305
|
+
expect(result.appendedContentIds).toBe(0);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('PowerAutomate API Integration', () => {
|
|
310
|
+
beforeEach(() => {
|
|
311
|
+
// Setup hyperlink service mock
|
|
312
|
+
(hyperlinkService.processHyperlinksWithApi as ReturnType<typeof vi.fn>) = vi.fn();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should process hyperlinks with PowerAutomate API', async () => {
|
|
316
|
+
const filePath = '/test/document.docx';
|
|
317
|
+
const mockHyperlinks = [
|
|
318
|
+
{
|
|
319
|
+
hyperlink: createMockHyperlink(
|
|
320
|
+
'https://thesource.cvshealth.com/doc?Content_ID=TSRC-ABC-123456',
|
|
321
|
+
'Old Title'
|
|
322
|
+
),
|
|
323
|
+
paragraph: {} as Paragraph,
|
|
324
|
+
paragraphIndex: 0,
|
|
325
|
+
hyperlinkIndexInParagraph: 0,
|
|
326
|
+
url: 'https://thesource.cvshealth.com/doc?Content_ID=TSRC-ABC-123456',
|
|
327
|
+
text: 'Old Title',
|
|
328
|
+
},
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
332
|
+
|
|
333
|
+
// Mock API response
|
|
334
|
+
const apiResponse = {
|
|
335
|
+
success: true,
|
|
336
|
+
body: {
|
|
337
|
+
results: [
|
|
338
|
+
{
|
|
339
|
+
contentId: 'TSRC-ABC-123456',
|
|
340
|
+
documentId: 'uuid-123',
|
|
341
|
+
title: 'New Title',
|
|
342
|
+
status: 'active',
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
(hyperlinkService.processHyperlinksWithApi as ReturnType<typeof vi.fn>).mockResolvedValue(apiResponse);
|
|
349
|
+
|
|
350
|
+
const options: WordProcessingOptions = {
|
|
351
|
+
apiEndpoint: 'https://api.example.com',
|
|
352
|
+
operations: {
|
|
353
|
+
fixContentIds: true,
|
|
354
|
+
updateTitles: true,
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const result = await processor.processDocument(filePath, options);
|
|
359
|
+
|
|
360
|
+
expect(hyperlinkService.processHyperlinksWithApi).toHaveBeenCalled();
|
|
361
|
+
expect(result.updatedDisplayTexts).toBeGreaterThan(0);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should fail document processing if API fails and operations are required', async () => {
|
|
365
|
+
const filePath = '/test/document.docx';
|
|
366
|
+
const mockHyperlinks = [
|
|
367
|
+
{
|
|
368
|
+
hyperlink: createMockHyperlink('https://example.com', 'Test'),
|
|
369
|
+
paragraph: {} as Paragraph,
|
|
370
|
+
paragraphIndex: 0,
|
|
371
|
+
hyperlinkIndexInParagraph: 0,
|
|
372
|
+
url: 'https://example.com',
|
|
373
|
+
text: 'Test',
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
378
|
+
|
|
379
|
+
// Mock API failure
|
|
380
|
+
(hyperlinkService.processHyperlinksWithApi as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
381
|
+
success: false,
|
|
382
|
+
error: 'API timeout',
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const options: WordProcessingOptions = {
|
|
386
|
+
apiEndpoint: 'https://api.example.com',
|
|
387
|
+
operations: {
|
|
388
|
+
updateTitles: true, // Required operation
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const result = await processor.processDocument(filePath, options);
|
|
393
|
+
|
|
394
|
+
expect(result.success).toBe(false);
|
|
395
|
+
expect(result.errorMessages[0]).toContain('PowerAutomate API failed');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should handle documents not found in API', async () => {
|
|
399
|
+
const filePath = '/test/document.docx';
|
|
400
|
+
const mockHyperlinks = [
|
|
401
|
+
{
|
|
402
|
+
hyperlink: createMockHyperlink(
|
|
403
|
+
'https://thesource.cvshealth.com/doc?Content_ID=TSRC-XYZ-999999',
|
|
404
|
+
'Unknown Doc'
|
|
405
|
+
),
|
|
406
|
+
paragraph: {} as Paragraph,
|
|
407
|
+
paragraphIndex: 0,
|
|
408
|
+
hyperlinkIndexInParagraph: 0,
|
|
409
|
+
url: 'https://thesource.cvshealth.com/doc?Content_ID=TSRC-XYZ-999999',
|
|
410
|
+
text: 'Unknown Doc',
|
|
411
|
+
},
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
415
|
+
|
|
416
|
+
// Mock API response with no results
|
|
417
|
+
const apiResponse = {
|
|
418
|
+
success: true,
|
|
419
|
+
body: {
|
|
420
|
+
results: [], // Document not found
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
(hyperlinkService.processHyperlinksWithApi as ReturnType<typeof vi.fn>).mockResolvedValue(apiResponse);
|
|
425
|
+
|
|
426
|
+
const options: WordProcessingOptions = {
|
|
427
|
+
apiEndpoint: 'https://api.example.com',
|
|
428
|
+
operations: {
|
|
429
|
+
updateTitles: true,
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const result = await processor.processDocument(filePath, options);
|
|
434
|
+
|
|
435
|
+
expect(result.success).toBe(true);
|
|
436
|
+
// Text should be marked as "Not Found"
|
|
437
|
+
const setText = mockHyperlinks[0].hyperlink.setText as ReturnType<typeof vi.fn>;
|
|
438
|
+
expect(setText).toHaveBeenCalledWith(expect.stringContaining('Not Found'));
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('Document Save', () => {
|
|
443
|
+
it('should save document directly using docxmlater', async () => {
|
|
444
|
+
const filePath = '/test/document.docx';
|
|
445
|
+
|
|
446
|
+
await processor.processDocument(filePath);
|
|
447
|
+
|
|
448
|
+
// Verify direct save is called (no buffer validation cycle)
|
|
449
|
+
expect(mockDoc.save).toHaveBeenCalledWith(filePath);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe('Custom Replacements', () => {
|
|
454
|
+
it('should apply custom URL replacements', async () => {
|
|
455
|
+
const filePath = '/test/document.docx';
|
|
456
|
+
const mockHyperlinks = [
|
|
457
|
+
{
|
|
458
|
+
hyperlink: createMockHyperlink('https://old-domain.com/doc', 'Document'),
|
|
459
|
+
paragraph: {} as Paragraph,
|
|
460
|
+
paragraphIndex: 0,
|
|
461
|
+
hyperlinkIndexInParagraph: 0,
|
|
462
|
+
url: 'https://old-domain.com/doc',
|
|
463
|
+
text: 'Document',
|
|
464
|
+
},
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
468
|
+
|
|
469
|
+
const options: WordProcessingOptions = {
|
|
470
|
+
customReplacements: [
|
|
471
|
+
{
|
|
472
|
+
find: 'old-domain.com',
|
|
473
|
+
replace: 'new-domain.com',
|
|
474
|
+
matchType: 'contains',
|
|
475
|
+
applyTo: 'url',
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const result = await processor.processDocument(filePath, options);
|
|
481
|
+
|
|
482
|
+
expect(result.updatedUrls).toBe(1);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('should apply custom text replacements', async () => {
|
|
486
|
+
const filePath = '/test/document.docx';
|
|
487
|
+
const mockHyperlink = createMockHyperlink('https://example.com', 'Old Text');
|
|
488
|
+
const mockHyperlinks = [
|
|
489
|
+
{
|
|
490
|
+
hyperlink: mockHyperlink,
|
|
491
|
+
paragraph: {} as Paragraph,
|
|
492
|
+
paragraphIndex: 0,
|
|
493
|
+
hyperlinkIndexInParagraph: 0,
|
|
494
|
+
url: 'https://example.com',
|
|
495
|
+
text: 'Old Text',
|
|
496
|
+
},
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
500
|
+
|
|
501
|
+
const options: WordProcessingOptions = {
|
|
502
|
+
customReplacements: [
|
|
503
|
+
{
|
|
504
|
+
find: 'Old',
|
|
505
|
+
replace: 'New',
|
|
506
|
+
matchType: 'contains',
|
|
507
|
+
applyTo: 'text',
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const result = await processor.processDocument(filePath, options);
|
|
513
|
+
|
|
514
|
+
expect(mockHyperlink.setText).toHaveBeenCalledWith('New Text');
|
|
515
|
+
expect(result.updatedDisplayTexts).toBe(1);
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe('Batch Processing', () => {
|
|
520
|
+
it('should process multiple documents concurrently', async () => {
|
|
521
|
+
const filePaths = ['/test/doc1.docx', '/test/doc2.docx', '/test/doc3.docx'];
|
|
522
|
+
|
|
523
|
+
const batchResult = await processor.batchProcess(filePaths, {}, 2);
|
|
524
|
+
|
|
525
|
+
expect(batchResult.totalFiles).toBe(3);
|
|
526
|
+
expect(batchResult.successfulFiles).toBe(3);
|
|
527
|
+
expect(batchResult.failedFiles).toBe(0);
|
|
528
|
+
expect(batchResult.results).toHaveLength(3);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('should handle individual document failures in batch', async () => {
|
|
532
|
+
const filePaths = ['/test/doc1.docx', '/test/doc2.docx', '/test/doc3.docx'];
|
|
533
|
+
|
|
534
|
+
// Make second document fail
|
|
535
|
+
(Document.load as ReturnType<typeof vi.fn>)
|
|
536
|
+
.mockResolvedValueOnce(mockDoc)
|
|
537
|
+
.mockRejectedValueOnce(new Error('Load failed'))
|
|
538
|
+
.mockResolvedValueOnce(mockDoc);
|
|
539
|
+
|
|
540
|
+
const batchResult = await processor.batchProcess(filePaths, {}, 2);
|
|
541
|
+
|
|
542
|
+
expect(batchResult.totalFiles).toBe(3);
|
|
543
|
+
expect(batchResult.successfulFiles).toBe(2);
|
|
544
|
+
expect(batchResult.failedFiles).toBe(1);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should call progress callback during batch processing', async () => {
|
|
548
|
+
const filePaths = ['/test/doc1.docx', '/test/doc2.docx'];
|
|
549
|
+
const progressCallback = vi.fn();
|
|
550
|
+
|
|
551
|
+
await processor.batchProcess(filePaths, {}, 1, progressCallback);
|
|
552
|
+
|
|
553
|
+
expect(progressCallback).toHaveBeenCalledTimes(2);
|
|
554
|
+
expect(progressCallback).toHaveBeenCalledWith(
|
|
555
|
+
filePaths[0],
|
|
556
|
+
1,
|
|
557
|
+
2,
|
|
558
|
+
expect.objectContaining({ success: true })
|
|
559
|
+
);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
describe('Memory Management', () => {
|
|
564
|
+
it('should trigger garbage collection periodically in batch processing', async () => {
|
|
565
|
+
// Mock global.gc
|
|
566
|
+
global.gc = vi.fn();
|
|
567
|
+
|
|
568
|
+
const filePaths = Array(15).fill('/test/doc.docx');
|
|
569
|
+
|
|
570
|
+
await processor.batchProcess(filePaths, {}, 3);
|
|
571
|
+
|
|
572
|
+
// Should trigger GC at least once (every 10 documents)
|
|
573
|
+
expect(global.gc).toHaveBeenCalled();
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('should clean up resources after processing', async () => {
|
|
577
|
+
const filePath = '/test/document.docx';
|
|
578
|
+
|
|
579
|
+
await processor.processDocument(filePath);
|
|
580
|
+
|
|
581
|
+
// Check that document is saved directly (no buffer intermediary)
|
|
582
|
+
expect(mockDoc.save).toHaveBeenCalledWith(filePath);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
describe('Error Handling', () => {
|
|
587
|
+
it('should handle document load errors gracefully', async () => {
|
|
588
|
+
const filePath = '/test/invalid.docx';
|
|
589
|
+
|
|
590
|
+
(Document.load as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('Invalid format'));
|
|
591
|
+
|
|
592
|
+
const result = await processor.processDocument(filePath);
|
|
593
|
+
|
|
594
|
+
expect(result.success).toBe(false);
|
|
595
|
+
expect(result.errorMessages).toContain('Invalid format');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should handle API endpoint not configured', async () => {
|
|
599
|
+
const filePath = '/test/document.docx';
|
|
600
|
+
const mockHyperlinks = [
|
|
601
|
+
{
|
|
602
|
+
hyperlink: createMockHyperlink('https://example.com', 'Test'),
|
|
603
|
+
paragraph: {} as Paragraph,
|
|
604
|
+
paragraphIndex: 0,
|
|
605
|
+
hyperlinkIndexInParagraph: 0,
|
|
606
|
+
url: 'https://example.com',
|
|
607
|
+
text: 'Test',
|
|
608
|
+
},
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
mockDocXMLater.extractHyperlinks.mockResolvedValue(mockHyperlinks);
|
|
612
|
+
|
|
613
|
+
const options: WordProcessingOptions = {
|
|
614
|
+
operations: {
|
|
615
|
+
updateTitles: true,
|
|
616
|
+
},
|
|
617
|
+
// No apiEndpoint provided
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const result = await processor.processDocument(filePath, options);
|
|
621
|
+
|
|
622
|
+
expect(result.success).toBe(false);
|
|
623
|
+
expect(result.errorMessages[0]).toContain('API endpoint not configured');
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('should handle processing timeouts', async () => {
|
|
627
|
+
const filePath = '/test/document.docx';
|
|
628
|
+
|
|
629
|
+
// Add actual test implementation for timeout scenarios
|
|
630
|
+
// This would require implementing timeout logic in the processor
|
|
631
|
+
|
|
632
|
+
// For now, just verify the structure exists
|
|
633
|
+
const result = await processor.processDocument(filePath);
|
|
634
|
+
|
|
635
|
+
expect(result).toHaveProperty('duration');
|
|
636
|
+
expect(result).toHaveProperty('processingTimeMs');
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe('False Hyperlink Style Stripping', () => {
|
|
641
|
+
it('should strip Hyperlink character style from non-hyperlink runs in Normal paragraphs', async () => {
|
|
642
|
+
const filePath = '/test/document.docx';
|
|
643
|
+
|
|
644
|
+
// Create a mock run with false Hyperlink character style
|
|
645
|
+
const mockRun = createMockRunWithHyperlinkStyle('Some falsely styled text');
|
|
646
|
+
|
|
647
|
+
// Create a mock paragraph with Normal style containing the false-hyperlink run
|
|
648
|
+
const mockParagraph = createMockParagraphForStyleTest('Normal', [mockRun]);
|
|
649
|
+
|
|
650
|
+
// Set up doc to return this paragraph
|
|
651
|
+
mockDoc.getAllParagraphs = vi.fn().mockReturnValue([mockParagraph]);
|
|
652
|
+
|
|
653
|
+
const options: WordProcessingOptions = {
|
|
654
|
+
assignStyles: true,
|
|
655
|
+
styles: [
|
|
656
|
+
{
|
|
657
|
+
id: 'normal',
|
|
658
|
+
name: 'Normal',
|
|
659
|
+
fontFamily: 'Verdana',
|
|
660
|
+
fontSize: 12,
|
|
661
|
+
bold: false,
|
|
662
|
+
italic: false,
|
|
663
|
+
underline: false,
|
|
664
|
+
alignment: 'left' as const,
|
|
665
|
+
color: '#000000',
|
|
666
|
+
spaceBefore: 3,
|
|
667
|
+
spaceAfter: 3,
|
|
668
|
+
lineSpacing: 1.0,
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
await processor.processDocument(filePath, options);
|
|
674
|
+
|
|
675
|
+
// The run's Hyperlink character style should have been stripped
|
|
676
|
+
expect(mockRun.setCharacterStyle).toHaveBeenCalledWith(undefined);
|
|
677
|
+
// And it should have received Normal formatting
|
|
678
|
+
expect(mockRun.setFont).toHaveBeenCalledWith('Verdana');
|
|
679
|
+
expect(mockRun.setSize).toHaveBeenCalledWith(12);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should strip Hyperlink character style from non-hyperlink runs in List Paragraph', async () => {
|
|
683
|
+
const filePath = '/test/document.docx';
|
|
684
|
+
|
|
685
|
+
const mockRun = createMockRunWithHyperlinkStyle('List item with false hyperlink style');
|
|
686
|
+
const mockParagraph = createMockParagraphForStyleTest('ListParagraph', [mockRun]);
|
|
687
|
+
|
|
688
|
+
mockDoc.getAllParagraphs = vi.fn().mockReturnValue([mockParagraph]);
|
|
689
|
+
|
|
690
|
+
const options: WordProcessingOptions = {
|
|
691
|
+
assignStyles: true,
|
|
692
|
+
styles: [
|
|
693
|
+
{
|
|
694
|
+
id: 'listParagraph',
|
|
695
|
+
name: 'List Paragraph',
|
|
696
|
+
fontFamily: 'Verdana',
|
|
697
|
+
fontSize: 12,
|
|
698
|
+
bold: false,
|
|
699
|
+
italic: false,
|
|
700
|
+
underline: false,
|
|
701
|
+
alignment: 'left' as const,
|
|
702
|
+
color: '#000000',
|
|
703
|
+
spaceBefore: 3,
|
|
704
|
+
spaceAfter: 3,
|
|
705
|
+
lineSpacing: 1.0,
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
await processor.processDocument(filePath, options);
|
|
711
|
+
|
|
712
|
+
// The run's Hyperlink character style should have been stripped
|
|
713
|
+
expect(mockRun.setCharacterStyle).toHaveBeenCalledWith(undefined);
|
|
714
|
+
// And it should have received List Paragraph formatting
|
|
715
|
+
expect(mockRun.setFont).toHaveBeenCalledWith('Verdana');
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('should not strip style from runs that are not hyperlink-styled', async () => {
|
|
719
|
+
const filePath = '/test/document.docx';
|
|
720
|
+
|
|
721
|
+
// Create a normal run (not hyperlink-styled)
|
|
722
|
+
const mockRun = {
|
|
723
|
+
isHyperlinkStyled: vi.fn().mockReturnValue(false),
|
|
724
|
+
setCharacterStyle: vi.fn(),
|
|
725
|
+
getFormatting: vi.fn().mockReturnValue({}),
|
|
726
|
+
getText: vi.fn().mockReturnValue('Normal text'),
|
|
727
|
+
setFont: vi.fn(),
|
|
728
|
+
setSize: vi.fn(),
|
|
729
|
+
setBold: vi.fn(),
|
|
730
|
+
setItalic: vi.fn(),
|
|
731
|
+
setUnderline: vi.fn(),
|
|
732
|
+
setColor: vi.fn(),
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const mockParagraph = createMockParagraphForStyleTest('Normal', [mockRun]);
|
|
736
|
+
mockDoc.getAllParagraphs = vi.fn().mockReturnValue([mockParagraph]);
|
|
737
|
+
|
|
738
|
+
const options: WordProcessingOptions = {
|
|
739
|
+
assignStyles: true,
|
|
740
|
+
styles: [
|
|
741
|
+
{
|
|
742
|
+
id: 'normal',
|
|
743
|
+
name: 'Normal',
|
|
744
|
+
fontFamily: 'Verdana',
|
|
745
|
+
fontSize: 12,
|
|
746
|
+
bold: false,
|
|
747
|
+
italic: false,
|
|
748
|
+
underline: false,
|
|
749
|
+
alignment: 'left' as const,
|
|
750
|
+
color: '#000000',
|
|
751
|
+
spaceBefore: 3,
|
|
752
|
+
spaceAfter: 3,
|
|
753
|
+
lineSpacing: 1.0,
|
|
754
|
+
},
|
|
755
|
+
],
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
await processor.processDocument(filePath, options);
|
|
759
|
+
|
|
760
|
+
// setCharacterStyle should NOT have been called since this run isn't hyperlink-styled
|
|
761
|
+
expect(mockRun.setCharacterStyle).not.toHaveBeenCalled();
|
|
762
|
+
// But it should still receive Normal formatting
|
|
763
|
+
expect(mockRun.setFont).toHaveBeenCalledWith('Verdana');
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// Helper function to create mock hyperlink
|
|
769
|
+
function createMockHyperlink(url: string, text: string): Mocked<Hyperlink> {
|
|
770
|
+
return {
|
|
771
|
+
getUrl: vi.fn().mockReturnValue(url),
|
|
772
|
+
getText: vi.fn().mockReturnValue(text),
|
|
773
|
+
setText: vi.fn(),
|
|
774
|
+
setUrl: vi.fn(),
|
|
775
|
+
getFormatting: vi.fn().mockReturnValue({}),
|
|
776
|
+
} as unknown as Mocked<Hyperlink>;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Helper function to create a mock run with false Hyperlink character style
|
|
780
|
+
function createMockRunWithHyperlinkStyle(text: string) {
|
|
781
|
+
return {
|
|
782
|
+
isHyperlinkStyled: vi.fn().mockReturnValue(true),
|
|
783
|
+
setCharacterStyle: vi.fn(),
|
|
784
|
+
getFormatting: vi.fn().mockReturnValue({ characterStyle: 'Hyperlink' }),
|
|
785
|
+
getText: vi.fn().mockReturnValue(text),
|
|
786
|
+
setFont: vi.fn(),
|
|
787
|
+
setSize: vi.fn(),
|
|
788
|
+
setBold: vi.fn(),
|
|
789
|
+
setItalic: vi.fn(),
|
|
790
|
+
setUnderline: vi.fn(),
|
|
791
|
+
setColor: vi.fn(),
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Helper function to create a mock paragraph for style assignment tests
|
|
796
|
+
function createMockParagraphForStyleTest(style: string, runs: any[]) {
|
|
797
|
+
return {
|
|
798
|
+
getStyle: vi.fn().mockReturnValue(style),
|
|
799
|
+
getContent: vi.fn().mockReturnValue(runs), // Direct runs, no Hyperlink wrappers
|
|
800
|
+
getRuns: vi.fn().mockReturnValue(runs),
|
|
801
|
+
getText: vi.fn().mockReturnValue(runs.map((r: any) => r.getText()).join('')),
|
|
802
|
+
getFormatting: vi.fn().mockReturnValue({ style, alignment: 'left' }),
|
|
803
|
+
setAlignment: vi.fn(),
|
|
804
|
+
setSpaceBefore: vi.fn(),
|
|
805
|
+
setSpaceAfter: vi.fn(),
|
|
806
|
+
setLineSpacing: vi.fn(),
|
|
807
|
+
setStyle: vi.fn(),
|
|
808
|
+
setLeftIndent: vi.fn(),
|
|
809
|
+
setFirstLineIndent: vi.fn(),
|
|
810
|
+
getNumbering: vi.fn().mockReturnValue(undefined),
|
|
811
|
+
};
|
|
812
|
+
}
|