Skip to content

Commit

Permalink
feat: make:factory command + --factory flag to make:model (#837)
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 authored May 7, 2022
1 parent f8e0c8c commit bd22c96
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 0 deletions.
97 changes: 97 additions & 0 deletions commands/MakeFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* @adonisjs/assembler
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { join } from 'path'
import { args, BaseCommand, flags } from '@adonisjs/core/build/standalone'

/**
* Command to make a new Factory
*/
export default class MakeFactory extends BaseCommand {
public static commandName = 'make:factory'
public static description = 'Make a new factory'

/**
* Name of the model to be used in the factory
*/
@args.string({ description: 'The name of the model' })
public model: string

/**
* Import path to the model used in the factory
*/
@flags.string({ description: 'The path to the model' })
public modelPath: string

@flags.boolean({
description: 'Create the factory with the exact name as provided',
alias: 'e',
})
public exact: boolean

/**
* Generate model import path used in the factory
*/
private generateModelImportPath() {
let base = this.application.rcFile.namespaces.models || 'App/Models'
if (!base.endsWith('/')) {
base += '/'
}

let importPath = this.model
if (this.modelPath) {
importPath = this.modelPath
} else if (importPath.endsWith('Factory')) {
importPath = importPath.replace(/Factory$/, '')
}

if (importPath.startsWith(base)) {
return importPath
}

return base + importPath
}

/**
* Path to the factories directory
*/
protected getDestinationPath() {
const base = this.application.rcFile.directories.database || 'database'
return join(base, 'factories')
}

/**
* Passed down to the stub template
*/
protected templateData() {
return {
model: this.model,
modelImportPath: this.generateModelImportPath(),
toModelName: () => {
return function (model: string, render: any) {
return render(model).split('/').pop()
}
},
}
}

public async run() {
const stub = join(__dirname, '..', 'templates', 'factory.txt')

this.generator
.addFile(this.model, { pattern: 'pascalcase', form: 'singular', suffix: 'Factory' })
.stub(stub)
.useMustache()
.destinationDir(this.getDestinationPath())
.appRoot(this.application.appRoot)
.apply(this.templateData())

await this.generator.run()
}
}
24 changes: 24 additions & 0 deletions commands/MakeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ export default class MakeModel extends BaseCommand {
})
public controller: boolean

/**
* Defines if we generate the factory for the model.
*/
@flags.boolean({
name: 'factory',
alias: 'f',
description: 'Generate a factory for the model',
})
public factory: boolean

/**
* Run migrations
*/
Expand All @@ -69,6 +79,19 @@ export default class MakeModel extends BaseCommand {
this.error = makeController.error
}

/**
* Make factory
*/
private async runMakeFactory() {
if (!this.factory) {
return
}

const makeFactory = await this.kernel.exec('make:factory', [this.name])
this.exitCode = makeFactory.exitCode
this.error = makeFactory.error
}

/**
* Execute command
*/
Expand All @@ -91,5 +114,6 @@ export default class MakeModel extends BaseCommand {
}

await this.runMakeController()
await this.runMakeFactory()
}
}
1 change: 1 addition & 0 deletions commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default [
'@adonisjs/lucid/build/commands/MakeModel',
'@adonisjs/lucid/build/commands/MakeMigration',
'@adonisjs/lucid/build/commands/MakeSeeder',
'@adonisjs/lucid/build/commands/MakeFactory',
'@adonisjs/lucid/build/commands/DbWipe',
'@adonisjs/lucid/build/commands/Migration/Run',
'@adonisjs/lucid/build/commands/Migration/Rollback',
Expand Down
8 changes: 8 additions & 0 deletions templates/factory.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {{#toModelName}}{{{ model }}}{{/toModelName}} from '{{{ modelImportPath }}}'
import Factory from '@ioc:Adonis/Lucid/Factory'

export default Factory.define({{#toModelName}}{{{ model }}}{{/toModelName}}, ({ faker }) => {
return {
//
}
})
8 changes: 8 additions & 0 deletions test-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,3 +667,11 @@ export async function cleanupReplicaDb(connection: Knex) {
export function sleep(timeout: number) {
return new Promise((resolve) => setTimeout(resolve, timeout))
}

export function replaceFactoryBindings(source: string, model: string, importPath: string) {
return toNewlineArray(
source
.replace('{{{ modelImportPath }}}', importPath)
.replace(/{{#toModelName}}{{{ model }}}{{\/toModelName}}/gi, model)
)
}
96 changes: 96 additions & 0 deletions test/commands/make-factory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* @adonisjs/assembler
*
* (c) AdonisJS
*
* 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 { join } from 'path'
import { Kernel } from '@adonisjs/ace'
import { Filesystem } from '@poppinss/dev-utils'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import MakeFactory from '../../commands/MakeFactory'
import {
fs,
toNewlineArray,
setupApplication,
replaceFactoryBindings,
} from '../../test-helpers/index'

const templates = new Filesystem(join(__dirname, '..', '..', 'templates'))

test.group('Make Factory', async (group) => {
let app: ApplicationContract

group.each.setup(async () => {
app = await setupApplication()
return () => fs.cleanup()
})

test('generate a factory for {model}')
.with([
{
argument: 'User',
model: 'User',
finalDestination: 'UserFactory.ts',
finalImportPath: 'App/Models/User',
},
{
argument: 'Blog/Post',
model: 'Post',
finalDestination: 'Blog/PostFactory.ts',
finalImportPath: 'App/Models/Blog/Post',
},
])
.run(async ({ assert }, set) => {
const factory = new MakeFactory(app, new Kernel(app).mockConsoleOutput())

factory.model = set.argument
await factory.run()

const UserFactory = await fs.get(`database/factories/${set.finalDestination}`)
const factoryTemplate = await templates.get('factory.txt')

assert.deepEqual(
toNewlineArray(UserFactory),
replaceFactoryBindings(factoryTemplate, set.model, set.finalImportPath)
)
})

test('generate a factory with custom import path')
.with([
{
argument: 'User',
model: 'User',
modelPath: 'Test/User',
finalDestination: 'UserFactory.ts',
finalImportPath: 'App/Models/Test/User',
},
{
argument: 'Client',
model: 'Client',
modelPath: 'App/Models/Test/B/Client',
finalDestination: 'ClientFactory.ts',
finalImportPath: 'App/Models/Test/B/Client',
},
])
.run(async ({ assert }, set) => {
const factory = new MakeFactory(app, new Kernel(app).mockConsoleOutput())

factory.model = set.model
factory.modelPath = set.modelPath

await factory.run()

const UserFactory = await fs.get(`database/factories/${set.finalDestination}`)
const factoryTemplate = await templates.get('factory.txt')

assert.deepEqual(
toNewlineArray(UserFactory),
replaceFactoryBindings(factoryTemplate, set.model, set.finalImportPath)
)
})
})

0 comments on commit bd22c96

Please sign in to comment.