diff --git a/.eslintrc.js b/.eslintrc.js index ff627feb1..14d1f4f06 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,6 +36,7 @@ module.exports = { { files: [ // TODO: add packages as they are migrated to ESM. + './packages/ckeditor5-dev-stale-bot/**/*', './packages/ckeditor5-dev-ci/**/*' ], rules: { diff --git a/packages/ckeditor5-dev-stale-bot/bin/stale-bot.js b/packages/ckeditor5-dev-stale-bot/bin/stale-bot.js index d08a2a800..759f420e1 100755 --- a/packages/ckeditor5-dev-stale-bot/bin/stale-bot.js +++ b/packages/ckeditor5-dev-stale-bot/bin/stale-bot.js @@ -5,15 +5,13 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const fs = require( 'fs-extra' ); -const chalk = require( 'chalk' ); -const createSpinner = require( './utils/createspinner' ); -const parseArguments = require( './utils/parsearguments' ); -const validateConfig = require( './utils/validateconfig' ); -const parseConfig = require( './utils/parseconfig' ); -const GitHubRepository = require( '../lib/githubrepository' ); +import fs from 'fs-extra'; +import chalk from 'chalk'; +import createSpinner from './utils/createspinner.js'; +import parseArguments from './utils/parsearguments.js'; +import validateConfig from './utils/validateconfig.js'; +import parseConfig from './utils/parseconfig.js'; +import GitHubRepository from '../lib/githubrepository.js'; main().catch( error => { console.error( '\nšŸ”„ Unable to process stale issues and pull requests.\n', error ); @@ -34,7 +32,7 @@ async function main() { throw new Error( 'Missing or invalid CLI argument: --config-path' ); } - const config = require( configPath ); + const config = await import( configPath ); validateConfig( config ); @@ -286,7 +284,7 @@ function printStatus( dryRun, searchResult, options ) { /** * Prints in the console issues and pull requests from a single section. * - * @param {String} statusMessage Seaction header. + * @param {String} statusMessage Section header. * @param {Array.} entries Found issues and pull requests. */ function printStatusSection( statusMessage, entries ) { diff --git a/packages/ckeditor5-dev-stale-bot/bin/utils/createspinner.js b/packages/ckeditor5-dev-stale-bot/bin/utils/createspinner.js index 9c74f1b72..1da67107d 100644 --- a/packages/ckeditor5-dev-stale-bot/bin/utils/createspinner.js +++ b/packages/ckeditor5-dev-stale-bot/bin/utils/createspinner.js @@ -3,17 +3,15 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const ora = require( 'ora' ); -const chalk = require( 'chalk' ); +import ora from 'ora'; +import chalk from 'chalk'; /** * Creates the spinner instance with methods to update spinner text. * * @returns {Spinner} */ -module.exports = function createSpinner() { +export default function createSpinner() { const instance = ora(); const printStatus = text => { @@ -40,7 +38,7 @@ module.exports = function createSpinner() { printStatus, onProgress }; -}; +} /** * @typedef {Object} Spinner diff --git a/packages/ckeditor5-dev-stale-bot/bin/utils/parsearguments.js b/packages/ckeditor5-dev-stale-bot/bin/utils/parsearguments.js index ac394ea3e..1f6220d23 100644 --- a/packages/ckeditor5-dev-stale-bot/bin/utils/parsearguments.js +++ b/packages/ckeditor5-dev-stale-bot/bin/utils/parsearguments.js @@ -3,10 +3,8 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const minimist = require( 'minimist' ); -const upath = require( 'upath' ); +import minimist from 'minimist'; +import upath from 'upath'; /** * Parses CLI arguments. @@ -16,7 +14,7 @@ const upath = require( 'upath' ); * @returns {Boolean} result.dryRun * @returns {String} result.configPath */ -module.exports = function parseArguments( args ) { +export default function parseArguments( args ) { const config = { boolean: [ 'dry-run' @@ -38,4 +36,4 @@ module.exports = function parseArguments( args ) { dryRun: options[ 'dry-run' ], configPath: upath.join( process.cwd(), options[ 'config-path' ] ) }; -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/bin/utils/parseconfig.js b/packages/ckeditor5-dev-stale-bot/bin/utils/parseconfig.js index a397e1b84..ccfa3e54b 100644 --- a/packages/ckeditor5-dev-stale-bot/bin/utils/parseconfig.js +++ b/packages/ckeditor5-dev-stale-bot/bin/utils/parseconfig.js @@ -3,9 +3,7 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { subDays, formatISO } = require( 'date-fns' ); +import { subDays, formatISO } from 'date-fns'; /** * Converts configuration options into format required by the GitHubRepository. @@ -14,7 +12,7 @@ const { subDays, formatISO } = require( 'date-fns' ); * @param {Config} config Configuration options. * @returns {Options} */ -module.exports = function parseConfig( viewerLogin, config ) { +export default function parseConfig( viewerLogin, config ) { const { REPOSITORY_SLUG, STALE_LABELS, @@ -63,7 +61,7 @@ module.exports = function parseConfig( viewerLogin, config ) { [ ...IGNORED_ACTIVITY_LOGINS, viewerLogin ] : IGNORED_ACTIVITY_LOGINS }; -}; +} /** * @typedef {Object} Config diff --git a/packages/ckeditor5-dev-stale-bot/bin/utils/validateconfig.js b/packages/ckeditor5-dev-stale-bot/bin/utils/validateconfig.js index 0a5f363d5..8a4dc534c 100644 --- a/packages/ckeditor5-dev-stale-bot/bin/utils/validateconfig.js +++ b/packages/ckeditor5-dev-stale-bot/bin/utils/validateconfig.js @@ -3,8 +3,6 @@ * For licensing, see LICENSE.md. */ -'use strict'; - const requiredFields = [ 'GITHUB_TOKEN', 'REPOSITORY_SLUG', @@ -23,7 +21,7 @@ const requiredFields = [ * @param {Config} config Configuration options. * @returns {void} */ -module.exports = function validateConfig( config ) { +export default function validateConfig( config ) { const missingFields = requiredFields.filter( fieldName => !config[ fieldName ] ); if ( !missingFields.length ) { @@ -31,5 +29,5 @@ module.exports = function validateConfig( config ) { } throw new Error( `Missing configuration options: ${ missingFields.join( ', ' ) }.` ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/githubrepository.js b/packages/ckeditor5-dev-stale-bot/lib/githubrepository.js index 9a6bfb1a4..189373de4 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/githubrepository.js +++ b/packages/ckeditor5-dev-stale-bot/lib/githubrepository.js @@ -3,47 +3,32 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const upath = require( 'upath' ); -const fs = require( 'fs-extra' ); -const { GraphQLClient } = require( 'graphql-request' ); -const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); -const { +import upath from 'upath'; +import fs from 'fs-extra'; +import { GraphQLClient } from 'graphql-request'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import { addSeconds, fromUnixTime, formatDistanceToNow, differenceInSeconds -} = require( 'date-fns' ); -const prepareSearchQuery = require( './utils/preparesearchquery' ); -const isIssueOrPullRequestToStale = require( './utils/isissueorpullrequesttostale' ); -const isIssueOrPullRequestToUnstale = require( './utils/isissueorpullrequesttounstale' ); -const isIssueOrPullRequestToClose = require( './utils/isissueorpullrequesttoclose' ); -const isPendingIssueToStale = require( './utils/ispendingissuetostale' ); -const isPendingIssueToUnlabel = require( './utils/ispendingissuetounlabel' ); +} from 'date-fns'; +import prepareSearchQuery from './utils/preparesearchquery.js'; +import isIssueOrPullRequestToStale from './utils/isissueorpullrequesttostale.js'; +import isIssueOrPullRequestToUnstale from './utils/isissueorpullrequesttounstale.js'; +import isIssueOrPullRequestToClose from './utils/isissueorpullrequesttoclose.js'; +import isPendingIssueToStale from './utils/ispendingissuetostale.js'; +import isPendingIssueToUnlabel from './utils/ispendingissuetounlabel.js'; const GRAPHQL_PATH = upath.join( __dirname, 'graphql' ); -const queries = { - getViewerLogin: readGraphQL( 'getviewerlogin' ), - searchIssuesOrPullRequests: readGraphQL( 'searchissuesorpullrequests' ), - searchPendingIssues: readGraphQL( 'searchpendingissues' ), - getIssueOrPullRequestTimelineItems: readGraphQL( 'getissueorpullrequesttimelineitems' ), - addComment: readGraphQL( 'addcomment' ), - getLabels: readGraphQL( 'getlabels' ), - addLabels: readGraphQL( 'addlabels' ), - removeLabels: readGraphQL( 'removelabels' ), - closeIssue: readGraphQL( 'closeissue' ), - closePullRequest: readGraphQL( 'closepullrequest' ) -}; - /** * A GitHub client containing methods used to interact with GitHub using its GraphQL API. * * All methods handles paginated data and it supports a case when a request has exceeded the GitHub API rate limit. * In such a case, the request waits until the limit is reset and it is automatically sent again. */ -module.exports = class GitHubRepository { +export default class GitHubRepository { constructor( authToken ) { /** * @private @@ -63,6 +48,22 @@ module.exports = class GitHubRepository { * @property {Logger} */ this.logger = logger(); + + /** + * @private + */ + this.queries = { + getViewerLogin: readGraphQL( 'getviewerlogin' ), + searchIssuesOrPullRequests: readGraphQL( 'searchissuesorpullrequests' ), + searchPendingIssues: readGraphQL( 'searchpendingissues' ), + getIssueOrPullRequestTimelineItems: readGraphQL( 'getissueorpullrequesttimelineitems' ), + addComment: readGraphQL( 'addcomment' ), + getLabels: readGraphQL( 'getlabels' ), + addLabels: readGraphQL( 'addlabels' ), + removeLabels: readGraphQL( 'removelabels' ), + closeIssue: readGraphQL( 'closeissue' ), + closePullRequest: readGraphQL( 'closepullrequest' ) + }; } /** @@ -71,7 +72,7 @@ module.exports = class GitHubRepository { * @returns {Promise.} */ async getViewerLogin() { - return this.sendRequest( await queries.getViewerLogin ) + return this.sendRequest( await this.queries.getViewerLogin ) .then( data => data.viewer.login ) .catch( error => { this.logger.error( 'Unexpected error when executing "#getViewerLogin()".', error ); @@ -105,7 +106,7 @@ module.exports = class GitHubRepository { cursor: pageInfo.cursor || null }; - return this.sendRequest( await queries.searchIssuesOrPullRequests, variables ) + return this.sendRequest( await this.queries.searchIssuesOrPullRequests, variables ) .then( async data => { const issuesOrPullRequests = await this.parseIssuesOrPullRequests( data.search ); @@ -151,7 +152,7 @@ module.exports = class GitHubRepository { cursor: pageInfo.cursor || null }; - return this.sendRequest( await queries.searchIssuesOrPullRequests, variables ) + return this.sendRequest( await this.queries.searchIssuesOrPullRequests, variables ) .then( async data => { const issuesOrPullRequests = await this.parseIssuesOrPullRequests( data.search ); @@ -217,7 +218,7 @@ module.exports = class GitHubRepository { cursor: pageInfo.cursor || null }; - return this.sendRequest( await queries.searchPendingIssues, variables ) + return this.sendRequest( await this.queries.searchPendingIssues, variables ) .then( async data => { const pendingIssues = this.parsePendingIssues( data.search ); @@ -263,7 +264,7 @@ module.exports = class GitHubRepository { cursor: pageInfo.cursor || null }; - return this.sendRequest( await queries.getIssueOrPullRequestTimelineItems, variables ) + return this.sendRequest( await this.queries.getIssueOrPullRequestTimelineItems, variables ) .then( async data => { pageInfo = data.node.timelineItems.pageInfo; @@ -295,7 +296,7 @@ module.exports = class GitHubRepository { comment }; - return this.sendRequest( await queries.addComment, variables ) + return this.sendRequest( await this.queries.addComment, variables ) .catch( error => { this.logger.error( 'Unexpected error when executing "#addComment()".', error ); @@ -322,7 +323,7 @@ module.exports = class GitHubRepository { labelNames: labelNames.join( ' ' ) }; - return this.sendRequest( await queries.getLabels, variables ) + return this.sendRequest( await this.queries.getLabels, variables ) .then( data => { return data.repository.labels.nodes // Additional filtering is needed, because GitHub endpoint may return many more results than match the query. @@ -349,7 +350,7 @@ module.exports = class GitHubRepository { labelIds }; - return this.sendRequest( await queries.addLabels, variables ) + return this.sendRequest( await this.queries.addLabels, variables ) .catch( error => { this.logger.error( 'Unexpected error when executing "#addLabels()".', error ); @@ -370,7 +371,7 @@ module.exports = class GitHubRepository { labelIds }; - return this.sendRequest( await queries.removeLabels, variables ) + return this.sendRequest( await this.queries.removeLabels, variables ) .catch( error => { this.logger.error( 'Unexpected error when executing "#removeLabels()".', error ); @@ -390,7 +391,7 @@ module.exports = class GitHubRepository { nodeId }; - const query = type === 'Issue' ? await queries.closeIssue : await queries.closePullRequest; + const query = type === 'Issue' ? await this.queries.closeIssue : await this.queries.closePullRequest; return this.sendRequest( query, variables ) .catch( error => { @@ -556,7 +557,7 @@ module.exports = class GitHubRepository { return Promise.reject( error ); } ); } -}; +} /** * Reads the GraphQL query from filesystem. diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/findstaledate.js b/packages/ckeditor5-dev-stale-bot/lib/utils/findstaledate.js index 51d738c8e..f4156042c 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/findstaledate.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/findstaledate.js @@ -3,9 +3,7 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { isAfter, parseISO } = require( 'date-fns' ); +import { isAfter, parseISO } from 'date-fns'; /** * Finds the most recent event date of the stale label assignment to issue or pull request. @@ -14,7 +12,7 @@ const { isAfter, parseISO } = require( 'date-fns' ); * @param {Options} options Configuration options. * @returns {String} */ -module.exports = function findStaleDate( issueOrPullRequest, options ) { +export default function findStaleDate( issueOrPullRequest, options ) { const { staleLabels } = options; return issueOrPullRequest.timelineItems @@ -27,4 +25,4 @@ module.exports = function findStaleDate( issueOrPullRequest, options ) { } ) .find( entry => staleLabels.includes( entry.label ) ) .eventDate; -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequestactive.js b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequestactive.js index 3193a3c82..843082da3 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequestactive.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequestactive.js @@ -3,9 +3,7 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { isAfter, parseISO } = require( 'date-fns' ); +import { isAfter, parseISO } from 'date-fns'; /** * Verifies dates from an issue or pull request to check if some of them occurred after the provided moment, meaning that the issue or pull @@ -25,7 +23,7 @@ const { isAfter, parseISO } = require( 'date-fns' ); * @param {Options} options Configuration options. * @returns {Boolean} */ -module.exports = function isIssueOrPullRequestActive( issueOrPullRequest, staleDate, options ) { +export default function isIssueOrPullRequestActive( issueOrPullRequest, staleDate, options ) { const { ignoredActivityLogins, ignoredActivityLabels } = options; const dates = [ @@ -53,4 +51,4 @@ module.exports = function isIssueOrPullRequestActive( issueOrPullRequest, staleD return dates .filter( Boolean ) .some( date => isAfter( parseISO( date ), parseISO( staleDate ) ) ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttoclose.js b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttoclose.js index 9a024721f..97b5b70a9 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttoclose.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttoclose.js @@ -3,11 +3,9 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { isAfter, parseISO } = require( 'date-fns' ); -const isIssueOrPullRequestActive = require( './isissueorpullrequestactive' ); -const findStaleDate = require( './findstaledate' ); +import { isAfter, parseISO } from 'date-fns'; +import isIssueOrPullRequestActive from './isissueorpullrequestactive.js'; +import findStaleDate from './findstaledate.js'; /** * Checks whether the time to close a stale issue or pull request has passed and whether it is still inactive. @@ -16,7 +14,7 @@ const findStaleDate = require( './findstaledate' ); * @param {Options} options Configuration options. * @returns {Boolean} */ -module.exports = function isIssueOrPullRequestToClose( issueOrPullRequest, options ) { +export default function isIssueOrPullRequestToClose( issueOrPullRequest, options ) { const staleDate = findStaleDate( issueOrPullRequest, options ); const hasTimeToClosePassed = isAfter( parseISO( options.closeDate ), parseISO( staleDate ) ); @@ -25,4 +23,4 @@ module.exports = function isIssueOrPullRequestToClose( issueOrPullRequest, optio } return !isIssueOrPullRequestActive( issueOrPullRequest, staleDate, options ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttostale.js b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttostale.js index 42ad9ba0a..12c6c12cf 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttostale.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttostale.js @@ -3,9 +3,7 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const isIssueOrPullRequestActive = require( './isissueorpullrequestactive' ); +import isIssueOrPullRequestActive from './isissueorpullrequestactive.js'; /** * Checks whether issue or pull request should be staled, because it was not active since the defined moment of time. @@ -14,8 +12,8 @@ const isIssueOrPullRequestActive = require( './isissueorpullrequestactive' ); * @param {Options} options Configuration options. * @returns {Boolean} */ -module.exports = function isIssueOrPullRequestToStale( issueOrPullRequest, options ) { +export default function isIssueOrPullRequestToStale( issueOrPullRequest, options ) { const { staleDate } = options; return !isIssueOrPullRequestActive( issueOrPullRequest, staleDate, options ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttounstale.js b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttounstale.js index b287a2681..7d0d04348 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttounstale.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/isissueorpullrequesttounstale.js @@ -3,10 +3,8 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const isIssueOrPullRequestActive = require( './isissueorpullrequestactive' ); -const findStaleDate = require( './findstaledate' ); +import isIssueOrPullRequestActive from './isissueorpullrequestactive.js'; +import findStaleDate from './findstaledate.js'; /** * Checks whether issue or pull request should be unstaled, because it was active after it was staled. @@ -15,8 +13,8 @@ const findStaleDate = require( './findstaledate' ); * @param {Options} options Configuration options. * @returns {Boolean} */ -module.exports = function isIssueOrPullRequestToUnstale( issueOrPullRequest, options ) { +export default function isIssueOrPullRequestToUnstale( issueOrPullRequest, options ) { const staleDate = findStaleDate( issueOrPullRequest, options ); return isIssueOrPullRequestActive( issueOrPullRequest, staleDate, options ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuestale.js b/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuestale.js index 8a1531466..d24656a21 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuestale.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuestale.js @@ -3,8 +3,6 @@ * For licensing, see LICENSE.md. */ -'use strict'; - /** * Checks whether pending issue is already stale. * @@ -12,6 +10,6 @@ * @param {Options} options Configuration options. * @returns {Boolean} */ -module.exports = function isPendingIssueStale( pendingIssue, options ) { +export default function isPendingIssueStale( pendingIssue, options ) { return options.staleLabels.every( staleLabel => pendingIssue.labels.includes( staleLabel ) ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetostale.js b/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetostale.js index b8d10a7bb..049e28712 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetostale.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetostale.js @@ -3,10 +3,8 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { isBefore, parseISO } = require( 'date-fns' ); -const isPendingIssueStale = require( './ispendingissuestale' ); +import { isBefore, parseISO } from 'date-fns'; +import isPendingIssueStale from './ispendingissuestale.js'; /** * Checks whether pending issue should be staled, because it was not answered by a community member since the defined moment of time. @@ -15,7 +13,7 @@ const isPendingIssueStale = require( './ispendingissuestale' ); * @param {Options} options Configuration options. * @returns {Boolean} */ -module.exports = function isPendingIssueToStale( pendingIssue, options ) { +export default function isPendingIssueToStale( pendingIssue, options ) { const { lastComment } = pendingIssue; const { staleDatePendingIssue } = options; @@ -32,4 +30,4 @@ module.exports = function isPendingIssueToStale( pendingIssue, options ) { } return isBefore( parseISO( lastComment.createdAt ), parseISO( staleDatePendingIssue ) ); -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetounlabel.js b/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetounlabel.js index 404472608..c27877b1f 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetounlabel.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/ispendingissuetounlabel.js @@ -3,18 +3,16 @@ * For licensing, see LICENSE.md. */ -'use strict'; - /** * Checks whether pending issue should be unlabeled, because it was answered by a community member. * * @param {PendingIssue} pendingIssue Pending issue to check. * @returns {Boolean} */ -module.exports = function isPendingIssueToUnlabel( pendingIssue ) { +export default function isPendingIssueToUnlabel( pendingIssue ) { if ( !pendingIssue.lastComment ) { return false; } return pendingIssue.lastComment.isExternal; -}; +} diff --git a/packages/ckeditor5-dev-stale-bot/lib/utils/preparesearchquery.js b/packages/ckeditor5-dev-stale-bot/lib/utils/preparesearchquery.js index 05a9238fc..34faa943b 100644 --- a/packages/ckeditor5-dev-stale-bot/lib/utils/preparesearchquery.js +++ b/packages/ckeditor5-dev-stale-bot/lib/utils/preparesearchquery.js @@ -3,8 +3,6 @@ * For licensing, see LICENSE.md. */ -'use strict'; - /** * Creates a query to search for issues or pull requests. * @@ -16,7 +14,7 @@ * @param {Array.} [options.ignoredLabels=[]] * @returns {String} */ -module.exports = function prepareSearchQuery( options ) { +export default function prepareSearchQuery( options ) { const { repositorySlug, searchDate, @@ -36,7 +34,7 @@ module.exports = function prepareSearchQuery( options ) { ...labels.map( label => `label:${ label }` ), ...ignoredLabels.map( label => `-label:${ label }` ) ].filter( Boolean ).join( ' ' ); -}; +} function mapGitHubResourceType( type ) { const resourceMap = { diff --git a/packages/ckeditor5-dev-stale-bot/package.json b/packages/ckeditor5-dev-stale-bot/package.json index dca97c5bb..c2bc10592 100644 --- a/packages/ckeditor5-dev-stale-bot/package.json +++ b/packages/ckeditor5-dev-stale-bot/package.json @@ -16,6 +16,7 @@ "node": ">=18.0.0", "npm": ">=5.7.1" }, + "type": "module", "files": [ "bin", "lib" @@ -35,14 +36,11 @@ "upath": "^2.0.1" }, "devDependencies": { - "chai": "^4.2.0", - "mocha": "^7.1.2", - "sinon": "^9.2.4", - "proxyquire": "^2.1.3" + "vitest": "^2.0.5" }, "scripts": { - "test": "mocha './tests/**/*.js' --timeout 10000", - "coverage": "nyc --reporter=lcov --reporter=text-summary yarn run test" + "test": "vitest run --config vitest.config.js", + "coverage": "vitest run --config vitest.config.js --coverage" }, "depcheckIgnore": [ "graphql" diff --git a/packages/ckeditor5-dev-stale-bot/tests/githubrepository.js b/packages/ckeditor5-dev-stale-bot/tests/githubrepository.js index a9d50d724..9757e6ed4 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/githubrepository.js +++ b/packages/ckeditor5-dev-stale-bot/tests/githubrepository.js @@ -3,48 +3,84 @@ * For licensing, see LICENSE.md. */ -const upath = require( 'upath' ); -const expect = require( 'chai' ).expect; -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); +import upath from 'upath'; +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import prepareSearchQuery from '../lib/utils/preparesearchquery.js'; +import isIssueOrPullRequestToStale from '../lib/utils/isissueorpullrequesttostale.js'; +import isIssueOrPullRequestToUnstale from '../lib/utils/isissueorpullrequesttounstale.js'; +import isIssueOrPullRequestToClose from '../lib/utils/isissueorpullrequesttoclose.js'; +import isPendingIssueToStale from '../lib/utils/ispendingissuetostale.js'; +import isPendingIssueToUnlabel from '../lib/utils/ispendingissuetounlabel.js'; +import GitHubRepository from '../lib/githubrepository.js'; + +vi.mock( '../lib/utils/preparesearchquery' ); +vi.mock( '../lib/utils/isissueorpullrequesttostale' ); +vi.mock( '../lib/utils/isissueorpullrequesttounstale' ); +vi.mock( '../lib/utils/isissueorpullrequesttoclose' ); +vi.mock( '../lib/utils/ispendingissuetostale' ); +vi.mock( '../lib/utils/ispendingissuetounlabel' ); + +const { + fsReadFileMock, + loggerInfoMock, + loggerErrorMock, + graphQLClientConstructorSpy, + graphQLClientRequestMock +} = vi.hoisted( () => { + return { + fsReadFileMock: vi.fn(), + loggerInfoMock: vi.fn(), + loggerErrorMock: vi.fn(), + graphQLClientConstructorSpy: vi.fn(), + graphQLClientRequestMock: vi.fn() + + }; +} ); + +vi.mock( 'fs-extra', () => { + return { + default: { + readFile: fsReadFileMock + } + }; +} ); + +vi.mock( '@ckeditor/ckeditor5-dev-utils', () => { + return { + logger: () => { + return { + info: loggerInfoMock, + error: loggerErrorMock + }; + } + }; +} ); + +vi.mock( 'graphql-request', () => { + return { + GraphQLClient: class { + constructor( ...args ) { + graphQLClientConstructorSpy( ...args ); -const GRAPHQL_PATH = upath.join( __dirname, '..', 'lib', 'graphql' ); + this.request = graphQLClientRequestMock; + } + } + }; +} ); describe( 'dev-stale-bot/lib', () => { describe( 'GitHubRepository', () => { - let githubRepository, pageInfoNoNextPage, pageInfoWithNextPage, stubs, sandbox; + let githubRepository, pageInfoNoNextPage, pageInfoWithNextPage; beforeEach( () => { - sandbox = sinon.createSandbox(); - - stubs = { - fs: { - readFile: sinon.stub() - }, - logger: { - error: sinon.stub(), - info: sinon.stub() - }, - GraphQLClient: { - class: class { - constructor( ...args ) { - stubs.GraphQLClient.constructor( ...args ); - } + vi.mocked( prepareSearchQuery ).mockReturnValue( 'search query' ); + vi.mocked( isIssueOrPullRequestToStale ).mockReturnValue( true ); + vi.mocked( isIssueOrPullRequestToUnstale ).mockReturnValue( true ); + vi.mocked( isIssueOrPullRequestToClose ).mockReturnValue( true ); + vi.mocked( isPendingIssueToStale ).mockReturnValue( true ); + vi.mocked( isPendingIssueToUnlabel ).mockReturnValue( true ); - request( ...args ) { - return stubs.GraphQLClient.request( ...args ); - } - }, - constructor: sinon.stub(), - request: sinon.stub() - }, - prepareSearchQuery: sinon.stub().returns( 'search query' ), - isIssueOrPullRequestToStale: sinon.stub().returns( true ), - isIssueOrPullRequestToUnstale: sinon.stub().returns( true ), - isIssueOrPullRequestToClose: sinon.stub().returns( true ), - isPendingIssueToStale: sinon.stub().returns( true ), - isPendingIssueToUnlabel: sinon.stub().returns( true ) - }; + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( {} ); pageInfoWithNextPage = { hasNextPage: true, @@ -57,117 +93,113 @@ describe( 'dev-stale-bot/lib', () => { }; const queries = { - getviewerlogin: 'query GetViewerLogin', - searchissuesorpullrequests: 'query SearchIssuesOrPullRequests', - searchpendingissues: 'query SearchPendingIssues', - getissueorpullrequesttimelineitems: 'query GetIssueOrPullRequestTimelineItems', - addcomment: 'mutation AddComment', - getlabels: 'query GetLabels', - addlabels: 'mutation AddLabels', - removelabels: 'mutation RemoveLabels', - closeissue: 'mutation CloseIssue', - closepullrequest: 'mutation ClosePullRequest' + 'getviewerlogin.graphql': 'query GetViewerLogin', + 'searchissuesorpullrequests.graphql': 'query SearchIssuesOrPullRequests', + 'searchpendingissues.graphql': 'query SearchPendingIssues', + 'getissueorpullrequesttimelineitems.graphql': 'query GetIssueOrPullRequestTimelineItems', + 'addcomment.graphql': 'mutation AddComment', + 'getlabels.graphql': 'query GetLabels', + 'addlabels.graphql': 'mutation AddLabels', + 'removelabels.graphql': 'mutation RemoveLabels', + 'closeissue.graphql': 'mutation CloseIssue', + 'closepullrequest.graphql': 'mutation ClosePullRequest' }; - for ( const [ file, query ] of Object.entries( queries ) ) { - const absolutePath = upath.join( GRAPHQL_PATH, `${ file }.graphql` ); - - stubs.fs.readFile.withArgs( absolutePath, 'utf-8' ).resolves( query ); - } - - const GitHubRepository = proxyquire( '../lib/githubrepository', { - 'fs-extra': stubs.fs, - 'graphql-request': { - GraphQLClient: stubs.GraphQLClient.class - }, - '@ckeditor/ckeditor5-dev-utils': { - logger() { - return stubs.logger; - } - }, - './utils/preparesearchquery': stubs.prepareSearchQuery, - './utils/isissueorpullrequesttostale': stubs.isIssueOrPullRequestToStale, - './utils/isissueorpullrequesttounstale': stubs.isIssueOrPullRequestToUnstale, - './utils/isissueorpullrequesttoclose': stubs.isIssueOrPullRequestToClose, - './utils/ispendingissuetostale': stubs.isPendingIssueToStale, - './utils/ispendingissuetounlabel': stubs.isPendingIssueToUnlabel - } ); + vi.mocked( fsReadFileMock ).mockImplementation( path => queries[ upath.basename( path ) ] ); githubRepository = new GitHubRepository( 'authorization-token' ); } ); - afterEach( () => { - sandbox.restore(); - } ); - describe( '#constructor()', () => { it( 'should create a new instance of GraphQLClient', () => { - expect( stubs.GraphQLClient.constructor.callCount ).to.equal( 1 ); + expect( graphQLClientConstructorSpy ).toHaveBeenCalledTimes( 1 ); } ); it( 'should pass the API URL to the GraphQLClient instance', () => { - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 0 ] ).to.equal( 'https://api.github.com/graphql' ); + expect( graphQLClientConstructorSpy ).toHaveBeenCalledWith( + 'https://api.github.com/graphql', + expect.any( Object ) + ); } ); it( 'should pass the authorization token to the GraphQLClient instance', () => { - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ] ).to.have.property( 'headers' ); - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ].headers ).to.have.property( - 'Authorization', - 'Bearer authorization-token' + expect( graphQLClientConstructorSpy ).toHaveBeenCalledWith( + expect.any( String ), + expect.objectContaining( { + headers: expect.objectContaining( { + Authorization: 'Bearer authorization-token' + } ) + } ) ); } ); it( 'should pass a proper "Accept" header to the GraphQLClient instance', () => { - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ] ).to.have.property( 'headers' ); - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ].headers ).to.have.property( - 'Accept', - 'application/vnd.github.bane-preview+json' + expect( graphQLClientConstructorSpy ).toHaveBeenCalledWith( + expect.any( String ), + expect.objectContaining( { + headers: expect.objectContaining( { + Accept: 'application/vnd.github.bane-preview+json' + } ) + } ) ); } ); it( 'should switch to the new global GitHub ID namespace in the GraphQLClient instance', () => { - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ] ).to.have.property( 'headers' ); - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ].headers ).to.have.property( 'X-Github-Next-Global-ID', 1 ); + expect( graphQLClientConstructorSpy ).toHaveBeenCalledWith( + expect.any( String ), + expect.objectContaining( { + headers: expect.objectContaining( { + 'X-Github-Next-Global-ID': 1 + } ) + } ) + ); } ); it( 'should disable the cache in the GraphQLClient instance', () => { - expect( stubs.GraphQLClient.constructor.getCall( 0 ).args[ 1 ] ).to.have.property( 'cache', 'no-store' ); + expect( graphQLClientConstructorSpy ).toHaveBeenCalledWith( + expect.any( String ), + expect.objectContaining( { + cache: 'no-store' + } ) + ); } ); } ); describe( '#getViewerLogin()', () => { it( 'should be a function', () => { - expect( githubRepository.getViewerLogin ).to.be.a( 'function' ); + expect( githubRepository.getViewerLogin ).toBeInstanceOf( Function ); } ); - it( 'should return viewer login', () => { - stubs.GraphQLClient.request.resolves( { + it( 'should return viewer login', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { viewer: { login: 'CKEditorBot' } } ); - return githubRepository.getViewerLogin().then( result => { - expect( result ).to.equal( 'CKEditorBot' ); - } ); + const result = await githubRepository.getViewerLogin(); + + expect( result ).toEqual( 'CKEditorBot' ); } ); - it( 'should send one request for viewer login', () => { - stubs.GraphQLClient.request.resolves( { + it( 'should send one request for viewer login', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { viewer: { login: 'CKEditorBot' } } ); - return githubRepository.getViewerLogin().then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query GetViewerLogin' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( {} ); - } ); + await githubRepository.getViewerLogin(); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'query GetViewerLogin', + {} + ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.getViewerLogin().then( () => { @@ -182,18 +214,18 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.getViewerLogin().then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#getViewerLogin()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#getViewerLogin()".', + error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -201,17 +233,17 @@ describe( 'dev-stale-bot/lib', () => { describe( '#getIssueOrPullRequestTimelineItems()', () => { it( 'should be a function', () => { - expect( githubRepository.getIssueOrPullRequestTimelineItems ).to.be.a( 'function' ); + expect( githubRepository.getIssueOrPullRequestTimelineItems ).toBeInstanceOf( Function ); } ); - it( 'should return all timeline events if they are not paginated', () => { + it( 'should return all timeline events if they are not paginated', async () => { const timelineItems = [ { createdAt: '2022-12-01T09:00:00Z' }, { createdAt: '2022-12-02T09:00:00Z' }, { createdAt: '2022-12-03T09:00:00Z' } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { node: { timelineItems: { nodes: timelineItems, @@ -220,16 +252,16 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.deep.equal( { eventDate: '2022-12-01T09:00:00Z' } ); - expect( result[ 1 ] ).to.deep.equal( { eventDate: '2022-12-02T09:00:00Z' } ); - expect( result[ 2 ] ).to.deep.equal( { eventDate: '2022-12-03T09:00:00Z' } ); - } ); + const result = await githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ); + + expect( result ).toEqual( [ + { eventDate: '2022-12-01T09:00:00Z' }, + { eventDate: '2022-12-02T09:00:00Z' }, + { eventDate: '2022-12-03T09:00:00Z' } + ] ); } ); - it( 'should return all timeline events if they are paginated', () => { + it( 'should return all timeline events if they are paginated', async () => { const timelineItems = [ { createdAt: '2022-12-01T09:00:00Z' }, { createdAt: '2022-12-02T09:00:00Z' }, @@ -247,23 +279,23 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.deep.equal( { eventDate: '2022-12-01T09:00:00Z' } ); - expect( result[ 1 ] ).to.deep.equal( { eventDate: '2022-12-02T09:00:00Z' } ); - expect( result[ 2 ] ).to.deep.equal( { eventDate: '2022-12-03T09:00:00Z' } ); - } ); + const result = await githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ); + + expect( result ).toEqual( [ + { eventDate: '2022-12-01T09:00:00Z' }, + { eventDate: '2022-12-02T09:00:00Z' }, + { eventDate: '2022-12-03T09:00:00Z' } + ] ); } ); - it( 'should send one request for all timeline events if they are not paginated', () => { + it( 'should send one request for all timeline events if they are not paginated', async () => { const timelineItems = [ { createdAt: '2022-12-01T09:00:00Z' }, { createdAt: '2022-12-02T09:00:00Z' }, { createdAt: '2022-12-03T09:00:00Z' } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { node: { timelineItems: { nodes: timelineItems, @@ -272,14 +304,16 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query GetIssueOrPullRequestTimelineItems' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { nodeId: 'IssueId', cursor: null } ); - } ); + await githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'query GetIssueOrPullRequestTimelineItems', + { nodeId: 'IssueId', cursor: null } + ); } ); - it( 'should send multiple requests for all timeline events if they are paginated', () => { + it( 'should send multiple requests for all timeline events if they are paginated', async () => { const timelineItems = [ { createdAt: '2022-12-01T09:00:00Z' }, { createdAt: '2022-12-02T09:00:00Z' }, @@ -297,21 +331,27 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( () => { - expect( stubs.GraphQLClient.request.callCount ).to.equal( 3 ); + await githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query GetIssueOrPullRequestTimelineItems' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { nodeId: 'IssueId', cursor: null } ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 3 ); - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 0 ] ).to.equal( 'query GetIssueOrPullRequestTimelineItems' ); - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 1 ] ).to.deep.equal( { nodeId: 'IssueId', cursor: 'cursor' } ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( 1, + 'query GetIssueOrPullRequestTimelineItems', + { nodeId: 'IssueId', cursor: null } + ); - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 0 ] ).to.equal( 'query GetIssueOrPullRequestTimelineItems' ); - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 1 ] ).to.deep.equal( { nodeId: 'IssueId', cursor: 'cursor' } ); - } ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( 2, + 'query GetIssueOrPullRequestTimelineItems', + { nodeId: 'IssueId', cursor: 'cursor' } + ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( 3, + 'query GetIssueOrPullRequestTimelineItems', + { nodeId: 'IssueId', cursor: 'cursor' } + ); } ); - it( 'should return event date, event author and label if any of these exist', () => { + it( 'should return event date, event author and label if any of these exist', async () => { const timelineItems = [ { createdAt: '2022-12-01T09:00:00Z' }, { updatedAt: '2022-12-02T09:00:00Z' }, @@ -320,7 +360,7 @@ describe( 'dev-stale-bot/lib', () => { { createdAt: '2022-12-05T09:00:00Z', label: { name: 'type:bug' } } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { node: { timelineItems: { nodes: timelineItems, @@ -329,19 +369,19 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 5 ); - expect( result[ 0 ] ).to.deep.equal( { eventDate: '2022-12-01T09:00:00Z' } ); - expect( result[ 1 ] ).to.deep.equal( { eventDate: '2022-12-02T09:00:00Z' } ); - expect( result[ 2 ] ).to.deep.equal( { eventDate: '2022-12-03T09:00:00Z', author: 'RandomUser' } ); - expect( result[ 3 ] ).to.deep.equal( { eventDate: '2022-12-04T09:00:00Z', author: 'RandomUser' } ); - expect( result[ 4 ] ).to.deep.equal( { eventDate: '2022-12-05T09:00:00Z', label: 'type:bug' } ); - } ); + const result = await githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ); + + expect( result ).toEqual( [ + { eventDate: '2022-12-01T09:00:00Z' }, + { eventDate: '2022-12-02T09:00:00Z' }, + { eventDate: '2022-12-03T09:00:00Z', author: 'RandomUser' }, + { eventDate: '2022-12-04T09:00:00Z', author: 'RandomUser' }, + { eventDate: '2022-12-05T09:00:00Z', label: 'type:bug' } + ] ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( () => { @@ -357,7 +397,7 @@ describe( 'dev-stale-bot/lib', () => { const timelineItems = [ { createdAt: '2022-12-01T09:00:00Z' }, { createdAt: '2022-12-02T09:00:00Z' }, - { createdAt: '2022-12-03T09:00:00Z' } + { error: new Error( '500 Internal Server Error' ) } ]; paginateRequest( timelineItems, ( { nodes, pageInfo } ) => { @@ -371,8 +411,6 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - stubs.GraphQLClient.request.onCall( 2 ).rejects( new Error( '500 Internal Server Error' ) ); - return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( () => { throw new Error( 'Expected to be rejected.' ); @@ -386,18 +424,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.getIssueOrPullRequestTimelineItems( 'IssueId' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#getIssueOrPullRequestTimelineItems()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#getIssueOrPullRequestTimelineItems()".', error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -407,7 +444,7 @@ describe( 'dev-stale-bot/lib', () => { let onProgress, optionsBase, issueBase; beforeEach( () => { - onProgress = sinon.stub(); + onProgress = vi.fn(); optionsBase = { repositorySlug: 'ckeditor/ckeditor5', @@ -447,10 +484,10 @@ describe( 'dev-stale-bot/lib', () => { describe( '#searchIssuesOrPullRequestsToStale()', () => { it( 'should be a function', () => { - expect( githubRepository.searchIssuesOrPullRequestsToStale ).to.be.a( 'function' ); + expect( githubRepository.searchIssuesOrPullRequestsToStale ).toBeInstanceOf( Function ); } ); - it( 'should ask for issue search query', () => { + it( 'should ask for issue search query', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -462,7 +499,7 @@ describe( 'dev-stale-bot/lib', () => { ignoredIssueLabels: [ 'support:1', 'support:2', 'support:3' ] }; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -470,18 +507,18 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', options, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.deep.equal( { - type: 'Issue', - searchDate: '2022-12-01', - repositorySlug: 'ckeditor/ckeditor5', - ignoredLabels: [ 'status:stale', 'support:1', 'support:2', 'support:3' ] - } ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', options, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( { + type: 'Issue', + searchDate: '2022-12-01', + repositorySlug: 'ckeditor/ckeditor5', + ignoredLabels: [ 'status:stale', 'support:1', 'support:2', 'support:3' ] } ); } ); - it( 'should ask for pull request search query', () => { + it( 'should ask for pull request search query', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -493,7 +530,7 @@ describe( 'dev-stale-bot/lib', () => { ignoredPullRequestLabels: [ 'support:1', 'support:2', 'support:3' ] }; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -501,25 +538,25 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'PullRequest', options, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.deep.equal( { - type: 'PullRequest', - searchDate: '2022-12-01', - repositorySlug: 'ckeditor/ckeditor5', - ignoredLabels: [ 'status:stale', 'support:1', 'support:2', 'support:3' ] - } ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'PullRequest', options, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( { + type: 'PullRequest', + searchDate: '2022-12-01', + repositorySlug: 'ckeditor/ckeditor5', + ignoredLabels: [ 'status:stale', 'support:1', 'support:2', 'support:3' ] } ); } ); - it( 'should start the search from stale date if search date is not set', () => { + it( 'should start the search from stale date if search date is not set', async () => { const options = { ...optionsBase, searchDate: undefined, staleDate: '2023-01-01' }; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: 0, nodes: [], @@ -527,20 +564,22 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', options, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.have.property( 'searchDate', '2023-01-01' ); - } ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', options, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( + expect.objectContaining( { 'searchDate': '2023-01-01' } ) + ); } ); - it( 'should return all issues to stale if they are not paginated', () => { + it( 'should return all issues to stale if they are not paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -548,22 +587,16 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - expect( result[ 1 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - expect( result[ 2 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - } ); + const result = await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); + + expect( result ).toEqual( [ + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' }, + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' }, + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } + ] ); } ); - it( 'should return all issues to stale if they are paginated', () => { + it( 'should return all issues to stale if they are paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -580,29 +613,23 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - expect( result[ 1 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - expect( result[ 2 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - } ); + const result = await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); + + expect( result ).toEqual( [ + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' }, + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' }, + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } + ] ); } ); - it( 'should send one request for all issues to stale if they are not paginated', () => { + it( 'should send one request for all issues to stale if they are not paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -610,16 +637,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: null } - ); - } ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: null } + ); } ); - it( 'should send multiple requests for all issues to stale if they are paginated', () => { + it( 'should send multiple requests for all issues to stale if they are paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -636,27 +662,20 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { - expect( stubs.GraphQLClient.request.callCount ).to.equal( 3 ); - - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: null } - ); - - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: 'cursor' } - ); - - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: 'cursor' } - ); - } ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 1, 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: null } + ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 2, 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: 'cursor' } + ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 3, 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: 'cursor' } + ); } ); - it( 'should fetch all timeline events for any issue if they are paginated', () => { + it( 'should fetch all timeline events for any issue if they are paginated', async () => { const issues = [ { ...issueBase, number: 1, timelineItems: { nodes: [], @@ -664,9 +683,9 @@ describe( 'dev-stale-bot/lib', () => { } } ]; - sinon.stub( githubRepository, 'getIssueOrPullRequestTimelineItems' ).resolves( [] ); + githubRepository.getIssueOrPullRequestTimelineItems = vi.fn().mockResolvedValue( [] ); - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -674,18 +693,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { - expect( githubRepository.getIssueOrPullRequestTimelineItems.callCount ).to.equal( 1 ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); - expect( githubRepository.getIssueOrPullRequestTimelineItems.getCall( 0 ).args[ 0 ] ).to.equal( 'IssueId' ); - expect( githubRepository.getIssueOrPullRequestTimelineItems.getCall( 0 ).args[ 1 ] ).to.deep.equal( { - hasNextPage: true, - cursor: 'cursor' - } ); - } ); + expect( vi.mocked( githubRepository.getIssueOrPullRequestTimelineItems ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( githubRepository.getIssueOrPullRequestTimelineItems ) ).toHaveBeenCalledWith( + 'IssueId', { hasNextPage: true, cursor: 'cursor' } + ); } ); - it( 'should ask for a new search query with new offset if GitHub prevents going to the next page', () => { + it( 'should ask for a new search query with new offset if GitHub prevents going to the next page', async () => { const issues = [ { ...issueBase, number: 1, createdAt: '2022-11-01T09:00:00Z' }, { ...issueBase, number: 2, createdAt: '2022-10-01T09:00:00Z' }, @@ -702,15 +718,21 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.callCount ).to.equal( 3 ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-12-01' ); - expect( stubs.prepareSearchQuery.getCall( 1 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-11-01T09:00:00Z' ); - expect( stubs.prepareSearchQuery.getCall( 2 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-10-01T09:00:00Z' ); - } ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 1, expect.objectContaining( { searchDate: '2022-12-01' } ) + ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 2, expect.objectContaining( { searchDate: '2022-11-01T09:00:00Z' } ) + ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 3, expect.objectContaining( { searchDate: '2022-10-01T09:00:00Z' } ) + ); } ); - it( 'should return all issues to stale if GitHub prevents going to the next page', () => { + it( 'should return all issues to stale if GitHub prevents going to the next page', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -727,29 +749,25 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - expect( result[ 1 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - expect( result[ 2 ] ).to.deep.equal( - { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } - ); - } ); + const result = await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); + + expect( result ).toBeInstanceOf( Array ); + expect( result.length ).toEqual( 3 ); + expect( result ).toEqual( [ + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' }, + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' }, + { id: 'IssueId', title: 'IssueTitle', type: 'Issue', url: 'https://github.com/' } + ] ); } ); - it( 'should check each issue if it is stale', () => { + it( 'should check each issue if it is stale', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -757,32 +775,33 @@ describe( 'dev-stale-bot/lib', () => { } } ); - stubs.isIssueOrPullRequestToStale.onCall( 1 ).returns( false ); - - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( result => { - const expectedIssue = { - ...issueBase, - lastReactedAt: null, - timelineItems: [] - }; - - expect( stubs.isIssueOrPullRequestToStale.callCount ).to.equal( 3 ); + vi.mocked( isIssueOrPullRequestToStale ).mockReturnValueOnce( true ); + vi.mocked( isIssueOrPullRequestToStale ).mockReturnValueOnce( false ); - expect( stubs.isIssueOrPullRequestToStale.getCall( 0 ).args[ 0 ] ).to.deep.equal( { ...expectedIssue, number: 1 } ); - expect( stubs.isIssueOrPullRequestToStale.getCall( 0 ).args[ 1 ] ).to.deep.equal( optionsBase ); + const result = await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); - expect( stubs.isIssueOrPullRequestToStale.getCall( 1 ).args[ 0 ] ).to.deep.equal( { ...expectedIssue, number: 2 } ); - expect( stubs.isIssueOrPullRequestToStale.getCall( 1 ).args[ 1 ] ).to.deep.equal( optionsBase ); + const expectedIssue = { + ...issueBase, + lastReactedAt: null, + timelineItems: [] + }; - expect( stubs.isIssueOrPullRequestToStale.getCall( 2 ).args[ 0 ] ).to.deep.equal( { ...expectedIssue, number: 3 } ); - expect( stubs.isIssueOrPullRequestToStale.getCall( 2 ).args[ 1 ] ).to.deep.equal( optionsBase ); + expect( vi.mocked( isIssueOrPullRequestToStale ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( isIssueOrPullRequestToStale ) ).toHaveBeenNthCalledWith( + 1, { ...expectedIssue, number: 1 }, optionsBase + ); + expect( vi.mocked( isIssueOrPullRequestToStale ) ).toHaveBeenNthCalledWith( + 2, { ...expectedIssue, number: 2 }, optionsBase + ); + expect( vi.mocked( isIssueOrPullRequestToStale ) ).toHaveBeenNthCalledWith( + 3, { ...expectedIssue, number: 3 }, optionsBase + ); - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 2 ); - } ); + expect( result ).toBeInstanceOf( Array ); + expect( result.length ).toEqual( 2 ); } ); - it( 'should call on progress callback', () => { + it( 'should call on progress callback', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -799,16 +818,15 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { - expect( onProgress.callCount ).to.equal( 3 ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); - expect( onProgress.getCall( 0 ).args[ 0 ] ).to.deep.equal( { done: 1, total: 3 } ); - expect( onProgress.getCall( 1 ).args[ 0 ] ).to.deep.equal( { done: 2, total: 3 } ); - expect( onProgress.getCall( 2 ).args[ 0 ] ).to.deep.equal( { done: 3, total: 3 } ); - } ); + expect( vi.mocked( onProgress ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 1, { done: 1, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 2, { done: 2, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 3, { done: 3, total: 3 } ); } ); - it( 'should count total hits only once using the value from first response', () => { + it( 'should count total hits only once using the value from first response', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -825,17 +843,16 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { - expect( onProgress.callCount ).to.equal( 3 ); + await githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ); - expect( onProgress.getCall( 0 ).args[ 0 ] ).to.deep.equal( { done: 1, total: 3 } ); - expect( onProgress.getCall( 1 ).args[ 0 ] ).to.deep.equal( { done: 2, total: 3 } ); - expect( onProgress.getCall( 2 ).args[ 0 ] ).to.deep.equal( { done: 3, total: 3 } ); - } ); + expect( vi.mocked( onProgress ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 1, { done: 1, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 2, { done: 2, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 3, { done: 3, total: 3 } ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { @@ -851,7 +868,7 @@ describe( 'dev-stale-bot/lib', () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, - { ...issueBase, number: 3 } + { error: new Error( '500 Internal Server Error' ) } ]; paginateRequest( issues, ( { nodes, pageInfo } ) => { @@ -864,8 +881,6 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - stubs.GraphQLClient.request.onCall( 2 ).rejects( new Error( '500 Internal Server Error' ) ); - return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { throw new Error( 'Expected to be rejected.' ); @@ -879,18 +894,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.searchIssuesOrPullRequestsToStale( 'Issue', optionsBase, onProgress ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#searchIssuesOrPullRequestsToStale()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#searchIssuesOrPullRequestsToStale()".', error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -898,17 +912,17 @@ describe( 'dev-stale-bot/lib', () => { describe( '#searchStaleIssuesOrPullRequests()', () => { it( 'should be a function', () => { - expect( githubRepository.searchStaleIssuesOrPullRequests ).to.be.a( 'function' ); + expect( githubRepository.searchStaleIssuesOrPullRequests ).toBeInstanceOf( Function ); } ); - it( 'should ask for search query', () => { + it( 'should ask for search query', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -916,24 +930,24 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.deep.equal( { - searchDate: undefined, - repositorySlug: 'ckeditor/ckeditor5', - labels: [ 'status:stale' ] - } ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( { + searchDate: undefined, + repositorySlug: 'ckeditor/ckeditor5', + labels: [ 'status:stale' ] } ); } ); - it( 'should not set the initial start date', () => { + it( 'should not set the initial start date', async () => { const options = { ...optionsBase, searchDate: undefined, staleDate: '2023-01-01' }; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: 0, nodes: [], @@ -941,20 +955,22 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchStaleIssuesOrPullRequests( options, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.have.property( 'searchDate', undefined ); - } ); + await githubRepository.searchStaleIssuesOrPullRequests( options, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( + expect.objectContaining( { 'searchDate': undefined } ) + ); } ); - it( 'should return all stale issues if they are not paginated', () => { + it( 'should return all stale issues if they are not paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -962,37 +978,23 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( result => { - expect( result ).to.have.property( 'issuesOrPullRequestsToClose' ); - expect( result ).to.have.property( 'issuesOrPullRequestsToUnstale' ); + const result = await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( result.issuesOrPullRequestsToClose ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToClose ).to.have.length( 3 ); - expect( result.issuesOrPullRequestsToClose[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToClose[ 1 ] ).to.deep.equal( + expect( result ).toEqual( { + issuesOrPullRequestsToClose: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToClose[ 2 ] ).to.deep.equal( + ], + issuesOrPullRequestsToUnstale: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - - expect( result.issuesOrPullRequestsToUnstale ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToUnstale ).to.have.length( 3 ); - expect( result.issuesOrPullRequestsToUnstale[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToUnstale[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToUnstale[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + ] } ); } ); - it( 'should return all stale issues if they are paginated', () => { + it( 'should return all stale issues if they are paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1009,44 +1011,30 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( result => { - expect( result ).to.have.property( 'issuesOrPullRequestsToClose' ); - expect( result ).to.have.property( 'issuesOrPullRequestsToUnstale' ); - - expect( result.issuesOrPullRequestsToClose ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToClose ).to.have.length( 3 ); - expect( result.issuesOrPullRequestsToClose[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToClose[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToClose[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + const result = await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( result.issuesOrPullRequestsToUnstale ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToUnstale ).to.have.length( 3 ); - expect( result.issuesOrPullRequestsToUnstale[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToUnstale[ 1 ] ).to.deep.equal( + expect( result ).toEqual( { + issuesOrPullRequestsToClose: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToUnstale[ 2 ] ).to.deep.equal( + ], + issuesOrPullRequestsToUnstale: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + ] } ); } ); - it( 'should send one request for all stale issues if they are not paginated', () => { + it( 'should send one request for all stale issues if they are not paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1054,16 +1042,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: null } - ); - } ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: null } + ); } ); - it( 'should send multiple requests for all stale issues if they are paginated', () => { + it( 'should send multiple requests for all stale issues if they are paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1080,27 +1067,22 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( stubs.GraphQLClient.request.callCount ).to.equal( 3 ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: null } - ); - - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: 'cursor' } - ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 3 ); - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 0 ] ).to.equal( 'query SearchIssuesOrPullRequests' ); - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: 'cursor' } - ); - } ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 1, 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: null } + ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 2, 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: 'cursor' } + ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 3, 'query SearchIssuesOrPullRequests', { query: 'search query', cursor: 'cursor' } + ); } ); - it( 'should fetch all timeline events for any issue if they are paginated', () => { + it( 'should fetch all timeline events for any issue if they are paginated', async () => { const issues = [ { ...issueBase, number: 1, timelineItems: { nodes: [], @@ -1108,9 +1090,9 @@ describe( 'dev-stale-bot/lib', () => { } } ]; - sinon.stub( githubRepository, 'getIssueOrPullRequestTimelineItems' ).resolves( [] ); + githubRepository.getIssueOrPullRequestTimelineItems = vi.fn().mockResolvedValue( [] ); - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1118,18 +1100,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( githubRepository.getIssueOrPullRequestTimelineItems.callCount ).to.equal( 1 ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( githubRepository.getIssueOrPullRequestTimelineItems.getCall( 0 ).args[ 0 ] ).to.equal( 'IssueId' ); - expect( githubRepository.getIssueOrPullRequestTimelineItems.getCall( 0 ).args[ 1 ] ).to.deep.equal( { - hasNextPage: true, - cursor: 'cursor' - } ); - } ); + expect( vi.mocked( githubRepository.getIssueOrPullRequestTimelineItems ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( githubRepository.getIssueOrPullRequestTimelineItems ) ).toHaveBeenCalledWith( + 'IssueId', { hasNextPage: true, cursor: 'cursor' } + ); } ); - it( 'should ask for a new search query with new offset if GitHub prevents going to the next page', () => { + it( 'should ask for a new search query with new offset if GitHub prevents going to the next page', async () => { const issues = [ { ...issueBase, number: 1, createdAt: '2022-11-01T09:00:00Z' }, { ...issueBase, number: 2, createdAt: '2022-10-01T09:00:00Z' }, @@ -1146,15 +1125,21 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.callCount ).to.equal( 3 ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.have.property( 'searchDate', undefined ); - expect( stubs.prepareSearchQuery.getCall( 1 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-11-01T09:00:00Z' ); - expect( stubs.prepareSearchQuery.getCall( 2 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-10-01T09:00:00Z' ); - } ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 3 ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 1, expect.objectContaining( { searchDate: undefined } ) + ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 2, expect.objectContaining( { searchDate: '2022-11-01T09:00:00Z' } ) + ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 3, expect.objectContaining( { searchDate: '2022-10-01T09:00:00Z' } ) + ); } ); - it( 'should return all stale issues if GitHub prevents going to the next page', () => { + it( 'should return all stale issues if GitHub prevents going to the next page', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1171,44 +1156,30 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( result => { - expect( result ).to.have.property( 'issuesOrPullRequestsToClose' ); - expect( result ).to.have.property( 'issuesOrPullRequestsToUnstale' ); - - expect( result.issuesOrPullRequestsToClose ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToClose ).to.have.length( 3 ); - expect( result.issuesOrPullRequestsToClose[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToClose[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToClose[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + const result = await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( result.issuesOrPullRequestsToUnstale ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToUnstale ).to.have.length( 3 ); - expect( result.issuesOrPullRequestsToUnstale[ 0 ] ).to.deep.equal( + expect( result ).toEqual( { + issuesOrPullRequestsToClose: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToUnstale[ 1 ] ).to.deep.equal( + ], + issuesOrPullRequestsToUnstale: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.issuesOrPullRequestsToUnstale[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + ] } ); } ); - it( 'should check each issue if it should be unstaled or closed', () => { + it( 'should check each issue if it should be unstaled or closed', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1216,52 +1187,46 @@ describe( 'dev-stale-bot/lib', () => { } } ); - stubs.isIssueOrPullRequestToUnstale.onCall( 1 ).returns( false ); - stubs.isIssueOrPullRequestToClose.onCall( 1 ).returns( false ); - - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( result => { - const expectedIssue = { - ...issueBase, - lastReactedAt: null, - timelineItems: [] - }; - - expect( stubs.isIssueOrPullRequestToUnstale.callCount ).to.equal( 3 ); - - expect( stubs.isIssueOrPullRequestToUnstale.getCall( 0 ).args[ 0 ] ).to.deep.equal( - { ...expectedIssue, number: 1 } - ); - expect( stubs.isIssueOrPullRequestToUnstale.getCall( 0 ).args[ 1 ] ).to.deep.equal( optionsBase ); - - expect( stubs.isIssueOrPullRequestToUnstale.getCall( 1 ).args[ 0 ] ).to.deep.equal( - { ...expectedIssue, number: 2 } - ); - expect( stubs.isIssueOrPullRequestToUnstale.getCall( 1 ).args[ 1 ] ).to.deep.equal( optionsBase ); - - expect( stubs.isIssueOrPullRequestToUnstale.getCall( 2 ).args[ 0 ] ).to.deep.equal( - { ...expectedIssue, number: 3 } - ); - expect( stubs.isIssueOrPullRequestToUnstale.getCall( 2 ).args[ 1 ] ).to.deep.equal( optionsBase ); + vi.mocked( isIssueOrPullRequestToUnstale ).mockReturnValueOnce( false ); + vi.mocked( isIssueOrPullRequestToClose ).mockReturnValueOnce( false ); - expect( stubs.isIssueOrPullRequestToClose.callCount ).to.equal( 3 ); + const result = await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( stubs.isIssueOrPullRequestToClose.getCall( 0 ).args[ 0 ] ).to.deep.equal( { ...expectedIssue, number: 1 } ); - expect( stubs.isIssueOrPullRequestToClose.getCall( 0 ).args[ 1 ] ).to.deep.equal( optionsBase ); + const expectedIssue = { + ...issueBase, + lastReactedAt: null, + timelineItems: [] + }; - expect( stubs.isIssueOrPullRequestToClose.getCall( 1 ).args[ 0 ] ).to.deep.equal( { ...expectedIssue, number: 2 } ); - expect( stubs.isIssueOrPullRequestToClose.getCall( 1 ).args[ 1 ] ).to.deep.equal( optionsBase ); + expect( vi.mocked( isIssueOrPullRequestToUnstale ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( isIssueOrPullRequestToUnstale ) ).toHaveBeenNthCalledWith( + 1, { ...expectedIssue, number: 1 }, optionsBase + ); + expect( vi.mocked( isIssueOrPullRequestToUnstale ) ).toHaveBeenNthCalledWith( + 2, { ...expectedIssue, number: 2 }, optionsBase + ); + expect( vi.mocked( isIssueOrPullRequestToUnstale ) ).toHaveBeenNthCalledWith( + 3, { ...expectedIssue, number: 3 }, optionsBase + ); - expect( stubs.isIssueOrPullRequestToClose.getCall( 2 ).args[ 0 ] ).to.deep.equal( { ...expectedIssue, number: 3 } ); - expect( stubs.isIssueOrPullRequestToClose.getCall( 2 ).args[ 1 ] ).to.deep.equal( optionsBase ); + expect( vi.mocked( isIssueOrPullRequestToClose ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( isIssueOrPullRequestToClose ) ).toHaveBeenNthCalledWith( + 1, { ...expectedIssue, number: 1 }, optionsBase + ); + expect( vi.mocked( isIssueOrPullRequestToClose ) ).toHaveBeenNthCalledWith( + 2, { ...expectedIssue, number: 2 }, optionsBase + ); + expect( vi.mocked( isIssueOrPullRequestToClose ) ).toHaveBeenNthCalledWith( + 3, { ...expectedIssue, number: 3 }, optionsBase + ); - expect( result.issuesOrPullRequestsToUnstale ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToUnstale ).to.have.length( 2 ); - expect( result.issuesOrPullRequestsToClose ).to.be.an( 'array' ); - expect( result.issuesOrPullRequestsToClose ).to.have.length( 2 ); + expect( result ).toEqual( { + issuesOrPullRequestsToUnstale: [ expect.anything(), expect.anything() ], + issuesOrPullRequestsToClose: [ expect.anything(), expect.anything() ] } ); } ); - it( 'should call on progress callback', () => { + it( 'should call on progress callback', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1278,16 +1243,16 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( onProgress.callCount ).to.equal( 3 ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( onProgress.getCall( 0 ).args[ 0 ] ).to.deep.equal( { done: 1, total: 3 } ); - expect( onProgress.getCall( 1 ).args[ 0 ] ).to.deep.equal( { done: 2, total: 3 } ); - expect( onProgress.getCall( 2 ).args[ 0 ] ).to.deep.equal( { done: 3, total: 3 } ); - } ); + expect( vi.mocked( onProgress ) ).toHaveBeenCalledTimes( 3 ); + + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 1, { done: 1, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 2, { done: 2, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 3, { done: 3, total: 3 } ); } ); - it( 'should count total hits only once using the value from first response', () => { + it( 'should count total hits only once using the value from first response', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1304,17 +1269,17 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { - expect( onProgress.callCount ).to.equal( 3 ); + await githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ); - expect( onProgress.getCall( 0 ).args[ 0 ] ).to.deep.equal( { done: 1, total: 3 } ); - expect( onProgress.getCall( 1 ).args[ 0 ] ).to.deep.equal( { done: 2, total: 3 } ); - expect( onProgress.getCall( 2 ).args[ 0 ] ).to.deep.equal( { done: 3, total: 3 } ); - } ); + expect( vi.mocked( onProgress ) ).toHaveBeenCalledTimes( 3 ); + + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 1, { done: 1, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 2, { done: 2, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 3, { done: 3, total: 3 } ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { @@ -1330,7 +1295,7 @@ describe( 'dev-stale-bot/lib', () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, - { ...issueBase, number: 3 } + { error: new Error( '500 Internal Server Error' ) } ]; paginateRequest( issues, ( { nodes, pageInfo } ) => { @@ -1343,8 +1308,6 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - stubs.GraphQLClient.request.onCall( 2 ).rejects( new Error( '500 Internal Server Error' ) ); - return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { throw new Error( 'Expected to be rejected.' ); @@ -1358,18 +1321,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.searchStaleIssuesOrPullRequests( optionsBase, onProgress ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#searchStaleIssuesOrPullRequests()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#searchStaleIssuesOrPullRequests()".', error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -1383,10 +1345,10 @@ describe( 'dev-stale-bot/lib', () => { } ); it( 'should be a function', () => { - expect( githubRepository.searchPendingIssues ).to.be.a( 'function' ); + expect( githubRepository.searchPendingIssues ).toBeInstanceOf( Function ); } ); - it( 'should ask for search query', () => { + it( 'should ask for search query', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1398,7 +1360,7 @@ describe( 'dev-stale-bot/lib', () => { ignoredIssueLabels: [ 'support:1', 'support:2', 'support:3' ] }; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1406,26 +1368,26 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchPendingIssues( options, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.deep.equal( { - type: 'Issue', - searchDate: undefined, - repositorySlug: 'ckeditor/ckeditor5', - labels: [ 'pending:feedback' ], - ignoredLabels: [ 'support:1', 'support:2', 'support:3' ] - } ); + await githubRepository.searchPendingIssues( options, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( { + type: 'Issue', + searchDate: undefined, + repositorySlug: 'ckeditor/ckeditor5', + labels: [ 'pending:feedback' ], + ignoredLabels: [ 'support:1', 'support:2', 'support:3' ] } ); } ); - it( 'should not set the initial start date', () => { + it( 'should not set the initial start date', async () => { const options = { ...optionsBase, searchDate: undefined, staleDate: '2023-01-01' }; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: 0, nodes: [], @@ -1433,20 +1395,22 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchPendingIssues( options, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.calledOnce ).to.equal( true ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.have.property( 'searchDate', undefined ); - } ); + await githubRepository.searchPendingIssues( options, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledWith( + expect.objectContaining( { searchDate: undefined } ) + ); } ); - it( 'should return all pending issues if they are not paginated', () => { + it( 'should return all pending issues if they are not paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1454,37 +1418,23 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( result => { - expect( result ).to.have.property( 'pendingIssuesToStale' ); - expect( result ).to.have.property( 'pendingIssuesToUnlabel' ); - - expect( result.pendingIssuesToStale ).to.be.an( 'array' ); - expect( result.pendingIssuesToStale ).to.have.length( 3 ); - expect( result.pendingIssuesToStale[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToStale[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToStale[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + const result = await githubRepository.searchPendingIssues( optionsBase, onProgress ); - expect( result.pendingIssuesToUnlabel ).to.be.an( 'array' ); - expect( result.pendingIssuesToUnlabel ).to.have.length( 3 ); - expect( result.pendingIssuesToUnlabel[ 0 ] ).to.deep.equal( + expect( result ).toEqual( { + pendingIssuesToStale: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToUnlabel[ 1 ] ).to.deep.equal( + ], + pendingIssuesToUnlabel: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToUnlabel[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + ] } ); } ); - it( 'should return all pending issues if they are paginated', () => { + it( 'should return all pending issues if they are paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1501,44 +1451,30 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( result => { - expect( result ).to.have.property( 'pendingIssuesToStale' ); - expect( result ).to.have.property( 'pendingIssuesToUnlabel' ); - - expect( result.pendingIssuesToStale ).to.be.an( 'array' ); - expect( result.pendingIssuesToStale ).to.have.length( 3 ); - expect( result.pendingIssuesToStale[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToStale[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToStale[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + const result = await githubRepository.searchPendingIssues( optionsBase, onProgress ); - expect( result.pendingIssuesToUnlabel ).to.be.an( 'array' ); - expect( result.pendingIssuesToUnlabel ).to.have.length( 3 ); - expect( result.pendingIssuesToUnlabel[ 0 ] ).to.deep.equal( + expect( result ).toEqual( { + pendingIssuesToStale: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToUnlabel[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToUnlabel[ 2 ] ).to.deep.equal( + ], + pendingIssuesToUnlabel: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + ] } ); } ); - it( 'should send one request for all pending issues if they are not paginated', () => { + it( 'should send one request for all pending issues if they are not paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, { ...issueBase, number: 3 } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1546,16 +1482,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query SearchPendingIssues' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: null } - ); - } ); + await githubRepository.searchPendingIssues( optionsBase, onProgress ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'query SearchPendingIssues', { query: 'search query', cursor: null } + ); } ); - it( 'should send multiple requests for all pending issues if they are paginated', () => { + it( 'should send multiple requests for all pending issues if they are paginated', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1572,27 +1507,21 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { - expect( stubs.GraphQLClient.request.callCount ).to.equal( 3 ); + await githubRepository.searchPendingIssues( optionsBase, onProgress ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query SearchPendingIssues' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: null } - ); - - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 0 ] ).to.equal( 'query SearchPendingIssues' ); - expect( stubs.GraphQLClient.request.getCall( 1 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: 'cursor' } - ); - - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 0 ] ).to.equal( 'query SearchPendingIssues' ); - expect( stubs.GraphQLClient.request.getCall( 2 ).args[ 1 ] ).to.deep.equal( - { query: 'search query', cursor: 'cursor' } - ); - } ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 1, 'query SearchPendingIssues', { query: 'search query', cursor: null } + ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 2, 'query SearchPendingIssues', { query: 'search query', cursor: 'cursor' } + ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenNthCalledWith( + 3, 'query SearchPendingIssues', { query: 'search query', cursor: 'cursor' } + ); } ); - it( 'should ask for a new search query with new offset if GitHub prevents going to the next page', () => { + it( 'should ask for a new search query with new offset if GitHub prevents going to the next page', async () => { const issues = [ { ...issueBase, number: 1, createdAt: '2022-11-01T09:00:00Z' }, { ...issueBase, number: 2, createdAt: '2022-10-01T09:00:00Z' }, @@ -1609,15 +1538,21 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { - expect( stubs.prepareSearchQuery.callCount ).to.equal( 3 ); - expect( stubs.prepareSearchQuery.getCall( 0 ).args[ 0 ] ).to.have.property( 'searchDate', undefined ); - expect( stubs.prepareSearchQuery.getCall( 1 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-11-01T09:00:00Z' ); - expect( stubs.prepareSearchQuery.getCall( 2 ).args[ 0 ] ).to.have.property( 'searchDate', '2022-10-01T09:00:00Z' ); - } ); + await githubRepository.searchPendingIssues( optionsBase, onProgress ); + + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 1, expect.objectContaining( { searchDate: undefined } ) + ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 2, expect.objectContaining( { searchDate: '2022-11-01T09:00:00Z' } ) + ); + expect( vi.mocked( prepareSearchQuery ) ).toHaveBeenNthCalledWith( + 3, expect.objectContaining( { searchDate: '2022-10-01T09:00:00Z' } ) + ); } ); - it( 'should return all pending issues if GitHub prevents going to the next page', () => { + it( 'should return all pending issues if GitHub prevents going to the next page', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1634,37 +1569,23 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( result => { - expect( result ).to.have.property( 'pendingIssuesToStale' ); - expect( result ).to.have.property( 'pendingIssuesToUnlabel' ); + const result = await githubRepository.searchPendingIssues( optionsBase, onProgress ); - expect( result.pendingIssuesToStale ).to.be.an( 'array' ); - expect( result.pendingIssuesToStale ).to.have.length( 3 ); - expect( result.pendingIssuesToStale[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToStale[ 1 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToStale[ 2 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - - expect( result.pendingIssuesToUnlabel ).to.be.an( 'array' ); - expect( result.pendingIssuesToUnlabel ).to.have.length( 3 ); - expect( result.pendingIssuesToUnlabel[ 0 ] ).to.deep.equal( - { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToUnlabel[ 1 ] ).to.deep.equal( + expect( result ).toEqual( { + pendingIssuesToStale: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); - expect( result.pendingIssuesToUnlabel[ 2 ] ).to.deep.equal( + ], + pendingIssuesToUnlabel: [ + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, + { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' }, { id: 'IssueId', type: 'Issue', title: 'IssueTitle', url: 'https://github.com/' } - ); + ] } ); } ); - it( 'should check each issue if it should be staled or unlabeled', () => { + it( 'should check each issue if it should be staled or unlabeled', async () => { const commentMember = { createdAt: '2022-11-30T23:59:59Z', authorAssociation: 'MEMBER' @@ -1681,7 +1602,7 @@ describe( 'dev-stale-bot/lib', () => { { ...issueBase, number: 3, comments: { nodes: [ commentNonMember ] } } ]; - stubs.GraphQLClient.request.resolves( { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { search: { issueCount: issues.length, nodes: issues, @@ -1689,47 +1610,61 @@ describe( 'dev-stale-bot/lib', () => { } } ); - stubs.isPendingIssueToStale.onCall( 1 ).returns( false ); - stubs.isPendingIssueToUnlabel.onCall( 1 ).returns( false ); - - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( result => { - expect( stubs.isPendingIssueToStale.callCount ).to.equal( 3 ); - - expect( stubs.isPendingIssueToStale.getCall( 0 ).args[ 0 ] ).to.have.deep.property( 'lastComment', { - createdAt: '2022-11-30T23:59:59Z', isExternal: false - } ); - expect( stubs.isPendingIssueToStale.getCall( 0 ).args[ 1 ] ).to.deep.equal( optionsBase ); - - expect( stubs.isPendingIssueToStale.getCall( 1 ).args[ 0 ] ).to.have.deep.property( 'lastComment', { - createdAt: '2022-11-30T23:59:59Z', isExternal: false - } ); - expect( stubs.isPendingIssueToStale.getCall( 1 ).args[ 1 ] ).to.deep.equal( optionsBase ); - - expect( stubs.isPendingIssueToStale.getCall( 2 ).args[ 0 ] ).to.have.deep.property( 'lastComment', { - createdAt: '2022-11-30T23:59:59Z', isExternal: true - } ); - expect( stubs.isPendingIssueToStale.getCall( 2 ).args[ 1 ] ).to.deep.equal( optionsBase ); - - expect( stubs.isPendingIssueToUnlabel.callCount ).to.equal( 3 ); - - expect( stubs.isPendingIssueToUnlabel.getCall( 0 ).args[ 0 ] ).to.have.deep.property( 'lastComment', { - createdAt: '2022-11-30T23:59:59Z', isExternal: false - } ); - expect( stubs.isPendingIssueToUnlabel.getCall( 1 ).args[ 0 ] ).to.have.deep.property( 'lastComment', { - createdAt: '2022-11-30T23:59:59Z', isExternal: false - } ); - expect( stubs.isPendingIssueToUnlabel.getCall( 2 ).args[ 0 ] ).to.have.deep.property( 'lastComment', { - createdAt: '2022-11-30T23:59:59Z', isExternal: true - } ); - - expect( result.pendingIssuesToStale ).to.be.an( 'array' ); - expect( result.pendingIssuesToStale ).to.have.length( 2 ); - expect( result.pendingIssuesToUnlabel ).to.be.an( 'array' ); - expect( result.pendingIssuesToUnlabel ).to.have.length( 2 ); + vi.mocked( isPendingIssueToStale ).mockReturnValueOnce( false ); + vi.mocked( isPendingIssueToUnlabel ).mockReturnValueOnce( false ); + + const result = await githubRepository.searchPendingIssues( optionsBase, onProgress ); + + expect( vi.mocked( isPendingIssueToStale ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( isPendingIssueToStale ) ).toHaveBeenNthCalledWith( + 1, + expect.objectContaining( { + lastComment: { createdAt: '2022-11-30T23:59:59Z', isExternal: false } + } ), + optionsBase + ); + expect( vi.mocked( isPendingIssueToStale ) ).toHaveBeenNthCalledWith( + 2, + expect.objectContaining( { + lastComment: { createdAt: '2022-11-30T23:59:59Z', isExternal: false } + } ), + optionsBase + ); + expect( vi.mocked( isPendingIssueToStale ) ).toHaveBeenNthCalledWith( + 3, + expect.objectContaining( { + lastComment: { createdAt: '2022-11-30T23:59:59Z', isExternal: true } + } ), + optionsBase + ); + + expect( vi.mocked( isPendingIssueToUnlabel ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( isPendingIssueToUnlabel ) ).toHaveBeenNthCalledWith( + 1, + expect.objectContaining( { + lastComment: { createdAt: '2022-11-30T23:59:59Z', isExternal: false } + } ) + ); + expect( vi.mocked( isPendingIssueToUnlabel ) ).toHaveBeenNthCalledWith( + 2, + expect.objectContaining( { + lastComment: { createdAt: '2022-11-30T23:59:59Z', isExternal: false } + } ) + ); + expect( vi.mocked( isPendingIssueToUnlabel ) ).toHaveBeenNthCalledWith( + 3, + expect.objectContaining( { + lastComment: { createdAt: '2022-11-30T23:59:59Z', isExternal: true } + } ) + ); + + expect( result ).toEqual( { + pendingIssuesToStale: [ expect.anything(), expect.anything() ], + pendingIssuesToUnlabel: [ expect.anything(), expect.anything() ] } ); } ); - it( 'should call on progress callback', () => { + it( 'should call on progress callback', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1746,16 +1681,15 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { - expect( onProgress.callCount ).to.equal( 3 ); + await githubRepository.searchPendingIssues( optionsBase, onProgress ); - expect( onProgress.getCall( 0 ).args[ 0 ] ).to.deep.equal( { done: 1, total: 3 } ); - expect( onProgress.getCall( 1 ).args[ 0 ] ).to.deep.equal( { done: 2, total: 3 } ); - expect( onProgress.getCall( 2 ).args[ 0 ] ).to.deep.equal( { done: 3, total: 3 } ); - } ); + expect( vi.mocked( onProgress ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 1, { done: 1, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 2, { done: 2, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 3, { done: 3, total: 3 } ); } ); - it( 'should count total hits only once using the value from first response', () => { + it( 'should count total hits only once using the value from first response', async () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, @@ -1772,17 +1706,16 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { - expect( onProgress.callCount ).to.equal( 3 ); + await githubRepository.searchPendingIssues( optionsBase, onProgress ); - expect( onProgress.getCall( 0 ).args[ 0 ] ).to.deep.equal( { done: 1, total: 3 } ); - expect( onProgress.getCall( 1 ).args[ 0 ] ).to.deep.equal( { done: 2, total: 3 } ); - expect( onProgress.getCall( 2 ).args[ 0 ] ).to.deep.equal( { done: 3, total: 3 } ); - } ); + expect( vi.mocked( onProgress ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 1, { done: 1, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 2, { done: 2, total: 3 } ); + expect( vi.mocked( onProgress ) ).toHaveBeenNthCalledWith( 3, { done: 3, total: 3 } ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { @@ -1798,7 +1731,7 @@ describe( 'dev-stale-bot/lib', () => { const issues = [ { ...issueBase, number: 1 }, { ...issueBase, number: 2 }, - { ...issueBase, number: 3 } + { error: new Error( '500 Internal Server Error' ) } ]; paginateRequest( issues, ( { nodes, pageInfo } ) => { @@ -1811,8 +1744,6 @@ describe( 'dev-stale-bot/lib', () => { }; } ); - stubs.GraphQLClient.request.onCall( 2 ).rejects( new Error( '500 Internal Server Error' ) ); - return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { throw new Error( 'Expected to be rejected.' ); @@ -1826,18 +1757,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.searchPendingIssues( optionsBase, onProgress ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#searchPendingIssues()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#searchPendingIssues()".', error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -1846,23 +1776,22 @@ describe( 'dev-stale-bot/lib', () => { describe( '#addComment()', () => { it( 'should be a function', () => { - expect( githubRepository.addComment ).to.be.a( 'function' ); + expect( githubRepository.addComment ).toBeInstanceOf( Function ); } ); - it( 'should add a comment', () => { - stubs.GraphQLClient.request.resolves(); + it( 'should add a comment', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue(); - return githubRepository.addComment( 'IssueId', 'A comment.' ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'mutation AddComment' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( - { nodeId: 'IssueId', comment: 'A comment.' } - ); - } ); + await githubRepository.addComment( 'IssueId', 'A comment.' ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'mutation AddComment', { nodeId: 'IssueId', comment: 'A comment.' } + ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.addComment( 'IssueId', 'A comment.' ).then( () => { @@ -1877,16 +1806,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.addComment( 'IssueId', 'A comment.' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( 'Unexpected error when executing "#addComment()".' ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#addComment()".', error + ); } ); } ); @@ -1904,18 +1834,17 @@ describe( 'dev-stale-bot/lib', () => { } ); it( 'should be a function', () => { - expect( githubRepository.getLabels ).to.be.a( 'function' ); + expect( githubRepository.getLabels ).toBeInstanceOf( Function ); } ); - it( 'should return an empty array if no labels are provided', () => { - return githubRepository.getLabels( 'ckeditor/ckeditor5', [] ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 0 ); - } ); + it( 'should return an empty array if no labels are provided', async () => { + const result = await githubRepository.getLabels( 'ckeditor/ckeditor5', [] ); + + expect( result ).toEqual( [] ); } ); - it( 'should return labels', () => { - stubs.GraphQLClient.request.resolves( { + it( 'should return labels', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { repository: { labels: { nodes: labels @@ -1923,17 +1852,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.equal( 'LabelId1' ); - expect( result[ 1 ] ).to.equal( 'LabelId2' ); - expect( result[ 2 ] ).to.equal( 'LabelId3' ); - } ); + const result = await githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ); + + expect( result ).toEqual( [ + 'LabelId1', 'LabelId2', 'LabelId3' + ] ); } ); - it( 'should return only requested labels even if GitHub endpoint returned additional ones', () => { - stubs.GraphQLClient.request.resolves( { + it( 'should return only requested labels even if GitHub endpoint returned additional ones', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { repository: { labels: { nodes: [ @@ -1947,17 +1874,15 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ).then( result => { - expect( result ).to.be.an( 'array' ); - expect( result ).to.have.length( 3 ); - expect( result[ 0 ] ).to.equal( 'LabelId1' ); - expect( result[ 1 ] ).to.equal( 'LabelId2' ); - expect( result[ 2 ] ).to.equal( 'LabelId3' ); - } ); + const result = await githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ); + + expect( result ).toEqual( [ + 'LabelId1', 'LabelId2', 'LabelId3' + ] ); } ); - it( 'should send one request for labels', () => { - stubs.GraphQLClient.request.resolves( { + it( 'should send one request for labels', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( { repository: { labels: { nodes: labels @@ -1965,19 +1890,16 @@ describe( 'dev-stale-bot/lib', () => { } } ); - return githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'query GetLabels' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { - repositoryOwner: 'ckeditor', - repositoryName: 'ckeditor5', - labelNames: 'type:bug type:task type:feature' - } ); - } ); + await githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'query GetLabels', + { repositoryOwner: 'ckeditor', repositoryName: 'ckeditor5', labelNames: 'type:bug type:task type:feature' } ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ).then( () => { @@ -1992,16 +1914,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.getLabels( 'ckeditor/ckeditor5', [ 'type:bug', 'type:task', 'type:feature' ] ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( 'Unexpected error when executing "#getLabels()".' ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#getLabels()".', error + ); } ); } ); @@ -2009,24 +1932,22 @@ describe( 'dev-stale-bot/lib', () => { describe( '#addLabels()', () => { it( 'should be a function', () => { - expect( githubRepository.addLabels ).to.be.a( 'function' ); + expect( githubRepository.addLabels ).toBeInstanceOf( Function ); } ); - it( 'should add a comment', () => { - stubs.GraphQLClient.request.resolves(); + it( 'should add a comment', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue(); - return githubRepository.addLabels( 'IssueId', [ 'LabelId' ] ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'mutation AddLabels' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { - nodeId: 'IssueId', - labelIds: [ 'LabelId' ] - } ); - } ); + await githubRepository.addLabels( 'IssueId', [ 'LabelId' ] ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'mutation AddLabels', { nodeId: 'IssueId', labelIds: [ 'LabelId' ] } + ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.addLabels( 'IssueId', [ 'LabelId' ] ).then( () => { @@ -2041,16 +1962,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.addLabels( 'IssueId', [ 'LabelId' ] ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( 'Unexpected error when executing "#addLabels()".' ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#addLabels()".', error + ); } ); } ); @@ -2058,24 +1980,22 @@ describe( 'dev-stale-bot/lib', () => { describe( '#removeLabels()', () => { it( 'should be a function', () => { - expect( githubRepository.removeLabels ).to.be.a( 'function' ); + expect( githubRepository.removeLabels ).toBeInstanceOf( Function ); } ); - it( 'should add a comment', () => { - stubs.GraphQLClient.request.resolves(); + it( 'should add a comment', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue(); - return githubRepository.removeLabels( 'IssueId', [ 'LabelId' ] ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'mutation RemoveLabels' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { - nodeId: 'IssueId', - labelIds: [ 'LabelId' ] - } ); - } ); + await githubRepository.removeLabels( 'IssueId', [ 'LabelId' ] ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'mutation RemoveLabels', { nodeId: 'IssueId', labelIds: [ 'LabelId' ] } + ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.removeLabels( 'IssueId', [ 'LabelId' ] ).then( () => { @@ -2090,18 +2010,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.removeLabels( 'IssueId', [ 'LabelId' ] ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#removeLabels()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#removeLabels()".', error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -2109,31 +2028,33 @@ describe( 'dev-stale-bot/lib', () => { describe( '#closeIssueOrPullRequest()', () => { it( 'should be a function', () => { - expect( githubRepository.closeIssueOrPullRequest ).to.be.a( 'function' ); + expect( githubRepository.closeIssueOrPullRequest ).toBeInstanceOf( Function ); } ); - it( 'should close issue', () => { - stubs.GraphQLClient.request.resolves(); + it( 'should close issue', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue(); - return githubRepository.closeIssueOrPullRequest( 'Issue', 'IssueId' ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'mutation CloseIssue' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { nodeId: 'IssueId' } ); - } ); + await githubRepository.closeIssueOrPullRequest( 'Issue', 'IssueId' ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'mutation CloseIssue', { nodeId: 'IssueId' } + ); } ); - it( 'should close pull request', () => { - stubs.GraphQLClient.request.resolves(); + it( 'should close pull request', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue(); - return githubRepository.closeIssueOrPullRequest( 'PullRequest', 'PullRequestId' ).then( () => { - expect( stubs.GraphQLClient.request.calledOnce ).to.equal( true ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 0 ] ).to.equal( 'mutation ClosePullRequest' ); - expect( stubs.GraphQLClient.request.getCall( 0 ).args[ 1 ] ).to.deep.equal( { nodeId: 'PullRequestId' } ); - } ); + await githubRepository.closeIssueOrPullRequest( 'PullRequest', 'PullRequestId' ); + + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledWith( + 'mutation ClosePullRequest', { nodeId: 'PullRequestId' } + ); } ); it( 'should reject if request failed', () => { - stubs.GraphQLClient.request.rejects( new Error( '500 Internal Server Error' ) ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( new Error( '500 Internal Server Error' ) ); return githubRepository.closeIssueOrPullRequest( 'Issue', 'IssueId' ).then( () => { @@ -2148,18 +2069,17 @@ describe( 'dev-stale-bot/lib', () => { it( 'should log an error if request failed', () => { const error = new Error( '500 Internal Server Error' ); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.closeIssueOrPullRequest( 'Issue', 'IssueId' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, () => { - expect( stubs.logger.error.callCount ).to.equal( 1 ); - expect( stubs.logger.error.getCall( 0 ).args[ 0 ] ).to.equal( - 'Unexpected error when executing "#closeIssueOrPullRequest()".' + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerErrorMock ) ).toHaveBeenCalledWith( + 'Unexpected error when executing "#closeIssueOrPullRequest()".', error ); - expect( stubs.logger.error.getCall( 0 ).args[ 1 ] ).to.equal( error ); } ); } ); @@ -2172,39 +2092,37 @@ describe( 'dev-stale-bot/lib', () => { } }; - let clock; - beforeEach( () => { - clock = sinon.useFakeTimers(); + vi.useFakeTimers(); } ); afterEach( () => { - clock.restore(); + vi.useRealTimers(); } ); it( 'should be a function', () => { - expect( githubRepository.sendRequest ).to.be.a( 'function' ); + expect( githubRepository.sendRequest ).toBeInstanceOf( Function ); } ); - it( 'should resolve with the payload if no error occurred', () => { - stubs.GraphQLClient.request.resolves( payload ); + it( 'should resolve with the payload if no error occurred', async () => { + vi.mocked( graphQLClientRequestMock ).mockResolvedValue( payload ); - return githubRepository.sendRequest( 'query' ).then( result => { - expect( result ).to.equal( payload ); - } ); + const result = await githubRepository.sendRequest( 'query' ); + + expect( result ).toEqual( payload ); } ); it( 'should reject with the error - no custom properties', () => { const error = new Error(); - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.sendRequest( 'query' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, err => { - expect( err ).to.equal( error ); + expect( err ).toEqual( error ); } ); } ); @@ -2213,14 +2131,14 @@ describe( 'dev-stale-bot/lib', () => { const error = new Error(); error.response = {}; - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.sendRequest( 'query' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, err => { - expect( err ).to.equal( error ); + expect( err ).toEqual( error ); } ); } ); @@ -2231,14 +2149,14 @@ describe( 'dev-stale-bot/lib', () => { errors: [] }; - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.sendRequest( 'query' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, err => { - expect( err ).to.equal( error ); + expect( err ).toEqual( error ); } ); } ); @@ -2251,14 +2169,14 @@ describe( 'dev-stale-bot/lib', () => { ] }; - stubs.GraphQLClient.request.rejects( error ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValue( error ); return githubRepository.sendRequest( 'query' ).then( () => { throw new Error( 'Expected to be rejected.' ); }, err => { - expect( err ).to.equal( error ); + expect( err ).toEqual( error ); } ); } ); @@ -2276,15 +2194,16 @@ describe( 'dev-stale-bot/lib', () => { headers: new Map( [ [ 'x-ratelimit-reset', resetTimestamp ] ] ) }; - stubs.GraphQLClient.request.onCall( 0 ).rejects( error ).onCall( 1 ).resolves( payload ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValueOnce( error ); + vi.mocked( graphQLClientRequestMock ).mockResolvedValueOnce( payload ); const sendPromise = githubRepository.sendRequest( 'query' ); - expect( stubs.GraphQLClient.request.callCount ).to.equal( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); - await clock.tickAsync( timeToWait * 1000 ); + await vi.advanceTimersByTimeAsync( timeToWait * 1000 ); - expect( stubs.GraphQLClient.request.callCount ).to.equal( 2 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 2 ); return sendPromise; } ); @@ -2297,17 +2216,18 @@ describe( 'dev-stale-bot/lib', () => { headers: new Map( [ [ 'retry-after', timeToWait ] ] ) }; - stubs.GraphQLClient.request.onCall( 0 ).rejects( error ).onCall( 1 ).resolves( payload ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValueOnce( error ); + vi.mocked( graphQLClientRequestMock ).mockResolvedValueOnce( payload ); const sendPromise = githubRepository.sendRequest( 'query' ); - expect( stubs.GraphQLClient.request.callCount ).to.equal( 1 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 1 ); - await clock.tickAsync( timeToWait * 1000 ); + await vi.advanceTimersByTimeAsync( timeToWait * 1000 ); - expect( stubs.GraphQLClient.request.callCount ).to.equal( 2 ); + expect( vi.mocked( graphQLClientRequestMock ) ).toHaveBeenCalledTimes( 2 ); - return sendPromise; + await sendPromise; } ); it( 'should log the progress and resolve with the payload after API rate is reset', async () => { @@ -2323,30 +2243,34 @@ describe( 'dev-stale-bot/lib', () => { headers: new Map( [ [ 'x-ratelimit-reset', resetTimestamp ] ] ) }; - stubs.GraphQLClient.request.onCall( 0 ).rejects( error ).onCall( 1 ).resolves( payload ); + vi.mocked( graphQLClientRequestMock ).mockRejectedValueOnce( error ); + vi.mocked( graphQLClientRequestMock ).mockResolvedValueOnce( payload ); const sendPromise = githubRepository.sendRequest( 'query' ); - await clock.tickAsync( 0 ); + await vi.advanceTimersByTimeAsync( 0 ); - expect( stubs.logger.info.callCount ).to.equal( 1 ); - expect( stubs.logger.info.getCall( 0 ).args[ 0 ] ).to.equal( + expect( vi.mocked( loggerInfoMock ) ).toHaveBeenCalledTimes( 1 ); + expect( vi.mocked( loggerInfoMock ) ).toHaveBeenLastCalledWith( 'ā›” The API limit is exceeded. Request is paused for 28 minutes.' ); - await clock.tickAsync( timeToWait * 1000 ); + await vi.advanceTimersByTimeAsync( timeToWait * 1000 ); - expect( stubs.logger.info.callCount ).to.equal( 2 ); - expect( stubs.logger.info.getCall( 1 ).args[ 0 ] ).to.equal( 'šŸ“ Re-sending postponed request.' ); + expect( vi.mocked( loggerInfoMock ) ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( loggerInfoMock ) ).toHaveBeenLastCalledWith( 'šŸ“ Re-sending postponed request.' ); - return sendPromise.then( result => { - expect( result ).to.equal( payload ); - } ); + const result = await sendPromise; + expect( result ).toEqual( payload ); } ); } ); function paginateRequest( dataToPaginate, paginator ) { for ( const entry of dataToPaginate ) { + if ( entry.error ) { + vi.mocked( graphQLClientRequestMock ).mockRejectedValueOnce( entry.error ); + } + const entryIndex = dataToPaginate.indexOf( entry ); const isLastEntry = entryIndex === dataToPaginate.length - 1; const pageInfo = isLastEntry ? pageInfoNoNextPage : pageInfoWithNextPage; @@ -2357,7 +2281,7 @@ describe( 'dev-stale-bot/lib', () => { entryIndex } ); - stubs.GraphQLClient.request.onCall( entryIndex ).resolves( result ); + vi.mocked( graphQLClientRequestMock ).mockResolvedValueOnce( result ); } } } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/findstaledate.js b/packages/ckeditor5-dev-stale-bot/tests/utils/findstaledate.js index 53fbed7ea..ec04151b2 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/findstaledate.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/findstaledate.js @@ -3,8 +3,8 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const findStaleDate = require( '../../lib/utils/findstaledate' ); +import { describe, it, expect, beforeEach } from 'vitest'; +import findStaleDate from '../../lib/utils/findstaledate.js'; describe( 'dev-stale-bot/lib/utils', () => { describe( 'findStaleDate', () => { @@ -17,7 +17,7 @@ describe( 'dev-stale-bot/lib/utils', () => { } ); it( 'should be a function', () => { - expect( findStaleDate ).to.be.a( 'function' ); + expect( findStaleDate ).toBeInstanceOf( Function ); } ); it( 'should return date when stale label was set', () => { @@ -27,7 +27,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ] }; - expect( findStaleDate( issue, optionsBase ) ).to.equal( '2022-12-01T00:00:00Z' ); + expect( findStaleDate( issue, optionsBase ) ).toEqual( '2022-12-01T00:00:00Z' ); } ); it( 'should return date when stale label was set if issue has multiple different events', () => { @@ -42,7 +42,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ] }; - expect( findStaleDate( issue, optionsBase ) ).to.equal( '2022-12-01T00:00:00Z' ); + expect( findStaleDate( issue, optionsBase ) ).toEqual( '2022-12-01T00:00:00Z' ); } ); it( 'should return most recent date when stale label was set if issue has multiple stale label events', () => { @@ -57,7 +57,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ] }; - expect( findStaleDate( issue, optionsBase ) ).to.equal( '2022-12-06T00:00:00Z' ); + expect( findStaleDate( issue, optionsBase ) ).toEqual( '2022-12-06T00:00:00Z' ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequestactive.js b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequestactive.js index 7d17211d8..716163c4e 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequestactive.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequestactive.js @@ -3,8 +3,8 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const isIssueOrPullRequestActive = require( '../../lib/utils/isissueorpullrequestactive' ); +import { describe, it, expect, beforeEach } from 'vitest'; +import isIssueOrPullRequestActive from '../../lib/utils/isissueorpullrequestactive.js'; describe( 'dev-stale-bot/lib/utils', () => { describe( 'isIssueOrPullRequestActive', () => { @@ -29,7 +29,7 @@ describe( 'dev-stale-bot/lib/utils', () => { } ); it( 'should be a function', () => { - expect( isIssueOrPullRequestActive ).to.be.a( 'function' ); + expect( isIssueOrPullRequestActive ).toBeInstanceOf( Function ); } ); it( 'should return true for issue created after stale date', () => { @@ -38,7 +38,7 @@ describe( 'dev-stale-bot/lib/utils', () => { createdAt: afterStaleDate }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( true ); } ); it( 'should return false for issue created before stale date', () => { @@ -47,7 +47,7 @@ describe( 'dev-stale-bot/lib/utils', () => { createdAt: beforeStaleDate }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( false ); } ); it( 'should return true for issue edited after stale date', () => { @@ -56,7 +56,7 @@ describe( 'dev-stale-bot/lib/utils', () => { lastEditedAt: afterStaleDate }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( true ); } ); it( 'should return false for issue edited before stale date', () => { @@ -65,7 +65,7 @@ describe( 'dev-stale-bot/lib/utils', () => { lastEditedAt: beforeStaleDate }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( false ); } ); it( 'should return true for issue with reaction after stale date', () => { @@ -74,7 +74,7 @@ describe( 'dev-stale-bot/lib/utils', () => { lastReactedAt: afterStaleDate }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( true ); } ); it( 'should return false for issue with reaction before stale date', () => { @@ -83,7 +83,7 @@ describe( 'dev-stale-bot/lib/utils', () => { lastReactedAt: beforeStaleDate }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( false ); } ); it( 'should return true for issue with activity after stale date', () => { @@ -95,7 +95,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ] }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( true ); } ); it( 'should return false for issue without activity after stale date', () => { @@ -107,7 +107,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ] }; - expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestActive( issue, staleDate, optionsBase ) ).toEqual( false ); } ); it( 'should return true for issue with activity after stale date and its author is not ignored', () => { @@ -124,7 +124,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ignoredActivityLogins: [ 'CKEditorBot' ] }; - expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).to.be.true; + expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).toEqual( true ); } ); it( 'should return false for issue with activity after stale date but its author is ignored', () => { @@ -141,7 +141,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ignoredActivityLogins: [ 'CKEditorBot' ] }; - expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).to.be.false; + expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).toEqual( false ); } ); it( 'should return true for issue with activity after stale date and label is not ignored', () => { @@ -158,7 +158,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ignoredActivityLabels: [ 'status:stale' ] }; - expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).to.be.true; + expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).toEqual( true ); } ); it( 'should return false for issue with activity after stale date but label is ignored', () => { @@ -175,7 +175,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ignoredActivityLabels: [ 'status:stale' ] }; - expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).to.be.false; + expect( isIssueOrPullRequestActive( issue, staleDate, options ) ).toEqual( false ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttoclose.js b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttoclose.js index c578a0310..5ef1a0208 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttoclose.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttoclose.js @@ -3,13 +3,17 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import isIssueOrPullRequestToClose from '../../lib/utils/isissueorpullrequesttoclose.js'; +import findStaleDate from '../../lib/utils/findstaledate.js'; +import isIssueOrPullRequestActive from '../../lib/utils/isissueorpullrequestactive.js'; + +vi.mock( '../../lib/utils/findstaledate' ); +vi.mock( '../../lib/utils/isissueorpullrequestactive' ); describe( 'dev-stale-bot/lib/utils', () => { describe( 'isIssueOrPullRequestToClose', () => { - let isIssueOrPullRequestToClose, staleDate, afterStaleDate, beforeStaleDate, issueBase, optionsBase, stubs; + let staleDate, afterStaleDate, beforeStaleDate, issueBase, optionsBase; beforeEach( () => { staleDate = '2022-12-01T00:00:00Z'; @@ -22,27 +26,18 @@ describe( 'dev-stale-bot/lib/utils', () => { closeDate: staleDate }; - stubs = { - findStaleDate: sinon.stub().returns( staleDate ), - isIssueOrPullRequestActive: sinon.stub() - }; - - isIssueOrPullRequestToClose = proxyquire( '../../lib/utils/isissueorpullrequesttoclose', { - './findstaledate': stubs.findStaleDate, - './isissueorpullrequestactive': stubs.isIssueOrPullRequestActive - } ); + vi.mocked( findStaleDate ).mockReturnValue( staleDate ); } ); it( 'should be a function', () => { - expect( isIssueOrPullRequestToClose ).to.be.a( 'function' ); + expect( isIssueOrPullRequestToClose ).toBeInstanceOf( Function ); } ); it( 'should get the stale date from issue activity', () => { isIssueOrPullRequestToClose( issueBase, optionsBase ); - expect( stubs.findStaleDate.calledOnce ).to.equal( true ); - expect( stubs.findStaleDate.getCall( 0 ).args[ 0 ] ).to.equal( issueBase ); - expect( stubs.findStaleDate.getCall( 0 ).args[ 1 ] ).to.equal( optionsBase ); + expect( vi.mocked( findStaleDate ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( findStaleDate ) ).toHaveBeenCalledWith( issueBase, optionsBase ); } ); it( 'should not check issue activity if time to close has not passed', () => { @@ -50,7 +45,7 @@ describe( 'dev-stale-bot/lib/utils', () => { isIssueOrPullRequestToClose( issueBase, optionsBase ); - expect( stubs.isIssueOrPullRequestActive.called ).to.equal( false ); + expect( vi.mocked( isIssueOrPullRequestActive ) ).not.toHaveBeenCalled(); } ); it( 'should check issue activity if time to close has passed', () => { @@ -58,38 +53,36 @@ describe( 'dev-stale-bot/lib/utils', () => { isIssueOrPullRequestToClose( issueBase, optionsBase ); - expect( stubs.isIssueOrPullRequestActive.calledOnce ).to.equal( true ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 0 ] ).to.equal( issueBase ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 1 ] ).to.equal( staleDate ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 2 ] ).to.equal( optionsBase ); + expect( vi.mocked( isIssueOrPullRequestActive ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( isIssueOrPullRequestActive ) ).toHaveBeenCalledWith( issueBase, staleDate, optionsBase ); } ); it( 'should return true if issue is not active after stale date and time to close has passed', () => { optionsBase.closeDate = afterStaleDate; - stubs.isIssueOrPullRequestActive.returns( false ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( false ); - expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).toEqual( true ); } ); it( 'should return false if issue is not active after stale date and time to close has not passed', () => { optionsBase.closeDate = beforeStaleDate; - stubs.isIssueOrPullRequestActive.returns( false ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( false ); - expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).toEqual( false ); } ); it( 'should return false if issue is active after stale date and time to close has passed', () => { optionsBase.closeDate = afterStaleDate; - stubs.isIssueOrPullRequestActive.returns( true ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( true ); - expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).toEqual( false ); } ); it( 'should return false if issue is active after stale date and time to close has not passed', () => { optionsBase.closeDate = beforeStaleDate; - stubs.isIssueOrPullRequestActive.returns( true ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( true ); - expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestToClose( issueBase, optionsBase ) ).toEqual( false ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttostale.js b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttostale.js index 36a6bd09b..7d5821801 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttostale.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttostale.js @@ -3,13 +3,15 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import isIssueOrPullRequestActive from '../../lib/utils/isissueorpullrequestactive.js'; +import isIssueOrPullRequestToStale from '../../lib/utils/isissueorpullrequesttostale.js'; + +vi.mock( '../../lib/utils/isissueorpullrequestactive' ); describe( 'dev-stale-bot/lib/utils', () => { describe( 'isIssueOrPullRequestToStale', () => { - let isIssueOrPullRequestToStale, staleDate, issueBase, optionsBase, stubs; + let staleDate, issueBase, optionsBase; beforeEach( () => { staleDate = '2022-12-01T00:00:00Z'; @@ -19,39 +21,29 @@ describe( 'dev-stale-bot/lib/utils', () => { optionsBase = { staleDate }; - - stubs = { - isIssueOrPullRequestActive: sinon.stub() - }; - - isIssueOrPullRequestToStale = proxyquire( '../../lib/utils/isissueorpullrequesttostale', { - './isissueorpullrequestactive': stubs.isIssueOrPullRequestActive - } ); } ); it( 'should be a function', () => { - expect( isIssueOrPullRequestToStale ).to.be.a( 'function' ); + expect( isIssueOrPullRequestToStale ).toBeInstanceOf( Function ); } ); it( 'should check issue activity', () => { isIssueOrPullRequestToStale( issueBase, optionsBase ); - expect( stubs.isIssueOrPullRequestActive.calledOnce ).to.equal( true ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 0 ] ).to.equal( issueBase ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 1 ] ).to.equal( staleDate ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 2 ] ).to.equal( optionsBase ); + expect( vi.mocked( isIssueOrPullRequestActive ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( isIssueOrPullRequestActive ) ).toHaveBeenLastCalledWith( issueBase, staleDate, optionsBase ); } ); it( 'should return true if issue is not active after stale date', () => { - stubs.isIssueOrPullRequestActive.returns( false ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( false ); - expect( isIssueOrPullRequestToStale( issueBase, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestToStale( issueBase, optionsBase ) ).toEqual( true ); } ); it( 'should return false if issue is active after stale date', () => { - stubs.isIssueOrPullRequestActive.returns( true ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( true ); - expect( isIssueOrPullRequestToStale( issueBase, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestToStale( issueBase, optionsBase ) ).toEqual( false ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttounstale.js b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttounstale.js index 82ccc69ab..03be2cb2b 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttounstale.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/isissueorpullrequesttounstale.js @@ -3,13 +3,17 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import findStaleDate from '../../lib/utils/findstaledate.js'; +import isIssueOrPullRequestActive from '../../lib/utils/isissueorpullrequestactive.js'; +import isIssueOrPullRequestToUnstale from '../../lib/utils/isissueorpullrequesttounstale.js'; + +vi.mock( '../../lib/utils/findstaledate' ); +vi.mock( '../../lib/utils/isissueorpullrequestactive' ); describe( 'dev-stale-bot/lib/utils', () => { describe( 'isIssueOrPullRequestToUnstale', () => { - let isIssueOrPullRequestToUnstale, staleDate, issueBase, optionsBase, stubs; + let staleDate, issueBase, optionsBase; beforeEach( () => { staleDate = '2022-12-01T00:00:00Z'; @@ -18,48 +22,37 @@ describe( 'dev-stale-bot/lib/utils', () => { optionsBase = {}; - stubs = { - findStaleDate: sinon.stub().returns( staleDate ), - isIssueOrPullRequestActive: sinon.stub() - }; - - isIssueOrPullRequestToUnstale = proxyquire( '../../lib/utils/isissueorpullrequesttounstale', { - './findstaledate': stubs.findStaleDate, - './isissueorpullrequestactive': stubs.isIssueOrPullRequestActive - } ); + vi.mocked( findStaleDate ).mockReturnValue( staleDate ); } ); it( 'should be a function', () => { - expect( isIssueOrPullRequestToUnstale ).to.be.a( 'function' ); + expect( isIssueOrPullRequestToUnstale ).toBeInstanceOf( Function ); } ); it( 'should get the stale date from issue activity', () => { isIssueOrPullRequestToUnstale( issueBase, optionsBase ); - expect( stubs.findStaleDate.calledOnce ).to.equal( true ); - expect( stubs.findStaleDate.getCall( 0 ).args[ 0 ] ).to.equal( issueBase ); - expect( stubs.findStaleDate.getCall( 0 ).args[ 1 ] ).to.equal( optionsBase ); + expect( vi.mocked( findStaleDate ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( findStaleDate ) ).toHaveBeenCalledWith( issueBase, optionsBase ); } ); it( 'should check issue activity', () => { isIssueOrPullRequestToUnstale( issueBase, optionsBase ); - expect( stubs.isIssueOrPullRequestActive.calledOnce ).to.equal( true ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 0 ] ).to.equal( issueBase ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 1 ] ).to.equal( staleDate ); - expect( stubs.isIssueOrPullRequestActive.getCall( 0 ).args[ 2 ] ).to.equal( optionsBase ); + expect( vi.mocked( isIssueOrPullRequestActive ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( isIssueOrPullRequestActive ) ).toHaveBeenCalledWith( issueBase, staleDate, optionsBase ); } ); it( 'should return true if issue is active after stale date', () => { - stubs.isIssueOrPullRequestActive.returns( true ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( true ); - expect( isIssueOrPullRequestToUnstale( issueBase, optionsBase ) ).to.be.true; + expect( isIssueOrPullRequestToUnstale( issueBase, optionsBase ) ).toEqual( true ); } ); it( 'should return false if issue is active after stale date', () => { - stubs.isIssueOrPullRequestActive.returns( false ); + vi.mocked( isIssueOrPullRequestActive ).mockReturnValue( false ); - expect( isIssueOrPullRequestToUnstale( issueBase, optionsBase ) ).to.be.false; + expect( isIssueOrPullRequestToUnstale( issueBase, optionsBase ) ).toEqual( false ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuestale.js b/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuestale.js index 7dad5db54..455121f47 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuestale.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuestale.js @@ -3,13 +3,13 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const isPendingIssueStale = require( '../../lib/utils/ispendingissuestale' ); +import { describe, it, expect } from 'vitest'; +import isPendingIssueStale from '../../lib/utils/ispendingissuestale.js'; describe( 'dev-stale-bot/lib/utils', () => { describe( 'isPendingIssueStale', () => { it( 'should be a function', () => { - expect( isPendingIssueStale ).to.be.a( 'function' ); + expect( isPendingIssueStale ).toBeInstanceOf( Function ); } ); it( 'should return false if issue does not have any label', () => { @@ -20,7 +20,7 @@ describe( 'dev-stale-bot/lib/utils', () => { staleLabels: [ 'pending:feedback' ] }; - expect( isPendingIssueStale( issue, options ) ).to.be.false; + expect( isPendingIssueStale( issue, options ) ).toEqual( false ); } ); it( 'should return false if issue does not have a pending label', () => { @@ -31,7 +31,7 @@ describe( 'dev-stale-bot/lib/utils', () => { staleLabels: [ 'pending:feedback' ] }; - expect( isPendingIssueStale( issue, options ) ).to.be.false; + expect( isPendingIssueStale( issue, options ) ).toEqual( false ); } ); it( 'should return false if issue does not have all pending labels', () => { @@ -42,7 +42,7 @@ describe( 'dev-stale-bot/lib/utils', () => { staleLabels: [ 'pending:feedback', 'pending:even-more-feedback' ] }; - expect( isPendingIssueStale( issue, options ) ).to.be.false; + expect( isPendingIssueStale( issue, options ) ).toEqual( false ); } ); it( 'should return true if issue have all pending labels - single label', () => { @@ -53,7 +53,7 @@ describe( 'dev-stale-bot/lib/utils', () => { staleLabels: [ 'pending:feedback' ] }; - expect( isPendingIssueStale( issue, options ) ).to.be.true; + expect( isPendingIssueStale( issue, options ) ).toEqual( true ); } ); it( 'should return true if issue have all pending labels - multiple labels', () => { @@ -64,7 +64,7 @@ describe( 'dev-stale-bot/lib/utils', () => { staleLabels: [ 'pending:feedback', 'pending:even-more-feedback' ] }; - expect( isPendingIssueStale( issue, options ) ).to.be.true; + expect( isPendingIssueStale( issue, options ) ).toEqual( true ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetostale.js b/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetostale.js index 4f506d831..380756baa 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetostale.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetostale.js @@ -3,14 +3,15 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import isPendingIssueToStale from '../../lib/utils/ispendingissuetostale.js'; +import isPendingIssueStale from '../../lib/utils/ispendingissuestale.js'; + +vi.mock( '../../lib/utils/ispendingissuestale' ); describe( 'dev-stale-bot/lib/utils', () => { describe( 'isPendingIssueToStale', () => { - let isPendingIssueToStale, issueBase, optionsBase, stubs; - let staleDatePendingIssue, afterStaleDatePendingIssue, beforeStaleDatePendingIssue; + let issueBase, optionsBase, staleDatePendingIssue, afterStaleDatePendingIssue, beforeStaleDatePendingIssue; beforeEach( () => { staleDatePendingIssue = '2022-12-01T00:00:00Z'; @@ -24,67 +25,58 @@ describe( 'dev-stale-bot/lib/utils', () => { optionsBase = { staleDatePendingIssue }; - - stubs = { - isPendingIssueStale: sinon.stub() - }; - - isPendingIssueToStale = proxyquire( '../../lib/utils/ispendingissuetostale', { - './ispendingissuestale': stubs.isPendingIssueStale - } ); } ); it( 'should be a function', () => { - expect( isPendingIssueToStale ).to.be.a( 'function' ); + expect( isPendingIssueToStale ).toBeInstanceOf( Function ); } ); it( 'should check if issue is stale', () => { isPendingIssueToStale( issueBase, optionsBase ); - expect( stubs.isPendingIssueStale.calledOnce ).to.equal( true ); - expect( stubs.isPendingIssueStale.getCall( 0 ).args[ 0 ] ).to.equal( issueBase ); - expect( stubs.isPendingIssueStale.getCall( 0 ).args[ 1 ] ).to.equal( optionsBase ); + expect( vi.mocked( isPendingIssueStale ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( isPendingIssueStale ) ).toHaveBeenCalledWith( issueBase, optionsBase ); } ); it( 'should return false if issue is already stale', () => { - stubs.isPendingIssueStale.returns( true ); + vi.mocked( isPendingIssueStale ).mockReturnValue( true ); - expect( isPendingIssueToStale( issueBase, optionsBase ) ).to.be.false; + expect( isPendingIssueToStale( issueBase, optionsBase ) ).toEqual( false ); } ); it( 'should return false if issue does not have any comment', () => { - stubs.isPendingIssueStale.returns( false ); + vi.mocked( isPendingIssueStale ).mockReturnValue( false ); - expect( isPendingIssueToStale( issueBase, optionsBase ) ).to.be.false; + expect( isPendingIssueToStale( issueBase, optionsBase ) ).toEqual( false ); } ); it( 'should return false if last comment was created by a community member', () => { - stubs.isPendingIssueStale.returns( false ); + vi.mocked( isPendingIssueStale ).mockReturnValue( false ); issueBase.lastComment = { isExternal: true }; - expect( isPendingIssueToStale( issueBase, optionsBase ) ).to.be.false; + expect( isPendingIssueToStale( issueBase, optionsBase ) ).toEqual( false ); } ); it( 'should return false if last comment was created by a team member and time to stale has not passed', () => { - stubs.isPendingIssueStale.returns( false ); + vi.mocked( isPendingIssueStale ).mockReturnValue( false ); issueBase.lastComment = { isExternal: false, createdAt: afterStaleDatePendingIssue }; - expect( isPendingIssueToStale( issueBase, optionsBase ) ).to.be.false; + expect( isPendingIssueToStale( issueBase, optionsBase ) ).toEqual( false ); } ); it( 'should return true if last comment was created by a team member and time to stale has passed', () => { - stubs.isPendingIssueStale.returns( false ); + vi.mocked( isPendingIssueStale ).mockReturnValue( false ); issueBase.lastComment = { isExternal: false, createdAt: beforeStaleDatePendingIssue }; - expect( isPendingIssueToStale( issueBase, optionsBase ) ).to.be.true; + expect( isPendingIssueToStale( issueBase, optionsBase ) ).toEqual( true ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetounlabel.js b/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetounlabel.js index c5676427f..af739f753 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetounlabel.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/ispendingissuetounlabel.js @@ -3,13 +3,13 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const isPendingIssueToUnlabel = require( '../../lib/utils/ispendingissuetounlabel' ); +import { describe, it, expect } from 'vitest'; +import isPendingIssueToUnlabel from '../../lib/utils/ispendingissuetounlabel.js'; describe( 'dev-stale-bot/lib/utils', () => { describe( 'isPendingIssueToUnlabel', () => { it( 'should be a function', () => { - expect( isPendingIssueToUnlabel ).to.be.a( 'function' ); + expect( isPendingIssueToUnlabel ).toBeInstanceOf( Function ); } ); it( 'should return false if issue does not have any comment', () => { @@ -17,7 +17,7 @@ describe( 'dev-stale-bot/lib/utils', () => { lastComment: null }; - expect( isPendingIssueToUnlabel( issue ) ).to.be.false; + expect( isPendingIssueToUnlabel( issue ) ).toEqual( false ); } ); it( 'should return false if last comment was created by a team member', () => { @@ -27,7 +27,7 @@ describe( 'dev-stale-bot/lib/utils', () => { } }; - expect( isPendingIssueToUnlabel( issue ) ).to.be.false; + expect( isPendingIssueToUnlabel( issue ) ).toEqual( false ); } ); it( 'should return true if last comment was created by a community member', () => { @@ -37,7 +37,7 @@ describe( 'dev-stale-bot/lib/utils', () => { } }; - expect( isPendingIssueToUnlabel( issue ) ).to.be.true; + expect( isPendingIssueToUnlabel( issue ) ).toEqual( true ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/tests/utils/preparesearchquery.js b/packages/ckeditor5-dev-stale-bot/tests/utils/preparesearchquery.js index 0a804bde5..f44b0d0f7 100644 --- a/packages/ckeditor5-dev-stale-bot/tests/utils/preparesearchquery.js +++ b/packages/ckeditor5-dev-stale-bot/tests/utils/preparesearchquery.js @@ -3,45 +3,45 @@ * For licensing, see LICENSE.md. */ -const expect = require( 'chai' ).expect; -const prepareSearchQuery = require( '../../lib/utils/preparesearchquery' ); +import { describe, it, expect } from 'vitest'; +import prepareSearchQuery from '../../lib/utils/preparesearchquery.js'; describe( 'dev-stale-bot/lib/utils', () => { describe( 'prepareSearchQuery', () => { it( 'should be a function', () => { - expect( prepareSearchQuery ).to.be.a( 'function' ); + expect( prepareSearchQuery ).toBeInstanceOf( Function ); } ); it( 'should prepare a query with repository slug', () => { - expect( prepareSearchQuery( { repositorySlug: 'ckeditor/ckeditor5' } ) ).to.include( 'repo:ckeditor/ckeditor5' ); + expect( prepareSearchQuery( { repositorySlug: 'ckeditor/ckeditor5' } ) ).toContain( 'repo:ckeditor/ckeditor5' ); } ); it( 'should prepare a query for issue', () => { - expect( prepareSearchQuery( { type: 'Issue' } ) ).to.include( 'type:issue' ); + expect( prepareSearchQuery( { type: 'Issue' } ) ).toContain( 'type:issue' ); } ); it( 'should prepare a query for pull request', () => { - expect( prepareSearchQuery( { type: 'PullRequest' } ) ).to.include( 'type:pr' ); + expect( prepareSearchQuery( { type: 'PullRequest' } ) ).toContain( 'type:pr' ); } ); it( 'should prepare a query for issue or pull request', () => { - expect( prepareSearchQuery( {} ) ).to.not.include( 'type:' ); + expect( prepareSearchQuery( {} ) ).not.toContain( 'type:' ); } ); it( 'should prepare a query from specified date', () => { - expect( prepareSearchQuery( { searchDate: '2022-12-01' } ) ).to.include( 'created:<2022-12-01' ); + expect( prepareSearchQuery( { searchDate: '2022-12-01' } ) ).toContain( 'created:<2022-12-01' ); } ); it( 'should prepare a query without specifying a start date', () => { - expect( prepareSearchQuery( {} ) ).to.not.include( 'created:' ); + expect( prepareSearchQuery( {} ) ).not.toContain( 'created:' ); } ); it( 'should prepare a query for open items', () => { - expect( prepareSearchQuery( {} ) ).to.include( 'state:open' ); + expect( prepareSearchQuery( {} ) ).toContain( 'state:open' ); } ); it( 'should prepare a query sorted in descending order by creation date', () => { - expect( prepareSearchQuery( {} ) ).to.include( 'sort:created-desc' ); + expect( prepareSearchQuery( {} ) ).toContain( 'sort:created-desc' ); } ); it( 'should prepare a query with ignored labels', () => { @@ -53,7 +53,7 @@ describe( 'dev-stale-bot/lib/utils', () => { 'domain:accessibility' ]; - expect( prepareSearchQuery( { ignoredLabels } ) ).to.include( + expect( prepareSearchQuery( { ignoredLabels } ) ).toContain( '-label:status:stale -label:support:1 -label:support:2 -label:support:3 -label:domain:accessibility' ); } ); @@ -64,7 +64,7 @@ describe( 'dev-stale-bot/lib/utils', () => { 'type:bug' ]; - expect( prepareSearchQuery( { labels } ) ).to.include( 'label:status:stale label:type:bug' ); + expect( prepareSearchQuery( { labels } ) ).toContain( 'label:status:stale label:type:bug' ); } ); it( 'should prepare a query with all fields separated by space', () => { @@ -76,7 +76,7 @@ describe( 'dev-stale-bot/lib/utils', () => { ignoredLabels: [ 'status:stale' ] }; - expect( prepareSearchQuery( options ) ).to.include( + expect( prepareSearchQuery( options ) ).toContain( 'repo:ckeditor/ckeditor5 created:<2022-12-01 type:issue state:open sort:created-desc label:type:bug -label:status:stale' ); } ); diff --git a/packages/ckeditor5-dev-stale-bot/vitest.config.js b/packages/ckeditor5-dev-stale-bot/vitest.config.js new file mode 100644 index 000000000..5ad784a28 --- /dev/null +++ b/packages/ckeditor5-dev-stale-bot/vitest.config.js @@ -0,0 +1,23 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { defineConfig } from 'vitest/config'; + +export default defineConfig( { + test: { + testTimeout: 10000, + restoreMocks: true, + include: [ + 'tests/**/*.js' + ], + coverage: { + provider: 'v8', + include: [ + 'lib/**' + ], + reporter: [ 'text', 'json', 'html', 'lcov' ] + } + } +} );