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.
Files changed (271) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +64 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/Current.md +97 -0
  6. package/DocHub_Image.png +0 -0
  7. package/README.md +666 -0
  8. package/USER_GUIDE.md +1173 -0
  9. package/Updater.md +311 -0
  10. package/build/256x256.png +0 -0
  11. package/build/512x512.png +0 -0
  12. package/build/app-update.yml +4 -0
  13. package/build/create-icon.js +208 -0
  14. package/build/icon.ico +0 -0
  15. package/build/icon.png +0 -0
  16. package/build/icon_1024x1024.png +0 -0
  17. package/dist/assets/Analytics-BpsG9895.js +1 -0
  18. package/dist/assets/Card-IAZin8kp.js +1 -0
  19. package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
  20. package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
  21. package/dist/assets/Documents-CqZ25axS.js +1 -0
  22. package/dist/assets/Input-l89xwXBi.js +1 -0
  23. package/dist/assets/Reporting-DqdHJY_a.js +1 -0
  24. package/dist/assets/Search-XNbu5z_3.js +1 -0
  25. package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
  26. package/dist/assets/Sessions-ClZOPYNc.js +1 -0
  27. package/dist/assets/Settings-DUEHGURa.js +11 -0
  28. package/dist/assets/index-8xUe8ptc.js +24 -0
  29. package/dist/assets/index-RYyJqF7O.css +1 -0
  30. package/dist/assets/path-BkOl0AGO.js +1 -0
  31. package/dist/assets/promises-ID_B9S-h.js +1 -0
  32. package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
  33. package/dist/assets/useToast-yRSO1dkm.js +1 -0
  34. package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
  35. package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
  36. package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
  37. package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
  38. package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
  39. package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
  40. package/dist/electron/main-CXkNtyv-.js +19789 -0
  41. package/dist/electron/main.js +5 -0
  42. package/dist/electron/preload.js +1 -0
  43. package/dist/icon.png +0 -0
  44. package/dist/index.html +27 -0
  45. package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
  46. package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
  47. package/docs/README.md +115 -0
  48. package/docs/TOC_WIRING_GUIDE.md +344 -0
  49. package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
  50. package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
  51. package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
  52. package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
  53. package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
  54. package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
  55. package/docs/analysis/List_Implementation.md +206 -0
  56. package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
  57. package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
  58. package/docs/analysis/RefactorStyles.md +852 -0
  59. package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
  60. package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
  61. package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
  62. package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
  63. package/docs/analysis/github-issues-to-create.md +237 -0
  64. package/docs/api/API_README.md +538 -0
  65. package/docs/api/API_REFERENCE.md +751 -0
  66. package/docs/api/TYPE_DEFINITIONS.md +869 -0
  67. package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
  68. package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
  69. package/docs/docxmlater-readme.md +1341 -0
  70. package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
  71. package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
  72. package/docs/fixes/README.md +37 -0
  73. package/docs/github-issues/issue-1-body.md +125 -0
  74. package/docs/github-issues/issue-10-body.md +850 -0
  75. package/docs/github-issues/issue-2-body.md +200 -0
  76. package/docs/github-issues/issue-3-body.md +270 -0
  77. package/docs/github-issues/issue-4-body.md +169 -0
  78. package/docs/github-issues/issue-5-body.md +173 -0
  79. package/docs/github-issues/issue-6-body.md +158 -0
  80. package/docs/github-issues/issue-7-body.md +171 -0
  81. package/docs/github-issues/issue-8-body.md +407 -0
  82. package/docs/github-issues/issue-9-body.md +515 -0
  83. package/docs/github-issues/issue-tracker.md +274 -0
  84. package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
  85. package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
  86. package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
  87. package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
  88. package/docs/implementation/RefactorStyles.md +588 -0
  89. package/docs/implementation/implement-plan.md +489 -0
  90. package/docs/implementation/missing-helpers-implementation.md +391 -0
  91. package/docs/implementation/refactor-plan.md +520 -0
  92. package/docs/implementation/session-implementation-complete.md +233 -0
  93. package/docs/implementation/session-management-plan.md +250 -0
  94. package/docs/setup-checklist.md +77 -0
  95. package/docs/versions/changelog.md +345 -0
  96. package/electron/customUpdater.ts +656 -0
  97. package/electron/main.ts +2441 -0
  98. package/electron/memoryConfig.ts +187 -0
  99. package/electron/preload.ts +394 -0
  100. package/electron/proxyConfig.ts +340 -0
  101. package/electron/services/BackupService.ts +452 -0
  102. package/electron/services/DictionaryService.ts +402 -0
  103. package/electron/services/LocalDictionaryLookupService.ts +147 -0
  104. package/electron/services/PowerAutomateApiService.ts +231 -0
  105. package/electron/services/SharePointSyncService.ts +474 -0
  106. package/electron/windowsCertStore.ts +427 -0
  107. package/electron/zscalerConfig.ts +381 -0
  108. package/eslint.config.js +92 -0
  109. package/jest.config.js +52 -0
  110. package/package.json +214 -0
  111. package/postcss.config.mjs +6 -0
  112. package/public/icon.png +0 -0
  113. package/publish-release.ps1 +5 -0
  114. package/renovate.json +30 -0
  115. package/src/App.tsx +216 -0
  116. package/src/__mocks__/p-limit.js +12 -0
  117. package/src/__mocks__/styleMock.js +1 -0
  118. package/src/components/common/BugReportButton.tsx +44 -0
  119. package/src/components/common/BugReportDialog.tsx +193 -0
  120. package/src/components/common/Button.tsx +153 -0
  121. package/src/components/common/Card.tsx +86 -0
  122. package/src/components/common/ColorPickerDialog.tsx +177 -0
  123. package/src/components/common/ConfirmDialog.tsx +96 -0
  124. package/src/components/common/DebugConsole.tsx +275 -0
  125. package/src/components/common/EmptyState.tsx +183 -0
  126. package/src/components/common/ErrorBoundary.tsx +98 -0
  127. package/src/components/common/ErrorDetailsDialog.tsx +153 -0
  128. package/src/components/common/ErrorFallback.tsx +218 -0
  129. package/src/components/common/Input.tsx +109 -0
  130. package/src/components/common/Skeleton.tsx +184 -0
  131. package/src/components/common/SplashScreen.tsx +81 -0
  132. package/src/components/common/Toast.tsx +155 -0
  133. package/src/components/common/Tooltip.tsx +79 -0
  134. package/src/components/common/UpdateNotification.tsx +320 -0
  135. package/src/components/comparison/ComparisonWindow.tsx +374 -0
  136. package/src/components/comparison/SideBySideDiff.tsx +486 -0
  137. package/src/components/comparison/index.ts +8 -0
  138. package/src/components/document/DocumentUploader.tsx +288 -0
  139. package/src/components/document/HyperlinkPreview.tsx +430 -0
  140. package/src/components/document/HyperlinkService.md +1484 -0
  141. package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
  142. package/src/components/document/InlineChangesView.tsx +707 -0
  143. package/src/components/document/ProcessingProgress.tsx +303 -0
  144. package/src/components/document/ProcessingResults.tsx +256 -0
  145. package/src/components/document/TrackedChangesDetail.tsx +530 -0
  146. package/src/components/document/TrackedChangesPanel.tsx +546 -0
  147. package/src/components/document/VirtualDocumentList.tsx +240 -0
  148. package/src/components/editor/DocumentEditor.tsx +723 -0
  149. package/src/components/editor/DocumentEditorModal.tsx +640 -0
  150. package/src/components/editor/EditorQuickActions.tsx +502 -0
  151. package/src/components/editor/EditorToolbar.tsx +312 -0
  152. package/src/components/editor/TableEditor.tsx +926 -0
  153. package/src/components/editor/index.ts +18 -0
  154. package/src/components/layout/Header.tsx +190 -0
  155. package/src/components/layout/Sidebar.tsx +313 -0
  156. package/src/components/layout/TitleBar.tsx +190 -0
  157. package/src/components/navigation/CommandPalette.tsx +233 -0
  158. package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
  159. package/src/components/sessions/ChangeItem.tsx +408 -0
  160. package/src/components/sessions/ChangeViewer.tsx +1155 -0
  161. package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
  162. package/src/components/sessions/ProcessingOptions.tsx +297 -0
  163. package/src/components/sessions/ReplacementsTab.tsx +438 -0
  164. package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
  165. package/src/components/sessions/SessionManager.tsx +188 -0
  166. package/src/components/sessions/StylesEditor.tsx +1335 -0
  167. package/src/components/sessions/TabContainer.tsx +151 -0
  168. package/src/components/sessions/VirtualSessionList.tsx +157 -0
  169. package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
  170. package/src/components/settings/CertificateManager.tsx +410 -0
  171. package/src/components/settings/SegmentedControl.tsx +88 -0
  172. package/src/components/settings/SettingRow.tsx +52 -0
  173. package/src/contexts/GlobalStatsContext.tsx +396 -0
  174. package/src/contexts/SessionContext.tsx +2129 -0
  175. package/src/contexts/ThemeContext.tsx +428 -0
  176. package/src/contexts/UserSettingsContext.tsx +290 -0
  177. package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
  178. package/src/global.d.ts +273 -0
  179. package/src/hooks/useDocumentQueue.tsx +210 -0
  180. package/src/hooks/useToast.tsx +55 -0
  181. package/src/main.tsx +10 -0
  182. package/src/pages/Analytics.tsx +386 -0
  183. package/src/pages/CurrentSession.tsx +1174 -0
  184. package/src/pages/Dashboard.tsx +319 -0
  185. package/src/pages/Documents.tsx +317 -0
  186. package/src/pages/Projects.tsx +250 -0
  187. package/src/pages/Reporting.tsx +386 -0
  188. package/src/pages/Search.tsx +349 -0
  189. package/src/pages/Sessions.tsx +285 -0
  190. package/src/pages/Settings.tsx +2662 -0
  191. package/src/services/HyperlinkService.ts +1085 -0
  192. package/src/services/document/DocXMLaterProcessor.ts +617 -0
  193. package/src/services/document/DocumentProcessingComparison.ts +856 -0
  194. package/src/services/document/DocumentSnapshotService.ts +575 -0
  195. package/src/services/document/WordDocumentProcessor.ts +10509 -0
  196. package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
  197. package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
  198. package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
  199. package/src/services/document/blanklines/BlankLineManager.ts +658 -0
  200. package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
  201. package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
  202. package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
  203. package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
  204. package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
  205. package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
  206. package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
  207. package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
  208. package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
  209. package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
  210. package/src/services/document/blanklines/index.ts +67 -0
  211. package/src/services/document/blanklines/rules/additionRules.ts +337 -0
  212. package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
  213. package/src/services/document/blanklines/rules/removalRules.ts +362 -0
  214. package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
  215. package/src/services/document/blanklines/types.ts +29 -0
  216. package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
  217. package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
  218. package/src/services/document/helpers/whitespace.ts +117 -0
  219. package/src/services/document/list/ListNormalizer.ts +947 -0
  220. package/src/services/document/list/index.ts +45 -0
  221. package/src/services/document/list/list-detection.ts +275 -0
  222. package/src/services/document/list/list-types.ts +162 -0
  223. package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
  224. package/src/services/document/processors/ListProcessor.ts +257 -0
  225. package/src/services/document/processors/StructureProcessor.ts +176 -0
  226. package/src/services/document/processors/StyleProcessor.ts +389 -0
  227. package/src/services/document/processors/TableProcessor.ts +2238 -0
  228. package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
  229. package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
  230. package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
  231. package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
  232. package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
  233. package/src/services/document/processors/index.ts +28 -0
  234. package/src/services/document/types/docx-processing.ts +310 -0
  235. package/src/services/editor/EditorActionHandlers.ts +901 -0
  236. package/src/services/editor/index.ts +13 -0
  237. package/src/setupTests.ts +47 -0
  238. package/src/styles/global.css +782 -0
  239. package/src/types/backup.ts +132 -0
  240. package/src/types/dictionary.ts +125 -0
  241. package/src/types/document-processing.ts +331 -0
  242. package/src/types/docxmlater-augments.d.ts +142 -0
  243. package/src/types/editor.ts +280 -0
  244. package/src/types/electron.ts +340 -0
  245. package/src/types/globalStats.ts +155 -0
  246. package/src/types/hyperlink.ts +471 -0
  247. package/src/types/operations.ts +354 -0
  248. package/src/types/session.ts +427 -0
  249. package/src/types/settings.ts +112 -0
  250. package/src/utils/MemoryMonitor.ts +248 -0
  251. package/src/utils/cn.ts +6 -0
  252. package/src/utils/colorConvert.ts +306 -0
  253. package/src/utils/diffUtils.ts +347 -0
  254. package/src/utils/documentUtils.ts +202 -0
  255. package/src/utils/electronGuard.ts +62 -0
  256. package/src/utils/indexedDB.ts +915 -0
  257. package/src/utils/logger.ts +717 -0
  258. package/src/utils/pathSecurity.ts +232 -0
  259. package/src/utils/pathValidator.ts +236 -0
  260. package/src/utils/processingTimeEstimator.ts +153 -0
  261. package/src/utils/safeJsonParse.ts +62 -0
  262. package/src/utils/textSanitizer.ts +162 -0
  263. package/src/utils/urlHelpers.ts +304 -0
  264. package/src/utils/urlPatterns.ts +198 -0
  265. package/src/utils/urlSanitizer.ts +152 -0
  266. package/src/vite-env.d.ts +11 -0
  267. package/tsconfig.electron.json +19 -0
  268. package/tsconfig.json +36 -0
  269. package/tsconfig.node.json +12 -0
  270. package/typedoc.json +45 -0
  271. package/vite.config.ts +152 -0
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Test Suite for HyperlinkProcessor
3
+ *
4
+ * Tests hyperlink manipulation, URL updates, and custom replacements.
5
+ */
6
+
7
+ import { vi, describe, it, expect, beforeEach, type Mocked } from 'vitest';
8
+ import { HyperlinkProcessor } from '../HyperlinkProcessor';
9
+ import { Document, Hyperlink, Paragraph, Revision } from 'docxmlater';
10
+ import { DocXMLaterProcessor } from '../../DocXMLaterProcessor';
11
+
12
+ // Mock dependencies
13
+ vi.mock('docxmlater');
14
+ vi.mock('../../DocXMLaterProcessor');
15
+
16
+ describe('HyperlinkProcessor', () => {
17
+ let processor: HyperlinkProcessor;
18
+ let mockDoc: Mocked<Document>;
19
+ let mockDocXMLater: Mocked<DocXMLaterProcessor>;
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ processor = new HyperlinkProcessor();
24
+
25
+ // Create mock document
26
+ mockDoc = {
27
+ getAllParagraphs: vi.fn().mockReturnValue([]),
28
+ isTrackChangesEnabled: vi.fn().mockReturnValue(false),
29
+ getRevisionManager: vi.fn().mockReturnValue({
30
+ register: vi.fn(),
31
+ }),
32
+ hasBookmark: vi.fn().mockReturnValue(false),
33
+ } as unknown as Mocked<Document>;
34
+
35
+ // Setup DocXMLaterProcessor mock
36
+ mockDocXMLater = new DocXMLaterProcessor() as Mocked<DocXMLaterProcessor>;
37
+ mockDocXMLater.extractHyperlinks = vi.fn().mockResolvedValue([]);
38
+ (processor as any).docXMLater = mockDocXMLater;
39
+ });
40
+
41
+ describe('standardizeFormatting', () => {
42
+ it('should standardize hyperlink formatting to Verdana 12pt blue', async () => {
43
+ const mockHyperlink = createMockHyperlink('https://example.com', 'Test Link');
44
+ mockDocXMLater.extractHyperlinks.mockResolvedValue([
45
+ {
46
+ hyperlink: mockHyperlink,
47
+ paragraph: {} as Paragraph,
48
+ paragraphIndex: 0,
49
+ hyperlinkIndexInParagraph: 0,
50
+ url: 'https://example.com',
51
+ text: 'Test Link',
52
+ },
53
+ ]);
54
+
55
+ const count = await processor.standardizeFormatting(mockDoc);
56
+
57
+ expect(count).toBe(1);
58
+ expect(mockHyperlink.setFormatting).toHaveBeenCalledWith({
59
+ font: 'Verdana',
60
+ size: 12,
61
+ color: '0000FF',
62
+ underline: 'single',
63
+ bold: false,
64
+ italic: false,
65
+ });
66
+ });
67
+
68
+ it('should handle empty hyperlink list', async () => {
69
+ mockDocXMLater.extractHyperlinks.mockResolvedValue([]);
70
+
71
+ const count = await processor.standardizeFormatting(mockDoc);
72
+
73
+ expect(count).toBe(0);
74
+ });
75
+
76
+ it('should continue processing if one hyperlink fails', async () => {
77
+ const mockHyperlink1 = createMockHyperlink('https://example1.com', 'Link 1');
78
+ const mockHyperlink2 = createMockHyperlink('https://example2.com', 'Link 2');
79
+
80
+ // Make first hyperlink throw error
81
+ mockHyperlink1.setFormatting = vi.fn().mockImplementation(() => {
82
+ throw new Error('Format error');
83
+ });
84
+
85
+ mockDocXMLater.extractHyperlinks.mockResolvedValue([
86
+ {
87
+ hyperlink: mockHyperlink1,
88
+ paragraph: {} as Paragraph,
89
+ paragraphIndex: 0,
90
+ hyperlinkIndexInParagraph: 0,
91
+ url: 'https://example1.com',
92
+ text: 'Link 1',
93
+ },
94
+ {
95
+ hyperlink: mockHyperlink2,
96
+ paragraph: {} as Paragraph,
97
+ paragraphIndex: 1,
98
+ hyperlinkIndexInParagraph: 0,
99
+ url: 'https://example2.com',
100
+ text: 'Link 2',
101
+ },
102
+ ]);
103
+
104
+ const count = await processor.standardizeFormatting(mockDoc);
105
+
106
+ expect(count).toBe(1); // Only second one succeeded
107
+ expect(mockHyperlink2.setFormatting).toHaveBeenCalled();
108
+ });
109
+ });
110
+
111
+ describe('applyUrlUpdates', () => {
112
+ it('should update URLs in hyperlinks', async () => {
113
+ const mockHyperlink = createMockHyperlink('https://old-url.com', 'Link');
114
+ const mockParagraph = {
115
+ getContent: vi.fn().mockReturnValue([mockHyperlink]),
116
+ };
117
+
118
+ mockDoc.getAllParagraphs.mockReturnValue([mockParagraph as unknown as Paragraph]);
119
+
120
+ // Make mockHyperlink an instance of Hyperlink
121
+ Object.setPrototypeOf(mockHyperlink, Hyperlink.prototype);
122
+
123
+ const urlMap = new Map([['https://old-url.com', 'https://new-url.com']]);
124
+
125
+ const result = await processor.applyUrlUpdates(mockDoc, urlMap, 'TestAuthor');
126
+
127
+ expect(result.updated).toBe(1);
128
+ expect(result.failed).toHaveLength(0);
129
+ expect(mockHyperlink.setUrl).toHaveBeenCalledWith('https://new-url.com');
130
+ });
131
+
132
+ it('should skip identical URLs', async () => {
133
+ const mockHyperlink = createMockHyperlink('https://same-url.com', 'Link');
134
+ const mockParagraph = {
135
+ getContent: vi.fn().mockReturnValue([mockHyperlink]),
136
+ };
137
+
138
+ mockDoc.getAllParagraphs.mockReturnValue([mockParagraph as unknown as Paragraph]);
139
+ Object.setPrototypeOf(mockHyperlink, Hyperlink.prototype);
140
+
141
+ const urlMap = new Map([['https://same-url.com', 'https://same-url.com']]);
142
+
143
+ const result = await processor.applyUrlUpdates(mockDoc, urlMap);
144
+
145
+ expect(result.updated).toBe(0);
146
+ expect(mockHyperlink.setUrl).not.toHaveBeenCalled();
147
+ });
148
+
149
+ it('should return empty result for empty map', async () => {
150
+ const result = await processor.applyUrlUpdates(mockDoc, new Map());
151
+
152
+ expect(result.updated).toBe(0);
153
+ expect(result.failed).toHaveLength(0);
154
+ });
155
+
156
+ it('should track failed updates', async () => {
157
+ const mockHyperlink = createMockHyperlink('https://old-url.com', 'Link');
158
+ mockHyperlink.setUrl = vi.fn().mockImplementation(() => {
159
+ throw new Error('Update failed');
160
+ });
161
+
162
+ const mockParagraph = {
163
+ getContent: vi.fn().mockReturnValue([mockHyperlink]),
164
+ };
165
+
166
+ mockDoc.getAllParagraphs.mockReturnValue([mockParagraph as unknown as Paragraph]);
167
+ Object.setPrototypeOf(mockHyperlink, Hyperlink.prototype);
168
+
169
+ const urlMap = new Map([['https://old-url.com', 'https://new-url.com']]);
170
+
171
+ const result = await processor.applyUrlUpdates(mockDoc, urlMap);
172
+
173
+ expect(result.updated).toBe(0);
174
+ expect(result.failed).toHaveLength(1);
175
+ expect(result.failed[0].oldUrl).toBe('https://old-url.com');
176
+ });
177
+ });
178
+
179
+ describe('processCustomReplacements', () => {
180
+ it('should apply URL replacement with contains match', async () => {
181
+ const mockHyperlink = createMockHyperlink('https://old-domain.com/path', 'Link');
182
+ mockDocXMLater.extractHyperlinks.mockResolvedValue([
183
+ {
184
+ hyperlink: mockHyperlink,
185
+ paragraph: {} as Paragraph,
186
+ paragraphIndex: 0,
187
+ hyperlinkIndexInParagraph: 0,
188
+ url: 'https://old-domain.com/path',
189
+ text: 'Link',
190
+ },
191
+ ]);
192
+
193
+ const result = await processor.processCustomReplacements(mockDoc, [
194
+ {
195
+ find: 'old-domain',
196
+ replace: 'new-domain',
197
+ matchType: 'contains',
198
+ applyTo: 'url',
199
+ },
200
+ ]);
201
+
202
+ expect(result.updatedUrls).toBe(1);
203
+ expect(mockHyperlink.setUrl).toHaveBeenCalledWith('https://new-domain.com/path');
204
+ });
205
+
206
+ it('should apply text replacement with exact match', async () => {
207
+ const mockHyperlink = createMockHyperlink('https://example.com', 'Old Text');
208
+ mockDocXMLater.extractHyperlinks.mockResolvedValue([
209
+ {
210
+ hyperlink: mockHyperlink,
211
+ paragraph: {} as Paragraph,
212
+ paragraphIndex: 0,
213
+ hyperlinkIndexInParagraph: 0,
214
+ url: 'https://example.com',
215
+ text: 'Old Text',
216
+ },
217
+ ]);
218
+
219
+ const result = await processor.processCustomReplacements(mockDoc, [
220
+ {
221
+ find: 'Old Text',
222
+ replace: 'New Text',
223
+ matchType: 'exact',
224
+ applyTo: 'text',
225
+ },
226
+ ]);
227
+
228
+ expect(result.updatedTexts).toBe(1);
229
+ expect(mockHyperlink.setText).toHaveBeenCalledWith('New Text');
230
+ });
231
+
232
+ it('should apply replacement to both URL and text', async () => {
233
+ const mockHyperlink = createMockHyperlink('https://old.com', 'old link');
234
+ mockDocXMLater.extractHyperlinks.mockResolvedValue([
235
+ {
236
+ hyperlink: mockHyperlink,
237
+ paragraph: {} as Paragraph,
238
+ paragraphIndex: 0,
239
+ hyperlinkIndexInParagraph: 0,
240
+ url: 'https://old.com',
241
+ text: 'old link',
242
+ },
243
+ ]);
244
+
245
+ const result = await processor.processCustomReplacements(mockDoc, [
246
+ {
247
+ find: 'old',
248
+ replace: 'new',
249
+ matchType: 'contains',
250
+ applyTo: 'both',
251
+ },
252
+ ]);
253
+
254
+ expect(result.updatedUrls).toBe(1);
255
+ expect(result.updatedTexts).toBe(1);
256
+ });
257
+ });
258
+
259
+ describe('findMatchingApiResult', () => {
260
+ it('should match by Content_ID', () => {
261
+ const apiResultsMap = new Map([['TSRC-ABC-123456', { title: 'Found Doc' }]]);
262
+
263
+ const result = processor.findMatchingApiResult(
264
+ 'https://thesource.cvshealth.com/doc?Content_ID=TSRC-ABC-123456',
265
+ apiResultsMap
266
+ );
267
+
268
+ expect(result).toEqual({ title: 'Found Doc' });
269
+ });
270
+
271
+ it('should match by Document_ID (UUID)', () => {
272
+ const uuid = '12345678-1234-1234-1234-123456789abc';
273
+ const apiResultsMap = new Map([[uuid, { title: 'UUID Doc' }]]);
274
+
275
+ const result = processor.findMatchingApiResult(
276
+ `https://thesource.cvshealth.com/nuxeo/thesource/#!/view?docid=${uuid}`,
277
+ apiResultsMap
278
+ );
279
+
280
+ expect(result).toEqual({ title: 'UUID Doc' });
281
+ });
282
+
283
+ it('should return null for URLs without IDs', () => {
284
+ const apiResultsMap = new Map([['TSRC-ABC-123456', { title: 'Doc' }]]);
285
+
286
+ const result = processor.findMatchingApiResult('https://example.com/page', apiResultsMap);
287
+
288
+ expect(result).toBeNull();
289
+ });
290
+
291
+ it('should return null for empty map', () => {
292
+ const result = processor.findMatchingApiResult(
293
+ 'https://thesource.cvshealth.com/doc?Content_ID=TSRC-ABC-123456',
294
+ new Map()
295
+ );
296
+
297
+ expect(result).toBeNull();
298
+ });
299
+ });
300
+ });
301
+
302
+ // Helper function to create mock hyperlink
303
+ function createMockHyperlink(url: string, text: string): Mocked<Hyperlink> {
304
+ return {
305
+ getUrl: vi.fn().mockReturnValue(url),
306
+ getText: vi.fn().mockReturnValue(text),
307
+ setText: vi.fn(),
308
+ setUrl: vi.fn(),
309
+ setFormatting: vi.fn(),
310
+ getFormatting: vi.fn().mockReturnValue({}),
311
+ getAnchor: vi.fn().mockReturnValue(null),
312
+ clone: vi.fn(),
313
+ } as unknown as Mocked<Hyperlink>;
314
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Test Suite for ListProcessor
3
+ *
4
+ * Tests list bullet settings, indentation, and numbered list formatting.
5
+ */
6
+
7
+ import { vi, describe, it, expect, beforeEach, type Mocked } from 'vitest';
8
+ import { ListProcessor, ListBulletSettings } from '../ListProcessor';
9
+ import { Document, Paragraph } from 'docxmlater';
10
+
11
+ // Mock docxmlater
12
+ vi.mock('docxmlater');
13
+
14
+ describe('ListProcessor', () => {
15
+ let processor: ListProcessor;
16
+ let mockDoc: Mocked<Document>;
17
+
18
+ beforeEach(() => {
19
+ vi.clearAllMocks();
20
+ processor = new ListProcessor();
21
+
22
+ mockDoc = {
23
+ getAllParagraphs: vi.fn().mockReturnValue([]),
24
+ getPart: vi.fn().mockResolvedValue(null),
25
+ setPart: vi.fn().mockResolvedValue(undefined),
26
+ getNumberingManager: vi.fn().mockReturnValue(null),
27
+ } as unknown as Mocked<Document>;
28
+ });
29
+
30
+ describe('applyListIndentation', () => {
31
+ it('should apply indentation to list items', async () => {
32
+ const mockParagraph = createMockListParagraph(0, 1);
33
+ mockDoc.getAllParagraphs.mockReturnValue([mockParagraph]);
34
+
35
+ const settings: ListBulletSettings = {
36
+ enabled: true,
37
+ indentationLevels: [
38
+ { level: 0, symbolIndent: 0.5, textIndent: 0.75 },
39
+ { level: 1, symbolIndent: 1.0, textIndent: 1.25 },
40
+ ],
41
+ spacingBetweenItems: 6,
42
+ };
43
+
44
+ const result = await processor.applyListIndentation(mockDoc, settings);
45
+
46
+ expect(result.listsUpdated).toBe(1);
47
+ expect(mockParagraph.setLeftIndent).toHaveBeenCalled();
48
+ expect(mockParagraph.setSpaceAfter).toHaveBeenCalledWith(120); // 6 * 20 twips
49
+ });
50
+
51
+ it('should skip non-list paragraphs', async () => {
52
+ const mockParagraph = createMockNormalParagraph();
53
+ mockDoc.getAllParagraphs.mockReturnValue([mockParagraph]);
54
+
55
+ const settings: ListBulletSettings = {
56
+ enabled: true,
57
+ indentationLevels: [{ level: 0, symbolIndent: 0.5, textIndent: 0.75 }],
58
+ spacingBetweenItems: 0,
59
+ };
60
+
61
+ const result = await processor.applyListIndentation(mockDoc, settings);
62
+
63
+ expect(result.listsUpdated).toBe(0);
64
+ expect(mockParagraph.setLeftIndent).not.toHaveBeenCalled();
65
+ });
66
+
67
+ it('should return zero counts when disabled', async () => {
68
+ const settings: ListBulletSettings = {
69
+ enabled: false,
70
+ indentationLevels: [],
71
+ spacingBetweenItems: 0,
72
+ };
73
+
74
+ const result = await processor.applyListIndentation(mockDoc, settings);
75
+
76
+ expect(result.listsUpdated).toBe(0);
77
+ expect(result.levelsProcessed).toBe(0);
78
+ });
79
+
80
+ it('should handle multiple list levels', async () => {
81
+ const level0Para = createMockListParagraph(0, 1);
82
+ const level1Para = createMockListParagraph(1, 1);
83
+ const level2Para = createMockListParagraph(2, 1);
84
+
85
+ mockDoc.getAllParagraphs.mockReturnValue([level0Para, level1Para, level2Para]);
86
+
87
+ const settings: ListBulletSettings = {
88
+ enabled: true,
89
+ indentationLevels: [
90
+ { level: 0, symbolIndent: 0.5, textIndent: 0.75 },
91
+ { level: 1, symbolIndent: 1.0, textIndent: 1.25 },
92
+ { level: 2, symbolIndent: 1.5, textIndent: 1.75 },
93
+ ],
94
+ spacingBetweenItems: 0,
95
+ };
96
+
97
+ const result = await processor.applyListIndentation(mockDoc, settings);
98
+
99
+ expect(result.listsUpdated).toBe(3);
100
+ expect(level0Para.setLeftIndent).toHaveBeenCalled();
101
+ expect(level1Para.setLeftIndent).toHaveBeenCalled();
102
+ expect(level2Para.setLeftIndent).toHaveBeenCalled();
103
+ });
104
+ });
105
+
106
+ describe('standardizeListPrefixFormatting', () => {
107
+ it('should standardize list prefix formatting to Verdana', async () => {
108
+ const mockLevel = createMockNumberingLevel('Arial');
109
+ const mockAbstractNum = {
110
+ getLevel: vi.fn((idx: number) => (idx === 0 ? mockLevel : null)),
111
+ };
112
+ const mockNumberingManager = {
113
+ getAllAbstractNumberings: vi.fn().mockReturnValue([mockAbstractNum]),
114
+ };
115
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
116
+
117
+ const count = await processor.standardizeListPrefixFormatting(mockDoc);
118
+
119
+ expect(count).toBe(1);
120
+ expect(mockLevel.setFont).toHaveBeenCalledWith('Verdana');
121
+ expect(mockLevel.setColor).toHaveBeenCalledWith('000000');
122
+ expect(mockLevel.setFontSize).toHaveBeenCalledWith(24);
123
+ expect(mockLevel.setBold).toHaveBeenCalledWith(false);
124
+ });
125
+
126
+ it('should handle missing numbering manager', async () => {
127
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(null);
128
+
129
+ const count = await processor.standardizeListPrefixFormatting(mockDoc);
130
+
131
+ expect(count).toBe(0);
132
+ });
133
+
134
+ it('should preserve special bullet fonts', async () => {
135
+ const mockLevel = createMockNumberingLevel('Symbol');
136
+ const mockAbstractNum = {
137
+ getLevel: vi.fn((idx: number) => (idx === 0 ? mockLevel : null)),
138
+ };
139
+ const mockNumberingManager = {
140
+ getAllAbstractNumberings: vi.fn().mockReturnValue([mockAbstractNum]),
141
+ };
142
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
143
+
144
+ await processor.standardizeListPrefixFormatting(mockDoc);
145
+
146
+ // Symbol font should be preserved, not replaced with Verdana
147
+ expect(mockLevel.setFont).toHaveBeenCalledWith('Symbol');
148
+ expect(mockLevel.setBold).toHaveBeenCalledWith(false);
149
+ });
150
+ });
151
+
152
+ describe('isBulletList', () => {
153
+ it('should identify bullet lists', () => {
154
+ const mockAbstractNum = {
155
+ getLevel: vi.fn().mockReturnValue({
156
+ getFormat: vi.fn().mockReturnValue('bullet'),
157
+ }),
158
+ };
159
+ const mockInstance = {
160
+ getAbstractNumId: vi.fn().mockReturnValue(0),
161
+ };
162
+ const mockNumberingManager = {
163
+ getNumberingInstance: vi.fn().mockReturnValue(mockInstance),
164
+ getAbstractNumbering: vi.fn().mockReturnValue(mockAbstractNum),
165
+ };
166
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
167
+
168
+ const result = processor.isBulletList(mockDoc, 1);
169
+
170
+ expect(result).toBe(true);
171
+ });
172
+
173
+ it('should return false for numbered lists', () => {
174
+ const mockAbstractNum = {
175
+ getLevel: vi.fn().mockReturnValue({
176
+ getFormat: vi.fn().mockReturnValue('decimal'),
177
+ }),
178
+ };
179
+ const mockInstance = {
180
+ getAbstractNumId: vi.fn().mockReturnValue(0),
181
+ };
182
+ const mockNumberingManager = {
183
+ getNumberingInstance: vi.fn().mockReturnValue(mockInstance),
184
+ getAbstractNumbering: vi.fn().mockReturnValue(mockAbstractNum),
185
+ };
186
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
187
+
188
+ const result = processor.isBulletList(mockDoc, 1);
189
+
190
+ expect(result).toBe(false);
191
+ });
192
+ });
193
+
194
+ describe('isNumberedList', () => {
195
+ it('should identify decimal numbered lists', () => {
196
+ const mockAbstractNum = {
197
+ getLevel: vi.fn().mockReturnValue({
198
+ getFormat: vi.fn().mockReturnValue('decimal'),
199
+ }),
200
+ };
201
+ const mockInstance = {
202
+ getAbstractNumId: vi.fn().mockReturnValue(0),
203
+ };
204
+ const mockNumberingManager = {
205
+ getNumberingInstance: vi.fn().mockReturnValue(mockInstance),
206
+ getAbstractNumbering: vi.fn().mockReturnValue(mockAbstractNum),
207
+ };
208
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
209
+
210
+ const result = processor.isNumberedList(mockDoc, 1);
211
+
212
+ expect(result).toBe(true);
213
+ });
214
+
215
+ it('should identify letter numbered lists', () => {
216
+ const mockAbstractNum = {
217
+ getLevel: vi.fn().mockReturnValue({
218
+ getFormat: vi.fn().mockReturnValue('lowerLetter'),
219
+ }),
220
+ };
221
+ const mockInstance = {
222
+ getAbstractNumId: vi.fn().mockReturnValue(0),
223
+ };
224
+ const mockNumberingManager = {
225
+ getNumberingInstance: vi.fn().mockReturnValue(mockInstance),
226
+ getAbstractNumbering: vi.fn().mockReturnValue(mockAbstractNum),
227
+ };
228
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
229
+
230
+ const result = processor.isNumberedList(mockDoc, 1);
231
+
232
+ expect(result).toBe(true);
233
+ });
234
+
235
+ it('should return false for bullet lists', () => {
236
+ const mockAbstractNum = {
237
+ getLevel: vi.fn().mockReturnValue({
238
+ getFormat: vi.fn().mockReturnValue('bullet'),
239
+ }),
240
+ };
241
+ const mockInstance = {
242
+ getAbstractNumId: vi.fn().mockReturnValue(0),
243
+ };
244
+ const mockNumberingManager = {
245
+ getNumberingInstance: vi.fn().mockReturnValue(mockInstance),
246
+ getAbstractNumbering: vi.fn().mockReturnValue(mockAbstractNum),
247
+ };
248
+ mockDoc.getNumberingManager = vi.fn().mockReturnValue(mockNumberingManager);
249
+
250
+ const result = processor.isNumberedList(mockDoc, 1);
251
+
252
+ expect(result).toBe(false);
253
+ });
254
+ });
255
+ });
256
+
257
+ // Helper functions
258
+
259
+ function createMockListParagraph(level: number, numId: number): Mocked<Paragraph> {
260
+ return {
261
+ getNumbering: vi.fn().mockReturnValue({ level, numId }),
262
+ setLeftIndent: vi.fn().mockReturnThis(),
263
+ setFirstLineIndent: vi.fn().mockReturnThis(),
264
+ setSpaceAfter: vi.fn(),
265
+ getText: vi.fn().mockReturnValue('List item'),
266
+ getStyle: vi.fn().mockReturnValue('ListParagraph'),
267
+ } as unknown as Mocked<Paragraph>;
268
+ }
269
+
270
+ function createMockNumberingLevel(font: string) {
271
+ return {
272
+ getProperties: vi.fn().mockReturnValue({ font }),
273
+ getFormat: vi.fn().mockReturnValue('bullet'),
274
+ setFont: vi.fn().mockReturnThis(),
275
+ setColor: vi.fn().mockReturnThis(),
276
+ setFontSize: vi.fn().mockReturnThis(),
277
+ setBold: vi.fn().mockReturnThis(),
278
+ setText: vi.fn().mockReturnThis(),
279
+ };
280
+ }
281
+
282
+ function createMockNormalParagraph(): Mocked<Paragraph> {
283
+ return {
284
+ getNumbering: vi.fn().mockReturnValue(null),
285
+ setLeftIndent: vi.fn().mockReturnThis(),
286
+ setFirstLineIndent: vi.fn().mockReturnThis(),
287
+ setSpaceAfter: vi.fn(),
288
+ getText: vi.fn().mockReturnValue('Normal paragraph'),
289
+ getStyle: vi.fn().mockReturnValue('Normal'),
290
+ } as unknown as Mocked<Paragraph>;
291
+ }