@codeyam/codeyam-cli 0.1.21 → 0.1.22
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/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +31 -8
- package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
- package/analyzer-template/packages/analyze/index.ts +4 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
- package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
- package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +235 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +170 -26
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +63 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/commands/editor.js +412 -78
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +354 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/analysisRunner.js +28 -1
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +4 -2
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +98 -3
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +5 -3
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +16 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
- package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
- package/codeyam-cli/src/utils/queue/job.js +20 -2
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/testRunner.js +199 -1
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +35 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CJzc4vOH.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-aIHKLB-m.js +96 -0
- package/codeyam-cli/src/webserver/build/client/assets/{editorPreview-NTuLi4Xg.js → editorPreview-CluPkvXJ.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-ByHz6rAQ.js} +13 -12
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BA5L8bU-.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-D4dmRgvO.js → entity._sha.scenarios._scenarioId.fullscreen-Bz9sCUF_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{manifest-5025e428.js → manifest-bcbb3d49.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-BCx1S8Z3.js → root-D2_tktnk.js} +6 -6
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DjF-soOH.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-C91yWWCI.js → index-nAvHGWbz.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-Dkas-RUS.js → init-XhpIt-OT.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DVwiibFu.js +644 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/idleDetector.js +15 -0
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +8 -2
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +2 -2
- package/package.json +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +23 -9
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/analyze/index.js +1 -1
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +116 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +139 -24
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DODLxLcw.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dx-h1rJK.js +0 -130
- package/codeyam-cli/src/webserver/build/client/assets/globals-BrPXT1iR.css +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-C1kjC9UJ.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/server-build-pulXLTrG.js +0 -640
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { Kysely, SqliteDialect } from 'kysely';
|
|
3
|
-
import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryPageScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, queryUnassociatedScenarios, isOnlyIncompleteEntities, isOnlyPreExistingIncomplete, isAutoRemediable, identifyScenariosNeedingRecapture, detectDuplicateNames, } from "../editorAudit.js";
|
|
3
|
+
import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryPageScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, queryUnassociatedScenarios, isOnlyIncompleteEntities, isOnlyPreExistingIncomplete, isAutoRemediable, identifyScenariosNeedingRecapture, detectDuplicateNames, aggregateClientErrorsByComponent, } from "../editorAudit.js";
|
|
4
4
|
describe('editorAudit', () => {
|
|
5
5
|
describe('isComponent', () => {
|
|
6
6
|
it('should return true for JSX.Element return type', () => {
|
|
@@ -1987,6 +1987,24 @@ describe('editorAudit', () => {
|
|
|
1987
1987
|
allPassing: false,
|
|
1988
1988
|
})).toBe(true);
|
|
1989
1989
|
});
|
|
1990
|
+
it('should return false when there are also runner errors', () => {
|
|
1991
|
+
// functionsRunnerError means the test runner crashed — a real failure
|
|
1992
|
+
// that cannot be fixed by entity SHA backfill or analyze-imports.
|
|
1993
|
+
// If this returns true, checkAuditGate would attempt a useless backfill
|
|
1994
|
+
// instead of reporting the runner error, and isOnlyPreExistingIncomplete
|
|
1995
|
+
// could let the gate pass entirely.
|
|
1996
|
+
expect(isOnlyIncompleteEntities({
|
|
1997
|
+
componentsMissing: 0,
|
|
1998
|
+
componentsWithErrors: 0,
|
|
1999
|
+
functionsFailing: 0,
|
|
2000
|
+
functionsRunnerError: 2,
|
|
2001
|
+
functionsNameMismatch: 0,
|
|
2002
|
+
functionsMissing: 0,
|
|
2003
|
+
missingFromGlossary: 0,
|
|
2004
|
+
incompleteEntities: 1,
|
|
2005
|
+
allPassing: false,
|
|
2006
|
+
})).toBe(false);
|
|
2007
|
+
});
|
|
1990
2008
|
});
|
|
1991
2009
|
// ── isAutoRemediable ─────────────────────────────────────────────────
|
|
1992
2010
|
describe('isAutoRemediable', () => {
|
|
@@ -2779,6 +2797,25 @@ describe('editorAudit', () => {
|
|
|
2779
2797
|
});
|
|
2780
2798
|
expect(result).toHaveLength(0);
|
|
2781
2799
|
});
|
|
2800
|
+
it('should NOT flag scenarios for new entities (they need creation, not recapture)', () => {
|
|
2801
|
+
// "new" entities are being seen for the first time. Their scenarios need
|
|
2802
|
+
// initial creation, not recapture of old screenshots. Flagging them as
|
|
2803
|
+
// "needs_recapture" sends the wrong remediation signal to Claude.
|
|
2804
|
+
const entityChangeStatus = {
|
|
2805
|
+
NewComponent: { status: 'new' },
|
|
2806
|
+
};
|
|
2807
|
+
const result = identifyScenariosNeedingRecapture({
|
|
2808
|
+
scenarios: [
|
|
2809
|
+
{
|
|
2810
|
+
name: 'NewComponent - Default',
|
|
2811
|
+
entityName: 'NewComponent',
|
|
2812
|
+
updatedInSession: false,
|
|
2813
|
+
},
|
|
2814
|
+
],
|
|
2815
|
+
entityChangeStatus,
|
|
2816
|
+
});
|
|
2817
|
+
expect(result).toHaveLength(0);
|
|
2818
|
+
});
|
|
2782
2819
|
});
|
|
2783
2820
|
// ── detectDuplicateNames ──────────────────────────────────────────
|
|
2784
2821
|
describe('detectDuplicateNames', () => {
|
|
@@ -2946,6 +2983,28 @@ describe('editorAudit', () => {
|
|
|
2946
2983
|
expect(result.components[0].status).toBe('missing');
|
|
2947
2984
|
expect(result.summary.componentsMissing).toBe(1);
|
|
2948
2985
|
});
|
|
2986
|
+
it('should not count needs_recapture components as componentsOk', () => {
|
|
2987
|
+
// A needs_recapture component is not "ok" — it needs action. Counting it
|
|
2988
|
+
// in componentsOk is misleading: if totalComponents=2, componentsOk=2,
|
|
2989
|
+
// and componentsNeedingRecapture=1, the numbers don't add up (2+1 > 2).
|
|
2990
|
+
const result = computeAudit({
|
|
2991
|
+
components: [
|
|
2992
|
+
{ name: 'Library', filePath: 'app/library/page.tsx' },
|
|
2993
|
+
{ name: 'DrinkCard', filePath: 'app/components/DrinkCard.tsx' },
|
|
2994
|
+
],
|
|
2995
|
+
functions: [],
|
|
2996
|
+
scenarioCounts: { DrinkCard: 2 },
|
|
2997
|
+
testFileExistence: {},
|
|
2998
|
+
totalScenarioCounts: { Library: 3 },
|
|
2999
|
+
entityChangeStatus: { Library: { status: 'impacted' } },
|
|
3000
|
+
});
|
|
3001
|
+
expect(result.components[0].status).toBe('needs_recapture');
|
|
3002
|
+
expect(result.components[1].status).toBe('ok');
|
|
3003
|
+
// needs_recapture is not "ok" — should be counted separately
|
|
3004
|
+
expect(result.summary.componentsOk).toBe(1);
|
|
3005
|
+
expect(result.summary.componentsNeedingRecapture).toBe(1);
|
|
3006
|
+
expect(result.summary.totalComponents).toBe(2);
|
|
3007
|
+
});
|
|
2949
3008
|
});
|
|
2950
3009
|
// ── queryUnassociatedScenarios ──────────────────────────────────────
|
|
2951
3010
|
describe('queryUnassociatedScenarios', () => {
|
|
@@ -3248,6 +3307,61 @@ describe('editorAudit', () => {
|
|
|
3248
3307
|
expect(result.functions[0].suggestedTestFile).toBeUndefined();
|
|
3249
3308
|
});
|
|
3250
3309
|
});
|
|
3310
|
+
describe('hint for function audit entries', () => {
|
|
3311
|
+
it('should include a hint for name_mismatch functions explaining the fix', () => {
|
|
3312
|
+
// Claude sees "name mismatch" with no guidance on what it means or how
|
|
3313
|
+
// to fix it. The hint should explain that a top-level describe block
|
|
3314
|
+
// matching the function name is required for the CodeYam UI.
|
|
3315
|
+
const result = computeAudit({
|
|
3316
|
+
components: [],
|
|
3317
|
+
functions: [
|
|
3318
|
+
{
|
|
3319
|
+
name: 'useDrinks',
|
|
3320
|
+
filePath: 'app/hooks/useDrinks.ts',
|
|
3321
|
+
testFile: 'app/hooks/useDrinks.test.ts',
|
|
3322
|
+
},
|
|
3323
|
+
],
|
|
3324
|
+
scenarioCounts: {},
|
|
3325
|
+
testFileExistence: { 'app/hooks/useDrinks.test.ts': true },
|
|
3326
|
+
testResults: {
|
|
3327
|
+
'app/hooks/useDrinks.test.ts': {
|
|
3328
|
+
passing: true,
|
|
3329
|
+
hasEntityNameDescribe: false,
|
|
3330
|
+
},
|
|
3331
|
+
},
|
|
3332
|
+
});
|
|
3333
|
+
expect(result.functions[0].status).toBe('name_mismatch');
|
|
3334
|
+
expect(result.functions[0].hint).toBeDefined();
|
|
3335
|
+
expect(result.functions[0].hint).toContain('describe');
|
|
3336
|
+
expect(result.functions[0].hint).toContain('useDrinks');
|
|
3337
|
+
});
|
|
3338
|
+
it('should include a hint for runner_error functions showing the error', () => {
|
|
3339
|
+
// When the test runner crashes, Claude needs to see WHY it crashed
|
|
3340
|
+
// to fix the underlying issue. Without this, Claude loops re-running audit.
|
|
3341
|
+
const result = computeAudit({
|
|
3342
|
+
components: [],
|
|
3343
|
+
functions: [
|
|
3344
|
+
{
|
|
3345
|
+
name: 'getTimeAgo',
|
|
3346
|
+
filePath: 'src/lib/format.ts',
|
|
3347
|
+
testFile: 'src/lib/format.test.ts',
|
|
3348
|
+
},
|
|
3349
|
+
],
|
|
3350
|
+
scenarioCounts: {},
|
|
3351
|
+
testFileExistence: { 'src/lib/format.test.ts': true },
|
|
3352
|
+
testResults: {
|
|
3353
|
+
'src/lib/format.test.ts': {
|
|
3354
|
+
passing: false,
|
|
3355
|
+
hasEntityNameDescribe: false,
|
|
3356
|
+
errorMessage: 'Cannot find module "@/lib/format"',
|
|
3357
|
+
},
|
|
3358
|
+
},
|
|
3359
|
+
});
|
|
3360
|
+
expect(result.functions[0].status).toBe('runner_error');
|
|
3361
|
+
expect(result.functions[0].hint).toBeDefined();
|
|
3362
|
+
expect(result.functions[0].hint).toContain('Cannot find module');
|
|
3363
|
+
});
|
|
3364
|
+
});
|
|
3251
3365
|
describe('hint for missing components', () => {
|
|
3252
3366
|
it('should hint that layout files need app-level scenarios', () => {
|
|
3253
3367
|
const result = computeAudit({
|
|
@@ -3295,6 +3409,21 @@ describe('editorAudit', () => {
|
|
|
3295
3409
|
});
|
|
3296
3410
|
expect(result.components[0].hint).toBeUndefined();
|
|
3297
3411
|
});
|
|
3412
|
+
it('should provide a hint for needs_recapture components', () => {
|
|
3413
|
+
// Components with needs_recapture status need guidance on what to do.
|
|
3414
|
+
// Without a hint, Claude has no instructions for fixing the issue.
|
|
3415
|
+
const result = computeAudit({
|
|
3416
|
+
components: [{ name: 'Library', filePath: 'app/library/page.tsx' }],
|
|
3417
|
+
functions: [],
|
|
3418
|
+
scenarioCounts: {},
|
|
3419
|
+
testFileExistence: {},
|
|
3420
|
+
totalScenarioCounts: { Library: 3 },
|
|
3421
|
+
entityChangeStatus: { Library: { status: 'impacted' } },
|
|
3422
|
+
});
|
|
3423
|
+
expect(result.components[0].status).toBe('needs_recapture');
|
|
3424
|
+
expect(result.components[0].hint).toBeDefined();
|
|
3425
|
+
expect(result.components[0].hint).toContain('recapture');
|
|
3426
|
+
});
|
|
3298
3427
|
});
|
|
3299
3428
|
describe('formatIncompleteEntityGuidance', () => {
|
|
3300
3429
|
it('should include the entity name and scenario count', () => {
|
|
@@ -3340,6 +3469,60 @@ describe('editorAudit', () => {
|
|
|
3340
3469
|
expect(result).toMatch(/scenario.*without.*import graph|import graph.*not.*built/i);
|
|
3341
3470
|
});
|
|
3342
3471
|
});
|
|
3472
|
+
describe('formatManualAnalysisGuidance', () => {
|
|
3473
|
+
it('should include entity name, scenario count, and error message', () => {
|
|
3474
|
+
const { formatManualAnalysisGuidance } = require('../editorAudit');
|
|
3475
|
+
const result = formatManualAnalysisGuidance({
|
|
3476
|
+
name: 'Header',
|
|
3477
|
+
filePath: 'src/components/Header.tsx',
|
|
3478
|
+
scenarioCount: 3,
|
|
3479
|
+
error: 'TypeScript parsing error: unexpected token',
|
|
3480
|
+
});
|
|
3481
|
+
expect(result).toContain('Header');
|
|
3482
|
+
expect(result).toContain('3 scenario(s)');
|
|
3483
|
+
expect(result).toContain('TypeScript parsing error');
|
|
3484
|
+
});
|
|
3485
|
+
it('should include MANUAL ANALYSIS REQUIRED header', () => {
|
|
3486
|
+
const { formatManualAnalysisGuidance } = require('../editorAudit');
|
|
3487
|
+
const result = formatManualAnalysisGuidance({
|
|
3488
|
+
name: 'Header',
|
|
3489
|
+
filePath: 'src/components/Header.tsx',
|
|
3490
|
+
scenarioCount: 2,
|
|
3491
|
+
error: 'Parse error',
|
|
3492
|
+
});
|
|
3493
|
+
expect(result).toContain('MANUAL ANALYSIS REQUIRED');
|
|
3494
|
+
});
|
|
3495
|
+
it('should tell Claude to read the source file', () => {
|
|
3496
|
+
const { formatManualAnalysisGuidance } = require('../editorAudit');
|
|
3497
|
+
const result = formatManualAnalysisGuidance({
|
|
3498
|
+
name: 'Header',
|
|
3499
|
+
filePath: 'src/components/Header.tsx',
|
|
3500
|
+
scenarioCount: 2,
|
|
3501
|
+
error: 'Parse error',
|
|
3502
|
+
});
|
|
3503
|
+
expect(result).toContain('Read src/components/Header.tsx');
|
|
3504
|
+
});
|
|
3505
|
+
it('should include the manual-entity command', () => {
|
|
3506
|
+
const { formatManualAnalysisGuidance } = require('../editorAudit');
|
|
3507
|
+
const result = formatManualAnalysisGuidance({
|
|
3508
|
+
name: 'Header',
|
|
3509
|
+
filePath: 'src/components/Header.tsx',
|
|
3510
|
+
scenarioCount: 2,
|
|
3511
|
+
error: 'Parse error',
|
|
3512
|
+
});
|
|
3513
|
+
expect(result).toContain('codeyam editor manual-entity');
|
|
3514
|
+
});
|
|
3515
|
+
it('should tell Claude to check glossary for imports', () => {
|
|
3516
|
+
const { formatManualAnalysisGuidance } = require('../editorAudit');
|
|
3517
|
+
const result = formatManualAnalysisGuidance({
|
|
3518
|
+
name: 'Header',
|
|
3519
|
+
filePath: 'src/components/Header.tsx',
|
|
3520
|
+
scenarioCount: 2,
|
|
3521
|
+
error: 'Parse error',
|
|
3522
|
+
});
|
|
3523
|
+
expect(result).toContain('glossary');
|
|
3524
|
+
});
|
|
3525
|
+
});
|
|
3343
3526
|
describe('getIncompleteEntityFilePaths', () => {
|
|
3344
3527
|
// The audit should auto-fix incomplete entities by running analysis on
|
|
3345
3528
|
// just their specific file paths, not all 117+ files. This function
|
|
@@ -3490,6 +3673,29 @@ describe('editorAudit', () => {
|
|
|
3490
3673
|
},
|
|
3491
3674
|
])).toBe(true);
|
|
3492
3675
|
});
|
|
3676
|
+
it('should return false when there are runner errors even if all incompletes are pre-existing', () => {
|
|
3677
|
+
// Safety net: runner errors (crashed test runner) must NEVER bypass the gate.
|
|
3678
|
+
// Without this test, a regression removing functionsRunnerError from
|
|
3679
|
+
// isOnlyIncompleteEntities would silently let broken test infrastructure through.
|
|
3680
|
+
expect(isOnlyPreExistingIncomplete({
|
|
3681
|
+
incompleteEntities: 1,
|
|
3682
|
+
preExistingIncompleteEntities: 1,
|
|
3683
|
+
componentsMissing: 0,
|
|
3684
|
+
componentsWithErrors: 0,
|
|
3685
|
+
functionsFailing: 0,
|
|
3686
|
+
functionsRunnerError: 1,
|
|
3687
|
+
functionsNameMismatch: 0,
|
|
3688
|
+
functionsMissing: 0,
|
|
3689
|
+
missingFromGlossary: 0,
|
|
3690
|
+
}, [
|
|
3691
|
+
{
|
|
3692
|
+
entitySha: 'a',
|
|
3693
|
+
name: 'OldComponent',
|
|
3694
|
+
scenarioCount: 1,
|
|
3695
|
+
preExisting: true,
|
|
3696
|
+
},
|
|
3697
|
+
])).toBe(false);
|
|
3698
|
+
});
|
|
3493
3699
|
it('should return false when some incomplete entities are NOT pre-existing', () => {
|
|
3494
3700
|
expect(isOnlyPreExistingIncomplete({
|
|
3495
3701
|
incompleteEntities: 2,
|
|
@@ -3555,5 +3761,152 @@ describe('editorAudit', () => {
|
|
|
3555
3761
|
})).toBe(false);
|
|
3556
3762
|
});
|
|
3557
3763
|
});
|
|
3764
|
+
// ── aggregateClientErrorsByComponent ─────────────────────────────────
|
|
3765
|
+
describe('aggregateClientErrorsByComponent', () => {
|
|
3766
|
+
it('should attribute errors to component using componentName from metadata', () => {
|
|
3767
|
+
const result = aggregateClientErrorsByComponent({
|
|
3768
|
+
'scenario-1': {
|
|
3769
|
+
scenarioName: 'DrinkCard - Default',
|
|
3770
|
+
errors: ['TypeError: Cannot read property "price"'],
|
|
3771
|
+
},
|
|
3772
|
+
}, [
|
|
3773
|
+
{
|
|
3774
|
+
name: 'DrinkCard - Default',
|
|
3775
|
+
componentName: 'DrinkCard',
|
|
3776
|
+
},
|
|
3777
|
+
]);
|
|
3778
|
+
expect(result['DrinkCard']).toEqual([
|
|
3779
|
+
'TypeError: Cannot read property "price"',
|
|
3780
|
+
]);
|
|
3781
|
+
});
|
|
3782
|
+
it('should attribute app-level scenario errors using pageFilePath', () => {
|
|
3783
|
+
// App-level scenarios have componentName=null. The old approach
|
|
3784
|
+
// parsed the scenario name "Full Page with Library" and got
|
|
3785
|
+
// "Full Page with Library" as the component — which matches nothing.
|
|
3786
|
+
// With metadata, we derive the entity name from pageFilePath.
|
|
3787
|
+
const result = aggregateClientErrorsByComponent({
|
|
3788
|
+
'scenario-1': {
|
|
3789
|
+
scenarioName: 'Full Page with Library',
|
|
3790
|
+
errors: ['TypeError: Cannot read property "title"'],
|
|
3791
|
+
},
|
|
3792
|
+
}, [
|
|
3793
|
+
{
|
|
3794
|
+
name: 'Full Page with Library',
|
|
3795
|
+
componentName: null,
|
|
3796
|
+
pageFilePath: 'app/library/page.tsx',
|
|
3797
|
+
},
|
|
3798
|
+
]);
|
|
3799
|
+
// Should be keyed by the canonical entity name from scenarioEntityName()
|
|
3800
|
+
expect(result['Library']).toEqual([
|
|
3801
|
+
'TypeError: Cannot read property "title"',
|
|
3802
|
+
]);
|
|
3803
|
+
});
|
|
3804
|
+
it('should fall back to scenario name parsing when no metadata match exists', () => {
|
|
3805
|
+
// If the scenario is not in the metadata list (e.g., metadata not yet loaded),
|
|
3806
|
+
// fall back to the "ComponentName - Variant" convention
|
|
3807
|
+
const result = aggregateClientErrorsByComponent({
|
|
3808
|
+
'scenario-1': {
|
|
3809
|
+
scenarioName: 'Header - Dark Mode',
|
|
3810
|
+
errors: ['ReferenceError: theme is not defined'],
|
|
3811
|
+
},
|
|
3812
|
+
}, []);
|
|
3813
|
+
expect(result['Header']).toEqual([
|
|
3814
|
+
'ReferenceError: theme is not defined',
|
|
3815
|
+
]);
|
|
3816
|
+
});
|
|
3817
|
+
it('should handle non-route file paths via scenarioEntityName', () => {
|
|
3818
|
+
// Non-route files (src/...) are treated as app entry points by
|
|
3819
|
+
// scenarioEntityName → buildRoutePattern returns '/' → 'Home'.
|
|
3820
|
+
// When a url is available, it provides a better entity name.
|
|
3821
|
+
const result = aggregateClientErrorsByComponent({
|
|
3822
|
+
'scenario-1': {
|
|
3823
|
+
scenarioName: 'LibraryApp - Rich',
|
|
3824
|
+
errors: ['Error: fetch failed'],
|
|
3825
|
+
},
|
|
3826
|
+
}, [
|
|
3827
|
+
{
|
|
3828
|
+
name: 'LibraryApp - Rich',
|
|
3829
|
+
componentName: null,
|
|
3830
|
+
pageFilePath: 'src/library/LibraryApp.tsx',
|
|
3831
|
+
url: '/library',
|
|
3832
|
+
},
|
|
3833
|
+
]);
|
|
3834
|
+
// scenarioEntityName uses url fallback when pageFilePath is a non-route file
|
|
3835
|
+
// pageFilePath 'src/...' → buildRoutePattern → '/' → routeDisplayName → 'Home'
|
|
3836
|
+
// But componentName takes priority, and with url '/library' as final fallback
|
|
3837
|
+
// scenarioEntityName({ pageFilePath: 'src/library/LibraryApp.tsx' }) → 'Home'
|
|
3838
|
+
// because pageFilePath is checked before url
|
|
3839
|
+
expect(result['Home']).toEqual(['Error: fetch failed']);
|
|
3840
|
+
});
|
|
3841
|
+
it('should skip scenarios with no errors', () => {
|
|
3842
|
+
const result = aggregateClientErrorsByComponent({
|
|
3843
|
+
'scenario-1': {
|
|
3844
|
+
scenarioName: 'DrinkCard - Default',
|
|
3845
|
+
errors: [],
|
|
3846
|
+
},
|
|
3847
|
+
}, [
|
|
3848
|
+
{
|
|
3849
|
+
name: 'DrinkCard - Default',
|
|
3850
|
+
componentName: 'DrinkCard',
|
|
3851
|
+
},
|
|
3852
|
+
]);
|
|
3853
|
+
expect(result).toEqual({});
|
|
3854
|
+
});
|
|
3855
|
+
it('should aggregate errors from multiple scenarios for same component', () => {
|
|
3856
|
+
const result = aggregateClientErrorsByComponent({
|
|
3857
|
+
'scenario-1': {
|
|
3858
|
+
scenarioName: 'DrinkCard - Default',
|
|
3859
|
+
errors: ['Error A'],
|
|
3860
|
+
},
|
|
3861
|
+
'scenario-2': {
|
|
3862
|
+
scenarioName: 'DrinkCard - Hover',
|
|
3863
|
+
errors: ['Error B', 'Error C'],
|
|
3864
|
+
},
|
|
3865
|
+
}, [
|
|
3866
|
+
{ name: 'DrinkCard - Default', componentName: 'DrinkCard' },
|
|
3867
|
+
{ name: 'DrinkCard - Hover', componentName: 'DrinkCard' },
|
|
3868
|
+
]);
|
|
3869
|
+
expect(result['DrinkCard']).toEqual(['Error A', 'Error B', 'Error C']);
|
|
3870
|
+
});
|
|
3871
|
+
it('should use capitalized display name for page file paths, not lowercase directory', () => {
|
|
3872
|
+
// aggregateClientErrorsByComponent must produce keys matching scenarioEntityName().
|
|
3873
|
+
// scenarioEntityName({ pageFilePath: 'app/library/page.tsx' }) returns 'Library',
|
|
3874
|
+
// so the key must be 'Library' — not 'library' (the raw directory name).
|
|
3875
|
+
// computeAudit checks clientErrors[glossaryEntryName], so a lowercase key
|
|
3876
|
+
// will never match, silently dropping all client errors for page-level scenarios.
|
|
3877
|
+
const result = aggregateClientErrorsByComponent({
|
|
3878
|
+
'sc-1': {
|
|
3879
|
+
scenarioName: 'Library - Default',
|
|
3880
|
+
errors: ['TypeError: fetch failed'],
|
|
3881
|
+
},
|
|
3882
|
+
}, [
|
|
3883
|
+
{
|
|
3884
|
+
name: 'Library - Default',
|
|
3885
|
+
componentName: null,
|
|
3886
|
+
pageFilePath: 'app/library/page.tsx',
|
|
3887
|
+
},
|
|
3888
|
+
]);
|
|
3889
|
+
expect(result).toHaveProperty('Library');
|
|
3890
|
+
expect(result['Library']).toEqual(['TypeError: fetch failed']);
|
|
3891
|
+
});
|
|
3892
|
+
it('should use "Home" for root page app/page.tsx, not "app"', () => {
|
|
3893
|
+
// Root page: scenarioEntityName returns 'Home', not 'app'.
|
|
3894
|
+
// Custom path parsing incorrectly pops the parent dir ('app').
|
|
3895
|
+
const result = aggregateClientErrorsByComponent({
|
|
3896
|
+
'sc-1': {
|
|
3897
|
+
scenarioName: 'Home - Default',
|
|
3898
|
+
errors: ['ReferenceError: window is not defined'],
|
|
3899
|
+
},
|
|
3900
|
+
}, [
|
|
3901
|
+
{
|
|
3902
|
+
name: 'Home - Default',
|
|
3903
|
+
componentName: null,
|
|
3904
|
+
pageFilePath: 'app/page.tsx',
|
|
3905
|
+
},
|
|
3906
|
+
]);
|
|
3907
|
+
expect(result).toHaveProperty('Home');
|
|
3908
|
+
expect(result['Home']).toEqual(['ReferenceError: window is not defined']);
|
|
3909
|
+
});
|
|
3910
|
+
});
|
|
3558
3911
|
});
|
|
3559
3912
|
//# sourceMappingURL=editorAudit.test.js.map
|