@theia/debug 1.70.0-next.81 → 1.71.0-next.0

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 (100) hide show
  1. package/lib/browser/breakpoint/breakpoint-manager.d.ts +80 -45
  2. package/lib/browser/breakpoint/breakpoint-manager.d.ts.map +1 -1
  3. package/lib/browser/breakpoint/breakpoint-manager.js +553 -170
  4. package/lib/browser/breakpoint/breakpoint-manager.js.map +1 -1
  5. package/lib/browser/breakpoint/breakpoint-manager.spec.d.ts +2 -0
  6. package/lib/browser/breakpoint/breakpoint-manager.spec.d.ts.map +1 -0
  7. package/lib/browser/breakpoint/breakpoint-manager.spec.js +861 -0
  8. package/lib/browser/breakpoint/breakpoint-manager.spec.js.map +1 -0
  9. package/lib/browser/breakpoint/breakpoint-marker.d.ts +7 -10
  10. package/lib/browser/breakpoint/breakpoint-marker.d.ts.map +1 -1
  11. package/lib/browser/breakpoint/breakpoint-marker.js +14 -11
  12. package/lib/browser/breakpoint/breakpoint-marker.js.map +1 -1
  13. package/lib/browser/breakpoint/debug-data-breakpoint-actions.js +1 -1
  14. package/lib/browser/breakpoint/debug-data-breakpoint-actions.js.map +1 -1
  15. package/lib/browser/debug-frontend-application-contribution.d.ts +1 -2
  16. package/lib/browser/debug-frontend-application-contribution.d.ts.map +1 -1
  17. package/lib/browser/debug-frontend-application-contribution.js +13 -21
  18. package/lib/browser/debug-frontend-application-contribution.js.map +1 -1
  19. package/lib/browser/debug-frontend-module.d.ts.map +1 -1
  20. package/lib/browser/debug-frontend-module.js +3 -0
  21. package/lib/browser/debug-frontend-module.js.map +1 -1
  22. package/lib/browser/debug-session-manager.d.ts +8 -27
  23. package/lib/browser/debug-session-manager.d.ts.map +1 -1
  24. package/lib/browser/debug-session-manager.js +14 -132
  25. package/lib/browser/debug-session-manager.js.map +1 -1
  26. package/lib/browser/debug-session.d.ts +1 -21
  27. package/lib/browser/debug-session.d.ts.map +1 -1
  28. package/lib/browser/debug-session.js +72 -203
  29. package/lib/browser/debug-session.js.map +1 -1
  30. package/lib/browser/disassembly-view/disassembly-view-breakpoint-renderer.js +1 -1
  31. package/lib/browser/disassembly-view/disassembly-view-breakpoint-renderer.js.map +1 -1
  32. package/lib/browser/disassembly-view/disassembly-view-widget.d.ts.map +1 -1
  33. package/lib/browser/disassembly-view/disassembly-view-widget.js +17 -24
  34. package/lib/browser/disassembly-view/disassembly-view-widget.js.map +1 -1
  35. package/lib/browser/editor/debug-editor-model.d.ts +15 -5
  36. package/lib/browser/editor/debug-editor-model.d.ts.map +1 -1
  37. package/lib/browser/editor/debug-editor-model.js +56 -32
  38. package/lib/browser/editor/debug-editor-model.js.map +1 -1
  39. package/lib/browser/model/debug-breakpoint-opener.d.ts +14 -0
  40. package/lib/browser/model/debug-breakpoint-opener.d.ts.map +1 -0
  41. package/lib/browser/model/debug-breakpoint-opener.js +67 -0
  42. package/lib/browser/model/debug-breakpoint-opener.js.map +1 -0
  43. package/lib/browser/model/debug-breakpoint.d.ts +32 -13
  44. package/lib/browser/model/debug-breakpoint.d.ts.map +1 -1
  45. package/lib/browser/model/debug-breakpoint.js +76 -16
  46. package/lib/browser/model/debug-breakpoint.js.map +1 -1
  47. package/lib/browser/model/debug-data-breakpoint.d.ts +1 -0
  48. package/lib/browser/model/debug-data-breakpoint.d.ts.map +1 -1
  49. package/lib/browser/model/debug-data-breakpoint.js +6 -5
  50. package/lib/browser/model/debug-data-breakpoint.js.map +1 -1
  51. package/lib/browser/model/debug-function-breakpoint.d.ts +4 -1
  52. package/lib/browser/model/debug-function-breakpoint.d.ts.map +1 -1
  53. package/lib/browser/model/debug-function-breakpoint.js +20 -29
  54. package/lib/browser/model/debug-function-breakpoint.js.map +1 -1
  55. package/lib/browser/model/debug-instruction-breakpoint.d.ts +2 -1
  56. package/lib/browser/model/debug-instruction-breakpoint.d.ts.map +1 -1
  57. package/lib/browser/model/debug-instruction-breakpoint.js +8 -8
  58. package/lib/browser/model/debug-instruction-breakpoint.js.map +1 -1
  59. package/lib/browser/model/debug-source-breakpoint.d.ts +6 -15
  60. package/lib/browser/model/debug-source-breakpoint.d.ts.map +1 -1
  61. package/lib/browser/model/debug-source-breakpoint.js +16 -90
  62. package/lib/browser/model/debug-source-breakpoint.js.map +1 -1
  63. package/lib/browser/view/debug-breakpoints-source.d.ts +0 -2
  64. package/lib/browser/view/debug-breakpoints-source.d.ts.map +1 -1
  65. package/lib/browser/view/debug-breakpoints-source.js +2 -10
  66. package/lib/browser/view/debug-breakpoints-source.js.map +1 -1
  67. package/lib/browser/view/debug-breakpoints-widget.d.ts +2 -0
  68. package/lib/browser/view/debug-breakpoints-widget.d.ts.map +1 -1
  69. package/lib/browser/view/debug-breakpoints-widget.js +3 -0
  70. package/lib/browser/view/debug-breakpoints-widget.js.map +1 -1
  71. package/lib/browser/view/debug-exception-breakpoint.d.ts +18 -11
  72. package/lib/browser/view/debug-exception-breakpoint.d.ts.map +1 -1
  73. package/lib/browser/view/debug-exception-breakpoint.js +58 -24
  74. package/lib/browser/view/debug-exception-breakpoint.js.map +1 -1
  75. package/lib/browser/view/debug-view-model.d.ts +8 -4
  76. package/lib/browser/view/debug-view-model.d.ts.map +1 -1
  77. package/lib/browser/view/debug-view-model.js +16 -9
  78. package/lib/browser/view/debug-view-model.js.map +1 -1
  79. package/package.json +16 -16
  80. package/src/browser/breakpoint/breakpoint-manager.spec.ts +1106 -0
  81. package/src/browser/breakpoint/breakpoint-manager.ts +583 -194
  82. package/src/browser/breakpoint/breakpoint-marker.ts +21 -15
  83. package/src/browser/breakpoint/debug-data-breakpoint-actions.ts +1 -1
  84. package/src/browser/debug-frontend-application-contribution.ts +18 -23
  85. package/src/browser/debug-frontend-module.ts +5 -1
  86. package/src/browser/debug-session-manager.ts +15 -147
  87. package/src/browser/debug-session.tsx +71 -221
  88. package/src/browser/disassembly-view/disassembly-view-breakpoint-renderer.ts +1 -1
  89. package/src/browser/disassembly-view/disassembly-view-widget.ts +17 -23
  90. package/src/browser/editor/debug-editor-model.ts +58 -35
  91. package/src/browser/model/debug-breakpoint-opener.ts +51 -0
  92. package/src/browser/model/debug-breakpoint.tsx +101 -20
  93. package/src/browser/model/debug-data-breakpoint.tsx +8 -5
  94. package/src/browser/model/debug-function-breakpoint.tsx +18 -29
  95. package/src/browser/model/debug-instruction-breakpoint.tsx +10 -8
  96. package/src/browser/model/debug-source-breakpoint.tsx +23 -101
  97. package/src/browser/view/debug-breakpoints-source.tsx +2 -9
  98. package/src/browser/view/debug-breakpoints-widget.ts +6 -0
  99. package/src/browser/view/debug-exception-breakpoint.tsx +66 -27
  100. package/src/browser/view/debug-view-model.ts +21 -13
