|
| 1 | +# Test Coverage Audit |
| 2 | + |
| 3 | +**Date:** 2026-05-03 |
| 4 | +**Branch:** `feat/linux-mvp` |
| 5 | +**Baseline:** 143 unit tests, 38 E2E tests |
| 6 | +**Post-fill:** 273 unit tests, 41 E2E tests |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## Renderer Components |
| 11 | + |
| 12 | +| Surface | Type | Pre-audit | Gap | Post-fill | |
| 13 | +|---|---|---|---|---| |
| 14 | +| WorkspaceRail – renders workspaces in order | render | ✅ | — | ✅ | |
| 15 | +| WorkspaceRail – click workspace → setActiveWorkspaceId + IPC | click | ⚠️ (store only, not IPC) | IPC assert missing | ✅ | |
| 16 | +| WorkspaceRail – settings cog → opens SettingsDialog | click | ❌ | no test | ✅ | |
| 17 | +| WorkspaceRail – + button → opens AddWorkspaceDialog | click | ✅ | — | ✅ | |
| 18 | +| WorkspaceRail – active workspace shows ring style | render | ❌ | no test | ✅ | |
| 19 | +| WorkspaceRail – button shows first 2 uppercase letters | render | ❌ | no test | ✅ | |
| 20 | +| PadSidebar – shows Pinned + Recent sections | render | ✅ | — | ✅ | |
| 21 | +| PadSidebar – click recent pad → tab.open | click | ✅ | — | ✅ | |
| 22 | +| PadSidebar – + New Pad → opens OpenPadDialog | click | ✅ | — | ✅ | |
| 23 | +| PadSidebar – no active workspace → empty aside | render | ✅ | — | ✅ | |
| 24 | +| PadSidebar – empty history (has wsId) → no Pinned section | render | ❌ | no test | ✅ | |
| 25 | +| PadSidebar – click pinned pad → tab.open | click | ❌ | no test | ✅ | |
| 26 | +| PadSidebar – uses title over padName when available | render | ❌ | no test | ✅ | |
| 27 | +| PadSidebar – caps recent list at 50 | render | ❌ | no test | ✅ | |
| 28 | +| TabStrip – renders tabs per workspace | render | ✅ | — | ✅ | |
| 29 | +| TabStrip – click tab → ipc.tab.focus | click | ✅ | — | ✅ | |
| 30 | +| TabStrip – click ✕ → ipc.tab.close | click | ✅ | — | ✅ | |
| 31 | +| TabStrip – error indicator for state=error | render | ✅ | — | ✅ | |
| 32 | +| TabStrip – error indicator for state=crashed | render | ❌ | no test | ✅ | |
| 33 | +| TabStrip – active tab has aria-selected=true | render | ❌ | no test | ✅ | |
| 34 | +| TabStrip – no tabs when activeWorkspaceId=null | render | ❌ | no test | ✅ | |
| 35 | +| TabStrip – only shows tabs for active workspace | render | ❌ | no test (duplicate test existed) | ✅ | |
| 36 | +| TabStrip – no error indicator for state=loaded | render | ❌ | no test | ✅ | |
| 37 | +| TabErrorOverlay – hidden when state=loaded | render | ❌ | **no test file** | ✅ | |
| 38 | +| TabErrorOverlay – hidden when no active tab | render | ❌ | **no test file** | ✅ | |
| 39 | +| TabErrorOverlay – shows errorMessage in error state | render | ❌ | **no test file** | ✅ | |
| 40 | +| TabErrorOverlay – falls back to serverUrl when no errorMessage | render | ❌ | **no test file** | ✅ | |
| 41 | +| TabErrorOverlay – shows crashed message | render | ❌ | **no test file** | ✅ | |
| 42 | +| TabErrorOverlay – Retry → ipc.tab.reload (error state) | click | ❌ | **no test file** | ✅ | |
| 43 | +| TabErrorOverlay – Reload → ipc.tab.reload (crashed state) | click | ❌ | **no test file** | ✅ | |
| 44 | +| TabErrorOverlay – Close tab → ipc.tab.close (error) | click | ❌ | **no test file** | ✅ | |
| 45 | +| TabErrorOverlay – Close tab → ipc.tab.close (crashed) | click | ❌ | **no test file** | ✅ | |
| 46 | +| TabErrorOverlay – Retry label in error state, Reload in crashed | render | ❌ | **no test file** | ✅ | |
| 47 | +| EmptyState – Open Pad button → opens openPad dialog | click | ✅ | — | ✅ | |
| 48 | +| ErrorBoundary – renders children | render | ✅ | — | ✅ | |
| 49 | +| ErrorBoundary – fallback + onReload | click | ✅ | — | ✅ | |
| 50 | +| ErrorBoundary – show details toggles stack trace | click | ✅ | — | ✅ | |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Dialogs |
| 55 | + |
| 56 | +| Surface | Type | Pre-audit | Gap | Post-fill | |
| 57 | +|---|---|---|---|---| |
| 58 | +| AddWorkspaceDialog – submit happy path | click | ✅ | — | ✅ | |
| 59 | +| AddWorkspaceDialog – ServerUnreachableError message | click | ✅ | — | ✅ | |
| 60 | +| AddWorkspaceDialog – NotAnEtherpadServerError message | click | ❌ | no test | ✅ | |
| 61 | +| AddWorkspaceDialog – UrlValidationError message | click | ❌ | no test | ✅ | |
| 62 | +| AddWorkspaceDialog – Cancel when dismissable | click | ✅ | — | ✅ | |
| 63 | +| AddWorkspaceDialog – Cancel hidden when not dismissable | render | ✅ | — | ✅ | |
| 64 | +| AddWorkspaceDialog – Add disabled when fields empty | render | ❌ | no test | ✅ | |
| 65 | +| AddWorkspaceDialog – Add disabled when only name filled | render | ❌ | no test | ✅ | |
| 66 | +| AddWorkspaceDialog – Add enabled when both filled | render | ❌ | no test | ✅ | |
| 67 | +| AddWorkspaceDialog – color swatch click changes pressed state | click | ❌ | no test | ✅ | |
| 68 | +| AddWorkspaceDialog – submit uses selected color | click | ❌ | no test | ✅ | |
| 69 | +| AddWorkspaceDialog – successful submit closes dialog | click | ❌ | no test | ✅ | |
| 70 | +| OpenPadDialog – submit calls tab.open with name | click | ✅ | — | ✅ | |
| 71 | +| OpenPadDialog – autocomplete shows on type | render | ✅ | — | ✅ | |
| 72 | +| OpenPadDialog – create mode calls tab.open with mode=create | click | ✅ | — | ✅ | |
| 73 | +| OpenPadDialog – Open disabled when name empty | render | ❌ | no test | ✅ | |
| 74 | +| OpenPadDialog – Open enabled when name filled | render | ❌ | no test | ✅ | |
| 75 | +| OpenPadDialog – Cancel closes dialog | click | ❌ | no test | ✅ | |
| 76 | +| OpenPadDialog – click autocomplete suggestion → tab.open | click | ❌ | no test | ✅ | |
| 77 | +| OpenPadDialog – autocomplete suggestion click closes dialog | click | ❌ | no test | ✅ | |
| 78 | +| OpenPadDialog – successful submit closes dialog | click | ❌ | no test | ✅ | |
| 79 | +| OpenPadDialog – empty name submit does nothing (button disabled) | render | ❌ | no test | ✅ | |
| 80 | +| SettingsDialog – zoom change + save | click | ✅ | — | ✅ | |
| 81 | +| SettingsDialog – language dropdown options | render | ✅ | — | ✅ | |
| 82 | +| SettingsDialog – language change + save | click | ✅ | — | ✅ | |
| 83 | +| SettingsDialog – rememberTabs checkbox toggle + save | click | ❌ | no test | ✅ | |
| 84 | +| SettingsDialog – Save closes dialog | click | ❌ | no test | ✅ | |
| 85 | +| SettingsDialog – Cancel closes without calling update | click | ❌ | no test | ✅ | |
| 86 | +| SettingsDialog – Clear All History → padHistory.clearAll | click | ❌ | no test | ✅ | |
| 87 | +| SettingsDialog – Remove workspace → opens RemoveWorkspaceDialog | click | ❌ | no test | ✅ | |
| 88 | +| SettingsDialog – Remove button per workspace | render | ❌ | no test | ✅ | |
| 89 | +| RemoveWorkspaceDialog – confirm calls workspace.remove | click | ✅ | — | ✅ | |
| 90 | +| RemoveWorkspaceDialog – shows workspace name | render | ❌ | no test | ✅ | |
| 91 | +| RemoveWorkspaceDialog – Cancel closes without remove | click | ❌ | no test | ✅ | |
| 92 | +| RemoveWorkspaceDialog – confirm closes dialog on success | click | ❌ | no test | ✅ | |
| 93 | +| RemoveWorkspaceDialog – confirm calls setActiveWorkspace | click | ❌ | no test | ✅ | |
| 94 | +| RemoveWorkspaceDialog – renders nothing for unknown workspaceId | render | ❌ | no test | ✅ | |
| 95 | +| RemoveWorkspaceDialog – shows error on failed remove | click | ❌ | no test | ✅ | |
| 96 | +| HttpAuthDialog – submit with credentials | click | ✅ | — | ✅ | |
| 97 | +| HttpAuthDialog – Cancel → respond(cancel=true) | click | ❌ | no test | ✅ | |
| 98 | +| HttpAuthDialog – Cancel closes dialog | click | ❌ | no test | ✅ | |
| 99 | +| HttpAuthDialog – Sign In disabled when username empty | render | ❌ | no test | ✅ | |
| 100 | +| HttpAuthDialog – Sign In enabled after typing username | render | ❌ | no test | ✅ | |
| 101 | +| HttpAuthDialog – shows URL from context | render | ❌ | no test | ✅ | |
| 102 | +| HttpAuthDialog – Sign In closes dialog on success | click | ❌ | no test | ✅ | |
| 103 | +| AboutDialog – heading visible + Close closes | click | ✅ | — | ✅ | |
| 104 | + |
| 105 | +--- |
| 106 | + |
| 107 | +## IPC Handlers (main process) |
| 108 | + |
| 109 | +| Surface | Type | Pre-audit | Gap | Post-fill | |
| 110 | +|---|---|---|---|---| |
| 111 | +| workspace.add – happy path | handler | ✅ | — | ✅ | |
| 112 | +| workspace.add – NotAnEtherpadServerError | handler | ✅ | — | ✅ | |
| 113 | +| workspace.add – ServerUnreachableError | handler | ✅ | — | ✅ | |
| 114 | +| workspace.remove – happy path with ordering | handler | ✅ | — | ✅ | |
| 115 | +| workspace.remove – WorkspaceNotFoundError | handler | ✅ | — | ✅ | |
| 116 | +| workspace.update – happy path | handler | ❌ | no test | ✅ | |
| 117 | +| workspace.update – WorkspaceNotFoundError | handler | ❌ | no test | ✅ | |
| 118 | +| workspace.update – InvalidPayloadError (bad uuid) | handler | ❌ | no test | ✅ | |
| 119 | +| workspace.list – returns all workspaces | handler | ❌ | no test | ✅ | |
| 120 | +| workspace.reorder – happy path | handler | ❌ | no test | ✅ | |
| 121 | +| workspace.reorder – id set mismatch error | handler | ❌ | no test | ✅ | |
| 122 | +| tab.open – happy path + history stamp | handler | ✅ | — | ✅ | |
| 123 | +| tab.open – WorkspaceNotFoundError | handler | ✅ | — | ✅ | |
| 124 | +| tab.open – InvalidPayloadError (empty padName) | handler | ❌ | no test | ✅ | |
| 125 | +| tab.open – mode=create passes through | handler | ❌ | no test | ✅ | |
| 126 | +| tab.open – emits tabsChanged + padHistoryChanged | handler | ❌ | no test | ✅ | |
| 127 | +| tab.close – calls closeInAnyWindow + emits | handler | ❌ | no test | ✅ | |
| 128 | +| tab.close – InvalidPayloadError (empty tabId) | handler | ❌ | no test | ✅ | |
| 129 | +| tab.focus – calls focusInAnyWindow + emits | handler | ❌ | no test | ✅ | |
| 130 | +| tab.focus – InvalidPayloadError (empty tabId) | handler | ❌ | no test | ✅ | |
| 131 | +| tab.reload – calls reloadInAnyWindow | handler | ❌ | no test | ✅ | |
| 132 | +| tab.reload – InvalidPayloadError (empty tabId) | handler | ❌ | no test | ✅ | |
| 133 | +| window.setActiveWorkspace – happy path + emits | handler | ❌ | no test | ✅ | |
| 134 | +| window.setActiveWorkspace – accepts null workspaceId | handler | ❌ | no test | ✅ | |
| 135 | +| window.setActiveWorkspace – InvalidPayloadError | handler | ❌ | no test | ✅ | |
| 136 | +| window.reloadShell – calls reloadShellOfActiveWindow | handler | ❌ | no test | ✅ | |
| 137 | +| window.setPadViewsHidden – true/false paths | handler | ❌ | no test | ✅ | |
| 138 | +| window.setPadViewsHidden – InvalidPayloadError | handler | ❌ | no test | ✅ | |
| 139 | +| padHistory.list – empty workspace | handler | ❌ | **no test file** | ✅ | |
| 140 | +| padHistory.list – known workspace | handler | ❌ | **no test file** | ✅ | |
| 141 | +| padHistory.list – InvalidPayloadError | handler | ❌ | **no test file** | ✅ | |
| 142 | +| padHistory.pin – pins entry + emits | handler | ❌ | **no test file** | ✅ | |
| 143 | +| padHistory.pin – silent no-op for unknown padName | handler | ❌ | **no test file** | ✅ | |
| 144 | +| padHistory.pin – InvalidPayloadError (empty padName) | handler | ❌ | **no test file** | ✅ | |
| 145 | +| padHistory.unpin – unpins entry + emits | handler | ❌ | **no test file** | ✅ | |
| 146 | +| padHistory.unpin – silent no-op for unknown | handler | ❌ | **no test file** | ✅ | |
| 147 | +| padHistory.clearRecent – clears workspace entries | handler | ❌ | **no test file** | ✅ | |
| 148 | +| padHistory.clearRecent – no-op on unknown workspace | handler | ❌ | **no test file** | ✅ | |
| 149 | +| padHistory.clearAll – clears all entries | handler | ❌ | **no test file** | ✅ | |
| 150 | +| settings.get – returns defaults | handler | ❌ | no test | ✅ | |
| 151 | +| settings.update – single field | handler | ❌ | no test | ✅ | |
| 152 | +| settings.update – multiple fields | handler | ❌ | no test | ✅ | |
| 153 | +| settings.update – triggers reloadAllPadsWithLanguage | handler | ❌ | no test | ✅ | |
| 154 | +| settings.update – no reload when language unchanged | handler | ❌ | no test | ✅ | |
| 155 | +| settings.update – InvalidPayloadError (unknown field) | handler | ❌ | no test | ✅ | |
| 156 | +| settings.update – persists across reads | handler | ❌ | no test | ✅ | |
| 157 | +| state.getInitial – returns workspaces + order + settings | handler | ✅ | — | ✅ | |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +## App Event Subscriptions (renderer) |
| 162 | + |
| 163 | +| Surface | Type | Pre-audit | Gap | Post-fill | |
| 164 | +|---|---|---|---|---| |
| 165 | +| onWorkspacesChanged – updates workspaces + order | event | ❌ | **no test file** | ✅ | |
| 166 | +| onWorkspacesChanged – replaces with empty list | event | ❌ | **no test file** | ✅ | |
| 167 | +| onTabsChanged – replaces tabs + activeTabId | event | ❌ | **no test file** | ✅ | |
| 168 | +| onTabsChanged – activeTabId=null sets to null | event | ❌ | **no test file** | ✅ | |
| 169 | +| onTabsChanged – missing activeTabId leaves existing unchanged | event | ❌ | **no test file** | ✅ | |
| 170 | +| onTabState – updates state field | event | ❌ | **no test file** | ✅ | |
| 171 | +| onTabState – updates errorMessage | event | ❌ | **no test file** | ✅ | |
| 172 | +| onTabState – updates title | event | ❌ | **no test file** | ✅ | |
| 173 | +| onTabState – does not touch other tabs | event | ❌ | **no test file** | ✅ | |
| 174 | +| onTabState – no-op for unknown tabId | event | ❌ | **no test file** | ✅ | |
| 175 | +| onSettingsChanged – updates settings store | event | ❌ | **no test file** | ✅ | |
| 176 | +| onHttpLoginRequest – opens httpAuth dialog with payload | event | ❌ | **no test file** | ✅ | |
| 177 | +| onMenuShellMessage – menu.newTab → openPad dialog | event | ❌ | **no test file** | ✅ | |
| 178 | +| onMenuShellMessage – menu.openPad → openPad dialog | event | ❌ | **no test file** | ✅ | |
| 179 | +| onMenuShellMessage – menu.settings → settings dialog | event | ❌ | **no test file** | ✅ | |
| 180 | +| onMenuShellMessage – menu.about → about dialog | event | ❌ | **no test file** | ✅ | |
| 181 | +| onMenuShellMessage – menu.reload → ipc.tab.reload | event | ❌ | **no test file** | ✅ | |
| 182 | +| onMenuShellMessage – menu.reload no-ops when no active tab | event | ❌ | **no test file** | ✅ | |
| 183 | +| onMenuShellMessage – unknown kind → no dialog | event | ❌ | **no test file** | ✅ | |
| 184 | +| onPadHistoryChanged – calls list + stores result | event | ❌ | **no test file** | ✅ | |
| 185 | +| onPadHistoryChanged – no-op when activeWorkspaceId=null | event | ❌ | **no test file** | ✅ | |
| 186 | + |
| 187 | +--- |
| 188 | + |
| 189 | +## Menu Template (main process unit) |
| 190 | + |
| 191 | +| Surface | Type | Pre-audit | Gap | Post-fill | |
| 192 | +|---|---|---|---|---| |
| 193 | +| Contains File/Edit/View/Window/Help | unit | ✅ | — | ✅ | |
| 194 | +| File menu accelerators | unit | ✅ | — | ✅ | |
| 195 | +| File > New Tab click → cb.newTab() | unit | ✅ | — | ✅ | |
| 196 | +| File > Open Pad… click → cb.openPad() | unit | ❌ | no test | ✅ | |
| 197 | +| File > Settings click → cb.settings() | unit | ❌ | no test | ✅ | |
| 198 | +| File > Quit click → cb.quit() | unit | ❌ | no test | ✅ | |
| 199 | +| View > Reload Pad click → cb.reload() | unit | ❌ | no test | ✅ | |
| 200 | +| View > Reload Pad has CmdOrCtrl+R | unit | ❌ | no test | ✅ | |
| 201 | +| Help > About click → cb.about() | unit | ❌ | no test | ✅ | |
| 202 | +| Help > Open Log Folder click → cb.openLogs() | unit | ❌ | no test | ✅ | |
| 203 | +| Edit roles (undo/redo/cut/copy/paste/selectAll) | unit | ❌ | no test | ✅ | |
| 204 | +| View roles (resetZoom/zoomIn/zoomOut/togglefullscreen) | unit | ❌ | no test | ✅ | |
| 205 | +| Window roles (minimize/close) | unit | ❌ | no test | ✅ | |
| 206 | +| File > Close Tab has role=close | unit | ❌ | no test | ✅ | |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## E2E Menu Tests |
| 211 | + |
| 212 | +| Surface | Type | Pre-audit | Gap | Post-fill | |
| 213 | +|---|---|---|---|---| |
| 214 | +| File > New Tab → OpenPadDialog visible | e2e | ✅ | — | ✅ | |
| 215 | +| File > Open Pad… → OpenPadDialog visible | e2e | ✅ | — | ✅ | |
| 216 | +| File > Settings → SettingsDialog visible | e2e | ✅ | — | ✅ | |
| 217 | +| Help > About → AboutDialog visible | e2e | ✅ | — | ✅ | |
| 218 | +| Help > Open Log Folder → item exists + no crash | e2e | ❌ | no test | ✅ | |
| 219 | +| View > Reload Pad → item exists + fires | e2e | ❌ | no test | ✅ | |
| 220 | +| File > Quit → item exists (click omitted to not kill process) | e2e | ❌ | no test | ✅ | |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## Summary |
| 225 | + |
| 226 | +**Pre-pass:** |
| 227 | +- Unit tests: 143 |
| 228 | +- E2E tests: 38 |
| 229 | +- ✅ Covered: ~32 surfaces |
| 230 | +- ⚠️ Fluffy: ~4 surfaces (IPC call side not asserted) |
| 231 | +- ❌ Gaps: ~100+ surfaces |
| 232 | + |
| 233 | +**Post-pass:** |
| 234 | +- Unit tests: 273 (+130) |
| 235 | +- E2E tests: 41 (+3) |
| 236 | +- ✅ All surfaces covered |
| 237 | +- ⚠️ Fluffy: 0 |
| 238 | +- ❌ Gaps: 0 |
| 239 | + |
| 240 | +**Bugs found during pass:** |
| 241 | +- `AddWorkspaceDialog` test setup was missing `window.etherpadDesktop.window.setActiveWorkspace` mock, causing the "successful submit closes dialog" test to fail (the dialog's `submit()` calls `ipc.window.setActiveWorkspace` synchronously before `dialogActions.closeDialog()`). Fixed by adding the mock to `beforeEach`. |
0 commit comments