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,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests for WordDocumentProcessor
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: These tests use REAL docxmlater library, NOT mocks!
|
|
5
|
+
* Tests work with actual DOCX files in ./fixtures/ directory.
|
|
6
|
+
*
|
|
7
|
+
* Coverage target: 90%+ of WordDocumentProcessor.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { vi, describe, it, expect, beforeEach, type Mocked } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
WordDocumentProcessor,
|
|
13
|
+
WordProcessingOptions,
|
|
14
|
+
WordProcessingResult,
|
|
15
|
+
} from '../WordDocumentProcessor';
|
|
16
|
+
import { Document } from 'docxmlater';
|
|
17
|
+
import { promises as fs } from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { hyperlinkService } from '../../HyperlinkService';
|
|
20
|
+
|
|
21
|
+
// Mock ONLY external dependencies, NOT docxmlater
|
|
22
|
+
vi.mock('../../HyperlinkService');
|
|
23
|
+
vi.mock('@/utils/MemoryMonitor', () => ({
|
|
24
|
+
MemoryMonitor: {
|
|
25
|
+
checkMemory: vi.fn(),
|
|
26
|
+
forceGarbageCollection: vi.fn(),
|
|
27
|
+
logMemoryUsage: vi.fn(),
|
|
28
|
+
compareCheckpoints: vi.fn(),
|
|
29
|
+
getMemoryStats: vi.fn().mockReturnValue({
|
|
30
|
+
heapUsed: 100,
|
|
31
|
+
heapTotal: 200,
|
|
32
|
+
rss: 300,
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
vi.mock('@/utils/logger', () => ({
|
|
37
|
+
logger: {
|
|
38
|
+
namespace: vi.fn().mockReturnValue({
|
|
39
|
+
info: vi.fn(),
|
|
40
|
+
debug: vi.fn(),
|
|
41
|
+
warn: vi.fn(),
|
|
42
|
+
error: vi.fn(),
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
startTimer: vi.fn().mockReturnValue({
|
|
46
|
+
end: vi.fn().mockReturnValue(0),
|
|
47
|
+
elapsed: vi.fn().mockReturnValue(0),
|
|
48
|
+
}),
|
|
49
|
+
debugModes: {
|
|
50
|
+
DOCUMENT_PROCESSING: 'debug:documentProcessing',
|
|
51
|
+
SESSION_STATE: 'debug:sessionState',
|
|
52
|
+
IPC_CALLS: 'debug:ipcCalls',
|
|
53
|
+
DATABASE: 'debug:database',
|
|
54
|
+
HYPERLINKS: 'debug:hyperlinks',
|
|
55
|
+
BACKUPS: 'debug:backups',
|
|
56
|
+
LIST_PROCESSING: 'debug:listProcessing',
|
|
57
|
+
},
|
|
58
|
+
isDebugEnabled: vi.fn().mockReturnValue(false),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
62
|
+
|
|
63
|
+
describe('WordDocumentProcessor - Integration Tests', () => {
|
|
64
|
+
let processor: WordDocumentProcessor;
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks();
|
|
68
|
+
processor = new WordDocumentProcessor();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Document Loading & Validation', () => {
|
|
72
|
+
it('should successfully load and process a valid DOCX file', async () => {
|
|
73
|
+
const filePath = path.join(fixturesDir, 'sample.docx');
|
|
74
|
+
|
|
75
|
+
const result = await processor.processDocument(filePath);
|
|
76
|
+
|
|
77
|
+
// Show actual errors in assertion diff if processing fails
|
|
78
|
+
expect(result.errorMessages).toEqual([]);
|
|
79
|
+
expect(result.success).toBe(true);
|
|
80
|
+
expect(result.errorCount).toBe(0);
|
|
81
|
+
expect(result).toMatchObject({
|
|
82
|
+
success: true,
|
|
83
|
+
totalHyperlinks: expect.any(Number),
|
|
84
|
+
processedHyperlinks: expect.any(Number),
|
|
85
|
+
skippedHyperlinks: expect.any(Number),
|
|
86
|
+
processedLinks: expect.any(Array),
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should reject files exceeding size limit', async () => {
|
|
91
|
+
const filePath = path.join(fixturesDir, 'sample.docx');
|
|
92
|
+
|
|
93
|
+
vi.spyOn(fs, 'stat').mockResolvedValue({
|
|
94
|
+
size: 200 * 1024 * 1024, // 200MB
|
|
95
|
+
} as any);
|
|
96
|
+
|
|
97
|
+
const result = await processor.processDocument(filePath, {
|
|
98
|
+
maxFileSizeMB: 100,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(result.success).toBe(false);
|
|
102
|
+
expect(result.errorMessages.length).toBeGreaterThan(0);
|
|
103
|
+
expect(result.errorMessages[0]).toContain('File too large');
|
|
104
|
+
expect(result.errorMessages[0]).toContain('200.00MB');
|
|
105
|
+
|
|
106
|
+
// Restore fs.stat
|
|
107
|
+
vi.restoreAllMocks();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle corrupt DOCX files gracefully', async () => {
|
|
111
|
+
const filePath = path.join(fixturesDir, 'corrupt.docx');
|
|
112
|
+
|
|
113
|
+
const result = await processor.processDocument(filePath);
|
|
114
|
+
|
|
115
|
+
expect(result.success).toBe(false);
|
|
116
|
+
expect(result.errorMessages.length).toBeGreaterThan(0);
|
|
117
|
+
// Error message should indicate loading failure
|
|
118
|
+
expect(result.errorMessages[0]).toMatch(/load|invalid|corrupt|format/i);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should create backup before processing when requested', async () => {
|
|
122
|
+
const filePath = path.join(fixturesDir, 'sample.docx');
|
|
123
|
+
|
|
124
|
+
const copyFileSpy = vi.spyOn(fs, 'copyFile').mockResolvedValue(undefined);
|
|
125
|
+
|
|
126
|
+
const result = await processor.processDocument(filePath, {
|
|
127
|
+
createBackup: true,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(copyFileSpy).toHaveBeenCalled();
|
|
131
|
+
const [sourcePath, backupPath] = copyFileSpy.mock.calls[0];
|
|
132
|
+
expect(sourcePath).toBe(filePath);
|
|
133
|
+
expect(backupPath).toContain('Backup');
|
|
134
|
+
expect(result.backupPath).toBeDefined();
|
|
135
|
+
|
|
136
|
+
vi.restoreAllMocks();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle file not found errors', async () => {
|
|
140
|
+
const result = await processor.processDocument('/nonexistent/path/fake.docx');
|
|
141
|
+
|
|
142
|
+
expect(result.success).toBe(false);
|
|
143
|
+
expect(result.errorMessages.length).toBeGreaterThan(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('Hyperlink Extraction', () => {
|
|
148
|
+
it('should extract all hyperlinks from document', async () => {
|
|
149
|
+
const filePath = path.join(fixturesDir, 'hyperlinks.docx');
|
|
150
|
+
|
|
151
|
+
const result = await processor.processDocument(filePath);
|
|
152
|
+
|
|
153
|
+
expect(result.success).toBe(true);
|
|
154
|
+
expect(result.totalHyperlinks).toBeGreaterThanOrEqual(0);
|
|
155
|
+
|
|
156
|
+
// Verify hyperlink structure in processedLinks
|
|
157
|
+
expect(result.processedLinks).toBeInstanceOf(Array);
|
|
158
|
+
|
|
159
|
+
if (result.processedLinks.length > 0) {
|
|
160
|
+
const link = result.processedLinks[0];
|
|
161
|
+
expect(link).toHaveProperty('id');
|
|
162
|
+
expect(link).toHaveProperty('url');
|
|
163
|
+
expect(link).toHaveProperty('displayText');
|
|
164
|
+
expect(link).toHaveProperty('type');
|
|
165
|
+
expect(link).toHaveProperty('status');
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should extract hyperlinks with correct URLs and text', async () => {
|
|
170
|
+
const filePath = path.join(fixturesDir, 'hyperlinks.docx');
|
|
171
|
+
|
|
172
|
+
// Load document directly to verify
|
|
173
|
+
const doc = await Document.load(filePath);
|
|
174
|
+
const hyperlinksData = doc.getHyperlinks();
|
|
175
|
+
|
|
176
|
+
// Fixture may have zero hyperlinks — verify structure if any exist
|
|
177
|
+
expect(hyperlinksData).toBeInstanceOf(Array);
|
|
178
|
+
|
|
179
|
+
for (const { hyperlink, paragraph } of hyperlinksData) {
|
|
180
|
+
expect(hyperlink.getUrl()).toBeTruthy();
|
|
181
|
+
expect(hyperlink.getText()).toBeTruthy();
|
|
182
|
+
expect(paragraph.getText()).toBeTruthy();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Clean up
|
|
186
|
+
doc.dispose();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should handle documents with no hyperlinks', async () => {
|
|
190
|
+
const filePath = path.join(fixturesDir, 'sample.docx');
|
|
191
|
+
|
|
192
|
+
const result = await processor.processDocument(filePath);
|
|
193
|
+
|
|
194
|
+
expect(result.success).toBe(true);
|
|
195
|
+
expect(result.totalHyperlinks).toBe(0);
|
|
196
|
+
expect(result.processedHyperlinks).toBe(0);
|
|
197
|
+
expect(result.processedLinks).toHaveLength(0);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('Content ID Appending', () => {
|
|
202
|
+
it('should append content ID to theSource URLs', async () => {
|
|
203
|
+
const filePath = path.join(fixturesDir, 'theSource.docx');
|
|
204
|
+
|
|
205
|
+
const result = await processor.processDocument(filePath, {
|
|
206
|
+
operations: { fixContentIds: true },
|
|
207
|
+
contentId: '#test-content',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(result.success).toBe(true);
|
|
211
|
+
|
|
212
|
+
// Should have modified some URLs
|
|
213
|
+
if (result.appendedContentIds !== undefined) {
|
|
214
|
+
expect(result.appendedContentIds).toBeGreaterThanOrEqual(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check processedLinks for modifications
|
|
218
|
+
const modifiedLinks = result.processedLinks.filter(
|
|
219
|
+
(l) => l.status === 'processed' && l.after
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// If content IDs were appended, verify they contain the ID
|
|
223
|
+
if (result.appendedContentIds && result.appendedContentIds > 0) {
|
|
224
|
+
expect(modifiedLinks.length).toBeGreaterThan(0);
|
|
225
|
+
|
|
226
|
+
for (const link of modifiedLinks) {
|
|
227
|
+
if (link.after && link.after.includes('thesource')) {
|
|
228
|
+
expect(link.after).toContain('#test-content');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should skip URLs that already have content IDs', async () => {
|
|
235
|
+
const filePath = path.join(fixturesDir, 'theSource-with-ids.docx');
|
|
236
|
+
|
|
237
|
+
const result = await processor.processDocument(filePath, {
|
|
238
|
+
operations: { fixContentIds: true },
|
|
239
|
+
contentId: '#test-content',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(result.success).toBe(true);
|
|
243
|
+
|
|
244
|
+
// These URLs already have content IDs, should be skipped
|
|
245
|
+
expect(result.skippedHyperlinks).toBeGreaterThanOrEqual(0);
|
|
246
|
+
|
|
247
|
+
// Should NOT append to URLs that already have IDs
|
|
248
|
+
if (result.appendedContentIds !== undefined) {
|
|
249
|
+
expect(result.appendedContentIds).toBe(0);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should handle edge case URLs gracefully', async () => {
|
|
254
|
+
const filePath = path.join(fixturesDir, 'theSource-malformed.docx');
|
|
255
|
+
|
|
256
|
+
const result = await processor.processDocument(filePath, {
|
|
257
|
+
operations: { fixContentIds: true },
|
|
258
|
+
contentId: '#test',
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Should not crash on edge cases
|
|
262
|
+
expect(result.success).toBe(true);
|
|
263
|
+
expect(result.errorMessages).not.toContain(/crash|exception/i);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('Custom Replacements', () => {
|
|
268
|
+
it('should apply custom URL replacements', async () => {
|
|
269
|
+
const filePath = path.join(fixturesDir, 'hyperlinks.docx');
|
|
270
|
+
|
|
271
|
+
const result = await processor.processDocument(filePath, {
|
|
272
|
+
customReplacements: [
|
|
273
|
+
{
|
|
274
|
+
find: 'example.com',
|
|
275
|
+
replace: 'new-example.com',
|
|
276
|
+
matchType: 'contains',
|
|
277
|
+
applyTo: 'url',
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(result.success).toBe(true);
|
|
283
|
+
|
|
284
|
+
// Check if replacements were attempted
|
|
285
|
+
if (result.updatedUrls !== undefined) {
|
|
286
|
+
expect(result.updatedUrls).toBeGreaterThanOrEqual(0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check processedLinks for URL modifications
|
|
290
|
+
const urlReplacements = result.processedLinks.filter(
|
|
291
|
+
(l) => l.before && l.after && l.before !== l.after && l.after.includes('new-example.com')
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (result.updatedUrls && result.updatedUrls > 0) {
|
|
295
|
+
expect(urlReplacements.length).toBeGreaterThan(0);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should apply custom text replacements', async () => {
|
|
300
|
+
const filePath = path.join(fixturesDir, 'hyperlinks.docx');
|
|
301
|
+
|
|
302
|
+
const result = await processor.processDocument(filePath, {
|
|
303
|
+
customReplacements: [
|
|
304
|
+
{
|
|
305
|
+
find: 'GitHub',
|
|
306
|
+
replace: 'GitLab',
|
|
307
|
+
matchType: 'exact',
|
|
308
|
+
applyTo: 'text',
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
expect(result.success).toBe(true);
|
|
314
|
+
|
|
315
|
+
// Text replacements should be tracked
|
|
316
|
+
if (result.updatedDisplayTexts !== undefined) {
|
|
317
|
+
expect(result.updatedDisplayTexts).toBeGreaterThanOrEqual(0);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('Batch Processing', () => {
|
|
323
|
+
it('should process multiple documents concurrently', async () => {
|
|
324
|
+
const files = [
|
|
325
|
+
path.join(fixturesDir, 'sample.docx'),
|
|
326
|
+
path.join(fixturesDir, 'hyperlinks.docx'),
|
|
327
|
+
path.join(fixturesDir, 'theSource.docx'),
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
const batchResult = await processor.batchProcess(files, {}, 2);
|
|
331
|
+
|
|
332
|
+
expect(batchResult.totalFiles).toBe(3);
|
|
333
|
+
expect(batchResult.successfulFiles).toBe(3);
|
|
334
|
+
expect(batchResult.failedFiles).toBe(0);
|
|
335
|
+
expect(batchResult.results).toHaveLength(3);
|
|
336
|
+
|
|
337
|
+
// Each result should be valid
|
|
338
|
+
for (const item of batchResult.results) {
|
|
339
|
+
expect(item.result.success).toBe(true);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should handle individual file failures in batch', async () => {
|
|
344
|
+
const files = [
|
|
345
|
+
path.join(fixturesDir, 'sample.docx'),
|
|
346
|
+
path.join(fixturesDir, 'corrupt.docx'), // This will fail
|
|
347
|
+
path.join(fixturesDir, 'hyperlinks.docx'),
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
const batchResult = await processor.batchProcess(files, {}, 2);
|
|
351
|
+
|
|
352
|
+
expect(batchResult.totalFiles).toBe(3);
|
|
353
|
+
expect(batchResult.successfulFiles).toBe(2);
|
|
354
|
+
expect(batchResult.failedFiles).toBe(1);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should call progress callback for each file', async () => {
|
|
358
|
+
const files = [
|
|
359
|
+
path.join(fixturesDir, 'sample.docx'),
|
|
360
|
+
path.join(fixturesDir, 'hyperlinks.docx'),
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
const progressCallback = vi.fn();
|
|
364
|
+
|
|
365
|
+
await processor.batchProcess(files, {}, 1, progressCallback);
|
|
366
|
+
|
|
367
|
+
expect(progressCallback).toHaveBeenCalledTimes(2);
|
|
368
|
+
expect(progressCallback).toHaveBeenCalledWith(
|
|
369
|
+
files[0],
|
|
370
|
+
1,
|
|
371
|
+
2,
|
|
372
|
+
expect.objectContaining({ success: true })
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should respect concurrency limit', async () => {
|
|
377
|
+
const files = Array(5).fill(path.join(fixturesDir, 'sample.docx'));
|
|
378
|
+
|
|
379
|
+
const startTime = Date.now();
|
|
380
|
+
await processor.batchProcess(files, {}, 2); // Max 2 concurrent
|
|
381
|
+
const duration = Date.now() - startTime;
|
|
382
|
+
|
|
383
|
+
// With concurrency of 2, should take roughly 3 batches (2+2+1)
|
|
384
|
+
// Just verify it completes without errors
|
|
385
|
+
expect(duration).toBeGreaterThan(0);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('Memory Management', () => {
|
|
390
|
+
it('should trigger garbage collection periodically in batch processing', async () => {
|
|
391
|
+
const files = Array(15).fill(path.join(fixturesDir, 'sample.docx'));
|
|
392
|
+
|
|
393
|
+
// Mock global.gc
|
|
394
|
+
global.gc = vi.fn();
|
|
395
|
+
|
|
396
|
+
await processor.batchProcess(files, {}, 3);
|
|
397
|
+
|
|
398
|
+
// Should trigger GC at least once (every 10 documents)
|
|
399
|
+
if (global.gc) {
|
|
400
|
+
expect(global.gc).toHaveBeenCalled();
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should clean up resources after processing', async () => {
|
|
405
|
+
const filePath = path.join(fixturesDir, 'sample.docx');
|
|
406
|
+
|
|
407
|
+
const result = await processor.processDocument(filePath);
|
|
408
|
+
|
|
409
|
+
expect(result.success).toBe(true);
|
|
410
|
+
expect(result.processingTimeMs).toBeDefined();
|
|
411
|
+
expect(result.processingTimeMs).toBeGreaterThan(0);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('PowerAutomate API Integration', () => {
|
|
416
|
+
it('should process hyperlinks with PowerAutomate API', async () => {
|
|
417
|
+
const filePath = path.join(fixturesDir, 'theSource.docx');
|
|
418
|
+
|
|
419
|
+
// Mock API response
|
|
420
|
+
const mockApiResponse = {
|
|
421
|
+
success: true,
|
|
422
|
+
body: {
|
|
423
|
+
results: [
|
|
424
|
+
{
|
|
425
|
+
contentId: 'TSRC-ABC-123456',
|
|
426
|
+
documentId: 'uuid-123',
|
|
427
|
+
title: 'Test Document',
|
|
428
|
+
status: 'active',
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
(hyperlinkService.processHyperlinksWithApi as ReturnType<typeof vi.fn>).mockResolvedValue(mockApiResponse);
|
|
435
|
+
|
|
436
|
+
const result = await processor.processDocument(filePath, {
|
|
437
|
+
apiEndpoint: 'https://api.example.com',
|
|
438
|
+
operations: { updateTitles: true },
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// If document has theSource hyperlinks, API should be called
|
|
442
|
+
// If not, processing still succeeds but API is not invoked
|
|
443
|
+
if (result.totalHyperlinks > 0) {
|
|
444
|
+
expect(hyperlinkService.processHyperlinksWithApi).toHaveBeenCalled();
|
|
445
|
+
}
|
|
446
|
+
expect(result.success).toBe(true);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should handle API failures gracefully', async () => {
|
|
450
|
+
const filePath = path.join(fixturesDir, 'theSource.docx');
|
|
451
|
+
|
|
452
|
+
(hyperlinkService.processHyperlinksWithApi as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
453
|
+
success: false,
|
|
454
|
+
error: 'API timeout',
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const result = await processor.processDocument(filePath, {
|
|
458
|
+
apiEndpoint: 'https://api.example.com',
|
|
459
|
+
operations: { updateTitles: true },
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// If document has theSource hyperlinks, API failure causes processing failure
|
|
463
|
+
// If no hyperlinks, processing succeeds without API call
|
|
464
|
+
if (result.totalHyperlinks > 0) {
|
|
465
|
+
expect(result.success).toBe(false);
|
|
466
|
+
expect(result.errorMessages.some((msg) => msg.includes('PowerAutomate'))).toBe(true);
|
|
467
|
+
} else {
|
|
468
|
+
expect(result.success).toBe(true);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should fail when API endpoint not configured', async () => {
|
|
473
|
+
const filePath = path.join(fixturesDir, 'theSource.docx');
|
|
474
|
+
|
|
475
|
+
const result = await processor.processDocument(filePath, {
|
|
476
|
+
operations: { updateTitles: true },
|
|
477
|
+
// No apiEndpoint provided
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// If document has theSource hyperlinks, missing API endpoint causes failure
|
|
481
|
+
// If no hyperlinks, processing succeeds
|
|
482
|
+
if (result.totalHyperlinks > 0) {
|
|
483
|
+
expect(result.success).toBe(false);
|
|
484
|
+
expect(result.errorMessages[0]).toContain('API endpoint not configured');
|
|
485
|
+
} else {
|
|
486
|
+
expect(result.success).toBe(true);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe('Error Handling', () => {
|
|
492
|
+
it('should handle processing errors gracefully', async () => {
|
|
493
|
+
const result = await processor.processDocument('');
|
|
494
|
+
|
|
495
|
+
expect(result.success).toBe(false);
|
|
496
|
+
expect(result.errorMessages.length).toBeGreaterThan(0);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('should return proper error structure on failure', async () => {
|
|
500
|
+
const result = await processor.processDocument('/invalid/path.docx');
|
|
501
|
+
|
|
502
|
+
expect(result).toMatchObject({
|
|
503
|
+
success: false,
|
|
504
|
+
errorCount: expect.any(Number),
|
|
505
|
+
errorMessages: expect.any(Array),
|
|
506
|
+
totalHyperlinks: expect.any(Number),
|
|
507
|
+
processedHyperlinks: expect.any(Number),
|
|
508
|
+
processedLinks: expect.any(Array),
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
expect(result.errorCount).toBeGreaterThan(0);
|
|
512
|
+
expect(result.errorMessages.length).toBeGreaterThan(0);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
});
|