diff --git a/package.json b/package.json index bf26215..eeb76ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/artisan", - "version": "1.1.9", + "version": "1.2.0", "description": "The Athenna CLI application. Built on top of commander.", "license": "MIT", "author": "João Lenon ", diff --git a/src/Commands/Make/Test.js b/src/Commands/Make/Test.js new file mode 100644 index 0000000..5fd6256 --- /dev/null +++ b/src/Commands/Make/Test.js @@ -0,0 +1,68 @@ +/** + * @athenna/artisan + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Path } from '@secjs/utils' +import { Artisan, Command } from '#src/index' +import { TemplateHelper } from '#src/Helpers/TemplateHelper' + +export class MakeTest extends Command { + /** + * The name and signature of the console command. + */ + signature = 'make:test ' + + /** + * The console command description. + */ + description = 'Make a new test file.' + + /** + * Set additional flags in the commander instance. + * This method is executed when registering your command. + * + * @param {import('commander').Command} commander + * @return {import('commander').Command} + */ + addFlags(commander) { + return commander + .option('-u, --unit', 'Create the test inside unit folder.', false) + .option('--no-lint', 'Do not run eslint in the facade.', true) + } + + /** + * Execute the console command. + * + * @param {string} name + * @param {any} options + * @return {Promise} + */ + async handle(name, options) { + const resource = 'Test' + let subPath = Path.tests('E2E') + + if (options.unit) { + subPath = Path.tests('Unit') + } + + this.simpleLog( + `[ MAKING ${resource.toUpperCase()} ]\n`, + 'rmNewLineStart', + 'bold', + 'green', + ) + + const file = await TemplateHelper.getResourceFile(name, resource, subPath) + + this.success(`${resource} ({yellow} "${file.name}") successfully created.`) + + if (options.lint) { + await Artisan.call(`eslint:fix ${file.path} --resource ${resource}`) + } + } +} diff --git a/src/Commands/Test.js b/src/Commands/Test.js new file mode 100644 index 0000000..74c4822 --- /dev/null +++ b/src/Commands/Test.js @@ -0,0 +1,80 @@ +/** + * @athenna/artisan + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Path } from '@secjs/utils' + +import { Command } from '#src/index' + +export class Test extends Command { + /** + * The name and signature of the console command. + */ + signature = 'test' + + /** + * The console command description. + */ + description = 'Run the tests of Athenna application.' + + /** + * Set additional flags in the commander instance. + * This method is executed when registering your command. + * + * @param {import('commander').Command} commander + * @return {import('commander').Command} + */ + addFlags(commander) { + return commander + .option('--c8-args', 'Arguments for c8 cli if needed.', null) + .option('--japa-args', 'Arguments for japa cli if needed.', null) + .option('--coverage', 'Coverage the code lines using c8 library.', false) + .option('--debug', 'Enable debug mode to see more logs.', false) + .option('--unit', 'Run unit tests.', false) + .option('--e2e', 'Run e2e tests.', false) + } + + /** + * Execute the console command. + * + * @params {any} options + * @return {Promise} + */ + async handle(options) { + process.env.BOOT_LOGS = 'false' + let command = '' + + if (options.coverage) { + command = command.concat(`${Path.bin('c8')} `) + + if (options.c8Args) { + command = command.concat(options.c8Args, ' ') + } + } + + if (options.debug) { + command = command.concat(`${Path.bin('cross-env')} DEBUG=api:* && `) + } + + command = command.concat(`node ${Path.tests('main.js')} `) + + if (options.unit) { + command = command.concat('Unit ') + } + + if (options.e2e) { + command = command.concat('E2E ') + } + + if (options.japaArgs) { + command = command.concat(options.japaArgs, ' ') + } + + await this.execCommand(command) + } +} diff --git a/src/Helpers/ArtisanLoader.js b/src/Helpers/ArtisanLoader.js index fbaecd4..b8b5ffa 100644 --- a/src/Helpers/ArtisanLoader.js +++ b/src/Helpers/ArtisanLoader.js @@ -20,9 +20,11 @@ export class ArtisanLoader { */ static loadConsole() { return [ + import('#src/Commands/Test'), import('#src/Commands/List'), import('#src/Commands/Serve'), import('#src/Commands/Eslint/Fix'), + import('#src/Commands/Make/Test'), import('#src/Commands/Make/Facade'), import('#src/Commands/Make/Service'), import('#src/Commands/Make/Command'), diff --git a/src/index.d.ts b/src/index.d.ts index ffe5b1f..d07a583 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -274,9 +274,9 @@ export class ArtisanImpl { * Call any command from Artisan. * * @param {string} command - * @return Promise + * @return Promise */ - call(command: string): Promise + call(command: string): Promise /** * List all commands with description. @@ -305,6 +305,13 @@ export class ArtisanImpl { */ getCommander(): Commander + /** + * Set the commander instance of Artisan. + * + * @return {import('commander').Command} + */ + setCommander(commander: Commander): void + /** * Register the command inside commander instance. * diff --git a/src/index.js b/src/index.js index f3c546c..4764f5e 100644 --- a/src/index.js +++ b/src/index.js @@ -72,10 +72,10 @@ export class ArtisanImpl { * Call any command from Artisan. * * @param {string} command - * @return Promise + * @return Promise */ async call(command) { - await this.#commander.parseAsync([ + return this.#commander.parseAsync([ 'node', // This will be ignored by commander 'artisan', // This will be ignored by commander ...command.split(' '), @@ -146,6 +146,16 @@ export class ArtisanImpl { return this.#commander } + /** + * Set the commander instance of Artisan. + * + * @param {import('commander').Command} commander + * @return {void} + */ + setCommander(commander) { + this.#commander = commander + } + /** * Register the command inside commander instance. * diff --git a/templates/__name__Test.js.ejs b/templates/__name__Test.js.ejs new file mode 100644 index 0000000..5ac63d7 --- /dev/null +++ b/templates/__name__Test.js.ejs @@ -0,0 +1,7 @@ +import { test } from '@japa/runner' + +test.group('<%= namePascal %>Test', () => { + test('should be able to run tests', async ({ assert }) => { + assert.equal(2 + 2, 4) + }) +}) diff --git a/tests/Unit/Commands/Make/TestTest.js b/tests/Unit/Commands/Make/TestTest.js new file mode 100644 index 0000000..3172831 --- /dev/null +++ b/tests/Unit/Commands/Make/TestTest.js @@ -0,0 +1,67 @@ +/** + * @athenna/artisan + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { Config, File, Folder, Path } from '@secjs/utils' + +import { Artisan } from '#src/index' +import { Kernel } from '#tests/Stubs/app/Console/Kernel' +import { ArtisanProvider } from '#src/Providers/ArtisanProvider' +import { LoggerProvider } from '@athenna/logger/providers/LoggerProvider' + +test.group('MakeTestTest', group => { + group.each.setup(async () => { + await new Folder(Path.stubs('app')).copy(Path.app()) + await new Folder(Path.stubs('config')).copy(Path.config()) + + await new Config().safeLoad(Path.config('app.js')) + await new Config().safeLoad(Path.config('logging.js')) + + new LoggerProvider().register() + new ArtisanProvider().register() + + const kernel = new Kernel() + + await kernel.registerErrorHandler() + await kernel.registerCommands() + }) + + group.each.teardown(async () => { + await Folder.safeRemove(Path.app()) + await Folder.safeRemove(Path.config()) + await Folder.safeRemove(Path.providers()) + }) + + test('should be able to create a test file', async ({ assert }) => { + await Artisan.call('make:test FeatureTest') + + const path = Path.tests('E2E/FeatureTest.js') + + assert.isTrue(await File.exists(path)) + + await Folder.safeRemove(Path.tests('E2E')) + }).timeout(60000) + + test('should be able to create a unit test file', async ({ assert }) => { + await Artisan.call('make:test --unit UnitTest') + + const path = Path.tests('Unit/UnitTest.js') + + assert.isTrue(await File.exists(path)) + + await File.safeRemove(path) + }).timeout(60000) + + test('should throw an error when the file already exists', async ({ assert }) => { + await Artisan.call('make:test TestTest') + await Artisan.call('make:test TestTest') + + await Folder.safeRemove(Path.tests('E2E')) + }).timeout(60000) +})