@@ -0,0 +1,861 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 EclipseSource GmbH and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const frontend_application_config_provider_1 = require("@theia/core/lib/browser/frontend-application-config-provider");
19
+ const jsdom_1 = require("@theia/core/lib/browser/test/jsdom");
20
+ const disableJSDOM = (0, jsdom_1.enableJSDOM)();
21
+ frontend_application_config_provider_1.FrontendApplicationConfigProvider.set({});
22
+ const inversify_1 = require("@theia/core/shared/inversify");
23
+ const common_1 = require("@theia/core/lib/common");
24
+ const browser_1 = require("@theia/core/lib/browser");
25
+ const storage_service_1 = require("@theia/core/lib/browser/storage-service");
26
+ const uri_1 = require("@theia/core/lib/common/uri");
27
+ const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
28
+ const files_1 = require("@theia/filesystem/lib/common/files");
29
+ const chai_1 = require("chai");
30
+ const breakpoint_manager_1 = require("./breakpoint-manager");
31
+ const breakpoint_marker_1 = require("./breakpoint-marker");
32
+ const debug_data_breakpoint_1 = require("../model/debug-data-breakpoint");
33
+ disableJSDOM();
34
+ // ── Helpers ──
35
+ const FILE_A = new uri_1.default('file:///workspace/a.ts');
36
+ const FILE_B = new uri_1.default('file:///workspace/b.ts');
37
+ function makeSourceBreakpoint(uri, line, opts) {
38
+ return breakpoint_marker_1.SourceBreakpoint.create(uri, { line, column: opts?.column, condition: opts?.condition, hitCondition: opts?.hitCondition, logMessage: opts?.logMessage }, opts?.id ? { id: opts.id, uri: uri.toString(), enabled: opts?.enabled ?? true, raw: { line } } : undefined);
39
+ }
40
+ function makeFunctionBreakpoint(name) {
41
+ return breakpoint_marker_1.FunctionBreakpoint.create({ name });
42
+ }
43
+ function makeDataBreakpoint(dataId, description = 'some var') {
44
+ return breakpoint_marker_1.DataBreakpoint.create({ dataId, accessType: 'write' }, { dataId, description, accessTypes: ['read', 'write'], canPersist: true }, { type: 0 /* DataBreakpointSourceType.Variable */, variable: description });
45
+ }
46
+ const defaultCapabilities = {};
47
+ function makeSessionData(overrides = {}) {
48
+ return {
49
+ id: 1,
50
+ verified: true,
51
+ line: 5,
52
+ supportsConditionalBreakpoints: false,
53
+ supportsHitConditionalBreakpoints: false,
54
+ supportsLogPoints: false,
55
+ supportsFunctionBreakpoints: false,
56
+ supportsDataBreakpoints: false,
57
+ supportsInstructionBreakpoints: false,
58
+ ...overrides,
59
+ };
60
+ }
61
+ // ── Test Setup ──
62
+ function createManager() {
63
+ const container = new inversify_1.Container();
64
+ const storageData = {};
65
+ const fileChangeEmitter = new common_1.Emitter();
66
+ container.bind(browser_1.LabelProvider).toConstantValue({
67
+ getName: (uri) => uri.path.base,
68
+ getLongName: (uri) => uri.path.toString(),
69
+ });
70
+ container.bind(browser_1.OpenerService).toConstantValue({});
71
+ container.bind(common_1.CommandService).toConstantValue({});
72
+ container.bind(storage_service_1.StorageService).toConstantValue({
73
+ getData: async (key, defaultValue) => storageData[key] ?? defaultValue,
74
+ setData: async (key, data) => { storageData[key] = data; },
75
+ });
76
+ container.bind(file_service_1.FileService).toConstantValue({
77
+ onDidFilesChange: fileChangeEmitter.event,
78
+ });
79
+ container.bind(breakpoint_manager_1.BreakpointManager).toSelf().inSingletonScope();
80
+ const manager = container.get(breakpoint_manager_1.BreakpointManager);
81
+ return { manager, storageData, fileChangeEmitter };
82
+ }
83
+ // ── Tests ──
84
+ describe('DebugBreakpoint.update() — session data lifecycle', () => {
85
+ let manager;
86
+ beforeEach(() => {
87
+ ({ manager } = createManager());
88
+ });
89
+ it('adding session data for a session updates _raw', () => {
90
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
91
+ bp.update('session-1', makeSessionData({ verified: true, line: 10 }));
92
+ (0, chai_1.expect)(bp.raw).to.not.be.undefined;
93
+ (0, chai_1.expect)(bp.raw.sessionId).to.equal('session-1');
94
+ (0, chai_1.expect)(bp.raw.line).to.equal(10);
95
+ });
96
+ it('verified single session: verified returns true', () => {
97
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
98
+ bp.update('session-1', makeSessionData({ verified: true }));
99
+ (0, chai_1.expect)(bp.verified).to.be.true;
100
+ });
101
+ it('unverified single session: verified returns false, _raw still available', () => {
102
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
103
+ bp.update('session-1', makeSessionData({ verified: false, message: 'not yet' }));
104
+ (0, chai_1.expect)(bp.verified).to.be.false;
105
+ (0, chai_1.expect)(bp.raw).to.not.be.undefined;
106
+ (0, chai_1.expect)(bp.raw.message).to.equal('not yet');
107
+ });
108
+ it('multiple sessions, one verified: _raw picks the verified one', () => {
109
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
110
+ bp.update('session-1', makeSessionData({ verified: false, line: 10 }));
111
+ bp.update('session-2', makeSessionData({ verified: true, line: 11 }));
112
+ (0, chai_1.expect)(bp.verified).to.be.true;
113
+ (0, chai_1.expect)(bp.raw.sessionId).to.equal('session-2');
114
+ (0, chai_1.expect)(bp.raw.line).to.equal(11);
115
+ });
116
+ it('multiple sessions, both verified at same location: _raw picks one', () => {
117
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
118
+ bp.update('session-1', makeSessionData({ verified: true, line: 10, column: 1 }));
119
+ bp.update('session-2', makeSessionData({ verified: true, line: 10, column: 1 }));
120
+ (0, chai_1.expect)(bp.verified).to.be.true;
121
+ // Both agree on location, so verifiedLocations.size === 1 → picks that one
122
+ (0, chai_1.expect)(bp.raw).to.not.be.undefined;
123
+ });
124
+ it('multiple sessions, verified at different locations: _raw cleared, verified stays true', () => {
125
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
126
+ bp.update('session-1', makeSessionData({ verified: true, line: 10 }));
127
+ bp.update('session-2', makeSessionData({ verified: true, line: 20 }));
128
+ // Sessions disagree → _raw is undefined so the breakpoint falls back
129
+ // to its user-set position, but still shows as verified (VSCode semantics).
130
+ (0, chai_1.expect)(bp.raw).to.be.undefined;
131
+ (0, chai_1.expect)(bp.verified).to.be.true;
132
+ (0, chai_1.expect)(bp.installed).to.be.true;
133
+ // Per-session data is still accessible for callers that need it.
134
+ (0, chai_1.expect)(bp.getDebugProtocolBreakpoint('session-1').line).to.equal(10);
135
+ (0, chai_1.expect)(bp.getDebugProtocolBreakpoint('session-2').line).to.equal(20);
136
+ });
137
+ it('removing session data for a session that contributed: data is deleted, _raw recomputed', () => {
138
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
139
+ bp.update('session-1', makeSessionData({ verified: true, line: 10 }));
140
+ bp.update('session-2', makeSessionData({ verified: true, line: 20 }));
141
+ bp.update('session-1', undefined);
142
+ (0, chai_1.expect)(bp.raw).to.not.be.undefined;
143
+ (0, chai_1.expect)(bp.raw.sessionId).to.equal('session-2');
144
+ });
145
+ it('removing session data for a session that never contributed: no-op', () => {
146
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
147
+ bp.update('session-1', makeSessionData({ verified: true, line: 10 }));
148
+ const rawBefore = bp.raw;
149
+ bp.update('session-never', undefined);
150
+ (0, chai_1.expect)(bp.raw).to.equal(rawBefore);
151
+ });
152
+ it('removing all session data: _raw becomes undefined, verified defaults to true', () => {
153
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
154
+ bp.update('session-1', makeSessionData({ verified: true, line: 10 }));
155
+ bp.update('session-1', undefined);
156
+ (0, chai_1.expect)(bp.raw).to.be.undefined;
157
+ // No session data at all → verified defaults to true (no adapter has
158
+ // said otherwise), installed is false (no session has reported).
159
+ (0, chai_1.expect)(bp.verified).to.be.true;
160
+ (0, chai_1.expect)(bp.installed).to.be.false;
161
+ });
162
+ it('before any session: verified defaults to true, installed is false', () => {
163
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
164
+ (0, chai_1.expect)(bp.raw).to.be.undefined;
165
+ (0, chai_1.expect)(bp.verified).to.be.true;
166
+ (0, chai_1.expect)(bp.installed).to.be.false;
167
+ (0, chai_1.expect)(bp.enabled).to.be.true;
168
+ });
169
+ it('single session unverified: installed is true, verified is false', () => {
170
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
171
+ bp.update('session-1', makeSessionData({ verified: false }));
172
+ (0, chai_1.expect)(bp.installed).to.be.true;
173
+ (0, chai_1.expect)(bp.verified).to.be.false;
174
+ });
175
+ it('disagreement resolved when one session removed: _raw restored from remaining', () => {
176
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
177
+ bp.update('session-1', makeSessionData({ verified: true, line: 10 }));
178
+ bp.update('session-2', makeSessionData({ verified: true, line: 20 }));
179
+ (0, chai_1.expect)(bp.raw).to.be.undefined; // disagreement
180
+ bp.update('session-1', undefined);
181
+ // Only session-2 remains → single verified location → _raw restored
182
+ (0, chai_1.expect)(bp.raw).to.not.be.undefined;
183
+ (0, chai_1.expect)(bp.raw.sessionId).to.equal('session-2');
184
+ (0, chai_1.expect)(bp.raw.line).to.equal(20);
185
+ });
186
+ it('getIdForSession returns the adapter id', () => {
187
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
188
+ bp.update('session-1', makeSessionData({ id: 42 }));
189
+ (0, chai_1.expect)(bp.getIdForSession('session-1')).to.equal(42);
190
+ (0, chai_1.expect)(bp.getIdForSession('unknown')).to.be.undefined;
191
+ });
192
+ it('getDebugProtocolBreakpoint returns protocol data for known session', () => {
193
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
194
+ bp.update('session-1', makeSessionData({ id: 7, verified: true, line: 10, message: 'ok' }));
195
+ const proto = bp.getDebugProtocolBreakpoint('session-1');
196
+ (0, chai_1.expect)(proto).to.deep.include({ id: 7, verified: true, line: 10, message: 'ok' });
197
+ });
198
+ it('getDebugProtocolBreakpoint returns undefined for unknown session', () => {
199
+ const bp = manager.addBreakpoint(makeSourceBreakpoint(FILE_A, 10));
200
+ (0, chai_1.expect)(bp.getDebugProtocolBreakpoint('nope')).to.be.undefined;
201
+ });
202
+ });
203
+ describe('BreakpointManager — source breakpoint identity preservation', () => {
204
+ let manager;
205
+ beforeEach(() => {
206
+ ({ manager } = createManager());
207
+ });
208
+ it('setBreakpoints with matching ID reuses existing wrapper', () => {
209
+ const original = makeSourceBreakpoint(FILE_A, 10);
210
+ manager.setBreakpoints(FILE_A, [original]);
211
+ const wrapper1 = manager.getBreakpoints(FILE_A)[0];
212
+ // Now "move" the breakpoint to line 20 but keep the same ID
213
+ const moved = { ...original, raw: { ...original.raw, line: 20 } };
214
+ manager.setBreakpoints(FILE_A, [moved]);
215
+ const wrapper2 = manager.getBreakpoints(FILE_A)[0];
216
+ (0, chai_1.expect)(wrapper2).to.equal(wrapper1); // same object identity
217
+ (0, chai_1.expect)(wrapper2.line).to.equal(20);
218
+ });
219
+ it('session data survives a position change via setBreakpoints', () => {
220
+ const original = makeSourceBreakpoint(FILE_A, 10);
221
+ manager.setBreakpoints(FILE_A, [original]);
222
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
223
+ wrapper.update('s1', makeSessionData({ verified: true, line: 10 }));
224
+ (0, chai_1.expect)(wrapper.verified).to.be.true;
225
+ // Move to line 20
226
+ const moved = { ...original, raw: { ...original.raw, line: 20 } };
227
+ manager.setBreakpoints(FILE_A, [moved]);
228
+ const wrapper2 = manager.getBreakpoints(FILE_A)[0];
229
+ (0, chai_1.expect)(wrapper2).to.equal(wrapper);
230
+ (0, chai_1.expect)(wrapper2.verified).to.be.true; // session data survived
231
+ });
232
+ it('setBreakpoints with a genuinely new breakpoint creates a new wrapper', () => {
233
+ const bp1 = makeSourceBreakpoint(FILE_A, 10);
234
+ manager.setBreakpoints(FILE_A, [bp1]);
235
+ const wrapper1 = manager.getBreakpoints(FILE_A)[0];
236
+ const bp2 = makeSourceBreakpoint(FILE_A, 20);
237
+ manager.setBreakpoints(FILE_A, [bp1, bp2]);
238
+ const wrappers = manager.getBreakpoints(FILE_A);
239
+ (0, chai_1.expect)(wrappers).to.have.length(2);
240
+ (0, chai_1.expect)(wrappers[0]).to.equal(wrapper1);
241
+ (0, chai_1.expect)(wrappers[1]).to.not.equal(wrapper1);
242
+ });
243
+ it('setBreakpoints deduplicates by position', () => {
244
+ const bp1 = makeSourceBreakpoint(FILE_A, 10);
245
+ const bp2 = makeSourceBreakpoint(FILE_A, 10); // different id, same position
246
+ manager.setBreakpoints(FILE_A, [bp1, bp2]);
247
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)).to.have.length(1);
248
+ });
249
+ it('addBreakpoint with a positional duplicate returns the existing wrapper', () => {
250
+ const bp1 = makeSourceBreakpoint(FILE_A, 10);
251
+ manager.setBreakpoints(FILE_A, [bp1]);
252
+ const wrapper1 = manager.getBreakpoints(FILE_A)[0];
253
+ const bp2 = makeSourceBreakpoint(FILE_A, 10);
254
+ const result = manager.addBreakpoint(bp2);
255
+ (0, chai_1.expect)(result).to.equal(wrapper1);
256
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)).to.have.length(1);
257
+ });
258
+ it('removeBreakpoint removes by identity and fires correct events', () => {
259
+ const bp1 = makeSourceBreakpoint(FILE_A, 10);
260
+ const bp2 = makeSourceBreakpoint(FILE_A, 20);
261
+ manager.setBreakpoints(FILE_A, [bp1, bp2]);
262
+ const events = [];
263
+ manager.onDidChangeBreakpoints(e => events.push(e));
264
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
265
+ manager.removeBreakpoint(wrapper);
266
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)).to.have.length(1);
267
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)[0].line).to.equal(20);
268
+ // At least one event should have the removed breakpoint
269
+ const removeEvent = events.find(e => e.removed.length > 0);
270
+ (0, chai_1.expect)(removeEvent).to.not.be.undefined;
271
+ (0, chai_1.expect)(removeEvent.removed[0]).to.equal(wrapper);
272
+ });
273
+ it('applySourceBreakpoints fires onDidChangeBreakpoints with correct added/removed/changed', () => {
274
+ const bp1 = makeSourceBreakpoint(FILE_A, 10);
275
+ const bp2 = makeSourceBreakpoint(FILE_A, 20);
276
+ manager.setBreakpoints(FILE_A, [bp1, bp2]);
277
+ const events = [];
278
+ manager.onDidChangeBreakpoints(e => events.push(e));
279
+ const bp3 = makeSourceBreakpoint(FILE_A, 30);
280
+ manager.setBreakpoints(FILE_A, [bp1, bp3]); // bp2 removed, bp3 added, bp1 changed (same identity)
281
+ (0, chai_1.expect)(events).to.have.length(1);
282
+ const event = events[0];
283
+ (0, chai_1.expect)(event.added).to.have.length(1);
284
+ (0, chai_1.expect)(event.added[0].line).to.equal(30);
285
+ (0, chai_1.expect)(event.removed).to.have.length(1);
286
+ (0, chai_1.expect)(event.removed[0].line).to.equal(20);
287
+ (0, chai_1.expect)(event.changed).to.have.length(1);
288
+ });
289
+ it('setBreakpoints sorts by line then column', () => {
290
+ const bp30 = makeSourceBreakpoint(FILE_A, 30);
291
+ const bp10 = makeSourceBreakpoint(FILE_A, 10);
292
+ const bp20 = makeSourceBreakpoint(FILE_A, 20);
293
+ manager.setBreakpoints(FILE_A, [bp30, bp10, bp20]);
294
+ const lines = manager.getBreakpoints(FILE_A).map(bp => bp.line);
295
+ (0, chai_1.expect)(lines).to.deep.equal([10, 20, 30]);
296
+ });
297
+ });
298
+ describe('BreakpointManager — enable/disable', () => {
299
+ let manager;
300
+ beforeEach(() => {
301
+ ({ manager } = createManager());
302
+ });
303
+ it('enableAllBreakpoints(true) enables all breakpoint types', () => {
304
+ const bp = makeSourceBreakpoint(FILE_A, 10, { enabled: false });
305
+ manager.setBreakpoints(FILE_A, [bp]);
306
+ const fbp = makeFunctionBreakpoint('myFunc');
307
+ fbp.enabled = false;
308
+ manager.addFunctionBreakpoint(fbp);
309
+ manager.addInstructionBreakpoint('0xDEAD', 0);
310
+ const dbp = makeDataBreakpoint('data1');
311
+ dbp.enabled = false;
312
+ manager.addDataBreakpoint(dbp);
313
+ // Disable all first
314
+ manager.enableAllBreakpoints(false);
315
+ const sourceEvents = [];
316
+ const funcEvents = [];
317
+ const instrEvents = [];
318
+ const dataEvents = [];
319
+ manager.onDidChangeBreakpoints(e => sourceEvents.push(e));
320
+ manager.onDidChangeFunctionBreakpoints(e => funcEvents.push(e));
321
+ manager.onDidChangeInstructionBreakpoints(e => instrEvents.push(e));
322
+ manager.onDidChangeDataBreakpoints(e => dataEvents.push(e));
323
+ manager.enableAllBreakpoints(true);
324
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)[0].origin.enabled).to.be.true;
325
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()[0].origin.enabled).to.be.true;
326
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()[0].origin.enabled).to.be.true;
327
+ (0, chai_1.expect)(manager.getDataBreakpoints()[0].origin.enabled).to.be.true;
328
+ (0, chai_1.expect)(sourceEvents).to.have.length.greaterThan(0);
329
+ (0, chai_1.expect)(funcEvents).to.have.length.greaterThan(0);
330
+ (0, chai_1.expect)(instrEvents).to.have.length.greaterThan(0);
331
+ (0, chai_1.expect)(dataEvents).to.have.length.greaterThan(0);
332
+ });
333
+ it('enableAllBreakpoints does not fire for types already in target state', () => {
334
+ const bp = makeSourceBreakpoint(FILE_A, 10, { enabled: true });
335
+ manager.setBreakpoints(FILE_A, [bp]);
336
+ // They're already enabled — should still fire because didChange is always true
337
+ // in current implementation (identity-based). This test verifies the function
338
+ // breakpoints emitter doesn't fire when there are no function breakpoints.
339
+ const funcEvents = [];
340
+ manager.onDidChangeFunctionBreakpoints(e => funcEvents.push(e));
341
+ manager.enableAllBreakpoints(true);
342
+ (0, chai_1.expect)(funcEvents).to.have.length(0);
343
+ });
344
+ it('set breakpointsEnabled fires onDidChangeMarkers for all synthetic URIs', () => {
345
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
346
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
347
+ const markerUris = [];
348
+ manager.onDidChangeMarkers(uri => markerUris.push(uri.toString()));
349
+ manager.breakpointsEnabled = false;
350
+ (0, chai_1.expect)(markerUris).to.include(FILE_A.toString());
351
+ (0, chai_1.expect)(markerUris).to.include(breakpoint_manager_1.BreakpointManager.FUNCTION_URI.toString());
352
+ (0, chai_1.expect)(markerUris).to.include(breakpoint_manager_1.BreakpointManager.INSTRUCTION_URI.toString());
353
+ (0, chai_1.expect)(markerUris).to.include(breakpoint_manager_1.BreakpointManager.DATA_URI.toString());
354
+ (0, chai_1.expect)(markerUris).to.include(breakpoint_manager_1.BreakpointManager.EXCEPTION_URI.toString());
355
+ });
356
+ it('set breakpointsEnabled does not fire if value unchanged', () => {
357
+ const markerUris = [];
358
+ manager.onDidChangeMarkers(uri => markerUris.push(uri.toString()));
359
+ manager.breakpointsEnabled = true; // already true
360
+ (0, chai_1.expect)(markerUris).to.have.length(0);
361
+ });
362
+ it('enableBreakpoint fires fireBreakpointChanged', () => {
363
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
364
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
365
+ const events = [];
366
+ manager.onDidChangeBreakpoints(e => events.push(e));
367
+ manager.enableBreakpoint(wrapper, false);
368
+ (0, chai_1.expect)(wrapper.origin.enabled).to.be.false;
369
+ (0, chai_1.expect)(events).to.have.length(1);
370
+ (0, chai_1.expect)(events[0].changed).to.include(wrapper);
371
+ });
372
+ it('enableBreakpoint does not fire if already at target state', () => {
373
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
374
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
375
+ const events = [];
376
+ manager.onDidChangeBreakpoints(e => events.push(e));
377
+ manager.enableBreakpoint(wrapper, true); // already enabled
378
+ (0, chai_1.expect)(events).to.have.length(0);
379
+ });
380
+ });
381
+ describe('BreakpointManager — non-source breakpoint types', () => {
382
+ let manager;
383
+ beforeEach(() => {
384
+ ({ manager } = createManager());
385
+ });
386
+ // Function breakpoints
387
+ it('addFunctionBreakpoint fires correct events', () => {
388
+ const events = [];
389
+ manager.onDidChangeFunctionBreakpoints(e => events.push(e));
390
+ const bp = makeFunctionBreakpoint('myFunction');
391
+ manager.addFunctionBreakpoint(bp);
392
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()).to.have.length(1);
393
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()[0].name).to.equal('myFunction');
394
+ (0, chai_1.expect)(events).to.have.length(1);
395
+ (0, chai_1.expect)(events[0].added).to.have.length(1);
396
+ });
397
+ it('addFunctionBreakpoint with duplicate name is a no-op', () => {
398
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('myFunc'));
399
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('myFunc'));
400
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()).to.have.length(1);
401
+ });
402
+ it('removeFunctionBreakpoint fires correct events', () => {
403
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
404
+ const wrapper = manager.getFunctionBreakpoints()[0];
405
+ const events = [];
406
+ manager.onDidChangeFunctionBreakpoints(e => events.push(e));
407
+ manager.removeFunctionBreakpoint(wrapper);
408
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()).to.have.length(0);
409
+ (0, chai_1.expect)(events).to.have.length(1);
410
+ (0, chai_1.expect)(events[0].removed).to.include(wrapper);
411
+ });
412
+ it('updateFunctionBreakpoint with a name collision removes the colliding breakpoint', () => {
413
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn1'));
414
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn2'));
415
+ const fn1 = manager.getFunctionBreakpoints().find(b => b.name === 'fn1');
416
+ const events = [];
417
+ manager.onDidChangeFunctionBreakpoints(e => events.push(e));
418
+ manager.updateFunctionBreakpoint(fn1, { name: 'fn2' });
419
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()).to.have.length(1);
420
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()[0].name).to.equal('fn2');
421
+ // The event should have a removed entry for the colliding breakpoint
422
+ const removeEvent = events.find(e => e.removed.length > 0);
423
+ (0, chai_1.expect)(removeEvent).to.not.be.undefined;
424
+ });
425
+ // Instruction breakpoints
426
+ it('addInstructionBreakpoint fires correct events', () => {
427
+ const events = [];
428
+ manager.onDidChangeInstructionBreakpoints(e => events.push(e));
429
+ manager.addInstructionBreakpoint('0xDEAD', 0);
430
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()).to.have.length(1);
431
+ (0, chai_1.expect)(events).to.have.length(1);
432
+ (0, chai_1.expect)(events[0].added).to.have.length(1);
433
+ });
434
+ it('addInstructionBreakpoint with duplicate address+offset is a no-op', () => {
435
+ manager.addInstructionBreakpoint('0xBEEF', 4);
436
+ manager.addInstructionBreakpoint('0xBEEF', 4);
437
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()).to.have.length(1);
438
+ });
439
+ it('addInstructionBreakpoint with same address but different offset creates new', () => {
440
+ manager.addInstructionBreakpoint('0xBEEF', 0);
441
+ manager.addInstructionBreakpoint('0xBEEF', 4);
442
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()).to.have.length(2);
443
+ });
444
+ it('removeInstructionBreakpoint fires correct events', () => {
445
+ manager.addInstructionBreakpoint('0xCAFE', 0);
446
+ const wrapper = manager.getInstructionBreakpoints()[0];
447
+ const events = [];
448
+ manager.onDidChangeInstructionBreakpoints(e => events.push(e));
449
+ manager.removeInstructionBreakpoint(wrapper);
450
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()).to.have.length(0);
451
+ (0, chai_1.expect)(events).to.have.length(1);
452
+ (0, chai_1.expect)(events[0].removed).to.include(wrapper);
453
+ });
454
+ // Data breakpoints
455
+ it('addDataBreakpoint fires correct events', () => {
456
+ const events = [];
457
+ manager.onDidChangeDataBreakpoints(e => events.push(e));
458
+ manager.addDataBreakpoint(makeDataBreakpoint('data-1'));
459
+ (0, chai_1.expect)(manager.getDataBreakpoints()).to.have.length(1);
460
+ (0, chai_1.expect)(events).to.have.length(1);
461
+ (0, chai_1.expect)(events[0].added).to.have.length(1);
462
+ });
463
+ it('addDataBreakpoint with duplicate dataId is a no-op', () => {
464
+ manager.addDataBreakpoint(makeDataBreakpoint('data-1'));
465
+ manager.addDataBreakpoint(makeDataBreakpoint('data-1'));
466
+ (0, chai_1.expect)(manager.getDataBreakpoints()).to.have.length(1);
467
+ });
468
+ it('removeDataBreakpoint fires correct events', () => {
469
+ manager.addDataBreakpoint(makeDataBreakpoint('data-1'));
470
+ const wrapper = manager.getDataBreakpoints()[0];
471
+ const events = [];
472
+ manager.onDidChangeDataBreakpoints(e => events.push(e));
473
+ manager.removeDataBreakpoint(wrapper);
474
+ (0, chai_1.expect)(manager.getDataBreakpoints()).to.have.length(0);
475
+ (0, chai_1.expect)(events).to.have.length(1);
476
+ (0, chai_1.expect)(events[0].removed).to.include(wrapper);
477
+ });
478
+ // removeBreakpointsById
479
+ it('removeBreakpointsById removes across all types', () => {
480
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
481
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
482
+ manager.addInstructionBreakpoint('0xABC', 0);
483
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
484
+ const srcId = manager.getBreakpoints(FILE_A)[0].id;
485
+ const fnId = manager.getFunctionBreakpoints()[0].id;
486
+ const instrId = manager.getInstructionBreakpoints()[0].id;
487
+ const dataId = manager.getDataBreakpoints()[0].id;
488
+ manager.removeBreakpointsById([srcId, fnId, instrId, dataId]);
489
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)).to.have.length(0);
490
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()).to.have.length(0);
491
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()).to.have.length(0);
492
+ (0, chai_1.expect)(manager.getDataBreakpoints()).to.have.length(0);
493
+ });
494
+ });
495
+ describe('BreakpointManager — updateSessionData', () => {
496
+ let manager;
497
+ beforeEach(() => {
498
+ ({ manager } = createManager());
499
+ });
500
+ it('with bps map: only matching breakpoints get updated', () => {
501
+ const sb1 = makeSourceBreakpoint(FILE_A, 10);
502
+ const sb2 = makeSourceBreakpoint(FILE_A, 20);
503
+ manager.setBreakpoints(FILE_A, [sb1, sb2]);
504
+ const wrappers = manager.getBreakpoints(FILE_A);
505
+ const bpsMap = new Map();
506
+ bpsMap.set(wrappers[0].id, { id: 1, verified: true, line: 10 });
507
+ manager.updateSessionData('s1', defaultCapabilities, bpsMap);
508
+ (0, chai_1.expect)(wrappers[0].installed).to.be.true;
509
+ (0, chai_1.expect)(wrappers[0].verified).to.be.true;
510
+ (0, chai_1.expect)(wrappers[1].installed).to.be.false; // not in the map — no session touched it
511
+ });
512
+ it('without bps map: all breakpoints have the session removed', () => {
513
+ const sb = makeSourceBreakpoint(FILE_A, 10);
514
+ manager.setBreakpoints(FILE_A, [sb]);
515
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
516
+ // First add session data
517
+ const bpsMap = new Map();
518
+ bpsMap.set(wrapper.id, { id: 1, verified: true, line: 10 });
519
+ manager.updateSessionData('s1', defaultCapabilities, bpsMap);
520
+ (0, chai_1.expect)(wrapper.installed).to.be.true;
521
+ (0, chai_1.expect)(wrapper.verified).to.be.true;
522
+ // Now cleanup (no bps map)
523
+ manager.updateSessionData('s1', defaultCapabilities, undefined);
524
+ (0, chai_1.expect)(wrapper.installed).to.be.false;
525
+ (0, chai_1.expect)(wrapper.raw).to.be.undefined;
526
+ // verified defaults to true when no session has weighed in
527
+ (0, chai_1.expect)(wrapper.verified).to.be.true;
528
+ });
529
+ it('cleanup short-circuits for breakpoints that never had data from the session', () => {
530
+ const sb = makeSourceBreakpoint(FILE_A, 10);
531
+ manager.setBreakpoints(FILE_A, [sb]);
532
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
533
+ // Add data from session-1
534
+ const bpsMap = new Map();
535
+ bpsMap.set(wrapper.id, { id: 1, verified: true, line: 10 });
536
+ manager.updateSessionData('s1', defaultCapabilities, bpsMap);
537
+ // Cleanup session-2 (never contributed) — wrapper should still have s1 data
538
+ manager.updateSessionData('s2', defaultCapabilities, undefined);
539
+ (0, chai_1.expect)(wrapper.verified).to.be.true;
540
+ (0, chai_1.expect)(wrapper.raw.sessionId).to.equal('s1');
541
+ });
542
+ it('capabilities are correctly extracted and merged into BPSessionData', () => {
543
+ const sb = makeSourceBreakpoint(FILE_A, 10);
544
+ manager.setBreakpoints(FILE_A, [sb]);
545
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
546
+ const caps = {
547
+ supportsConditionalBreakpoints: true,
548
+ supportsHitConditionalBreakpoints: true,
549
+ supportsLogPoints: false,
550
+ };
551
+ const bpsMap = new Map();
552
+ bpsMap.set(wrapper.id, { id: 1, verified: true, line: 10 });
553
+ manager.updateSessionData('s1', caps, bpsMap);
554
+ (0, chai_1.expect)(wrapper.raw.supportsConditionalBreakpoints).to.be.true;
555
+ (0, chai_1.expect)(wrapper.raw.supportsHitConditionalBreakpoints).to.be.true;
556
+ (0, chai_1.expect)(wrapper.raw.supportsLogPoints).to.be.false;
557
+ });
558
+ it('typed events fire grouped by URI', () => {
559
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
560
+ manager.setBreakpoints(FILE_B, [makeSourceBreakpoint(FILE_B, 5)]);
561
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
562
+ const sourceEvents = [];
563
+ const funcEvents = [];
564
+ manager.onDidChangeBreakpoints(e => sourceEvents.push(e));
565
+ manager.onDidChangeFunctionBreakpoints(e => funcEvents.push(e));
566
+ const bpsMap = new Map();
567
+ for (const bp of manager.getBreakpoints()) {
568
+ bpsMap.set(bp.id, { id: 1, verified: true, line: bp.line });
569
+ }
570
+ for (const bp of manager.getFunctionBreakpoints()) {
571
+ bpsMap.set(bp.id, { id: 2, verified: true });
572
+ }
573
+ manager.updateSessionData('s1', defaultCapabilities, bpsMap);
574
+ // Source breakpoints for two different URIs → two events
575
+ (0, chai_1.expect)(sourceEvents).to.have.length(2);
576
+ const uris = sourceEvents.map(e => e.uri.toString()).sort();
577
+ (0, chai_1.expect)(uris).to.deep.equal([FILE_A.toString(), FILE_B.toString()].sort());
578
+ // Function breakpoint → one event
579
+ (0, chai_1.expect)(funcEvents).to.have.length(1);
580
+ });
581
+ });
582
+ describe('BreakpointManager — exception breakpoints', () => {
583
+ let manager;
584
+ beforeEach(() => {
585
+ ({ manager } = createManager());
586
+ });
587
+ it('addExceptionBreakpoints creates new for unknown filters', () => {
588
+ const filter = { filter: 'all', label: 'All Exceptions' };
589
+ manager.addExceptionBreakpoints([filter], 'session-1');
590
+ (0, chai_1.expect)(manager.getExceptionBreakpoints()).to.have.length(1);
591
+ (0, chai_1.expect)(manager.getExceptionBreakpoints()[0].origin.raw.filter).to.equal('all');
592
+ });
593
+ it('addExceptionBreakpoints reuses existing for known filters', () => {
594
+ const filter = { filter: 'all', label: 'All Exceptions' };
595
+ manager.addExceptionBreakpoints([filter], 'session-1');
596
+ const first = manager.getExceptionBreakpoints()[0];
597
+ manager.addExceptionBreakpoints([filter], 'session-2');
598
+ (0, chai_1.expect)(manager.getExceptionBreakpoints()).to.have.length(1);
599
+ (0, chai_1.expect)(manager.getExceptionBreakpoints()[0]).to.equal(first);
600
+ });
601
+ it('clearExceptionSessionEnablement removes the session from all enablement sets', () => {
602
+ const filter = { filter: 'all', label: 'All Exceptions' };
603
+ manager.addExceptionBreakpoints([filter], 'session-1');
604
+ const bp = manager.getExceptionBreakpoints()[0];
605
+ (0, chai_1.expect)(bp.isEnabledForSession('session-1')).to.be.true;
606
+ manager.clearExceptionSessionEnablement('session-1');
607
+ (0, chai_1.expect)(bp.isEnabledForSession('session-1')).to.be.false;
608
+ });
609
+ it('persistentlyVisible remains true after session cleanup for filters that were visible', () => {
610
+ const filter = { filter: 'all', label: 'All Exceptions' };
611
+ manager.addExceptionBreakpoints([filter], 'session-1');
612
+ const bp = manager.getExceptionBreakpoints()[0];
613
+ // addExceptionBreakpoints calls doUpdateExceptionBreakpointVisibility which sets persistent visibility
614
+ (0, chai_1.expect)(bp.isPersistentlyVisible()).to.be.true;
615
+ // Clearing session enablement does NOT clear persistent visibility
616
+ manager.clearExceptionSessionEnablement('session-1');
617
+ (0, chai_1.expect)(bp.isPersistentlyVisible()).to.be.true;
618
+ });
619
+ it('getExceptionBreakpoint finds by filter match', () => {
620
+ const filter = { filter: 'uncaught', label: 'Uncaught Exceptions' };
621
+ manager.addExceptionBreakpoints([filter], 'session-1');
622
+ const found = manager.getExceptionBreakpoint(filter);
623
+ (0, chai_1.expect)(found).to.not.be.undefined;
624
+ (0, chai_1.expect)(found.origin.raw.filter).to.equal('uncaught');
625
+ const notFound = manager.getExceptionBreakpoint({ filter: 'other', label: 'Other' });
626
+ (0, chai_1.expect)(notFound).to.be.undefined;
627
+ });
628
+ });
629
+ describe('BreakpointManager — persistence', () => {
630
+ let manager;
631
+ let storageData;
632
+ beforeEach(() => {
633
+ ({ manager, storageData } = createManager());
634
+ });
635
+ it('save() extracts origin from all wrapper types', () => {
636
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
637
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
638
+ manager.addInstructionBreakpoint('0xDEAD', 0);
639
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
640
+ // Add an exception breakpoint that's persistently visible
641
+ const filter = { filter: 'all', label: 'All Exceptions' };
642
+ manager.addExceptionBreakpoints([filter], 'session-1');
643
+ manager.save();
644
+ const data = storageData['breakpoints'];
645
+ (0, chai_1.expect)(data).to.not.be.undefined;
646
+ (0, chai_1.expect)(Object.keys(data.breakpoints)).to.have.length(1);
647
+ (0, chai_1.expect)(data.breakpoints[FILE_A.toString()]).to.have.length(1);
648
+ (0, chai_1.expect)(data.functionBreakpoints).to.have.length(1);
649
+ (0, chai_1.expect)(data.instructionBreakpoints).to.have.length(1);
650
+ (0, chai_1.expect)(data.dataBreakpoints).to.have.length(1);
651
+ (0, chai_1.expect)(data.exceptionBreakpoints).to.have.length(1);
652
+ });
653
+ it('round-trip: save then load produces equivalent breakpoints', async () => {
654
+ const sbp = makeSourceBreakpoint(FILE_A, 42, { condition: 'x > 5' });
655
+ manager.setBreakpoints(FILE_A, [sbp]);
656
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('myFn'));
657
+ manager.breakpointsEnabled = false;
658
+ manager.save();
659
+ // Create a fresh manager and load
660
+ const { manager: manager2 } = createManager();
661
+ // Copy stored data to the new manager's storage
662
+ const freshStorageData = manager2['storage'];
663
+ await freshStorageData.setData('breakpoints', storageData['breakpoints']);
664
+ await manager2.load();
665
+ (0, chai_1.expect)(manager2.breakpointsEnabled).to.be.false;
666
+ const loaded = manager2.getBreakpoints(FILE_A);
667
+ (0, chai_1.expect)(loaded).to.have.length(1);
668
+ (0, chai_1.expect)(loaded[0].origin.raw.line).to.equal(42);
669
+ (0, chai_1.expect)(loaded[0].origin.raw.condition).to.equal('x > 5');
670
+ (0, chai_1.expect)(manager2.getFunctionBreakpoints()).to.have.length(1);
671
+ (0, chai_1.expect)(manager2.getFunctionBreakpoints()[0].name).to.equal('myFn');
672
+ });
673
+ it('exception breakpoints: only persistentlyVisible ones are saved', () => {
674
+ const filter1 = { filter: 'all', label: 'All' };
675
+ const filter2 = { filter: 'uncaught', label: 'Uncaught' };
676
+ manager.addExceptionBreakpoints([filter1], 'session-1');
677
+ manager.addExceptionBreakpoints([filter2], 'session-1');
678
+ // filter1 and filter2 are persistentlyVisible after addExceptionBreakpoints
679
+ // Now mark one as not persistently visible
680
+ const bp2 = manager.getExceptionBreakpoints()[1];
681
+ bp2.setPersistentVisibility(false);
682
+ manager.save();
683
+ const data = storageData['breakpoints'];
684
+ (0, chai_1.expect)(data.exceptionBreakpoints).to.have.length(1);
685
+ (0, chai_1.expect)(data.exceptionBreakpoints[0].raw.filter).to.equal('all');
686
+ });
687
+ });
688
+ describe('BreakpointManager — file deletion', () => {
689
+ let manager;
690
+ let fileChangeEmitter;
691
+ beforeEach(() => {
692
+ ({ manager, fileChangeEmitter } = createManager());
693
+ });
694
+ it('when a file is deleted, its source breakpoints are removed and onDidChangeMarkers fires', () => {
695
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
696
+ manager.setBreakpoints(FILE_B, [makeSourceBreakpoint(FILE_B, 5)]);
697
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)).to.have.length(1);
698
+ const markerUris = [];
699
+ manager.onDidChangeMarkers(uri => markerUris.push(uri.toString()));
700
+ fileChangeEmitter.fire(new files_1.FileChangesEvent([{
701
+ resource: FILE_A,
702
+ type: 2 /* FileChangeType.DELETED */,
703
+ }]));
704
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_A)).to.have.length(0);
705
+ (0, chai_1.expect)(markerUris).to.include(FILE_A.toString());
706
+ // FILE_B should be untouched
707
+ (0, chai_1.expect)(manager.getBreakpoints(FILE_B)).to.have.length(1);
708
+ });
709
+ });
710
+ describe('BreakpointManager — fireTypedBreakpointEvent dispatch', () => {
711
+ let manager;
712
+ beforeEach(() => {
713
+ ({ manager } = createManager());
714
+ });
715
+ it('fires onDidChangeBreakpoints for DebugSourceBreakpoint instances', () => {
716
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
717
+ const events = [];
718
+ manager.onDidChangeBreakpoints(e => events.push(e));
719
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
720
+ manager.fireBreakpointChanged(wrapper);
721
+ (0, chai_1.expect)(events).to.have.length(1);
722
+ (0, chai_1.expect)(events[0].changed).to.include(wrapper);
723
+ });
724
+ it('fires onDidChangeFunctionBreakpoints for DebugFunctionBreakpoint instances', () => {
725
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
726
+ const events = [];
727
+ manager.onDidChangeFunctionBreakpoints(e => events.push(e));
728
+ const wrapper = manager.getFunctionBreakpoints()[0];
729
+ manager.fireBreakpointChanged(wrapper);
730
+ (0, chai_1.expect)(events).to.have.length(1);
731
+ (0, chai_1.expect)(events[0].changed[0]).to.equal(wrapper);
732
+ });
733
+ it('fires onDidChangeInstructionBreakpoints for DebugInstructionBreakpoint instances', () => {
734
+ manager.addInstructionBreakpoint('0xABC', 0);
735
+ const events = [];
736
+ manager.onDidChangeInstructionBreakpoints(e => events.push(e));
737
+ const wrapper = manager.getInstructionBreakpoints()[0];
738
+ manager.fireBreakpointChanged(wrapper);
739
+ (0, chai_1.expect)(events).to.have.length(1);
740
+ (0, chai_1.expect)(events[0].changed[0]).to.equal(wrapper);
741
+ });
742
+ it('fires onDidChangeDataBreakpoints for DebugDataBreakpoint instances', () => {
743
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
744
+ const events = [];
745
+ manager.onDidChangeDataBreakpoints(e => events.push(e));
746
+ const wrapper = manager.getDataBreakpoints()[0];
747
+ manager.fireBreakpointChanged(wrapper);
748
+ (0, chai_1.expect)(events).to.have.length(1);
749
+ (0, chai_1.expect)(events[0].changed[0]).to.equal(wrapper);
750
+ });
751
+ });
752
+ describe('BreakpointManager — query helpers', () => {
753
+ let manager;
754
+ beforeEach(() => {
755
+ ({ manager } = createManager());
756
+ });
757
+ it('getLineBreakpoints returns breakpoints at a specific line', () => {
758
+ manager.setBreakpoints(FILE_A, [
759
+ makeSourceBreakpoint(FILE_A, 10),
760
+ makeSourceBreakpoint(FILE_A, 20),
761
+ makeSourceBreakpoint(FILE_A, 10, { column: 5 }),
762
+ ]);
763
+ const atLine10 = manager.getLineBreakpoints(FILE_A, 10);
764
+ (0, chai_1.expect)(atLine10).to.have.length(2);
765
+ });
766
+ it('getBreakpointById finds across all types', () => {
767
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
768
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
769
+ manager.addInstructionBreakpoint('0x1', 0);
770
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
771
+ const srcBp = manager.getBreakpoints(FILE_A)[0];
772
+ const fnBp = manager.getFunctionBreakpoints()[0];
773
+ const instrBp = manager.getInstructionBreakpoints()[0];
774
+ const dataBp = manager.getDataBreakpoints()[0];
775
+ (0, chai_1.expect)(manager.getBreakpointById(srcBp.id)).to.equal(srcBp);
776
+ (0, chai_1.expect)(manager.getBreakpointById(fnBp.id)).to.equal(fnBp);
777
+ (0, chai_1.expect)(manager.getBreakpointById(instrBp.id)).to.equal(instrBp);
778
+ (0, chai_1.expect)(manager.getBreakpointById(dataBp.id)).to.equal(dataBp);
779
+ (0, chai_1.expect)(manager.getBreakpointById('nonexistent')).to.be.undefined;
780
+ });
781
+ it('allBreakpoints yields all types', () => {
782
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
783
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
784
+ manager.addInstructionBreakpoint('0x1', 0);
785
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
786
+ manager.addExceptionBreakpoints([{ filter: 'all', label: 'All' }], 's1');
787
+ const all = [...manager.allBreakpoints()];
788
+ (0, chai_1.expect)(all).to.have.length(5);
789
+ const types = all.map(bp => bp.constructor.name);
790
+ (0, chai_1.expect)(types).to.include('DebugSourceBreakpoint');
791
+ (0, chai_1.expect)(types).to.include('DebugFunctionBreakpoint');
792
+ (0, chai_1.expect)(types).to.include('DebugInstructionBreakpoint');
793
+ (0, chai_1.expect)(types).to.include('DebugDataBreakpoint');
794
+ (0, chai_1.expect)(types).to.include('DebugExceptionBreakpoint');
795
+ });
796
+ it('hasBreakpoints returns true when any type exists', () => {
797
+ (0, chai_1.expect)(manager.hasBreakpoints()).to.be.false;
798
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
799
+ (0, chai_1.expect)(manager.hasBreakpoints()).to.be.true;
800
+ });
801
+ it('getUris returns all URIs with source breakpoints', () => {
802
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
803
+ manager.setBreakpoints(FILE_B, [makeSourceBreakpoint(FILE_B, 5)]);
804
+ const uris = [...manager.getUris()];
805
+ (0, chai_1.expect)(uris).to.have.length(2);
806
+ (0, chai_1.expect)(uris).to.include(FILE_A.toString());
807
+ (0, chai_1.expect)(uris).to.include(FILE_B.toString());
808
+ });
809
+ it('getBreakpoints with no URI returns all source breakpoints', () => {
810
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
811
+ manager.setBreakpoints(FILE_B, [makeSourceBreakpoint(FILE_B, 5)]);
812
+ const all = manager.getBreakpoints();
813
+ (0, chai_1.expect)(all).to.have.length(2);
814
+ });
815
+ it('removeBreakpoints clears all breakpoints of all types', () => {
816
+ manager.setBreakpoints(FILE_A, [makeSourceBreakpoint(FILE_A, 10)]);
817
+ manager.addFunctionBreakpoint(makeFunctionBreakpoint('fn'));
818
+ manager.addInstructionBreakpoint('0x1', 0);
819
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
820
+ manager.removeBreakpoints();
821
+ (0, chai_1.expect)(manager.getBreakpoints()).to.have.length(0);
822
+ (0, chai_1.expect)(manager.getFunctionBreakpoints()).to.have.length(0);
823
+ (0, chai_1.expect)(manager.getInstructionBreakpoints()).to.have.length(0);
824
+ (0, chai_1.expect)(manager.getDataBreakpoints()).to.have.length(0);
825
+ });
826
+ });
827
+ describe('BreakpointManager — updateBreakpoint', () => {
828
+ let manager;
829
+ beforeEach(() => {
830
+ ({ manager } = createManager());
831
+ });
832
+ it('updateBreakpoint merges partial raw and fires changed event', () => {
833
+ const bp = makeSourceBreakpoint(FILE_A, 10);
834
+ manager.setBreakpoints(FILE_A, [bp]);
835
+ const wrapper = manager.getBreakpoints(FILE_A)[0];
836
+ const events = [];
837
+ manager.onDidChangeBreakpoints(e => events.push(e));
838
+ manager.updateBreakpoint(wrapper, { condition: 'x > 10' });
839
+ (0, chai_1.expect)(wrapper.origin.raw.condition).to.equal('x > 10');
840
+ (0, chai_1.expect)(wrapper.origin.raw.line).to.equal(10); // line preserved
841
+ (0, chai_1.expect)(events).to.have.length(1);
842
+ });
843
+ it('updateDataBreakpoint updates enabled and raw fields', () => {
844
+ manager.addDataBreakpoint(makeDataBreakpoint('d1'));
845
+ const wrapper = manager.getDataBreakpoints()[0];
846
+ const events = [];
847
+ manager.onDidChangeDataBreakpoints(e => events.push(e));
848
+ manager.updateDataBreakpoint(wrapper, { enabled: false, raw: { condition: 'val > 0' } });
849
+ (0, chai_1.expect)(wrapper.origin.enabled).to.be.false;
850
+ (0, chai_1.expect)(wrapper.origin.raw.condition).to.equal('val > 0');
851
+ (0, chai_1.expect)(events).to.have.length(1);
852
+ });
853
+ it('updateDataBreakpoint on unknown breakpoint is a no-op', () => {
854
+ const orphan = debug_data_breakpoint_1.DebugDataBreakpoint.create(makeDataBreakpoint('orphan'), manager.getBreakpointOptions());
855
+ const events = [];
856
+ manager.onDidChangeDataBreakpoints(e => events.push(e));
857
+ manager.updateDataBreakpoint(orphan, { enabled: false });
858
+ (0, chai_1.expect)(events).to.have.length(0);
859
+ });
860
+ });
861
+ //# sourceMappingURL=breakpoint-manager.spec.js.map