edsger 0.45.0 → 0.46.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.
- package/dist/commands/workflow/executors/phase-executor.js +3 -1
- package/dist/commands/workflow/phase-orchestrator.js +1 -2
- package/dist/phases/app-store-generation/index.js +1 -2
- package/dist/phases/branch-planning/index.js +1 -2
- package/dist/phases/bug-fixing/analyzer.js +1 -2
- package/dist/phases/code-implementation/index.js +1 -2
- package/dist/phases/code-refine/index.js +1 -2
- package/dist/phases/code-review/index.js +1 -2
- package/dist/phases/code-testing/analyzer.js +1 -2
- package/dist/phases/feature-analysis/index.js +1 -2
- package/dist/phases/functional-testing/analyzer.js +1 -2
- package/dist/phases/growth-analysis/index.js +1 -2
- package/dist/phases/pr-execution/index.js +1 -0
- package/dist/phases/pr-splitting/index.js +1 -2
- package/dist/phases/run-sheet/index.js +7 -7
- package/dist/phases/run-sheet/render.js +3 -1
- package/dist/phases/smoke-test/agent.js +2 -4
- package/dist/phases/smoke-test/index.js +11 -6
- package/dist/phases/technical-design/index.js +1 -2
- package/dist/phases/test-cases-analysis/index.js +1 -2
- package/dist/phases/user-stories-analysis/index.js +1 -2
- package/package.json +3 -3
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +3 -9
- package/dist/api/__tests__/app-store.test.d.ts +0 -7
- package/dist/api/__tests__/app-store.test.js +0 -60
- package/dist/api/__tests__/intelligence.test.d.ts +0 -11
- package/dist/api/__tests__/intelligence.test.js +0 -315
- package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
- package/dist/api/features/__tests__/feature-utils.test.js +0 -370
- package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
- package/dist/api/features/__tests__/status-updater.test.js +0 -88
- package/dist/commands/build/__tests__/build.test.d.ts +0 -5
- package/dist/commands/build/__tests__/build.test.js +0 -206
- package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
- package/dist/commands/build/__tests__/detect-project.test.js +0 -160
- package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
- package/dist/commands/build/__tests__/run-build.test.js +0 -433
- package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
- package/dist/commands/intelligence/__tests__/command.test.js +0 -48
- package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
- package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
- package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
- package/dist/config/__tests__/config.test.d.ts +0 -4
- package/dist/config/__tests__/config.test.js +0 -286
- package/dist/config/__tests__/feature-status.test.d.ts +0 -4
- package/dist/config/__tests__/feature-status.test.js +0 -111
- package/dist/errors/__tests__/index.test.d.ts +0 -4
- package/dist/errors/__tests__/index.test.js +0 -349
- package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
- package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
- package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
- package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
- package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
- package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
- package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
- package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
- package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
- package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
- package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
- package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
- package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
- package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
- package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
- package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
- package/dist/phases/release-sync/__tests__/github.test.js +0 -123
- package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
- package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
- package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
- package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
- package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
- package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
- package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
- package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
- package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
- package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
- package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
- package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert';
|
|
2
|
-
import { describe, it } from 'node:test';
|
|
3
|
-
import { autoFixPROrdering, getTransitiveDependencies, parseImportSpecifiers, resolveImportToChangedFile, } from '../import-dep-validator.js';
|
|
4
|
-
// ============================================================
|
|
5
|
-
// parseImportSpecifiers
|
|
6
|
-
// ============================================================
|
|
7
|
-
void describe('parseImportSpecifiers', () => {
|
|
8
|
-
void it('parses named imports', () => {
|
|
9
|
-
const result = parseImportSpecifiers(`import { foo } from './utils'`);
|
|
10
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
11
|
-
});
|
|
12
|
-
void it('parses default imports', () => {
|
|
13
|
-
const result = parseImportSpecifiers(`import foo from './utils'`);
|
|
14
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
15
|
-
});
|
|
16
|
-
void it('parses namespace imports', () => {
|
|
17
|
-
const result = parseImportSpecifiers(`import * as foo from './utils'`);
|
|
18
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
19
|
-
});
|
|
20
|
-
void it('parses side-effect imports', () => {
|
|
21
|
-
const result = parseImportSpecifiers(`import './polyfill'`);
|
|
22
|
-
assert.deepStrictEqual(result, ['./polyfill']);
|
|
23
|
-
});
|
|
24
|
-
void it('parses type-only imports', () => {
|
|
25
|
-
const result = parseImportSpecifiers(`import type { Foo } from './types'`);
|
|
26
|
-
assert.deepStrictEqual(result, ['./types']);
|
|
27
|
-
});
|
|
28
|
-
void it('parses re-exports', () => {
|
|
29
|
-
const result = parseImportSpecifiers(`export { foo } from './utils'`);
|
|
30
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
31
|
-
});
|
|
32
|
-
void it('parses star re-exports', () => {
|
|
33
|
-
const result = parseImportSpecifiers(`export * from './utils'`);
|
|
34
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
35
|
-
});
|
|
36
|
-
void it('parses dynamic imports', () => {
|
|
37
|
-
const result = parseImportSpecifiers(`const m = import('./lazy')`);
|
|
38
|
-
assert.deepStrictEqual(result, ['./lazy']);
|
|
39
|
-
});
|
|
40
|
-
void it('parses multiple imports', () => {
|
|
41
|
-
const source = `
|
|
42
|
-
import { foo } from './utils'
|
|
43
|
-
import Bar from '../components/Bar'
|
|
44
|
-
import type { Baz } from './types'
|
|
45
|
-
`;
|
|
46
|
-
const result = parseImportSpecifiers(source);
|
|
47
|
-
assert.deepStrictEqual(result, ['./utils', '../components/Bar', './types']);
|
|
48
|
-
});
|
|
49
|
-
void it('filters out non-relative imports', () => {
|
|
50
|
-
const source = `
|
|
51
|
-
import { X } from 'lodash'
|
|
52
|
-
import React from 'react'
|
|
53
|
-
import { Y } from './local'
|
|
54
|
-
import { Z } from '@scope/package'
|
|
55
|
-
`;
|
|
56
|
-
const result = parseImportSpecifiers(source);
|
|
57
|
-
assert.deepStrictEqual(result, ['./local']);
|
|
58
|
-
});
|
|
59
|
-
void it('parses multiline imports', () => {
|
|
60
|
-
const source = `import {
|
|
61
|
-
foo,
|
|
62
|
-
bar,
|
|
63
|
-
baz
|
|
64
|
-
} from './utils'`;
|
|
65
|
-
const result = parseImportSpecifiers(source);
|
|
66
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
67
|
-
});
|
|
68
|
-
void it('deduplicates specifiers', () => {
|
|
69
|
-
const source = `
|
|
70
|
-
import { foo } from './utils'
|
|
71
|
-
import { bar } from './utils'
|
|
72
|
-
`;
|
|
73
|
-
const result = parseImportSpecifiers(source);
|
|
74
|
-
assert.deepStrictEqual(result, ['./utils']);
|
|
75
|
-
});
|
|
76
|
-
void it('handles .js extension in specifier', () => {
|
|
77
|
-
const result = parseImportSpecifiers(`import { X } from './utils.js'`);
|
|
78
|
-
assert.deepStrictEqual(result, ['./utils.js']);
|
|
79
|
-
});
|
|
80
|
-
void it('returns empty for empty source', () => {
|
|
81
|
-
assert.deepStrictEqual(parseImportSpecifiers(''), []);
|
|
82
|
-
});
|
|
83
|
-
void it('returns empty for non-relative only', () => {
|
|
84
|
-
const source = `import express from 'express'`;
|
|
85
|
-
assert.deepStrictEqual(parseImportSpecifiers(source), []);
|
|
86
|
-
});
|
|
87
|
-
void it('parses export default from', () => {
|
|
88
|
-
const result = parseImportSpecifiers(`export { default as X } from './module'`);
|
|
89
|
-
assert.deepStrictEqual(result, ['./module']);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
// ============================================================
|
|
93
|
-
// resolveImportToChangedFile
|
|
94
|
-
// ============================================================
|
|
95
|
-
void describe('resolveImportToChangedFile', () => {
|
|
96
|
-
void it('resolves exact match with extension', () => {
|
|
97
|
-
const changed = new Set(['src/utils.ts']);
|
|
98
|
-
const result = resolveImportToChangedFile('./utils.ts', 'src/app.ts', changed);
|
|
99
|
-
assert.strictEqual(result, 'src/utils.ts');
|
|
100
|
-
});
|
|
101
|
-
void it('infers .ts extension', () => {
|
|
102
|
-
const changed = new Set(['src/utils.ts']);
|
|
103
|
-
const result = resolveImportToChangedFile('./utils', 'src/app.ts', changed);
|
|
104
|
-
assert.strictEqual(result, 'src/utils.ts');
|
|
105
|
-
});
|
|
106
|
-
void it('infers .tsx extension', () => {
|
|
107
|
-
const changed = new Set(['src/Component.tsx']);
|
|
108
|
-
const result = resolveImportToChangedFile('./Component', 'src/app.ts', changed);
|
|
109
|
-
assert.strictEqual(result, 'src/Component.tsx');
|
|
110
|
-
});
|
|
111
|
-
void it('resolves index file', () => {
|
|
112
|
-
const changed = new Set(['src/types/index.ts']);
|
|
113
|
-
const result = resolveImportToChangedFile('./types', 'src/app.ts', changed);
|
|
114
|
-
assert.strictEqual(result, 'src/types/index.ts');
|
|
115
|
-
});
|
|
116
|
-
void it('resolves .js to .ts (ESM convention)', () => {
|
|
117
|
-
const changed = new Set(['src/utils.ts']);
|
|
118
|
-
const result = resolveImportToChangedFile('./utils.js', 'src/app.ts', changed);
|
|
119
|
-
assert.strictEqual(result, 'src/utils.ts');
|
|
120
|
-
});
|
|
121
|
-
void it('resolves parent directory traversal', () => {
|
|
122
|
-
const changed = new Set(['src/shared/helpers.ts']);
|
|
123
|
-
const result = resolveImportToChangedFile('../shared/helpers', 'src/components/Button.ts', changed);
|
|
124
|
-
assert.strictEqual(result, 'src/shared/helpers.ts');
|
|
125
|
-
});
|
|
126
|
-
void it('resolves double parent traversal', () => {
|
|
127
|
-
const changed = new Set(['src/utils.ts']);
|
|
128
|
-
const result = resolveImportToChangedFile('../../utils', 'src/features/explore/App.ts', changed);
|
|
129
|
-
assert.strictEqual(result, 'src/utils.ts');
|
|
130
|
-
});
|
|
131
|
-
void it('returns null for non-changed file', () => {
|
|
132
|
-
const changed = new Set(['src/other.ts']);
|
|
133
|
-
const result = resolveImportToChangedFile('./utils', 'src/app.ts', changed);
|
|
134
|
-
assert.strictEqual(result, null);
|
|
135
|
-
});
|
|
136
|
-
void it('returns null for external package import path', () => {
|
|
137
|
-
// This shouldn't happen since parseImportSpecifiers filters these,
|
|
138
|
-
// but test defensive behavior
|
|
139
|
-
const changed = new Set(['src/utils.ts']);
|
|
140
|
-
const result = resolveImportToChangedFile('./nonexistent', 'src/app.ts', changed);
|
|
141
|
-
assert.strictEqual(result, null);
|
|
142
|
-
});
|
|
143
|
-
void it('prefers .ts over .tsx when both exist', () => {
|
|
144
|
-
const changed = new Set(['src/utils.ts', 'src/utils.tsx']);
|
|
145
|
-
const result = resolveImportToChangedFile('./utils', 'src/app.ts', changed);
|
|
146
|
-
assert.strictEqual(result, 'src/utils.ts');
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
// ============================================================
|
|
150
|
-
// getTransitiveDependencies
|
|
151
|
-
// ============================================================
|
|
152
|
-
void describe('getTransitiveDependencies', () => {
|
|
153
|
-
void it('returns direct dependency', () => {
|
|
154
|
-
const graph = new Map([
|
|
155
|
-
['A', new Set(['B'])],
|
|
156
|
-
['B', new Set()],
|
|
157
|
-
]);
|
|
158
|
-
const result = getTransitiveDependencies('A', graph);
|
|
159
|
-
assert.deepStrictEqual(result, new Set(['B']));
|
|
160
|
-
});
|
|
161
|
-
void it('returns transitive dependencies', () => {
|
|
162
|
-
const graph = new Map([
|
|
163
|
-
['A', new Set(['B'])],
|
|
164
|
-
['B', new Set(['C'])],
|
|
165
|
-
['C', new Set()],
|
|
166
|
-
]);
|
|
167
|
-
const result = getTransitiveDependencies('A', graph);
|
|
168
|
-
assert.deepStrictEqual(result, new Set(['B', 'C']));
|
|
169
|
-
});
|
|
170
|
-
void it('handles diamond dependency', () => {
|
|
171
|
-
const graph = new Map([
|
|
172
|
-
['A', new Set(['B', 'C'])],
|
|
173
|
-
['B', new Set(['D'])],
|
|
174
|
-
['C', new Set(['D'])],
|
|
175
|
-
['D', new Set()],
|
|
176
|
-
]);
|
|
177
|
-
const result = getTransitiveDependencies('A', graph);
|
|
178
|
-
assert.deepStrictEqual(result, new Set(['B', 'C', 'D']));
|
|
179
|
-
});
|
|
180
|
-
void it('handles cycles without infinite loop', () => {
|
|
181
|
-
const graph = new Map([
|
|
182
|
-
['A', new Set(['B'])],
|
|
183
|
-
['B', new Set(['A'])],
|
|
184
|
-
]);
|
|
185
|
-
const result = getTransitiveDependencies('A', graph);
|
|
186
|
-
assert.deepStrictEqual(result, new Set(['B', 'A']));
|
|
187
|
-
});
|
|
188
|
-
void it('returns empty set for no dependencies', () => {
|
|
189
|
-
const graph = new Map([['A', new Set()]]);
|
|
190
|
-
const result = getTransitiveDependencies('A', graph);
|
|
191
|
-
assert.deepStrictEqual(result, new Set());
|
|
192
|
-
});
|
|
193
|
-
void it('returns empty set for unknown file', () => {
|
|
194
|
-
const graph = new Map();
|
|
195
|
-
const result = getTransitiveDependencies('unknown', graph);
|
|
196
|
-
assert.deepStrictEqual(result, new Set());
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
// ============================================================
|
|
200
|
-
// autoFixPROrdering
|
|
201
|
-
// ============================================================
|
|
202
|
-
function makePR(sequence, name, files, dependsOn) {
|
|
203
|
-
return {
|
|
204
|
-
sequence,
|
|
205
|
-
name,
|
|
206
|
-
description: name,
|
|
207
|
-
branch_name: `pr/feat/${sequence}-${name.toLowerCase().replace(/\s/g, '-')}`,
|
|
208
|
-
depends_on_branch_name: dependsOn ?? null,
|
|
209
|
-
files: files.map((f) => ({ path: f, change_type: 'modified' })),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
void describe('autoFixPROrdering', () => {
|
|
213
|
-
void it('returns unchanged when no violations', () => {
|
|
214
|
-
const prs = [
|
|
215
|
-
makePR(1, 'Foundation', ['src/utils.ts']),
|
|
216
|
-
makePR(2, 'Components', ['src/app.ts'], 'pr/feat/1-foundation'),
|
|
217
|
-
];
|
|
218
|
-
// app.ts depends on utils.ts, and utils.ts is in PR 1 (earlier) — OK
|
|
219
|
-
const graph = new Map([
|
|
220
|
-
['src/app.ts', new Set(['src/utils.ts'])],
|
|
221
|
-
['src/utils.ts', new Set()],
|
|
222
|
-
]);
|
|
223
|
-
const result = autoFixPROrdering(prs, graph);
|
|
224
|
-
assert.strictEqual(result.movedFiles.length, 0);
|
|
225
|
-
assert.strictEqual(result.pullRequests[0].files?.length, 1);
|
|
226
|
-
assert.strictEqual(result.pullRequests[1].files?.length, 1);
|
|
227
|
-
});
|
|
228
|
-
void it('moves dependency from later PR to earlier PR', () => {
|
|
229
|
-
const prs = [
|
|
230
|
-
makePR(1, 'Components', ['src/app.ts']),
|
|
231
|
-
makePR(2, 'Utils', ['src/utils.ts'], 'pr/feat/1-components'),
|
|
232
|
-
];
|
|
233
|
-
// app.ts (PR 1) depends on utils.ts (PR 2) — violation!
|
|
234
|
-
const graph = new Map([
|
|
235
|
-
['src/app.ts', new Set(['src/utils.ts'])],
|
|
236
|
-
['src/utils.ts', new Set()],
|
|
237
|
-
]);
|
|
238
|
-
const result = autoFixPROrdering(prs, graph);
|
|
239
|
-
assert.strictEqual(result.movedFiles.length, 1);
|
|
240
|
-
assert.strictEqual(result.movedFiles[0].file, 'src/utils.ts');
|
|
241
|
-
// utils.ts should now be in PR 1
|
|
242
|
-
const pr1Files = result.pullRequests[0].files?.map((f) => f.path) ?? [];
|
|
243
|
-
assert.ok(pr1Files.includes('src/utils.ts'));
|
|
244
|
-
assert.ok(pr1Files.includes('src/app.ts'));
|
|
245
|
-
// PR 2 should be empty
|
|
246
|
-
assert.strictEqual(result.pullRequests[1].files?.length, 0);
|
|
247
|
-
});
|
|
248
|
-
void it('handles transitive dependency moves', () => {
|
|
249
|
-
const prs = [
|
|
250
|
-
makePR(1, 'App', ['src/app.ts']),
|
|
251
|
-
makePR(2, 'Service', ['src/service.ts'], 'pr/feat/1-app'),
|
|
252
|
-
makePR(3, 'Utils', ['src/utils.ts'], 'pr/feat/2-service'),
|
|
253
|
-
];
|
|
254
|
-
// app.ts → service.ts → utils.ts (chain across 3 PRs)
|
|
255
|
-
const graph = new Map([
|
|
256
|
-
['src/app.ts', new Set(['src/service.ts'])],
|
|
257
|
-
['src/service.ts', new Set(['src/utils.ts'])],
|
|
258
|
-
['src/utils.ts', new Set()],
|
|
259
|
-
]);
|
|
260
|
-
const result = autoFixPROrdering(prs, graph);
|
|
261
|
-
// Both service.ts and utils.ts should move to PR 1
|
|
262
|
-
const pr1Files = result.pullRequests[0].files?.map((f) => f.path) ?? [];
|
|
263
|
-
assert.ok(pr1Files.includes('src/app.ts'));
|
|
264
|
-
assert.ok(pr1Files.includes('src/service.ts'));
|
|
265
|
-
assert.ok(pr1Files.includes('src/utils.ts'));
|
|
266
|
-
});
|
|
267
|
-
void it('moves dep to earliest PR that needs it', () => {
|
|
268
|
-
const prs = [
|
|
269
|
-
makePR(1, 'PR1', ['src/a.ts']),
|
|
270
|
-
makePR(2, 'PR2', ['src/b.ts'], 'pr/feat/1-pr1'),
|
|
271
|
-
makePR(3, 'PR3', ['src/utils.ts'], 'pr/feat/2-pr2'),
|
|
272
|
-
];
|
|
273
|
-
// Both a.ts (PR 1) and b.ts (PR 2) import utils.ts (PR 3)
|
|
274
|
-
const graph = new Map([
|
|
275
|
-
['src/a.ts', new Set(['src/utils.ts'])],
|
|
276
|
-
['src/b.ts', new Set(['src/utils.ts'])],
|
|
277
|
-
['src/utils.ts', new Set()],
|
|
278
|
-
]);
|
|
279
|
-
const result = autoFixPROrdering(prs, graph);
|
|
280
|
-
// utils.ts should move to PR 1 (earliest needer)
|
|
281
|
-
const pr1Files = result.pullRequests[0].files?.map((f) => f.path) ?? [];
|
|
282
|
-
assert.ok(pr1Files.includes('src/utils.ts'));
|
|
283
|
-
});
|
|
284
|
-
void it('handles no dependencies at all', () => {
|
|
285
|
-
const prs = [
|
|
286
|
-
makePR(1, 'PR1', ['src/a.ts']),
|
|
287
|
-
makePR(2, 'PR2', ['src/b.ts'], 'pr/feat/1-pr1'),
|
|
288
|
-
];
|
|
289
|
-
const graph = new Map([
|
|
290
|
-
['src/a.ts', new Set()],
|
|
291
|
-
['src/b.ts', new Set()],
|
|
292
|
-
]);
|
|
293
|
-
const result = autoFixPROrdering(prs, graph);
|
|
294
|
-
assert.strictEqual(result.movedFiles.length, 0);
|
|
295
|
-
});
|
|
296
|
-
void it('handles single PR with all files', () => {
|
|
297
|
-
const prs = [makePR(1, 'Everything', ['src/a.ts', 'src/b.ts', 'src/c.ts'])];
|
|
298
|
-
const graph = new Map([
|
|
299
|
-
['src/a.ts', new Set(['src/b.ts'])],
|
|
300
|
-
['src/b.ts', new Set(['src/c.ts'])],
|
|
301
|
-
['src/c.ts', new Set()],
|
|
302
|
-
]);
|
|
303
|
-
const result = autoFixPROrdering(prs, graph);
|
|
304
|
-
assert.strictEqual(result.movedFiles.length, 0);
|
|
305
|
-
});
|
|
306
|
-
void it('does not mutate original PR files arrays', () => {
|
|
307
|
-
const originalFiles = [{ path: 'src/app.ts', change_type: 'modified' }];
|
|
308
|
-
const prs = [
|
|
309
|
-
{
|
|
310
|
-
sequence: 1,
|
|
311
|
-
name: 'PR1',
|
|
312
|
-
description: 'PR1',
|
|
313
|
-
files: originalFiles,
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
sequence: 2,
|
|
317
|
-
name: 'PR2',
|
|
318
|
-
description: 'PR2',
|
|
319
|
-
files: [{ path: 'src/utils.ts', change_type: 'modified' }],
|
|
320
|
-
},
|
|
321
|
-
];
|
|
322
|
-
const graph = new Map([
|
|
323
|
-
['src/app.ts', new Set(['src/utils.ts'])],
|
|
324
|
-
['src/utils.ts', new Set()],
|
|
325
|
-
]);
|
|
326
|
-
autoFixPROrdering(prs, graph);
|
|
327
|
-
// Original files array should not be modified
|
|
328
|
-
assert.strictEqual(originalFiles.length, 1);
|
|
329
|
-
assert.strictEqual(originalFiles[0].path, 'src/app.ts');
|
|
330
|
-
});
|
|
331
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for smoke-test github helpers.
|
|
3
|
-
*
|
|
4
|
-
* Covers the pure functions that shape GitHub compare data into a
|
|
5
|
-
* prompt-ready digest. Network-facing functions (fetchLatestTwoReleases,
|
|
6
|
-
* fetchCompare) are exercised only indirectly — their output shape is
|
|
7
|
-
* fed through buildDiffDigest / summariseStats here.
|
|
8
|
-
*/
|
|
9
|
-
export {};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for smoke-test github helpers.
|
|
3
|
-
*
|
|
4
|
-
* Covers the pure functions that shape GitHub compare data into a
|
|
5
|
-
* prompt-ready digest. Network-facing functions (fetchLatestTwoReleases,
|
|
6
|
-
* fetchCompare) are exercised only indirectly — their output shape is
|
|
7
|
-
* fed through buildDiffDigest / summariseStats here.
|
|
8
|
-
*/
|
|
9
|
-
import assert from 'node:assert';
|
|
10
|
-
import { describe, it } from 'node:test';
|
|
11
|
-
import { buildDiffDigest, summariseStats, } from '../github.js';
|
|
12
|
-
function makeCompare(over = {}) {
|
|
13
|
-
return {
|
|
14
|
-
total_commits: 2,
|
|
15
|
-
commits: [
|
|
16
|
-
{
|
|
17
|
-
sha: '1111111aaaaaaaa',
|
|
18
|
-
commit: {
|
|
19
|
-
message: 'feat: add checkout flow\n\nmore body',
|
|
20
|
-
author: null,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
sha: '2222222bbbbbbbb',
|
|
25
|
-
commit: { message: 'fix: null deref', author: { name: 'Ada' } },
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
files: [
|
|
29
|
-
{
|
|
30
|
-
filename: 'src/checkout.ts',
|
|
31
|
-
status: 'modified',
|
|
32
|
-
additions: 10,
|
|
33
|
-
deletions: 2,
|
|
34
|
-
changes: 12,
|
|
35
|
-
patch: '@@ -1 +1 @@\n-old\n+new',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
filename: 'src/util.ts',
|
|
39
|
-
status: 'added',
|
|
40
|
-
additions: 5,
|
|
41
|
-
deletions: 0,
|
|
42
|
-
changes: 5,
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
...over,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
void describe('summariseStats', () => {
|
|
49
|
-
void it('sums additions, deletions, and file count', () => {
|
|
50
|
-
const stats = summariseStats(makeCompare());
|
|
51
|
-
assert.deepStrictEqual(stats, {
|
|
52
|
-
files_changed: 2,
|
|
53
|
-
additions: 15,
|
|
54
|
-
deletions: 2,
|
|
55
|
-
total_commits: 2,
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
void it('handles an empty diff', () => {
|
|
59
|
-
const stats = summariseStats({
|
|
60
|
-
total_commits: 0,
|
|
61
|
-
commits: [],
|
|
62
|
-
files: [],
|
|
63
|
-
});
|
|
64
|
-
assert.deepStrictEqual(stats, {
|
|
65
|
-
files_changed: 0,
|
|
66
|
-
additions: 0,
|
|
67
|
-
deletions: 0,
|
|
68
|
-
total_commits: 0,
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
void describe('buildDiffDigest', () => {
|
|
73
|
-
void it('includes commit subjects, file list, and patches', () => {
|
|
74
|
-
const digest = buildDiffDigest(makeCompare());
|
|
75
|
-
assert.match(digest, /Total commits: 2/);
|
|
76
|
-
assert.match(digest, /1111111 feat: add checkout flow/);
|
|
77
|
-
assert.match(digest, /2222222 fix: null deref/);
|
|
78
|
-
assert.match(digest, /modified src\/checkout\.ts \(\+10\/-2\)/);
|
|
79
|
-
assert.match(digest, /added src\/util\.ts \(\+5\/-0\)/);
|
|
80
|
-
assert.match(digest, /--- src\/checkout\.ts ---/);
|
|
81
|
-
assert.match(digest, /\+new/);
|
|
82
|
-
});
|
|
83
|
-
void it('truncates only the subject line of multi-line commit messages', () => {
|
|
84
|
-
const digest = buildDiffDigest(makeCompare());
|
|
85
|
-
// "more body" from the first commit's message body must not leak in.
|
|
86
|
-
assert.ok(!digest.includes('more body'));
|
|
87
|
-
});
|
|
88
|
-
void it('caps the patch budget and appends a truncation marker', () => {
|
|
89
|
-
const huge = 'x'.repeat(200_000);
|
|
90
|
-
const compare = makeCompare({
|
|
91
|
-
files: [
|
|
92
|
-
{
|
|
93
|
-
filename: 'src/big.ts',
|
|
94
|
-
status: 'modified',
|
|
95
|
-
additions: 1,
|
|
96
|
-
deletions: 0,
|
|
97
|
-
changes: 1,
|
|
98
|
-
patch: huge,
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
});
|
|
102
|
-
const digest = buildDiffDigest(compare);
|
|
103
|
-
assert.match(digest, /\[truncated\]/);
|
|
104
|
-
// Even with a 200k patch, digest stays bounded by the internal budget.
|
|
105
|
-
assert.ok(digest.length < 200_000);
|
|
106
|
-
});
|
|
107
|
-
void it('skips files without patches in the patches section', () => {
|
|
108
|
-
const compare = makeCompare({
|
|
109
|
-
files: [
|
|
110
|
-
{
|
|
111
|
-
filename: 'vendored.min.js',
|
|
112
|
-
status: 'added',
|
|
113
|
-
additions: 1,
|
|
114
|
-
deletions: 0,
|
|
115
|
-
changes: 1,
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
});
|
|
119
|
-
const digest = buildDiffDigest(compare);
|
|
120
|
-
assert.ok(digest.includes('== Patches (truncated) =='));
|
|
121
|
-
assert.ok(!digest.includes('--- vendored.min.js ---'));
|
|
122
|
-
});
|
|
123
|
-
});
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for snapshot-detection pure helpers.
|
|
3
|
-
*
|
|
4
|
-
* The network / SDK path (`detectSnapshotVersion`) is not covered here
|
|
5
|
-
* — it's exercised end-to-end when the CLI runs — but the parser and
|
|
6
|
-
* the plausibility check are critical and easy to cover.
|
|
7
|
-
*/
|
|
8
|
-
export {};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for snapshot-detection pure helpers.
|
|
3
|
-
*
|
|
4
|
-
* The network / SDK path (`detectSnapshotVersion`) is not covered here
|
|
5
|
-
* — it's exercised end-to-end when the CLI runs — but the parser and
|
|
6
|
-
* the plausibility check are critical and easy to cover.
|
|
7
|
-
*/
|
|
8
|
-
import assert from 'node:assert';
|
|
9
|
-
import { describe, it } from 'node:test';
|
|
10
|
-
import { buildSnapshotDetectionPrompt, isPlausibleSnapshotTag, parseSnapshotDetection, } from '../snapshot.js';
|
|
11
|
-
void describe('parseSnapshotDetection', () => {
|
|
12
|
-
void it('parses a clean JSON object', () => {
|
|
13
|
-
const raw = '{"snapshot_tag":"v2.0.0","source":"package.json","reasoning":"next ver"}';
|
|
14
|
-
const parsed = parseSnapshotDetection(raw);
|
|
15
|
-
assert.strictEqual(parsed.snapshot_tag, 'v2.0.0');
|
|
16
|
-
assert.strictEqual(parsed.source, 'package.json');
|
|
17
|
-
assert.match(parsed.reasoning, /next ver/);
|
|
18
|
-
});
|
|
19
|
-
void it('strips ```json fences', () => {
|
|
20
|
-
const raw = '```json\n{"snapshot_tag":"v1.0.1","source":"VERSION","reasoning":"x"}\n```';
|
|
21
|
-
const parsed = parseSnapshotDetection(raw);
|
|
22
|
-
assert.strictEqual(parsed.snapshot_tag, 'v1.0.1');
|
|
23
|
-
});
|
|
24
|
-
void it('tolerates leading and trailing prose', () => {
|
|
25
|
-
const raw = `I checked package.json:
|
|
26
|
-
{"snapshot_tag":"v3.0.0","source":"package.json","reasoning":"bumped"}
|
|
27
|
-
Done.`;
|
|
28
|
-
const parsed = parseSnapshotDetection(raw);
|
|
29
|
-
assert.strictEqual(parsed.snapshot_tag, 'v3.0.0');
|
|
30
|
-
});
|
|
31
|
-
void it('returns null snapshot_tag when model reports no snapshot', () => {
|
|
32
|
-
const raw = '{"snapshot_tag": null, "source": null, "reasoning": "version matches latest release"}';
|
|
33
|
-
const parsed = parseSnapshotDetection(raw);
|
|
34
|
-
assert.strictEqual(parsed.snapshot_tag, null);
|
|
35
|
-
assert.strictEqual(parsed.source, null);
|
|
36
|
-
});
|
|
37
|
-
void it('treats empty string snapshot_tag as null', () => {
|
|
38
|
-
const raw = '{"snapshot_tag":"","source":null,"reasoning":"nothing found"}';
|
|
39
|
-
const parsed = parseSnapshotDetection(raw);
|
|
40
|
-
assert.strictEqual(parsed.snapshot_tag, null);
|
|
41
|
-
});
|
|
42
|
-
void it('falls back to balanced-brace extraction for surrounding prose', () => {
|
|
43
|
-
const raw = 'Analysis: { could be v1 } — but the real answer is ' +
|
|
44
|
-
'{"snapshot_tag":"v1.2.0","source":"CHANGELOG","reasoning":"unreleased section present"}';
|
|
45
|
-
const parsed = parseSnapshotDetection(raw);
|
|
46
|
-
assert.strictEqual(parsed.snapshot_tag, 'v1.2.0');
|
|
47
|
-
assert.strictEqual(parsed.source, 'CHANGELOG');
|
|
48
|
-
});
|
|
49
|
-
void it('throws when no JSON object is present', () => {
|
|
50
|
-
assert.throws(() => parseSnapshotDetection('no json at all'), /JSON/);
|
|
51
|
-
});
|
|
52
|
-
void it('supplies default reasoning when missing', () => {
|
|
53
|
-
const raw = '{"snapshot_tag":"v1","source":"package.json"}';
|
|
54
|
-
const parsed = parseSnapshotDetection(raw);
|
|
55
|
-
assert.strictEqual(parsed.snapshot_tag, 'v1');
|
|
56
|
-
assert.strictEqual(parsed.reasoning, '(no reasoning given)');
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
void describe('isPlausibleSnapshotTag', () => {
|
|
60
|
-
void it('accepts normal version tags', () => {
|
|
61
|
-
assert.strictEqual(isPlausibleSnapshotTag('v2.0.0', 'v1.9.0'), true);
|
|
62
|
-
assert.strictEqual(isPlausibleSnapshotTag('2.0.0-rc.1', '1.9.0'), true);
|
|
63
|
-
assert.strictEqual(isPlausibleSnapshotTag('v1.2.3-SNAPSHOT', 'v1.2.2'), true);
|
|
64
|
-
assert.strictEqual(isPlausibleSnapshotTag('release/2.0', 'release/1.9'), true);
|
|
65
|
-
});
|
|
66
|
-
void it('rejects tags identical to the latest release', () => {
|
|
67
|
-
assert.strictEqual(isPlausibleSnapshotTag('v1.0.0', 'v1.0.0'), false);
|
|
68
|
-
});
|
|
69
|
-
void it('rejects empty or overlong tags', () => {
|
|
70
|
-
assert.strictEqual(isPlausibleSnapshotTag('', 'v1'), false);
|
|
71
|
-
assert.strictEqual(isPlausibleSnapshotTag('x'.repeat(101), 'v1'), false);
|
|
72
|
-
});
|
|
73
|
-
void it('rejects whitespace and shell metacharacters', () => {
|
|
74
|
-
assert.strictEqual(isPlausibleSnapshotTag('v 2', 'v1'), false);
|
|
75
|
-
assert.strictEqual(isPlausibleSnapshotTag('v2;rm -rf', 'v1'), false);
|
|
76
|
-
assert.strictEqual(isPlausibleSnapshotTag('v2$PATH', 'v1'), false);
|
|
77
|
-
});
|
|
78
|
-
void it('rejects leading dot or dash, double dot, @{ reflog', () => {
|
|
79
|
-
assert.strictEqual(isPlausibleSnapshotTag('.v2', 'v1'), false);
|
|
80
|
-
assert.strictEqual(isPlausibleSnapshotTag('-v2', 'v1'), false);
|
|
81
|
-
assert.strictEqual(isPlausibleSnapshotTag('v2..rc', 'v1'), false);
|
|
82
|
-
assert.strictEqual(isPlausibleSnapshotTag('v2@{1}', 'v1'), false);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
void describe('buildSnapshotDetectionPrompt', () => {
|
|
86
|
-
void it('injects the latest release tag into the prompt', () => {
|
|
87
|
-
const prompt = buildSnapshotDetectionPrompt('v1.4.2');
|
|
88
|
-
assert.match(prompt, /v1\.4\.2/);
|
|
89
|
-
// Must ask for JSON output
|
|
90
|
-
assert.match(prompt, /JSON/);
|
|
91
|
-
assert.match(prompt, /snapshot_tag/);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for smoke-test agent response parsing.
|
|
3
|
-
*/
|
|
4
|
-
import assert from 'node:assert';
|
|
5
|
-
import { describe, it } from 'node:test';
|
|
6
|
-
import { findBalancedJsonObject } from '../../../utils/json-extract.js';
|
|
7
|
-
import { extractJson } from '../agent.js';
|
|
8
|
-
void describe('extractJson', () => {
|
|
9
|
-
void it('parses a plain JSON object', () => {
|
|
10
|
-
const raw = `{
|
|
11
|
-
"summary": "one thing changed",
|
|
12
|
-
"test_cases": [
|
|
13
|
-
{ "name": "login still works", "description": "step 1", "is_critical": true }
|
|
14
|
-
]
|
|
15
|
-
}`;
|
|
16
|
-
const parsed = extractJson(raw);
|
|
17
|
-
assert.strictEqual(parsed.summary, 'one thing changed');
|
|
18
|
-
assert.strictEqual(parsed.test_cases.length, 1);
|
|
19
|
-
assert.strictEqual(parsed.test_cases[0].is_critical, true);
|
|
20
|
-
});
|
|
21
|
-
void it('strips ```json fences', () => {
|
|
22
|
-
const raw = '```json\n{"summary":"x","test_cases":[{"name":"a","description":"b"}]}\n```';
|
|
23
|
-
const parsed = extractJson(raw);
|
|
24
|
-
assert.strictEqual(parsed.summary, 'x');
|
|
25
|
-
assert.strictEqual(parsed.test_cases[0].name, 'a');
|
|
26
|
-
});
|
|
27
|
-
void it('strips unlabeled fences', () => {
|
|
28
|
-
const raw = '```\n{"summary":"x","test_cases":[]}\n```';
|
|
29
|
-
const parsed = extractJson(raw);
|
|
30
|
-
assert.deepStrictEqual(parsed.test_cases, []);
|
|
31
|
-
});
|
|
32
|
-
void it('tolerates leading and trailing prose', () => {
|
|
33
|
-
const raw = `Here you go:
|
|
34
|
-
{"summary":"x","test_cases":[{"name":"a","description":"b"}]}
|
|
35
|
-
|
|
36
|
-
Hope that helps.`;
|
|
37
|
-
const parsed = extractJson(raw);
|
|
38
|
-
assert.strictEqual(parsed.summary, 'x');
|
|
39
|
-
});
|
|
40
|
-
void it('throws when test_cases is missing', () => {
|
|
41
|
-
assert.throws(() => extractJson('{"summary":"x"}'), /test_cases/);
|
|
42
|
-
});
|
|
43
|
-
void it('throws on invalid JSON', () => {
|
|
44
|
-
assert.throws(() => extractJson('not json'));
|
|
45
|
-
});
|
|
46
|
-
void it('picks the first balanced object when prose contains decoy braces', () => {
|
|
47
|
-
const raw = 'Note: { pseudo-json example } but the real answer is:\n' +
|
|
48
|
-
'{"summary":"x","test_cases":[{"name":"a","description":"b"}]}\n' +
|
|
49
|
-
'Let me know if you want changes.';
|
|
50
|
-
const parsed = extractJson(raw);
|
|
51
|
-
assert.strictEqual(parsed.summary, 'x');
|
|
52
|
-
assert.strictEqual(parsed.test_cases[0].name, 'a');
|
|
53
|
-
});
|
|
54
|
-
void it('preserves braces inside JSON string values', () => {
|
|
55
|
-
const raw = '{"summary":"changes to {pricing} and {checkout}","test_cases":[{"name":"n","description":"d"}]}';
|
|
56
|
-
const parsed = extractJson(raw);
|
|
57
|
-
assert.strictEqual(parsed.summary, 'changes to {pricing} and {checkout}');
|
|
58
|
-
});
|
|
59
|
-
void it('throws when test_cases is not an array', () => {
|
|
60
|
-
assert.throws(() => extractJson('{"summary":"x","test_cases":"nope"}'), /test_cases/);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
void describe('findBalancedJsonObject', () => {
|
|
64
|
-
void it('returns null when there is no opening brace', () => {
|
|
65
|
-
assert.strictEqual(findBalancedJsonObject('no braces here'), null);
|
|
66
|
-
});
|
|
67
|
-
void it('returns the first balanced top-level object', () => {
|
|
68
|
-
assert.strictEqual(findBalancedJsonObject('prefix {"a": 1} and {"b": 2} suffix'), '{"a": 1}');
|
|
69
|
-
});
|
|
70
|
-
void it('handles nested braces', () => {
|
|
71
|
-
const text = 'xxx {"a": {"b": {"c": 1}}} yyy';
|
|
72
|
-
assert.strictEqual(findBalancedJsonObject(text), '{"a": {"b": {"c": 1}}}');
|
|
73
|
-
});
|
|
74
|
-
void it('ignores braces inside strings', () => {
|
|
75
|
-
const text = '{"msg": "this has { and } inside", "ok": true}';
|
|
76
|
-
assert.strictEqual(findBalancedJsonObject(text), text);
|
|
77
|
-
});
|
|
78
|
-
void it('handles escaped quotes inside strings', () => {
|
|
79
|
-
const text = '{"msg": "she said \\"hi\\" {not object}"}';
|
|
80
|
-
assert.strictEqual(findBalancedJsonObject(text), text);
|
|
81
|
-
});
|
|
82
|
-
void it('returns null on unbalanced input', () => {
|
|
83
|
-
assert.strictEqual(findBalancedJsonObject('{"a": 1'), null);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|