From 775a6f48811ef69080d35f280b2654515375fe2e Mon Sep 17 00:00:00 2001 From: Shazron Abdullah <36107+shazron@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:03:43 +0800 Subject: [PATCH] chore: update @oclif/core to v4 and eslint to v9 with neostandard (#905) Update @oclif/core from v2.11.6 to v4.9.0 and modernize the ESLint setup by migrating to @adobe/eslint-config-aio-lib-config v5 with neostandard and ESLint 9 flat config. oclif v4's parse() now calls config.runHook('preparse') before parsing, so all tests that instantiate commands directly need a mock config with runHook returning { successes: [] }. Co-Authored-By: Claude Opus 4.6 (1M context) --- .eslintrc.json | 33 ---------- eslint.config.js | 63 +++++++++++++++++++ package.json | 13 ++-- src/commands/app/install.js | 1 - src/commands/app/pack.js | 1 - src/lib/cleanup.js | 4 +- src/lib/install-helper.js | 1 - src/lib/run-dev.js | 1 - test/BaseCommand.test.js | 7 ++- test/TemplatesCommand.test.js | 2 +- test/commands/app/add/action.test.js | 2 +- test/commands/app/add/event.test.js | 1 + test/commands/app/add/extension.test.js | 4 +- test/commands/app/add/index.test.js | 2 +- test/commands/app/add/web-assets.test.js | 1 + test/commands/app/build.test.js | 1 + test/commands/app/config/get/index.test.js | 2 +- .../app/config/get/log-forwarding.test.js | 1 + .../config/get/log-forwarding/errors.test.js | 1 + test/commands/app/config/index.test.js | 2 +- test/commands/app/config/set/index.test.js | 2 +- .../app/config/set/log-forwarding.test.js | 1 + test/commands/app/delete/action.test.js | 2 + test/commands/app/delete/ci.test.js | 1 + test/commands/app/delete/extension.test.js | 1 + test/commands/app/delete/index.test.js | 2 +- test/commands/app/delete/web-assets.test.js | 2 + test/commands/app/deploy.test.js | 23 ++++--- test/commands/app/geturl.test.js | 1 + test/commands/app/index.test.js | 2 +- test/commands/app/info.test.js | 5 ++ test/commands/app/init.test.js | 5 +- test/commands/app/install.test.js | 14 ++++- .../app/list/extension-points.test.js | 1 + test/commands/app/list/extension.test.js | 4 ++ test/commands/app/list/index.test.js | 2 +- test/commands/app/logs.test.js | 1 + test/commands/app/pack.test.js | 47 ++++++-------- test/commands/app/run.test.js | 2 +- test/commands/app/test.test.js | 12 +--- test/commands/app/undeploy.test.js | 3 +- test/jest.setup.js | 12 +++- 42 files changed, 172 insertions(+), 116 deletions(-) delete mode 100644 .eslintrc.json create mode 100644 eslint.config.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 4d2ac8efc..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "extends": "@adobe/eslint-config-aio-lib-config", - "globals": { - "NoErrorThrownError": true, - "getErrorForCallThatShouldThrowAnError": true, - "fixturePath": true, - "fixtureFile": true, - "fixtureJson": true, - "fixtureHjson": true, - "fixtureYaml": true, - "fakeFileSystem": true, - "setFetchMock": true - }, - "settings": { - "jsdoc": { - "ignorePrivate": true - } - }, - "parserOptions": { - "ecmaVersion": "latest" - }, - "rules": { - "jsdoc/no-defaults": 0, - "jsdoc/tag-lines": [ - // The Error level should be `error`, `warn`, or `off` (or 2, 1, or 0) - "error", - "never", - { - "startLines": null - } - ] - } -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..5b270a70d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,63 @@ +const config = require('@adobe/eslint-config-aio-lib-config') + +module.exports = [ + ...config, + { + languageOptions: { + globals: { + NoErrorThrownError: true, + getErrorForCallThatShouldThrowAnError: true, + fixturePath: true, + fixtureFile: true, + fixtureJson: true, + fixtureHjson: true, + fixtureYaml: true, + fakeFileSystem: true, + setFetchMock: true, + createOclifMockConfig: true, + loadFixtureApp: true, + defaultAppHostName: true, + defaultTvmUrl: true, + defaultOwApihost: true, + fakeS3Bucket: true, + fakeOrgId: true, + fakeConfig: true, + aioLegacyAppConfig: true, + fakeS3Creds: true, + extraConfig: true, + fakeTVMResponse: true + } + }, + settings: { + jsdoc: { + ignorePrivate: true + } + }, + rules: { + 'jsdoc/no-defaults': 0, + 'jsdoc/tag-lines': [ + 'error', + 'never', + { + startLines: null + } + ] + } + }, + { + files: ['test/**/*.js'], + languageOptions: { + globals: { + jest: true, + describe: true, + test: true, + it: true, + expect: true, + beforeEach: true, + afterEach: true, + beforeAll: true, + afterAll: true + } + } + } +] diff --git a/package.json b/package.json index 1f64cc2b9..df301b901 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@adobe/generator-aio-app": "^9", "@adobe/generator-app-common-lib": "^3", "@adobe/inquirer-table-checkbox": "^2", - "@oclif/core": "^2.11.6", + "@oclif/core": "^4.9.0", "@octokit/rest": "^19.0.11", "@parcel/core": "^2.7.0", "@parcel/reporter-cli": "^2.7.0", @@ -51,19 +51,14 @@ }, "devDependencies": { "@adobe/aio-lib-test-proxy": "^2", - "@adobe/eslint-config-aio-lib-config": "^4.0.0", + "@adobe/eslint-config-aio-lib-config": "^5.0.0", "@types/jest": "^29", "babel-runtime": "^6.26.0", "core-js": "^3", "eol": "^0.9.1", - "eslint": "^8.57.1", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jest": "^27.9.0", + "eslint": "^9", "eslint-plugin-jsdoc": "^48.11.0", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.6.0", + "neostandard": "^0", "jest": "^29.5.0", "nock": "^13.2.9", "oclif": "^4.17.13", diff --git a/src/commands/app/install.js b/src/commands/app/install.js index 7cba0f875..501a6a952 100644 --- a/src/commands/app/install.js +++ b/src/commands/app/install.js @@ -22,7 +22,6 @@ const jsYaml = require('js-yaml') const { USER_CONFIG_FILE, DEPLOY_CONFIG_FILE, PACKAGE_LOCK_FILE } = require('../../lib/defaults') const ora = require('ora') -// eslint-disable-next-line node/no-missing-require const libConfig = require('@adobe/aio-cli-lib-app-config') class InstallCommand extends BaseCommand { diff --git a/src/commands/app/pack.js b/src/commands/app/pack.js index ba3d07ea5..dd83c478a 100644 --- a/src/commands/app/pack.js +++ b/src/commands/app/pack.js @@ -22,7 +22,6 @@ const { getObjectValue } = require('../../lib/app-helper') const ora = require('ora') const junk = require('junk') -// eslint-disable-next-line node/no-missing-require const libConfig = require('@adobe/aio-cli-lib-app-config') const DIST_FOLDER = 'dist' diff --git a/src/lib/cleanup.js b/src/lib/cleanup.js index bbbef835f..d8359f173 100644 --- a/src/lib/cleanup.js +++ b/src/lib/cleanup.js @@ -40,11 +40,11 @@ class Cleanup { try { await this.run() aioLogger.info('exiting!') - process.exit(0) // eslint-disable-line no-process-exit + process.exit(0) } catch (e) { aioLogger.error('unexpected error while cleaning up!') aioLogger.error(e) - process.exit(1) // eslint-disable-line no-process-exit + process.exit(1) } }) } diff --git a/src/lib/install-helper.js b/src/lib/install-helper.js index 1f04f861e..b83b7787b 100644 --- a/src/lib/install-helper.js +++ b/src/lib/install-helper.js @@ -20,7 +20,6 @@ const ajvAddFormats = require('ajv-formats') * @returns {object} with keys valid (boolean) and errors (object). errors is null if no errors */ function validateJsonWithSchema (fileJson, schemaName) { - /* eslint-disable-next-line node/no-unpublished-require */ const schemas = require('../../schema/index') const ajv = new Ajv({ allErrors: true, diff --git a/src/lib/run-dev.js b/src/lib/run-dev.js index a76d8fe15..2462f632f 100644 --- a/src/lib/run-dev.js +++ b/src/lib/run-dev.js @@ -9,7 +9,6 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -/* eslint-disable no-template-curly-in-string */ const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:runDev', { provider: 'debug' }) const rtLib = require('@adobe/aio-lib-runtime') const rtLibUtils = rtLib.utils diff --git a/test/BaseCommand.test.js b/test/BaseCommand.test.js index c77d56f00..a16077310 100644 --- a/test/BaseCommand.test.js +++ b/test/BaseCommand.test.js @@ -282,7 +282,7 @@ describe('getLibConsoleCLI', () => { test('init', async () => { const cmd = new TheCommand([]) - cmd.config = {} + cmd.config = global.createOclifMockConfig() await cmd.init() expect(cmd.prompt).toBe(mockExtensionPrompt) expect(inquirer.createPromptModule).toHaveBeenCalledWith({ output: process.stderr }) @@ -290,6 +290,7 @@ test('init', async () => { test('catch', async () => { const cmd = new TheCommand([]) + cmd.config = global.createOclifMockConfig() cmd.error = jest.fn() await cmd.catch(new Error('fake error')) expect(cmd.error).toHaveBeenCalledWith('fake error') @@ -297,6 +298,7 @@ test('catch', async () => { test('will change error message when aio app outside of the application root', async () => { const cmd = new TheCommand([]) + cmd.config = global.createOclifMockConfig() cmd.error = jest.fn() await cmd.catch(new Error('ENOENT: no such file or directory, open \'package.json\'')) @@ -308,6 +310,7 @@ test('will change error message when aio app outside of the application root', a test('will change error message when aio app outside of the application root (--verbose)', async () => { const cmd = new TheCommand(['--verbose']) + cmd.config = global.createOclifMockConfig() cmd.error = jest.fn() await cmd.catch(new Error('ENOENT: no such file or directory, open \'package.json\'')) @@ -319,6 +322,7 @@ test('will change error message when aio app outside of the application root (-- test('will handle errors without stack traces when using --verbose flag', async () => { const cmd = new TheCommand(['--verbose']) + cmd.config = global.createOclifMockConfig() cmd.error = jest.fn() const errorWithoutStack = new Error('fake error') delete errorWithoutStack.stack @@ -329,6 +333,7 @@ test('will handle errors without stack traces when using --verbose flag', async test('will handle errors without stack traces when not using --verbose flag', async () => { const cmd = new TheCommand([]) + cmd.config = global.createOclifMockConfig() cmd.error = jest.fn() const errorWithoutStack = new Error('fake error') delete errorWithoutStack.stack diff --git a/test/TemplatesCommand.test.js b/test/TemplatesCommand.test.js index 8b7645ed3..c6a903cea 100644 --- a/test/TemplatesCommand.test.js +++ b/test/TemplatesCommand.test.js @@ -97,7 +97,7 @@ beforeEach(() => { command = new TheCommand() command.config = { runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } inquirer.registerPrompt.mockReset() diff --git a/test/commands/app/add/action.test.js b/test/commands/app/add/action.test.js index 8f7726b21..7d3ce891a 100644 --- a/test/commands/app/add/action.test.js +++ b/test/commands/app/add/action.test.js @@ -59,7 +59,7 @@ beforeEach(() => { command.getLibConsoleCLI.mockResolvedValue({ getEnabledServicesForOrg: mockGetEnabledServicesForOrg }) - command.config = { bin: 'aio' } + command.config = global.createOclifMockConfig({ bin: 'aio' }) command.selectTemplates = jest.fn() command.selectTemplates.mockResolvedValue([]) diff --git a/test/commands/app/add/event.test.js b/test/commands/app/add/event.test.js index 468ebcbe1..315267935 100644 --- a/test/commands/app/add/event.test.js +++ b/test/commands/app/add/event.test.js @@ -22,6 +22,7 @@ const createAppConfig = (aioConfig = {}, appFixtureName = 'legacy-app') => { let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.getAppExtConfigs = jest.fn() command.getAppExtConfigs.mockResolvedValue(createAppConfig(command.appConfig)) command.getFullConfig = jest.fn() diff --git a/test/commands/app/add/extension.test.js b/test/commands/app/add/extension.test.js index 43f7623a0..fac31418b 100644 --- a/test/commands/app/add/extension.test.js +++ b/test/commands/app/add/extension.test.js @@ -41,9 +41,9 @@ beforeEach(() => { command.getFullConfig.mockResolvedValue(createFullConfig({})) command.getConfigFileForKey = jest.fn() command.getConfigFileForKey.mockResolvedValue({}) - command.config = { + command.config = global.createOclifMockConfig({ runCommand: jest.fn() - } + }) command.selectTemplates = jest.fn() command.selectTemplates.mockResolvedValue([]) diff --git a/test/commands/app/add/index.test.js b/test/commands/app/add/index.test.js index cf48fc829..076de7c68 100644 --- a/test/commands/app/add/index.test.js +++ b/test/commands/app/add/index.test.js @@ -46,7 +46,7 @@ describe('instance methods', () => { test('returns help file for app:add command', () => { const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) - command.config = {} + command.config = global.createOclifMockConfig() return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app:add', '--help']) }) diff --git a/test/commands/app/add/web-assets.test.js b/test/commands/app/add/web-assets.test.js index 5b7c5eb10..873bcf72c 100644 --- a/test/commands/app/add/web-assets.test.js +++ b/test/commands/app/add/web-assets.test.js @@ -28,6 +28,7 @@ const createAppConfig = (aioConfig = {}, appFixtureName = 'legacy-app') => { let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.getAppExtConfigs = jest.fn() command.getAppExtConfigs.mockResolvedValue(createAppConfig(command.appConfig)) command.getFullConfig = jest.fn() diff --git a/test/commands/app/build.test.js b/test/commands/app/build.test.js index 38680a6da..d6ad110dd 100644 --- a/test/commands/app/build.test.js +++ b/test/commands/app/build.test.js @@ -202,6 +202,7 @@ describe('run', () => { beforeEach(() => { spinner = ora() command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.getAppExtConfigs = jest.fn() diff --git a/test/commands/app/config/get/index.test.js b/test/commands/app/config/get/index.test.js index 087181afb..6ed8dd05a 100644 --- a/test/commands/app/config/get/index.test.js +++ b/test/commands/app/config/get/index.test.js @@ -15,7 +15,7 @@ const { Help } = require('@oclif/core') test('returns help file for app:config:get command', () => { const command = new TheCommand([]) - command.config = {} + command.config = global.createOclifMockConfig() const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app:config:get', '--help']) diff --git a/test/commands/app/config/get/log-forwarding.test.js b/test/commands/app/config/get/log-forwarding.test.js index 2003727ab..210bb49a4 100644 --- a/test/commands/app/config/get/log-forwarding.test.js +++ b/test/commands/app/config/get/log-forwarding.test.js @@ -25,6 +25,7 @@ jest.mock('../../../../../src/lib/log-forwarding', () => { let command, lf beforeEach(async () => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.appConfig = { aio: { runtime: { diff --git a/test/commands/app/config/get/log-forwarding/errors.test.js b/test/commands/app/config/get/log-forwarding/errors.test.js index 4d69d44c8..78f86386b 100644 --- a/test/commands/app/config/get/log-forwarding/errors.test.js +++ b/test/commands/app/config/get/log-forwarding/errors.test.js @@ -24,6 +24,7 @@ jest.mock('@adobe/aio-lib-runtime', () => ({ let command, logForwarding beforeEach(async () => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.appConfig = { aio: { runtime: { diff --git a/test/commands/app/config/index.test.js b/test/commands/app/config/index.test.js index 5f65c3825..955dbe3ff 100644 --- a/test/commands/app/config/index.test.js +++ b/test/commands/app/config/index.test.js @@ -15,7 +15,7 @@ const { Help } = require('@oclif/core') test('returns help file for app:config command', () => { const command = new TheCommand([]) - command.config = {} + command.config = global.createOclifMockConfig() const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app:config', '--help']) diff --git a/test/commands/app/config/set/index.test.js b/test/commands/app/config/set/index.test.js index 9dc7ffaab..a630574c5 100644 --- a/test/commands/app/config/set/index.test.js +++ b/test/commands/app/config/set/index.test.js @@ -15,7 +15,7 @@ const { Help } = require('@oclif/core') test('returns help file for app:config:set command', () => { const command = new TheCommand([]) - command.config = {} + command.config = global.createOclifMockConfig() const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app:config:set', '--help']) diff --git a/test/commands/app/config/set/log-forwarding.test.js b/test/commands/app/config/set/log-forwarding.test.js index 6f43dac9d..910f5bcd0 100644 --- a/test/commands/app/config/set/log-forwarding.test.js +++ b/test/commands/app/config/set/log-forwarding.test.js @@ -30,6 +30,7 @@ beforeEach(async () => { authHelper.setRuntimeApiHostAndAuthHandler.mockClear() command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.appConfig = { aio: { runtime: { diff --git a/test/commands/app/delete/action.test.js b/test/commands/app/delete/action.test.js index cb0b2bfe8..d84ef525f 100644 --- a/test/commands/app/delete/action.test.js +++ b/test/commands/app/delete/action.test.js @@ -85,6 +85,7 @@ beforeEach(() => { fs.removeSync.mockClear() command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.log = jest.fn() command.appConfig = cloneDeep(mockConfigData) command.buildOneExt = jest.fn() @@ -101,6 +102,7 @@ describe('command interface, flags', () => { test('--yes without ', async () => { const command = new TheCommand(['--yes']) + command.config = global.createOclifMockConfig() expect(typeof command.run).toBe('function') await expect(command.run()).rejects.toThrow(' must also be provided') }) diff --git a/test/commands/app/delete/ci.test.js b/test/commands/app/delete/ci.test.js index 67cc8b6a3..2cc052d53 100644 --- a/test/commands/app/delete/ci.test.js +++ b/test/commands/app/delete/ci.test.js @@ -23,6 +23,7 @@ beforeEach(() => { fs.removeSync.mockClear() fs.existsSync.mockClear() command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.prompt = jest.fn() }) diff --git a/test/commands/app/delete/extension.test.js b/test/commands/app/delete/extension.test.js index fe26b76d0..3f7e14765 100644 --- a/test/commands/app/delete/extension.test.js +++ b/test/commands/app/delete/extension.test.js @@ -39,6 +39,7 @@ let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.getAppExtConfigs = jest.fn() command.getAppExtConfigs.mockResolvedValue(createAppConfig(command.appConfig)) diff --git a/test/commands/app/delete/index.test.js b/test/commands/app/delete/index.test.js index bde3cc51c..88dba62b6 100644 --- a/test/commands/app/delete/index.test.js +++ b/test/commands/app/delete/index.test.js @@ -46,7 +46,7 @@ describe('instance methods', () => { test('returns help file for app:delete command', () => { const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) - command.config = {} + command.config = global.createOclifMockConfig() return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app:delete', '--help']) }) diff --git a/test/commands/app/delete/web-assets.test.js b/test/commands/app/delete/web-assets.test.js index f2ea89710..2fcbba9aa 100644 --- a/test/commands/app/delete/web-assets.test.js +++ b/test/commands/app/delete/web-assets.test.js @@ -37,6 +37,7 @@ describe('bad flags', () => { let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() }) test('unknown', async () => { command.argv = ['--wtf'] @@ -68,6 +69,7 @@ describe('bad user selections', () => { let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.getAppExtConfigs = jest.fn() command.appConfig = cloneDeep(mockConfigData) fs.removeSync.mockClear() diff --git a/test/commands/app/deploy.test.js b/test/commands/app/deploy.test.js index 64c654354..40bb0d976 100644 --- a/test/commands/app/deploy.test.js +++ b/test/commands/app/deploy.test.js @@ -215,7 +215,7 @@ beforeEach(() => { command.appConfig = cloneDeep(mockConfigData) command.appConfig.actions = { dist: 'actions' } command.appConfig.web.distProd = 'dist' - command.config = { runCommand: jest.fn(), runHook: jest.fn() } + command.config = { runCommand: jest.fn(), runHook: jest.fn().mockResolvedValue({ successes: [] }) } command.buildOneExt = jest.fn() command.getFullConfig = jest.fn().mockResolvedValue({ aio: { @@ -914,7 +914,7 @@ describe('run', () => { await expect(command.run()).rejects.toThrow('error-pre-app-deploy') expect(helpers.runInProcess).toHaveBeenCalledTimes(1) - expect(command.config.runHook).not.toHaveBeenCalled() + expect(command.config.runHook).not.toHaveBeenCalledWith(expect.stringContaining('deploy'), expect.anything()) expect(mockRuntimeLib.deployActions).not.toHaveBeenCalled() expect(mockWebLib.deployWeb).not.toHaveBeenCalled() }) @@ -935,7 +935,8 @@ describe('run', () => { appConfig: expect.any(Object), filterEntities: [] })) - expect(command.config.runHook).toHaveBeenCalledTimes(2) + // 3 calls: preparse (from parse()) + deploy-actions + deploy-static + expect(command.config.runHook).toHaveBeenCalledTimes(3) expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) }) @@ -1222,7 +1223,7 @@ describe('run', () => { }) test('does NOT fire `event` hooks when feature flag is NOT enabled', async () => { - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } command.getAppExtConfigs.mockResolvedValueOnce(createAppConfig(command.appConfig)) command.argv = [] @@ -1233,7 +1234,7 @@ describe('run', () => { }) test('DOES fire `event` hooks when feature flag IS enabled', async () => { - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } command.getAppExtConfigs.mockResolvedValueOnce(createAppConfig(command.appConfig)) await command.run() @@ -1243,10 +1244,12 @@ describe('run', () => { }) test('handles error and exits when first hook fails', async () => { - const runHook = jest.fn().mockResolvedValueOnce({ - successes: [], - failures: [{ plugin: { name: 'ifailedu' }, error: 'some error' }] - }) + const runHook = jest.fn() + .mockResolvedValueOnce({ successes: [] }) // preparse + .mockResolvedValueOnce({ + successes: [], + failures: [{ plugin: { name: 'ifailedu' }, error: 'some error' }] + }) command.config = { runHook } command.getAppExtConfigs.mockResolvedValueOnce(createAppConfig(command.appConfig)) await command.run() @@ -1258,6 +1261,7 @@ describe('run', () => { test('handles error and exits when second hook fails', async () => { const runHook = jest.fn() + .mockResolvedValueOnce({ successes: [] }) // preparse .mockResolvedValueOnce({ successes: [{ plugin: { name: 'imsuccess' }, result: 'some string' }], failures: [] @@ -1276,6 +1280,7 @@ describe('run', () => { test('handles error and exits when third hook fails', async () => { const runHook = jest.fn() + .mockResolvedValueOnce({ successes: [] }) // preparse .mockResolvedValueOnce({ successes: [{ plugin: { name: 'imsuccess' }, result: 'some string' }], failures: [] diff --git a/test/commands/app/geturl.test.js b/test/commands/app/geturl.test.js index 8312562e8..791ed8536 100644 --- a/test/commands/app/geturl.test.js +++ b/test/commands/app/geturl.test.js @@ -72,6 +72,7 @@ describe('run', () => { )) command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.appConfig = {} diff --git a/test/commands/app/index.test.js b/test/commands/app/index.test.js index a1b7a3ebf..7474f411d 100644 --- a/test/commands/app/index.test.js +++ b/test/commands/app/index.test.js @@ -46,7 +46,7 @@ describe('instance methods', () => { test('returns help file for app command', () => { const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) - command.config = {} + command.config = global.createOclifMockConfig() return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app', '--help']) }) diff --git a/test/commands/app/info.test.js b/test/commands/app/info.test.js index ea3d055d9..3e28a6d51 100644 --- a/test/commands/app/info.test.js +++ b/test/commands/app/info.test.js @@ -46,6 +46,7 @@ describe('instance methods', () => { beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() }) describe('run', () => { @@ -72,6 +73,7 @@ describe('run', () => { mockConfigLoader.load.mockResolvedValue(getMockConfig('exc', global.fakeConfig.tvm)) const command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() await command.run() @@ -89,6 +91,7 @@ describe('run', () => { ) const command = new TheCommand(['--json']) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() await command.run() @@ -105,6 +108,7 @@ describe('run', () => { getMockConfig('exc', global.fakeConfig.tvm, global.extraConfig.s3Creds('dx/excshell/1')) ) const command = new TheCommand(['--yml']) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() await command.run() @@ -121,6 +125,7 @@ describe('run', () => { getMockConfig('exc', global.fakeConfig.tvm, { 'all.dx/excshell/1.ow.auth': undefined }) ) const command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() await command.run() diff --git a/test/commands/app/init.test.js b/test/commands/app/init.test.js index aa9abd98e..5ebe41fa6 100644 --- a/test/commands/app/init.test.js +++ b/test/commands/app/init.test.js @@ -143,7 +143,7 @@ beforeEach(() => { command = new TheCommand([]) command.config = { findCommand: jest.fn(() => ({})), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } command.selectTemplates = jest.fn() @@ -327,7 +327,8 @@ describe('--no-login', () => { command.argv = ['--no-login', '--standalone-app'] await command.run() - expect(command.config.runHook).not.toHaveBeenCalled() + // runHook is only called for preparse (by oclif v4's parse()), not for any app hooks + expect(command.config.runHook).not.toHaveBeenCalledWith('telemetry', expect.anything()) expect(command.installTemplates).toHaveBeenCalledWith(installOptions) expect(command.runCodeGenerators).toHaveBeenCalledWith(['base-app', 'add-ci', 'add-vscode-config', 'application'], false, 'cwd', 'basic') expect(LibConsoleCLI.init).not.toHaveBeenCalled() diff --git a/test/commands/app/install.test.js b/test/commands/app/install.test.js index 1f76a39ee..a43f2331f 100644 --- a/test/commands/app/install.test.js +++ b/test/commands/app/install.test.js @@ -108,6 +108,7 @@ test('flags', async () => { test('unknown flag', async () => { const message = 'Nonexistent flag: --wtf\nSee more help with --help' const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip', '--wtf'] // have to specify the default arg because an oclif quirk await expect(command.run()).rejects.toEqual(expect.objectContaining({ message: expect.stringContaining(message) })) }) @@ -333,9 +334,9 @@ describe('runTests', () => { beforeEach(() => { command = new TheCommand() - command.config = { + command.config = global.createOclifMockConfig({ runCommand: jest.fn() - } + }) execa.mockImplementationOnce(() => { return Promise.resolve({ stdout: '' }) }) @@ -360,6 +361,7 @@ describe('run', () => { test('no flags, no lockfile', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip'] // since we already unit test the methods above, we mock it here @@ -386,6 +388,7 @@ describe('run', () => { test('no flags, has lockfile', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip'] // since we already unit test the methods above, we mock it here @@ -415,6 +418,7 @@ describe('run', () => { test('no flags, has lockfile, npm ci fails and falls back to npm install', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip'] const npmCIError = new Error('npm ci can only install packages when your package.json and package-lock.json are in sync') @@ -449,6 +453,7 @@ describe('run', () => { test('subcommand throws error (--verbose)', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip', '--verbose'] const errorObject = new Error('this is a subcommand error message') @@ -480,6 +485,7 @@ describe('run', () => { test('subcommand throws error (not verbose)', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip'] const errorMessage = 'this is a subcommand error message' @@ -511,6 +517,7 @@ describe('run', () => { test('flag --output', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip', '--output', 'my-dest-folder'] // since we already unit test the methods above, we mock it here @@ -538,6 +545,7 @@ describe('run', () => { test('app config validation error', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip'] // since we already unit test the methods above, we mock it here @@ -560,6 +568,7 @@ describe('run', () => { test('flag --tests', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip', '--output', 'my-dest-folder', '--tests'] // since we already unit test the methods above, we mock it here @@ -586,6 +595,7 @@ describe('run', () => { test('flag --no-tests', async () => { const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['my-app.zip', '--output', 'my-dest-folder', '--no-tests'] // since we already unit test the methods above, we mock it here diff --git a/test/commands/app/list/extension-points.test.js b/test/commands/app/list/extension-points.test.js index ca4d29208..3245ef589 100644 --- a/test/commands/app/list/extension-points.test.js +++ b/test/commands/app/list/extension-points.test.js @@ -60,6 +60,7 @@ describe('run', () => { beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.getAppExtConfigs = jest.fn() diff --git a/test/commands/app/list/extension.test.js b/test/commands/app/list/extension.test.js index f0c2d7a0d..bdb80b90c 100644 --- a/test/commands/app/list/extension.test.js +++ b/test/commands/app/list/extension.test.js @@ -55,6 +55,7 @@ describe('run', () => { let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.getAppExtConfigs = jest.fn() @@ -73,6 +74,7 @@ describe('run', () => { test('list all extension --json', async () => { command = new TheCommand(['--json']) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.getAppExtConfigs = jest.fn() @@ -91,6 +93,7 @@ describe('run', () => { test('list all extension --yml', async () => { command = new TheCommand(['--yml']) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.getAppExtConfigs = jest.fn() @@ -109,6 +112,7 @@ describe('run', () => { test('list all extension points legacy app', async () => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() command.log = jest.fn() command.getAppExtConfigs = jest.fn() diff --git a/test/commands/app/list/index.test.js b/test/commands/app/list/index.test.js index 829f6a88c..b2af5b994 100644 --- a/test/commands/app/list/index.test.js +++ b/test/commands/app/list/index.test.js @@ -45,7 +45,7 @@ describe('instance methods', () => { }) test('returns help file for app:list command', () => { - command.config = {} + command.config = global.createOclifMockConfig() const spy = jest.spyOn(Help.prototype, 'showHelp').mockReturnValue(true) return command.run().then(() => { expect(spy).toHaveBeenCalledWith(['app:list', '--help']) diff --git a/test/commands/app/logs.test.js b/test/commands/app/logs.test.js index e4323d043..9c40e76bc 100644 --- a/test/commands/app/logs.test.js +++ b/test/commands/app/logs.test.js @@ -69,6 +69,7 @@ describe('run', () => { mockFS.existsSync.mockReset() command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.appConfig = createFullConfig() command.error = jest.fn() command.log = jest.fn() diff --git a/test/commands/app/pack.test.js b/test/commands/app/pack.test.js index aa2ae28b3..2d6319f1f 100644 --- a/test/commands/app/pack.test.js +++ b/test/commands/app/pack.test.js @@ -10,16 +10,6 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -/* eslint jest/expect-expect: [ - "error", - { - "assertFunctionNames": [ - "expect" - ] - } -] -*/ - const TheCommand = require('../../../src/commands/app/pack') const BaseCommand = require('../../../src/BaseCommand') const execa = require('execa') @@ -103,6 +93,7 @@ test('flags', async () => { test('unknown flag', async () => { const message = 'Nonexistent flag: --wtf\nSee more help with --help' const command = new TheCommand() + command.config = global.createOclifMockConfig() command.argv = ['.', '--wtf'] // have to specify the default arg because an oclif quirk await expect(command.run()).rejects.toEqual(expect.objectContaining({ message: expect.stringContaining(message) })) }) @@ -137,7 +128,7 @@ test('createDeployYamlFile (1 extension)', async () => { command.config = { findCommand: jest.fn().mockReturnValue({}), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } execa.mockImplementationOnce((cmd, args) => { @@ -156,7 +147,7 @@ test('createDeployYamlFile (1 extension)', async () => { command.config = { findCommand: jest.fn().mockReturnValue(null), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } importHelper.writeFile.mockClear() @@ -175,7 +166,7 @@ test('createDeployYamlFile (1 extension), no api-mesh', async () => { command.config = { findCommand: jest.fn().mockReturnValue({}), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } execa.mockImplementationOnce((cmd, args) => { @@ -201,7 +192,7 @@ test('createDeployYamlFile (1 extension), no api-mesh, plugin throws error', asy command.config = { findCommand: jest.fn().mockReturnValue({}), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } execa.mockImplementationOnce((cmd, args) => { @@ -224,13 +215,13 @@ test('createDeployYamlFile (1 extension), api-mesh get call throws non 404 error command.config = { findCommand: jest.fn().mockReturnValue({}), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } execa.mockImplementationOnce((cmd, args) => { expect(cmd).toEqual('aio') expect(args).toEqual(['api-mesh', 'get', '--json']) - // eslint-disable-next-line no-throw-literal + return { stderr: 'Error: api-mesh service is unavailable' } @@ -247,7 +238,7 @@ test('createDeployYamlFile (coverage: standalone app, no services)', async () => command.config = { findCommand: jest.fn().mockReturnValue(null), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } await command.createDeployYamlFile(extConfig) @@ -265,7 +256,7 @@ test('createDeployYamlFile error on invalid version string', async () => { command.config = { findCommand: jest.fn().mockReturnValue(null), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } await expect(command.createDeployYamlFile(extConfig)).rejects.toThrow('Application version format must be "X.Y.Z", where X, Y, and Z are non-negative integers.') @@ -544,7 +535,7 @@ describe('run', () => { command.createDeployYamlFile = jest.fn() command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -585,7 +576,7 @@ describe('run', () => { command.createDeployYamlFile = jest.fn() command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -636,7 +627,7 @@ describe('run', () => { command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn(() => { throw errorObject }) command.error = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -685,7 +676,7 @@ describe('run', () => { command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn(() => { throw new Error(errorMessage) }) command.error = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -732,7 +723,7 @@ describe('run', () => { command.createDeployYamlFile = jest.fn() command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -791,7 +782,7 @@ describe('run', () => { command.filesToPack = jest.fn(() => ([])) command.createDeployYamlFile = jest.fn() command.zipHelper = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await expect(command.run()).rejects.toThrow('invalid fake config error') @@ -816,7 +807,7 @@ describe('run', () => { command.createDeployYamlFile = jest.fn() command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -854,7 +845,7 @@ describe('run', () => { command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() command.error = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -895,7 +886,7 @@ describe('run', () => { command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() command.error = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() @@ -936,7 +927,7 @@ describe('run', () => { command.addCodeDownloadAnnotation = jest.fn() command.zipHelper = jest.fn() command.error = jest.fn() - const runHook = jest.fn() + const runHook = jest.fn().mockResolvedValue({ successes: [] }) command.config = { runHook } await command.run() diff --git a/test/commands/app/run.test.js b/test/commands/app/run.test.js index 5f3b939da..1cf3784f8 100644 --- a/test/commands/app/run.test.js +++ b/test/commands/app/run.test.js @@ -115,7 +115,7 @@ beforeEach(() => { command.error = jest.fn() command.log = jest.fn() command.config = { - runHook: jest.fn(), + runHook: jest.fn().mockResolvedValue({ successes: [] }), findCommand: jest.fn().mockReturnValue({ load: mockFindCommandLoad }), diff --git a/test/commands/app/test.test.js b/test/commands/app/test.test.js index 45df70472..ad478530e 100644 --- a/test/commands/app/test.test.js +++ b/test/commands/app/test.test.js @@ -10,16 +10,6 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -/* eslint jest/expect-expect: [ - "error", - { - "assertFunctionNames": [ - "expect", "expectFlagError", "expectNoErrors", "expectErrors" - ] - } -] -*/ - const TheCommand = require('../../../src/commands/app/test') const BaseCommand = require('../../../src/BaseCommand') const appHelper = require('../../../src/lib/app-helper') @@ -115,6 +105,7 @@ describe('Command Prototype', () => { const expectFlagError = async (argv, message) => { const command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.argv = argv await expect(command.run()).rejects.toEqual(expect.objectContaining({ message: expect.stringContaining(message) })) } @@ -137,6 +128,7 @@ describe('run', () => { let command beforeEach(() => { command = new TheCommand([]) + command.config = global.createOclifMockConfig() command.error = jest.fn() appHelper.runScript.mockClear() diff --git a/test/commands/app/undeploy.test.js b/test/commands/app/undeploy.test.js index c7076d214..788d1f6f3 100644 --- a/test/commands/app/undeploy.test.js +++ b/test/commands/app/undeploy.test.js @@ -76,7 +76,7 @@ const getCommandConfig = () => { return { findCommand: jest.fn().mockReturnValue({}), runCommand: jest.fn(), - runHook: jest.fn() + runHook: jest.fn().mockResolvedValue({ successes: [] }) } } @@ -512,6 +512,7 @@ describe('run', () => { test('DOES fire `event` hooks when feature flag IS enabled', async () => { command.config.runHook + .mockResolvedValueOnce({ successes: [] }) // preparse .mockResolvedValue({ successes: ['ok'], failures: [] diff --git a/test/jest.setup.js b/test/jest.setup.js index 79e240814..e0ea6df35 100644 --- a/test/jest.setup.js +++ b/test/jest.setup.js @@ -47,6 +47,15 @@ jest.mock('execa') jest.mock('@adobe/aio-lib-env') +// oclif v4's parse() calls this.config.runHook('preparse', ...) which requires +// a mock config when commands are instantiated directly in tests +global.createOclifMockConfig = (overrides = {}) => ({ + runHook: jest.fn().mockResolvedValue({ successes: [] }), + runCommand: jest.fn(), + findCommand: jest.fn(), + ...overrides +}) + /* jest/no-conditional-expect See: @@ -108,7 +117,6 @@ global.fixtureYaml = (output) => { expect.extend({ toMatchFixture (received, argument) { const val = fixtureFile(argument) - // eslint-disable-next-line jest/no-standalone-expect expect(eol.auto(received)).toEqual(eol.auto(val)) return { pass: true } } @@ -117,7 +125,6 @@ expect.extend({ expect.extend({ toMatchFixtureJson (received, argument) { const val = fixtureJson(argument) - // eslint-disable-next-line jest/no-standalone-expect expect(received).toEqual(val) return { pass: true } } @@ -126,7 +133,6 @@ expect.extend({ expect.extend({ toMatchFixtureHjson (received, argument) { const val = fixtureHjson(argument) - // eslint-disable-next-line jest/no-standalone-expect expect(received).toEqual(val) return { pass: true } }