Skip to content

Commit

Permalink
feat: added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kielingraphael committed Oct 17, 2023
1 parent 9dd42e4 commit 3a4a00c
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 14 deletions.
1 change: 1 addition & 0 deletions examples/empty.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = () => {}
12 changes: 12 additions & 0 deletions examples/notvalid.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = () => {
return [
{
titles: 'parent 1',
tasks: [
{
titles: 'child 1',
},
],
},
]
}
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"enquirer": "^2.4.1",
"esbuild": "^0.19.4",
"execa": "^8.0.1",
"joi": "^17.11.0",
"listr2": "^7.0.1",
"np": "^8.0.4",
"tsx": "^3.13.0",
Expand Down
88 changes: 88 additions & 0 deletions src/builder.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { Builder } from './builder'
import { arrayOfTasksSchema, taskSchema } from './validator'

describe('Builder', () => {
let builder

beforeEach(() => {
builder = new Builder()
})

describe('task', () => {
it('adds a task definition to the list of tasks', () => {
builder.task('Task 1', () => {})
builder.task('Task 2', () => {})

const { tasks } = builder.build()

expect(tasks).toHaveLength(2)
expect(tasks[0].title).toBe('Task 1')
expect(tasks[1].title).toBe('Task 2')
})

it('should be validated using the schema using the builder', () => {
builder.task('Task 1', () => {})
builder.task('Task 2', () => {})

const { tasks } = builder.build()
const { error } = arrayOfTasksSchema.validate(tasks)
expect(error).toBe(undefined)
})
})

describe('subTask', () => {
it('sets the current task to the newly added task definition', () => {
builder.task('Task 1', () => {})
builder.subTask('Subtask 1', () => {})
builder.task('Task 2', () => {})

const { tasks } = builder.build()

expect(tasks[0].title).toBe('Task 1')
expect(tasks[0].tasks).toHaveLength(1)
expect(tasks[0].tasks[0].title).toBe('Subtask 1')

expect(tasks[1].title).toBe('Task 2')
expect(tasks[1].tasks).toHaveLength(0)
})

it('adds a subtask definition to the current task', () => {
builder.task('Task 1', () => {})
builder.subTask('Subtask 1', () => {})
builder.subTask('Subtask 2', () => {})

const { tasks } = builder.build()

expect(tasks[0].tasks).toHaveLength(2)
expect(tasks[0].tasks[0].title).toBe('Subtask 1')
expect(tasks[0].tasks[1].title).toBe('Subtask 2')
})

it('does not add a subtask definition if there is no current task', () => {
builder.subTask('Subtask 1', () => {})

const { tasks } = builder.build()

expect(tasks).toHaveLength(0)
})
})

describe('setConcurrent', () => {
it('sets the concurrent option to true', () => {
builder.setConcurrent()

const { options } = builder.build()

expect(options.concurrent).toBe(true)
})

it('sets the concurrent option to false', () => {
builder.setConcurrent(false)

const { options } = builder.build()

expect(options.concurrent).toBe(false)
})
})
})
39 changes: 25 additions & 14 deletions src/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@ import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer'
import { Listr } from 'listr2'
import { $ } from 'execa'
import { Builder } from './builder'
import { arrayOfTasksSchema } from './validator'

function buildContext(ctx, task) {
ctx.task = task
ctx.bash = $
ctx.prompt = task.prompt(ListrEnquirerPromptAdapter).run.bind(task)
ctx.delay = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
ctx.checkEnv = (envName) => {
if (!process.env[envName]) {
throw new Error(`env ${envName} is not set`)
}
const newCtx = {
...ctx,
task,
bash: $,
prompt: task.prompt(ListrEnquirerPromptAdapter).run.bind(task),
delay: (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
},
checkEnv: (envName) => {
if (!process.env[envName]) {
throw new Error(`env ${envName} is not set`)
}
},
}

return ctx
return newCtx
}

function mapper(def) {
Expand Down Expand Up @@ -48,12 +52,19 @@ export async function startFile(filePath) {

const options = Array.isArray(data) ? {} : data?.options
const definitions = Array.isArray(data) ? data : data.tasks
const tasksFromDefinitions = definitions.map(mapper)

const { error } = arrayOfTasksSchema.validate(definitions, {
abortEarly: false,
})
if (error) throw error

const tasksFromDefinitions = definitions.map(mapper)
const tasks = new Listr(tasksFromDefinitions, {
concurrent: options?.concurrent ?? false,
exitOnError: false,
})

await tasks.run()

return true
}
31 changes: 31 additions & 0 deletions src/reader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { it, describe, expect, vi } from 'vitest'
import { startFile } from './reader'
import path from 'node:path'

vi.mock('listr2')

describe('startFile', () => {
it('should throw an error if no data is returned from the config file', async () => {
const filePath = path.resolve(__dirname, '../examples/empty.config.js')
await expect(startFile(filePath)).rejects.toThrow(
'No data returned from config file'
)
})

it('should not throw an error for a raw mode', async () => {
const filePath = path.resolve(__dirname, '../examples/raw.config.js')
await expect(startFile(filePath)).resolves.toBeTruthy()
})

it('should not throw an error for a builder mode', async () => {
const filePath = path.resolve(__dirname, '../examples/builder.config.js')
await expect(startFile(filePath)).resolves.toBeTruthy()
})

it('should throw an error for an invalid schema', async () => {
const filePath = path.resolve(__dirname, '../examples/notvalid.config.js')
await expect(startFile(filePath)).rejects.toThrowError(
'"[0].title" is required. "[0].tasks[0].title" is required. "[0].tasks[0].titles" is not allowed. "[0].titles" is not allowed'
)
})
})
11 changes: 11 additions & 0 deletions src/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Joi from 'joi'

export const taskSchema = Joi.object({
title: Joi.string().required(),
task: Joi.function(),
tasks: Joi.array().items(Joi.link('#task')),
}).id('task')

export const arrayOfTasksSchema = Joi.array()
.items(taskSchema)
.id('arrayOfTasks')
48 changes: 48 additions & 0 deletions src/validator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, it, expect } from 'vitest'
import { taskSchema } from './validator.js'

describe('taskSchema', () => {
it('should validate a valid task object', () => {
const validTask = {
title: 'My Task',
task: () => {},
tasks: [
{
title: 'Subtask 1',
task: () => {},
tasks: [
{
title: 'Subtask 1.1',
task: () => {},
},
],
},
],
}

const { error } = taskSchema.validate(validTask)
expect(error).toBe(undefined)
})

it('should not validate an invalid task object', () => {
const invalidTask = {
title: 'My Task',
task: () => {},
tasks: [
{
title: 'Subtask 1',
task: () => {},
tasks: [
{
title: 'Subtask 1.1',
task: 'not a function',
},
],
},
],
}

const { error } = taskSchema.validate(invalidTask)
expect(error).not.toBe(undefined)
})
})

0 comments on commit 3a4a00c

Please sign in to comment.