Add comprehensive Cypress E2E tests for Claude Explain feature (#7751)

- Add comprehensive test suite covering all Claude Explain functionality:
  - Basic pane opening and consent flow
  - no-ai directive detection
  - API interactions and error handling
  - Options/customization features
  - Caching behavior and persistence
  - Compilation state handling
  - State persistence across page loads

- Fix caching bug in explain-view.ts:
  - Cache was incorrectly implemented as instance variable, losing cached explanations when panes were closed/reopened
  - Made cache static to persist across all pane instances (matches consent persistence pattern)
  - Fixes failing "Caching and reload" Cypress test
  - Aligns implementation with documented behavior: "shared across all explain views in the session"

- Add test utilities and helpers:
  - Monaco editor content manipulation using clipboard events
  - Claude Explain specific helpers moved to test file
  - General utilities remain in utils.ts

- Performance optimizations:
  - Clear Cypress intercepts in afterEach to prevent O(n²) degradation
  - Use :visible selectors to avoid GoldenLayout template elements
  - Proper mock setup timing to prevent race conditions

- Add comprehensive README with lessons learned and best practices

All tests use fake test data (test_first, focus_a, etc.) to clearly distinguish from production values and prevent accidental API calls.
This commit is contained in:
Matt Godbolt
2025-08-05 16:42:48 -05:00
committed by GitHub
parent e9011ec592
commit 50ec53d0e7
6 changed files with 916 additions and 9 deletions

View File

@@ -97,8 +97,6 @@ export class ExplainView extends Pane<ExplainViewState> {
private readonly explanationInfoButton: JQuery;
private readonly explainApiEndpoint: string;
private readonly fontScale: FontScale;
private readonly cache: LRUCache<string, ClaudeExplainResponse>;
// Use a static variable to persist consent across all instances during the session
private static consentGiven = false;
@@ -106,6 +104,9 @@ export class ExplainView extends Pane<ExplainViewState> {
private static availableOptions: AvailableOptions | null = null;
private static optionsFetchPromise: Promise<AvailableOptions> | null = null;
// Static explanation cache shared across all instances (200KB limit)
private static cache: LRUCache<string, ClaudeExplainResponse> | null = null;
// Instance variables for selected options
private selectedAudience: string;
private selectedExplanation: string;
@@ -118,10 +119,14 @@ export class ExplainView extends Pane<ExplainViewState> {
super(hub, container, state);
this.explainApiEndpoint = options.explainApiEndpoint ?? '';
this.cache = new LRUCache({
maxSize: 200 * 1024,
sizeCalculation: n => JSON.stringify(n).length,
});
// Initialize static cache if not already done
if (!ExplainView.cache) {
ExplainView.cache = new LRUCache({
maxSize: 200 * 1024,
sizeCalculation: n => JSON.stringify(n).length,
});
}
this.statusIcon = this.domRoot.find('.status-icon');
this.consentElement = this.domRoot.find('.explain-consent');
@@ -465,7 +470,7 @@ export class ExplainView extends Pane<ExplainViewState> {
}
this.showSuccess();
this.cache.set(cacheKey, data);
ExplainView.cache!.set(cacheKey, data);
this.renderMarkdown(data.explanation);
this.showBottomBar();
@@ -505,7 +510,7 @@ export class ExplainView extends Pane<ExplainViewState> {
// Check cache first unless bypassing
if (!bypassCache) {
const cachedResult = this.cache.get(cacheKey);
const cachedResult = ExplainView.cache!.get(cacheKey);
if (cachedResult) {
this.displayCachedResult(cachedResult);
return;