ai-debug-local-mcp
Version:
🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
413 lines • 16.8 kB
JavaScript
/**
* Test Expectation Tracker
* Compares test expectations vs actual UI rendering
* Based on Cycle 22 user feedback - widget key comparison
*/
export class TestExpectationTracker {
static instance;
static getInstance() {
if (!TestExpectationTracker.instance) {
TestExpectationTracker.instance = new TestExpectationTracker();
}
return TestExpectationTracker.instance;
}
expectations = new Map();
trackingHistory = new Map();
constructor() {
this.initializeCommonExpectations();
}
/**
* Initialize common expectations from Cycle 22 success pattern
*/
initializeCommonExpectations() {
const commonExpectations = [
{
key: 'family_overview_section',
type: 'exact',
description: 'Main family overview container section',
criticality: 'must_have',
dependencies: ['unifiedAuthNotifierProvider', 'unifiedFamilyMembersProvider'],
testFile: 'dashboard_test.dart',
testFunction: 'testFamilyOverviewSection'
},
{
key: 'family_member_status_card_',
type: 'pattern',
description: 'Family member status cards with member ID suffix',
criticality: 'must_have',
dependencies: ['unifiedFamilyMembersProvider'],
testFile: 'dashboard_test.dart',
testFunction: 'testFamilyMemberCards'
},
{
key: 'add_family_event_quick_action',
type: 'exact',
description: 'Quick action button for adding family events',
criticality: 'should_have',
dependencies: ['unifiedAuthNotifierProvider'],
testFile: 'dashboard_test.dart',
testFunction: 'testQuickActions'
},
{
key: 'add_family_chore_quick_action',
type: 'exact',
description: 'Quick action button for adding family chores',
criticality: 'should_have',
dependencies: ['unifiedAuthNotifierProvider'],
testFile: 'dashboard_test.dart',
testFunction: 'testQuickActions'
},
{
key: 'family_wellbeing_indicator',
type: 'exact',
description: 'Family wellbeing status indicator',
criticality: 'nice_to_have',
dependencies: ['unifiedFamilyMembersProvider', 'wellbeingProvider'],
testFile: 'dashboard_test.dart',
testFunction: 'testWellbeingIndicator'
},
{
key: 'family_calendar_sync_status',
type: 'exact',
description: 'Calendar synchronization status display',
criticality: 'nice_to_have',
dependencies: ['calendarSyncProvider'],
testFile: 'dashboard_test.dart',
testFunction: 'testCalendarSync'
}
];
commonExpectations.forEach(expectation => {
this.expectations.set(expectation.key, expectation);
});
}
/**
* Track test expectations against actual rendering
*/
async trackExpectations(sessionId, page) {
const actualState = await this.captureActualRenderingState(page);
const comparisons = [];
// Compare each expectation with actual state
for (const [key, expectation] of this.expectations) {
const comparison = this.compareExpectationWithActual(expectation, actualState);
comparisons.push(comparison);
}
// Generate overall result
const result = {
overallStatus: this.determineOverallStatus(comparisons),
totalExpectations: this.expectations.size,
metExpectations: comparisons.filter(c => c.status === 'met').length,
comparisons,
implementationPlan: this.generateImplementationPlan(comparisons),
criticalGaps: this.identifyCriticalGaps(comparisons)
};
// Store in history
this.trackingHistory.set(sessionId, result);
return result;
}
/**
* Capture actual rendering state from the page
*/
async captureActualRenderingState(page) {
const state = {
renderedKeys: [],
missingKeys: [],
extraKeys: [],
elementCounts: new Map(),
renderingTimestamp: Date.now(),
errorMessages: []
};
if (!page) {
state.errorMessages.push('No page available for rendering state capture');
return state;
}
try {
const renderingAnalysis = await page.evaluate(() => {
const analysis = {
renderedKeys: [],
elementCounts: {},
errorMessages: []
};
try {
// Find all elements with test keys
const testElements = document.querySelectorAll('[data-test-key]');
testElements.forEach(element => {
const key = element.getAttribute('data-test-key');
if (key) {
analysis.renderedKeys.push(key);
analysis.elementCounts[key] = (analysis.elementCounts[key] || 0) + 1;
}
});
// Also check for Flutter-specific key attributes
const flutterKeys = document.querySelectorAll('[data-flutter-key]');
flutterKeys.forEach(element => {
const key = element.getAttribute('data-flutter-key');
if (key) {
analysis.renderedKeys.push(key);
analysis.elementCounts[key] = (analysis.elementCounts[key] || 0) + 1;
}
});
// Check for common pattern-based keys
const patternKeys = document.querySelectorAll('[class*="test-key-"], [id*="test-key-"]');
patternKeys.forEach(element => {
const className = element.className;
const id = element.id;
// Extract test keys from class names and IDs
const classMatches = className.match(/test-key-([a-zA-Z0-9_-]+)/g);
const idMatches = id.match(/test-key-([a-zA-Z0-9_-]+)/g);
[...(classMatches || []), ...(idMatches || [])].forEach(match => {
const key = match.replace('test-key-', '');
analysis.renderedKeys.push(key);
analysis.elementCounts[key] = (analysis.elementCounts[key] || 0) + 1;
});
});
}
catch (error) {
analysis.errorMessages.push(`Error analyzing rendered elements: ${error}`);
}
return analysis;
});
state.renderedKeys = [...new Set(renderingAnalysis.renderedKeys)]; // Remove duplicates
state.elementCounts = new Map(Object.entries(renderingAnalysis.elementCounts));
state.errorMessages = renderingAnalysis.errorMessages;
}
catch (error) {
state.errorMessages.push(`Failed to capture rendering state: ${error}`);
}
return state;
}
/**
* Compare expectation with actual rendering state
*/
compareExpectationWithActual(expectation, actualState) {
const comparison = {
expectation,
status: 'not_met',
actualElements: [],
expectedElements: [expectation.key],
gap: '',
recommendation: '',
implementationHint: ''
};
switch (expectation.type) {
case 'exact':
comparison.actualElements = actualState.renderedKeys.filter(key => key === expectation.key);
comparison.status = comparison.actualElements.length > 0 ? 'met' : 'not_met';
break;
case 'pattern':
comparison.actualElements = actualState.renderedKeys.filter(key => key.startsWith(expectation.key));
comparison.status = comparison.actualElements.length > 0 ? 'met' : 'not_met';
break;
case 'count':
const count = actualState.elementCounts.get(expectation.key) || 0;
comparison.actualElements = count > 0 ? [expectation.key] : [];
comparison.status = count > 0 ? 'met' : 'not_met';
break;
case 'conditional':
// Custom logic for conditional expectations
comparison.status = this.evaluateConditionalExpectation(expectation, actualState);
break;
}
// Generate gap analysis and recommendations
comparison.gap = this.generateGapAnalysis(comparison);
comparison.recommendation = this.generateRecommendation(comparison);
comparison.implementationHint = this.generateImplementationHint(comparison);
return comparison;
}
/**
* Evaluate conditional expectations
*/
evaluateConditionalExpectation(expectation, actualState) {
// Check if dependencies are met first
const dependenciesMet = expectation.dependencies.every(dep => actualState.renderedKeys.some(key => key.includes(dep) || key.startsWith(dep)));
if (!dependenciesMet) {
return 'error'; // Cannot evaluate without dependencies
}
// Look for the actual key
const found = actualState.renderedKeys.some(key => key === expectation.key || key.startsWith(expectation.key));
return found ? 'met' : 'not_met';
}
/**
* Generate gap analysis
*/
generateGapAnalysis(comparison) {
if (comparison.status === 'met') {
return 'Expectation fully met';
}
const { expectation, actualElements } = comparison;
if (actualElements.length === 0) {
return `Missing: ${expectation.key} (${expectation.description})`;
}
if (expectation.type === 'pattern' && actualElements.length > 0) {
return `Partial match: Found ${actualElements.length} elements with pattern ${expectation.key}`;
}
return `Incomplete implementation: ${expectation.description}`;
}
/**
* Generate recommendation
*/
generateRecommendation(comparison) {
const { expectation, status } = comparison;
if (status === 'met') {
return 'Continue with current implementation';
}
switch (expectation.criticality) {
case 'must_have':
return `CRITICAL: Implement ${expectation.key} immediately. This blocks core functionality.`;
case 'should_have':
return `HIGH PRIORITY: Add ${expectation.key} for important user features.`;
case 'nice_to_have':
return `ENHANCEMENT: Consider implementing ${expectation.key} for improved UX.`;
default:
return `Implement ${expectation.key} as needed.`;
}
}
/**
* Generate implementation hint
*/
generateImplementationHint(comparison) {
const { expectation } = comparison;
const hintMap = {
'family_overview_section': 'Add Container or Section widget with key: Key("family_overview_section")',
'family_member_status_card_': 'Create ListView.builder with cards having keys: Key("family_member_status_card_${member.id}")',
'add_family_event_quick_action': 'Add ElevatedButton or FloatingActionButton with key: Key("add_family_event_quick_action")',
'add_family_chore_quick_action': 'Add ElevatedButton or IconButton with key: Key("add_family_chore_quick_action")',
'family_wellbeing_indicator': 'Add status indicator widget with key: Key("family_wellbeing_indicator")',
'family_calendar_sync_status': 'Add status display widget with key: Key("family_calendar_sync_status")'
};
return hintMap[expectation.key] ||
`Add widget with key: Key("${expectation.key}") in the appropriate component`;
}
/**
* Determine overall status
*/
determineOverallStatus(comparisons) {
const critical = comparisons.filter(c => c.expectation.criticality === 'must_have');
const criticalMet = critical.filter(c => c.status === 'met');
if (criticalMet.length === critical.length) {
const allMet = comparisons.filter(c => c.status === 'met');
return allMet.length === comparisons.length ? 'passing' : 'partial';
}
return 'failing';
}
/**
* Generate implementation plan
*/
generateImplementationPlan(comparisons) {
const steps = [];
let order = 1;
// Sort by criticality and dependencies
const sortedComparisons = [...comparisons]
.filter(c => c.status !== 'met')
.sort((a, b) => {
const criticalityOrder = { 'must_have': 0, 'should_have': 1, 'nice_to_have': 2 };
return criticalityOrder[a.expectation.criticality] - criticalityOrder[b.expectation.criticality];
});
sortedComparisons.forEach(comparison => {
const { expectation } = comparison;
steps.push({
order: order++,
action: `Implement ${expectation.description}`,
target: expectation.key,
code: this.generateImplementationCode(expectation),
reason: `Required for test: ${expectation.testFunction || 'dashboard tests'}`,
dependencies: expectation.dependencies,
estimatedEffort: this.estimateImplementationEffort(expectation)
});
});
return steps;
}
/**
* Generate implementation code
*/
generateImplementationCode(expectation) {
const codeTemplates = {
'family_overview_section': `
Container(
key: const Key('family_overview_section'),
child: Column(
children: [
// Family overview content
],
),
)`,
'family_member_status_card_': `
ListView.builder(
itemCount: familyMembers.length,
itemBuilder: (context, index) {
final member = familyMembers[index];
return Card(
key: Key('family_member_status_card_\${member.id}'),
child: ListTile(
title: Text(member.name),
// Add status indicator
),
);
},
)`,
'add_family_event_quick_action': `
ElevatedButton(
key: const Key('add_family_event_quick_action'),
onPressed: () => _addFamilyEvent(),
child: const Text('Add Event'),
)`,
'add_family_chore_quick_action': `
ElevatedButton(
key: const Key('add_family_chore_quick_action'),
onPressed: () => _addFamilyChore(),
child: const Text('Add Chore'),
)`
};
return codeTemplates[expectation.key] ||
`Widget(\n key: const Key('${expectation.key}'),\n // Implement ${expectation.description}\n)`;
}
/**
* Estimate implementation effort
*/
estimateImplementationEffort(expectation) {
if (expectation.dependencies.length === 0)
return 'small';
if (expectation.dependencies.length <= 2)
return 'medium';
return 'large';
}
/**
* Identify critical gaps
*/
identifyCriticalGaps(comparisons) {
return comparisons
.filter(c => c.expectation.criticality === 'must_have' && c.status !== 'met')
.map(c => `${c.expectation.key}: ${c.gap}`);
}
/**
* Add custom expectation
*/
addExpectation(expectation) {
this.expectations.set(expectation.key, expectation);
}
/**
* Get expectation by key
*/
getExpectation(key) {
return this.expectations.get(key);
}
/**
* Get tracking history
*/
getTrackingHistory(sessionId) {
return this.trackingHistory.get(sessionId);
}
/**
* Clear expectations (for testing)
*/
clearExpectations() {
this.expectations.clear();
this.initializeCommonExpectations();
}
/**
* Get all expectations
*/
getAllExpectations() {
return Array.from(this.expectations.values());
}
}
//# sourceMappingURL=test-expectation-tracker.js.map