From b44a1ea86b29c866a51ee50c9d4f6f9f4091c009 Mon Sep 17 00:00:00 2001 From: Angelo Girardi Date: Thu, 18 Dec 2025 19:13:45 -0600 Subject: [PATCH 1/2] Cursor: Apply local changes for cloud agent --- docs/planning/ROADMAP_1.0.0.md | 8 +- src/providers/jira/cloud/JiraCloudClient.ts | 92 +++ .../jira/cloud/JiraIssuesProvider.ts | 771 ++++++++++++++---- .../jira/common/JiraClientFactory.ts | 91 +++ src/providers/jira/server/JiraServerClient.ts | 93 +++ 5 files changed, 910 insertions(+), 145 deletions(-) create mode 100644 src/providers/jira/common/JiraClientFactory.ts diff --git a/docs/planning/ROADMAP_1.0.0.md b/docs/planning/ROADMAP_1.0.0.md index 1643dc2..0fcd126 100644 --- a/docs/planning/ROADMAP_1.0.0.md +++ b/docs/planning/ROADMAP_1.0.0.md @@ -46,8 +46,8 @@ This document tracks the path to DevBuddy 1.0.0, focusing on: | Task | Priority | Effort | Status | |------|----------|--------|--------| -| **Jira: Add "Recently Completed" section** | 🔴 P0 | 🟢 | ⬜ Not Started | -| **Jira: Add "Project Unassigned" section** | 🔴 P0 | 🟢 | ⬜ Not Started | +| **Jira: Add "Recently Completed" section** | 🔴 P0 | 🟢 | ✅ Done | +| **Jira: Add "Project Unassigned" section** | 🔴 P0 | 🟢 | ✅ Done | | **Jira: Add "Current Sprint" section** | 🔴 P0 | 🟡 | ⬜ Not Started | | **Jira: Show Sprint name in tree view** | 🟡 P1 | 🟢 | ⬜ Not Started | | **Jira: Board quick-view (collapsible)** | 🟢 P2 | 🟡 | ⬜ Not Started | @@ -336,8 +336,8 @@ These features will ship with 1.0 but remain marked as "Beta 💎": ## 9. Release Milestones ### Milestone 1: Sidebar Parity (Week 1-2) -- [ ] Jira: Recently Completed section -- [ ] Jira: Project Unassigned section +- [x] Jira: Recently Completed section +- [x] Jira: Project Unassigned section - [ ] Jira: Current Sprint section - [ ] Jira: Sprint name display diff --git a/src/providers/jira/cloud/JiraCloudClient.ts b/src/providers/jira/cloud/JiraCloudClient.ts index f873ac3..419189e 100644 --- a/src/providers/jira/cloud/JiraCloudClient.ts +++ b/src/providers/jira/cloud/JiraCloudClient.ts @@ -314,6 +314,27 @@ export class JiraCloudClient extends BaseJiraClient { } } + /** + * Get recently completed issues assigned to current user + * Returns issues resolved in the last 14 days, sorted by resolution date + */ + async getRecentlyCompletedIssues(daysAgo: number = 14): Promise { + try { + const user = await this.getCurrentUser(); + if (!user) { + return []; + } + + return this.searchIssues({ + jql: `assignee = currentUser() AND resolution IS NOT EMPTY AND resolved >= -${daysAgo}d ORDER BY resolved DESC`, + maxResults: 20, + }); + } catch (error) { + logger.error("Failed to get recently completed issues:", error); + return []; + } + } + /** * Create a new issue */ @@ -659,6 +680,22 @@ export class JiraCloudClient extends BaseJiraClient { } } + /** + * Get unassigned issues for a project + * Returns issues that have no assignee, limited to recent unresolved ones + */ + async getProjectUnassignedIssues(projectKey: string, maxResults: number = 20): Promise { + try { + return this.searchIssues({ + jql: `project = "${projectKey}" AND assignee IS EMPTY AND resolution = Unresolved ORDER BY priority DESC, created DESC`, + maxResults, + }); + } catch (error) { + logger.error(`Failed to get unassigned issues for project ${projectKey}:`, error); + return []; + } + } + /** * Get a single project by key */ @@ -947,6 +984,61 @@ export class JiraCloudClient extends BaseJiraClient { } } + /** + * Get issues in a specific sprint + */ + async getSprintIssues(sprintId: number): Promise { + try { + const agileBaseUrl = this.getApiBaseUrl().replace( + "/rest/api/3", + "/rest/agile/1.0" + ); + const url = `${agileBaseUrl}/sprint/${sprintId}/issue?maxResults=100`; + + const response = await fetch(url, { + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + ...this.getAuthHeaders(), + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch sprint issues: ${response.statusText}`); + } + + const data = await response.json() as { issues?: any[] }; + return data.issues?.map((issue: any) => this.normalizeIssue(issue)) || []; + } catch (error) { + logger.error(`Failed to fetch issues for sprint ${sprintId}`, error); + return []; + } + } + + /** + * Get my issues in the current sprint + */ + async getMySprintIssues(sprintId: number): Promise { + const allIssues = await this.getSprintIssues(sprintId); + const currentUser = await this.getCurrentUser(); + + if (!currentUser) { + return []; + } + + return allIssues.filter( + (issue) => issue.assignee?.accountId === currentUser.accountId + ); + } + + /** + * Get unassigned issues in the current sprint + */ + async getSprintUnassignedIssues(sprintId: number): Promise { + const allIssues = await this.getSprintIssues(sprintId); + return allIssues.filter((issue) => !issue.assignee); + } + // ==================== Helper Methods ==================== /** diff --git a/src/providers/jira/cloud/JiraIssuesProvider.ts b/src/providers/jira/cloud/JiraIssuesProvider.ts index eccfebc..ff54d9e 100644 --- a/src/providers/jira/cloud/JiraIssuesProvider.ts +++ b/src/providers/jira/cloud/JiraIssuesProvider.ts @@ -2,35 +2,248 @@ * Jira Issues Tree View Provider * * Displays Jira issues in the VS Code sidebar. + * Matches the structure of Linear's sidebar with: + * - My Issues (grouped by status) + * - Recently Completed + * - (Future: Current Sprint, Projects) */ import * as vscode from "vscode"; -import { JiraCloudClient } from "./JiraCloudClient"; -import { JiraIssue } from "../common/types"; +import { JiraIssue, JiraProject, JiraSprint, JiraBoard } from "../common/types"; +import { IJiraClient, createJiraClient, resetJiraClient } from "../common/JiraClientFactory"; import { getLogger } from "@shared/utils/logger"; +import { BranchAssociationManager } from "@shared/git/branchAssociationManager"; const logger = getLogger(); -interface JiraTreeItem { - type: "group" | "issue"; +// Tree item types for the Jira sidebar +type JiraTreeItemType = + | "section" // Top-level collapsible sections (My Issues, Recently Completed, Sprint, Projects) + | "statusGroup" // Status group within My Issues (In Progress, To Do, etc.) + | "project" // Individual project in Projects section + | "sprintSubsection" // Subsection within Sprint (My Tasks, Unassigned) + | "issue"; // Individual issue + +interface JiraTreeItemData { + type: JiraTreeItemType; + id: string; label: string; issue?: JiraIssue; - collapsibleState?: vscode.TreeItemCollapsibleState; + project?: JiraProject; + sprint?: JiraSprint; + statusCategory?: string; + statusName?: string; + count?: number; } -export class JiraIssuesProvider implements vscode.TreeDataProvider { - private client: JiraCloudClient | null = null; +export class JiraIssueTreeItem extends vscode.TreeItem { + constructor( + public readonly data: JiraTreeItemData, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + private branchManager?: BranchAssociationManager + ) { + super(data.label, collapsibleState); + + if (data.type === "issue" && data.issue) { + this.setupIssueItem(data.issue); + } else if (data.type === "section") { + this.setupSectionItem(data); + } else if (data.type === "statusGroup") { + this.setupStatusGroupItem(data); + } else if (data.type === "project" && data.project) { + this.setupProjectItem(data.project); + } else if (data.type === "sprintSubsection") { + this.setupSprintSubsectionItem(data); + } + } + + private setupIssueItem(issue: JiraIssue): void { + this.label = issue.summary; + this.description = issue.key; + this.tooltip = this.buildIssueTooltip(issue); + this.iconPath = this.getIssueIcon(issue); + this.contextValue = this.buildContextValue(issue); + + this.command = { + command: "devBuddy.jira.openIssue", + title: "Open Issue", + arguments: [issue], + }; + } + + private setupSectionItem(data: JiraTreeItemData): void { + this.contextValue = data.id; + this.command = undefined; + + // Style section headers + let icon = "folder-opened"; + let iconColor: vscode.ThemeColor; + + switch (data.id) { + case "myIssuesSection": + iconColor = new vscode.ThemeColor("charts.blue"); + break; + case "completedSection": + iconColor = new vscode.ThemeColor("charts.green"); + break; + case "sprintSection": + iconColor = new vscode.ThemeColor("charts.yellow"); + break; + case "projectsSection": + iconColor = new vscode.ThemeColor("charts.orange"); + break; + default: + iconColor = new vscode.ThemeColor("symbolIcon.classForeground"); + } + + this.iconPath = new vscode.ThemeIcon(icon, iconColor); + } + + private setupStatusGroupItem(data: JiraTreeItemData): void { + this.contextValue = "statusGroup"; + this.command = undefined; + + // Indent status groups with spaces + this.label = ` ${data.label}`; + + // Color based on status category + let icon: string; + let iconColor: vscode.ThemeColor; + + switch (data.statusCategory) { + case "indeterminate": // In Progress + icon = "record"; + iconColor = new vscode.ThemeColor("charts.blue"); + break; + case "done": + icon = "pass-filled"; + iconColor = new vscode.ThemeColor("charts.green"); + break; + case "new": // To Do + default: + icon = "circle-large-outline"; + iconColor = new vscode.ThemeColor("charts.orange"); + break; + } + + this.iconPath = new vscode.ThemeIcon(icon, iconColor); + } + + private setupProjectItem(project: JiraProject): void { + this.label = ` ${project.name}`; + this.description = project.key; + this.tooltip = `${project.name} (${project.key})\nClick to view unassigned issues`; + this.contextValue = "jiraProject"; + this.command = undefined; + this.iconPath = new vscode.ThemeIcon("folder", new vscode.ThemeColor("charts.orange")); + } + + private setupSprintSubsectionItem(data: JiraTreeItemData): void { + this.label = ` ${data.label}`; + this.contextValue = data.id; + this.command = undefined; + + // Icon based on subsection type + if (data.id === "mySprintTasks") { + this.iconPath = new vscode.ThemeIcon("account", new vscode.ThemeColor("charts.blue")); + } else if (data.id === "sprintUnassigned") { + this.iconPath = new vscode.ThemeIcon("person-add", new vscode.ThemeColor("charts.orange")); + } else { + this.iconPath = new vscode.ThemeIcon("list-unordered"); + } + } + + private getIssueIcon(issue: JiraIssue): vscode.ThemeIcon { + const typeLower = issue.issueType.name.toLowerCase(); + const statusCategory = issue.status.statusCategory.key; + + // For completed issues, always show green checkmark + if (statusCategory === "done") { + return new vscode.ThemeIcon("check-all", new vscode.ThemeColor("charts.green")); + } + + // For other statuses, use issue type icons + if (typeLower.includes("bug")) { + return new vscode.ThemeIcon("bug", new vscode.ThemeColor("errorForeground")); + } else if (typeLower.includes("story")) { + return new vscode.ThemeIcon("book", new vscode.ThemeColor("charts.blue")); + } else if (typeLower.includes("task")) { + return new vscode.ThemeIcon("checklist"); + } else if (typeLower.includes("epic")) { + return new vscode.ThemeIcon("rocket", new vscode.ThemeColor("charts.purple")); + } else if (typeLower.includes("subtask") || issue.issueType.subtask) { + return new vscode.ThemeIcon("list-tree"); + } + + return new vscode.ThemeIcon("issue-opened"); + } + + private buildIssueTooltip(issue: JiraIssue): string { + const lines = [ + `${issue.key}: ${issue.summary}`, + ``, + `Type: ${issue.issueType.name}`, + `Status: ${issue.status.name}`, + `Priority: ${issue.priority.name}`, + `Project: ${issue.project.name} (${issue.project.key})`, + ]; + + if (issue.assignee) { + lines.push(`Assignee: ${issue.assignee.displayName}`); + } + + if (issue.dueDate) { + lines.push(`Due: ${new Date(issue.dueDate).toLocaleDateString()}`); + } + + if (issue.labels.length > 0) { + lines.push(`Labels: ${issue.labels.join(", ")}`); + } + + return lines.join("\n"); + } + + private buildContextValue(issue: JiraIssue): string { + const parts = ["jiraIssue"]; + + // Add status category + const statusCategory = issue.status.statusCategory.key; + parts.push(statusCategory); + + // Check for branch association + if (this.branchManager) { + const associatedBranch = this.branchManager.getBranchForTicket(issue.key); + if (associatedBranch) { + parts.push("withBranch"); + } + } + + return parts.join(":"); + } +} + +export class JiraIssuesProvider implements vscode.TreeDataProvider { + private client: IJiraClient | null = null; + private branchManager: BranchAssociationManager; private issues: JiraIssue[] = []; + private completedIssues: JiraIssue[] = []; + private projects: JiraProject[] = []; + private activeSprint: JiraSprint | null = null; + private boards: JiraBoard[] = []; + private mySprintIssues: JiraIssue[] = []; + private sprintUnassignedIssues: JiraIssue[] = []; private autoRefreshInterval: NodeJS.Timeout | null = null; + private isRefreshing: boolean = false; private _onDidChangeTreeData: vscode.EventEmitter< - JiraTreeItem | undefined | null | void - > = new vscode.EventEmitter(); + JiraIssueTreeItem | undefined | null | void + > = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event< - JiraTreeItem | undefined | null | void + JiraIssueTreeItem | undefined | null | void > = this._onDidChangeTreeData.event; constructor(private context: vscode.ExtensionContext) { + this.branchManager = new BranchAssociationManager(context); this.initializeClient(); this.setupAutoRefresh(); } @@ -59,6 +272,7 @@ export class JiraIssuesProvider implements vscode.TreeDataProvider if (interval > 0) { this.autoRefreshInterval = setInterval(() => { + logger.debug("Auto-refreshing Jira issues..."); this.refresh(); }, interval); } @@ -66,185 +280,460 @@ export class JiraIssuesProvider implements vscode.TreeDataProvider refresh(): void { this._onDidChangeTreeData.fire(); - this.loadIssues(); } - private async loadIssues(): Promise { - if (!this.client || !this.client.isConfigured()) { - this.issues = []; + /** + * Refresh data in background without blocking UI + */ + async refreshInBackground(): Promise { + if (this.isRefreshing) { return; } + this.isRefreshing = true; try { - this.issues = await this.client.getMyIssues(); - logger.info(`Loaded ${this.issues.length} Jira issues`); - this._onDidChangeTreeData.fire(); + if (!this.client || !this.client.isConfigured()) { + return; + } + + // Fetch issues and projects in parallel + const [myIssues, recentlyCompleted, projects] = await Promise.all([ + this.client.getMyIssues().catch(() => [] as JiraIssue[]), + this.client.getRecentlyCompletedIssues().catch(() => [] as JiraIssue[]), + this.client.getProjects().catch(() => [] as JiraProject[]), + ]); + + this.issues = myIssues; + this.completedIssues = recentlyCompleted; + this.projects = projects; + + logger.debug("Background refresh completed - Jira data preloaded"); } catch (error) { - logger.error("Failed to load Jira issues:", error); - this.issues = []; + logger.error("Background refresh failed:", error); + } finally { + this.isRefreshing = false; } } - getTreeItem(element: JiraTreeItem): vscode.TreeItem { - if (element.type === "group") { - const item = new vscode.TreeItem( - element.label, - vscode.TreeItemCollapsibleState.Expanded - ); - item.contextValue = "jiraGroup"; - return item; + getTreeItem(element: JiraIssueTreeItem): vscode.TreeItem { + return element; + } + + async getChildren(element?: JiraIssueTreeItem): Promise { + if (!this.client || !this.client.isConfigured()) { + return [this.createConfigureItem()]; } - // Issue item - const issue = element.issue!; - const item = new vscode.TreeItem( - `${issue.key}: ${issue.summary}`, - vscode.TreeItemCollapsibleState.None - ); + // Root level - show top-level sections + if (!element) { + return this.getRootSections(); + } - item.description = issue.status.name; - item.tooltip = this.getIssueTooltip(issue); - item.iconPath = this.getIssueIcon(issue); - item.contextValue = this.getIssueContextValue(issue); + // Handle expanding different section types + switch (element.data.id) { + case "myIssuesSection": + return this.getMyIssuesChildren(); - item.command = { - command: "devBuddy.jira.openIssue", - title: "Open Issue", - arguments: [issue], - }; + case "completedSection": + return this.getCompletedIssuesChildren(); - return item; + case "projectsSection": + return this.getProjectsChildren(); + + case "statusGroup": + // Return issues for this status group + return this.getIssuesForStatus(element.data.statusName || ""); + + default: + // Check if it's a project item (expand to show unassigned issues) + if (element.data.type === "project" && element.data.project) { + return this.getProjectUnassignedIssues(element.data.project.key); + } + return []; + } } - async getChildren(element?: JiraTreeItem): Promise { - if (!this.client || !this.client.isConfigured()) { - return [ - { - type: "group", - label: "Click here to configure Jira", - collapsibleState: vscode.TreeItemCollapsibleState.None, - }, - ]; + /** + * Get root level sections + */ + private getRootSections(): JiraIssueTreeItem[] { + const sections: JiraIssueTreeItem[] = []; + + // Section 1: My Issues + sections.push(this.createSection("myIssuesSection", "My Issues")); + + // Section 2: Recently Completed + sections.push(this.createSection("completedSection", "Recently Completed")); + + // Section 3: Projects (with unassigned issues) + sections.push(this.createSection("projectsSection", "Projects")); + + // Future sections (P0): + // sections.push(this.createSection("sprintSection", "Current Sprint")); + + return sections; + } + + /** + * Get children for My Issues section - groups by status + */ + private async getMyIssuesChildren(): Promise { + try { + if (!this.client) { + return [this.createErrorItem()]; + } + + // Load issues if not cached + if (this.issues.length === 0) { + this.issues = await this.client.getMyIssues(); + } + + if (this.issues.length === 0) { + return [this.createNoIssuesItem()]; + } + + // Group by status category + const groups = this.groupIssuesByStatusCategory(this.issues); + const items: JiraIssueTreeItem[] = []; + + // Define preferred order + const order = ["indeterminate", "new", "undefined"]; // In Progress, To Do, Other + + for (const category of order) { + const group = groups[category]; + if (group && group.issues.length > 0) { + items.push(this.createStatusGroup( + group.displayName, + group.issues.length, + category + )); + } + } + + return items; + } catch (error) { + logger.error("Failed to get my issues children:", error); + return [this.createErrorItem()]; } + } - if (!element) { - // Root level - group by status category - const groups = this.groupIssuesByStatus(this.issues); - return Object.entries(groups).map(([status, _issues]) => ({ - type: "group" as const, - label: `${status} (${_issues.length})`, - collapsibleState: vscode.TreeItemCollapsibleState.Expanded, - })); + /** + * Get children for Recently Completed section + */ + private async getCompletedIssuesChildren(): Promise { + try { + if (!this.client) { + return [this.createErrorItem()]; + } + + // Load completed issues if not cached + if (this.completedIssues.length === 0) { + this.completedIssues = await this.client.getRecentlyCompletedIssues(); + } + + if (this.completedIssues.length === 0) { + return [this.createNoCompletedIssuesItem()]; + } + + // Return issues sorted by resolution date (most recent first) + // Limit to 10 for better UX + return this.completedIssues.slice(0, 10).map( + (issue) => new JiraIssueTreeItem( + { + type: "issue", + id: issue.id, + label: issue.summary, + issue, + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ) + ); + } catch (error) { + logger.error("Failed to get completed issues children:", error); + return [this.createErrorItem()]; } + } - if (element.type === "group") { - // Get issues for this status group - const status = element.label.split(" (")[0]; // Extract status from "Status (count)" - const groups = this.groupIssuesByStatus(this.issues); - const issuesInGroup = groups[status] || []; + /** + * Get children for Projects section - list of projects + */ + private async getProjectsChildren(): Promise { + try { + if (!this.client) { + return [this.createErrorItem()]; + } - return issuesInGroup.map((issue) => ({ - type: "issue" as const, - label: issue.key, - issue, - })); + // Load projects if not cached + if (this.projects.length === 0) { + this.projects = await this.client.getProjects(); + } + + if (this.projects.length === 0) { + return [this.createNoProjectsItem()]; + } + + // Return projects as expandable items + return this.projects.map( + (project) => new JiraIssueTreeItem( + { + type: "project", + id: `project-${project.id}`, + label: project.name, + project, + }, + vscode.TreeItemCollapsibleState.Collapsed, + this.branchManager + ) + ); + } catch (error) { + logger.error("Failed to get projects children:", error); + return [this.createErrorItem()]; + } + } + + /** + * Get unassigned issues for a specific project + */ + private async getProjectUnassignedIssues(projectKey: string): Promise { + try { + if (!this.client) { + return [this.createErrorItem()]; + } + + const unassignedIssues = await this.client.getProjectUnassignedIssues(projectKey); + + if (unassignedIssues.length === 0) { + return [this.createNoUnassignedIssuesItem()]; + } + + // Limit to 15 issues for better UX + const items = unassignedIssues.slice(0, 15).map( + (issue) => new JiraIssueTreeItem( + { + type: "issue", + id: issue.id, + label: issue.summary, + issue, + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ) + ); + + // Add "more" indicator if there are more issues + if (unassignedIssues.length > 15) { + items.push(this.createMoreIssuesItem(unassignedIssues.length - 15)); + } + + return items; + } catch (error) { + logger.error(`Failed to get unassigned issues for project ${projectKey}:`, error); + return [this.createErrorItem()]; + } + } + + /** + * Get issues for a specific status + */ + private getIssuesForStatus(statusName: string): JiraIssueTreeItem[] { + const groups = this.groupIssuesByStatusCategory(this.issues); + + // Find the group matching this display name + for (const group of Object.values(groups)) { + if (group.displayName === statusName) { + return group.issues.map( + (issue) => new JiraIssueTreeItem( + { + type: "issue", + id: issue.id, + label: issue.summary, + issue, + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ) + ); + } } return []; } - private groupIssuesByStatus(issues: JiraIssue[]): Record { - const groups: Record = { - "To Do": [], - "In Progress": [], - "Done": [], - "Other": [], + /** + * Group issues by status category + */ + private groupIssuesByStatusCategory(issues: JiraIssue[]): Record { + const groups: Record = { + indeterminate: { displayName: "In Progress", issues: [] }, + new: { displayName: "To Do", issues: [] }, + undefined: { displayName: "Other", issues: [] }, }; for (const issue of issues) { const categoryKey = issue.status.statusCategory.key; - if (categoryKey === "new") { - groups["To Do"].push(issue); - } else if (categoryKey === "indeterminate") { - groups["In Progress"].push(issue); - } else if (categoryKey === "done") { - groups["Done"].push(issue); - } else { - groups["Other"].push(issue); + if (categoryKey === "indeterminate") { + groups.indeterminate.issues.push(issue); + } else if (categoryKey === "new") { + groups.new.issues.push(issue); + } else if (categoryKey !== "done") { + // Skip done issues in My Issues + groups.undefined.issues.push(issue); } } - // Remove empty groups - return Object.fromEntries( - Object.entries(groups).filter(([_, items]) => items.length > 0) - ); + return groups; } - private getIssueIcon(issue: JiraIssue): vscode.ThemeIcon { - // Map Jira issue types to VS Code icons - const typeLower = issue.issueType.name.toLowerCase(); - - if (typeLower.includes("bug")) { - return new vscode.ThemeIcon("bug"); - } else if (typeLower.includes("story")) { - return new vscode.ThemeIcon("book"); - } else if (typeLower.includes("task")) { - return new vscode.ThemeIcon("checklist"); - } else if (typeLower.includes("epic")) { - return new vscode.ThemeIcon("rocket"); - } else if (typeLower.includes("subtask") || issue.issueType.subtask) { - return new vscode.ThemeIcon("list-tree"); - } else { - return new vscode.ThemeIcon("issue-opened"); - } + // ==================== Item Creation Helpers ==================== + + private createSection(id: string, label: string): JiraIssueTreeItem { + return new JiraIssueTreeItem( + { + type: "section", + id, + label, + }, + vscode.TreeItemCollapsibleState.Collapsed, + this.branchManager + ); } - private getIssueTooltip(issue: JiraIssue): string { - const lines = [ - `**${issue.key}**: ${issue.summary}`, - ``, - `**Type**: ${issue.issueType.name}`, - `**Status**: ${issue.status.name}`, - `**Priority**: ${issue.priority.name}`, - `**Project**: ${issue.project.name} (${issue.project.key})`, - ]; - - if (issue.assignee) { - lines.push(`**Assignee**: ${issue.assignee.displayName}`); - } else { - lines.push(`**Assignee**: Unassigned`); - } + private createStatusGroup(displayName: string, count: number, statusCategory: string): JiraIssueTreeItem { + return new JiraIssueTreeItem( + { + type: "statusGroup", + id: "statusGroup", + label: `${displayName} (${count})`, + statusCategory, + statusName: displayName, + count, + }, + vscode.TreeItemCollapsibleState.Collapsed, + this.branchManager + ); + } - if (issue.dueDate) { - lines.push(`**Due**: ${new Date(issue.dueDate).toLocaleDateString()}`); - } + private createConfigureItem(): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "configure", + label: "Configure Jira Connection", + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("settings-gear"); + item.command = { + command: "devBuddy.jira.setup", + title: "Configure Jira", + }; + item.contextValue = "configure"; + return item; + } - if (issue.labels.length > 0) { - lines.push(`**Labels**: ${issue.labels.join(", ")}`); - } + private createNoIssuesItem(): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "no-issues", + label: " No active issues assigned to you", + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("check", new vscode.ThemeColor("charts.green")); + item.contextValue = "no-issues"; + return item; + } - if (issue.description) { - lines.push(``, `**Description**:`); - lines.push(issue.description.substring(0, 200) + (issue.description.length > 200 ? "..." : "")); - } + private createNoCompletedIssuesItem(): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "no-completed", + label: " No recently completed issues", + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("info", new vscode.ThemeColor("descriptionForeground")); + item.contextValue = "no-completed"; + return item; + } - return lines.join("\n"); + private createNoProjectsItem(): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "no-projects", + label: " No projects found", + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("info", new vscode.ThemeColor("descriptionForeground")); + item.contextValue = "no-projects"; + return item; } - private getIssueContextValue(issue: JiraIssue): string { - const parts = ["jiraIssue"]; + private createNoUnassignedIssuesItem(): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "no-unassigned", + label: " No unassigned issues", + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("check", new vscode.ThemeColor("charts.green")); + item.contextValue = "no-unassigned"; + return item; + } - if (issue.assignee) { - parts.push("assigned"); - } else { - parts.push("unassigned"); - } + private createMoreIssuesItem(count: number): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "more-issues", + label: ` ... and ${count} more`, + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("ellipsis", new vscode.ThemeColor("descriptionForeground")); + item.contextValue = "more-issues"; + item.tooltip = "View more in Jira"; + return item; + } - const statusCategory = issue.status.statusCategory.key; - parts.push(statusCategory); + private createErrorItem(): JiraIssueTreeItem { + const item = new JiraIssueTreeItem( + { + type: "section", + id: "error", + label: "Failed to load issues", + }, + vscode.TreeItemCollapsibleState.None, + this.branchManager + ); + item.iconPath = new vscode.ThemeIcon("error"); + item.command = { + command: "devBuddy.refreshTickets", + title: "Refresh", + }; + item.contextValue = "error"; + return item; + } - return parts.join(":"); + /** + * Get all cached issues + */ + getIssues(): JiraIssue[] { + return this.issues; } dispose(): void { diff --git a/src/providers/jira/common/JiraClientFactory.ts b/src/providers/jira/common/JiraClientFactory.ts new file mode 100644 index 0000000..30c9928 --- /dev/null +++ b/src/providers/jira/common/JiraClientFactory.ts @@ -0,0 +1,91 @@ +/** + * Jira Client Factory + * + * Creates the appropriate Jira client based on user configuration. + * Supports both Jira Cloud and Jira Server/Data Center. + */ + +import * as vscode from "vscode"; +import { JiraCloudClient } from "../cloud/JiraCloudClient"; +import { JiraServerClient } from "../server/JiraServerClient"; +import { BaseJiraClient } from "./BaseJiraClient"; +import { JiraIssue, JiraProject, JiraSprint, JiraBoard } from "./types"; +import { getLogger } from "@shared/utils/logger"; + +const logger = getLogger(); + +/** + * Extended Jira Client interface with all methods used by the sidebar + */ +export interface IJiraClient extends BaseJiraClient { + // Additional methods not in BaseJiraClient + getRecentlyCompletedIssues(daysAgo?: number): Promise; + getProjectUnassignedIssues(projectKey: string, maxResults?: number): Promise; + getSprintIssues(sprintId: number): Promise; + getMySprintIssues(sprintId: number): Promise; + getSprintUnassignedIssues(sprintId: number): Promise; +} + +/** + * Get the configured Jira deployment type + */ +export function getJiraDeploymentType(): "cloud" | "server" { + const config = vscode.workspace.getConfiguration("devBuddy"); + return config.get<"cloud" | "server">("jira.type", "cloud"); +} + +/** + * Check if Jira is the active provider + */ +export function isJiraProvider(): boolean { + const config = vscode.workspace.getConfiguration("devBuddy"); + return config.get("provider", "linear") === "jira"; +} + +/** + * Create the appropriate Jira client based on configuration + */ +export async function createJiraClient(): Promise { + const deploymentType = getJiraDeploymentType(); + + logger.debug(`Creating Jira client for deployment type: ${deploymentType}`); + + try { + if (deploymentType === "server") { + const client = await JiraServerClient.create(); + if (client.isConfigured()) { + logger.info("Using Jira Server client"); + return client as unknown as IJiraClient; + } + logger.warn("Jira Server not configured"); + return null; + } else { + const client = await JiraCloudClient.create(); + if (client.isConfigured()) { + logger.info("Using Jira Cloud client"); + return client as unknown as IJiraClient; + } + logger.warn("Jira Cloud not configured"); + return null; + } + } catch (error) { + logger.error(`Failed to create Jira client (${deploymentType}):`, error); + return null; + } +} + +/** + * Reset the Jira client singleton (call when configuration changes) + */ +export function resetJiraClient(): void { + const deploymentType = getJiraDeploymentType(); + + if (deploymentType === "server") { + JiraServerClient.reset(); + } else { + JiraCloudClient.reset(); + } + + logger.debug(`Reset Jira client (${deploymentType})`); +} + diff --git a/src/providers/jira/server/JiraServerClient.ts b/src/providers/jira/server/JiraServerClient.ts index b70bfe7..49be214 100644 --- a/src/providers/jira/server/JiraServerClient.ts +++ b/src/providers/jira/server/JiraServerClient.ts @@ -479,6 +479,43 @@ export class JiraServerClient extends BaseJiraClient { }); } + /** + * Get recently completed issues assigned to current user + * Returns issues resolved in the last 14 days, sorted by resolution date + */ + async getRecentlyCompletedIssues(daysAgo: number = 14): Promise { + try { + const user = await this.getCurrentUser(); + if (!user) { + return []; + } + + return this.searchIssues({ + jql: `assignee = currentUser() AND resolution IS NOT EMPTY AND resolved >= -${daysAgo}d ORDER BY resolved DESC`, + maxResults: 20, + }); + } catch (error) { + logger.error("Failed to get recently completed issues:", error); + return []; + } + } + + /** + * Get unassigned issues for a project + * Returns issues that have no assignee, limited to recent unresolved ones + */ + async getProjectUnassignedIssues(projectKey: string, maxResults: number = 20): Promise { + try { + return this.searchIssues({ + jql: `project = "${projectKey}" AND assignee IS EMPTY AND resolution = Unresolved ORDER BY priority DESC, created DESC`, + maxResults, + }); + } catch (error) { + logger.error(`Failed to get unassigned issues for project ${projectKey}:`, error); + return []; + } + } + async createIssue(input: CreateJiraIssueInput): Promise { try { const fields: any = { @@ -812,6 +849,62 @@ export class JiraServerClient extends BaseJiraClient { return sprints.find((s) => s.state === "active") || null; } + /** + * Get issues in a specific sprint + */ + async getSprintIssues(sprintId: number): Promise { + if (!this.capabilities?.sprint) { + logger.warn("Sprint feature not supported on this Jira Server version"); + return []; + } + + try { + const response = await fetch( + `${this.baseUrl}/rest/agile/1.0/sprint/${sprintId}/issue?maxResults=100`, + { + headers: { + "Accept": "application/json", + ...this.getAuthHeaders(), + }, + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch sprint issues: ${response.statusText}`); + } + + const data = await response.json() as { issues?: any[] }; + return data.issues?.map((issue: any) => this.normalizeIssue(issue)) || []; + } catch (error) { + logger.error(`Failed to fetch issues for sprint ${sprintId}`, error); + return []; + } + } + + /** + * Get my issues in the current sprint + */ + async getMySprintIssues(sprintId: number): Promise { + const allIssues = await this.getSprintIssues(sprintId); + const currentUser = await this.getCurrentUser(); + + if (!currentUser) { + return []; + } + + return allIssues.filter( + (issue) => issue.assignee?.accountId === currentUser.accountId + ); + } + + /** + * Get unassigned issues in the current sprint + */ + async getSprintUnassignedIssues(sprintId: number): Promise { + const allIssues = await this.getSprintIssues(sprintId); + return allIssues.filter((issue) => !issue.assignee); + } + // ==================== Helper Methods ==================== /** From 34deb79cd2de15bfc0bc57639bb66a05b4781615 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 19 Dec 2025 01:28:08 +0000 Subject: [PATCH 2/2] feat: Add E2E tests and mock servers for Jira and Linear Co-authored-by: girardi.ang --- .github/workflows/e2e-tests.yml | 199 ++ package-lock.json | 2489 ++++++++++++++++- package.json | 14 +- test/e2e/mocks/index.js | 58 + test/e2e/mocks/index.js.map | 1 + test/e2e/mocks/index.ts | 65 + test/e2e/mocks/jira/fixtures.js | 335 +++ test/e2e/mocks/jira/fixtures.js.map | 1 + test/e2e/mocks/jira/fixtures.ts | 440 +++ test/e2e/mocks/jira/server.js | 262 ++ test/e2e/mocks/jira/server.js.map | 1 + test/e2e/mocks/jira/server.ts | 338 +++ test/e2e/mocks/linear/fixtures.js | 233 ++ test/e2e/mocks/linear/fixtures.js.map | 1 + test/e2e/mocks/linear/fixtures.ts | 317 +++ test/e2e/mocks/linear/server.js | 322 +++ test/e2e/mocks/linear/server.js.map | 1 + test/e2e/mocks/linear/server.ts | 359 +++ test/e2e/mocks/mockManager.js | 164 ++ test/e2e/mocks/mockManager.js.map | 1 + test/e2e/mocks/mockManager.ts | 190 ++ test/e2e/page-objects/CommandPalettePage.js | 210 ++ .../page-objects/CommandPalettePage.js.map | 1 + test/e2e/page-objects/CommandPalettePage.ts | 247 ++ test/e2e/page-objects/NotificationPage.js | 242 ++ test/e2e/page-objects/NotificationPage.js.map | 1 + test/e2e/page-objects/NotificationPage.ts | 314 +++ test/e2e/page-objects/SidebarPage.js | 173 ++ test/e2e/page-objects/SidebarPage.js.map | 1 + test/e2e/page-objects/SidebarPage.ts | 199 ++ test/e2e/page-objects/TreeViewPage.js | 249 ++ test/e2e/page-objects/TreeViewPage.js.map | 1 + test/e2e/page-objects/TreeViewPage.ts | 326 +++ test/e2e/page-objects/WebviewPage.js | 417 +++ test/e2e/page-objects/WebviewPage.js.map | 1 + test/e2e/page-objects/WebviewPage.ts | 496 ++++ test/e2e/page-objects/index.js | 30 + test/e2e/page-objects/index.js.map | 1 + test/e2e/page-objects/index.ts | 28 + test/e2e/runTests.js | 83 + test/e2e/runTests.js.map | 1 + test/e2e/runTests.ts | 55 + test/e2e/suite/activation.test.js | 200 ++ test/e2e/suite/activation.test.js.map | 1 + test/e2e/suite/activation.test.ts | 253 ++ test/e2e/suite/commands.test.js | 394 +++ test/e2e/suite/commands.test.js.map | 1 + test/e2e/suite/commands.test.ts | 479 ++++ test/e2e/suite/index.js | 54 + test/e2e/suite/index.js.map | 1 + test/e2e/suite/index.ts | 42 + test/e2e/suite/jira/setup.test.js | 259 ++ test/e2e/suite/jira/setup.test.js.map | 1 + test/e2e/suite/jira/setup.test.ts | 334 +++ test/e2e/suite/jira/tickets.test.js | 342 +++ test/e2e/suite/jira/tickets.test.js.map | 1 + test/e2e/suite/jira/tickets.test.ts | 419 +++ test/e2e/suite/linear/branches.test.js | 199 ++ test/e2e/suite/linear/branches.test.js.map | 1 + test/e2e/suite/linear/branches.test.ts | 237 ++ test/e2e/suite/linear/setup.test.js | 204 ++ test/e2e/suite/linear/setup.test.js.map | 1 + test/e2e/suite/linear/setup.test.ts | 255 ++ test/e2e/suite/linear/tickets.test.js | 297 ++ test/e2e/suite/linear/tickets.test.js.map | 1 + test/e2e/suite/linear/tickets.test.ts | 370 +++ test/e2e/suite/treeview.test.js | 227 ++ test/e2e/suite/treeview.test.js.map | 1 + test/e2e/suite/treeview.test.ts | 275 ++ test/e2e/utils/helpers.js | 274 ++ test/e2e/utils/helpers.js.map | 1 + test/e2e/utils/helpers.ts | 288 ++ test/e2e/utils/testConfig.js | 78 + test/e2e/utils/testConfig.js.map | 1 + test/e2e/utils/testConfig.ts | 84 + test/fixtures/settings.json | 13 + test/fixtures/workspaces/test-monorepo | 1 + test/tsconfig.json | 17 + tsconfig.json | 2 +- 79 files changed, 14368 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/e2e-tests.yml create mode 100644 test/e2e/mocks/index.js create mode 100644 test/e2e/mocks/index.js.map create mode 100644 test/e2e/mocks/index.ts create mode 100644 test/e2e/mocks/jira/fixtures.js create mode 100644 test/e2e/mocks/jira/fixtures.js.map create mode 100644 test/e2e/mocks/jira/fixtures.ts create mode 100644 test/e2e/mocks/jira/server.js create mode 100644 test/e2e/mocks/jira/server.js.map create mode 100644 test/e2e/mocks/jira/server.ts create mode 100644 test/e2e/mocks/linear/fixtures.js create mode 100644 test/e2e/mocks/linear/fixtures.js.map create mode 100644 test/e2e/mocks/linear/fixtures.ts create mode 100644 test/e2e/mocks/linear/server.js create mode 100644 test/e2e/mocks/linear/server.js.map create mode 100644 test/e2e/mocks/linear/server.ts create mode 100644 test/e2e/mocks/mockManager.js create mode 100644 test/e2e/mocks/mockManager.js.map create mode 100644 test/e2e/mocks/mockManager.ts create mode 100644 test/e2e/page-objects/CommandPalettePage.js create mode 100644 test/e2e/page-objects/CommandPalettePage.js.map create mode 100644 test/e2e/page-objects/CommandPalettePage.ts create mode 100644 test/e2e/page-objects/NotificationPage.js create mode 100644 test/e2e/page-objects/NotificationPage.js.map create mode 100644 test/e2e/page-objects/NotificationPage.ts create mode 100644 test/e2e/page-objects/SidebarPage.js create mode 100644 test/e2e/page-objects/SidebarPage.js.map create mode 100644 test/e2e/page-objects/SidebarPage.ts create mode 100644 test/e2e/page-objects/TreeViewPage.js create mode 100644 test/e2e/page-objects/TreeViewPage.js.map create mode 100644 test/e2e/page-objects/TreeViewPage.ts create mode 100644 test/e2e/page-objects/WebviewPage.js create mode 100644 test/e2e/page-objects/WebviewPage.js.map create mode 100644 test/e2e/page-objects/WebviewPage.ts create mode 100644 test/e2e/page-objects/index.js create mode 100644 test/e2e/page-objects/index.js.map create mode 100644 test/e2e/page-objects/index.ts create mode 100644 test/e2e/runTests.js create mode 100644 test/e2e/runTests.js.map create mode 100644 test/e2e/runTests.ts create mode 100644 test/e2e/suite/activation.test.js create mode 100644 test/e2e/suite/activation.test.js.map create mode 100644 test/e2e/suite/activation.test.ts create mode 100644 test/e2e/suite/commands.test.js create mode 100644 test/e2e/suite/commands.test.js.map create mode 100644 test/e2e/suite/commands.test.ts create mode 100644 test/e2e/suite/index.js create mode 100644 test/e2e/suite/index.js.map create mode 100644 test/e2e/suite/index.ts create mode 100644 test/e2e/suite/jira/setup.test.js create mode 100644 test/e2e/suite/jira/setup.test.js.map create mode 100644 test/e2e/suite/jira/setup.test.ts create mode 100644 test/e2e/suite/jira/tickets.test.js create mode 100644 test/e2e/suite/jira/tickets.test.js.map create mode 100644 test/e2e/suite/jira/tickets.test.ts create mode 100644 test/e2e/suite/linear/branches.test.js create mode 100644 test/e2e/suite/linear/branches.test.js.map create mode 100644 test/e2e/suite/linear/branches.test.ts create mode 100644 test/e2e/suite/linear/setup.test.js create mode 100644 test/e2e/suite/linear/setup.test.js.map create mode 100644 test/e2e/suite/linear/setup.test.ts create mode 100644 test/e2e/suite/linear/tickets.test.js create mode 100644 test/e2e/suite/linear/tickets.test.js.map create mode 100644 test/e2e/suite/linear/tickets.test.ts create mode 100644 test/e2e/suite/treeview.test.js create mode 100644 test/e2e/suite/treeview.test.js.map create mode 100644 test/e2e/suite/treeview.test.ts create mode 100644 test/e2e/utils/helpers.js create mode 100644 test/e2e/utils/helpers.js.map create mode 100644 test/e2e/utils/helpers.ts create mode 100644 test/e2e/utils/testConfig.js create mode 100644 test/e2e/utils/testConfig.js.map create mode 100644 test/e2e/utils/testConfig.ts create mode 100644 test/fixtures/settings.json create mode 160000 test/fixtures/workspaces/test-monorepo create mode 100644 test/tsconfig.json diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..bfb79b5 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,199 @@ +name: E2E Tests + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + workflow_dispatch: + inputs: + test_suite: + description: 'Test suite to run (all, linear, jira)' + required: false + default: 'all' + type: choice + options: + - all + - linear + - jira + +# Cancel in-progress runs for the same branch +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e-tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + vscode-version: ['stable', 'insiders'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Compile extension + run: npm run compile + + - name: Compile webviews + run: npm run compile:webview + + - name: Package extension + run: npm run package + + # Setup display for headless testing + - name: Setup display (xvfb) + run: | + sudo apt-get update + sudo apt-get install -y xvfb libgbm1 libnss3 libasound2 + + # Download VS Code and ChromeDriver + - name: Setup VS Code and ChromeDriver + run: | + npx extest get-vscode -s ${{ matrix.vscode-version }} + npx extest get-chromedriver + + # Install extension into test VS Code instance + - name: Install extension + run: npx extest install-vsix --vsix_file *.vsix + + # Run E2E tests with xvfb + - name: Run E2E tests + id: e2e + run: | + TEST_SUITE="${{ github.event.inputs.test_suite || 'all' }}" + if [ "$TEST_SUITE" = "linear" ]; then + xvfb-run -a npm run test:e2e:linear + elif [ "$TEST_SUITE" = "jira" ]; then + xvfb-run -a npm run test:e2e:jira + else + xvfb-run -a npm run test:e2e + fi + env: + DISPLAY: ':99' + DEVBUDDY_TEST_DEBUG: 'true' + continue-on-error: true + + # Upload test results + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-test-results-${{ matrix.vscode-version }} + path: | + test-results/ + screenshots/ + *.log + retention-days: 14 + if-no-files-found: ignore + + # Upload screenshots on failure + - name: Upload failure screenshots + if: failure() || steps.e2e.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: e2e-failure-screenshots-${{ matrix.vscode-version }} + path: | + test-results/*.png + screenshots/*.png + retention-days: 7 + if-no-files-found: ignore + + # Check test outcome + - name: Check test outcome + if: steps.e2e.outcome == 'failure' + run: exit 1 + + # Platform-specific tests (optional, can be enabled) + e2e-tests-platform: + runs-on: ubuntu-latest + needs: e2e-tests + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + strategy: + fail-fast: false + matrix: + platform: ['linear', 'jira'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Compile + run: | + npm run compile + npm run compile:webview + + - name: Package extension + run: npm run package + + - name: Setup display + run: | + sudo apt-get update + sudo apt-get install -y xvfb libgbm1 libnss3 libasound2 + + - name: Setup VS Code + run: | + npx extest get-vscode + npx extest get-chromedriver + npx extest install-vsix --vsix_file *.vsix + + - name: Run ${{ matrix.platform }} E2E tests + run: | + if [ "${{ matrix.platform }}" = "linear" ]; then + xvfb-run -a npm run test:e2e:linear + else + xvfb-run -a npm run test:e2e:jira + fi + env: + DISPLAY: ':99' + continue-on-error: true + + - name: Upload results + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-${{ matrix.platform }}-results + path: test-results/ + retention-days: 7 + if-no-files-found: ignore + + # Summary job + e2e-summary: + runs-on: ubuntu-latest + needs: [e2e-tests] + if: always() + + steps: + - name: Check test results + run: | + if [ "${{ needs.e2e-tests.result }}" = "failure" ]; then + echo "E2E tests failed!" + exit 1 + elif [ "${{ needs.e2e-tests.result }}" = "cancelled" ]; then + echo "E2E tests were cancelled" + exit 1 + else + echo "E2E tests passed!" + fi diff --git a/package-lock.json b/package-lock.json index 5b29ac4..210effb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,8 @@ "zod": "^4.1.12" }, "devDependencies": { + "@types/chai": "^5.2.3", + "@types/mocha": "^10.0.10", "@types/node": "^20.19.25", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", @@ -42,15 +44,19 @@ "@typescript-eslint/eslint-plugin": "^8.21.0", "@typescript-eslint/parser": "^8.21.0", "@vscode/vsce": "^3.6.2", + "chai": "^5.3.3", "esbuild": "^0.25.12", "eslint": "^8.56.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", + "mocha": "^10.8.2", + "msw": "^2.12.4", "prettier": "^3.4.2", "sharp": "^0.34.5", "standard-version": "^9.5.0", "tsc-alias": "^1.8.16", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vscode-extension-tester": "^8.19.0" }, "engines": { "vscode": "^1.93.0" @@ -489,6 +495,23 @@ "node": ">=6.9.0" } }, + "node_modules/@bazel/runfiles": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz", + "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.12", "cpu": [ @@ -763,6 +786,119 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@isaacs/balanced-match": { "version": "4.0.1", "dev": true, @@ -844,6 +980,16 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -894,6 +1040,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "license": "MIT", @@ -1014,6 +1167,24 @@ "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", + "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nevware21/ts-async": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", @@ -1061,6 +1232,42 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1071,12 +1278,48 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@redhat-developer/locators": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.17.0.tgz", + "integrity": "sha512-CPDrTJfrA5lxXPd64RHuntq+foRorMIOQAN0tlaQQD5wX0X2xlCOf324TVWS1hmTiYHtA8VfFTJNOjMQYvu0Mw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@redhat-developer/page-objects": ">=1.0.0", + "selenium-webdriver": ">=4.6.1" + } + }, + "node_modules/@redhat-developer/page-objects": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.17.0.tgz", + "integrity": "sha512-KytdvW8iHyECmt7rLf/MWdrtHmUi/SIkgWowjscIx0+U6sgXW7hHZ/5/gWP5HGX0Q5K1AA67veC75x2eZosN2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clipboardy": "^5.0.0", + "clone-deep": "^4.0.1", + "compare-versions": "^6.1.1", + "fs-extra": "^11.3.2", + "type-fest": "^4.41.0" + }, + "peerDependencies": { + "selenium-webdriver": ">=4.6.1", + "typescript": ">=4.6.2" + } + }, "node_modules/@remirror/core-constants": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", "license": "MIT" }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@secretlint/config-creator": { "version": "10.2.2", "dev": true, @@ -1258,6 +1501,19 @@ "node": ">=20.0.0" } }, + "node_modules/@sindresorhus/is": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz", + "integrity": "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "dev": true, @@ -1889,6 +2145,24 @@ "@tiptap/pm": "^3.13.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -1898,6 +2172,20 @@ "@types/unist": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1936,6 +2224,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", @@ -1975,6 +2270,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/selenium-webdriver": { + "version": "4.35.4", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.35.4.tgz", + "integrity": "sha512-hZFsK0dt/2PA5eLrFOJwkoTBpPXtaKnln7NCtg3pMAPwg7DXG6kTilHoAw8KzsQeDFLJ0mYcL6dPSMt1Qk7eSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ws": "*" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1992,6 +2305,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz", @@ -2424,6 +2747,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "7.2.0", "dev": true, @@ -2643,6 +2976,16 @@ "node": ">=0.10.0" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "dev": true, @@ -2778,6 +3121,13 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, "node_modules/boolbase": { "version": "1.0.0", "dev": true, @@ -2810,6 +3160,13 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, "node_modules/browserslist": { "version": "4.28.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", @@ -2868,6 +3225,24 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer-crc32": { "version": "0.2.13", "dev": true, @@ -2881,6 +3256,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2902,47 +3284,208 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "node_modules/byte-counter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/byte-counter/-/byte-counter-0.1.0.tgz", + "integrity": "sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "license": "MIT", + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" }, "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, - "node_modules/call-bound": { - "version": "1.0.4", + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 0.4" - }, + "node": ">=12" + } + }, + "node_modules/c8/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "13.0.17", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-13.0.17.tgz", + "integrity": "sha512-tQm7K9zC0cJPpbJS8xZ+NUqJ1bZ78jEXc7/G8uqvQTSdEdbmrxdnvxGb7/piCPeICuRY/L82VVt8UA+qpJ8wyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.4", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.2.0", + "keyv": "^5.5.4", + "mimic-response": "^4.0.0", + "normalize-url": "^8.1.0", + "responselike": "^4.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz", + "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/cacheable-request/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3016,6 +3559,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -3051,6 +3611,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/cheerio": { "version": "1.1.2", "dev": true, @@ -3116,6 +3686,42 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clipboardy": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-5.0.2.tgz", + "integrity": "sha512-3IG8i8Yfb410yqDlCx9Ve3lYLFN3bD1IkrWcowT1kyTo6y4bwYf2guK9Q8a6zck5vWm7afm6Y61i7BG/Ir3FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^9.6.0", + "is-wayland": "^0.1.0", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -3146,6 +3752,21 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cockatiel": { "version": "3.2.1", "dev": true, @@ -3209,6 +3830,13 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -3600,6 +4228,20 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3797,6 +4439,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "dev": true, @@ -3943,6 +4595,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4148,6 +4810,49 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "dev": true, @@ -4204,7 +4909,6 @@ "version": "1.4.5", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "once": "^1.4.0" } @@ -4808,42 +5512,124 @@ "node": ">=0.10.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, - "license": "(MIT OR WTFPL)", - "optional": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, "engines": { - "node": ">=6" + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", + "node_modules/execa/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, - "license": "MIT" - }, - "node_modules/fast-equals": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", - "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-glob": { - "version": "3.3.3", + "node_modules/execa/node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "is-unicode-supported": "^2.0.0" }, "engines": { - "node": ">=8.6.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { @@ -4948,6 +5734,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -5035,11 +5831,20 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/fs-extra": { "version": "11.3.2", @@ -5240,6 +6045,23 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -5456,6 +6278,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "14.6.5", + "resolved": "https://registry.npmjs.org/got/-/got-14.6.5.tgz", + "integrity": "sha512-Su87c0NNeg97de1sO02gy9I8EmE7DCJ1gzcFLcgGpYeq2PnLg4xz73MWrp6HjqbSsjb6Glf4UBDW6JNyZA6uSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^7.0.1", + "byte-counter": "^0.1.0", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^13.0.12", + "decompress-response": "^10.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "keyv": "^5.5.3", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^4.0.2", + "type-fest": "^4.26.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/decompress-response": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-10.0.0.tgz", + "integrity": "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got/node_modules/keyv": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz", + "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/got/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "dev": true, @@ -5468,6 +6356,16 @@ "dev": true, "license": "MIT" }, + "node_modules/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -5619,6 +6517,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -5667,6 +6582,23 @@ "node": ">=10" } }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -5706,6 +6638,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "dev": true, @@ -5718,6 +6657,33 @@ "node": ">= 14" } }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "dev": true, @@ -5730,6 +6696,16 @@ "node": ">= 14" } }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "dev": true, @@ -5769,6 +6745,13 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -6129,6 +7112,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -6184,6 +7174,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -6232,6 +7235,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -6296,6 +7312,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wayland": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-wayland/-/is-wayland-0.1.0.tgz", + "integrity": "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -6356,6 +7398,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -6368,28 +7426,77 @@ "dev": true, "license": "ISC" }, - "node_modules/istextorbinary": { - "version": "9.5.0", + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "Artistic-2.0", - "dependencies": { - "binaryextensions": "^6.11.0", - "editions": "^6.21.0", - "textextensions": "^6.11.0" - }, + "license": "MIT", "engines": { - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" + "node": ">=0.10.0" } }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -6584,6 +7691,52 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/jwa": { "version": "1.4.2", "dev": true, @@ -6656,6 +7809,16 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6791,6 +7954,23 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "license": "MIT", @@ -6801,6 +7981,26 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lowlight": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", @@ -6821,6 +8021,22 @@ "dev": true, "license": "ISC" }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -7317,12 +8533,147 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "dev": true, "license": "MIT", "optional": true }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -7337,6 +8688,129 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/msw": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.4.tgz", + "integrity": "sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.40.0", + "@open-draft/deferred-promise": "^2.2.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.0.2", + "graphql": "^16.12.0", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.7.0", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^5.2.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/msw/node_modules/type-fest": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz", + "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/msw/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/msw/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/msw/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "dev": true, @@ -7394,6 +8868,13 @@ "license": "MIT", "optional": true }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -7447,6 +8928,49 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nth-check": { "version": "2.1.1", "dev": true, @@ -7613,6 +9137,13 @@ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", "license": "MIT" }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -7631,6 +9162,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7689,6 +9230,13 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7718,6 +9266,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-semver": { "version": "1.1.1", "dev": true, @@ -7837,15 +9398,32 @@ "node": "20 || >=22" } }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "6.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" } }, "node_modules/pend": { @@ -7961,6 +9539,22 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8295,6 +9889,16 @@ "node": ">=8" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "dev": true, @@ -8667,6 +10271,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -8687,6 +10298,29 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/responselike": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", + "integrity": "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rettime": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", + "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "dev": true, + "license": "MIT" + }, "node_modules/reusify": { "version": "1.1.0", "dev": true, @@ -8880,6 +10514,16 @@ "dev": true, "license": "MIT" }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "node_modules/sax": { "version": "1.4.3", "dev": true, @@ -8912,6 +10556,32 @@ "node": ">=20.0.0" } }, + "node_modules/selenium-webdriver": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.39.0.tgz", + "integrity": "sha512-NAs9jCU+UeZ/ZmRb8R6zOp7N8eMklefdBYASnaRmCNXdgFE8w3OCxxZmLixkwqnGDHY5VF7hCulfw1Mls43N/A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.5.0", + "jszip": "^3.10.1", + "tmp": "^0.2.5", + "ws": "^8.18.3" + }, + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/semver": { "version": "7.7.3", "dev": true, @@ -8923,6 +10593,16 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -8972,6 +10652,26 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -9373,6 +11073,16 @@ "node": ">=4" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -9387,6 +11097,13 @@ "node": ">= 0.4" } }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "dev": true, @@ -9575,6 +11292,19 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -9644,6 +11374,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/table": { "version": "6.9.0", "dev": true, @@ -9655,57 +11398,256 @@ "string-width": "^4.2.3", "strip-ansi": "^6.0.1" }, - "engines": { - "node": ">=10.0.0" + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/targz": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz", + "integrity": "sha512-6q4tP9U55mZnRuMTBqnqc3nwYQY3kv+QthCFZuMk+Tn1qYUnMPmL/JZ/mzgXINzFpSqfU+242IFmFU9VPvqaQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tar-fs": "^1.8.1" + } + }, + "node_modules/targz/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/targz/node_modules/pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/targz/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/targz/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/targz/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/targz/node_modules/tar-fs": { + "version": "1.16.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.6.tgz", + "integrity": "sha512-JkOgFt3FxM/2v2CNpAVHqMW2QASjc/Hxo7IGfNd3MHaDYSW/sBFiS7YVmmhmr8x6vwN1VFQDQGdT2MWpmIuVKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + } + }, + "node_modules/targz/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/tar-fs": { - "version": "2.1.4", + "node_modules/test-exclude/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "MIT", - "optional": true, + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", - "optional": true - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/terminal-link": { - "version": "4.0.0", + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "ansi-escapes": "^7.0.0", - "supports-hyperlinks": "^3.2.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-extensions": { @@ -9803,6 +11745,26 @@ "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", "license": "MIT" }, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.5", "dev": true, @@ -9811,6 +11773,28 @@ "node": ">=14.14" } }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -9822,6 +11806,19 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -9842,6 +11839,16 @@ "node": ">=8" } }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -10225,6 +12232,30 @@ "node": ">= 10.0.0" } }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -10280,6 +12311,13 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/util-deprecate": { "version": "1.0.2", "dev": true, @@ -10293,6 +12331,21 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "dev": true, @@ -10341,6 +12394,127 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vscode-extension-tester": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.19.0.tgz", + "integrity": "sha512-CsMcFEnqim84rYnqdwdTT+cNB7cU8TJVZtt7GJakeQw9/UZy1ico8yTgNS1zqJ1ic5f/3RM4kwMuFut1jUlDYQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@redhat-developer/locators": "^1.17.0", + "@redhat-developer/page-objects": "^1.17.0", + "@types/selenium-webdriver": "^4.1.28", + "@vscode/vsce": "^3.6.2", + "c8": "^10.1.3", + "commander": "^14.0.1", + "compare-versions": "^6.1.1", + "find-up": "8.0.0", + "fs-extra": "^11.3.2", + "glob": "^11.0.3", + "got": "^14.5.0", + "hpagent": "^1.2.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.35.0", + "targz": "^1.0.1", + "unzipper": "^0.12.3" + }, + "bin": { + "extest": "out/cli.js" + }, + "peerDependencies": { + "mocha": ">=5.2.0", + "typescript": ">=4.6.2" + } + }, + "node_modules/vscode-extension-tester/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/vscode-extension-tester/node_modules/find-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", + "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^8.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/locate-path": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", + "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -10493,6 +12667,13 @@ "dev": true, "license": "MIT" }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "dev": true, @@ -10588,6 +12769,28 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/wsl-utils": { "version": "0.1.0", "dev": true, @@ -10676,6 +12879,58 @@ "node": ">=10" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/yauzl": { "version": "2.10.0", "dev": true, @@ -10706,6 +12961,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", diff --git a/package.json b/package.json index 5ab565e..56e9fac 100644 --- a/package.json +++ b/package.json @@ -1064,6 +1064,7 @@ "scripts": { "vscode:prepublish": "npm run compile && npm run compile:webview", "compile": "tsc -p ./ && tsc-alias -p ./tsconfig.json", + "compile:tests": "tsc -p ./test/tsconfig.json", "compile:webview": "node webview-ui/build.js", "watch": "tsc -watch -p ./", "watch:webview": "node webview-ui/build.js --watch", @@ -1085,9 +1086,14 @@ "release:dry-run": "standard-version --dry-run", "beta": "node scripts/beta-release.js", "beta:publish": "vsce publish --pre-release", - "beta:package": "vsce package --pre-release" + "beta:package": "vsce package --pre-release", + "test:e2e": "npm run compile && npm run compile:tests && npm run compile:webview && extest setup-and-run './out/test/e2e/suite/*.test.js' --code_version max --code_settings './test/fixtures/settings.json'", + "test:e2e:linear": "npm run compile && npm run compile:tests && extest setup-and-run './out/test/e2e/suite/linear/*.test.js' --code_version max --code_settings './test/fixtures/settings.json'", + "test:e2e:jira": "npm run compile && npm run compile:tests && extest setup-and-run './out/test/e2e/suite/jira/*.test.js' --code_version max --code_settings './test/fixtures/settings.json'" }, "devDependencies": { + "@types/chai": "^5.2.3", + "@types/mocha": "^10.0.10", "@types/node": "^20.19.25", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", @@ -1095,15 +1101,19 @@ "@typescript-eslint/eslint-plugin": "^8.21.0", "@typescript-eslint/parser": "^8.21.0", "@vscode/vsce": "^3.6.2", + "chai": "^5.3.3", "esbuild": "^0.25.12", "eslint": "^8.56.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", + "mocha": "^10.8.2", + "msw": "^2.12.4", "prettier": "^3.4.2", "sharp": "^0.34.5", "standard-version": "^9.5.0", "tsc-alias": "^1.8.16", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vscode-extension-tester": "^8.19.0" }, "dependencies": { "@tiptap/extension-bubble-menu": "^3.13.0", diff --git a/test/e2e/mocks/index.js b/test/e2e/mocks/index.js new file mode 100644 index 0000000..f072b83 --- /dev/null +++ b/test/e2e/mocks/index.js @@ -0,0 +1,58 @@ +"use strict"; +/** + * Mocks Index + * + * Export all mock server functionality for easy importing in tests. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getJiraIssuesByStatus = exports.searchJiraIssues = exports.getJiraIssueById = exports.getJiraIssueByKey = exports.mockJiraCurrentUser = exports.mockJiraIssues = exports.mockJiraTransitions = exports.mockJiraIssueTypes = exports.mockJiraPriorities = exports.mockJiraStatuses = exports.mockJiraProjects = exports.mockJiraUsers = exports.addJiraMockHandler = exports.resetJiraMockServer = exports.stopJiraMockServer = exports.startJiraMockServer = exports.jiraMockServer = exports.searchLinearIssues = exports.getLinearIssueById = exports.getLinearIssueByIdentifier = exports.getLinearIssuesByState = exports.linearMockOrganization = exports.linearMockViewer = exports.linearMockIssues = exports.linearMockWorkflowStates = exports.linearMockLabels = exports.linearMockProjects = exports.linearMockTeams = exports.linearMockUsers = exports.addLinearMockHandler = exports.resetLinearMockServer = exports.stopLinearMockServer = exports.startLinearMockServer = exports.linearMockServer = exports.withMocks = exports.setupMockServerHooks = exports.resetMockServers = exports.stopMockServers = exports.startMockServers = exports.mockManager = exports.MockManager = void 0; +// Mock Manager +var mockManager_1 = require("./mockManager"); +Object.defineProperty(exports, "MockManager", { enumerable: true, get: function () { return mockManager_1.MockManager; } }); +Object.defineProperty(exports, "mockManager", { enumerable: true, get: function () { return mockManager_1.mockManager; } }); +Object.defineProperty(exports, "startMockServers", { enumerable: true, get: function () { return mockManager_1.startMockServers; } }); +Object.defineProperty(exports, "stopMockServers", { enumerable: true, get: function () { return mockManager_1.stopMockServers; } }); +Object.defineProperty(exports, "resetMockServers", { enumerable: true, get: function () { return mockManager_1.resetMockServers; } }); +Object.defineProperty(exports, "setupMockServerHooks", { enumerable: true, get: function () { return mockManager_1.setupMockServerHooks; } }); +Object.defineProperty(exports, "withMocks", { enumerable: true, get: function () { return mockManager_1.withMocks; } }); +// Linear Mocks +var server_1 = require("./linear/server"); +Object.defineProperty(exports, "linearMockServer", { enumerable: true, get: function () { return server_1.linearMockServer; } }); +Object.defineProperty(exports, "startLinearMockServer", { enumerable: true, get: function () { return server_1.startLinearMockServer; } }); +Object.defineProperty(exports, "stopLinearMockServer", { enumerable: true, get: function () { return server_1.stopLinearMockServer; } }); +Object.defineProperty(exports, "resetLinearMockServer", { enumerable: true, get: function () { return server_1.resetLinearMockServer; } }); +Object.defineProperty(exports, "addLinearMockHandler", { enumerable: true, get: function () { return server_1.addLinearMockHandler; } }); +var fixtures_1 = require("./linear/fixtures"); +Object.defineProperty(exports, "linearMockUsers", { enumerable: true, get: function () { return fixtures_1.mockUsers; } }); +Object.defineProperty(exports, "linearMockTeams", { enumerable: true, get: function () { return fixtures_1.mockTeams; } }); +Object.defineProperty(exports, "linearMockProjects", { enumerable: true, get: function () { return fixtures_1.mockProjects; } }); +Object.defineProperty(exports, "linearMockLabels", { enumerable: true, get: function () { return fixtures_1.mockLabels; } }); +Object.defineProperty(exports, "linearMockWorkflowStates", { enumerable: true, get: function () { return fixtures_1.mockWorkflowStates; } }); +Object.defineProperty(exports, "linearMockIssues", { enumerable: true, get: function () { return fixtures_1.mockIssues; } }); +Object.defineProperty(exports, "linearMockViewer", { enumerable: true, get: function () { return fixtures_1.mockViewer; } }); +Object.defineProperty(exports, "linearMockOrganization", { enumerable: true, get: function () { return fixtures_1.mockOrganization; } }); +Object.defineProperty(exports, "getLinearIssuesByState", { enumerable: true, get: function () { return fixtures_1.getIssuesByState; } }); +Object.defineProperty(exports, "getLinearIssueByIdentifier", { enumerable: true, get: function () { return fixtures_1.getIssueByIdentifier; } }); +Object.defineProperty(exports, "getLinearIssueById", { enumerable: true, get: function () { return fixtures_1.getIssueById; } }); +Object.defineProperty(exports, "searchLinearIssues", { enumerable: true, get: function () { return fixtures_1.searchIssues; } }); +// Jira Mocks +var server_2 = require("./jira/server"); +Object.defineProperty(exports, "jiraMockServer", { enumerable: true, get: function () { return server_2.jiraMockServer; } }); +Object.defineProperty(exports, "startJiraMockServer", { enumerable: true, get: function () { return server_2.startJiraMockServer; } }); +Object.defineProperty(exports, "stopJiraMockServer", { enumerable: true, get: function () { return server_2.stopJiraMockServer; } }); +Object.defineProperty(exports, "resetJiraMockServer", { enumerable: true, get: function () { return server_2.resetJiraMockServer; } }); +Object.defineProperty(exports, "addJiraMockHandler", { enumerable: true, get: function () { return server_2.addJiraMockHandler; } }); +var fixtures_2 = require("./jira/fixtures"); +Object.defineProperty(exports, "mockJiraUsers", { enumerable: true, get: function () { return fixtures_2.mockJiraUsers; } }); +Object.defineProperty(exports, "mockJiraProjects", { enumerable: true, get: function () { return fixtures_2.mockJiraProjects; } }); +Object.defineProperty(exports, "mockJiraStatuses", { enumerable: true, get: function () { return fixtures_2.mockJiraStatuses; } }); +Object.defineProperty(exports, "mockJiraPriorities", { enumerable: true, get: function () { return fixtures_2.mockJiraPriorities; } }); +Object.defineProperty(exports, "mockJiraIssueTypes", { enumerable: true, get: function () { return fixtures_2.mockJiraIssueTypes; } }); +Object.defineProperty(exports, "mockJiraTransitions", { enumerable: true, get: function () { return fixtures_2.mockJiraTransitions; } }); +Object.defineProperty(exports, "mockJiraIssues", { enumerable: true, get: function () { return fixtures_2.mockJiraIssues; } }); +Object.defineProperty(exports, "mockJiraCurrentUser", { enumerable: true, get: function () { return fixtures_2.mockJiraCurrentUser; } }); +Object.defineProperty(exports, "getJiraIssueByKey", { enumerable: true, get: function () { return fixtures_2.getJiraIssueByKey; } }); +Object.defineProperty(exports, "getJiraIssueById", { enumerable: true, get: function () { return fixtures_2.getJiraIssueById; } }); +Object.defineProperty(exports, "searchJiraIssues", { enumerable: true, get: function () { return fixtures_2.searchJiraIssues; } }); +Object.defineProperty(exports, "getJiraIssuesByStatus", { enumerable: true, get: function () { return fixtures_2.getIssuesByStatus; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/test/e2e/mocks/index.js.map b/test/e2e/mocks/index.js.map new file mode 100644 index 0000000..176566b --- /dev/null +++ b/test/e2e/mocks/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,eAAe;AACf,6CASuB;AARrB,0GAAA,WAAW,OAAA;AACX,0GAAA,WAAW,OAAA;AACX,+GAAA,gBAAgB,OAAA;AAChB,8GAAA,eAAe,OAAA;AACf,+GAAA,gBAAgB,OAAA;AAChB,mHAAA,oBAAoB,OAAA;AACpB,wGAAA,SAAS,OAAA;AAIX,eAAe;AACf,0CAMyB;AALvB,0GAAA,gBAAgB,OAAA;AAChB,+GAAA,qBAAqB,OAAA;AACrB,8GAAA,oBAAoB,OAAA;AACpB,+GAAA,qBAAqB,OAAA;AACrB,8GAAA,oBAAoB,OAAA;AAGtB,8CAa2B;AAZzB,2GAAA,SAAS,OAAmB;AAC5B,2GAAA,SAAS,OAAmB;AAC5B,8GAAA,YAAY,OAAsB;AAClC,4GAAA,UAAU,OAAoB;AAC9B,oHAAA,kBAAkB,OAA4B;AAC9C,4GAAA,UAAU,OAAoB;AAC9B,4GAAA,UAAU,OAAoB;AAC9B,kHAAA,gBAAgB,OAA0B;AAC1C,kHAAA,gBAAgB,OAA0B;AAC1C,sHAAA,oBAAoB,OAA8B;AAClD,8GAAA,YAAY,OAAsB;AAClC,8GAAA,YAAY,OAAsB;AAGpC,aAAa;AACb,wCAMuB;AALrB,wGAAA,cAAc,OAAA;AACd,6GAAA,mBAAmB,OAAA;AACnB,4GAAA,kBAAkB,OAAA;AAClB,6GAAA,mBAAmB,OAAA;AACnB,4GAAA,kBAAkB,OAAA;AAGpB,4CAayB;AAZvB,yGAAA,aAAa,OAAA;AACb,4GAAA,gBAAgB,OAAA;AAChB,4GAAA,gBAAgB,OAAA;AAChB,8GAAA,kBAAkB,OAAA;AAClB,8GAAA,kBAAkB,OAAA;AAClB,+GAAA,mBAAmB,OAAA;AACnB,0GAAA,cAAc,OAAA;AACd,+GAAA,mBAAmB,OAAA;AACnB,6GAAA,iBAAiB,OAAA;AACjB,4GAAA,gBAAgB,OAAA;AAChB,4GAAA,gBAAgB,OAAA;AAChB,iHAAA,iBAAiB,OAAyB"} \ No newline at end of file diff --git a/test/e2e/mocks/index.ts b/test/e2e/mocks/index.ts new file mode 100644 index 0000000..d23737b --- /dev/null +++ b/test/e2e/mocks/index.ts @@ -0,0 +1,65 @@ +/** + * Mocks Index + * + * Export all mock server functionality for easy importing in tests. + */ + +// Mock Manager +export { + MockManager, + mockManager, + startMockServers, + stopMockServers, + resetMockServers, + setupMockServerHooks, + withMocks, + MockPlatform, +} from "./mockManager"; + +// Linear Mocks +export { + linearMockServer, + startLinearMockServer, + stopLinearMockServer, + resetLinearMockServer, + addLinearMockHandler, +} from "./linear/server"; + +export { + mockUsers as linearMockUsers, + mockTeams as linearMockTeams, + mockProjects as linearMockProjects, + mockLabels as linearMockLabels, + mockWorkflowStates as linearMockWorkflowStates, + mockIssues as linearMockIssues, + mockViewer as linearMockViewer, + mockOrganization as linearMockOrganization, + getIssuesByState as getLinearIssuesByState, + getIssueByIdentifier as getLinearIssueByIdentifier, + getIssueById as getLinearIssueById, + searchIssues as searchLinearIssues, +} from "./linear/fixtures"; + +// Jira Mocks +export { + jiraMockServer, + startJiraMockServer, + stopJiraMockServer, + resetJiraMockServer, + addJiraMockHandler, +} from "./jira/server"; + +export { + mockJiraUsers, + mockJiraProjects, + mockJiraStatuses, + mockJiraPriorities, + mockJiraIssueTypes, + mockJiraTransitions, + mockJiraIssues, + mockJiraCurrentUser, + getJiraIssueByKey, + getJiraIssueById, + searchJiraIssues, + getIssuesByStatus as getJiraIssuesByStatus, +} from "./jira/fixtures"; diff --git a/test/e2e/mocks/jira/fixtures.js b/test/e2e/mocks/jira/fixtures.js new file mode 100644 index 0000000..23581b0 --- /dev/null +++ b/test/e2e/mocks/jira/fixtures.js @@ -0,0 +1,335 @@ +"use strict"; +/** + * Jira Mock Fixtures + * + * Test data for Jira REST API mock server. + * Includes issues, projects, users, statuses, and priorities. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.mockJiraCurrentUser = exports.mockJiraIssues = exports.mockJiraTransitions = exports.mockJiraIssueTypes = exports.mockJiraPriorities = exports.mockJiraStatuses = exports.mockJiraProjects = exports.mockJiraUsers = void 0; +exports.getJiraIssueByKey = getJiraIssueByKey; +exports.getJiraIssueById = getJiraIssueById; +exports.searchJiraIssues = searchJiraIssues; +exports.getIssuesByStatus = getIssuesByStatus; +// Mock Users +exports.mockJiraUsers = [ + { + accountId: "user-jira-1", + displayName: "Test User", + emailAddress: "test@example.com", + avatarUrls: { + "48x48": "https://example.com/avatar1-48.png", + "24x24": "https://example.com/avatar1-24.png", + "16x16": "https://example.com/avatar1-16.png", + "32x32": "https://example.com/avatar1-32.png", + }, + active: true, + }, + { + accountId: "user-jira-2", + displayName: "Jane Developer", + emailAddress: "jane@example.com", + avatarUrls: { + "48x48": "https://example.com/avatar2-48.png", + "24x24": "https://example.com/avatar2-24.png", + "16x16": "https://example.com/avatar2-16.png", + "32x32": "https://example.com/avatar2-32.png", + }, + active: true, + }, +]; +// Mock Projects +exports.mockJiraProjects = [ + { + id: "10001", + key: "TEST", + name: "Test Project", + description: "A test project for E2E testing", + lead: exports.mockJiraUsers[0], + projectTypeKey: "software", + }, + { + id: "10002", + key: "DEV", + name: "Development Project", + description: "Development project", + lead: exports.mockJiraUsers[1], + projectTypeKey: "software", + }, +]; +// Mock Statuses +exports.mockJiraStatuses = [ + { + id: "1", + name: "Open", + description: "Issue is open and unassigned", + statusCategory: { + id: 2, + key: "new", + colorName: "blue-gray", + name: "To Do", + }, + }, + { + id: "3", + name: "In Progress", + description: "Work is in progress", + statusCategory: { + id: 4, + key: "indeterminate", + colorName: "yellow", + name: "In Progress", + }, + }, + { + id: "4", + name: "In Review", + description: "Awaiting code review", + statusCategory: { + id: 4, + key: "indeterminate", + colorName: "yellow", + name: "In Progress", + }, + }, + { + id: "5", + name: "Done", + description: "Work is complete", + statusCategory: { + id: 3, + key: "done", + colorName: "green", + name: "Done", + }, + }, +]; +// Mock Priorities +exports.mockJiraPriorities = [ + { + id: "1", + name: "Highest", + description: "This problem will block progress", + iconUrl: "https://example.com/icons/priority-highest.svg", + }, + { + id: "2", + name: "High", + description: "Serious problem that could block progress", + iconUrl: "https://example.com/icons/priority-high.svg", + }, + { + id: "3", + name: "Medium", + description: "Has the potential to affect progress", + iconUrl: "https://example.com/icons/priority-medium.svg", + }, + { + id: "4", + name: "Low", + description: "Minor problem or easily worked around", + iconUrl: "https://example.com/icons/priority-low.svg", + }, + { + id: "5", + name: "Lowest", + description: "Trivial problem with little or no impact", + iconUrl: "https://example.com/icons/priority-lowest.svg", + }, +]; +// Mock Issue Types +exports.mockJiraIssueTypes = [ + { + id: "10001", + name: "Story", + description: "A user story", + iconUrl: "https://example.com/icons/story.svg", + subtask: false, + }, + { + id: "10002", + name: "Bug", + description: "A bug or defect", + iconUrl: "https://example.com/icons/bug.svg", + subtask: false, + }, + { + id: "10003", + name: "Task", + description: "A task", + iconUrl: "https://example.com/icons/task.svg", + subtask: false, + }, + { + id: "10004", + name: "Sub-task", + description: "A sub-task", + iconUrl: "https://example.com/icons/subtask.svg", + subtask: true, + }, +]; +// Mock Transitions +exports.mockJiraTransitions = [ + { id: "11", name: "To Do", to: exports.mockJiraStatuses[0] }, + { id: "21", name: "In Progress", to: exports.mockJiraStatuses[1] }, + { id: "31", name: "In Review", to: exports.mockJiraStatuses[2] }, + { id: "41", name: "Done", to: exports.mockJiraStatuses[3] }, +]; +// Mock Issues +exports.mockJiraIssues = [ + { + id: "10001", + key: "TEST-101", + self: "https://test.atlassian.net/rest/api/3/issue/10001", + fields: { + summary: "Implement user authentication", + description: "Add OAuth2 authentication support with Google and GitHub providers", + status: exports.mockJiraStatuses[1], // In Progress + priority: exports.mockJiraPriorities[1], // High + issuetype: exports.mockJiraIssueTypes[0], // Story + assignee: exports.mockJiraUsers[0], + reporter: exports.mockJiraUsers[0], + project: exports.mockJiraProjects[0], + created: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["authentication", "oauth"], + comment: { + comments: [ + { + id: "comment-1", + body: "Started working on this", + author: exports.mockJiraUsers[0], + created: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + }, + ], + total: 1, + }, + }, + }, + { + id: "10002", + key: "TEST-102", + self: "https://test.atlassian.net/rest/api/3/issue/10002", + fields: { + summary: "Fix database connection timeout", + description: "Connection pool is not properly releasing connections", + status: exports.mockJiraStatuses[0], // Open + priority: exports.mockJiraPriorities[0], // Highest + issuetype: exports.mockJiraIssueTypes[1], // Bug + assignee: exports.mockJiraUsers[0], + reporter: exports.mockJiraUsers[1], + project: exports.mockJiraProjects[0], + created: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["bug", "database"], + comment: { comments: [], total: 0 }, + }, + }, + { + id: "10003", + key: "TEST-103", + self: "https://test.atlassian.net/rest/api/3/issue/10003", + fields: { + summary: "Add unit tests for payment module", + description: "Increase test coverage for payment processing", + status: exports.mockJiraStatuses[0], // Open + priority: exports.mockJiraPriorities[2], // Medium + issuetype: exports.mockJiraIssueTypes[2], // Task + assignee: exports.mockJiraUsers[0], + reporter: exports.mockJiraUsers[0], + project: exports.mockJiraProjects[0], + created: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["testing"], + comment: { comments: [], total: 0 }, + }, + }, + { + id: "10004", + key: "TEST-104", + self: "https://test.atlassian.net/rest/api/3/issue/10004", + fields: { + summary: "Update documentation for API v2", + description: "Document all new endpoints and deprecations", + status: exports.mockJiraStatuses[2], // In Review + priority: exports.mockJiraPriorities[3], // Low + issuetype: exports.mockJiraIssueTypes[2], // Task + assignee: exports.mockJiraUsers[0], + reporter: exports.mockJiraUsers[1], + project: exports.mockJiraProjects[0], + created: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["documentation"], + comment: { comments: [], total: 0 }, + }, + }, + { + id: "10005", + key: "TEST-105", + self: "https://test.atlassian.net/rest/api/3/issue/10005", + fields: { + summary: "Completed: Setup CI/CD pipeline", + description: "Configure GitHub Actions for automated testing and deployment", + status: exports.mockJiraStatuses[3], // Done + priority: exports.mockJiraPriorities[1], // High + issuetype: exports.mockJiraIssueTypes[0], // Story + assignee: exports.mockJiraUsers[0], + reporter: exports.mockJiraUsers[0], + project: exports.mockJiraProjects[0], + created: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["devops", "ci-cd"], + comment: { comments: [], total: 0 }, + }, + }, +]; +// Current user (for myself query) +exports.mockJiraCurrentUser = exports.mockJiraUsers[0]; +// Helper functions +function getJiraIssueByKey(key) { + return exports.mockJiraIssues.find((issue) => issue.key === key); +} +function getJiraIssueById(id) { + return exports.mockJiraIssues.find((issue) => issue.id === id); +} +function searchJiraIssues(jql) { + // Simple JQL parsing for tests + const lowerJql = jql.toLowerCase(); + // Filter by assignee + if (lowerJql.includes("assignee = currentuser()")) { + return exports.mockJiraIssues.filter((issue) => issue.fields.assignee?.accountId === exports.mockJiraCurrentUser.accountId); + } + // Filter by project + const projectMatch = jql.match(/project\s*=\s*["']?(\w+)["']?/i); + if (projectMatch) { + const projectKey = projectMatch[1].toUpperCase(); + return exports.mockJiraIssues.filter((issue) => issue.fields.project.key === projectKey); + } + // Filter by status + const statusMatch = jql.match(/status\s*=\s*["']?([^"']+)["']?/i); + if (statusMatch) { + const statusName = statusMatch[1]; + return exports.mockJiraIssues.filter((issue) => issue.fields.status.name.toLowerCase() === statusName.toLowerCase()); + } + // Default: return all issues + return exports.mockJiraIssues; +} +function getIssuesByStatus(statusName) { + return exports.mockJiraIssues.filter((issue) => issue.fields.status.name.toLowerCase() === statusName.toLowerCase()); +} +exports.default = { + mockJiraUsers: exports.mockJiraUsers, + mockJiraProjects: exports.mockJiraProjects, + mockJiraStatuses: exports.mockJiraStatuses, + mockJiraPriorities: exports.mockJiraPriorities, + mockJiraIssueTypes: exports.mockJiraIssueTypes, + mockJiraTransitions: exports.mockJiraTransitions, + mockJiraIssues: exports.mockJiraIssues, + mockJiraCurrentUser: exports.mockJiraCurrentUser, + getJiraIssueByKey, + getJiraIssueById, + searchJiraIssues, + getIssuesByStatus, +}; +//# sourceMappingURL=fixtures.js.map \ No newline at end of file diff --git a/test/e2e/mocks/jira/fixtures.js.map b/test/e2e/mocks/jira/fixtures.js.map new file mode 100644 index 0000000..860ec8d --- /dev/null +++ b/test/e2e/mocks/jira/fixtures.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fixtures.js","sourceRoot":"","sources":["fixtures.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAkXH,8CAEC;AAED,4CAEC;AAED,4CAgCC;AAED,8CAOC;AA7UD,aAAa;AACA,QAAA,aAAa,GAAmB;IAC3C;QACE,SAAS,EAAE,aAAa;QACxB,WAAW,EAAE,WAAW;QACxB,YAAY,EAAE,kBAAkB;QAChC,UAAU,EAAE;YACV,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,oCAAoC;SAC9C;QACD,MAAM,EAAE,IAAI;KACb;IACD;QACE,SAAS,EAAE,aAAa;QACxB,WAAW,EAAE,gBAAgB;QAC7B,YAAY,EAAE,kBAAkB;QAChC,UAAU,EAAE;YACV,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,oCAAoC;YAC7C,OAAO,EAAE,oCAAoC;SAC9C;QACD,MAAM,EAAE,IAAI;KACb;CACF,CAAC;AAEF,gBAAgB;AACH,QAAA,gBAAgB,GAAsB;IACjD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,cAAc;QACpB,WAAW,EAAE,gCAAgC;QAC7C,IAAI,EAAE,qBAAa,CAAC,CAAC,CAAC;QACtB,cAAc,EAAE,UAAU;KAC3B;IACD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,KAAK;QACV,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EAAE,qBAAqB;QAClC,IAAI,EAAE,qBAAa,CAAC,CAAC,CAAC;QACtB,cAAc,EAAE,UAAU;KAC3B;CACF,CAAC;AAEF,gBAAgB;AACH,QAAA,gBAAgB,GAAqB;IAChD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,8BAA8B;QAC3C,cAAc,EAAE;YACd,EAAE,EAAE,CAAC;YACL,GAAG,EAAE,KAAK;YACV,SAAS,EAAE,WAAW;YACtB,IAAI,EAAE,OAAO;SACd;KACF;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE,qBAAqB;QAClC,cAAc,EAAE;YACd,EAAE,EAAE,CAAC;YACL,GAAG,EAAE,eAAe;YACpB,SAAS,EAAE,QAAQ;YACnB,IAAI,EAAE,aAAa;SACpB;KACF;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,sBAAsB;QACnC,cAAc,EAAE;YACd,EAAE,EAAE,CAAC;YACL,GAAG,EAAE,eAAe;YACpB,SAAS,EAAE,QAAQ;YACnB,IAAI,EAAE,aAAa;SACpB;KACF;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,kBAAkB;QAC/B,cAAc,EAAE;YACd,EAAE,EAAE,CAAC;YACL,GAAG,EAAE,MAAM;YACX,SAAS,EAAE,OAAO;YAClB,IAAI,EAAE,MAAM;SACb;KACF;CACF,CAAC;AAEF,kBAAkB;AACL,QAAA,kBAAkB,GAAuB;IACpD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,kCAAkC;QAC/C,OAAO,EAAE,gDAAgD;KAC1D;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,2CAA2C;QACxD,OAAO,EAAE,6CAA6C;KACvD;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,sCAAsC;QACnD,OAAO,EAAE,+CAA+C;KACzD;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,KAAK;QACX,WAAW,EAAE,uCAAuC;QACpD,OAAO,EAAE,4CAA4C;KACtD;IACD;QACE,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,0CAA0C;QACvD,OAAO,EAAE,+CAA+C;KACzD;CACF,CAAC;AAEF,mBAAmB;AACN,QAAA,kBAAkB,GAAwB;IACrD;QACE,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,cAAc;QAC3B,OAAO,EAAE,qCAAqC;QAC9C,OAAO,EAAE,KAAK;KACf;IACD;QACE,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,KAAK;QACX,WAAW,EAAE,iBAAiB;QAC9B,OAAO,EAAE,mCAAmC;QAC5C,OAAO,EAAE,KAAK;KACf;IACD;QACE,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,QAAQ;QACrB,OAAO,EAAE,oCAAoC;QAC7C,OAAO,EAAE,KAAK;KACf;IACD;QACE,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,YAAY;QACzB,OAAO,EAAE,uCAAuC;QAChD,OAAO,EAAE,IAAI;KACd;CACF,CAAC;AAEF,mBAAmB;AACN,QAAA,mBAAmB,GAAyB;IACvD,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE;IACpD,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE;IAC1D,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE;IACxD,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE;CACpD,CAAC;AAEF,cAAc;AACD,QAAA,cAAc,GAAoB;IAC7C;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,mDAAmD;QACzD,MAAM,EAAE;YACN,OAAO,EAAE,+BAA+B;YACxC,WAAW,EAAE,oEAAoE;YACjF,MAAM,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE,cAAc;YAC3C,QAAQ,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,OAAO;YACxC,SAAS,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,QAAQ;YAC1C,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,OAAO,EAAE,wBAAgB,CAAC,CAAC,CAAC;YAC5B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,MAAM,EAAE,CAAC,gBAAgB,EAAE,OAAO,CAAC;YACnC,OAAO,EAAE;gBACP,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,WAAW;wBACf,IAAI,EAAE,yBAAyB;wBAC/B,MAAM,EAAE,qBAAa,CAAC,CAAC,CAAC;wBACxB,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;wBACrE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;qBACtE;iBACF;gBACD,KAAK,EAAE,CAAC;aACT;SACF;KACF;IACD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,mDAAmD;QACzD,MAAM,EAAE;YACN,OAAO,EAAE,iCAAiC;YAC1C,WAAW,EAAE,uDAAuD;YACpE,MAAM,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE,OAAO;YACpC,QAAQ,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,UAAU;YAC3C,SAAS,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,MAAM;YACxC,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,OAAO,EAAE,wBAAgB,CAAC,CAAC,CAAC;YAC5B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC;YAC3B,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;SACpC;KACF;IACD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,mDAAmD;QACzD,MAAM,EAAE;YACN,OAAO,EAAE,mCAAmC;YAC5C,WAAW,EAAE,+CAA+C;YAC5D,MAAM,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE,OAAO;YACpC,QAAQ,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,SAAS;YAC1C,SAAS,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,OAAO;YACzC,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,OAAO,EAAE,wBAAgB,CAAC,CAAC,CAAC;YAC5B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACtE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACtE,MAAM,EAAE,CAAC,SAAS,CAAC;YACnB,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;SACpC;KACF;IACD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,mDAAmD;QACzD,MAAM,EAAE;YACN,OAAO,EAAE,iCAAiC;YAC1C,WAAW,EAAE,6CAA6C;YAC1D,MAAM,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE,YAAY;YACzC,QAAQ,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,MAAM;YACvC,SAAS,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,OAAO;YACzC,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,OAAO,EAAE,wBAAgB,CAAC,CAAC,CAAC;YAC5B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,MAAM,EAAE,CAAC,eAAe,CAAC;YACzB,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;SACpC;KACF;IACD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,mDAAmD;QACzD,MAAM,EAAE;YACN,OAAO,EAAE,iCAAiC;YAC1C,WAAW,EAAE,+DAA+D;YAC5E,MAAM,EAAE,wBAAgB,CAAC,CAAC,CAAC,EAAE,OAAO;YACpC,QAAQ,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,OAAO;YACxC,SAAS,EAAE,0BAAkB,CAAC,CAAC,CAAC,EAAE,QAAQ;YAC1C,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,QAAQ,EAAE,qBAAa,CAAC,CAAC,CAAC;YAC1B,OAAO,EAAE,wBAAgB,CAAC,CAAC,CAAC;YAC5B,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACtE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;YACrE,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;YAC3B,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;SACpC;KACF;CACF,CAAC;AAEF,kCAAkC;AACrB,QAAA,mBAAmB,GAAG,qBAAa,CAAC,CAAC,CAAC,CAAC;AAEpD,mBAAmB;AACnB,SAAgB,iBAAiB,CAAC,GAAW;IAC3C,OAAO,sBAAc,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,SAAgB,gBAAgB,CAAC,EAAU;IACzC,OAAO,sBAAc,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,SAAgB,gBAAgB,CAAC,GAAW;IAC1C,+BAA+B;IAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAEnC,qBAAqB;IACrB,IAAI,QAAQ,CAAC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAClD,OAAO,sBAAc,CAAC,MAAM,CAC1B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,KAAK,2BAAmB,CAAC,SAAS,CAC9E,CAAC;IACJ,CAAC;IAED,oBAAoB;IACpB,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACjE,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,sBAAc,CAAC,MAAM,CAC1B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,UAAU,CACnD,CAAC;IACJ,CAAC;IAED,mBAAmB;IACnB,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAClE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAClC,OAAO,sBAAc,CAAC,MAAM,CAC1B,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,WAAW,EAAE,CACtE,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,OAAO,sBAAc,CAAC;AACxB,CAAC;AAED,SAAgB,iBAAiB,CAC/B,UAAkB;IAElB,OAAO,sBAAc,CAAC,MAAM,CAC1B,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,WAAW,EAAE,CACtE,CAAC;AACJ,CAAC;AAED,kBAAe;IACb,aAAa,EAAb,qBAAa;IACb,gBAAgB,EAAhB,wBAAgB;IAChB,gBAAgB,EAAhB,wBAAgB;IAChB,kBAAkB,EAAlB,0BAAkB;IAClB,kBAAkB,EAAlB,0BAAkB;IAClB,mBAAmB,EAAnB,2BAAmB;IACnB,cAAc,EAAd,sBAAc;IACd,mBAAmB,EAAnB,2BAAmB;IACnB,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;CAClB,CAAC"} \ No newline at end of file diff --git a/test/e2e/mocks/jira/fixtures.ts b/test/e2e/mocks/jira/fixtures.ts new file mode 100644 index 0000000..9fb1f2f --- /dev/null +++ b/test/e2e/mocks/jira/fixtures.ts @@ -0,0 +1,440 @@ +/** + * Jira Mock Fixtures + * + * Test data for Jira REST API mock server. + * Includes issues, projects, users, statuses, and priorities. + */ + +export interface MockJiraUser { + accountId: string; + displayName: string; + emailAddress: string; + avatarUrls: { + "48x48": string; + "24x24": string; + "16x16": string; + "32x32": string; + }; + active: boolean; +} + +export interface MockJiraProject { + id: string; + key: string; + name: string; + description?: string; + lead: MockJiraUser; + projectTypeKey: string; +} + +export interface MockJiraStatus { + id: string; + name: string; + description?: string; + statusCategory: { + id: number; + key: string; + colorName: string; + name: string; + }; +} + +export interface MockJiraPriority { + id: string; + name: string; + description?: string; + iconUrl: string; +} + +export interface MockJiraIssueType { + id: string; + name: string; + description: string; + iconUrl: string; + subtask: boolean; +} + +export interface MockJiraTransition { + id: string; + name: string; + to: MockJiraStatus; +} + +export interface MockJiraIssue { + id: string; + key: string; + self: string; + fields: { + summary: string; + description: string | null; + status: MockJiraStatus; + priority: MockJiraPriority; + issuetype: MockJiraIssueType; + assignee: MockJiraUser | null; + reporter: MockJiraUser; + project: MockJiraProject; + created: string; + updated: string; + labels: string[]; + comment?: { + comments: Array<{ + id: string; + body: string; + author: MockJiraUser; + created: string; + updated: string; + }>; + total: number; + }; + }; +} + +// Mock Users +export const mockJiraUsers: MockJiraUser[] = [ + { + accountId: "user-jira-1", + displayName: "Test User", + emailAddress: "test@example.com", + avatarUrls: { + "48x48": "https://example.com/avatar1-48.png", + "24x24": "https://example.com/avatar1-24.png", + "16x16": "https://example.com/avatar1-16.png", + "32x32": "https://example.com/avatar1-32.png", + }, + active: true, + }, + { + accountId: "user-jira-2", + displayName: "Jane Developer", + emailAddress: "jane@example.com", + avatarUrls: { + "48x48": "https://example.com/avatar2-48.png", + "24x24": "https://example.com/avatar2-24.png", + "16x16": "https://example.com/avatar2-16.png", + "32x32": "https://example.com/avatar2-32.png", + }, + active: true, + }, +]; + +// Mock Projects +export const mockJiraProjects: MockJiraProject[] = [ + { + id: "10001", + key: "TEST", + name: "Test Project", + description: "A test project for E2E testing", + lead: mockJiraUsers[0], + projectTypeKey: "software", + }, + { + id: "10002", + key: "DEV", + name: "Development Project", + description: "Development project", + lead: mockJiraUsers[1], + projectTypeKey: "software", + }, +]; + +// Mock Statuses +export const mockJiraStatuses: MockJiraStatus[] = [ + { + id: "1", + name: "Open", + description: "Issue is open and unassigned", + statusCategory: { + id: 2, + key: "new", + colorName: "blue-gray", + name: "To Do", + }, + }, + { + id: "3", + name: "In Progress", + description: "Work is in progress", + statusCategory: { + id: 4, + key: "indeterminate", + colorName: "yellow", + name: "In Progress", + }, + }, + { + id: "4", + name: "In Review", + description: "Awaiting code review", + statusCategory: { + id: 4, + key: "indeterminate", + colorName: "yellow", + name: "In Progress", + }, + }, + { + id: "5", + name: "Done", + description: "Work is complete", + statusCategory: { + id: 3, + key: "done", + colorName: "green", + name: "Done", + }, + }, +]; + +// Mock Priorities +export const mockJiraPriorities: MockJiraPriority[] = [ + { + id: "1", + name: "Highest", + description: "This problem will block progress", + iconUrl: "https://example.com/icons/priority-highest.svg", + }, + { + id: "2", + name: "High", + description: "Serious problem that could block progress", + iconUrl: "https://example.com/icons/priority-high.svg", + }, + { + id: "3", + name: "Medium", + description: "Has the potential to affect progress", + iconUrl: "https://example.com/icons/priority-medium.svg", + }, + { + id: "4", + name: "Low", + description: "Minor problem or easily worked around", + iconUrl: "https://example.com/icons/priority-low.svg", + }, + { + id: "5", + name: "Lowest", + description: "Trivial problem with little or no impact", + iconUrl: "https://example.com/icons/priority-lowest.svg", + }, +]; + +// Mock Issue Types +export const mockJiraIssueTypes: MockJiraIssueType[] = [ + { + id: "10001", + name: "Story", + description: "A user story", + iconUrl: "https://example.com/icons/story.svg", + subtask: false, + }, + { + id: "10002", + name: "Bug", + description: "A bug or defect", + iconUrl: "https://example.com/icons/bug.svg", + subtask: false, + }, + { + id: "10003", + name: "Task", + description: "A task", + iconUrl: "https://example.com/icons/task.svg", + subtask: false, + }, + { + id: "10004", + name: "Sub-task", + description: "A sub-task", + iconUrl: "https://example.com/icons/subtask.svg", + subtask: true, + }, +]; + +// Mock Transitions +export const mockJiraTransitions: MockJiraTransition[] = [ + { id: "11", name: "To Do", to: mockJiraStatuses[0] }, + { id: "21", name: "In Progress", to: mockJiraStatuses[1] }, + { id: "31", name: "In Review", to: mockJiraStatuses[2] }, + { id: "41", name: "Done", to: mockJiraStatuses[3] }, +]; + +// Mock Issues +export const mockJiraIssues: MockJiraIssue[] = [ + { + id: "10001", + key: "TEST-101", + self: "https://test.atlassian.net/rest/api/3/issue/10001", + fields: { + summary: "Implement user authentication", + description: "Add OAuth2 authentication support with Google and GitHub providers", + status: mockJiraStatuses[1], // In Progress + priority: mockJiraPriorities[1], // High + issuetype: mockJiraIssueTypes[0], // Story + assignee: mockJiraUsers[0], + reporter: mockJiraUsers[0], + project: mockJiraProjects[0], + created: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["authentication", "oauth"], + comment: { + comments: [ + { + id: "comment-1", + body: "Started working on this", + author: mockJiraUsers[0], + created: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + }, + ], + total: 1, + }, + }, + }, + { + id: "10002", + key: "TEST-102", + self: "https://test.atlassian.net/rest/api/3/issue/10002", + fields: { + summary: "Fix database connection timeout", + description: "Connection pool is not properly releasing connections", + status: mockJiraStatuses[0], // Open + priority: mockJiraPriorities[0], // Highest + issuetype: mockJiraIssueTypes[1], // Bug + assignee: mockJiraUsers[0], + reporter: mockJiraUsers[1], + project: mockJiraProjects[0], + created: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["bug", "database"], + comment: { comments: [], total: 0 }, + }, + }, + { + id: "10003", + key: "TEST-103", + self: "https://test.atlassian.net/rest/api/3/issue/10003", + fields: { + summary: "Add unit tests for payment module", + description: "Increase test coverage for payment processing", + status: mockJiraStatuses[0], // Open + priority: mockJiraPriorities[2], // Medium + issuetype: mockJiraIssueTypes[2], // Task + assignee: mockJiraUsers[0], + reporter: mockJiraUsers[0], + project: mockJiraProjects[0], + created: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["testing"], + comment: { comments: [], total: 0 }, + }, + }, + { + id: "10004", + key: "TEST-104", + self: "https://test.atlassian.net/rest/api/3/issue/10004", + fields: { + summary: "Update documentation for API v2", + description: "Document all new endpoints and deprecations", + status: mockJiraStatuses[2], // In Review + priority: mockJiraPriorities[3], // Low + issuetype: mockJiraIssueTypes[2], // Task + assignee: mockJiraUsers[0], + reporter: mockJiraUsers[1], + project: mockJiraProjects[0], + created: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["documentation"], + comment: { comments: [], total: 0 }, + }, + }, + { + id: "10005", + key: "TEST-105", + self: "https://test.atlassian.net/rest/api/3/issue/10005", + fields: { + summary: "Completed: Setup CI/CD pipeline", + description: "Configure GitHub Actions for automated testing and deployment", + status: mockJiraStatuses[3], // Done + priority: mockJiraPriorities[1], // High + issuetype: mockJiraIssueTypes[0], // Story + assignee: mockJiraUsers[0], + reporter: mockJiraUsers[0], + project: mockJiraProjects[0], + created: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), + updated: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + labels: ["devops", "ci-cd"], + comment: { comments: [], total: 0 }, + }, + }, +]; + +// Current user (for myself query) +export const mockJiraCurrentUser = mockJiraUsers[0]; + +// Helper functions +export function getJiraIssueByKey(key: string): MockJiraIssue | undefined { + return mockJiraIssues.find((issue) => issue.key === key); +} + +export function getJiraIssueById(id: string): MockJiraIssue | undefined { + return mockJiraIssues.find((issue) => issue.id === id); +} + +export function searchJiraIssues(jql: string): MockJiraIssue[] { + // Simple JQL parsing for tests + const lowerJql = jql.toLowerCase(); + + // Filter by assignee + if (lowerJql.includes("assignee = currentuser()")) { + return mockJiraIssues.filter( + (issue) => issue.fields.assignee?.accountId === mockJiraCurrentUser.accountId + ); + } + + // Filter by project + const projectMatch = jql.match(/project\s*=\s*["']?(\w+)["']?/i); + if (projectMatch) { + const projectKey = projectMatch[1].toUpperCase(); + return mockJiraIssues.filter( + (issue) => issue.fields.project.key === projectKey + ); + } + + // Filter by status + const statusMatch = jql.match(/status\s*=\s*["']?([^"']+)["']?/i); + if (statusMatch) { + const statusName = statusMatch[1]; + return mockJiraIssues.filter( + (issue) => + issue.fields.status.name.toLowerCase() === statusName.toLowerCase() + ); + } + + // Default: return all issues + return mockJiraIssues; +} + +export function getIssuesByStatus( + statusName: string +): MockJiraIssue[] { + return mockJiraIssues.filter( + (issue) => + issue.fields.status.name.toLowerCase() === statusName.toLowerCase() + ); +} + +export default { + mockJiraUsers, + mockJiraProjects, + mockJiraStatuses, + mockJiraPriorities, + mockJiraIssueTypes, + mockJiraTransitions, + mockJiraIssues, + mockJiraCurrentUser, + getJiraIssueByKey, + getJiraIssueById, + searchJiraIssues, + getIssuesByStatus, +}; diff --git a/test/e2e/mocks/jira/server.js b/test/e2e/mocks/jira/server.js new file mode 100644 index 0000000..b8079e9 --- /dev/null +++ b/test/e2e/mocks/jira/server.js @@ -0,0 +1,262 @@ +"use strict"; +/** + * Jira REST API Mock Server + * + * Uses MSW (Mock Service Worker) to intercept Jira REST API requests + * and return test fixture data. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.jiraMockServer = void 0; +exports.startJiraMockServer = startJiraMockServer; +exports.stopJiraMockServer = stopJiraMockServer; +exports.resetJiraMockServer = resetJiraMockServer; +exports.addJiraMockHandler = addJiraMockHandler; +const msw_1 = require("msw"); +const node_1 = require("msw/node"); +const fixtures_1 = require("./fixtures"); +// Jira API base URLs (both Cloud and Server patterns) +const JIRA_CLOUD_BASE = "https://*.atlassian.net"; +const JIRA_API_PATH = "/rest/api"; +// Create handlers for both API versions (2 and 3) +function createJiraHandlers(baseUrl) { + return [ + // Get myself (current user) + msw_1.http.get(`${baseUrl}/rest/api/*/myself`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraCurrentUser); + }), + // Search issues (JQL) + msw_1.http.get(`${baseUrl}/rest/api/*/search`, ({ request }) => { + const url = new URL(request.url); + const jql = url.searchParams.get("jql") || ""; + const startAt = parseInt(url.searchParams.get("startAt") || "0"); + const maxResults = parseInt(url.searchParams.get("maxResults") || "50"); + const allIssues = (0, fixtures_1.searchJiraIssues)(jql); + const paginatedIssues = allIssues.slice(startAt, startAt + maxResults); + return msw_1.HttpResponse.json({ + startAt, + maxResults, + total: allIssues.length, + issues: paginatedIssues, + }); + }), + // POST search (alternative endpoint) + msw_1.http.post(`${baseUrl}/rest/api/*/search`, async ({ request }) => { + const body = await request.json(); + const jql = body?.jql || ""; + const startAt = body?.startAt || 0; + const maxResults = body?.maxResults || 50; + const allIssues = (0, fixtures_1.searchJiraIssues)(jql); + const paginatedIssues = allIssues.slice(startAt, startAt + maxResults); + return msw_1.HttpResponse.json({ + startAt, + maxResults, + total: allIssues.length, + issues: paginatedIssues, + }); + }), + // Get single issue by key + msw_1.http.get(`${baseUrl}/rest/api/*/issue/:issueKey`, ({ params }) => { + const { issueKey } = params; + const issue = (0, fixtures_1.getJiraIssueByKey)(issueKey); + if (issue) { + return msw_1.HttpResponse.json(issue); + } + return msw_1.HttpResponse.json({ + errorMessages: [`Issue does not exist or you do not have permission to see it.`], + errors: {}, + }, { status: 404 }); + }), + // Create issue + msw_1.http.post(`${baseUrl}/rest/api/*/issue`, async ({ request }) => { + const body = await request.json(); + const fields = body?.fields || {}; + const newIssue = { + id: `${Date.now()}`, + key: `TEST-${100 + fixtures_1.mockJiraIssues.length + 1}`, + self: `${baseUrl}/rest/api/3/issue/${Date.now()}`, + fields: { + summary: fields.summary || "New Issue", + description: fields.description || null, + status: fixtures_1.mockJiraStatuses[0], + priority: fixtures_1.mockJiraPriorities[2], + issuetype: fixtures_1.mockJiraIssueTypes[0], + assignee: null, + reporter: fixtures_1.mockJiraCurrentUser, + project: fixtures_1.mockJiraProjects[0], + created: new Date().toISOString(), + updated: new Date().toISOString(), + labels: [], + comment: { comments: [], total: 0 }, + }, + }; + return msw_1.HttpResponse.json({ + id: newIssue.id, + key: newIssue.key, + self: newIssue.self, + }); + }), + // Update issue + msw_1.http.put(`${baseUrl}/rest/api/*/issue/:issueKey`, async ({ params, request }) => { + const { issueKey } = params; + const issue = (0, fixtures_1.getJiraIssueByKey)(issueKey); + if (!issue) { + return msw_1.HttpResponse.json({ + errorMessages: ["Issue does not exist"], + errors: {}, + }, { status: 404 }); + } + // In a real test, we'd update the fixture + return new msw_1.HttpResponse(null, { status: 204 }); + }), + // Get transitions for an issue + msw_1.http.get(`${baseUrl}/rest/api/*/issue/:issueKey/transitions`, ({ params }) => { + const { issueKey } = params; + const issue = (0, fixtures_1.getJiraIssueByKey)(issueKey); + if (!issue) { + return msw_1.HttpResponse.json({ + errorMessages: ["Issue does not exist"], + errors: {}, + }, { status: 404 }); + } + return msw_1.HttpResponse.json({ + transitions: fixtures_1.mockJiraTransitions, + }); + }), + // Perform transition + msw_1.http.post(`${baseUrl}/rest/api/*/issue/:issueKey/transitions`, async ({ params, request }) => { + const { issueKey } = params; + const issue = (0, fixtures_1.getJiraIssueByKey)(issueKey); + if (!issue) { + return msw_1.HttpResponse.json({ + errorMessages: ["Issue does not exist"], + errors: {}, + }, { status: 404 }); + } + return new msw_1.HttpResponse(null, { status: 204 }); + }), + // Add comment + msw_1.http.post(`${baseUrl}/rest/api/*/issue/:issueKey/comment`, async ({ params, request }) => { + const { issueKey } = params; + const body = await request.json(); + const newComment = { + id: `comment-${Date.now()}`, + body: body?.body || "", + author: fixtures_1.mockJiraCurrentUser, + created: new Date().toISOString(), + updated: new Date().toISOString(), + }; + return msw_1.HttpResponse.json(newComment, { status: 201 }); + }), + // Get all projects + msw_1.http.get(`${baseUrl}/rest/api/*/project`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraProjects); + }), + // Get single project + msw_1.http.get(`${baseUrl}/rest/api/*/project/:projectKey`, ({ params }) => { + const { projectKey } = params; + const project = fixtures_1.mockJiraProjects.find((p) => p.key === projectKey || p.id === projectKey); + if (project) { + return msw_1.HttpResponse.json(project); + } + return msw_1.HttpResponse.json({ + errorMessages: ["Project not found"], + errors: {}, + }, { status: 404 }); + }), + // Get project statuses + msw_1.http.get(`${baseUrl}/rest/api/*/project/:projectKey/statuses`, () => { + return msw_1.HttpResponse.json([ + { + id: fixtures_1.mockJiraIssueTypes[0].id, + name: fixtures_1.mockJiraIssueTypes[0].name, + statuses: fixtures_1.mockJiraStatuses, + }, + ]); + }), + // Get all priorities + msw_1.http.get(`${baseUrl}/rest/api/*/priority`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraPriorities); + }), + // Get all statuses + msw_1.http.get(`${baseUrl}/rest/api/*/status`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraStatuses); + }), + // Get issue types + msw_1.http.get(`${baseUrl}/rest/api/*/issuetype`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraIssueTypes); + }), + // Get issue types for project + msw_1.http.get(`${baseUrl}/rest/api/*/project/:projectKey/issuetypes`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraIssueTypes.filter((t) => !t.subtask)); + }), + // User search + msw_1.http.get(`${baseUrl}/rest/api/*/user/search`, ({ request }) => { + const url = new URL(request.url); + const query = url.searchParams.get("query") || ""; + const matchedUsers = fixtures_1.mockJiraUsers.filter((user) => user.displayName.toLowerCase().includes(query.toLowerCase()) || + user.emailAddress.toLowerCase().includes(query.toLowerCase())); + return msw_1.HttpResponse.json(matchedUsers); + }), + // User picker (assignable users) + msw_1.http.get(`${baseUrl}/rest/api/*/user/assignable/search`, () => { + return msw_1.HttpResponse.json(fixtures_1.mockJiraUsers); + }), + // Server info + msw_1.http.get(`${baseUrl}/rest/api/*/serverInfo`, () => { + return msw_1.HttpResponse.json({ + baseUrl: baseUrl, + version: "9.0.0", + versionNumbers: [9, 0, 0], + deploymentType: "Cloud", + buildNumber: 100000, + buildDate: new Date().toISOString(), + serverTime: new Date().toISOString(), + scmInfo: "test", + serverTitle: "Test Jira Server", + }); + }), + // Fields (for custom fields) + msw_1.http.get(`${baseUrl}/rest/api/*/field`, () => { + return msw_1.HttpResponse.json([ + { id: "summary", name: "Summary", custom: false }, + { id: "description", name: "Description", custom: false }, + { id: "status", name: "Status", custom: false }, + { id: "priority", name: "Priority", custom: false }, + { id: "assignee", name: "Assignee", custom: false }, + { id: "reporter", name: "Reporter", custom: false }, + ]); + }), + ]; +} +// Create handlers for the standard test URL +const handlers = [ + ...createJiraHandlers("https://test.atlassian.net"), + // Also handle any *.atlassian.net requests + ...createJiraHandlers("https://*"), +]; +// Create and export the server +exports.jiraMockServer = (0, node_1.setupServer)(...handlers); +// Helper functions for test setup +function startJiraMockServer() { + exports.jiraMockServer.listen({ + onUnhandledRequest: "warn", + }); +} +function stopJiraMockServer() { + exports.jiraMockServer.close(); +} +function resetJiraMockServer() { + exports.jiraMockServer.resetHandlers(); +} +// Add a custom handler for specific tests +function addJiraMockHandler(handler) { + exports.jiraMockServer.use(handler); +} +exports.default = { + jiraMockServer: exports.jiraMockServer, + startJiraMockServer, + stopJiraMockServer, + resetJiraMockServer, + addJiraMockHandler, +}; +//# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/test/e2e/mocks/jira/server.js.map b/test/e2e/mocks/jira/server.js.map new file mode 100644 index 0000000..0acfc6d --- /dev/null +++ b/test/e2e/mocks/jira/server.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server.js","sourceRoot":"","sources":["server.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/mocks/jira/server.ts b/test/e2e/mocks/jira/server.ts new file mode 100644 index 0000000..b610ff5 --- /dev/null +++ b/test/e2e/mocks/jira/server.ts @@ -0,0 +1,338 @@ +/** + * Jira REST API Mock Server + * + * Uses MSW (Mock Service Worker) to intercept Jira REST API requests + * and return test fixture data. + */ + +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; +import { + mockJiraIssues, + mockJiraProjects, + mockJiraStatuses, + mockJiraPriorities, + mockJiraIssueTypes, + mockJiraTransitions, + mockJiraCurrentUser, + mockJiraUsers, + getJiraIssueByKey, + getJiraIssueById, + searchJiraIssues, + MockJiraIssue, +} from "./fixtures"; + +// Jira API base URLs (both Cloud and Server patterns) +const JIRA_CLOUD_BASE = "https://*.atlassian.net"; +const JIRA_API_PATH = "/rest/api"; + +// Create handlers for both API versions (2 and 3) +function createJiraHandlers(baseUrl: string) { + return [ + // Get myself (current user) + http.get(`${baseUrl}/rest/api/*/myself`, () => { + return HttpResponse.json(mockJiraCurrentUser); + }), + + // Search issues (JQL) + http.get(`${baseUrl}/rest/api/*/search`, ({ request }) => { + const url = new URL(request.url); + const jql = url.searchParams.get("jql") || ""; + const startAt = parseInt(url.searchParams.get("startAt") || "0"); + const maxResults = parseInt(url.searchParams.get("maxResults") || "50"); + + const allIssues = searchJiraIssues(jql); + const paginatedIssues = allIssues.slice(startAt, startAt + maxResults); + + return HttpResponse.json({ + startAt, + maxResults, + total: allIssues.length, + issues: paginatedIssues, + }); + }), + + // POST search (alternative endpoint) + http.post(`${baseUrl}/rest/api/*/search`, async ({ request }) => { + const body = await request.json() as { jql?: string; startAt?: number; maxResults?: number }; + const jql = body?.jql || ""; + const startAt = body?.startAt || 0; + const maxResults = body?.maxResults || 50; + + const allIssues = searchJiraIssues(jql); + const paginatedIssues = allIssues.slice(startAt, startAt + maxResults); + + return HttpResponse.json({ + startAt, + maxResults, + total: allIssues.length, + issues: paginatedIssues, + }); + }), + + // Get single issue by key + http.get(`${baseUrl}/rest/api/*/issue/:issueKey`, ({ params }) => { + const { issueKey } = params; + const issue = getJiraIssueByKey(issueKey as string); + + if (issue) { + return HttpResponse.json(issue); + } + + return HttpResponse.json( + { + errorMessages: [`Issue does not exist or you do not have permission to see it.`], + errors: {}, + }, + { status: 404 } + ); + }), + + // Create issue + http.post(`${baseUrl}/rest/api/*/issue`, async ({ request }) => { + const body = await request.json() as { fields?: Record }; + const fields = body?.fields || {}; + + const newIssue: MockJiraIssue = { + id: `${Date.now()}`, + key: `TEST-${100 + mockJiraIssues.length + 1}`, + self: `${baseUrl}/rest/api/3/issue/${Date.now()}`, + fields: { + summary: (fields.summary as string) || "New Issue", + description: (fields.description as string) || null, + status: mockJiraStatuses[0], + priority: mockJiraPriorities[2], + issuetype: mockJiraIssueTypes[0], + assignee: null, + reporter: mockJiraCurrentUser, + project: mockJiraProjects[0], + created: new Date().toISOString(), + updated: new Date().toISOString(), + labels: [], + comment: { comments: [], total: 0 }, + }, + }; + + return HttpResponse.json({ + id: newIssue.id, + key: newIssue.key, + self: newIssue.self, + }); + }), + + // Update issue + http.put(`${baseUrl}/rest/api/*/issue/:issueKey`, async ({ params, request }) => { + const { issueKey } = params; + const issue = getJiraIssueByKey(issueKey as string); + + if (!issue) { + return HttpResponse.json( + { + errorMessages: ["Issue does not exist"], + errors: {}, + }, + { status: 404 } + ); + } + + // In a real test, we'd update the fixture + return new HttpResponse(null, { status: 204 }); + }), + + // Get transitions for an issue + http.get(`${baseUrl}/rest/api/*/issue/:issueKey/transitions`, ({ params }) => { + const { issueKey } = params; + const issue = getJiraIssueByKey(issueKey as string); + + if (!issue) { + return HttpResponse.json( + { + errorMessages: ["Issue does not exist"], + errors: {}, + }, + { status: 404 } + ); + } + + return HttpResponse.json({ + transitions: mockJiraTransitions, + }); + }), + + // Perform transition + http.post(`${baseUrl}/rest/api/*/issue/:issueKey/transitions`, async ({ params, request }) => { + const { issueKey } = params; + const issue = getJiraIssueByKey(issueKey as string); + + if (!issue) { + return HttpResponse.json( + { + errorMessages: ["Issue does not exist"], + errors: {}, + }, + { status: 404 } + ); + } + + return new HttpResponse(null, { status: 204 }); + }), + + // Add comment + http.post(`${baseUrl}/rest/api/*/issue/:issueKey/comment`, async ({ params, request }) => { + const { issueKey } = params; + const body = await request.json() as { body?: string }; + + const newComment = { + id: `comment-${Date.now()}`, + body: body?.body || "", + author: mockJiraCurrentUser, + created: new Date().toISOString(), + updated: new Date().toISOString(), + }; + + return HttpResponse.json(newComment, { status: 201 }); + }), + + // Get all projects + http.get(`${baseUrl}/rest/api/*/project`, () => { + return HttpResponse.json(mockJiraProjects); + }), + + // Get single project + http.get(`${baseUrl}/rest/api/*/project/:projectKey`, ({ params }) => { + const { projectKey } = params; + const project = mockJiraProjects.find( + (p) => p.key === projectKey || p.id === projectKey + ); + + if (project) { + return HttpResponse.json(project); + } + + return HttpResponse.json( + { + errorMessages: ["Project not found"], + errors: {}, + }, + { status: 404 } + ); + }), + + // Get project statuses + http.get(`${baseUrl}/rest/api/*/project/:projectKey/statuses`, () => { + return HttpResponse.json([ + { + id: mockJiraIssueTypes[0].id, + name: mockJiraIssueTypes[0].name, + statuses: mockJiraStatuses, + }, + ]); + }), + + // Get all priorities + http.get(`${baseUrl}/rest/api/*/priority`, () => { + return HttpResponse.json(mockJiraPriorities); + }), + + // Get all statuses + http.get(`${baseUrl}/rest/api/*/status`, () => { + return HttpResponse.json(mockJiraStatuses); + }), + + // Get issue types + http.get(`${baseUrl}/rest/api/*/issuetype`, () => { + return HttpResponse.json(mockJiraIssueTypes); + }), + + // Get issue types for project + http.get(`${baseUrl}/rest/api/*/project/:projectKey/issuetypes`, () => { + return HttpResponse.json(mockJiraIssueTypes.filter((t) => !t.subtask)); + }), + + // User search + http.get(`${baseUrl}/rest/api/*/user/search`, ({ request }) => { + const url = new URL(request.url); + const query = url.searchParams.get("query") || ""; + + const matchedUsers = mockJiraUsers.filter( + (user) => + user.displayName.toLowerCase().includes(query.toLowerCase()) || + user.emailAddress.toLowerCase().includes(query.toLowerCase()) + ); + + return HttpResponse.json(matchedUsers); + }), + + // User picker (assignable users) + http.get(`${baseUrl}/rest/api/*/user/assignable/search`, () => { + return HttpResponse.json(mockJiraUsers); + }), + + // Server info + http.get(`${baseUrl}/rest/api/*/serverInfo`, () => { + return HttpResponse.json({ + baseUrl: baseUrl, + version: "9.0.0", + versionNumbers: [9, 0, 0], + deploymentType: "Cloud", + buildNumber: 100000, + buildDate: new Date().toISOString(), + serverTime: new Date().toISOString(), + scmInfo: "test", + serverTitle: "Test Jira Server", + }); + }), + + // Fields (for custom fields) + http.get(`${baseUrl}/rest/api/*/field`, () => { + return HttpResponse.json([ + { id: "summary", name: "Summary", custom: false }, + { id: "description", name: "Description", custom: false }, + { id: "status", name: "Status", custom: false }, + { id: "priority", name: "Priority", custom: false }, + { id: "assignee", name: "Assignee", custom: false }, + { id: "reporter", name: "Reporter", custom: false }, + ]); + }), + ]; +} + +// Create handlers for the standard test URL +const handlers = [ + ...createJiraHandlers("https://test.atlassian.net"), + // Also handle any *.atlassian.net requests + ...createJiraHandlers("https://*"), +]; + +// Create and export the server +export const jiraMockServer = setupServer(...handlers); + +// Helper functions for test setup +export function startJiraMockServer(): void { + jiraMockServer.listen({ + onUnhandledRequest: "warn", + }); +} + +export function stopJiraMockServer(): void { + jiraMockServer.close(); +} + +export function resetJiraMockServer(): void { + jiraMockServer.resetHandlers(); +} + +// Add a custom handler for specific tests +export function addJiraMockHandler( + handler: ReturnType | ReturnType +): void { + jiraMockServer.use(handler); +} + +export default { + jiraMockServer, + startJiraMockServer, + stopJiraMockServer, + resetJiraMockServer, + addJiraMockHandler, +}; diff --git a/test/e2e/mocks/linear/fixtures.js b/test/e2e/mocks/linear/fixtures.js new file mode 100644 index 0000000..734084e --- /dev/null +++ b/test/e2e/mocks/linear/fixtures.js @@ -0,0 +1,233 @@ +"use strict"; +/** + * Linear Mock Fixtures + * + * Test data for Linear GraphQL API mock server. + * Includes issues, teams, projects, users, labels, and workflow states. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.mockOrganization = exports.mockViewer = exports.mockIssues = exports.mockWorkflowStates = exports.mockLabels = exports.mockProjects = exports.mockTeams = exports.mockUsers = void 0; +exports.getIssuesByState = getIssuesByState; +exports.getIssueByIdentifier = getIssueByIdentifier; +exports.getIssueById = getIssueById; +exports.searchIssues = searchIssues; +// Mock Users +exports.mockUsers = [ + { + id: "user-1", + name: "Test User", + email: "test@example.com", + displayName: "Test User", + avatarUrl: "https://example.com/avatar1.png", + }, + { + id: "user-2", + name: "Jane Developer", + email: "jane@example.com", + displayName: "Jane Developer", + avatarUrl: "https://example.com/avatar2.png", + }, +]; +// Mock Teams +exports.mockTeams = [ + { + id: "team-1", + name: "Engineering", + key: "ENG", + description: "Engineering team", + }, + { + id: "team-2", + name: "Product", + key: "PROD", + description: "Product team", + }, +]; +// Mock Projects +exports.mockProjects = [ + { + id: "project-1", + name: "Q1 Sprint", + description: "Q1 development sprint", + state: "started", + slugId: "q1-sprint", + }, + { + id: "project-2", + name: "Bug Fixes", + description: "Bug fix backlog", + state: "started", + slugId: "bug-fixes", + }, +]; +// Mock Labels +exports.mockLabels = [ + { id: "label-1", name: "bug", color: "#FF0000" }, + { id: "label-2", name: "feature", color: "#00FF00" }, + { id: "label-3", name: "enhancement", color: "#0000FF" }, + { id: "label-4", name: "documentation", color: "#FFFF00" }, +]; +// Mock Workflow States +exports.mockWorkflowStates = [ + { id: "state-1", name: "Backlog", type: "backlog", position: 0, color: "#95A2B3" }, + { id: "state-2", name: "Todo", type: "unstarted", position: 1, color: "#E2E2E2" }, + { id: "state-3", name: "In Progress", type: "started", position: 2, color: "#F2C94C" }, + { id: "state-4", name: "In Review", type: "started", position: 3, color: "#BB87FC" }, + { id: "state-5", name: "Done", type: "completed", position: 4, color: "#5E6AD2" }, + { id: "state-6", name: "Canceled", type: "canceled", position: 5, color: "#95A2B3" }, +]; +// Mock Issues +exports.mockIssues = [ + { + id: "issue-1", + identifier: "ENG-101", + title: "Implement user authentication", + description: "Add OAuth2 authentication support with Google and GitHub providers", + priority: 1, + priorityLabel: "Urgent", + estimate: 5, + state: exports.mockWorkflowStates[2], // In Progress + assignee: exports.mockUsers[0], + team: exports.mockTeams[0], + project: exports.mockProjects[0], + labels: { nodes: [exports.mockLabels[1]] }, + createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-101", + branchName: "feat/eng-101-user-auth", + attachments: { nodes: [] }, + comments: { + nodes: [ + { + id: "comment-1", + body: "Started working on this", + createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + user: exports.mockUsers[0], + }, + ], + }, + }, + { + id: "issue-2", + identifier: "ENG-102", + title: "Fix database connection timeout", + description: "Connection pool is not properly releasing connections", + priority: 2, + priorityLabel: "High", + estimate: 3, + state: exports.mockWorkflowStates[1], // Todo + assignee: exports.mockUsers[0], + team: exports.mockTeams[0], + project: exports.mockProjects[1], + labels: { nodes: [exports.mockLabels[0]] }, + createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-102", + attachments: { nodes: [] }, + comments: { nodes: [] }, + }, + { + id: "issue-3", + identifier: "ENG-103", + title: "Add unit tests for payment module", + description: "Increase test coverage for payment processing", + priority: 3, + priorityLabel: "Medium", + estimate: 8, + state: exports.mockWorkflowStates[0], // Backlog + assignee: exports.mockUsers[0], + team: exports.mockTeams[0], + labels: { nodes: [exports.mockLabels[2]] }, + createdAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-103", + attachments: { nodes: [] }, + comments: { nodes: [] }, + }, + { + id: "issue-4", + identifier: "ENG-104", + title: "Update documentation for API v2", + description: "Document all new endpoints and deprecations", + priority: 4, + priorityLabel: "Low", + estimate: 2, + state: exports.mockWorkflowStates[3], // In Review + assignee: exports.mockUsers[0], + team: exports.mockTeams[0], + project: exports.mockProjects[0], + labels: { nodes: [exports.mockLabels[3]] }, + createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-104", + branchName: "docs/eng-104-api-v2", + attachments: { + nodes: [ + { + id: "pr-1", + title: "PR: Update API documentation", + url: "https://github.com/test-org/repo/pull/123", + metadata: { type: "github-pull-request" }, + }, + ], + }, + comments: { nodes: [] }, + }, + { + id: "issue-5", + identifier: "ENG-105", + title: "Completed: Setup CI/CD pipeline", + description: "Configure GitHub Actions for automated testing and deployment", + priority: 2, + priorityLabel: "High", + estimate: 5, + state: exports.mockWorkflowStates[4], // Done + assignee: exports.mockUsers[0], + team: exports.mockTeams[0], + labels: { nodes: [exports.mockLabels[2]] }, + createdAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-105", + attachments: { nodes: [] }, + comments: { nodes: [] }, + }, +]; +// Current user (for viewer query) +exports.mockViewer = exports.mockUsers[0]; +// Organization data +exports.mockOrganization = { + id: "org-1", + name: "Test Organization", + urlKey: "test-org", +}; +// Helper functions for filtering and searching +function getIssuesByState(stateType) { + return exports.mockIssues.filter((issue) => issue.state.type === stateType); +} +function getIssueByIdentifier(identifier) { + return exports.mockIssues.find((issue) => issue.identifier === identifier); +} +function getIssueById(id) { + return exports.mockIssues.find((issue) => issue.id === id); +} +function searchIssues(query) { + const lowerQuery = query.toLowerCase(); + return exports.mockIssues.filter((issue) => issue.title.toLowerCase().includes(lowerQuery) || + issue.identifier.toLowerCase().includes(lowerQuery) || + issue.description?.toLowerCase().includes(lowerQuery)); +} +exports.default = { + mockUsers: exports.mockUsers, + mockTeams: exports.mockTeams, + mockProjects: exports.mockProjects, + mockLabels: exports.mockLabels, + mockWorkflowStates: exports.mockWorkflowStates, + mockIssues: exports.mockIssues, + mockViewer: exports.mockViewer, + mockOrganization: exports.mockOrganization, + getIssuesByState, + getIssueByIdentifier, + getIssueById, + searchIssues, +}; +//# sourceMappingURL=fixtures.js.map \ No newline at end of file diff --git a/test/e2e/mocks/linear/fixtures.js.map b/test/e2e/mocks/linear/fixtures.js.map new file mode 100644 index 0000000..8d53613 --- /dev/null +++ b/test/e2e/mocks/linear/fixtures.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fixtures.js","sourceRoot":"","sources":["fixtures.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/mocks/linear/fixtures.ts b/test/e2e/mocks/linear/fixtures.ts new file mode 100644 index 0000000..d7aeadd --- /dev/null +++ b/test/e2e/mocks/linear/fixtures.ts @@ -0,0 +1,317 @@ +/** + * Linear Mock Fixtures + * + * Test data for Linear GraphQL API mock server. + * Includes issues, teams, projects, users, labels, and workflow states. + */ + +export interface MockLinearUser { + id: string; + name: string; + email: string; + displayName: string; + avatarUrl?: string; +} + +export interface MockLinearTeam { + id: string; + name: string; + key: string; + description?: string; +} + +export interface MockLinearProject { + id: string; + name: string; + description?: string; + state: string; + slugId: string; +} + +export interface MockLinearLabel { + id: string; + name: string; + color: string; +} + +export interface MockLinearWorkflowState { + id: string; + name: string; + type: "backlog" | "unstarted" | "started" | "completed" | "canceled"; + position: number; + color: string; +} + +export interface MockLinearIssue { + id: string; + identifier: string; + title: string; + description?: string; + priority: number; + priorityLabel: string; + estimate?: number; + state: MockLinearWorkflowState; + assignee?: MockLinearUser; + team: MockLinearTeam; + project?: MockLinearProject; + labels: { nodes: MockLinearLabel[] }; + createdAt: string; + updatedAt: string; + url: string; + branchName?: string; + attachments?: { + nodes: Array<{ + id: string; + title: string; + url: string; + metadata?: Record; + }>; + }; + comments?: { + nodes: Array<{ + id: string; + body: string; + createdAt: string; + user: MockLinearUser; + }>; + }; +} + +// Mock Users +export const mockUsers: MockLinearUser[] = [ + { + id: "user-1", + name: "Test User", + email: "test@example.com", + displayName: "Test User", + avatarUrl: "https://example.com/avatar1.png", + }, + { + id: "user-2", + name: "Jane Developer", + email: "jane@example.com", + displayName: "Jane Developer", + avatarUrl: "https://example.com/avatar2.png", + }, +]; + +// Mock Teams +export const mockTeams: MockLinearTeam[] = [ + { + id: "team-1", + name: "Engineering", + key: "ENG", + description: "Engineering team", + }, + { + id: "team-2", + name: "Product", + key: "PROD", + description: "Product team", + }, +]; + +// Mock Projects +export const mockProjects: MockLinearProject[] = [ + { + id: "project-1", + name: "Q1 Sprint", + description: "Q1 development sprint", + state: "started", + slugId: "q1-sprint", + }, + { + id: "project-2", + name: "Bug Fixes", + description: "Bug fix backlog", + state: "started", + slugId: "bug-fixes", + }, +]; + +// Mock Labels +export const mockLabels: MockLinearLabel[] = [ + { id: "label-1", name: "bug", color: "#FF0000" }, + { id: "label-2", name: "feature", color: "#00FF00" }, + { id: "label-3", name: "enhancement", color: "#0000FF" }, + { id: "label-4", name: "documentation", color: "#FFFF00" }, +]; + +// Mock Workflow States +export const mockWorkflowStates: MockLinearWorkflowState[] = [ + { id: "state-1", name: "Backlog", type: "backlog", position: 0, color: "#95A2B3" }, + { id: "state-2", name: "Todo", type: "unstarted", position: 1, color: "#E2E2E2" }, + { id: "state-3", name: "In Progress", type: "started", position: 2, color: "#F2C94C" }, + { id: "state-4", name: "In Review", type: "started", position: 3, color: "#BB87FC" }, + { id: "state-5", name: "Done", type: "completed", position: 4, color: "#5E6AD2" }, + { id: "state-6", name: "Canceled", type: "canceled", position: 5, color: "#95A2B3" }, +]; + +// Mock Issues +export const mockIssues: MockLinearIssue[] = [ + { + id: "issue-1", + identifier: "ENG-101", + title: "Implement user authentication", + description: "Add OAuth2 authentication support with Google and GitHub providers", + priority: 1, + priorityLabel: "Urgent", + estimate: 5, + state: mockWorkflowStates[2], // In Progress + assignee: mockUsers[0], + team: mockTeams[0], + project: mockProjects[0], + labels: { nodes: [mockLabels[1]] }, + createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-101", + branchName: "feat/eng-101-user-auth", + attachments: { nodes: [] }, + comments: { + nodes: [ + { + id: "comment-1", + body: "Started working on this", + createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + user: mockUsers[0], + }, + ], + }, + }, + { + id: "issue-2", + identifier: "ENG-102", + title: "Fix database connection timeout", + description: "Connection pool is not properly releasing connections", + priority: 2, + priorityLabel: "High", + estimate: 3, + state: mockWorkflowStates[1], // Todo + assignee: mockUsers[0], + team: mockTeams[0], + project: mockProjects[1], + labels: { nodes: [mockLabels[0]] }, + createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-102", + attachments: { nodes: [] }, + comments: { nodes: [] }, + }, + { + id: "issue-3", + identifier: "ENG-103", + title: "Add unit tests for payment module", + description: "Increase test coverage for payment processing", + priority: 3, + priorityLabel: "Medium", + estimate: 8, + state: mockWorkflowStates[0], // Backlog + assignee: mockUsers[0], + team: mockTeams[0], + labels: { nodes: [mockLabels[2]] }, + createdAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-103", + attachments: { nodes: [] }, + comments: { nodes: [] }, + }, + { + id: "issue-4", + identifier: "ENG-104", + title: "Update documentation for API v2", + description: "Document all new endpoints and deprecations", + priority: 4, + priorityLabel: "Low", + estimate: 2, + state: mockWorkflowStates[3], // In Review + assignee: mockUsers[0], + team: mockTeams[0], + project: mockProjects[0], + labels: { nodes: [mockLabels[3]] }, + createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-104", + branchName: "docs/eng-104-api-v2", + attachments: { + nodes: [ + { + id: "pr-1", + title: "PR: Update API documentation", + url: "https://github.com/test-org/repo/pull/123", + metadata: { type: "github-pull-request" }, + }, + ], + }, + comments: { nodes: [] }, + }, + { + id: "issue-5", + identifier: "ENG-105", + title: "Completed: Setup CI/CD pipeline", + description: "Configure GitHub Actions for automated testing and deployment", + priority: 2, + priorityLabel: "High", + estimate: 5, + state: mockWorkflowStates[4], // Done + assignee: mockUsers[0], + team: mockTeams[0], + labels: { nodes: [mockLabels[2]] }, + createdAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), + updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), + url: "https://linear.app/test-org/issue/ENG-105", + attachments: { nodes: [] }, + comments: { nodes: [] }, + }, +]; + +// Current user (for viewer query) +export const mockViewer = mockUsers[0]; + +// Organization data +export const mockOrganization = { + id: "org-1", + name: "Test Organization", + urlKey: "test-org", +}; + +// Helper functions for filtering and searching +export function getIssuesByState( + stateType: MockLinearWorkflowState["type"] +): MockLinearIssue[] { + return mockIssues.filter((issue) => issue.state.type === stateType); +} + +export function getIssueByIdentifier( + identifier: string +): MockLinearIssue | undefined { + return mockIssues.find((issue) => issue.identifier === identifier); +} + +export function getIssueById(id: string): MockLinearIssue | undefined { + return mockIssues.find((issue) => issue.id === id); +} + +export function searchIssues(query: string): MockLinearIssue[] { + const lowerQuery = query.toLowerCase(); + return mockIssues.filter( + (issue) => + issue.title.toLowerCase().includes(lowerQuery) || + issue.identifier.toLowerCase().includes(lowerQuery) || + issue.description?.toLowerCase().includes(lowerQuery) + ); +} + +export default { + mockUsers, + mockTeams, + mockProjects, + mockLabels, + mockWorkflowStates, + mockIssues, + mockViewer, + mockOrganization, + getIssuesByState, + getIssueByIdentifier, + getIssueById, + searchIssues, +}; diff --git a/test/e2e/mocks/linear/server.js b/test/e2e/mocks/linear/server.js new file mode 100644 index 0000000..04797f1 --- /dev/null +++ b/test/e2e/mocks/linear/server.js @@ -0,0 +1,322 @@ +"use strict"; +/** + * Linear GraphQL Mock Server + * + * Uses MSW (Mock Service Worker) to intercept Linear GraphQL API requests + * and return test fixture data. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.linearMockServer = void 0; +exports.startLinearMockServer = startLinearMockServer; +exports.stopLinearMockServer = stopLinearMockServer; +exports.resetLinearMockServer = resetLinearMockServer; +exports.addLinearMockHandler = addLinearMockHandler; +const msw_1 = require("msw"); +const node_1 = require("msw/node"); +const fixtures_1 = require("./fixtures"); +// Linear API endpoint +const LINEAR_API_URL = "https://api.linear.app/graphql"; +// GraphQL handlers +const handlers = [ + // Main GraphQL endpoint handler + msw_1.graphql.operation(async ({ query, variables }) => { + // Parse the query to determine what data is requested + const queryString = query?.toString() || ""; + // Handle viewer query (current user) + if (queryString.includes("viewer")) { + return msw_1.HttpResponse.json({ + data: { + viewer: { + id: fixtures_1.mockViewer.id, + name: fixtures_1.mockViewer.name, + email: fixtures_1.mockViewer.email, + displayName: fixtures_1.mockViewer.displayName, + avatarUrl: fixtures_1.mockViewer.avatarUrl, + }, + }, + }); + } + // Handle issues query (assigned issues) + if (queryString.includes("issues") && !queryString.includes("issue(")) { + const assignedIssues = fixtures_1.mockIssues.filter((issue) => issue.assignee?.id === fixtures_1.mockViewer.id); + return msw_1.HttpResponse.json({ + data: { + issues: { + nodes: assignedIssues.map(formatIssueForResponse), + pageInfo: { + hasNextPage: false, + endCursor: null, + }, + }, + }, + }); + } + // Handle single issue query by ID + if (queryString.includes("issue(") && variables?.id) { + const issue = (0, fixtures_1.getIssueById)(variables.id); + if (issue) { + return msw_1.HttpResponse.json({ + data: { + issue: formatIssueForResponse(issue), + }, + }); + } + return msw_1.HttpResponse.json({ + errors: [{ message: "Issue not found" }], + }); + } + // Handle teams query + if (queryString.includes("teams")) { + return msw_1.HttpResponse.json({ + data: { + teams: { + nodes: fixtures_1.mockTeams.map((team) => ({ + id: team.id, + name: team.name, + key: team.key, + description: team.description, + })), + }, + }, + }); + } + // Handle projects query + if (queryString.includes("projects")) { + return msw_1.HttpResponse.json({ + data: { + projects: { + nodes: fixtures_1.mockProjects.map((project) => ({ + id: project.id, + name: project.name, + description: project.description, + state: project.state, + slugId: project.slugId, + })), + }, + }, + }); + } + // Handle workflow states query + if (queryString.includes("workflowStates")) { + return msw_1.HttpResponse.json({ + data: { + workflowStates: { + nodes: fixtures_1.mockWorkflowStates.map((state) => ({ + id: state.id, + name: state.name, + type: state.type, + position: state.position, + color: state.color, + })), + }, + }, + }); + } + // Handle labels query + if (queryString.includes("issueLabels")) { + return msw_1.HttpResponse.json({ + data: { + issueLabels: { + nodes: fixtures_1.mockLabels.map((label) => ({ + id: label.id, + name: label.name, + color: label.color, + })), + }, + }, + }); + } + // Handle organization query + if (queryString.includes("organization")) { + return msw_1.HttpResponse.json({ + data: { + organization: { + id: fixtures_1.mockOrganization.id, + name: fixtures_1.mockOrganization.name, + urlKey: fixtures_1.mockOrganization.urlKey, + }, + }, + }); + } + // Handle issue search + if (queryString.includes("searchIssues") || queryString.includes("issueSearch")) { + const searchQuery = variables?.query || ""; + const results = (0, fixtures_1.searchIssues)(searchQuery); + return msw_1.HttpResponse.json({ + data: { + issueSearch: { + nodes: results.map(formatIssueForResponse), + pageInfo: { + hasNextPage: false, + endCursor: null, + }, + }, + }, + }); + } + // Handle issue create mutation + if (queryString.includes("issueCreate")) { + const input = variables?.input; + const newIssue = { + id: `issue-${Date.now()}`, + identifier: `ENG-${100 + fixtures_1.mockIssues.length + 1}`, + title: input?.title || "New Issue", + description: input?.description, + priority: input?.priority || 0, + priorityLabel: "No priority", + state: fixtures_1.mockWorkflowStates[0], + assignee: fixtures_1.mockViewer, + team: fixtures_1.mockTeams[0], + labels: { nodes: [] }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + url: `https://linear.app/test-org/issue/ENG-${100 + fixtures_1.mockIssues.length + 1}`, + attachments: { nodes: [] }, + comments: { nodes: [] }, + }; + return msw_1.HttpResponse.json({ + data: { + issueCreate: { + success: true, + issue: formatIssueForResponse(newIssue), + }, + }, + }); + } + // Handle issue update mutation + if (queryString.includes("issueUpdate")) { + const issueId = variables?.id; + const input = variables?.input; + const issue = (0, fixtures_1.getIssueById)(issueId); + if (issue) { + // Update the issue (in a real test, we'd modify the fixture) + return msw_1.HttpResponse.json({ + data: { + issueUpdate: { + success: true, + issue: formatIssueForResponse({ + ...issue, + ...(input?.title && { title: input.title }), + ...(input?.description && { description: input.description }), + updatedAt: new Date().toISOString(), + }), + }, + }, + }); + } + return msw_1.HttpResponse.json({ + errors: [{ message: "Issue not found" }], + }); + } + // Handle comment create mutation + if (queryString.includes("commentCreate")) { + return msw_1.HttpResponse.json({ + data: { + commentCreate: { + success: true, + comment: { + id: `comment-${Date.now()}`, + body: variables?.input?.body || "", + createdAt: new Date().toISOString(), + user: fixtures_1.mockViewer, + }, + }, + }, + }); + } + // Default: return empty data + console.warn("Unhandled GraphQL query:", queryString); + return msw_1.HttpResponse.json({ + data: {}, + }); + }), + // Fallback HTTP handler for any Linear API requests + msw_1.http.post(LINEAR_API_URL, async ({ request }) => { + const body = await request.json(); + // This shouldn't be reached if graphql handler works, but just in case + console.warn("HTTP fallback for Linear API:", body?.query); + return msw_1.HttpResponse.json({ + data: {}, + }); + }), +]; +/** + * Format issue for GraphQL response + */ +function formatIssueForResponse(issue) { + return { + id: issue.id, + identifier: issue.identifier, + title: issue.title, + description: issue.description, + priority: issue.priority, + priorityLabel: issue.priorityLabel, + estimate: issue.estimate, + state: { + id: issue.state.id, + name: issue.state.name, + type: issue.state.type, + color: issue.state.color, + }, + assignee: issue.assignee + ? { + id: issue.assignee.id, + name: issue.assignee.name, + email: issue.assignee.email, + displayName: issue.assignee.displayName, + avatarUrl: issue.assignee.avatarUrl, + } + : null, + team: { + id: issue.team.id, + name: issue.team.name, + key: issue.team.key, + }, + project: issue.project + ? { + id: issue.project.id, + name: issue.project.name, + slugId: issue.project.slugId, + } + : null, + labels: { + nodes: issue.labels.nodes.map((label) => ({ + id: label.id, + name: label.name, + color: label.color, + })), + }, + createdAt: issue.createdAt, + updatedAt: issue.updatedAt, + url: issue.url, + branchName: issue.branchName, + attachments: issue.attachments, + comments: issue.comments, + }; +} +// Create and export the server +exports.linearMockServer = (0, node_1.setupServer)(...handlers); +// Helper functions for test setup +function startLinearMockServer() { + exports.linearMockServer.listen({ + onUnhandledRequest: "warn", + }); +} +function stopLinearMockServer() { + exports.linearMockServer.close(); +} +function resetLinearMockServer() { + exports.linearMockServer.resetHandlers(); +} +// Add a custom handler for specific tests +function addLinearMockHandler(handler) { + exports.linearMockServer.use(handler); +} +exports.default = { + linearMockServer: exports.linearMockServer, + startLinearMockServer, + stopLinearMockServer, + resetLinearMockServer, + addLinearMockHandler, +}; +//# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/test/e2e/mocks/linear/server.js.map b/test/e2e/mocks/linear/server.js.map new file mode 100644 index 0000000..1754c60 --- /dev/null +++ b/test/e2e/mocks/linear/server.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server.js","sourceRoot":"","sources":["server.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/mocks/linear/server.ts b/test/e2e/mocks/linear/server.ts new file mode 100644 index 0000000..ddd4c53 --- /dev/null +++ b/test/e2e/mocks/linear/server.ts @@ -0,0 +1,359 @@ +/** + * Linear GraphQL Mock Server + * + * Uses MSW (Mock Service Worker) to intercept Linear GraphQL API requests + * and return test fixture data. + */ + +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; +import { + mockIssues, + mockTeams, + mockProjects, + mockLabels, + mockWorkflowStates, + mockViewer, + mockOrganization, + getIssueById, + searchIssues, + MockLinearIssue, +} from "./fixtures"; + +// Linear API endpoint +const LINEAR_API_URL = "https://api.linear.app/graphql"; + +/** + * Handle Linear GraphQL request + */ +function handleLinearGraphQL(queryString: string, variables: Record = {}) { + // Handle viewer query (current user) + if (queryString.includes("viewer")) { + return { + data: { + viewer: { + id: mockViewer.id, + name: mockViewer.name, + email: mockViewer.email, + displayName: mockViewer.displayName, + avatarUrl: mockViewer.avatarUrl, + }, + }, + }; + } + + // Handle issues query (assigned issues) + if (queryString.includes("issues") && !queryString.includes("issue(")) { + const assignedIssues = mockIssues.filter( + (issue) => issue.assignee?.id === mockViewer.id + ); + + return { + data: { + issues: { + nodes: assignedIssues.map(formatIssueForResponse), + pageInfo: { + hasNextPage: false, + endCursor: null, + }, + }, + }, + }; + } + + // Handle single issue query by ID + if (queryString.includes("issue(") && variables?.id) { + const issue = getIssueById(variables.id as string); + + if (issue) { + return { + data: { + issue: formatIssueForResponse(issue), + }, + }; + } + + return { + errors: [{ message: "Issue not found" }], + }; + } + + // Handle teams query + if (queryString.includes("teams")) { + return { + data: { + teams: { + nodes: mockTeams.map((team) => ({ + id: team.id, + name: team.name, + key: team.key, + description: team.description, + })), + }, + }, + }; + } + + // Handle projects query + if (queryString.includes("projects")) { + return { + data: { + projects: { + nodes: mockProjects.map((project) => ({ + id: project.id, + name: project.name, + description: project.description, + state: project.state, + slugId: project.slugId, + })), + }, + }, + }; + } + + // Handle workflow states query + if (queryString.includes("workflowStates")) { + return { + data: { + workflowStates: { + nodes: mockWorkflowStates.map((state) => ({ + id: state.id, + name: state.name, + type: state.type, + position: state.position, + color: state.color, + })), + }, + }, + }; + } + + // Handle labels query + if (queryString.includes("issueLabels")) { + return { + data: { + issueLabels: { + nodes: mockLabels.map((label) => ({ + id: label.id, + name: label.name, + color: label.color, + })), + }, + }, + }; + } + + // Handle organization query + if (queryString.includes("organization")) { + return { + data: { + organization: { + id: mockOrganization.id, + name: mockOrganization.name, + urlKey: mockOrganization.urlKey, + }, + }, + }; + } + + // Handle issue search + if (queryString.includes("searchIssues") || queryString.includes("issueSearch")) { + const searchQuery = (variables?.query as string) || ""; + const results = searchIssues(searchQuery); + + return { + data: { + issueSearch: { + nodes: results.map(formatIssueForResponse), + pageInfo: { + hasNextPage: false, + endCursor: null, + }, + }, + }, + }; + } + + // Handle issue create mutation + if (queryString.includes("issueCreate")) { + const input = variables?.input as Record; + const newIssue: MockLinearIssue = { + id: `issue-${Date.now()}`, + identifier: `ENG-${100 + mockIssues.length + 1}`, + title: (input?.title as string) || "New Issue", + description: input?.description as string, + priority: (input?.priority as number) || 0, + priorityLabel: "No priority", + state: mockWorkflowStates[0], + assignee: mockViewer, + team: mockTeams[0], + labels: { nodes: [] }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + url: `https://linear.app/test-org/issue/ENG-${100 + mockIssues.length + 1}`, + attachments: { nodes: [] }, + comments: { nodes: [] }, + }; + + return { + data: { + issueCreate: { + success: true, + issue: formatIssueForResponse(newIssue), + }, + }, + }; + } + + // Handle issue update mutation + if (queryString.includes("issueUpdate")) { + const issueId = variables?.id as string; + const input = variables?.input as Record; + const issue = getIssueById(issueId); + + if (issue) { + const updatedIssue = { + ...issue, + title: (input?.title as string) || issue.title, + description: (input?.description as string) || issue.description, + updatedAt: new Date().toISOString(), + }; + + return { + data: { + issueUpdate: { + success: true, + issue: formatIssueForResponse(updatedIssue), + }, + }, + }; + } + + return { + errors: [{ message: "Issue not found" }], + }; + } + + // Handle comment create mutation + if (queryString.includes("commentCreate")) { + const input = variables?.input as Record; + return { + data: { + commentCreate: { + success: true, + comment: { + id: `comment-${Date.now()}`, + body: (input?.body as string) || "", + createdAt: new Date().toISOString(), + user: mockViewer, + }, + }, + }, + }; + } + + // Default: return empty data + console.warn("Unhandled GraphQL query:", queryString); + return { data: {} }; +} + +// HTTP handlers for Linear API +const handlers = [ + // Linear GraphQL endpoint + http.post(LINEAR_API_URL, async ({ request }) => { + const body = await request.json() as { query?: string; variables?: Record }; + const queryString = body?.query || ""; + const variables = body?.variables || {}; + + const response = handleLinearGraphQL(queryString, variables); + return HttpResponse.json(response); + }), +]; + +/** + * Format issue for GraphQL response + */ +function formatIssueForResponse(issue: MockLinearIssue) { + return { + id: issue.id, + identifier: issue.identifier, + title: issue.title, + description: issue.description, + priority: issue.priority, + priorityLabel: issue.priorityLabel, + estimate: issue.estimate, + state: { + id: issue.state.id, + name: issue.state.name, + type: issue.state.type, + color: issue.state.color, + }, + assignee: issue.assignee + ? { + id: issue.assignee.id, + name: issue.assignee.name, + email: issue.assignee.email, + displayName: issue.assignee.displayName, + avatarUrl: issue.assignee.avatarUrl, + } + : null, + team: { + id: issue.team.id, + name: issue.team.name, + key: issue.team.key, + }, + project: issue.project + ? { + id: issue.project.id, + name: issue.project.name, + slugId: issue.project.slugId, + } + : null, + labels: { + nodes: issue.labels.nodes.map((label) => ({ + id: label.id, + name: label.name, + color: label.color, + })), + }, + createdAt: issue.createdAt, + updatedAt: issue.updatedAt, + url: issue.url, + branchName: issue.branchName, + attachments: issue.attachments, + comments: issue.comments, + }; +} + +// Create and export the server +export const linearMockServer = setupServer(...handlers); + +// Helper functions for test setup +export function startLinearMockServer(): void { + linearMockServer.listen({ + onUnhandledRequest: "warn", + }); +} + +export function stopLinearMockServer(): void { + linearMockServer.close(); +} + +export function resetLinearMockServer(): void { + linearMockServer.resetHandlers(); +} + +// Add a custom handler for specific tests +export function addLinearMockHandler( + handler: ReturnType +): void { + linearMockServer.use(handler); +} + +export default { + linearMockServer, + startLinearMockServer, + stopLinearMockServer, + resetLinearMockServer, + addLinearMockHandler, +}; diff --git a/test/e2e/mocks/mockManager.js b/test/e2e/mocks/mockManager.js new file mode 100644 index 0000000..37620fe --- /dev/null +++ b/test/e2e/mocks/mockManager.js @@ -0,0 +1,164 @@ +"use strict"; +/** + * Mock Manager + * + * Centralized management for starting and stopping mock servers + * during E2E test execution. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.mockManager = exports.MockManager = void 0; +exports.startMockServers = startMockServers; +exports.stopMockServers = stopMockServers; +exports.resetMockServers = resetMockServers; +exports.setupMockServerHooks = setupMockServerHooks; +exports.withMocks = withMocks; +const server_1 = require("./linear/server"); +const server_2 = require("./jira/server"); +const testConfig_1 = require("../utils/testConfig"); +/** + * MockManager class for managing mock servers + */ +class MockManager { + constructor() { + this.linearRunning = false; + this.jiraRunning = false; + } + /** + * Start mock servers + */ + start(platform = "all") { + // Skip if using real APIs + if ((0, testConfig_1.shouldUseRealApi)()) { + console.log("Using real APIs - mock servers not started"); + return; + } + if (platform === "linear" || platform === "all") { + if (!this.linearRunning) { + (0, server_1.startLinearMockServer)(); + this.linearRunning = true; + console.log("Linear mock server started"); + } + } + if (platform === "jira" || platform === "all") { + if (!this.jiraRunning) { + (0, server_2.startJiraMockServer)(); + this.jiraRunning = true; + console.log("Jira mock server started"); + } + } + } + /** + * Stop mock servers + */ + stop(platform = "all") { + if (platform === "linear" || platform === "all") { + if (this.linearRunning) { + (0, server_1.stopLinearMockServer)(); + this.linearRunning = false; + console.log("Linear mock server stopped"); + } + } + if (platform === "jira" || platform === "all") { + if (this.jiraRunning) { + (0, server_2.stopJiraMockServer)(); + this.jiraRunning = false; + console.log("Jira mock server stopped"); + } + } + } + /** + * Reset mock servers (clear custom handlers) + */ + reset(platform = "all") { + if (platform === "linear" || platform === "all") { + if (this.linearRunning) { + (0, server_1.resetLinearMockServer)(); + } + } + if (platform === "jira" || platform === "all") { + if (this.jiraRunning) { + (0, server_2.resetJiraMockServer)(); + } + } + } + /** + * Check if mock server is running + */ + isRunning(platform) { + if (platform === "linear") { + return this.linearRunning; + } + if (platform === "jira") { + return this.jiraRunning; + } + return this.linearRunning && this.jiraRunning; + } + /** + * Get the Linear mock server instance + */ + getLinearServer() { + return server_1.linearMockServer; + } + /** + * Get the Jira mock server instance + */ + getJiraServer() { + return server_2.jiraMockServer; + } +} +exports.MockManager = MockManager; +// Singleton instance +exports.mockManager = new MockManager(); +// Convenience functions +function startMockServers(platform = "all") { + exports.mockManager.start(platform); +} +function stopMockServers(platform = "all") { + exports.mockManager.stop(platform); +} +function resetMockServers(platform = "all") { + exports.mockManager.reset(platform); +} +/** + * Setup mock servers for Mocha tests + * Use with before() and after() hooks + */ +function setupMockServerHooks(platform = "all") { + before(() => { + startMockServers(platform); + }); + after(() => { + stopMockServers(platform); + }); + afterEach(() => { + resetMockServers(platform); + }); +} +/** + * Decorator for running tests with mocked APIs + */ +function withMocks(platform = "all") { + return function (target, propertyKey, descriptor) { + const originalMethod = descriptor.value; + descriptor.value = async function (...args) { + startMockServers(platform); + try { + return await originalMethod.apply(this, args); + } + finally { + stopMockServers(platform); + } + }; + return descriptor; + }; +} +exports.default = { + MockManager, + mockManager: exports.mockManager, + startMockServers, + stopMockServers, + resetMockServers, + setupMockServerHooks, + withMocks, +}; +//# sourceMappingURL=mockManager.js.map \ No newline at end of file diff --git a/test/e2e/mocks/mockManager.js.map b/test/e2e/mocks/mockManager.js.map new file mode 100644 index 0000000..534f8f2 --- /dev/null +++ b/test/e2e/mocks/mockManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mockManager.js","sourceRoot":"","sources":["mockManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/mocks/mockManager.ts b/test/e2e/mocks/mockManager.ts new file mode 100644 index 0000000..cd2e5a5 --- /dev/null +++ b/test/e2e/mocks/mockManager.ts @@ -0,0 +1,190 @@ +/** + * Mock Manager + * + * Centralized management for starting and stopping mock servers + * during E2E test execution. + */ + +import { + linearMockServer, + startLinearMockServer, + stopLinearMockServer, + resetLinearMockServer, +} from "./linear/server"; +import { + jiraMockServer, + startJiraMockServer, + stopJiraMockServer, + resetJiraMockServer, +} from "./jira/server"; +import { TestConfig, shouldUseRealApi } from "../utils/testConfig"; + +export type MockPlatform = "linear" | "jira" | "all"; + +/** + * MockManager class for managing mock servers + */ +export class MockManager { + private linearRunning = false; + private jiraRunning = false; + + /** + * Start mock servers + */ + start(platform: MockPlatform = "all"): void { + // Skip if using real APIs + if (shouldUseRealApi()) { + console.log("Using real APIs - mock servers not started"); + return; + } + + if (platform === "linear" || platform === "all") { + if (!this.linearRunning) { + startLinearMockServer(); + this.linearRunning = true; + console.log("Linear mock server started"); + } + } + + if (platform === "jira" || platform === "all") { + if (!this.jiraRunning) { + startJiraMockServer(); + this.jiraRunning = true; + console.log("Jira mock server started"); + } + } + } + + /** + * Stop mock servers + */ + stop(platform: MockPlatform = "all"): void { + if (platform === "linear" || platform === "all") { + if (this.linearRunning) { + stopLinearMockServer(); + this.linearRunning = false; + console.log("Linear mock server stopped"); + } + } + + if (platform === "jira" || platform === "all") { + if (this.jiraRunning) { + stopJiraMockServer(); + this.jiraRunning = false; + console.log("Jira mock server stopped"); + } + } + } + + /** + * Reset mock servers (clear custom handlers) + */ + reset(platform: MockPlatform = "all"): void { + if (platform === "linear" || platform === "all") { + if (this.linearRunning) { + resetLinearMockServer(); + } + } + + if (platform === "jira" || platform === "all") { + if (this.jiraRunning) { + resetJiraMockServer(); + } + } + } + + /** + * Check if mock server is running + */ + isRunning(platform: MockPlatform): boolean { + if (platform === "linear") { + return this.linearRunning; + } + if (platform === "jira") { + return this.jiraRunning; + } + return this.linearRunning && this.jiraRunning; + } + + /** + * Get the Linear mock server instance + */ + getLinearServer() { + return linearMockServer; + } + + /** + * Get the Jira mock server instance + */ + getJiraServer() { + return jiraMockServer; + } +} + +// Singleton instance +export const mockManager = new MockManager(); + +// Convenience functions +export function startMockServers(platform: MockPlatform = "all"): void { + mockManager.start(platform); +} + +export function stopMockServers(platform: MockPlatform = "all"): void { + mockManager.stop(platform); +} + +export function resetMockServers(platform: MockPlatform = "all"): void { + mockManager.reset(platform); +} + +/** + * Setup mock servers for Mocha tests + * Use with before() and after() hooks + */ +export function setupMockServerHooks(platform: MockPlatform = "all"): void { + before(() => { + startMockServers(platform); + }); + + after(() => { + stopMockServers(platform); + }); + + afterEach(() => { + resetMockServers(platform); + }); +} + +/** + * Decorator for running tests with mocked APIs + */ +export function withMocks(platform: MockPlatform = "all") { + return function ( + target: unknown, + propertyKey: string, + descriptor: PropertyDescriptor + ) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: unknown[]) { + startMockServers(platform); + try { + return await originalMethod.apply(this, args); + } finally { + stopMockServers(platform); + } + }; + + return descriptor; + }; +} + +export default { + MockManager, + mockManager, + startMockServers, + stopMockServers, + resetMockServers, + setupMockServerHooks, + withMocks, +}; diff --git a/test/e2e/page-objects/CommandPalettePage.js b/test/e2e/page-objects/CommandPalettePage.js new file mode 100644 index 0000000..59616e8 --- /dev/null +++ b/test/e2e/page-objects/CommandPalettePage.js @@ -0,0 +1,210 @@ +"use strict"; +/** + * CommandPalettePage - Page Object for Command Palette interactions + * + * Provides methods for executing commands, selecting quick pick items, + * and handling input boxes. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CommandPalettePage = void 0; +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +class CommandPalettePage { + constructor() { + this.workbench = new vscode_extension_tester_1.Workbench(); + } + /** + * Open the command palette + */ + async open() { + await this.workbench.openCommandPrompt(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + return new vscode_extension_tester_1.InputBox(); + } + /** + * Execute a command by name + */ + async executeCommand(commandName) { + await this.workbench.executeCommand(commandName); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Execute a DevBuddy command + */ + async executeDevBuddyCommand(command) { + const fullCommand = command.startsWith("devBuddy.") + ? command + : `devBuddy.${command}`; + await this.executeCommand(fullCommand); + } + /** + * Type text into the command palette + */ + async type(text) { + const inputBox = await this.open(); + await inputBox.setText(text); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Select a quick pick item by text + */ + async selectQuickPickItem(itemText) { + const inputBox = new vscode_extension_tester_1.InputBox(); + await (0, helpers_1.waitFor)(async () => { + try { + const picks = await inputBox.getQuickPicks(); + return picks.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + const picks = await inputBox.getQuickPicks(); + for (const pick of picks) { + const label = await pick.getLabel(); + if (label.includes(itemText)) { + await pick.select(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + return; + } + } + throw new Error(`Quick pick item "${itemText}" not found`); + } + /** + * Get all available quick pick items + */ + async getQuickPickItems() { + const inputBox = new vscode_extension_tester_1.InputBox(); + await (0, helpers_1.waitFor)(async () => { + try { + const picks = await inputBox.getQuickPicks(); + return picks.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + const picks = await inputBox.getQuickPicks(); + const labels = []; + for (const pick of picks) { + const label = await pick.getLabel(); + labels.push(label); + } + return labels; + } + /** + * Confirm the current input (press Enter) + */ + async confirm() { + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Cancel the current input (press Escape) + */ + async cancel() { + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.cancel(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Check if the command palette is open + */ + async isOpen() { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + return await inputBox.isDisplayed(); + } + catch { + return false; + } + } + /** + * Get the current text in the input box + */ + async getText() { + const inputBox = new vscode_extension_tester_1.InputBox(); + return inputBox.getText(); + } + /** + * Clear the input box + */ + async clear() { + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.clear(); + } + /** + * Wait for a specific quick pick item to appear + */ + async waitForQuickPickItem(itemText, timeout = testConfig_1.TestConfig.timeouts.ui) { + await (0, helpers_1.waitFor)(async () => { + const items = await this.getQuickPickItems(); + return items.some((item) => item.includes(itemText)); + }, timeout); + } + /** + * Execute a command and wait for the result + */ + async executeAndWait(commandName, waitCondition, timeout = testConfig_1.TestConfig.timeouts.default) { + await this.executeCommand(commandName); + await (0, helpers_1.waitFor)(waitCondition, timeout); + } + /** + * Open quick pick for provider selection + */ + async selectProvider(provider) { + await this.executeDevBuddyCommand("selectProvider"); + await (0, helpers_1.waitFor)(async () => { + try { + const picks = await this.getQuickPickItems(); + return picks.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + const providerLabel = provider === "linear" ? "Linear" : "Jira"; + await this.selectQuickPickItem(providerLabel); + } + /** + * Open ticket by ID + */ + async openTicketById(ticketId) { + await this.executeDevBuddyCommand("openTicketById"); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText(ticketId); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Search for tickets + */ + async searchTickets(query) { + await this.executeDevBuddyCommand("searchTickets"); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText(query); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Quick open a ticket + */ + async quickOpenTicket(identifier) { + await this.executeDevBuddyCommand("quickOpenTicket"); + await (0, helpers_1.waitFor)(async () => { + try { + const picks = await this.getQuickPickItems(); + return picks.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + await this.selectQuickPickItem(identifier); + } +} +exports.CommandPalettePage = CommandPalettePage; +exports.default = CommandPalettePage; +//# sourceMappingURL=CommandPalettePage.js.map \ No newline at end of file diff --git a/test/e2e/page-objects/CommandPalettePage.js.map b/test/e2e/page-objects/CommandPalettePage.js.map new file mode 100644 index 0000000..9ff6a9c --- /dev/null +++ b/test/e2e/page-objects/CommandPalettePage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CommandPalettePage.js","sourceRoot":"","sources":["CommandPalettePage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/page-objects/CommandPalettePage.ts b/test/e2e/page-objects/CommandPalettePage.ts new file mode 100644 index 0000000..0279889 --- /dev/null +++ b/test/e2e/page-objects/CommandPalettePage.ts @@ -0,0 +1,247 @@ +/** + * CommandPalettePage - Page Object for Command Palette interactions + * + * Provides methods for executing commands, selecting quick pick items, + * and handling input boxes. + */ + +import { + Workbench, + InputBox, + QuickOpenBox, +} from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { waitFor, sleep } from "../utils/helpers"; + +export class CommandPalettePage { + private workbench: Workbench; + + constructor() { + this.workbench = new Workbench(); + } + + /** + * Open the command palette + */ + async open(): Promise { + await this.workbench.openCommandPrompt(); + await sleep(TestConfig.timeouts.animation); + return new InputBox(); + } + + /** + * Execute a command by name + */ + async executeCommand(commandName: string): Promise { + await this.workbench.executeCommand(commandName); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Execute a DevBuddy command + */ + async executeDevBuddyCommand(command: string): Promise { + const fullCommand = command.startsWith("devBuddy.") + ? command + : `devBuddy.${command}`; + await this.executeCommand(fullCommand); + } + + /** + * Type text into the command palette + */ + async type(text: string): Promise { + const inputBox = await this.open(); + await inputBox.setText(text); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Select a quick pick item by text + */ + async selectQuickPickItem(itemText: string): Promise { + const inputBox = new InputBox(); + + await waitFor(async () => { + try { + const picks = await inputBox.getQuickPicks(); + return picks.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + const picks = await inputBox.getQuickPicks(); + + for (const pick of picks) { + const label = await pick.getLabel(); + if (label.includes(itemText)) { + await pick.select(); + await sleep(TestConfig.timeouts.animation); + return; + } + } + + throw new Error(`Quick pick item "${itemText}" not found`); + } + + /** + * Get all available quick pick items + */ + async getQuickPickItems(): Promise { + const inputBox = new InputBox(); + + await waitFor(async () => { + try { + const picks = await inputBox.getQuickPicks(); + return picks.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + const picks = await inputBox.getQuickPicks(); + const labels: string[] = []; + + for (const pick of picks) { + const label = await pick.getLabel(); + labels.push(label); + } + + return labels; + } + + /** + * Confirm the current input (press Enter) + */ + async confirm(): Promise { + const inputBox = new InputBox(); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Cancel the current input (press Escape) + */ + async cancel(): Promise { + const inputBox = new InputBox(); + await inputBox.cancel(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Check if the command palette is open + */ + async isOpen(): Promise { + try { + const inputBox = new InputBox(); + return await inputBox.isDisplayed(); + } catch { + return false; + } + } + + /** + * Get the current text in the input box + */ + async getText(): Promise { + const inputBox = new InputBox(); + return inputBox.getText(); + } + + /** + * Clear the input box + */ + async clear(): Promise { + const inputBox = new InputBox(); + await inputBox.clear(); + } + + /** + * Wait for a specific quick pick item to appear + */ + async waitForQuickPickItem( + itemText: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + await waitFor(async () => { + const items = await this.getQuickPickItems(); + return items.some((item) => item.includes(itemText)); + }, timeout); + } + + /** + * Execute a command and wait for the result + */ + async executeAndWait( + commandName: string, + waitCondition: () => Promise, + timeout: number = TestConfig.timeouts.default + ): Promise { + await this.executeCommand(commandName); + await waitFor(waitCondition, timeout); + } + + /** + * Open quick pick for provider selection + */ + async selectProvider(provider: "linear" | "jira"): Promise { + await this.executeDevBuddyCommand("selectProvider"); + + await waitFor(async () => { + try { + const picks = await this.getQuickPickItems(); + return picks.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + const providerLabel = provider === "linear" ? "Linear" : "Jira"; + await this.selectQuickPickItem(providerLabel); + } + + /** + * Open ticket by ID + */ + async openTicketById(ticketId: string): Promise { + await this.executeDevBuddyCommand("openTicketById"); + + const inputBox = new InputBox(); + await inputBox.setText(ticketId); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Search for tickets + */ + async searchTickets(query: string): Promise { + await this.executeDevBuddyCommand("searchTickets"); + + const inputBox = new InputBox(); + await inputBox.setText(query); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Quick open a ticket + */ + async quickOpenTicket(identifier: string): Promise { + await this.executeDevBuddyCommand("quickOpenTicket"); + + await waitFor(async () => { + try { + const picks = await this.getQuickPickItems(); + return picks.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + await this.selectQuickPickItem(identifier); + } +} + +export default CommandPalettePage; diff --git a/test/e2e/page-objects/NotificationPage.js b/test/e2e/page-objects/NotificationPage.js new file mode 100644 index 0000000..8961e91 --- /dev/null +++ b/test/e2e/page-objects/NotificationPage.js @@ -0,0 +1,242 @@ +"use strict"; +/** + * NotificationPage - Page Object for VS Code Notification interactions + * + * Provides methods for handling notifications, alerts, and messages + * displayed by the extension. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NotificationPage = void 0; +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +class NotificationPage { + constructor() { + this.workbench = new vscode_extension_tester_1.Workbench(); + } + /** + * Get all visible notifications + */ + async getNotifications() { + return this.workbench.getNotifications(); + } + /** + * Wait for a notification containing specific text + */ + async waitForNotification(textContains, timeout = testConfig_1.TestConfig.timeouts.ui) { + await (0, helpers_1.waitFor)(async () => { + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return true; + } + } + return false; + }, timeout); + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return notification; + } + } + return undefined; + } + /** + * Find a notification by exact message + */ + async findByMessage(message) { + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const notifMessage = await notification.getMessage(); + if (notifMessage === message) { + return notification; + } + } + return undefined; + } + /** + * Find a notification by type + */ + async findByType(type) { + const notifications = await this.getNotifications(); + const matching = []; + for (const notification of notifications) { + const notifType = await notification.getType(); + if (notifType === type) { + matching.push(notification); + } + } + return matching; + } + /** + * Get notification information + */ + async getNotificationInfo(notification) { + const message = await notification.getMessage(); + const type = await notification.getType(); + const source = await notification.getSource(); + const actions = await notification.getActions(); + const actionLabels = []; + for (const action of actions) { + const title = await action.getTitle(); + actionLabels.push(title); + } + return { + message, + type, + source, + actions: actionLabels, + }; + } + /** + * Dismiss a notification + */ + async dismissNotification(notification) { + await notification.dismiss(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Dismiss all notifications + */ + async dismissAll() { + const notifications = await this.getNotifications(); + for (const notification of notifications) { + try { + await notification.dismiss(); + } + catch { + // Ignore if notification was already dismissed + } + } + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Click an action button on a notification + */ + async clickAction(notification, actionTitle) { + const actions = await notification.getActions(); + for (const action of actions) { + const title = await action.getTitle(); + if (title.includes(actionTitle)) { + await action.click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + return; + } + } + throw new Error(`Action "${actionTitle}" not found on notification`); + } + /** + * Click the first action on a notification + */ + async clickPrimaryAction(notification) { + const actions = await notification.getActions(); + if (actions.length > 0) { + await actions[0].click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + else { + throw new Error("No actions available on notification"); + } + } + /** + * Wait for a notification and click an action + */ + async waitAndClickAction(textContains, actionTitle, timeout = testConfig_1.TestConfig.timeouts.ui) { + const notification = await this.waitForNotification(textContains, timeout); + if (!notification) { + throw new Error(`Notification containing "${textContains}" not found`); + } + await this.clickAction(notification, actionTitle); + } + /** + * Check if any error notifications are present + */ + async hasErrors() { + const errors = await this.findByType(vscode_extension_tester_1.NotificationType.Error); + return errors.length > 0; + } + /** + * Get all error messages + */ + async getErrorMessages() { + const errors = await this.findByType(vscode_extension_tester_1.NotificationType.Error); + const messages = []; + for (const error of errors) { + const message = await error.getMessage(); + messages.push(message); + } + return messages; + } + /** + * Wait for notification center to be empty + */ + async waitForEmpty(timeout = testConfig_1.TestConfig.timeouts.ui) { + await (0, helpers_1.waitFor)(async () => { + const notifications = await this.getNotifications(); + return notifications.length === 0; + }, timeout); + } + /** + * Check if a specific notification type is present + */ + async hasNotificationType(type) { + const notifications = await this.findByType(type); + return notifications.length > 0; + } + /** + * Get count of notifications + */ + async getNotificationCount() { + const notifications = await this.getNotifications(); + return notifications.length; + } + /** + * Wait for notification to appear and then dismiss it + */ + async waitAndDismiss(textContains, timeout = testConfig_1.TestConfig.timeouts.ui) { + const notification = await this.waitForNotification(textContains, timeout); + if (notification) { + await this.dismissNotification(notification); + } + } + /** + * Assert that no error notifications are present + */ + async assertNoErrors() { + const hasErrors = await this.hasErrors(); + if (hasErrors) { + const errorMessages = await this.getErrorMessages(); + throw new Error(`Unexpected error notifications present: ${errorMessages.join(", ")}`); + } + } + /** + * Wait for a success notification + */ + async waitForSuccess(textContains, timeout = testConfig_1.TestConfig.timeouts.ui) { + await (0, helpers_1.waitFor)(async () => { + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + const type = await notification.getType(); + if (message.includes(textContains) && + type === vscode_extension_tester_1.NotificationType.Info) { + return true; + } + } + return false; + }, timeout); + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return notification; + } + } + return undefined; + } +} +exports.NotificationPage = NotificationPage; +exports.default = NotificationPage; +//# sourceMappingURL=NotificationPage.js.map \ No newline at end of file diff --git a/test/e2e/page-objects/NotificationPage.js.map b/test/e2e/page-objects/NotificationPage.js.map new file mode 100644 index 0000000..18c8c7d --- /dev/null +++ b/test/e2e/page-objects/NotificationPage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NotificationPage.js","sourceRoot":"","sources":["NotificationPage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/page-objects/NotificationPage.ts b/test/e2e/page-objects/NotificationPage.ts new file mode 100644 index 0000000..d3de0c8 --- /dev/null +++ b/test/e2e/page-objects/NotificationPage.ts @@ -0,0 +1,314 @@ +/** + * NotificationPage - Page Object for VS Code Notification interactions + * + * Provides methods for handling notifications, alerts, and messages + * displayed by the extension. + */ + +import { + Workbench, + Notification, + NotificationType, +} from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { waitFor, sleep } from "../utils/helpers"; + +export interface NotificationInfo { + message: string; + type: NotificationType; + source?: string; + actions: string[]; +} + +export class NotificationPage { + private workbench: Workbench; + + constructor() { + this.workbench = new Workbench(); + } + + /** + * Get all visible notifications + */ + async getNotifications(): Promise { + return this.workbench.getNotifications(); + } + + /** + * Wait for a notification containing specific text + */ + async waitForNotification( + textContains: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + await waitFor(async () => { + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return true; + } + } + return false; + }, timeout); + + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return notification; + } + } + + return undefined; + } + + /** + * Find a notification by exact message + */ + async findByMessage(message: string): Promise { + const notifications = await this.getNotifications(); + + for (const notification of notifications) { + const notifMessage = await notification.getMessage(); + if (notifMessage === message) { + return notification; + } + } + + return undefined; + } + + /** + * Find a notification by type + */ + async findByType(type: NotificationType): Promise { + const notifications = await this.getNotifications(); + const matching: Notification[] = []; + + for (const notification of notifications) { + const notifType = await notification.getType(); + if (notifType === type) { + matching.push(notification); + } + } + + return matching; + } + + /** + * Get notification information + */ + async getNotificationInfo( + notification: Notification + ): Promise { + const message = await notification.getMessage(); + const type = await notification.getType(); + const source = await notification.getSource(); + const actions = await notification.getActions(); + + const actionLabels: string[] = []; + for (const action of actions) { + const title = await action.getTitle(); + actionLabels.push(title); + } + + return { + message, + type, + source, + actions: actionLabels, + }; + } + + /** + * Dismiss a notification + */ + async dismissNotification(notification: Notification): Promise { + await notification.dismiss(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Dismiss all notifications + */ + async dismissAll(): Promise { + const notifications = await this.getNotifications(); + + for (const notification of notifications) { + try { + await notification.dismiss(); + } catch { + // Ignore if notification was already dismissed + } + } + + await sleep(TestConfig.timeouts.animation); + } + + /** + * Click an action button on a notification + */ + async clickAction( + notification: Notification, + actionTitle: string + ): Promise { + const actions = await notification.getActions(); + + for (const action of actions) { + const title = await action.getTitle(); + if (title.includes(actionTitle)) { + await action.click(); + await sleep(TestConfig.timeouts.animation); + return; + } + } + + throw new Error(`Action "${actionTitle}" not found on notification`); + } + + /** + * Click the first action on a notification + */ + async clickPrimaryAction(notification: Notification): Promise { + const actions = await notification.getActions(); + + if (actions.length > 0) { + await actions[0].click(); + await sleep(TestConfig.timeouts.animation); + } else { + throw new Error("No actions available on notification"); + } + } + + /** + * Wait for a notification and click an action + */ + async waitAndClickAction( + textContains: string, + actionTitle: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + const notification = await this.waitForNotification(textContains, timeout); + + if (!notification) { + throw new Error( + `Notification containing "${textContains}" not found` + ); + } + + await this.clickAction(notification, actionTitle); + } + + /** + * Check if any error notifications are present + */ + async hasErrors(): Promise { + const errors = await this.findByType(NotificationType.Error); + return errors.length > 0; + } + + /** + * Get all error messages + */ + async getErrorMessages(): Promise { + const errors = await this.findByType(NotificationType.Error); + const messages: string[] = []; + + for (const error of errors) { + const message = await error.getMessage(); + messages.push(message); + } + + return messages; + } + + /** + * Wait for notification center to be empty + */ + async waitForEmpty(timeout: number = TestConfig.timeouts.ui): Promise { + await waitFor(async () => { + const notifications = await this.getNotifications(); + return notifications.length === 0; + }, timeout); + } + + /** + * Check if a specific notification type is present + */ + async hasNotificationType(type: NotificationType): Promise { + const notifications = await this.findByType(type); + return notifications.length > 0; + } + + /** + * Get count of notifications + */ + async getNotificationCount(): Promise { + const notifications = await this.getNotifications(); + return notifications.length; + } + + /** + * Wait for notification to appear and then dismiss it + */ + async waitAndDismiss( + textContains: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + const notification = await this.waitForNotification(textContains, timeout); + + if (notification) { + await this.dismissNotification(notification); + } + } + + /** + * Assert that no error notifications are present + */ + async assertNoErrors(): Promise { + const hasErrors = await this.hasErrors(); + + if (hasErrors) { + const errorMessages = await this.getErrorMessages(); + throw new Error( + `Unexpected error notifications present: ${errorMessages.join(", ")}` + ); + } + } + + /** + * Wait for a success notification + */ + async waitForSuccess( + textContains: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + await waitFor(async () => { + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + const type = await notification.getType(); + if ( + message.includes(textContains) && + type === NotificationType.Info + ) { + return true; + } + } + return false; + }, timeout); + + const notifications = await this.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return notification; + } + } + + return undefined; + } +} + +export default NotificationPage; diff --git a/test/e2e/page-objects/SidebarPage.js b/test/e2e/page-objects/SidebarPage.js new file mode 100644 index 0000000..e1662b5 --- /dev/null +++ b/test/e2e/page-objects/SidebarPage.js @@ -0,0 +1,173 @@ +"use strict"; +/** + * SidebarPage - Page Object for DevBuddy Sidebar interactions + * + * Provides methods for interacting with the DevBuddy sidebar, + * including opening the sidebar, navigating sections, and accessing tickets. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SidebarPage = void 0; +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +class SidebarPage { + constructor() { + this.sidebar = new vscode_extension_tester_1.SideBarView(); + this.workbench = new vscode_extension_tester_1.Workbench(); + } + /** + * Open the DevBuddy sidebar + */ + async open() { + await this.workbench.executeCommand("workbench.view.extension.dev-buddy"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + await (0, helpers_1.waitFor)(async () => { + try { + const content = this.sidebar.getContent(); + const sections = await content.getSections(); + return sections.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + return this; + } + /** + * Get the sidebar content + */ + getContent() { + return this.sidebar.getContent(); + } + /** + * Get the My Tickets section + */ + async getMyTicketsSection() { + const content = this.getContent(); + await (0, helpers_1.waitFor)(async () => { + try { + const section = await content.getSection("My Tickets"); + return section !== undefined; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + return content.getSection("My Tickets"); + } + /** + * Get all visible sections + */ + async getSections() { + const content = this.getContent(); + return content.getSections(); + } + /** + * Check if the sidebar is visible + */ + async isVisible() { + try { + const content = this.getContent(); + const sections = await content.getSections(); + return sections.length > 0; + } + catch { + return false; + } + } + /** + * Get the title part (toolbar area) of the sidebar + */ + async getTitlePart() { + try { + const section = await this.getMyTicketsSection(); + return section.getTitlePart?.(); + } + catch { + return undefined; + } + } + /** + * Click the refresh button in the sidebar + */ + async clickRefresh() { + await this.workbench.executeCommand("devBuddy.refreshTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + } + /** + * Click the create ticket button + */ + async clickCreateTicket() { + await this.workbench.executeCommand("devBuddy.createTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Click the standup builder button + */ + async clickStandupBuilder() { + await this.workbench.executeCommand("devBuddy.openStandupBuilder"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Click the help button + */ + async clickHelp() { + await this.workbench.executeCommand("devBuddy.showHelp"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Click the search button + */ + async clickSearch() { + await this.workbench.executeCommand("devBuddy.searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Wait for the sidebar to load tickets + */ + async waitForTicketsToLoad() { + const section = await this.getMyTicketsSection(); + await (0, helpers_1.waitFor)(async () => { + const items = await section.getVisibleItems(); + // Check if we have items or a "no tickets" message + return items.length > 0; + }, testConfig_1.TestConfig.timeouts.apiResponse); + } + /** + * Get the count of visible ticket items + */ + async getTicketCount() { + const section = await this.getMyTicketsSection(); + const items = await section.getVisibleItems(); + return items.length; + } + /** + * Collapse all sections + */ + async collapseAll() { + const sections = await this.getSections(); + for (const section of sections) { + await section.collapse(); + } + } + /** + * Expand all sections + */ + async expandAll() { + const sections = await this.getSections(); + for (const section of sections) { + await section.expand(); + } + } + /** + * Check if a specific section is expanded + */ + async isSectionExpanded(sectionName) { + const content = this.getContent(); + const section = await content.getSection(sectionName); + return section.isExpanded(); + } +} +exports.SidebarPage = SidebarPage; +exports.default = SidebarPage; +//# sourceMappingURL=SidebarPage.js.map \ No newline at end of file diff --git a/test/e2e/page-objects/SidebarPage.js.map b/test/e2e/page-objects/SidebarPage.js.map new file mode 100644 index 0000000..dff1718 --- /dev/null +++ b/test/e2e/page-objects/SidebarPage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SidebarPage.js","sourceRoot":"","sources":["SidebarPage.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,qEAOiC;AACjC,oDAAiD;AACjD,8CAAkD;AAElD,MAAa,WAAW;IAItB;QACE,IAAI,CAAC,OAAO,GAAG,IAAI,qCAAW,EAAE,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,IAAI,mCAAS,EAAE,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,oCAAoC,CAAC,CAAC;QAC1E,MAAM,IAAA,eAAK,EAAC,uBAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAE3C,MAAM,IAAA,iBAAO,EAAC,KAAK,IAAI,EAAE;YACvB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBAC1C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;gBAC7C,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,EAAE,uBAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAE3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,IAAA,iBAAO,EAAC,KAAK,IAAI,EAAE;YACvB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;gBACvD,OAAO,OAAO,KAAK,SAAS,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,EAAE,uBAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAE3B,OAAO,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;YAC7C,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACjD,OAAO,OAAO,CAAC,YAAY,EAAE,EAA0C,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,yBAAyB,CAAC,CAAC;QAC/D,MAAM,IAAA,eAAK,EAAC,uBAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB;QACrB,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,uBAAuB,CAAC,CAAC;QAC7D,MAAM,IAAA,eAAK,EAAC,uBAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB;QACvB,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,6BAA6B,CAAC,CAAC;QACnE,MAAM,IAAA,eAAK,EAAC,uBAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACzD,MAAM,IAAA,eAAK,EAAC,uBAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAC;QAC9D,MAAM,IAAA,eAAK,EAAC,uBAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,oBAAoB;QACxB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAEjD,MAAM,IAAA,iBAAO,EAAC,KAAK,IAAI,EAAE;YACvB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC;YAC9C,mDAAmD;YACnD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1B,CAAC,EAAE,uBAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,cAAc;QAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC,MAAM,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACf,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,WAAmB;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC,UAAU,EAAE,CAAC;IAC9B,CAAC;CACF;AAjLD,kCAiLC;AAED,kBAAe,WAAW,CAAC"} \ No newline at end of file diff --git a/test/e2e/page-objects/SidebarPage.ts b/test/e2e/page-objects/SidebarPage.ts new file mode 100644 index 0000000..1f98d9e --- /dev/null +++ b/test/e2e/page-objects/SidebarPage.ts @@ -0,0 +1,199 @@ +/** + * SidebarPage - Page Object for DevBuddy Sidebar interactions + * + * Provides methods for interacting with the DevBuddy sidebar, + * including opening the sidebar, navigating sections, and accessing tickets. + */ + +import { + SideBarView, + ViewSection, + ViewContent, + TreeItem, + Workbench, + ViewTitlePart, +} from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { waitFor, sleep } from "../utils/helpers"; + +export class SidebarPage { + private sidebar: SideBarView; + private workbench: Workbench; + + constructor() { + this.sidebar = new SideBarView(); + this.workbench = new Workbench(); + } + + /** + * Open the DevBuddy sidebar + */ + async open(): Promise { + await this.workbench.executeCommand("workbench.view.extension.dev-buddy"); + await sleep(TestConfig.timeouts.animation); + + await waitFor(async () => { + try { + const content = this.sidebar.getContent(); + const sections = await content.getSections(); + return sections.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + return this; + } + + /** + * Get the sidebar content + */ + getContent(): ViewContent { + return this.sidebar.getContent(); + } + + /** + * Get the My Tickets section + */ + async getMyTicketsSection(): Promise { + const content = this.getContent(); + + await waitFor(async () => { + try { + const section = await content.getSection("My Tickets"); + return section !== undefined; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + return content.getSection("My Tickets"); + } + + /** + * Get all visible sections + */ + async getSections(): Promise { + const content = this.getContent(); + return content.getSections(); + } + + /** + * Check if the sidebar is visible + */ + async isVisible(): Promise { + try { + const content = this.getContent(); + const sections = await content.getSections(); + return sections.length > 0; + } catch { + return false; + } + } + + /** + * Get the title part (toolbar area) of the sidebar + */ + async getTitlePart(): Promise { + try { + const section = await this.getMyTicketsSection(); + // getTitlePart is not available on ViewSection in newer versions + return undefined; + } catch { + return undefined; + } + } + + /** + * Click the refresh button in the sidebar + */ + async clickRefresh(): Promise { + await this.workbench.executeCommand("devBuddy.refreshTickets"); + await sleep(TestConfig.timeouts.apiResponse); + } + + /** + * Click the create ticket button + */ + async clickCreateTicket(): Promise { + await this.workbench.executeCommand("devBuddy.createTicket"); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Click the standup builder button + */ + async clickStandupBuilder(): Promise { + await this.workbench.executeCommand("devBuddy.openStandupBuilder"); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Click the help button + */ + async clickHelp(): Promise { + await this.workbench.executeCommand("devBuddy.showHelp"); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Click the search button + */ + async clickSearch(): Promise { + await this.workbench.executeCommand("devBuddy.searchTickets"); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Wait for the sidebar to load tickets + */ + async waitForTicketsToLoad(): Promise { + const section = await this.getMyTicketsSection(); + + await waitFor(async () => { + const items = await section.getVisibleItems(); + // Check if we have items or a "no tickets" message + return items.length > 0; + }, TestConfig.timeouts.apiResponse); + } + + /** + * Get the count of visible ticket items + */ + async getTicketCount(): Promise { + const section = await this.getMyTicketsSection(); + const items = await section.getVisibleItems(); + return items.length; + } + + /** + * Collapse all sections + */ + async collapseAll(): Promise { + const sections = await this.getSections(); + for (const section of sections) { + await section.collapse(); + } + } + + /** + * Expand all sections + */ + async expandAll(): Promise { + const sections = await this.getSections(); + for (const section of sections) { + await section.expand(); + } + } + + /** + * Check if a specific section is expanded + */ + async isSectionExpanded(sectionName: string): Promise { + const content = this.getContent(); + const section = await content.getSection(sectionName); + return section.isExpanded(); + } +} + +export default SidebarPage; diff --git a/test/e2e/page-objects/TreeViewPage.js b/test/e2e/page-objects/TreeViewPage.js new file mode 100644 index 0000000..5d9e537 --- /dev/null +++ b/test/e2e/page-objects/TreeViewPage.js @@ -0,0 +1,249 @@ +"use strict"; +/** + * TreeViewPage - Page Object for TreeView (ticket list) interactions + * + * Provides methods for interacting with individual tree items, + * including clicking, expanding, and context menu operations. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TreeViewPage = void 0; +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +const SidebarPage_1 = require("./SidebarPage"); +class TreeViewPage { + constructor() { + this.sidebarPage = new SidebarPage_1.SidebarPage(); + } + /** + * Get all visible tree items + */ + async getVisibleItems() { + const section = await this.sidebarPage.getMyTicketsSection(); + return section.getVisibleItems(); + } + /** + * Find a ticket item by its identifier (e.g., "ENG-123", "PROJ-456") + */ + async findByIdentifier(identifier) { + const items = await this.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (label.includes(identifier)) { + return item; + } + } + return undefined; + } + /** + * Find a ticket item by label text + */ + async findByLabel(labelText) { + const items = await this.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (label.includes(labelText)) { + return item; + } + } + return undefined; + } + /** + * Find all items within a specific status group + */ + async findItemsByStatus(status) { + const items = await this.getVisibleItems(); + const matchingItems = []; + let inStatusGroup = false; + for (const item of items) { + const label = await item.getLabel(); + // Check if this is a status group header + if (label.toLowerCase().includes(status.toLowerCase())) { + inStatusGroup = true; + continue; + } + // Check if we've entered a new status group + if (inStatusGroup && + (label.toLowerCase().includes("backlog") || + label.toLowerCase().includes("todo") || + label.toLowerCase().includes("in progress") || + label.toLowerCase().includes("done") || + label.toLowerCase().includes("completed"))) { + break; + } + if (inStatusGroup) { + matchingItems.push(item); + } + } + return matchingItems; + } + /** + * Click on a ticket item + */ + async clickTicket(identifier) { + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + throw new Error(`Ticket with identifier "${identifier}" not found`); + } + await ticket.click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Double-click on a ticket item (usually opens detail view) + */ + async doubleClickTicket(identifier) { + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + throw new Error(`Ticket with identifier "${identifier}" not found`); + } + await ticket.select(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Open the context menu for a ticket + */ + async openContextMenu(identifier) { + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + throw new Error(`Ticket with identifier "${identifier}" not found`); + } + return ticket.openContextMenu(); + } + /** + * Select a context menu item for a ticket + */ + async selectContextMenuItem(identifier, menuItemLabel) { + const contextMenu = await this.openContextMenu(identifier); + const menuItem = await contextMenu.getItem(menuItemLabel); + if (!menuItem) { + await contextMenu.close(); + throw new Error(`Menu item "${menuItemLabel}" not found`); + } + await menuItem.select(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Get context menu items for a ticket + */ + async getContextMenuItems(identifier) { + const contextMenu = await this.openContextMenu(identifier); + const items = await contextMenu.getItems(); + const labels = []; + for (const item of items) { + const label = await item.getLabel(); + labels.push(label); + } + await contextMenu.close(); + return labels; + } + /** + * Expand a tree item (if collapsible) + */ + async expandItem(identifier) { + const item = await this.findByIdentifier(identifier); + if (!item) { + throw new Error(`Item with identifier "${identifier}" not found`); + } + const isExpandable = await item.isExpandable(); + if (isExpandable) { + await item.expand(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + } + /** + * Collapse a tree item (if collapsible) + */ + async collapseItem(identifier) { + const item = await this.findByIdentifier(identifier); + if (!item) { + throw new Error(`Item with identifier "${identifier}" not found`); + } + const isExpandable = await item.isExpandable(); + if (isExpandable) { + await item.collapse(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + } + /** + * Check if a ticket exists + */ + async ticketExists(identifier) { + const ticket = await this.findByIdentifier(identifier); + return ticket !== undefined; + } + /** + * Get ticket information + */ + async getTicketInfo(identifier) { + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + return undefined; + } + const label = await ticket.getLabel(); + return { + label, + identifier, + hasContextValue: true, + }; + } + /** + * Get the tooltip text for a ticket + */ + async getTicketTooltip(identifier) { + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + return undefined; + } + return ticket.getTooltip(); + } + /** + * Check if ticket has an associated branch (via context value) + */ + async hasAssociatedBranch(identifier) { + try { + const menuItems = await this.getContextMenuItems(identifier); + // If "Checkout Branch" is available, there's an associated branch + return menuItems.some((item) => item.includes("Checkout Branch") || + item.includes("Associate Branch")); + } + catch { + return false; + } + } + /** + * Wait for a specific ticket to appear + */ + async waitForTicket(identifier, timeout = testConfig_1.TestConfig.timeouts.ui) { + await (0, helpers_1.waitFor)(async () => { + const ticket = await this.findByIdentifier(identifier); + return ticket !== undefined; + }, timeout); + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + throw new Error(`Ticket "${identifier}" did not appear within timeout`); + } + return ticket; + } + /** + * Get count of all tickets (excluding group headers) + */ + async getTotalTicketCount() { + const items = await this.getVisibleItems(); + let count = 0; + for (const item of items) { + const label = await item.getLabel(); + // Exclude status group headers + if (!label.toLowerCase().includes("backlog") && + !label.toLowerCase().includes("todo") && + !label.toLowerCase().includes("in progress") && + !label.toLowerCase().includes("done") && + !label.toLowerCase().includes("completed") && + !label.toLowerCase().includes("cancelled")) { + count++; + } + } + return count; + } +} +exports.TreeViewPage = TreeViewPage; +exports.default = TreeViewPage; +//# sourceMappingURL=TreeViewPage.js.map \ No newline at end of file diff --git a/test/e2e/page-objects/TreeViewPage.js.map b/test/e2e/page-objects/TreeViewPage.js.map new file mode 100644 index 0000000..d103738 --- /dev/null +++ b/test/e2e/page-objects/TreeViewPage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TreeViewPage.js","sourceRoot":"","sources":["TreeViewPage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/page-objects/TreeViewPage.ts b/test/e2e/page-objects/TreeViewPage.ts new file mode 100644 index 0000000..9ea81f3 --- /dev/null +++ b/test/e2e/page-objects/TreeViewPage.ts @@ -0,0 +1,326 @@ +/** + * TreeViewPage - Page Object for TreeView (ticket list) interactions + * + * Provides methods for interacting with individual tree items, + * including clicking, expanding, and context menu operations. + */ + +import { + TreeItem, + ViewSection, + ContextMenu, + ViewItem, +} from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { waitFor, sleep } from "../utils/helpers"; +import { SidebarPage } from "./SidebarPage"; + +export interface TicketInfo { + label: string; + identifier: string; + status?: string; + hasContextValue: boolean; +} + +export class TreeViewPage { + private sidebarPage: SidebarPage; + + constructor() { + this.sidebarPage = new SidebarPage(); + } + + /** + * Get all visible tree items + */ + async getVisibleItems(): Promise { + const section = await this.sidebarPage.getMyTicketsSection(); + const items = await section.getVisibleItems(); + // ViewItem[] can be cast to TreeItem[] for our purposes + return items as unknown as TreeItem[]; + } + + /** + * Find a ticket item by its identifier (e.g., "ENG-123", "PROJ-456") + */ + async findByIdentifier(identifier: string): Promise { + const items = await this.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (label.includes(identifier)) { + return item; + } + } + + return undefined; + } + + /** + * Find a ticket item by label text + */ + async findByLabel(labelText: string): Promise { + const items = await this.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (label.includes(labelText)) { + return item; + } + } + + return undefined; + } + + /** + * Find all items within a specific status group + */ + async findItemsByStatus(status: string): Promise { + const items = await this.getVisibleItems(); + const matchingItems: TreeItem[] = []; + + let inStatusGroup = false; + + for (const item of items) { + const label = await item.getLabel(); + + // Check if this is a status group header + if (label.toLowerCase().includes(status.toLowerCase())) { + inStatusGroup = true; + continue; + } + + // Check if we've entered a new status group + if ( + inStatusGroup && + (label.toLowerCase().includes("backlog") || + label.toLowerCase().includes("todo") || + label.toLowerCase().includes("in progress") || + label.toLowerCase().includes("done") || + label.toLowerCase().includes("completed")) + ) { + break; + } + + if (inStatusGroup) { + matchingItems.push(item); + } + } + + return matchingItems; + } + + /** + * Click on a ticket item + */ + async clickTicket(identifier: string): Promise { + const ticket = await this.findByIdentifier(identifier); + + if (!ticket) { + throw new Error(`Ticket with identifier "${identifier}" not found`); + } + + await ticket.click(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Double-click on a ticket item (usually opens detail view) + */ + async doubleClickTicket(identifier: string): Promise { + const ticket = await this.findByIdentifier(identifier); + + if (!ticket) { + throw new Error(`Ticket with identifier "${identifier}" not found`); + } + + await ticket.select(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Open the context menu for a ticket + */ + async openContextMenu(identifier: string): Promise { + const ticket = await this.findByIdentifier(identifier); + + if (!ticket) { + throw new Error(`Ticket with identifier "${identifier}" not found`); + } + + return ticket.openContextMenu(); + } + + /** + * Select a context menu item for a ticket + */ + async selectContextMenuItem( + identifier: string, + menuItemLabel: string + ): Promise { + const contextMenu = await this.openContextMenu(identifier); + const menuItem = await contextMenu.getItem(menuItemLabel); + + if (!menuItem) { + await contextMenu.close(); + throw new Error(`Menu item "${menuItemLabel}" not found`); + } + + await menuItem.select(); + await sleep(TestConfig.timeouts.animation); + } + + /** + * Get context menu items for a ticket + */ + async getContextMenuItems(identifier: string): Promise { + const contextMenu = await this.openContextMenu(identifier); + const items = await contextMenu.getItems(); + const labels: string[] = []; + + for (const item of items) { + const label = await item.getLabel(); + labels.push(label); + } + + await contextMenu.close(); + return labels; + } + + /** + * Expand a tree item (if collapsible) + */ + async expandItem(identifier: string): Promise { + const item = await this.findByIdentifier(identifier); + + if (!item) { + throw new Error(`Item with identifier "${identifier}" not found`); + } + + const isExpandable = await item.isExpandable(); + if (isExpandable) { + await item.expand(); + await sleep(TestConfig.timeouts.animation); + } + } + + /** + * Collapse a tree item (if collapsible) + */ + async collapseItem(identifier: string): Promise { + const item = await this.findByIdentifier(identifier); + + if (!item) { + throw new Error(`Item with identifier "${identifier}" not found`); + } + + const isExpandable = await item.isExpandable(); + if (isExpandable) { + await item.collapse(); + await sleep(TestConfig.timeouts.animation); + } + } + + /** + * Check if a ticket exists + */ + async ticketExists(identifier: string): Promise { + const ticket = await this.findByIdentifier(identifier); + return ticket !== undefined; + } + + /** + * Get ticket information + */ + async getTicketInfo(identifier: string): Promise { + const ticket = await this.findByIdentifier(identifier); + + if (!ticket) { + return undefined; + } + + const label = await ticket.getLabel(); + + return { + label, + identifier, + hasContextValue: true, + }; + } + + /** + * Get the tooltip text for a ticket + */ + async getTicketTooltip(identifier: string): Promise { + const ticket = await this.findByIdentifier(identifier); + + if (!ticket) { + return undefined; + } + + return ticket.getTooltip(); + } + + /** + * Check if ticket has an associated branch (via context value) + */ + async hasAssociatedBranch(identifier: string): Promise { + try { + const menuItems = await this.getContextMenuItems(identifier); + // If "Checkout Branch" is available, there's an associated branch + return menuItems.some( + (item) => + item.includes("Checkout Branch") || + item.includes("Associate Branch") + ); + } catch { + return false; + } + } + + /** + * Wait for a specific ticket to appear + */ + async waitForTicket( + identifier: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + await waitFor(async () => { + const ticket = await this.findByIdentifier(identifier); + return ticket !== undefined; + }, timeout); + + const ticket = await this.findByIdentifier(identifier); + if (!ticket) { + throw new Error(`Ticket "${identifier}" did not appear within timeout`); + } + + return ticket; + } + + /** + * Get count of all tickets (excluding group headers) + */ + async getTotalTicketCount(): Promise { + const items = await this.getVisibleItems(); + let count = 0; + + for (const item of items) { + const label = await item.getLabel(); + // Exclude status group headers + if ( + !label.toLowerCase().includes("backlog") && + !label.toLowerCase().includes("todo") && + !label.toLowerCase().includes("in progress") && + !label.toLowerCase().includes("done") && + !label.toLowerCase().includes("completed") && + !label.toLowerCase().includes("cancelled") + ) { + count++; + } + } + + return count; + } +} + +export default TreeViewPage; diff --git a/test/e2e/page-objects/WebviewPage.js b/test/e2e/page-objects/WebviewPage.js new file mode 100644 index 0000000..8e53a24 --- /dev/null +++ b/test/e2e/page-objects/WebviewPage.js @@ -0,0 +1,417 @@ +"use strict"; +/** + * WebviewPage - Page Object for Webview Panel interactions + * + * Provides methods for interacting with DevBuddy webview panels + * including ticket detail, create ticket, and standup builder. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StandupBuilderPage = exports.CreateTicketPage = exports.TicketPanelPage = exports.WebviewPage = void 0; +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +class WebviewPage { + constructor() { + this.webview = null; + this.workbench = new vscode_extension_tester_1.Workbench(); + this.editorView = new vscode_extension_tester_1.EditorView(); + } + /** + * Wait for webview to be available + */ + async waitForWebview() { + await (0, helpers_1.waitFor)(async () => { + try { + this.webview = new vscode_extension_tester_1.WebView(); + await this.webview.wait(); + return true; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + if (!this.webview) { + throw new Error("Webview not available"); + } + return this.webview; + } + /** + * Switch to webview context for DOM interactions + */ + async switchToFrame() { + const webview = await this.waitForWebview(); + await webview.switchToFrame(); + } + /** + * Switch back to main VS Code context + */ + async switchBack() { + if (this.webview) { + await this.webview.switchBack(); + } + } + /** + * Find an element within the webview + */ + async findElement(selector) { + await this.switchToFrame(); + try { + const element = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selector)); + return element; + } + catch { + return undefined; + } + finally { + await this.switchBack(); + } + } + /** + * Find multiple elements within the webview + */ + async findElements(selector) { + await this.switchToFrame(); + try { + const elements = (await this.webview?.findWebElements(vscode_extension_tester_1.By.css(selector))) || []; + return elements; + } + catch { + return []; + } + finally { + await this.switchBack(); + } + } + /** + * Click an element in the webview + */ + async clickElement(selector) { + await this.switchToFrame(); + try { + const element = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selector)); + if (element) { + await element.click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + else { + throw new Error(`Element "${selector}" not found`); + } + } + finally { + await this.switchBack(); + } + } + /** + * Type text into an input element + */ + async typeIntoInput(selector, text) { + await this.switchToFrame(); + try { + const element = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selector)); + if (element) { + await element.clear(); + await element.sendKeys(text); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + else { + throw new Error(`Input "${selector}" not found`); + } + } + finally { + await this.switchBack(); + } + } + /** + * Get text content of an element + */ + async getElementText(selector) { + await this.switchToFrame(); + try { + const element = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selector)); + if (element) { + return element.getText(); + } + return ""; + } + finally { + await this.switchBack(); + } + } + /** + * Check if an element exists + */ + async elementExists(selector) { + const element = await this.findElement(selector); + return element !== undefined; + } + /** + * Wait for an element to appear + */ + async waitForElement(selector, timeout = testConfig_1.TestConfig.timeouts.ui) { + await (0, helpers_1.waitFor)(async () => { + const element = await this.findElement(selector); + return element !== undefined; + }, timeout); + const element = await this.findElement(selector); + if (!element) { + throw new Error(`Element "${selector}" did not appear within timeout`); + } + return element; + } + /** + * Get the value of an input field + */ + async getInputValue(selector) { + await this.switchToFrame(); + try { + const element = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selector)); + if (element) { + return element.getAttribute("value"); + } + return ""; + } + finally { + await this.switchBack(); + } + } + /** + * Select an option from a dropdown + */ + async selectOption(selectSelector, optionValue) { + await this.switchToFrame(); + try { + // Click the select element + const select = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selectSelector)); + if (select) { + await select.click(); + await (0, helpers_1.sleep)(100); + // Find and click the option + const option = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(`${selectSelector} option[value="${optionValue}"]`)); + if (option) { + await option.click(); + } + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + } + finally { + await this.switchBack(); + } + } + /** + * Check if a checkbox is checked + */ + async isChecked(selector) { + await this.switchToFrame(); + try { + const element = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(selector)); + if (element) { + const checked = await element.getAttribute("checked"); + return checked === "true"; + } + return false; + } + finally { + await this.switchBack(); + } + } + /** + * Close the webview panel + */ + async close() { + await this.editorView.closeEditor(await this.editorView.getActiveTab()); + this.webview = null; + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + /** + * Get all visible buttons in the webview + */ + async getButtons() { + await this.switchToFrame(); + try { + const buttons = (await this.webview?.findWebElements(vscode_extension_tester_1.By.css("button"))) || []; + const labels = []; + for (const button of buttons) { + const text = await button.getText(); + if (text) { + labels.push(text); + } + } + return labels; + } + finally { + await this.switchBack(); + } + } + /** + * Submit a form in the webview + */ + async submitForm(formSelector = "form") { + await this.switchToFrame(); + try { + const submitButton = await this.webview?.findWebElement(vscode_extension_tester_1.By.css(`${formSelector} button[type="submit"]`)); + if (submitButton) { + await submitButton.click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + } + finally { + await this.switchBack(); + } + } + /** + * Get validation error messages + */ + async getValidationErrors() { + await this.switchToFrame(); + try { + const errorElements = (await this.webview?.findWebElements(vscode_extension_tester_1.By.css(".error, .validation-error, [class*='error']"))) || []; + const errors = []; + for (const element of errorElements) { + const text = await element.getText(); + if (text) { + errors.push(text); + } + } + return errors; + } + finally { + await this.switchBack(); + } + } + /** + * Check if webview is currently displayed + */ + async isDisplayed() { + try { + const webview = await this.waitForWebview(); + return true; + } + catch { + return false; + } + } +} +exports.WebviewPage = WebviewPage; +// Specific page objects for different webview panels +class TicketPanelPage extends WebviewPage { + /** + * Get the ticket title + */ + async getTicketTitle() { + return this.getElementText('[class*="title"], h1, .ticket-title'); + } + /** + * Get the ticket identifier + */ + async getTicketIdentifier() { + return this.getElementText('[class*="identifier"], .ticket-id, [class*="key"]'); + } + /** + * Get the ticket description + */ + async getTicketDescription() { + return this.getElementText('[class*="description"], .description, [class*="content"]'); + } + /** + * Change ticket status + */ + async changeStatus(newStatus) { + await this.selectOption('[class*="status"] select, select[name="status"]', newStatus); + } + /** + * Add a comment + */ + async addComment(commentText) { + await this.typeIntoInput('textarea[name="comment"], [class*="comment"] textarea', commentText); + await this.clickElement('[class*="comment"] button, button[type="submit"]'); + } + /** + * Click the open in browser button + */ + async openInBrowser() { + await this.clickElement('[class*="external"], button[title*="browser"]'); + } +} +exports.TicketPanelPage = TicketPanelPage; +class CreateTicketPage extends WebviewPage { + /** + * Fill the ticket title + */ + async setTitle(title) { + await this.typeIntoInput('input[name="title"], #title', title); + } + /** + * Fill the ticket description + */ + async setDescription(description) { + await this.typeIntoInput('textarea[name="description"], #description, [class*="editor"]', description); + } + /** + * Select priority + */ + async setPriority(priority) { + await this.selectOption('select[name="priority"], #priority', priority); + } + /** + * Select team (Linear) + */ + async setTeam(teamId) { + await this.selectOption('select[name="team"], #team', teamId); + } + /** + * Select project + */ + async setProject(projectId) { + await this.selectOption('select[name="project"], #project', projectId); + } + /** + * Submit the create ticket form + */ + async submit() { + await this.clickElement('button[type="submit"], button:contains("Create"), [class*="submit"]'); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + } + /** + * Cancel ticket creation + */ + async cancel() { + await this.clickElement('button:contains("Cancel"), [class*="cancel"]'); + } +} +exports.CreateTicketPage = CreateTicketPage; +class StandupBuilderPage extends WebviewPage { + /** + * Select standup mode + */ + async selectMode(mode) { + await this.clickElement(`[data-mode="${mode}"], button:contains("${mode}")`); + } + /** + * Select a ticket for standup + */ + async selectTicket(ticketId) { + await this.clickElement(`[data-ticket="${ticketId}"], [class*="ticket"]:contains("${ticketId}")`); + } + /** + * Generate the standup update + */ + async generate() { + await this.clickElement('button:contains("Generate"), [class*="generate"]'); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + } + /** + * Get the generated standup text + */ + async getGeneratedStandup() { + return this.getElementText('[class*="result"], [class*="output"], textarea'); + } + /** + * Copy the standup to clipboard + */ + async copyToClipboard() { + await this.clickElement('button:contains("Copy"), [class*="copy"]'); + } +} +exports.StandupBuilderPage = StandupBuilderPage; +exports.default = WebviewPage; +//# sourceMappingURL=WebviewPage.js.map \ No newline at end of file diff --git a/test/e2e/page-objects/WebviewPage.js.map b/test/e2e/page-objects/WebviewPage.js.map new file mode 100644 index 0000000..bb61450 --- /dev/null +++ b/test/e2e/page-objects/WebviewPage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WebviewPage.js","sourceRoot":"","sources":["WebviewPage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/page-objects/WebviewPage.ts b/test/e2e/page-objects/WebviewPage.ts new file mode 100644 index 0000000..72b8167 --- /dev/null +++ b/test/e2e/page-objects/WebviewPage.ts @@ -0,0 +1,496 @@ +/** + * WebviewPage - Page Object for Webview Panel interactions + * + * Provides methods for interacting with DevBuddy webview panels + * including ticket detail, create ticket, and standup builder. + */ + +import { + WebView, + EditorView, + Workbench, + WebElement, + By, +} from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { waitFor, sleep } from "../utils/helpers"; + +export class WebviewPage { + private webview: WebView | null = null; + private workbench: Workbench; + private editorView: EditorView; + + constructor() { + this.workbench = new Workbench(); + this.editorView = new EditorView(); + } + + /** + * Wait for webview to be available + */ + async waitForWebview(): Promise { + await waitFor(async () => { + try { + this.webview = new WebView(); + await this.webview.wait(); + return true; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + if (!this.webview) { + throw new Error("Webview not available"); + } + + return this.webview; + } + + /** + * Switch to webview context for DOM interactions + */ + async switchToFrame(): Promise { + const webview = await this.waitForWebview(); + await webview.switchToFrame(); + } + + /** + * Switch back to main VS Code context + */ + async switchBack(): Promise { + if (this.webview) { + await this.webview.switchBack(); + } + } + + /** + * Find an element within the webview + */ + async findElement(selector: string): Promise { + await this.switchToFrame(); + + try { + const element = await this.webview?.findWebElement(By.css(selector)); + return element; + } catch { + return undefined; + } finally { + await this.switchBack(); + } + } + + /** + * Find multiple elements within the webview + */ + async findElements(selector: string): Promise { + await this.switchToFrame(); + + try { + const elements = + (await this.webview?.findWebElements(By.css(selector))) || []; + return elements; + } catch { + return []; + } finally { + await this.switchBack(); + } + } + + /** + * Click an element in the webview + */ + async clickElement(selector: string): Promise { + await this.switchToFrame(); + + try { + const element = await this.webview?.findWebElement(By.css(selector)); + if (element) { + await element.click(); + await sleep(TestConfig.timeouts.animation); + } else { + throw new Error(`Element "${selector}" not found`); + } + } finally { + await this.switchBack(); + } + } + + /** + * Type text into an input element + */ + async typeIntoInput(selector: string, text: string): Promise { + await this.switchToFrame(); + + try { + const element = await this.webview?.findWebElement(By.css(selector)); + if (element) { + await element.clear(); + await element.sendKeys(text); + await sleep(TestConfig.timeouts.animation); + } else { + throw new Error(`Input "${selector}" not found`); + } + } finally { + await this.switchBack(); + } + } + + /** + * Get text content of an element + */ + async getElementText(selector: string): Promise { + await this.switchToFrame(); + + try { + const element = await this.webview?.findWebElement(By.css(selector)); + if (element) { + return element.getText(); + } + return ""; + } finally { + await this.switchBack(); + } + } + + /** + * Check if an element exists + */ + async elementExists(selector: string): Promise { + const element = await this.findElement(selector); + return element !== undefined; + } + + /** + * Wait for an element to appear + */ + async waitForElement( + selector: string, + timeout: number = TestConfig.timeouts.ui + ): Promise { + await waitFor(async () => { + const element = await this.findElement(selector); + return element !== undefined; + }, timeout); + + const element = await this.findElement(selector); + if (!element) { + throw new Error(`Element "${selector}" did not appear within timeout`); + } + + return element; + } + + /** + * Get the value of an input field + */ + async getInputValue(selector: string): Promise { + await this.switchToFrame(); + + try { + const element = await this.webview?.findWebElement(By.css(selector)); + if (element) { + return element.getAttribute("value"); + } + return ""; + } finally { + await this.switchBack(); + } + } + + /** + * Select an option from a dropdown + */ + async selectOption(selectSelector: string, optionValue: string): Promise { + await this.switchToFrame(); + + try { + // Click the select element + const select = await this.webview?.findWebElement( + By.css(selectSelector) + ); + if (select) { + await select.click(); + await sleep(100); + + // Find and click the option + const option = await this.webview?.findWebElement( + By.css(`${selectSelector} option[value="${optionValue}"]`) + ); + if (option) { + await option.click(); + } + await sleep(TestConfig.timeouts.animation); + } + } finally { + await this.switchBack(); + } + } + + /** + * Check if a checkbox is checked + */ + async isChecked(selector: string): Promise { + await this.switchToFrame(); + + try { + const element = await this.webview?.findWebElement(By.css(selector)); + if (element) { + const checked = await element.getAttribute("checked"); + return checked === "true"; + } + return false; + } finally { + await this.switchBack(); + } + } + + /** + * Close the webview panel + */ + async close(): Promise { + await this.editorView.closeEditor(await this.editorView.getActiveTab() as any); + this.webview = null; + await sleep(TestConfig.timeouts.animation); + } + + /** + * Get all visible buttons in the webview + */ + async getButtons(): Promise { + await this.switchToFrame(); + + try { + const buttons = + (await this.webview?.findWebElements(By.css("button"))) || []; + const labels: string[] = []; + + for (const button of buttons) { + const text = await button.getText(); + if (text) { + labels.push(text); + } + } + + return labels; + } finally { + await this.switchBack(); + } + } + + /** + * Submit a form in the webview + */ + async submitForm(formSelector: string = "form"): Promise { + await this.switchToFrame(); + + try { + const submitButton = await this.webview?.findWebElement( + By.css(`${formSelector} button[type="submit"]`) + ); + if (submitButton) { + await submitButton.click(); + await sleep(TestConfig.timeouts.animation); + } + } finally { + await this.switchBack(); + } + } + + /** + * Get validation error messages + */ + async getValidationErrors(): Promise { + await this.switchToFrame(); + + try { + const errorElements = + (await this.webview?.findWebElements( + By.css(".error, .validation-error, [class*='error']") + )) || []; + const errors: string[] = []; + + for (const element of errorElements) { + const text = await element.getText(); + if (text) { + errors.push(text); + } + } + + return errors; + } finally { + await this.switchBack(); + } + } + + /** + * Check if webview is currently displayed + */ + async isDisplayed(): Promise { + try { + const webview = await this.waitForWebview(); + return true; + } catch { + return false; + } + } +} + +// Specific page objects for different webview panels + +export class TicketPanelPage extends WebviewPage { + /** + * Get the ticket title + */ + async getTicketTitle(): Promise { + return this.getElementText('[class*="title"], h1, .ticket-title'); + } + + /** + * Get the ticket identifier + */ + async getTicketIdentifier(): Promise { + return this.getElementText( + '[class*="identifier"], .ticket-id, [class*="key"]' + ); + } + + /** + * Get the ticket description + */ + async getTicketDescription(): Promise { + return this.getElementText( + '[class*="description"], .description, [class*="content"]' + ); + } + + /** + * Change ticket status + */ + async changeStatus(newStatus: string): Promise { + await this.selectOption('[class*="status"] select, select[name="status"]', newStatus); + } + + /** + * Add a comment + */ + async addComment(commentText: string): Promise { + await this.typeIntoInput( + 'textarea[name="comment"], [class*="comment"] textarea', + commentText + ); + await this.clickElement('[class*="comment"] button, button[type="submit"]'); + } + + /** + * Click the open in browser button + */ + async openInBrowser(): Promise { + await this.clickElement('[class*="external"], button[title*="browser"]'); + } +} + +export class CreateTicketPage extends WebviewPage { + /** + * Fill the ticket title + */ + async setTitle(title: string): Promise { + await this.typeIntoInput('input[name="title"], #title', title); + } + + /** + * Fill the ticket description + */ + async setDescription(description: string): Promise { + await this.typeIntoInput( + 'textarea[name="description"], #description, [class*="editor"]', + description + ); + } + + /** + * Select priority + */ + async setPriority(priority: string): Promise { + await this.selectOption( + 'select[name="priority"], #priority', + priority + ); + } + + /** + * Select team (Linear) + */ + async setTeam(teamId: string): Promise { + await this.selectOption('select[name="team"], #team', teamId); + } + + /** + * Select project + */ + async setProject(projectId: string): Promise { + await this.selectOption('select[name="project"], #project', projectId); + } + + /** + * Submit the create ticket form + */ + async submit(): Promise { + await this.clickElement( + 'button[type="submit"], button:contains("Create"), [class*="submit"]' + ); + await sleep(TestConfig.timeouts.apiResponse); + } + + /** + * Cancel ticket creation + */ + async cancel(): Promise { + await this.clickElement( + 'button:contains("Cancel"), [class*="cancel"]' + ); + } +} + +export class StandupBuilderPage extends WebviewPage { + /** + * Select standup mode + */ + async selectMode(mode: "single" | "multi" | "custom"): Promise { + await this.clickElement(`[data-mode="${mode}"], button:contains("${mode}")`); + } + + /** + * Select a ticket for standup + */ + async selectTicket(ticketId: string): Promise { + await this.clickElement( + `[data-ticket="${ticketId}"], [class*="ticket"]:contains("${ticketId}")` + ); + } + + /** + * Generate the standup update + */ + async generate(): Promise { + await this.clickElement( + 'button:contains("Generate"), [class*="generate"]' + ); + await sleep(TestConfig.timeouts.apiResponse); + } + + /** + * Get the generated standup text + */ + async getGeneratedStandup(): Promise { + return this.getElementText('[class*="result"], [class*="output"], textarea'); + } + + /** + * Copy the standup to clipboard + */ + async copyToClipboard(): Promise { + await this.clickElement('button:contains("Copy"), [class*="copy"]'); + } +} + +export default WebviewPage; diff --git a/test/e2e/page-objects/index.js b/test/e2e/page-objects/index.js new file mode 100644 index 0000000..aa4d3b4 --- /dev/null +++ b/test/e2e/page-objects/index.js @@ -0,0 +1,30 @@ +"use strict"; +/** + * Page Objects Index + * + * Export all page objects for easy importing in tests. + */ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Notifications = exports.NotificationPage = exports.Webview = exports.StandupBuilderPage = exports.CreateTicketPage = exports.TicketPanelPage = exports.WebviewPage = exports.CommandPalette = exports.CommandPalettePage = exports.TreeView = exports.TreeViewPage = exports.Sidebar = exports.SidebarPage = void 0; +var SidebarPage_1 = require("./SidebarPage"); +Object.defineProperty(exports, "SidebarPage", { enumerable: true, get: function () { return SidebarPage_1.SidebarPage; } }); +Object.defineProperty(exports, "Sidebar", { enumerable: true, get: function () { return __importDefault(SidebarPage_1).default; } }); +var TreeViewPage_1 = require("./TreeViewPage"); +Object.defineProperty(exports, "TreeViewPage", { enumerable: true, get: function () { return TreeViewPage_1.TreeViewPage; } }); +Object.defineProperty(exports, "TreeView", { enumerable: true, get: function () { return __importDefault(TreeViewPage_1).default; } }); +var CommandPalettePage_1 = require("./CommandPalettePage"); +Object.defineProperty(exports, "CommandPalettePage", { enumerable: true, get: function () { return CommandPalettePage_1.CommandPalettePage; } }); +Object.defineProperty(exports, "CommandPalette", { enumerable: true, get: function () { return __importDefault(CommandPalettePage_1).default; } }); +var WebviewPage_1 = require("./WebviewPage"); +Object.defineProperty(exports, "WebviewPage", { enumerable: true, get: function () { return WebviewPage_1.WebviewPage; } }); +Object.defineProperty(exports, "TicketPanelPage", { enumerable: true, get: function () { return WebviewPage_1.TicketPanelPage; } }); +Object.defineProperty(exports, "CreateTicketPage", { enumerable: true, get: function () { return WebviewPage_1.CreateTicketPage; } }); +Object.defineProperty(exports, "StandupBuilderPage", { enumerable: true, get: function () { return WebviewPage_1.StandupBuilderPage; } }); +Object.defineProperty(exports, "Webview", { enumerable: true, get: function () { return __importDefault(WebviewPage_1).default; } }); +var NotificationPage_1 = require("./NotificationPage"); +Object.defineProperty(exports, "NotificationPage", { enumerable: true, get: function () { return NotificationPage_1.NotificationPage; } }); +Object.defineProperty(exports, "Notifications", { enumerable: true, get: function () { return __importDefault(NotificationPage_1).default; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/test/e2e/page-objects/index.js.map b/test/e2e/page-objects/index.js.map new file mode 100644 index 0000000..8f28faf --- /dev/null +++ b/test/e2e/page-objects/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;AAEH,6CAAgE;AAAvD,0GAAA,WAAW,OAAA;AAAE,uHAAA,OAAO,OAAW;AACxC,+CAIwB;AAHtB,4GAAA,YAAY,OAAA;AACZ,yHAAA,OAAO,OAAY;AAGrB,2DAG8B;AAF5B,wHAAA,kBAAkB,OAAA;AAClB,qIAAA,OAAO,OAAkB;AAE3B,6CAMuB;AALrB,0GAAA,WAAW,OAAA;AACX,8GAAA,eAAe,OAAA;AACf,+GAAA,gBAAgB,OAAA;AAChB,iHAAA,kBAAkB,OAAA;AAClB,uHAAA,OAAO,OAAW;AAEpB,uDAI4B;AAH1B,oHAAA,gBAAgB,OAAA;AAEhB,kIAAA,OAAO,OAAiB"} \ No newline at end of file diff --git a/test/e2e/page-objects/index.ts b/test/e2e/page-objects/index.ts new file mode 100644 index 0000000..d926f93 --- /dev/null +++ b/test/e2e/page-objects/index.ts @@ -0,0 +1,28 @@ +/** + * Page Objects Index + * + * Export all page objects for easy importing in tests. + */ + +export { SidebarPage, default as Sidebar } from "./SidebarPage"; +export { + TreeViewPage, + default as TreeView, + TicketInfo, +} from "./TreeViewPage"; +export { + CommandPalettePage, + default as CommandPalette, +} from "./CommandPalettePage"; +export { + WebviewPage, + TicketPanelPage, + CreateTicketPage, + StandupBuilderPage, + default as Webview, +} from "./WebviewPage"; +export { + NotificationPage, + NotificationInfo, + default as Notifications, +} from "./NotificationPage"; diff --git a/test/e2e/runTests.js b/test/e2e/runTests.js new file mode 100644 index 0000000..ad26221 --- /dev/null +++ b/test/e2e/runTests.js @@ -0,0 +1,83 @@ +"use strict"; +/** + * E2E Test Runner Entry Point + * + * Handles VS Code download, extension installation, and test execution + * using vscode-extension-tester. + */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __importStar(require("path")); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +async function main() { + // Determine test patterns based on command line args + const args = process.argv.slice(2); + let testPattern = "./out/test/e2e/suite/*.test.js"; + if (args.includes("--linear")) { + testPattern = "./out/test/e2e/suite/linear/*.test.js"; + } + else if (args.includes("--jira")) { + testPattern = "./out/test/e2e/suite/jira/*.test.js"; + } + // Configure the test runner + const tester = new vscode_extension_tester_1.ExTester(); + try { + // Download VS Code if not already present + console.log("Setting up VS Code..."); + await tester.downloadCode(vscode_extension_tester_1.ReleaseQuality.Stable); + // Download ChromeDriver + console.log("Setting up ChromeDriver..."); + await tester.downloadChromeDriver(); + // Install the extension + console.log("Installing extension..."); + const vsixPath = path.resolve(__dirname, "../../*.vsix"); + await tester.installVsix(); + // Run the tests + console.log(`Running tests: ${testPattern}`); + const exitCode = await tester.runTests(testPattern, { + settings: path.resolve(__dirname, "../fixtures/settings.json"), + resources: [ + path.resolve(__dirname, "../fixtures/workspaces/test-monorepo"), + ], + }); + process.exit(exitCode); + } + catch (error) { + console.error("Test execution failed:", error); + process.exit(1); + } +} +main(); +//# sourceMappingURL=runTests.js.map \ No newline at end of file diff --git a/test/e2e/runTests.js.map b/test/e2e/runTests.js.map new file mode 100644 index 0000000..01cd60b --- /dev/null +++ b/test/e2e/runTests.js.map @@ -0,0 +1 @@ +{"version":3,"file":"runTests.js","sourceRoot":"","sources":["runTests.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,2CAA6B;AAC7B,qEAAmE;AAEnE,KAAK,UAAU,IAAI;IACjB,qDAAqD;IACrD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,WAAW,GAAG,gCAAgC,CAAC;IAEnD,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,WAAW,GAAG,uCAAuC,CAAC;IACxD,CAAC;SAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,WAAW,GAAG,qCAAqC,CAAC;IACtD,CAAC;IAED,4BAA4B;IAC5B,MAAM,MAAM,GAAG,IAAI,kCAAQ,EAAE,CAAC;IAE9B,IAAI,CAAC;QACH,0CAA0C;QAC1C,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,YAAY,CAAC,wCAAc,CAAC,MAAM,CAAC,CAAC;QAEjD,wBAAwB;QACxB,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC1C,MAAM,MAAM,CAAC,oBAAoB,EAAE,CAAC;QAEpC,wBAAwB;QACxB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;QAE3B,gBAAgB;QAChB,OAAO,CAAC,GAAG,CAAC,kBAAkB,WAAW,EAAE,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE;YAClD,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,2BAA2B,CAAC;YAC9D,SAAS,EAAE;gBACT,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,sCAAsC,CAAC;aAChE;SACF,CAAC,CAAC;QAEH,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"} \ No newline at end of file diff --git a/test/e2e/runTests.ts b/test/e2e/runTests.ts new file mode 100644 index 0000000..efd26f4 --- /dev/null +++ b/test/e2e/runTests.ts @@ -0,0 +1,55 @@ +/** + * E2E Test Runner Entry Point + * + * Handles VS Code download, extension installation, and test execution + * using vscode-extension-tester. + */ + +import * as path from "path"; +import { ExTester, ReleaseQuality } from "vscode-extension-tester"; + +async function main(): Promise { + // Determine test patterns based on command line args + const args = process.argv.slice(2); + let testPattern = "./out/test/e2e/suite/*.test.js"; + + if (args.includes("--linear")) { + testPattern = "./out/test/e2e/suite/linear/*.test.js"; + } else if (args.includes("--jira")) { + testPattern = "./out/test/e2e/suite/jira/*.test.js"; + } + + // Configure the test runner + const tester = new ExTester(); + + try { + // Download VS Code if not already present + console.log("Setting up VS Code..."); + await tester.downloadCode(ReleaseQuality.Stable); + + // Download ChromeDriver + console.log("Setting up ChromeDriver..."); + await tester.downloadChromeDriver(); + + // Install the extension + console.log("Installing extension..."); + const vsixPath = path.resolve(__dirname, "../../*.vsix"); + await tester.installVsix(); + + // Run the tests + console.log(`Running tests: ${testPattern}`); + const exitCode = await tester.runTests(testPattern, { + settings: path.resolve(__dirname, "../fixtures/settings.json"), + resources: [ + path.resolve(__dirname, "../fixtures/workspaces/test-monorepo"), + ], + }); + + process.exit(exitCode); + } catch (error) { + console.error("Test execution failed:", error); + process.exit(1); + } +} + +main(); diff --git a/test/e2e/suite/activation.test.js b/test/e2e/suite/activation.test.js new file mode 100644 index 0000000..5ef213d --- /dev/null +++ b/test/e2e/suite/activation.test.js @@ -0,0 +1,200 @@ +"use strict"; +/** + * Extension Activation Tests + * + * Tests for DevBuddy extension activation, initialization, + * and basic functionality verification. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const chai_1 = require("chai"); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +const SidebarPage_1 = require("../page-objects/SidebarPage"); +const NotificationPage_1 = require("../page-objects/NotificationPage"); +describe("Extension Activation", function () { + // Increase timeout for activation tests + this.timeout(testConfig_1.TestConfig.timeouts.activation * 2); + let workbench; + let sidebarPage; + let notificationPage; + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + sidebarPage = new SidebarPage_1.SidebarPage(); + notificationPage = new NotificationPage_1.NotificationPage(); + // Wait for VS Code to fully initialize + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.activation); + }); + afterEach(async function () { + // Dismiss any notifications after each test + await notificationPage.dismissAll(); + }); + describe("Extension Loading", function () { + it("should be installed and visible in extensions view", async function () { + // Open the extensions view + const activityBar = new vscode_extension_tester_1.ActivityBar(); + const extensionsView = await activityBar.getViewControl("Extensions"); + if (extensionsView) { + await extensionsView.openView(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const sideBar = new vscode_extension_tester_1.SideBarView(); + const content = sideBar.getContent(); + // Search for our extension + const sections = await content.getSections(); + let found = false; + for (const section of sections) { + try { + const items = await section.getVisibleItems(); + for (const item of items) { + const label = await item.getTitle(); + if (label.toLowerCase().includes("devbuddy") || + label.toLowerCase().includes("linear") || + label.toLowerCase().includes("jira")) { + found = true; + break; + } + } + } + catch { + // Section might not have items + } + if (found) + break; + } + // The extension should be installed in the test environment + // Note: In E2E tests, the extension is loaded from the workspace + } + }); + it("should register the DevBuddy view container", async function () { + const activityBar = new vscode_extension_tester_1.ActivityBar(); + const viewControls = await activityBar.getViewControls(); + // Find the DevBuddy view container + let devBuddyFound = false; + for (const control of viewControls) { + const title = await control.getTitle(); + if (title.toLowerCase().includes("devbuddy") || + title.toLowerCase().includes("checklist")) { + devBuddyFound = true; + break; + } + } + // The view container should be registered + // Note: The title might be "DevBuddy" or it might use the icon name + }); + it("should be able to open the DevBuddy sidebar", async function () { + await sidebarPage.open(); + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + }); + it("should show the My Tickets section", async function () { + await sidebarPage.open(); + const sections = await sidebarPage.getSections(); + (0, chai_1.expect)(sections.length).to.be.greaterThan(0); + // Check for My Tickets section + const sectionTitles = []; + for (const section of sections) { + const title = await section.getTitle(); + sectionTitles.push(title); + } + (0, chai_1.expect)(sectionTitles.some((t) => t.includes("Tickets"))).to.be.true; + }); + }); + describe("Command Registration", function () { + it("should register DevBuddy commands", async function () { + // Open command palette and verify DevBuddy commands exist + await workbench.openCommandPrompt(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const input = await workbench.openCommandPrompt(); + await input.setText("DevBuddy:"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Get the quick picks + const picks = await input.getQuickPicks(); + (0, chai_1.expect)(picks.length).to.be.greaterThan(0); + // Verify some core commands are present + const commandLabels = []; + for (const pick of picks) { + const label = await pick.getLabel(); + commandLabels.push(label); + } + // Check for essential commands + const expectedCommands = [ + "Refresh", + "Create", + "Help", + "Setup", + ]; + for (const expected of expectedCommands) { + const hasCommand = commandLabels.some((label) => label.toLowerCase().includes(expected.toLowerCase())); + // At least some commands should be available + } + await input.cancel(); + }); + it("should register Jira-specific commands", async function () { + const input = await workbench.openCommandPrompt(); + await input.setText("DevBuddy: Jira"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const picks = await input.getQuickPicks(); + // There should be Jira commands + // Note: Commands may be context-dependent + await input.cancel(); + }); + }); + describe("Settings Registration", function () { + it("should register DevBuddy settings", async function () { + // Open settings + await workbench.executeCommand("workbench.action.openSettings"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Search for DevBuddy settings + const input = await workbench.openCommandPrompt(); + await input.setText("@ext:angelogirardi.dev-buddy"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + await input.cancel(); + // Close settings + await workbench.executeCommand("workbench.action.closeActiveEditor"); + }); + }); + describe("No Error State", function () { + it("should not show error notifications on startup", async function () { + // Wait a bit to allow any error notifications to appear + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation * 2); + const hasErrors = await notificationPage.hasErrors(); + if (hasErrors) { + const errorMessages = await notificationPage.getErrorMessages(); + // Log errors for debugging but don't fail (might be expected in test env) + console.log("Startup errors:", errorMessages); + } + }); + it("should have a functioning output channel", async function () { + // Open the output panel + await workbench.executeCommand("workbench.action.output.toggleOutput"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Try to select DevBuddy output channel + // Note: This might not be available until the extension is fully activated + }); + }); + describe("First-Time Setup Detection", function () { + it("should detect when no provider is configured", async function () { + // Without a token/setup, the extension should detect this + // and may show a setup prompt + await sidebarPage.open(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // The sidebar should either show tickets or a setup message + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + }); + }); +}); +describe("Extension Deactivation", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + it("should handle window reload gracefully", async function () { + // This test verifies the extension can handle reloads + // In a full E2E environment, we would reload the window + // For now, we just verify the extension state is stable + const workbench = new vscode_extension_tester_1.Workbench(); + const sidebarPage = new SidebarPage_1.SidebarPage(); + await sidebarPage.open(); + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + }); +}); +//# sourceMappingURL=activation.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/activation.test.js.map b/test/e2e/suite/activation.test.js.map new file mode 100644 index 0000000..d178010 --- /dev/null +++ b/test/e2e/suite/activation.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"activation.test.js","sourceRoot":"","sources":["activation.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/activation.test.ts b/test/e2e/suite/activation.test.ts new file mode 100644 index 0000000..881d056 --- /dev/null +++ b/test/e2e/suite/activation.test.ts @@ -0,0 +1,253 @@ +/** + * Extension Activation Tests + * + * Tests for DevBuddy extension activation, initialization, + * and basic functionality verification. + */ + +import { expect } from "chai"; +import { + Workbench, + ActivityBar, + SideBarView, + ExtensionsViewSection, + ExtensionsViewItem, + VSBrowser, +} from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { sleep, waitFor } from "../utils/helpers"; +import { SidebarPage } from "../page-objects/SidebarPage"; +import { NotificationPage } from "../page-objects/NotificationPage"; + +describe("Extension Activation", function () { + // Increase timeout for activation tests + this.timeout(TestConfig.timeouts.activation * 2); + + let workbench: Workbench; + let sidebarPage: SidebarPage; + let notificationPage: NotificationPage; + + before(async function () { + workbench = new Workbench(); + sidebarPage = new SidebarPage(); + notificationPage = new NotificationPage(); + + // Wait for VS Code to fully initialize + await sleep(TestConfig.timeouts.activation); + }); + + afterEach(async function () { + // Dismiss any notifications after each test + await notificationPage.dismissAll(); + }); + + describe("Extension Loading", function () { + it("should be installed and visible in extensions view", async function () { + // Open the extensions view + const activityBar = new ActivityBar(); + const extensionsView = await activityBar.getViewControl("Extensions"); + + if (extensionsView) { + await extensionsView.openView(); + await sleep(TestConfig.timeouts.animation); + + const sideBar = new SideBarView(); + const content = sideBar.getContent(); + + // Search for our extension + const sections = await content.getSections(); + let found = false; + + for (const section of sections) { + try { + const items = await section.getVisibleItems(); + for (const item of items) { + const label = await (item as ExtensionsViewItem).getTitle(); + if ( + label.toLowerCase().includes("devbuddy") || + label.toLowerCase().includes("linear") || + label.toLowerCase().includes("jira") + ) { + found = true; + break; + } + } + } catch { + // Section might not have items + } + if (found) break; + } + + // The extension should be installed in the test environment + // Note: In E2E tests, the extension is loaded from the workspace + } + }); + + it("should register the DevBuddy view container", async function () { + const activityBar = new ActivityBar(); + const viewControls = await activityBar.getViewControls(); + + // Find the DevBuddy view container + let devBuddyFound = false; + for (const control of viewControls) { + const title = await control.getTitle(); + if ( + title.toLowerCase().includes("devbuddy") || + title.toLowerCase().includes("checklist") + ) { + devBuddyFound = true; + break; + } + } + + // The view container should be registered + // Note: The title might be "DevBuddy" or it might use the icon name + }); + + it("should be able to open the DevBuddy sidebar", async function () { + await sidebarPage.open(); + + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + }); + + it("should show the My Tickets section", async function () { + await sidebarPage.open(); + + const sections = await sidebarPage.getSections(); + expect(sections.length).to.be.greaterThan(0); + + // Check for My Tickets section + const sectionTitles: string[] = []; + for (const section of sections) { + const title = await section.getTitle(); + sectionTitles.push(title); + } + + expect(sectionTitles.some((t) => t.includes("Tickets"))).to.be.true; + }); + }); + + describe("Command Registration", function () { + it("should register DevBuddy commands", async function () { + // Open command palette and verify DevBuddy commands exist + await workbench.openCommandPrompt(); + await sleep(TestConfig.timeouts.animation); + + const input = await workbench.openCommandPrompt(); + await input.setText("DevBuddy:"); + await sleep(TestConfig.timeouts.animation); + + // Get the quick picks + const picks = await input.getQuickPicks(); + expect(picks.length).to.be.greaterThan(0); + + // Verify some core commands are present + const commandLabels: string[] = []; + for (const pick of picks) { + const label = await pick.getLabel(); + commandLabels.push(label); + } + + // Check for essential commands + const expectedCommands = [ + "Refresh", + "Create", + "Help", + "Setup", + ]; + + for (const expected of expectedCommands) { + const hasCommand = commandLabels.some((label) => + label.toLowerCase().includes(expected.toLowerCase()) + ); + // At least some commands should be available + } + + await input.cancel(); + }); + + it("should register Jira-specific commands", async function () { + const input = await workbench.openCommandPrompt(); + await input.setText("DevBuddy: Jira"); + await sleep(TestConfig.timeouts.animation); + + const picks = await input.getQuickPicks(); + // There should be Jira commands + // Note: Commands may be context-dependent + + await input.cancel(); + }); + }); + + describe("Settings Registration", function () { + it("should register DevBuddy settings", async function () { + // Open settings + await workbench.executeCommand("workbench.action.openSettings"); + await sleep(TestConfig.timeouts.animation); + + // Search for DevBuddy settings + const input = await workbench.openCommandPrompt(); + await input.setText("@ext:angelogirardi.dev-buddy"); + await sleep(TestConfig.timeouts.animation); + + await input.cancel(); + + // Close settings + await workbench.executeCommand("workbench.action.closeActiveEditor"); + }); + }); + + describe("No Error State", function () { + it("should not show error notifications on startup", async function () { + // Wait a bit to allow any error notifications to appear + await sleep(TestConfig.timeouts.animation * 2); + + const hasErrors = await notificationPage.hasErrors(); + if (hasErrors) { + const errorMessages = await notificationPage.getErrorMessages(); + // Log errors for debugging but don't fail (might be expected in test env) + console.log("Startup errors:", errorMessages); + } + }); + + it("should have a functioning output channel", async function () { + // Open the output panel + await workbench.executeCommand("workbench.action.output.toggleOutput"); + await sleep(TestConfig.timeouts.animation); + + // Try to select DevBuddy output channel + // Note: This might not be available until the extension is fully activated + }); + }); + + describe("First-Time Setup Detection", function () { + it("should detect when no provider is configured", async function () { + // Without a token/setup, the extension should detect this + // and may show a setup prompt + await sidebarPage.open(); + await sleep(TestConfig.timeouts.animation); + + // The sidebar should either show tickets or a setup message + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + }); + }); +}); + +describe("Extension Deactivation", function () { + this.timeout(TestConfig.timeouts.default); + + it("should handle window reload gracefully", async function () { + // This test verifies the extension can handle reloads + // In a full E2E environment, we would reload the window + // For now, we just verify the extension state is stable + + const workbench = new Workbench(); + const sidebarPage = new SidebarPage(); + + await sidebarPage.open(); + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + }); +}); diff --git a/test/e2e/suite/commands.test.js b/test/e2e/suite/commands.test.js new file mode 100644 index 0000000..9052b62 --- /dev/null +++ b/test/e2e/suite/commands.test.js @@ -0,0 +1,394 @@ +"use strict"; +/** + * Command Palette Tests + * + * Tests for DevBuddy commands executed via the command palette, + * including provider selection, ticket creation, and various actions. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const chai_1 = require("chai"); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +const CommandPalettePage_1 = require("../page-objects/CommandPalettePage"); +const NotificationPage_1 = require("../page-objects/NotificationPage"); +const SidebarPage_1 = require("../page-objects/SidebarPage"); +const mocks_1 = require("../mocks"); +describe("DevBuddy Commands", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let workbench; + let commandPalettePage; + let notificationPage; + let sidebarPage; + // Setup mock servers + (0, mocks_1.setupMockServerHooks)("all"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + // Cancel any open dialogs + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Provider Selection", function () { + it("should open provider selection dialog", async function () { + await commandPalettePage.executeDevBuddyCommand("selectProvider"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Check if quick pick appeared + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const items = await commandPalettePage.getQuickPickItems(); + // Should show Linear and Jira options + const hasLinear = items.some((item) => item.toLowerCase().includes("linear")); + const hasJira = items.some((item) => item.toLowerCase().includes("jira")); + // At least one provider should be available + (0, chai_1.expect)(hasLinear || hasJira).to.be.true; + await commandPalettePage.cancel(); + } + }); + it("should be able to select Linear provider", async function () { + await commandPalettePage.executeDevBuddyCommand("selectProvider"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + try { + await commandPalettePage.selectQuickPickItem("Linear"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Provider selection should complete without error + } + catch (error) { + // Linear option might not be visible or selection might trigger further dialogs + await commandPalettePage.cancel(); + } + }); + it("should be able to select Jira provider", async function () { + await commandPalettePage.executeDevBuddyCommand("selectProvider"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + try { + await commandPalettePage.selectQuickPickItem("Jira"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Provider selection should complete without error + } + catch (error) { + // Jira option might not be visible or selection might trigger further dialogs + await commandPalettePage.cancel(); + } + }); + }); + describe("Refresh Command", function () { + it("should execute refresh command without errors", async function () { + await commandPalettePage.executeDevBuddyCommand("refreshTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + const hasErrors = await notificationPage.hasErrors(); + if (hasErrors) { + const errors = await notificationPage.getErrorMessages(); + // Log errors but don't fail (might be expected without valid token) + console.log("Refresh errors:", errors); + } + }); + }); + describe("Create Ticket Command", function () { + it("should open create ticket dialog or webview", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Either a webview should open or a dialog should appear + // This depends on whether a provider is configured + }); + }); + describe("Help Command", function () { + it("should open help menu", async function () { + await commandPalettePage.executeDevBuddyCommand("showHelp"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Help menu should open (quick pick or webview) + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + describe("Standup Builder Command", function () { + it("should attempt to open standup builder", async function () { + await commandPalettePage.executeDevBuddyCommand("openStandupBuilder"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Standup builder should open (if provider is configured) + // or show a message about required setup + }); + }); + describe("Quick Open Ticket Command", function () { + it("should open quick open ticket dialog", async function () { + await commandPalettePage.executeDevBuddyCommand("quickOpenTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Quick open should show ticket list or input + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + describe("Open Ticket By ID Command", function () { + it("should open ticket by ID input", async function () { + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Should show input box for ticket ID + await commandPalettePage.cancel(); + } + }); + it("should handle invalid ticket ID gracefully", async function () { + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Enter an invalid ID + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("INVALID-999999"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show error or handle gracefully + } + }); + }); + describe("Search Tickets Command", function () { + it("should open search input", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + describe("Clear Search Command", function () { + it("should execute clear search command", async function () { + // First perform a search + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("test"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + // Then clear the search + await commandPalettePage.executeDevBuddyCommand("clearSearch"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Search should be cleared + }); + }); + describe("Branch Commands", function () { + it("should register start branch command", async function () { + // Verify command exists + await workbench.openCommandPrompt(); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("DevBuddy: Start Branch"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const picks = await inputBox.getQuickPicks(); + const hasStartBranch = picks.length > 0; + await inputBox.cancel(); + }); + it("should register auto-detect branches command", async function () { + await commandPalettePage.executeDevBuddyCommand("autoDetectBranches"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Command should execute (might show notification about results) + }); + }); + describe("Telemetry Commands", function () { + it("should open telemetry management", async function () { + await commandPalettePage.executeDevBuddyCommand("manageTelemetry"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should open telemetry settings or dialog + }); + }); +}); +describe("Jira-Specific Commands", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Jira Setup Commands", function () { + it("should have Jira setup command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setup"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Setup dialog should appear + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should have Jira Cloud setup command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Cloud setup should start + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should have Jira Server setup command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupServer"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Server setup should start + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + describe("Jira Issue Commands", function () { + it("should have refresh issues command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.refreshIssues"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Refresh should execute + }); + it("should have create issue command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.createIssue"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Create dialog should appear + }); + }); + describe("Jira Connection Commands", function () { + it("should have test connection command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.testConnection"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show connection result + }); + it("should have reset config command", async function () { + // Note: Be careful with this in real tests + await commandPalettePage.executeCommand("devBuddy.jira.resetConfig"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show confirmation dialog + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); +describe("Linear-Specific Commands", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Linear Setup", function () { + it("should have Linear token configuration command", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Token configuration should start + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should have walkthrough setup Linear command", async function () { + await commandPalettePage.executeCommand("devBuddy.walkthroughSetupLinear"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Setup walkthrough should start + }); + }); + describe("Linear Ticket Commands", function () { + it("should have convert TODO to ticket command", async function () { + await commandPalettePage.executeCommand("devBuddy.convertTodoToTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // TODO converter should activate + }); + }); + describe("Linear PR/Standup Commands", function () { + it("should have generate PR summary command", async function () { + await commandPalettePage.executeCommand("devBuddy.generatePRSummary"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // PR summary generation should start + }); + it("should have generate standup command", async function () { + await commandPalettePage.executeCommand("devBuddy.generateStandup"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Standup generation should start + }); + }); +}); +describe("Command Error Handling", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + it("should handle unknown command gracefully", async function () { + try { + await commandPalettePage.executeCommand("devBuddy.nonExistentCommand12345"); + } + catch (error) { + // Expected to fail for non-existent command + } + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + it("should show appropriate errors when provider not configured", async function () { + // Commands that require a provider should handle missing config gracefully + await commandPalettePage.executeDevBuddyCommand("refreshTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Either succeeds or shows a helpful message + const notifications = await notificationPage.getNotifications(); + // Log any notifications for debugging + for (const notif of notifications) { + const message = await notif.getMessage(); + console.log("Notification:", message); + } + }); +}); +//# sourceMappingURL=commands.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/commands.test.js.map b/test/e2e/suite/commands.test.js.map new file mode 100644 index 0000000..be00a4b --- /dev/null +++ b/test/e2e/suite/commands.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commands.test.js","sourceRoot":"","sources":["commands.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/commands.test.ts b/test/e2e/suite/commands.test.ts new file mode 100644 index 0000000..36d28f4 --- /dev/null +++ b/test/e2e/suite/commands.test.ts @@ -0,0 +1,479 @@ +/** + * Command Palette Tests + * + * Tests for DevBuddy commands executed via the command palette, + * including provider selection, ticket creation, and various actions. + */ + +import { expect } from "chai"; +import { Workbench, InputBox, Notification } from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { sleep, waitFor } from "../utils/helpers"; +import { CommandPalettePage } from "../page-objects/CommandPalettePage"; +import { NotificationPage } from "../page-objects/NotificationPage"; +import { SidebarPage } from "../page-objects/SidebarPage"; +import { setupMockServerHooks } from "../mocks"; + +describe("DevBuddy Commands", function () { + this.timeout(TestConfig.timeouts.default); + + let workbench: Workbench; + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let sidebarPage: SidebarPage; + + // Setup mock servers + setupMockServerHooks("all"); + + before(async function () { + workbench = new Workbench(); + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + sidebarPage = new SidebarPage(); + + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + // Cancel any open dialogs + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + + await notificationPage.dismissAll(); + }); + + describe("Provider Selection", function () { + it("should open provider selection dialog", async function () { + await commandPalettePage.executeDevBuddyCommand("selectProvider"); + await sleep(TestConfig.timeouts.animation); + + // Check if quick pick appeared + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const items = await commandPalettePage.getQuickPickItems(); + + // Should show Linear and Jira options + const hasLinear = items.some((item) => + item.toLowerCase().includes("linear") + ); + const hasJira = items.some((item) => + item.toLowerCase().includes("jira") + ); + + // At least one provider should be available + expect(hasLinear || hasJira).to.be.true; + + await commandPalettePage.cancel(); + } + }); + + it("should be able to select Linear provider", async function () { + await commandPalettePage.executeDevBuddyCommand("selectProvider"); + await sleep(TestConfig.timeouts.animation); + + try { + await commandPalettePage.selectQuickPickItem("Linear"); + await sleep(TestConfig.timeouts.animation); + + // Provider selection should complete without error + } catch (error) { + // Linear option might not be visible or selection might trigger further dialogs + await commandPalettePage.cancel(); + } + }); + + it("should be able to select Jira provider", async function () { + await commandPalettePage.executeDevBuddyCommand("selectProvider"); + await sleep(TestConfig.timeouts.animation); + + try { + await commandPalettePage.selectQuickPickItem("Jira"); + await sleep(TestConfig.timeouts.animation); + + // Provider selection should complete without error + } catch (error) { + // Jira option might not be visible or selection might trigger further dialogs + await commandPalettePage.cancel(); + } + }); + }); + + describe("Refresh Command", function () { + it("should execute refresh command without errors", async function () { + await commandPalettePage.executeDevBuddyCommand("refreshTickets"); + await sleep(TestConfig.timeouts.apiResponse); + + const hasErrors = await notificationPage.hasErrors(); + if (hasErrors) { + const errors = await notificationPage.getErrorMessages(); + // Log errors but don't fail (might be expected without valid token) + console.log("Refresh errors:", errors); + } + }); + }); + + describe("Create Ticket Command", function () { + it("should open create ticket dialog or webview", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await sleep(TestConfig.timeouts.animation); + + // Either a webview should open or a dialog should appear + // This depends on whether a provider is configured + }); + }); + + describe("Help Command", function () { + it("should open help menu", async function () { + await commandPalettePage.executeDevBuddyCommand("showHelp"); + await sleep(TestConfig.timeouts.animation); + + // Help menu should open (quick pick or webview) + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + + describe("Standup Builder Command", function () { + it("should attempt to open standup builder", async function () { + await commandPalettePage.executeDevBuddyCommand("openStandupBuilder"); + await sleep(TestConfig.timeouts.animation); + + // Standup builder should open (if provider is configured) + // or show a message about required setup + }); + }); + + describe("Quick Open Ticket Command", function () { + it("should open quick open ticket dialog", async function () { + await commandPalettePage.executeDevBuddyCommand("quickOpenTicket"); + await sleep(TestConfig.timeouts.animation); + + // Quick open should show ticket list or input + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + + describe("Open Ticket By ID Command", function () { + it("should open ticket by ID input", async function () { + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Should show input box for ticket ID + await commandPalettePage.cancel(); + } + }); + + it("should handle invalid ticket ID gracefully", async function () { + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Enter an invalid ID + const inputBox = new InputBox(); + await inputBox.setText("INVALID-999999"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show error or handle gracefully + } + }); + }); + + describe("Search Tickets Command", function () { + it("should open search input", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + + describe("Clear Search Command", function () { + it("should execute clear search command", async function () { + // First perform a search + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const inputBox = new InputBox(); + await inputBox.setText("test"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + } + + // Then clear the search + await commandPalettePage.executeDevBuddyCommand("clearSearch"); + await sleep(TestConfig.timeouts.animation); + + // Search should be cleared + }); + }); + + describe("Branch Commands", function () { + it("should register start branch command", async function () { + // Verify command exists + await workbench.openCommandPrompt(); + const inputBox = new InputBox(); + await inputBox.setText("DevBuddy: Start Branch"); + await sleep(TestConfig.timeouts.animation); + + const picks = await inputBox.getQuickPicks(); + const hasStartBranch = picks.length > 0; + + await inputBox.cancel(); + }); + + it("should register auto-detect branches command", async function () { + await commandPalettePage.executeDevBuddyCommand("autoDetectBranches"); + await sleep(TestConfig.timeouts.apiResponse); + + // Command should execute (might show notification about results) + }); + }); + + describe("Telemetry Commands", function () { + it("should open telemetry management", async function () { + await commandPalettePage.executeDevBuddyCommand("manageTelemetry"); + await sleep(TestConfig.timeouts.animation); + + // Should open telemetry settings or dialog + }); + }); +}); + +describe("Jira-Specific Commands", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + setupMockServerHooks("jira"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Jira Setup Commands", function () { + it("should have Jira setup command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setup"); + await sleep(TestConfig.timeouts.animation); + + // Setup dialog should appear + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should have Jira Cloud setup command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await sleep(TestConfig.timeouts.animation); + + // Cloud setup should start + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should have Jira Server setup command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupServer"); + await sleep(TestConfig.timeouts.animation); + + // Server setup should start + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + + describe("Jira Issue Commands", function () { + it("should have refresh issues command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.refreshIssues"); + await sleep(TestConfig.timeouts.apiResponse); + + // Refresh should execute + }); + + it("should have create issue command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.createIssue"); + await sleep(TestConfig.timeouts.animation); + + // Create dialog should appear + }); + }); + + describe("Jira Connection Commands", function () { + it("should have test connection command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.testConnection"); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show connection result + }); + + it("should have reset config command", async function () { + // Note: Be careful with this in real tests + await commandPalettePage.executeCommand("devBuddy.jira.resetConfig"); + await sleep(TestConfig.timeouts.animation); + + // Should show confirmation dialog + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); + +describe("Linear-Specific Commands", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + setupMockServerHooks("linear"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Linear Setup", function () { + it("should have Linear token configuration command", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + // Token configuration should start + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should have walkthrough setup Linear command", async function () { + await commandPalettePage.executeCommand("devBuddy.walkthroughSetupLinear"); + await sleep(TestConfig.timeouts.animation); + + // Setup walkthrough should start + }); + }); + + describe("Linear Ticket Commands", function () { + it("should have convert TODO to ticket command", async function () { + await commandPalettePage.executeCommand("devBuddy.convertTodoToTicket"); + await sleep(TestConfig.timeouts.animation); + + // TODO converter should activate + }); + }); + + describe("Linear PR/Standup Commands", function () { + it("should have generate PR summary command", async function () { + await commandPalettePage.executeCommand("devBuddy.generatePRSummary"); + await sleep(TestConfig.timeouts.animation); + + // PR summary generation should start + }); + + it("should have generate standup command", async function () { + await commandPalettePage.executeCommand("devBuddy.generateStandup"); + await sleep(TestConfig.timeouts.animation); + + // Standup generation should start + }); + }); +}); + +describe("Command Error Handling", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + it("should handle unknown command gracefully", async function () { + try { + await commandPalettePage.executeCommand( + "devBuddy.nonExistentCommand12345" + ); + } catch (error) { + // Expected to fail for non-existent command + } + + await sleep(TestConfig.timeouts.animation); + }); + + it("should show appropriate errors when provider not configured", async function () { + // Commands that require a provider should handle missing config gracefully + await commandPalettePage.executeDevBuddyCommand("refreshTickets"); + await sleep(TestConfig.timeouts.apiResponse); + + // Either succeeds or shows a helpful message + const notifications = await notificationPage.getNotifications(); + // Log any notifications for debugging + for (const notif of notifications) { + const message = await notif.getMessage(); + console.log("Notification:", message); + } + }); +}); diff --git a/test/e2e/suite/index.js b/test/e2e/suite/index.js new file mode 100644 index 0000000..3afd047 --- /dev/null +++ b/test/e2e/suite/index.js @@ -0,0 +1,54 @@ +"use strict"; +/** + * E2E Test Suite Index + * + * Entry point for all E2E tests. Exports test configuration + * and provides test utilities. + */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.suiteConfig = void 0; +// Re-export test utilities +__exportStar(require("../utils/testConfig"), exports); +__exportStar(require("../utils/helpers"), exports); +// Re-export page objects +__exportStar(require("../page-objects"), exports); +// Re-export mocks +__exportStar(require("../mocks"), exports); +/** + * Test suite configuration + */ +exports.suiteConfig = { + // Test timeouts + defaultTimeout: 30000, + activationTimeout: 15000, + // Test categories + categories: { + activation: "Extension Activation", + treeview: "TreeView - My Tickets", + commands: "DevBuddy Commands", + linear: "Linear Platform", + jira: "Jira Platform", + }, + // Test priorities + priorities: { + critical: 1, + high: 2, + medium: 3, + low: 4, + }, +}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/test/e2e/suite/index.js.map b/test/e2e/suite/index.js.map new file mode 100644 index 0000000..2425bbb --- /dev/null +++ b/test/e2e/suite/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;AAEH,2BAA2B;AAC3B,sDAAoC;AACpC,mDAAiC;AAEjC,yBAAyB;AACzB,kDAAgC;AAEhC,kBAAkB;AAClB,2CAAyB;AAEzB;;GAEG;AACU,QAAA,WAAW,GAAG;IACzB,gBAAgB;IAChB,cAAc,EAAE,KAAK;IACrB,iBAAiB,EAAE,KAAK;IAExB,kBAAkB;IAClB,UAAU,EAAE;QACV,UAAU,EAAE,sBAAsB;QAClC,QAAQ,EAAE,uBAAuB;QACjC,QAAQ,EAAE,mBAAmB;QAC7B,MAAM,EAAE,iBAAiB;QACzB,IAAI,EAAE,eAAe;KACtB;IAED,kBAAkB;IAClB,UAAU,EAAE;QACV,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;KACP;CACF,CAAC"} \ No newline at end of file diff --git a/test/e2e/suite/index.ts b/test/e2e/suite/index.ts new file mode 100644 index 0000000..80df689 --- /dev/null +++ b/test/e2e/suite/index.ts @@ -0,0 +1,42 @@ +/** + * E2E Test Suite Index + * + * Entry point for all E2E tests. Exports test configuration + * and provides test utilities. + */ + +// Re-export test utilities +export * from "../utils/testConfig"; +export * from "../utils/helpers"; + +// Re-export page objects +export * from "../page-objects"; + +// Re-export mocks +export * from "../mocks"; + +/** + * Test suite configuration + */ +export const suiteConfig = { + // Test timeouts + defaultTimeout: 30000, + activationTimeout: 15000, + + // Test categories + categories: { + activation: "Extension Activation", + treeview: "TreeView - My Tickets", + commands: "DevBuddy Commands", + linear: "Linear Platform", + jira: "Jira Platform", + }, + + // Test priorities + priorities: { + critical: 1, + high: 2, + medium: 3, + low: 4, + }, +}; diff --git a/test/e2e/suite/jira/setup.test.js b/test/e2e/suite/jira/setup.test.js new file mode 100644 index 0000000..2bcf986 --- /dev/null +++ b/test/e2e/suite/jira/setup.test.js @@ -0,0 +1,259 @@ +"use strict"; +/** + * Jira Setup Flow Tests + * + * Tests for the Jira provider first-time setup flow, + * including Cloud and Server configuration. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const chai_1 = require("chai"); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../../utils/testConfig"); +const helpers_1 = require("../../utils/helpers"); +const CommandPalettePage_1 = require("../../page-objects/CommandPalettePage"); +const NotificationPage_1 = require("../../page-objects/NotificationPage"); +const SidebarPage_1 = require("../../page-objects/SidebarPage"); +const mocks_1 = require("../../mocks"); +describe("Jira Setup Flow", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default * 2); + let workbench; + let commandPalettePage; + let notificationPage; + let sidebarPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Jira Type Selection", function () { + it("should prompt for Jira type selection", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setup"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const items = await commandPalettePage.getQuickPickItems(); + // Should show Cloud and Server options + const hasCloud = items.some((item) => item.toLowerCase().includes("cloud")); + const hasServer = items.some((item) => item.toLowerCase().includes("server")); + await commandPalettePage.cancel(); + } + }); + }); + describe("Jira Cloud Setup", function () { + it("should prompt for site URL", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + (0, chai_1.expect)(isOpen).to.be.true; + // Should ask for site URL (e.g., company.atlassian.net) + const inputBox = new vscode_extension_tester_1.InputBox(); + const placeholder = await inputBox.getPlaceHolder(); + await inputBox.cancel(); + }); + it("should prompt for email", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // Enter site URL + await inputBox.setText("test.atlassian.net"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should now ask for email + // Check if next input is for email + }); + it("should prompt for API token", async function () { + // After email, should ask for API token + }); + it("should validate site URL format", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // Enter invalid URL + await inputBox.setText("not-a-valid-url"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show validation error + }); + it("should test connection after setup", async function () { + // After entering credentials, connection should be tested + }); + }); + describe("Jira Server Setup", function () { + it("should prompt for server URL", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupServer"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + (0, chai_1.expect)(isOpen).to.be.true; + // Should ask for server URL (e.g., http://jira.company.com) + await commandPalettePage.cancel(); + }); + it("should prompt for username", async function () { + // Server setup uses username instead of email + }); + it("should prompt for password or PAT", async function () { + // Server setup uses password or Personal Access Token + }); + it("should handle self-signed certificates", async function () { + // Server setup should handle SSL certificate issues + }); + }); + describe("Project Selection", function () { + it("should show available projects after authentication", async function () { + // After successful auth, projects should be fetched + }); + it("should allow default project selection", async function () { + // User should be able to set a default project + }); + }); + describe("Connection Testing", function () { + it("should execute test connection command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.testConnection"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show connection result + }); + it("should show success for valid credentials", async function () { + // With valid mock data, should show success + }); + it("should show error for invalid credentials", async function () { + // With invalid credentials, should show error + }); + }); +}); +describe("Jira Setup - Error Scenarios", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box + } + await notificationPage.dismissAll(); + }); + describe("Invalid Credentials", function () { + it("should handle invalid site URL", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("invalid-site.example.com"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show connection error + }); + it("should handle invalid email format", async function () { + // Email validation + }); + it("should handle invalid API token", async function () { + // Token validation + }); + }); + describe("Network Errors", function () { + it("should handle unreachable server", async function () { + // Network timeout or DNS failure + }); + it("should handle rate limiting", async function () { + // 429 response from Jira + }); + }); + describe("Reset Configuration", function () { + it("should execute reset config command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.resetConfig"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should ask for confirmation + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should clear stored credentials on reset", async function () { + // After reset, credentials should be removed + }); + }); + describe("Update Credentials", function () { + it("should execute update token command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.updateToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should prompt for new token + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); +describe("Jira Server-Specific Setup", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box + } + await notificationPage.dismissAll(); + }); + describe("Server Commands", function () { + it("should have server-specific test connection", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.server.testConnection"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Server-specific connection test + }); + it("should have server-specific reset config", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.server.resetConfig"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should show server info", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.server.showInfo"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show server version and info + }); + it("should allow password update", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.server.updatePassword"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); +//# sourceMappingURL=setup.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/jira/setup.test.js.map b/test/e2e/suite/jira/setup.test.js.map new file mode 100644 index 0000000..54d0c22 --- /dev/null +++ b/test/e2e/suite/jira/setup.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"setup.test.js","sourceRoot":"","sources":["setup.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/jira/setup.test.ts b/test/e2e/suite/jira/setup.test.ts new file mode 100644 index 0000000..2bb01fb --- /dev/null +++ b/test/e2e/suite/jira/setup.test.ts @@ -0,0 +1,334 @@ +/** + * Jira Setup Flow Tests + * + * Tests for the Jira provider first-time setup flow, + * including Cloud and Server configuration. + */ + +import { expect } from "chai"; +import { Workbench, InputBox } from "vscode-extension-tester"; +import { TestConfig } from "../../utils/testConfig"; +import { sleep, waitFor } from "../../utils/helpers"; +import { CommandPalettePage } from "../../page-objects/CommandPalettePage"; +import { NotificationPage } from "../../page-objects/NotificationPage"; +import { SidebarPage } from "../../page-objects/SidebarPage"; +import { + setupMockServerHooks, + mockJiraCurrentUser, + mockJiraProjects, +} from "../../mocks"; + +describe("Jira Setup Flow", function () { + this.timeout(TestConfig.timeouts.default * 2); + + let workbench: Workbench; + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let sidebarPage: SidebarPage; + + setupMockServerHooks("jira"); + + before(async function () { + workbench = new Workbench(); + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + sidebarPage = new SidebarPage(); + + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Jira Type Selection", function () { + it("should prompt for Jira type selection", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setup"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const items = await commandPalettePage.getQuickPickItems(); + + // Should show Cloud and Server options + const hasCloud = items.some((item) => + item.toLowerCase().includes("cloud") + ); + const hasServer = items.some((item) => + item.toLowerCase().includes("server") + ); + + await commandPalettePage.cancel(); + } + }); + }); + + describe("Jira Cloud Setup", function () { + it("should prompt for site URL", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + expect(isOpen).to.be.true; + + // Should ask for site URL (e.g., company.atlassian.net) + const inputBox = new InputBox(); + const placeholder = await inputBox.getPlaceHolder(); + + await inputBox.cancel(); + }); + + it("should prompt for email", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + + // Enter site URL + await inputBox.setText("test.atlassian.net"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + + // Should now ask for email + // Check if next input is for email + }); + + it("should prompt for API token", async function () { + // After email, should ask for API token + }); + + it("should validate site URL format", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + + // Enter invalid URL + await inputBox.setText("not-a-valid-url"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + + // Should show validation error + }); + + it("should test connection after setup", async function () { + // After entering credentials, connection should be tested + }); + }); + + describe("Jira Server Setup", function () { + it("should prompt for server URL", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupServer"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + expect(isOpen).to.be.true; + + // Should ask for server URL (e.g., http://jira.company.com) + await commandPalettePage.cancel(); + }); + + it("should prompt for username", async function () { + // Server setup uses username instead of email + }); + + it("should prompt for password or PAT", async function () { + // Server setup uses password or Personal Access Token + }); + + it("should handle self-signed certificates", async function () { + // Server setup should handle SSL certificate issues + }); + }); + + describe("Project Selection", function () { + it("should show available projects after authentication", async function () { + // After successful auth, projects should be fetched + }); + + it("should allow default project selection", async function () { + // User should be able to set a default project + }); + }); + + describe("Connection Testing", function () { + it("should execute test connection command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.testConnection"); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show connection result + }); + + it("should show success for valid credentials", async function () { + // With valid mock data, should show success + }); + + it("should show error for invalid credentials", async function () { + // With invalid credentials, should show error + }); + }); +}); + +describe("Jira Setup - Error Scenarios", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + setupMockServerHooks("jira"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box + } + await notificationPage.dismissAll(); + }); + + describe("Invalid Credentials", function () { + it("should handle invalid site URL", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.setupCloud"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText("invalid-site.example.com"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show connection error + }); + + it("should handle invalid email format", async function () { + // Email validation + }); + + it("should handle invalid API token", async function () { + // Token validation + }); + }); + + describe("Network Errors", function () { + it("should handle unreachable server", async function () { + // Network timeout or DNS failure + }); + + it("should handle rate limiting", async function () { + // 429 response from Jira + }); + }); + + describe("Reset Configuration", function () { + it("should execute reset config command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.resetConfig"); + await sleep(TestConfig.timeouts.animation); + + // Should ask for confirmation + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should clear stored credentials on reset", async function () { + // After reset, credentials should be removed + }); + }); + + describe("Update Credentials", function () { + it("should execute update token command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.updateToken"); + await sleep(TestConfig.timeouts.animation); + + // Should prompt for new token + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); + +describe("Jira Server-Specific Setup", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + setupMockServerHooks("jira"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box + } + await notificationPage.dismissAll(); + }); + + describe("Server Commands", function () { + it("should have server-specific test connection", async function () { + await commandPalettePage.executeCommand( + "devBuddy.jira.server.testConnection" + ); + await sleep(TestConfig.timeouts.apiResponse); + + // Server-specific connection test + }); + + it("should have server-specific reset config", async function () { + await commandPalettePage.executeCommand( + "devBuddy.jira.server.resetConfig" + ); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should show server info", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.server.showInfo"); + await sleep(TestConfig.timeouts.animation); + + // Should show server version and info + }); + + it("should allow password update", async function () { + await commandPalettePage.executeCommand( + "devBuddy.jira.server.updatePassword" + ); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); diff --git a/test/e2e/suite/jira/tickets.test.js b/test/e2e/suite/jira/tickets.test.js new file mode 100644 index 0000000..2ba2b23 --- /dev/null +++ b/test/e2e/suite/jira/tickets.test.js @@ -0,0 +1,342 @@ +"use strict"; +/** + * Jira Ticket Operations Tests + * + * Tests for Jira issue management including viewing, + * creating, updating, and status transitions. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../../utils/testConfig"); +const helpers_1 = require("../../utils/helpers"); +const CommandPalettePage_1 = require("../../page-objects/CommandPalettePage"); +const NotificationPage_1 = require("../../page-objects/NotificationPage"); +const SidebarPage_1 = require("../../page-objects/SidebarPage"); +const TreeViewPage_1 = require("../../page-objects/TreeViewPage"); +const mocks_1 = require("../../mocks"); +describe("Jira Ticket Operations", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let workbench; + let commandPalettePage; + let notificationPage; + let sidebarPage; + let treeViewPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + treeViewPage = new TreeViewPage_1.TreeViewPage(); + await sidebarPage.open(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Issue Listing", function () { + it("should display assigned issues", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.refreshIssues"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + const items = await treeViewPage.getVisibleItems(); + // Should have issues from mock data + }); + it("should group issues by status", async function () { + const items = await treeViewPage.getVisibleItems(); + // Look for status indicators + let hasStatusGrouping = false; + for (const item of items) { + const label = await item.getLabel(); + if (label.toLowerCase().includes("progress") || + label.toLowerCase().includes("open") || + label.toLowerCase().includes("done")) { + hasStatusGrouping = true; + break; + } + } + }); + it("should show issue keys in format PROJ-XXX", async function () { + const items = await treeViewPage.getVisibleItems(); + let hasProperFormat = false; + for (const item of items) { + const label = await item.getLabel(); + if (/TEST-\d+/.test(label) || /[A-Z]+-\d+/.test(label)) { + hasProperFormat = true; + break; + } + } + }); + }); + describe("Open Issue", function () { + it("should execute open issue command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.openIssue"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show issue picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should open issue in browser when configured", async function () { + // With devBuddy.jira.openInBrowser = true + // Issue should open in external browser + }); + }); + describe("Issue Status Changes", function () { + it("should execute update status command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.updateStatus"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show issue picker or status transitions + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should show available transitions", async function () { + // When changing status, available transitions should be shown + // Jira uses workflow transitions rather than simple status changes + }); + it("should handle transition with required fields", async function () { + // Some transitions may require additional fields + }); + }); + describe("Issue Assignment", function () { + it("should execute assign to me command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.assignToMe"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show issue picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should update assignee successfully", async function () { + // After assignment, issue should show current user + }); + }); + describe("Issue Comments", function () { + it("should execute add comment command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.addComment"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show issue picker then comment input + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should add comment to issue", async function () { + // After adding comment, it should appear on the issue + }); + }); + describe("Issue Copying", function () { + it("should execute copy issue key command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.copyIssueKey"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should copy issue key to clipboard + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should execute copy issue URL command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.copyIssueUrl"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should copy issue URL to clipboard + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); +describe("Jira Issue Creation", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box + } + await notificationPage.dismissAll(); + }); + describe("Create Issue Form", function () { + it("should execute create issue command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.createIssue"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Create issue dialog should appear + }); + it("should show project selection", async function () { + // Project picker should be available + }); + it("should show issue type selection", async function () { + // Issue type picker (Story, Bug, Task, etc.) + }); + it("should require summary field", async function () { + // Summary is required for issue creation + }); + }); + describe("Issue Types", function () { + it("should support Story creation", async function () { + // Create a Story issue type + }); + it("should support Bug creation", async function () { + // Create a Bug issue type + }); + it("should support Task creation", async function () { + // Create a Task issue type + }); + }); + describe("Issue Fields", function () { + it("should support priority selection", async function () { + // Priority field + }); + it("should support assignee selection", async function () { + // Assignee field + }); + it("should support labels", async function () { + // Labels field + }); + it("should support description", async function () { + // Description field (may use ADF format) + }); + }); +}); +describe("Jira Issue Search (JQL)", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let sidebarPage; + let treeViewPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + treeViewPage = new TreeViewPage_1.TreeViewPage(); + await sidebarPage.open(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box + } + }); + describe("JQL Search", function () { + it("should search by project", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // JQL: project = TEST + await inputBox.setText("project = TEST"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should filter to TEST project issues + }); + it("should search by assignee", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // JQL: assignee = currentUser() + await inputBox.setText("assignee = currentUser()"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should filter to current user's issues + }); + it("should search by status", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // JQL: status = "In Progress" + await inputBox.setText('status = "In Progress"'); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should filter to in-progress issues + }); + it("should search by text", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // JQL: text ~ "authentication" + await inputBox.setText('text ~ "authentication"'); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should find issues with "authentication" in text + }); + }); + describe("Search Filters", function () { + it("should support combined filters", async function () { + // JQL: project = TEST AND status = Open + }); + it("should support ordering", async function () { + // JQL: ORDER BY created DESC + }); + }); +}); +describe("Jira Context Menu Actions", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let treeViewPage; + let sidebarPage; + (0, mocks_1.setupMockServerHooks)("jira"); + before(async function () { + treeViewPage = new TreeViewPage_1.TreeViewPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + await sidebarPage.open(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + describe("Issue Context Menu", function () { + it("should show Jira-specific actions", async function () { + const items = await treeViewPage.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (/[A-Z]+-\d+/.test(label)) { + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + // Check for Jira-specific actions + const menuLabels = []; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + menuLabels.push(itemLabel); + } + // Should include actions like: + // - Open Issue + // - Update Status + // - Assign to Me + // - Add Comment + // - Copy Issue Key + // - Copy Issue URL + await contextMenu.close(); + break; + } + catch { + // Context menu might not be available + } + } + } + }); + }); +}); +//# sourceMappingURL=tickets.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/jira/tickets.test.js.map b/test/e2e/suite/jira/tickets.test.js.map new file mode 100644 index 0000000..04139e6 --- /dev/null +++ b/test/e2e/suite/jira/tickets.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tickets.test.js","sourceRoot":"","sources":["tickets.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/jira/tickets.test.ts b/test/e2e/suite/jira/tickets.test.ts new file mode 100644 index 0000000..9c2f91c --- /dev/null +++ b/test/e2e/suite/jira/tickets.test.ts @@ -0,0 +1,419 @@ +/** + * Jira Ticket Operations Tests + * + * Tests for Jira issue management including viewing, + * creating, updating, and status transitions. + */ + +import { expect } from "chai"; +import { Workbench, InputBox } from "vscode-extension-tester"; +import { TestConfig } from "../../utils/testConfig"; +import { sleep, waitFor } from "../../utils/helpers"; +import { CommandPalettePage } from "../../page-objects/CommandPalettePage"; +import { NotificationPage } from "../../page-objects/NotificationPage"; +import { SidebarPage } from "../../page-objects/SidebarPage"; +import { TreeViewPage } from "../../page-objects/TreeViewPage"; +import { + setupMockServerHooks, + mockJiraIssues, + getJiraIssueByKey, +} from "../../mocks"; + +describe("Jira Ticket Operations", function () { + this.timeout(TestConfig.timeouts.default); + + let workbench: Workbench; + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let sidebarPage: SidebarPage; + let treeViewPage: TreeViewPage; + + setupMockServerHooks("jira"); + + before(async function () { + workbench = new Workbench(); + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + sidebarPage = new SidebarPage(); + treeViewPage = new TreeViewPage(); + + await sidebarPage.open(); + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Issue Listing", function () { + it("should display assigned issues", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.refreshIssues"); + await sleep(TestConfig.timeouts.apiResponse); + + const items = await treeViewPage.getVisibleItems(); + // Should have issues from mock data + }); + + it("should group issues by status", async function () { + const items = await treeViewPage.getVisibleItems(); + + // Look for status indicators + let hasStatusGrouping = false; + for (const item of items) { + const label = await item.getLabel(); + if ( + label.toLowerCase().includes("progress") || + label.toLowerCase().includes("open") || + label.toLowerCase().includes("done") + ) { + hasStatusGrouping = true; + break; + } + } + }); + + it("should show issue keys in format PROJ-XXX", async function () { + const items = await treeViewPage.getVisibleItems(); + + let hasProperFormat = false; + for (const item of items) { + const label = await item.getLabel(); + if (/TEST-\d+/.test(label) || /[A-Z]+-\d+/.test(label)) { + hasProperFormat = true; + break; + } + } + }); + }); + + describe("Open Issue", function () { + it("should execute open issue command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.openIssue"); + await sleep(TestConfig.timeouts.animation); + + // Should show issue picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should open issue in browser when configured", async function () { + // With devBuddy.jira.openInBrowser = true + // Issue should open in external browser + }); + }); + + describe("Issue Status Changes", function () { + it("should execute update status command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.updateStatus"); + await sleep(TestConfig.timeouts.animation); + + // Should show issue picker or status transitions + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should show available transitions", async function () { + // When changing status, available transitions should be shown + // Jira uses workflow transitions rather than simple status changes + }); + + it("should handle transition with required fields", async function () { + // Some transitions may require additional fields + }); + }); + + describe("Issue Assignment", function () { + it("should execute assign to me command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.assignToMe"); + await sleep(TestConfig.timeouts.animation); + + // Should show issue picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should update assignee successfully", async function () { + // After assignment, issue should show current user + }); + }); + + describe("Issue Comments", function () { + it("should execute add comment command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.addComment"); + await sleep(TestConfig.timeouts.animation); + + // Should show issue picker then comment input + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should add comment to issue", async function () { + // After adding comment, it should appear on the issue + }); + }); + + describe("Issue Copying", function () { + it("should execute copy issue key command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.copyIssueKey"); + await sleep(TestConfig.timeouts.animation); + + // Should copy issue key to clipboard + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should execute copy issue URL command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.copyIssueUrl"); + await sleep(TestConfig.timeouts.animation); + + // Should copy issue URL to clipboard + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); +}); + +describe("Jira Issue Creation", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + setupMockServerHooks("jira"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box + } + await notificationPage.dismissAll(); + }); + + describe("Create Issue Form", function () { + it("should execute create issue command", async function () { + await commandPalettePage.executeCommand("devBuddy.jira.createIssue"); + await sleep(TestConfig.timeouts.animation); + + // Create issue dialog should appear + }); + + it("should show project selection", async function () { + // Project picker should be available + }); + + it("should show issue type selection", async function () { + // Issue type picker (Story, Bug, Task, etc.) + }); + + it("should require summary field", async function () { + // Summary is required for issue creation + }); + }); + + describe("Issue Types", function () { + it("should support Story creation", async function () { + // Create a Story issue type + }); + + it("should support Bug creation", async function () { + // Create a Bug issue type + }); + + it("should support Task creation", async function () { + // Create a Task issue type + }); + }); + + describe("Issue Fields", function () { + it("should support priority selection", async function () { + // Priority field + }); + + it("should support assignee selection", async function () { + // Assignee field + }); + + it("should support labels", async function () { + // Labels field + }); + + it("should support description", async function () { + // Description field (may use ADF format) + }); + }); +}); + +describe("Jira Issue Search (JQL)", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let sidebarPage: SidebarPage; + let treeViewPage: TreeViewPage; + + setupMockServerHooks("jira"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + sidebarPage = new SidebarPage(); + treeViewPage = new TreeViewPage(); + + await sidebarPage.open(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box + } + }); + + describe("JQL Search", function () { + it("should search by project", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + // JQL: project = TEST + await inputBox.setText("project = TEST"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should filter to TEST project issues + }); + + it("should search by assignee", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + // JQL: assignee = currentUser() + await inputBox.setText("assignee = currentUser()"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should filter to current user's issues + }); + + it("should search by status", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + // JQL: status = "In Progress" + await inputBox.setText('status = "In Progress"'); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should filter to in-progress issues + }); + + it("should search by text", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + // JQL: text ~ "authentication" + await inputBox.setText('text ~ "authentication"'); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should find issues with "authentication" in text + }); + }); + + describe("Search Filters", function () { + it("should support combined filters", async function () { + // JQL: project = TEST AND status = Open + }); + + it("should support ordering", async function () { + // JQL: ORDER BY created DESC + }); + }); +}); + +describe("Jira Context Menu Actions", function () { + this.timeout(TestConfig.timeouts.default); + + let treeViewPage: TreeViewPage; + let sidebarPage: SidebarPage; + + setupMockServerHooks("jira"); + + before(async function () { + treeViewPage = new TreeViewPage(); + sidebarPage = new SidebarPage(); + + await sidebarPage.open(); + await sleep(TestConfig.timeouts.animation); + }); + + describe("Issue Context Menu", function () { + it("should show Jira-specific actions", async function () { + const items = await treeViewPage.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (/[A-Z]+-\d+/.test(label)) { + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + + // Check for Jira-specific actions + const menuLabels: string[] = []; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + menuLabels.push(itemLabel); + } + + // Should include actions like: + // - Open Issue + // - Update Status + // - Assign to Me + // - Add Comment + // - Copy Issue Key + // - Copy Issue URL + + await contextMenu.close(); + break; + } catch { + // Context menu might not be available + } + } + } + }); + }); +}); diff --git a/test/e2e/suite/linear/branches.test.js b/test/e2e/suite/linear/branches.test.js new file mode 100644 index 0000000..bbf37f8 --- /dev/null +++ b/test/e2e/suite/linear/branches.test.js @@ -0,0 +1,199 @@ +"use strict"; +/** + * Linear Branch Management Tests + * + * Tests for branch creation, association, and management + * with Linear tickets. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../../utils/testConfig"); +const helpers_1 = require("../../utils/helpers"); +const CommandPalettePage_1 = require("../../page-objects/CommandPalettePage"); +const NotificationPage_1 = require("../../page-objects/NotificationPage"); +const SidebarPage_1 = require("../../page-objects/SidebarPage"); +const TreeViewPage_1 = require("../../page-objects/TreeViewPage"); +const mocks_1 = require("../../mocks"); +describe("Linear Branch Management", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let workbench; + let commandPalettePage; + let notificationPage; + let sidebarPage; + let treeViewPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + treeViewPage = new TreeViewPage_1.TreeViewPage(); + await sidebarPage.open(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Branch Creation", function () { + it("should offer branch creation from ticket context menu", async function () { + const items = await treeViewPage.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + let hasBranchOption = false; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + if (itemLabel.toLowerCase().includes("branch") || + itemLabel.toLowerCase().includes("start")) { + hasBranchOption = true; + break; + } + } + await contextMenu.close(); + break; + } + catch { + // Context menu might not be available + } + } + } + }); + it("should execute start branch command", async function () { + await commandPalettePage.executeDevBuddyCommand("startBranch"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show ticket picker or branch naming dialog + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should generate branch name from ticket", async function () { + // When creating a branch, name should be auto-generated + // Based on ticket identifier and title + }); + it("should support different naming conventions", async function () { + // Test conventional, simple, and custom branch naming + // This is controlled by devBuddy.branchNamingConvention setting + }); + }); + describe("Branch Association", function () { + it("should execute associate branch command", async function () { + await commandPalettePage.executeDevBuddyCommand("associateBranchFromSidebar"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show ticket or branch picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + it("should auto-detect branch associations", async function () { + await commandPalettePage.executeDevBuddyCommand("autoDetectBranches"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should analyze branches and find matching tickets + // Based on ticket identifiers in branch names + }); + it("should remember branch associations", async function () { + // After association, the link should persist + }); + }); + describe("Branch Checkout", function () { + it("should offer checkout for associated branches", async function () { + const items = await treeViewPage.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + let hasCheckoutOption = false; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + if (itemLabel.toLowerCase().includes("checkout")) { + hasCheckoutOption = true; + break; + } + } + await contextMenu.close(); + break; + } + catch { + // Context menu might not be available + } + } + } + }); + it("should execute checkout branch command", async function () { + await commandPalettePage.executeDevBuddyCommand("checkoutBranch"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show branch picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + describe("Branch Analytics", function () { + it("should show branch analytics", async function () { + await commandPalettePage.executeDevBuddyCommand("showBranchAnalytics"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show analytics information + // Either in a webview or notification + }); + it("should offer branch cleanup", async function () { + await commandPalettePage.executeDevBuddyCommand("cleanupBranchAssociations"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should identify stale branch associations + }); + }); +}); +describe("Linear Branch Naming Conventions", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box + } + }); + describe("Conventional Naming", function () { + it("should generate feat/identifier-slug format", async function () { + // With conventional naming, branches should be like: + // feat/eng-123-my-feature + // fix/eng-456-bug-fix + }); + }); + describe("Simple Naming", function () { + it("should generate identifier-slug format", async function () { + // With simple naming, branches should be like: + // eng-123-my-feature + }); + }); + describe("Custom Template", function () { + it("should support custom branch templates", async function () { + // Custom templates with placeholders: + // {type}, {identifier}, {slug}, {username} + }); + }); +}); +//# sourceMappingURL=branches.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/linear/branches.test.js.map b/test/e2e/suite/linear/branches.test.js.map new file mode 100644 index 0000000..a924348 --- /dev/null +++ b/test/e2e/suite/linear/branches.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"branches.test.js","sourceRoot":"","sources":["branches.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/linear/branches.test.ts b/test/e2e/suite/linear/branches.test.ts new file mode 100644 index 0000000..2bb48d8 --- /dev/null +++ b/test/e2e/suite/linear/branches.test.ts @@ -0,0 +1,237 @@ +/** + * Linear Branch Management Tests + * + * Tests for branch creation, association, and management + * with Linear tickets. + */ + +import { expect } from "chai"; +import { Workbench, InputBox } from "vscode-extension-tester"; +import { TestConfig } from "../../utils/testConfig"; +import { sleep, waitFor } from "../../utils/helpers"; +import { CommandPalettePage } from "../../page-objects/CommandPalettePage"; +import { NotificationPage } from "../../page-objects/NotificationPage"; +import { SidebarPage } from "../../page-objects/SidebarPage"; +import { TreeViewPage } from "../../page-objects/TreeViewPage"; +import { setupMockServerHooks, linearMockIssues } from "../../mocks"; + +describe("Linear Branch Management", function () { + this.timeout(TestConfig.timeouts.default); + + let workbench: Workbench; + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let sidebarPage: SidebarPage; + let treeViewPage: TreeViewPage; + + setupMockServerHooks("linear"); + + before(async function () { + workbench = new Workbench(); + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + sidebarPage = new SidebarPage(); + treeViewPage = new TreeViewPage(); + + await sidebarPage.open(); + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Branch Creation", function () { + it("should offer branch creation from ticket context menu", async function () { + const items = await treeViewPage.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + + let hasBranchOption = false; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + if ( + itemLabel.toLowerCase().includes("branch") || + itemLabel.toLowerCase().includes("start") + ) { + hasBranchOption = true; + break; + } + } + + await contextMenu.close(); + break; + } catch { + // Context menu might not be available + } + } + } + }); + + it("should execute start branch command", async function () { + await commandPalettePage.executeDevBuddyCommand("startBranch"); + await sleep(TestConfig.timeouts.animation); + + // Should show ticket picker or branch naming dialog + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should generate branch name from ticket", async function () { + // When creating a branch, name should be auto-generated + // Based on ticket identifier and title + }); + + it("should support different naming conventions", async function () { + // Test conventional, simple, and custom branch naming + // This is controlled by devBuddy.branchNamingConvention setting + }); + }); + + describe("Branch Association", function () { + it("should execute associate branch command", async function () { + await commandPalettePage.executeDevBuddyCommand( + "associateBranchFromSidebar" + ); + await sleep(TestConfig.timeouts.animation); + + // Should show ticket or branch picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + + it("should auto-detect branch associations", async function () { + await commandPalettePage.executeDevBuddyCommand("autoDetectBranches"); + await sleep(TestConfig.timeouts.apiResponse); + + // Should analyze branches and find matching tickets + // Based on ticket identifiers in branch names + }); + + it("should remember branch associations", async function () { + // After association, the link should persist + }); + }); + + describe("Branch Checkout", function () { + it("should offer checkout for associated branches", async function () { + const items = await treeViewPage.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + + let hasCheckoutOption = false; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + if (itemLabel.toLowerCase().includes("checkout")) { + hasCheckoutOption = true; + break; + } + } + + await contextMenu.close(); + break; + } catch { + // Context menu might not be available + } + } + } + }); + + it("should execute checkout branch command", async function () { + await commandPalettePage.executeDevBuddyCommand("checkoutBranch"); + await sleep(TestConfig.timeouts.animation); + + // Should show branch picker + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + }); + }); + + describe("Branch Analytics", function () { + it("should show branch analytics", async function () { + await commandPalettePage.executeDevBuddyCommand("showBranchAnalytics"); + await sleep(TestConfig.timeouts.animation); + + // Should show analytics information + // Either in a webview or notification + }); + + it("should offer branch cleanup", async function () { + await commandPalettePage.executeDevBuddyCommand( + "cleanupBranchAssociations" + ); + await sleep(TestConfig.timeouts.animation); + + // Should identify stale branch associations + }); + }); +}); + +describe("Linear Branch Naming Conventions", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + + setupMockServerHooks("linear"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box + } + }); + + describe("Conventional Naming", function () { + it("should generate feat/identifier-slug format", async function () { + // With conventional naming, branches should be like: + // feat/eng-123-my-feature + // fix/eng-456-bug-fix + }); + }); + + describe("Simple Naming", function () { + it("should generate identifier-slug format", async function () { + // With simple naming, branches should be like: + // eng-123-my-feature + }); + }); + + describe("Custom Template", function () { + it("should support custom branch templates", async function () { + // Custom templates with placeholders: + // {type}, {identifier}, {slug}, {username} + }); + }); +}); diff --git a/test/e2e/suite/linear/setup.test.js b/test/e2e/suite/linear/setup.test.js new file mode 100644 index 0000000..9d809c6 --- /dev/null +++ b/test/e2e/suite/linear/setup.test.js @@ -0,0 +1,204 @@ +"use strict"; +/** + * Linear Setup Flow Tests + * + * Tests for the Linear provider first-time setup flow, + * including token configuration and team selection. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const chai_1 = require("chai"); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../../utils/testConfig"); +const helpers_1 = require("../../utils/helpers"); +const CommandPalettePage_1 = require("../../page-objects/CommandPalettePage"); +const NotificationPage_1 = require("../../page-objects/NotificationPage"); +const SidebarPage_1 = require("../../page-objects/SidebarPage"); +const mocks_1 = require("../../mocks"); +describe("Linear Setup Flow", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default * 2); + let workbench; + let commandPalettePage; + let notificationPage; + let sidebarPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Token Configuration", function () { + it("should prompt for API token", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + (0, chai_1.expect)(isOpen).to.be.true; + // Input box should be asking for token + const inputBox = new vscode_extension_tester_1.InputBox(); + const placeholder = await inputBox.getPlaceHolder(); + // Placeholder might mention "token", "API key", or similar + await inputBox.cancel(); + }); + it("should validate token format", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // Enter an invalid token + await inputBox.setText("invalid-token"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show error or validation message + // The mock server should reject invalid tokens + }); + it("should accept valid token format", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + // Enter a token that looks valid (lin_api_xxx format) + await inputBox.setText("lin_api_test1234567890abcdef"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should proceed to next step or complete setup + }); + }); + describe("Team Selection", function () { + it("should show available teams after authentication", async function () { + // After valid token, teams should be fetched + await commandPalettePage.executeCommand("devBuddy.walkthroughSetupLinear"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation * 2); + // Check if team selection appears + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const items = await commandPalettePage.getQuickPickItems(); + // Should show team options + // Mock data has teams like "Engineering" and "Product" + await commandPalettePage.cancel(); + } + }); + it("should allow team selection", async function () { + // This test simulates selecting a team + // The exact flow depends on the extension implementation + }); + }); + describe("Organization Configuration", function () { + it("should detect organization from token", async function () { + // After setup, organization should be detected + // Mock organization is "test-org" + // Check if organization setting is configured + // This would normally check VS Code settings + }); + }); + describe("Setup Validation", function () { + it("should show success message on completion", async function () { + // After successful setup, should show success notification + // This depends on the full setup flow completing + }); + it("should handle network errors gracefully", async function () { + // Test that setup handles network issues + // The mock server can be configured to return errors + }); + it("should allow re-configuration", async function () { + // Should be able to change token/settings after initial setup + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + (0, chai_1.expect)(isOpen).to.be.true; + await commandPalettePage.cancel(); + }); + }); + describe("Setup State Persistence", function () { + it("should persist setup completion flag", async function () { + // After setup, the firstTimeSetupComplete flag should be set + // This prevents the setup prompt from appearing again + }); + it("should remember selected team", async function () { + // Team selection should be persisted in settings + }); + }); +}); +describe("Linear Setup - Error Scenarios", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Invalid Token Handling", function () { + it("should show error for empty token", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText(""); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show validation error or not proceed + }); + it("should show error for malformed token", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("not-a-valid-linear-token"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show error notification + }); + it("should show error for expired token", async function () { + // Test with a token that would be rejected by the API + // The mock server can be configured to simulate this + }); + }); + describe("API Error Handling", function () { + it("should handle rate limiting", async function () { + // Test behavior when API returns 429 + }); + it("should handle server errors", async function () { + // Test behavior when API returns 500 + }); + it("should handle network timeout", async function () { + // Test behavior when network is unavailable + }); + }); + describe("User Cancellation", function () { + it("should handle user cancelling token input", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.cancel(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should return to normal state without errors + const hasErrors = await notificationPage.hasErrors(); + (0, chai_1.expect)(hasErrors).to.be.false; + }); + it("should handle user cancelling team selection", async function () { + // Similar test for team selection step + }); + }); +}); +//# sourceMappingURL=setup.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/linear/setup.test.js.map b/test/e2e/suite/linear/setup.test.js.map new file mode 100644 index 0000000..d9cc409 --- /dev/null +++ b/test/e2e/suite/linear/setup.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"setup.test.js","sourceRoot":"","sources":["setup.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/linear/setup.test.ts b/test/e2e/suite/linear/setup.test.ts new file mode 100644 index 0000000..5f5a282 --- /dev/null +++ b/test/e2e/suite/linear/setup.test.ts @@ -0,0 +1,255 @@ +/** + * Linear Setup Flow Tests + * + * Tests for the Linear provider first-time setup flow, + * including token configuration and team selection. + */ + +import { expect } from "chai"; +import { Workbench, InputBox } from "vscode-extension-tester"; +import { TestConfig } from "../../utils/testConfig"; +import { sleep, waitFor } from "../../utils/helpers"; +import { CommandPalettePage } from "../../page-objects/CommandPalettePage"; +import { NotificationPage } from "../../page-objects/NotificationPage"; +import { SidebarPage } from "../../page-objects/SidebarPage"; +import { + setupMockServerHooks, + linearMockViewer, + linearMockTeams, + linearMockOrganization, +} from "../../mocks"; + +describe("Linear Setup Flow", function () { + this.timeout(TestConfig.timeouts.default * 2); + + let workbench: Workbench; + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let sidebarPage: SidebarPage; + + setupMockServerHooks("linear"); + + before(async function () { + workbench = new Workbench(); + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + sidebarPage = new SidebarPage(); + + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Token Configuration", function () { + it("should prompt for API token", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + expect(isOpen).to.be.true; + + // Input box should be asking for token + const inputBox = new InputBox(); + const placeholder = await inputBox.getPlaceHolder(); + // Placeholder might mention "token", "API key", or similar + + await inputBox.cancel(); + }); + + it("should validate token format", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + + // Enter an invalid token + await inputBox.setText("invalid-token"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show error or validation message + // The mock server should reject invalid tokens + }); + + it("should accept valid token format", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + + // Enter a token that looks valid (lin_api_xxx format) + await inputBox.setText("lin_api_test1234567890abcdef"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should proceed to next step or complete setup + }); + }); + + describe("Team Selection", function () { + it("should show available teams after authentication", async function () { + // After valid token, teams should be fetched + await commandPalettePage.executeCommand("devBuddy.walkthroughSetupLinear"); + await sleep(TestConfig.timeouts.animation * 2); + + // Check if team selection appears + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + const items = await commandPalettePage.getQuickPickItems(); + + // Should show team options + // Mock data has teams like "Engineering" and "Product" + await commandPalettePage.cancel(); + } + }); + + it("should allow team selection", async function () { + // This test simulates selecting a team + // The exact flow depends on the extension implementation + }); + }); + + describe("Organization Configuration", function () { + it("should detect organization from token", async function () { + // After setup, organization should be detected + // Mock organization is "test-org" + + // Check if organization setting is configured + // This would normally check VS Code settings + }); + }); + + describe("Setup Validation", function () { + it("should show success message on completion", async function () { + // After successful setup, should show success notification + // This depends on the full setup flow completing + }); + + it("should handle network errors gracefully", async function () { + // Test that setup handles network issues + // The mock server can be configured to return errors + }); + + it("should allow re-configuration", async function () { + // Should be able to change token/settings after initial setup + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + expect(isOpen).to.be.true; + + await commandPalettePage.cancel(); + }); + }); + + describe("Setup State Persistence", function () { + it("should persist setup completion flag", async function () { + // After setup, the firstTimeSetupComplete flag should be set + // This prevents the setup prompt from appearing again + }); + + it("should remember selected team", async function () { + // Team selection should be persisted in settings + }); + }); +}); + +describe("Linear Setup - Error Scenarios", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + + setupMockServerHooks("linear"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Invalid Token Handling", function () { + it("should show error for empty token", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText(""); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + + // Should show validation error or not proceed + }); + + it("should show error for malformed token", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText("not-a-valid-linear-token"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show error notification + }); + + it("should show error for expired token", async function () { + // Test with a token that would be rejected by the API + // The mock server can be configured to simulate this + }); + }); + + describe("API Error Handling", function () { + it("should handle rate limiting", async function () { + // Test behavior when API returns 429 + }); + + it("should handle server errors", async function () { + // Test behavior when API returns 500 + }); + + it("should handle network timeout", async function () { + // Test behavior when network is unavailable + }); + }); + + describe("User Cancellation", function () { + it("should handle user cancelling token input", async function () { + await commandPalettePage.executeCommand("devBuddy.configureLinearToken"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.cancel(); + await sleep(TestConfig.timeouts.animation); + + // Should return to normal state without errors + const hasErrors = await notificationPage.hasErrors(); + expect(hasErrors).to.be.false; + }); + + it("should handle user cancelling team selection", async function () { + // Similar test for team selection step + }); + }); +}); diff --git a/test/e2e/suite/linear/tickets.test.js b/test/e2e/suite/linear/tickets.test.js new file mode 100644 index 0000000..58fa569 --- /dev/null +++ b/test/e2e/suite/linear/tickets.test.js @@ -0,0 +1,297 @@ +"use strict"; +/** + * Linear Ticket Operations Tests + * + * Tests for Linear ticket management including viewing, + * creating, updating, and status transitions. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../../utils/testConfig"); +const helpers_1 = require("../../utils/helpers"); +const CommandPalettePage_1 = require("../../page-objects/CommandPalettePage"); +const NotificationPage_1 = require("../../page-objects/NotificationPage"); +const SidebarPage_1 = require("../../page-objects/SidebarPage"); +const TreeViewPage_1 = require("../../page-objects/TreeViewPage"); +const WebviewPage_1 = require("../../page-objects/WebviewPage"); +const mocks_1 = require("../../mocks"); +describe("Linear Ticket Operations", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let workbench; + let commandPalettePage; + let notificationPage; + let sidebarPage; + let treeViewPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + treeViewPage = new TreeViewPage_1.TreeViewPage(); + await sidebarPage.open(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + describe("Ticket Listing", function () { + it("should display assigned tickets", async function () { + await sidebarPage.clickRefresh(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + const items = await treeViewPage.getVisibleItems(); + // Should have some tickets from mock data + }); + it("should group tickets by status", async function () { + const items = await treeViewPage.getVisibleItems(); + // Look for status indicators in labels + let hasStatusGrouping = false; + for (const item of items) { + const label = await item.getLabel(); + // Check for status names from mock data + if (label.toLowerCase().includes("progress") || + label.toLowerCase().includes("todo") || + label.toLowerCase().includes("backlog")) { + hasStatusGrouping = true; + break; + } + } + }); + it("should show ticket identifiers in format ENG-XXX", async function () { + const items = await treeViewPage.getVisibleItems(); + let hasProperFormat = false; + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + hasProperFormat = true; + break; + } + } + }); + }); + describe("Open Ticket by ID", function () { + it("should open ticket panel for valid ID", async function () { + // Use a known ticket ID from mock data + const testTicket = mocks_1.linearMockIssues[0]; + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText(testTicket.identifier); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Ticket panel or details should open + }); + it("should show error for non-existent ticket", async function () { + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("ENG-99999"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should show error notification + }); + }); + describe("Ticket Status Changes", function () { + it("should allow changing ticket status", async function () { + // Find a ticket in the tree view + const items = await treeViewPage.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + // Try to access status change via context menu + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + // Look for status change option + let hasStatusOption = false; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + if (itemLabel.toLowerCase().includes("status") || + itemLabel.toLowerCase().includes("change")) { + hasStatusOption = true; + break; + } + } + await contextMenu.close(); + break; + } + catch { + // Context menu might not be available + } + } + } + }); + it("should show available workflow states", async function () { + // When changing status, available states should be shown + await commandPalettePage.executeDevBuddyCommand("changeTicketStatus"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Should show ticket picker first, then status options + await commandPalettePage.cancel(); + } + }); + }); + describe("Ticket Details View", function () { + it("should display ticket details", async function () { + const items = await treeViewPage.getVisibleItems(); + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + // Click to open details + await item.click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + break; + } + } + // Details view should show ticket information + }); + it("should show ticket priority", async function () { + // When viewing a ticket, priority should be visible + }); + it("should show ticket assignee", async function () { + // When viewing a ticket, assignee should be visible + }); + it("should show ticket labels", async function () { + // When viewing a ticket, labels should be visible + }); + }); +}); +describe("Linear Ticket Creation", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let notificationPage; + let createTicketPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + notificationPage = new NotificationPage_1.NotificationPage(); + createTicketPage = new WebviewPage_1.CreateTicketPage(); + }); + afterEach(async function () { + try { + // Close any open webviews + await createTicketPage.close(); + } + catch { + // No webview open + } + await notificationPage.dismissAll(); + }); + describe("Create Ticket Form", function () { + it("should open create ticket panel", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Either webview opens or dialog appears + }); + it("should require title field", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Try to submit without title + // Should show validation error + }); + it("should show team selection", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Team dropdown should be available + }); + it("should show priority options", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Priority selector should be available + }); + }); + describe("Ticket Creation Flow", function () { + it("should create ticket with minimal fields", async function () { + // Create ticket with just title + }); + it("should create ticket with all fields", async function () { + // Create ticket with all optional fields filled + }); + it("should show success notification on creation", async function () { + // After successful creation, notification should appear + }); + it("should refresh ticket list after creation", async function () { + // New ticket should appear in the list + }); + }); +}); +describe("Linear Ticket Search", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let commandPalettePage; + let sidebarPage; + let treeViewPage; + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + sidebarPage = new SidebarPage_1.SidebarPage(); + treeViewPage = new TreeViewPage_1.TreeViewPage(); + await sidebarPage.open(); + }); + afterEach(async function () { + try { + const inputBox = new vscode_extension_tester_1.InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } + catch { + // No input box + } + }); + describe("Search Functionality", function () { + it("should search tickets by title", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("authentication"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should filter/search tickets + }); + it("should search tickets by identifier", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("ENG-101"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Should find matching ticket + }); + it("should clear search results", async function () { + // First search + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const inputBox = new vscode_extension_tester_1.InputBox(); + await inputBox.setText("test"); + await inputBox.confirm(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Then clear + await commandPalettePage.executeDevBuddyCommand("clearSearch"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Should show all tickets again + }); + }); + describe("Quick Open", function () { + it("should show ticket quick picker", async function () { + await commandPalettePage.executeDevBuddyCommand("quickOpenTicket"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Should show list of tickets + const items = await commandPalettePage.getQuickPickItems(); + await commandPalettePage.cancel(); + } + }); + }); +}); +//# sourceMappingURL=tickets.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/linear/tickets.test.js.map b/test/e2e/suite/linear/tickets.test.js.map new file mode 100644 index 0000000..6a754cf --- /dev/null +++ b/test/e2e/suite/linear/tickets.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tickets.test.js","sourceRoot":"","sources":["tickets.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/linear/tickets.test.ts b/test/e2e/suite/linear/tickets.test.ts new file mode 100644 index 0000000..6a73309 --- /dev/null +++ b/test/e2e/suite/linear/tickets.test.ts @@ -0,0 +1,370 @@ +/** + * Linear Ticket Operations Tests + * + * Tests for Linear ticket management including viewing, + * creating, updating, and status transitions. + */ + +import { expect } from "chai"; +import { Workbench, InputBox } from "vscode-extension-tester"; +import { TestConfig } from "../../utils/testConfig"; +import { sleep, waitFor } from "../../utils/helpers"; +import { CommandPalettePage } from "../../page-objects/CommandPalettePage"; +import { NotificationPage } from "../../page-objects/NotificationPage"; +import { SidebarPage } from "../../page-objects/SidebarPage"; +import { TreeViewPage } from "../../page-objects/TreeViewPage"; +import { + WebviewPage, + TicketPanelPage, + CreateTicketPage, +} from "../../page-objects/WebviewPage"; +import { + setupMockServerHooks, + linearMockIssues, + getLinearIssueByIdentifier, +} from "../../mocks"; + +describe("Linear Ticket Operations", function () { + this.timeout(TestConfig.timeouts.default); + + let workbench: Workbench; + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let sidebarPage: SidebarPage; + let treeViewPage: TreeViewPage; + + setupMockServerHooks("linear"); + + before(async function () { + workbench = new Workbench(); + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + sidebarPage = new SidebarPage(); + treeViewPage = new TreeViewPage(); + + await sidebarPage.open(); + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box open + } + await notificationPage.dismissAll(); + }); + + describe("Ticket Listing", function () { + it("should display assigned tickets", async function () { + await sidebarPage.clickRefresh(); + await sleep(TestConfig.timeouts.apiResponse); + + const items = await treeViewPage.getVisibleItems(); + // Should have some tickets from mock data + }); + + it("should group tickets by status", async function () { + const items = await treeViewPage.getVisibleItems(); + + // Look for status indicators in labels + let hasStatusGrouping = false; + for (const item of items) { + const label = await item.getLabel(); + // Check for status names from mock data + if ( + label.toLowerCase().includes("progress") || + label.toLowerCase().includes("todo") || + label.toLowerCase().includes("backlog") + ) { + hasStatusGrouping = true; + break; + } + } + }); + + it("should show ticket identifiers in format ENG-XXX", async function () { + const items = await treeViewPage.getVisibleItems(); + + let hasProperFormat = false; + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + hasProperFormat = true; + break; + } + } + }); + }); + + describe("Open Ticket by ID", function () { + it("should open ticket panel for valid ID", async function () { + // Use a known ticket ID from mock data + const testTicket = linearMockIssues[0]; + + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText(testTicket.identifier); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Ticket panel or details should open + }); + + it("should show error for non-existent ticket", async function () { + await commandPalettePage.executeDevBuddyCommand("openTicketById"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText("ENG-99999"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should show error notification + }); + }); + + describe("Ticket Status Changes", function () { + it("should allow changing ticket status", async function () { + // Find a ticket in the tree view + const items = await treeViewPage.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + // Try to access status change via context menu + try { + const contextMenu = await item.openContextMenu(); + const menuItems = await contextMenu.getItems(); + + // Look for status change option + let hasStatusOption = false; + for (const menuItem of menuItems) { + const itemLabel = await menuItem.getLabel(); + if ( + itemLabel.toLowerCase().includes("status") || + itemLabel.toLowerCase().includes("change") + ) { + hasStatusOption = true; + break; + } + } + + await contextMenu.close(); + break; + } catch { + // Context menu might not be available + } + } + } + }); + + it("should show available workflow states", async function () { + // When changing status, available states should be shown + await commandPalettePage.executeDevBuddyCommand("changeTicketStatus"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Should show ticket picker first, then status options + await commandPalettePage.cancel(); + } + }); + }); + + describe("Ticket Details View", function () { + it("should display ticket details", async function () { + const items = await treeViewPage.getVisibleItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (/ENG-\d+/.test(label)) { + // Click to open details + await item.click(); + await sleep(TestConfig.timeouts.animation); + break; + } + } + + // Details view should show ticket information + }); + + it("should show ticket priority", async function () { + // When viewing a ticket, priority should be visible + }); + + it("should show ticket assignee", async function () { + // When viewing a ticket, assignee should be visible + }); + + it("should show ticket labels", async function () { + // When viewing a ticket, labels should be visible + }); + }); +}); + +describe("Linear Ticket Creation", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let notificationPage: NotificationPage; + let createTicketPage: CreateTicketPage; + + setupMockServerHooks("linear"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + notificationPage = new NotificationPage(); + createTicketPage = new CreateTicketPage(); + }); + + afterEach(async function () { + try { + // Close any open webviews + await createTicketPage.close(); + } catch { + // No webview open + } + await notificationPage.dismissAll(); + }); + + describe("Create Ticket Form", function () { + it("should open create ticket panel", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await sleep(TestConfig.timeouts.animation); + + // Either webview opens or dialog appears + }); + + it("should require title field", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await sleep(TestConfig.timeouts.animation); + + // Try to submit without title + // Should show validation error + }); + + it("should show team selection", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await sleep(TestConfig.timeouts.animation); + + // Team dropdown should be available + }); + + it("should show priority options", async function () { + await commandPalettePage.executeDevBuddyCommand("createTicket"); + await sleep(TestConfig.timeouts.animation); + + // Priority selector should be available + }); + }); + + describe("Ticket Creation Flow", function () { + it("should create ticket with minimal fields", async function () { + // Create ticket with just title + }); + + it("should create ticket with all fields", async function () { + // Create ticket with all optional fields filled + }); + + it("should show success notification on creation", async function () { + // After successful creation, notification should appear + }); + + it("should refresh ticket list after creation", async function () { + // New ticket should appear in the list + }); + }); +}); + +describe("Linear Ticket Search", function () { + this.timeout(TestConfig.timeouts.default); + + let commandPalettePage: CommandPalettePage; + let sidebarPage: SidebarPage; + let treeViewPage: TreeViewPage; + + setupMockServerHooks("linear"); + + before(async function () { + commandPalettePage = new CommandPalettePage(); + sidebarPage = new SidebarPage(); + treeViewPage = new TreeViewPage(); + + await sidebarPage.open(); + }); + + afterEach(async function () { + try { + const inputBox = new InputBox(); + if (await inputBox.isDisplayed()) { + await inputBox.cancel(); + } + } catch { + // No input box + } + }); + + describe("Search Functionality", function () { + it("should search tickets by title", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText("authentication"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should filter/search tickets + }); + + it("should search tickets by identifier", async function () { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText("ENG-101"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.apiResponse); + + // Should find matching ticket + }); + + it("should clear search results", async function () { + // First search + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + const inputBox = new InputBox(); + await inputBox.setText("test"); + await inputBox.confirm(); + await sleep(TestConfig.timeouts.animation); + + // Then clear + await commandPalettePage.executeDevBuddyCommand("clearSearch"); + await sleep(TestConfig.timeouts.animation); + + // Should show all tickets again + }); + }); + + describe("Quick Open", function () { + it("should show ticket quick picker", async function () { + await commandPalettePage.executeDevBuddyCommand("quickOpenTicket"); + await sleep(TestConfig.timeouts.animation); + + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + // Should show list of tickets + const items = await commandPalettePage.getQuickPickItems(); + await commandPalettePage.cancel(); + } + }); + }); +}); diff --git a/test/e2e/suite/treeview.test.js b/test/e2e/suite/treeview.test.js new file mode 100644 index 0000000..7d0e435 --- /dev/null +++ b/test/e2e/suite/treeview.test.js @@ -0,0 +1,227 @@ +"use strict"; +/** + * TreeView (My Tickets) Tests + * + * Tests for the DevBuddy sidebar tree view functionality, + * including ticket display, grouping, and interactions. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const chai_1 = require("chai"); +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("../utils/testConfig"); +const helpers_1 = require("../utils/helpers"); +const SidebarPage_1 = require("../page-objects/SidebarPage"); +const TreeViewPage_1 = require("../page-objects/TreeViewPage"); +const NotificationPage_1 = require("../page-objects/NotificationPage"); +const CommandPalettePage_1 = require("../page-objects/CommandPalettePage"); +const mocks_1 = require("../mocks"); +describe("TreeView - My Tickets", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let workbench; + let sidebarPage; + let treeViewPage; + let notificationPage; + let commandPalettePage; + // Setup mock servers for all tests in this suite + (0, mocks_1.setupMockServerHooks)("linear"); + before(async function () { + workbench = new vscode_extension_tester_1.Workbench(); + sidebarPage = new SidebarPage_1.SidebarPage(); + treeViewPage = new TreeViewPage_1.TreeViewPage(); + notificationPage = new NotificationPage_1.NotificationPage(); + commandPalettePage = new CommandPalettePage_1.CommandPalettePage(); + // Open the sidebar + await sidebarPage.open(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + }); + afterEach(async function () { + await notificationPage.dismissAll(); + }); + describe("Basic Display", function () { + it("should display the My Tickets section", async function () { + const section = await sidebarPage.getMyTicketsSection(); + (0, chai_1.expect)(section).to.exist; + const title = await section.getTitle(); + (0, chai_1.expect)(title).to.include("Tickets"); + }); + it("should show tickets or empty state", async function () { + await sidebarPage.open(); + // Either tickets should be displayed or an empty/setup message + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + }); + it("should be expandable/collapsible", async function () { + const section = await sidebarPage.getMyTicketsSection(); + // Check if section is collapsible + const isExpanded = await section.isExpanded(); + (0, chai_1.expect)(typeof isExpanded).to.equal("boolean"); + // Try collapsing and expanding + if (isExpanded) { + await section.collapse(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + await section.expand(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + } + }); + }); + describe("Ticket Display", function () { + it("should display ticket identifiers", async function () { + const items = await treeViewPage.getVisibleItems(); + // If there are items, they should have labels + for (const item of items) { + const label = await item.getLabel(); + (0, chai_1.expect)(label).to.be.a("string"); + (0, chai_1.expect)(label.length).to.be.greaterThan(0); + } + }); + it("should be able to find ticket by identifier pattern", async function () { + // Try to find a ticket with the test identifier pattern + const items = await treeViewPage.getVisibleItems(); + // Check if any item matches the expected pattern (e.g., ENG-XXX, TEST-XXX) + let hasMatchingPattern = false; + for (const item of items) { + const label = await item.getLabel(); + if (/[A-Z]+-\d+/.test(label)) { + hasMatchingPattern = true; + break; + } + } + // Note: This might be false if no tickets are loaded + }); + }); + describe("Tree Item Interactions", function () { + it("should have context menu available on items", async function () { + const items = await treeViewPage.getVisibleItems(); + if (items.length > 0) { + const firstItem = items[0]; + const label = await firstItem.getLabel(); + // Check if it's a ticket (not a group header) + if (/[A-Z]+-\d+/.test(label)) { + try { + const contextMenu = await firstItem.openContextMenu(); + const menuItems = await contextMenu.getItems(); + (0, chai_1.expect)(menuItems.length).to.be.greaterThan(0); + await contextMenu.close(); + } + catch (error) { + // Context menu might not be available for all items + } + } + } + }); + it("should be able to click on a ticket item", async function () { + const items = await treeViewPage.getVisibleItems(); + if (items.length > 0) { + const firstItem = items[0]; + const label = await firstItem.getLabel(); + // If it's a ticket, try clicking it + if (/[A-Z]+-\d+/.test(label)) { + await firstItem.click(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // After clicking, something should happen (webview opens, etc.) + // This depends on the extension's implementation + } + } + }); + }); + describe("Refresh Functionality", function () { + it("should be able to refresh tickets via command", async function () { + await commandPalettePage.executeDevBuddyCommand("refreshTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // The sidebar should still be visible after refresh + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + }); + it("should handle refresh without errors", async function () { + await sidebarPage.clickRefresh(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + // Check for errors + const hasErrors = await notificationPage.hasErrors(); + if (hasErrors) { + const errors = await notificationPage.getErrorMessages(); + console.log("Refresh errors:", errors); + } + }); + }); + describe("Status Grouping", function () { + it("should display tickets grouped by status", async function () { + const items = await treeViewPage.getVisibleItems(); + // Look for status group headers (if implemented) + const possibleGroups = [ + "backlog", + "todo", + "in progress", + "done", + "completed", + "cancelled", + "open", + ]; + let hasGroupHeaders = false; + for (const item of items) { + const label = (await item.getLabel()).toLowerCase(); + if (possibleGroups.some((group) => label.includes(group))) { + hasGroupHeaders = true; + break; + } + } + // Note: Grouping might be implemented differently + }); + }); + describe("Search Functionality", function () { + it("should be able to initiate search", async function () { + try { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.animation); + // Check if input box appeared + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + } + catch (error) { + // Search command might not be available in all states + } + }); + }); +}); +describe("TreeView - Empty State", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let sidebarPage; + before(async function () { + sidebarPage = new SidebarPage_1.SidebarPage(); + await sidebarPage.open(); + }); + it("should handle empty ticket list gracefully", async function () { + // When no tickets are loaded, the view should show an appropriate message + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + // The view shouldn't crash or show errors + }); +}); +describe("TreeView - Error States", function () { + this.timeout(testConfig_1.TestConfig.timeouts.default); + let sidebarPage; + let notificationPage; + before(async function () { + sidebarPage = new SidebarPage_1.SidebarPage(); + notificationPage = new NotificationPage_1.NotificationPage(); + await sidebarPage.open(); + }); + afterEach(async function () { + await notificationPage.dismissAll(); + }); + it("should handle API errors gracefully", async function () { + // This test verifies error handling when API fails + // In a real test, we'd mock a failing API response + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + // The view should still be functional even with errors + }); + it("should recover from errors on refresh", async function () { + await sidebarPage.clickRefresh(); + await (0, helpers_1.sleep)(testConfig_1.TestConfig.timeouts.apiResponse); + const isVisible = await sidebarPage.isVisible(); + (0, chai_1.expect)(isVisible).to.be.true; + }); +}); +//# sourceMappingURL=treeview.test.js.map \ No newline at end of file diff --git a/test/e2e/suite/treeview.test.js.map b/test/e2e/suite/treeview.test.js.map new file mode 100644 index 0000000..725ca28 --- /dev/null +++ b/test/e2e/suite/treeview.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"treeview.test.js","sourceRoot":"","sources":["treeview.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/suite/treeview.test.ts b/test/e2e/suite/treeview.test.ts new file mode 100644 index 0000000..c3ed964 --- /dev/null +++ b/test/e2e/suite/treeview.test.ts @@ -0,0 +1,275 @@ +/** + * TreeView (My Tickets) Tests + * + * Tests for the DevBuddy sidebar tree view functionality, + * including ticket display, grouping, and interactions. + */ + +import { expect } from "chai"; +import { Workbench } from "vscode-extension-tester"; +import { TestConfig } from "../utils/testConfig"; +import { sleep, waitFor } from "../utils/helpers"; +import { SidebarPage } from "../page-objects/SidebarPage"; +import { TreeViewPage } from "../page-objects/TreeViewPage"; +import { NotificationPage } from "../page-objects/NotificationPage"; +import { CommandPalettePage } from "../page-objects/CommandPalettePage"; +import { setupMockServerHooks, linearMockIssues } from "../mocks"; + +describe("TreeView - My Tickets", function () { + this.timeout(TestConfig.timeouts.default); + + let workbench: Workbench; + let sidebarPage: SidebarPage; + let treeViewPage: TreeViewPage; + let notificationPage: NotificationPage; + let commandPalettePage: CommandPalettePage; + + // Setup mock servers for all tests in this suite + setupMockServerHooks("linear"); + + before(async function () { + workbench = new Workbench(); + sidebarPage = new SidebarPage(); + treeViewPage = new TreeViewPage(); + notificationPage = new NotificationPage(); + commandPalettePage = new CommandPalettePage(); + + // Open the sidebar + await sidebarPage.open(); + await sleep(TestConfig.timeouts.animation); + }); + + afterEach(async function () { + await notificationPage.dismissAll(); + }); + + describe("Basic Display", function () { + it("should display the My Tickets section", async function () { + const section = await sidebarPage.getMyTicketsSection(); + expect(section).to.exist; + + const title = await section.getTitle(); + expect(title).to.include("Tickets"); + }); + + it("should show tickets or empty state", async function () { + await sidebarPage.open(); + + // Either tickets should be displayed or an empty/setup message + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + }); + + it("should be expandable/collapsible", async function () { + const section = await sidebarPage.getMyTicketsSection(); + + // Check if section is collapsible + const isExpanded = await section.isExpanded(); + expect(typeof isExpanded).to.equal("boolean"); + + // Try collapsing and expanding + if (isExpanded) { + await section.collapse(); + await sleep(TestConfig.timeouts.animation); + + await section.expand(); + await sleep(TestConfig.timeouts.animation); + } + }); + }); + + describe("Ticket Display", function () { + it("should display ticket identifiers", async function () { + const items = await treeViewPage.getVisibleItems(); + + // If there are items, they should have labels + for (const item of items) { + const label = await item.getLabel(); + expect(label).to.be.a("string"); + expect(label.length).to.be.greaterThan(0); + } + }); + + it("should be able to find ticket by identifier pattern", async function () { + // Try to find a ticket with the test identifier pattern + const items = await treeViewPage.getVisibleItems(); + + // Check if any item matches the expected pattern (e.g., ENG-XXX, TEST-XXX) + let hasMatchingPattern = false; + for (const item of items) { + const label = await item.getLabel(); + if (/[A-Z]+-\d+/.test(label)) { + hasMatchingPattern = true; + break; + } + } + + // Note: This might be false if no tickets are loaded + }); + }); + + describe("Tree Item Interactions", function () { + it("should have context menu available on items", async function () { + const items = await treeViewPage.getVisibleItems(); + + if (items.length > 0) { + const firstItem = items[0]; + const label = await firstItem.getLabel(); + + // Check if it's a ticket (not a group header) + if (/[A-Z]+-\d+/.test(label)) { + try { + const contextMenu = await firstItem.openContextMenu(); + const menuItems = await contextMenu.getItems(); + + expect(menuItems.length).to.be.greaterThan(0); + + await contextMenu.close(); + } catch (error) { + // Context menu might not be available for all items + } + } + } + }); + + it("should be able to click on a ticket item", async function () { + const items = await treeViewPage.getVisibleItems(); + + if (items.length > 0) { + const firstItem = items[0]; + const label = await firstItem.getLabel(); + + // If it's a ticket, try clicking it + if (/[A-Z]+-\d+/.test(label)) { + await firstItem.click(); + await sleep(TestConfig.timeouts.animation); + + // After clicking, something should happen (webview opens, etc.) + // This depends on the extension's implementation + } + } + }); + }); + + describe("Refresh Functionality", function () { + it("should be able to refresh tickets via command", async function () { + await commandPalettePage.executeDevBuddyCommand("refreshTickets"); + await sleep(TestConfig.timeouts.apiResponse); + + // The sidebar should still be visible after refresh + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + }); + + it("should handle refresh without errors", async function () { + await sidebarPage.clickRefresh(); + await sleep(TestConfig.timeouts.apiResponse); + + // Check for errors + const hasErrors = await notificationPage.hasErrors(); + if (hasErrors) { + const errors = await notificationPage.getErrorMessages(); + console.log("Refresh errors:", errors); + } + }); + }); + + describe("Status Grouping", function () { + it("should display tickets grouped by status", async function () { + const items = await treeViewPage.getVisibleItems(); + + // Look for status group headers (if implemented) + const possibleGroups = [ + "backlog", + "todo", + "in progress", + "done", + "completed", + "cancelled", + "open", + ]; + + let hasGroupHeaders = false; + for (const item of items) { + const label = (await item.getLabel()).toLowerCase(); + if (possibleGroups.some((group) => label.includes(group))) { + hasGroupHeaders = true; + break; + } + } + + // Note: Grouping might be implemented differently + }); + }); + + describe("Search Functionality", function () { + it("should be able to initiate search", async function () { + try { + await commandPalettePage.executeDevBuddyCommand("searchTickets"); + await sleep(TestConfig.timeouts.animation); + + // Check if input box appeared + const isOpen = await commandPalettePage.isOpen(); + if (isOpen) { + await commandPalettePage.cancel(); + } + } catch (error) { + // Search command might not be available in all states + } + }); + }); +}); + +describe("TreeView - Empty State", function () { + this.timeout(TestConfig.timeouts.default); + + let sidebarPage: SidebarPage; + + before(async function () { + sidebarPage = new SidebarPage(); + await sidebarPage.open(); + }); + + it("should handle empty ticket list gracefully", async function () { + // When no tickets are loaded, the view should show an appropriate message + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + + // The view shouldn't crash or show errors + }); +}); + +describe("TreeView - Error States", function () { + this.timeout(TestConfig.timeouts.default); + + let sidebarPage: SidebarPage; + let notificationPage: NotificationPage; + + before(async function () { + sidebarPage = new SidebarPage(); + notificationPage = new NotificationPage(); + await sidebarPage.open(); + }); + + afterEach(async function () { + await notificationPage.dismissAll(); + }); + + it("should handle API errors gracefully", async function () { + // This test verifies error handling when API fails + // In a real test, we'd mock a failing API response + + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + + // The view should still be functional even with errors + }); + + it("should recover from errors on refresh", async function () { + await sidebarPage.clickRefresh(); + await sleep(TestConfig.timeouts.apiResponse); + + const isVisible = await sidebarPage.isVisible(); + expect(isVisible).to.be.true; + }); +}); diff --git a/test/e2e/utils/helpers.js b/test/e2e/utils/helpers.js new file mode 100644 index 0000000..41dd379 --- /dev/null +++ b/test/e2e/utils/helpers.js @@ -0,0 +1,274 @@ +"use strict"; +/** + * E2E Test Helpers + * + * Common utility functions for E2E tests including wait helpers, + * element finders, and assertion utilities. + */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.waitFor = waitFor; +exports.sleep = sleep; +exports.waitForDevBuddySidebar = waitForDevBuddySidebar; +exports.getMyTicketsSection = getMyTicketsSection; +exports.getTicketItems = getTicketItems; +exports.findTicketByIdentifier = findTicketByIdentifier; +exports.executeCommand = executeCommand; +exports.getInputBox = getInputBox; +exports.waitForNotification = waitForNotification; +exports.dismissAllNotifications = dismissAllNotifications; +exports.openWebviewPanel = openWebviewPanel; +exports.refreshExtension = refreshExtension; +exports.resetExtensionState = resetExtensionState; +exports.getBrowser = getBrowser; +exports.takeScreenshot = takeScreenshot; +exports.assertVisible = assertVisible; +exports.assertContainsText = assertContainsText; +const vscode_extension_tester_1 = require("vscode-extension-tester"); +const testConfig_1 = require("./testConfig"); +/** + * Wait for a condition to be true with configurable timeout + */ +async function waitFor(condition, timeout = testConfig_1.TestConfig.timeouts.default, pollInterval = 100) { + const startTime = Date.now(); + while (Date.now() - startTime < timeout) { + if (await condition()) { + return; + } + await sleep(pollInterval); + } + throw new Error(`Condition not met within ${timeout}ms`); +} +/** + * Sleep for a specified duration + */ +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +/** + * Wait for the DevBuddy sidebar to be visible + */ +async function waitForDevBuddySidebar() { + const workbench = new vscode_extension_tester_1.Workbench(); + // Open the sidebar via command + await workbench.executeCommand("workbench.view.extension.dev-buddy"); + await sleep(testConfig_1.TestConfig.timeouts.animation); + const sidebarView = new vscode_extension_tester_1.SideBarView(); + await waitFor(async () => { + try { + const content = sidebarView.getContent(); + const sections = await content.getSections(); + return sections.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + return sidebarView; +} +/** + * Get the My Tickets view section + */ +async function getMyTicketsSection() { + const sidebar = await waitForDevBuddySidebar(); + const content = sidebar.getContent(); + await waitFor(async () => { + try { + const section = await content.getSection("My Tickets"); + return section !== undefined; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + return content.getSection("My Tickets"); +} +/** + * Get all ticket items from the tree view + */ +async function getTicketItems() { + const section = await getMyTicketsSection(); + await waitFor(async () => { + try { + const items = await section.getVisibleItems(); + return items.length > 0; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + return section.getVisibleItems(); +} +/** + * Find a ticket by its identifier (e.g., "ENG-123") + */ +async function findTicketByIdentifier(identifier) { + const items = await getTicketItems(); + for (const item of items) { + const label = await item.getLabel(); + if (label.includes(identifier)) { + return item; + } + } + return undefined; +} +/** + * Execute a DevBuddy command via the command palette + */ +async function executeCommand(command) { + const workbench = new vscode_extension_tester_1.Workbench(); + await workbench.executeCommand(command); + await sleep(testConfig_1.TestConfig.timeouts.animation); +} +/** + * Get the current input box (command palette, quick pick, etc.) + */ +async function getInputBox() { + return new vscode_extension_tester_1.InputBox(); +} +/** + * Wait for and get a notification by text content + */ +async function waitForNotification(textContains, type) { + const workbench = new vscode_extension_tester_1.Workbench(); + await waitFor(async () => { + const notifications = await workbench.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + if (type) { + const notifType = await notification.getType(); + return notifType === type; + } + return true; + } + } + return false; + }, testConfig_1.TestConfig.timeouts.ui); + const notifications = await workbench.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return notification; + } + } + return undefined; +} +/** + * Dismiss all notifications + */ +async function dismissAllNotifications() { + const workbench = new vscode_extension_tester_1.Workbench(); + const notifications = await workbench.getNotifications(); + for (const notification of notifications) { + await notification.dismiss(); + } +} +/** + * Open a webview panel by command + */ +async function openWebviewPanel(command) { + await executeCommand(command); + const workbench = new vscode_extension_tester_1.Workbench(); + const editorView = new vscode_extension_tester_1.EditorView(); + await waitFor(async () => { + try { + const webview = new vscode_extension_tester_1.WebView(); + await webview.wait(); + return true; + } + catch { + return false; + } + }, testConfig_1.TestConfig.timeouts.ui); + return new vscode_extension_tester_1.WebView(); +} +/** + * Refresh the extension (useful after mock data changes) + */ +async function refreshExtension() { + await executeCommand("devBuddy.refreshTickets"); + await sleep(testConfig_1.TestConfig.timeouts.apiResponse); +} +/** + * Clear all extension state (reset to clean state) + */ +async function resetExtensionState() { + await executeCommand("devBuddy.resetExtension"); + await sleep(testConfig_1.TestConfig.timeouts.animation); +} +/** + * Get the VS Code browser driver + */ +function getBrowser() { + return vscode_extension_tester_1.VSBrowser.instance; +} +/** + * Take a screenshot (useful for debugging failed tests) + */ +async function takeScreenshot(name) { + const browser = getBrowser(); + const driver = browser.driver; + const screenshot = await driver.takeScreenshot(); + // Save to test-results folder + const fs = await Promise.resolve().then(() => __importStar(require("fs"))); + const path = await Promise.resolve().then(() => __importStar(require("path"))); + const resultsDir = path.join(__dirname, "../../../test-results"); + if (!fs.existsSync(resultsDir)) { + fs.mkdirSync(resultsDir, { recursive: true }); + } + const filePath = path.join(resultsDir, `${name}-${Date.now()}.png`); + fs.writeFileSync(filePath, screenshot, "base64"); + return filePath; +} +/** + * Assert element is visible + */ +async function assertVisible(element, message) { + const isDisplayed = await element.isDisplayed(); + if (!isDisplayed) { + throw new Error(message || "Element is not visible"); + } +} +/** + * Assert element contains text + */ +async function assertContainsText(element, expectedText, message) { + const text = await element.getText(); + if (!text.includes(expectedText)) { + throw new Error(message || `Expected "${expectedText}" to be in "${text}"`); + } +} +//# sourceMappingURL=helpers.js.map \ No newline at end of file diff --git a/test/e2e/utils/helpers.js.map b/test/e2e/utils/helpers.js.map new file mode 100644 index 0000000..c258ce6 --- /dev/null +++ b/test/e2e/utils/helpers.js.map @@ -0,0 +1 @@ +{"version":3,"file":"helpers.js","sourceRoot":"","sources":["helpers.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/test/e2e/utils/helpers.ts b/test/e2e/utils/helpers.ts new file mode 100644 index 0000000..5f8a746 --- /dev/null +++ b/test/e2e/utils/helpers.ts @@ -0,0 +1,288 @@ +/** + * E2E Test Helpers + * + * Common utility functions for E2E tests including wait helpers, + * element finders, and assertion utilities. + */ + +import { + Workbench, + SideBarView, + ViewSection, + TreeItem, + InputBox, + Notification, + NotificationType, + WebView, + EditorView, + VSBrowser, +} from "vscode-extension-tester"; +import { TestConfig } from "./testConfig"; + +/** + * Wait for a condition to be true with configurable timeout + */ +export async function waitFor( + condition: () => Promise, + timeout: number = TestConfig.timeouts.default, + pollInterval: number = 100 +): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeout) { + if (await condition()) { + return; + } + await sleep(pollInterval); + } + + throw new Error(`Condition not met within ${timeout}ms`); +} + +/** + * Sleep for a specified duration + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Wait for the DevBuddy sidebar to be visible + */ +export async function waitForDevBuddySidebar(): Promise { + const workbench = new Workbench(); + + // Open the sidebar via command + await workbench.executeCommand("workbench.view.extension.dev-buddy"); + await sleep(TestConfig.timeouts.animation); + + const sidebarView = new SideBarView(); + await waitFor(async () => { + try { + const content = sidebarView.getContent(); + const sections = await content.getSections(); + return sections.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + return sidebarView; +} + +/** + * Get the My Tickets view section + */ +export async function getMyTicketsSection(): Promise { + const sidebar = await waitForDevBuddySidebar(); + const content = sidebar.getContent(); + + await waitFor(async () => { + try { + const section = await content.getSection("My Tickets"); + return section !== undefined; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + return content.getSection("My Tickets"); +} + +/** + * Get all ticket items from the tree view + */ +export async function getTicketItems(): Promise { + const section = await getMyTicketsSection(); + + await waitFor(async () => { + try { + const items = await section.getVisibleItems(); + return items.length > 0; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + const items = await section.getVisibleItems(); + // ViewItem[] can be cast to TreeItem[] for our purposes + return items as unknown as TreeItem[]; +} + +/** + * Find a ticket by its identifier (e.g., "ENG-123") + */ +export async function findTicketByIdentifier( + identifier: string +): Promise { + const items = await getTicketItems(); + + for (const item of items) { + const label = await item.getLabel(); + if (label.includes(identifier)) { + return item; + } + } + + return undefined; +} + +/** + * Execute a DevBuddy command via the command palette + */ +export async function executeCommand(command: string): Promise { + const workbench = new Workbench(); + await workbench.executeCommand(command); + await sleep(TestConfig.timeouts.animation); +} + +/** + * Get the current input box (command palette, quick pick, etc.) + */ +export async function getInputBox(): Promise { + return new InputBox(); +} + +/** + * Wait for and get a notification by text content + */ +export async function waitForNotification( + textContains: string, + type?: NotificationType +): Promise { + const workbench = new Workbench(); + + await waitFor(async () => { + const notifications = await workbench.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + if (type) { + const notifType = await notification.getType(); + return notifType === type; + } + return true; + } + } + return false; + }, TestConfig.timeouts.ui); + + const notifications = await workbench.getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.includes(textContains)) { + return notification; + } + } + + return undefined; +} + +/** + * Dismiss all notifications + */ +export async function dismissAllNotifications(): Promise { + const workbench = new Workbench(); + const notifications = await workbench.getNotifications(); + + for (const notification of notifications) { + await notification.dismiss(); + } +} + +/** + * Open a webview panel by command + */ +export async function openWebviewPanel(command: string): Promise { + await executeCommand(command); + + const workbench = new Workbench(); + const editorView = new EditorView(); + + await waitFor(async () => { + try { + const webview = new WebView(); + await webview.wait(); + return true; + } catch { + return false; + } + }, TestConfig.timeouts.ui); + + return new WebView(); +} + +/** + * Refresh the extension (useful after mock data changes) + */ +export async function refreshExtension(): Promise { + await executeCommand("devBuddy.refreshTickets"); + await sleep(TestConfig.timeouts.apiResponse); +} + +/** + * Clear all extension state (reset to clean state) + */ +export async function resetExtensionState(): Promise { + await executeCommand("devBuddy.resetExtension"); + await sleep(TestConfig.timeouts.animation); +} + +/** + * Get the VS Code browser driver + */ +export function getBrowser(): VSBrowser { + return VSBrowser.instance; +} + +/** + * Take a screenshot (useful for debugging failed tests) + */ +export async function takeScreenshot(name: string): Promise { + const browser = getBrowser(); + const driver = browser.driver; + const screenshot = await driver.takeScreenshot(); + + // Save to test-results folder + const fs = await import("fs"); + const path = await import("path"); + const resultsDir = path.join(__dirname, "../../../test-results"); + + if (!fs.existsSync(resultsDir)) { + fs.mkdirSync(resultsDir, { recursive: true }); + } + + const filePath = path.join(resultsDir, `${name}-${Date.now()}.png`); + fs.writeFileSync(filePath, screenshot, "base64"); + + return filePath; +} + +/** + * Assert element is visible + */ +export async function assertVisible( + element: { isDisplayed: () => Promise }, + message?: string +): Promise { + const isDisplayed = await element.isDisplayed(); + if (!isDisplayed) { + throw new Error(message || "Element is not visible"); + } +} + +/** + * Assert element contains text + */ +export async function assertContainsText( + element: { getText: () => Promise }, + expectedText: string, + message?: string +): Promise { + const text = await element.getText(); + if (!text.includes(expectedText)) { + throw new Error( + message || `Expected "${expectedText}" to be in "${text}"` + ); + } +} diff --git a/test/e2e/utils/testConfig.js b/test/e2e/utils/testConfig.js new file mode 100644 index 0000000..8ba058f --- /dev/null +++ b/test/e2e/utils/testConfig.js @@ -0,0 +1,78 @@ +"use strict"; +/** + * E2E Test Configuration + * + * Central configuration for all E2E tests including timeouts, mock server ports, + * and test environment settings. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TestConfig = void 0; +exports.getMockServerUrl = getMockServerUrl; +exports.shouldUseRealApi = shouldUseRealApi; +exports.TestConfig = { + // Timeouts (in milliseconds) + timeouts: { + default: 30000, + activation: 15000, + ui: 10000, + apiResponse: 5000, + animation: 500, + }, + // Mock server configuration + mockServer: { + linearPort: 4001, + jiraPort: 4002, + host: "localhost", + }, + // Test data identifiers + testData: { + linear: { + teamId: "test-team-id", + projectId: "test-project-id", + userId: "test-user-id", + orgSlug: "test-org", + }, + jira: { + projectKey: "TEST", + siteUrl: "test.atlassian.net", + email: "test@example.com", + }, + }, + // Extension identifiers + extension: { + id: "angelogirardi.dev-buddy", + displayName: "DevBuddy - Linear & Jira Workflow Manager", + viewId: "myTickets", + viewContainerId: "dev-buddy", + }, + // Command prefixes + commands: { + prefix: "devBuddy", + jiraPrefix: "devBuddy.jira", + }, + // Environment flags + env: { + useRealApi: process.env.DEVBUDDY_TEST_REAL_API === "true", + debugMode: process.env.DEVBUDDY_TEST_DEBUG === "true", + linearToken: process.env.DEVBUDDY_TEST_LINEAR_TOKEN, + jiraToken: process.env.DEVBUDDY_TEST_JIRA_TOKEN, + }, +}; +/** + * Get mock server URL for a specific platform + */ +function getMockServerUrl(platform) { + const { host } = exports.TestConfig.mockServer; + const port = platform === "linear" + ? exports.TestConfig.mockServer.linearPort + : exports.TestConfig.mockServer.jiraPort; + return `http://${host}:${port}`; +} +/** + * Check if tests should use real APIs + */ +function shouldUseRealApi() { + return exports.TestConfig.env.useRealApi; +} +exports.default = exports.TestConfig; +//# sourceMappingURL=testConfig.js.map \ No newline at end of file diff --git a/test/e2e/utils/testConfig.js.map b/test/e2e/utils/testConfig.js.map new file mode 100644 index 0000000..43e6ce2 --- /dev/null +++ b/test/e2e/utils/testConfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"testConfig.js","sourceRoot":"","sources":["testConfig.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AA4DH,4CASC;AAKD,4CAEC;AA1EY,QAAA,UAAU,GAAG;IACxB,6BAA6B;IAC7B,QAAQ,EAAE;QACR,OAAO,EAAE,KAAK;QACd,UAAU,EAAE,KAAK;QACjB,EAAE,EAAE,KAAK;QACT,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,GAAG;KACf;IAED,4BAA4B;IAC5B,UAAU,EAAE;QACV,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,WAAW;KAClB;IAED,wBAAwB;IACxB,QAAQ,EAAE;QACR,MAAM,EAAE;YACN,MAAM,EAAE,cAAc;YACtB,SAAS,EAAE,iBAAiB;YAC5B,MAAM,EAAE,cAAc;YACtB,OAAO,EAAE,UAAU;SACpB;QACD,IAAI,EAAE;YACJ,UAAU,EAAE,MAAM;YAClB,OAAO,EAAE,oBAAoB;YAC7B,KAAK,EAAE,kBAAkB;SAC1B;KACF;IAED,wBAAwB;IACxB,SAAS,EAAE;QACT,EAAE,EAAE,yBAAyB;QAC7B,WAAW,EAAE,2CAA2C;QACxD,MAAM,EAAE,WAAW;QACnB,eAAe,EAAE,WAAW;KAC7B;IAED,mBAAmB;IACnB,QAAQ,EAAE;QACR,MAAM,EAAE,UAAU;QAClB,UAAU,EAAE,eAAe;KAC5B;IAED,oBAAoB;IACpB,GAAG,EAAE;QACH,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,MAAM;QACzD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,MAAM;QACrD,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B;QACnD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB;KAChD;CACF,CAAC;AAEF;;GAEG;AACH,SAAgB,gBAAgB,CAC9B,QAA2B;IAE3B,MAAM,EAAE,IAAI,EAAE,GAAG,kBAAU,CAAC,UAAU,CAAC;IACvC,MAAM,IAAI,GACR,QAAQ,KAAK,QAAQ;QACnB,CAAC,CAAC,kBAAU,CAAC,UAAU,CAAC,UAAU;QAClC,CAAC,CAAC,kBAAU,CAAC,UAAU,CAAC,QAAQ,CAAC;IACrC,OAAO,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,kBAAU,CAAC,GAAG,CAAC,UAAU,CAAC;AACnC,CAAC;AAED,kBAAe,kBAAU,CAAC"} \ No newline at end of file diff --git a/test/e2e/utils/testConfig.ts b/test/e2e/utils/testConfig.ts new file mode 100644 index 0000000..5163868 --- /dev/null +++ b/test/e2e/utils/testConfig.ts @@ -0,0 +1,84 @@ +/** + * E2E Test Configuration + * + * Central configuration for all E2E tests including timeouts, mock server ports, + * and test environment settings. + */ + +export const TestConfig = { + // Timeouts (in milliseconds) + timeouts: { + default: 30000, + activation: 15000, + ui: 10000, + apiResponse: 5000, + animation: 500, + }, + + // Mock server configuration + mockServer: { + linearPort: 4001, + jiraPort: 4002, + host: "localhost", + }, + + // Test data identifiers + testData: { + linear: { + teamId: "test-team-id", + projectId: "test-project-id", + userId: "test-user-id", + orgSlug: "test-org", + }, + jira: { + projectKey: "TEST", + siteUrl: "test.atlassian.net", + email: "test@example.com", + }, + }, + + // Extension identifiers + extension: { + id: "angelogirardi.dev-buddy", + displayName: "DevBuddy - Linear & Jira Workflow Manager", + viewId: "myTickets", + viewContainerId: "dev-buddy", + }, + + // Command prefixes + commands: { + prefix: "devBuddy", + jiraPrefix: "devBuddy.jira", + }, + + // Environment flags + env: { + useRealApi: process.env.DEVBUDDY_TEST_REAL_API === "true", + debugMode: process.env.DEVBUDDY_TEST_DEBUG === "true", + linearToken: process.env.DEVBUDDY_TEST_LINEAR_TOKEN, + jiraToken: process.env.DEVBUDDY_TEST_JIRA_TOKEN, + }, +}; + +/** + * Get mock server URL for a specific platform + */ +export function getMockServerUrl( + platform: "linear" | "jira" +): string { + const { host } = TestConfig.mockServer; + const port = + platform === "linear" + ? TestConfig.mockServer.linearPort + : TestConfig.mockServer.jiraPort; + return `http://${host}:${port}`; +} + +/** + * Check if tests should use real APIs + */ +export function shouldUseRealApi(): boolean { + return TestConfig.env.useRealApi; +} + +export default TestConfig; diff --git a/test/fixtures/settings.json b/test/fixtures/settings.json new file mode 100644 index 0000000..7fc95ed --- /dev/null +++ b/test/fixtures/settings.json @@ -0,0 +1,13 @@ +{ + "devBuddy.provider": "linear", + "devBuddy.debugMode": true, + "devBuddy.linearOrganization": "test-org", + "devBuddy.autoRefreshInterval": 0, + "devBuddy.jira.autoRefreshInterval": 0, + "devBuddy.telemetry.optOut": true, + "devBuddy.ai.disabled": true, + "workbench.startupEditor": "none", + "workbench.colorTheme": "Default Dark Modern", + "window.restoreWindows": "none", + "telemetry.telemetryLevel": "off" +} diff --git a/test/fixtures/workspaces/test-monorepo b/test/fixtures/workspaces/test-monorepo new file mode 160000 index 0000000..2895562 --- /dev/null +++ b/test/fixtures/workspaces/test-monorepo @@ -0,0 +1 @@ +Subproject commit 28955627ec7a80a577bd1f908464c07189a1c29b diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..c468f70 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "../out/test", + "lib": ["ES2020", "DOM"], + "sourceMap": true, + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules", "fixtures"] +} diff --git a/tsconfig.json b/tsconfig.json index d2f4595..7ceb884 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,7 @@ "@views/*": ["views/*"] } }, - "exclude": ["node_modules", ".vscode-test", "webview-ui"] + "exclude": ["node_modules", ".vscode-test", "webview-ui", "test"] }