diff --git a/bin/commands/issue.js b/bin/commands/issue.js index 4fd15d9..05a07d1 100644 --- a/bin/commands/issue.js +++ b/bin/commands/issue.js @@ -398,7 +398,7 @@ async function getIssue(client, io, issueKey, options = {}) { const markdown = formatIssueAsMarkdown(issue); io.out('\n' + markdown); } else { - displayIssueDetails(issue); + displayIssueDetails(issue, io); } } catch (err) { diff --git a/lib/utils.js b/lib/utils.js index db5cc80..78ad207 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -233,31 +233,31 @@ function formatIssueAsMarkdown(issue) { } // Display issue details -function displayIssueDetails(issue) { +function displayIssueDetails(issue, io) { if (!issue || !issue.fields) { throw new Error('Unexpected API response: issue fields missing. Try setting API version with: jira config --api-version 2'); } - console.log(chalk.bold(`\n${issue.key}: ${issue.fields.summary}`)); - console.log(chalk.gray('─'.repeat(60))); + io.println(chalk.bold(`\n${issue.key}: ${issue.fields.summary}`)); + io.println(chalk.gray('─'.repeat(60))); - console.log(`${chalk.bold('Status:')} ${chalk.yellow(issue.fields.status.name)}`); - console.log(`${chalk.bold('Type:')} ${issue.fields.issuetype.name}`); - console.log(`${chalk.bold('Priority:')} ${issue.fields.priority ? issue.fields.priority.name : 'N/A'}`); - console.log(`${chalk.bold('Assignee:')} ${issue.fields.assignee ? issue.fields.assignee.displayName : 'Unassigned'}`); - console.log(`${chalk.bold('Reporter:')} ${issue.fields.reporter ? issue.fields.reporter.displayName : 'N/A'}`); - console.log(`${chalk.bold('Created:')} ${formatDate(issue.fields.created)}`); - console.log(`${chalk.bold('Updated:')} ${formatDate(issue.fields.updated)}`); + io.println(`${chalk.bold('Status:')} ${chalk.yellow(issue.fields.status.name)}`); + io.println(`${chalk.bold('Type:')} ${issue.fields.issuetype.name}`); + io.println(`${chalk.bold('Priority:')} ${issue.fields.priority ? issue.fields.priority.name : 'N/A'}`); + io.println(`${chalk.bold('Assignee:')} ${issue.fields.assignee ? issue.fields.assignee.displayName : 'Unassigned'}`); + io.println(`${chalk.bold('Reporter:')} ${issue.fields.reporter ? issue.fields.reporter.displayName : 'N/A'}`); + io.println(`${chalk.bold('Created:')} ${formatDate(issue.fields.created)}`); + io.println(`${chalk.bold('Updated:')} ${formatDate(issue.fields.updated)}`); if (issue.fields.description) { - console.log(`\n${chalk.bold('Description:')}`); - console.log(resolveDescription(issue.fields.description)); + io.println(`\n${chalk.bold('Description:')}`); + io.println(resolveDescription(issue.fields.description)); } if (issue.fields.labels && issue.fields.labels.length > 0) { - console.log(`\n${chalk.bold('Labels:')} ${issue.fields.labels.join(', ')}`); + io.println(`\n${chalk.bold('Labels:')} ${issue.fields.labels.join(', ')}`); } - console.log(`\n${chalk.bold('URL:')} ${issue.self.replace(new RegExp(`/rest/api/(2|3)/issue/${issue.id}`), '/browse/' + issue.key)}`); + io.println(`\n${chalk.bold('URL:')} ${issue.self.replace(new RegExp(`/rest/api/(2|3)/issue/${issue.id}`), '/browse/' + issue.key)}`); } // Escape a value for safe use inside a JQL double-quoted string literal. @@ -396,26 +396,6 @@ function buildJQL(options) { return base || 'ORDER BY updated DESC'; } -// Success message -function success(message) { - console.log(chalk.green('✓'), message); -} - -// Error message -function error(message) { - console.error(chalk.red('✗'), message); -} - -// Warning message -function warning(message) { - console.log(chalk.yellow('⚠'), message); -} - -// Info message -function info(message) { - console.log(chalk.blue('ℹ'), message); -} - // Create table for comments function createCommentsTable(comments) { const table = new Table({ @@ -474,23 +454,6 @@ function createRemoteLinksTable(remoteLinks) { return table; } -// Display single comment details -function displayCommentDetails(comment) { - console.log(chalk.bold(`\nComment ID: ${comment.id}`)); - console.log(chalk.gray('─'.repeat(60))); - console.log(`${chalk.bold('Author:')} ${comment.author ? comment.author.displayName : 'Unknown'}`); - console.log(`${chalk.bold('Created:')} ${formatDate(comment.created)}`); - console.log(`${chalk.bold('Updated:')} ${formatDate(comment.updated)}`); - - if (comment.visibility) { - console.log(`${chalk.bold('Visibility:')} ${comment.visibility.type} - ${comment.visibility.value}`); - } - - console.log(`\n${chalk.bold('Body:')}`); - console.log(comment.body || 'No content'); - console.log(''); -} - module.exports = { formatDate, formatIssueForTable, @@ -501,13 +464,8 @@ module.exports = { formatIssueAsMarkdown, buildJQL, escapeJqlString, - success, - error, - warning, - info, createCommentsTable, createRemoteLinksTable, - displayCommentDetails, convertAdfToText, resolveDescription, expandHomePath diff --git a/tests/utils.test.js b/tests/utils.test.js index 9426f74..f7278cc 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -257,6 +257,47 @@ describe('Utils', () => { }); }); + describe('displayIssueDetails', () => { + function createIo() { + const lines = []; + return { + lines, + println: (msg = '') => lines.push(msg) + }; + } + + const baseIssue = { + key: 'TEST-1', + id: '10001', + self: 'https://jira.example.com/rest/api/3/issue/10001', + fields: { + summary: 'Sample', + status: { name: 'Open' }, + issuetype: { name: 'Bug' }, + priority: { name: 'High' }, + assignee: { displayName: 'John' }, + reporter: { displayName: 'Jane' }, + created: '2024-01-01T00:00:00.000Z', + updated: '2024-01-02T00:00:00.000Z' + } + }; + + it('should write to the provided IOStreams instead of console', () => { + const io = createIo(); + Utils.displayIssueDetails(baseIssue, io); + const output = io.lines.join('\n'); + expect(output).toContain('TEST-1'); + expect(output).toContain('Sample'); + expect(output).toContain('Open'); + expect(output).toContain('/browse/TEST-1'); + }); + + it('should throw if issue fields are missing', () => { + const io = createIo(); + expect(() => Utils.displayIssueDetails({}, io)).toThrow(/issue fields missing/); + }); + }); + describe('formatIssueAsMarkdown with ADF description', () => { it('should render ADF description as text instead of [object Object]', () => { const issue = {